Skip to content

Commit c9cd394

Browse files
Marco Britokaiw
authored andcommitted
diffgrid: Allow resizing file and folder comparison panes (bgo#576934)
To individual adjust the width of each pane in a file or folder comparison is something that can be pratical good to have. But by using GtkGrid for layout its view creates a limitation of not allowing pane resizing. Here is an implementation of a GtkGrid based custom widget, by overriding its childrens size allocation and adding in the row at the top of each LinkMap a drag handle, the size and position can easy be set.
1 parent f064296 commit c9cd394

5 files changed

Lines changed: 321 additions & 54 deletions

File tree

data/ui/dirdiff.ui

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
<property name="visible">True</property>
103103
<property name="can_focus">False</property>
104104
<child>
105-
<object class="GtkGrid" id="grid">
105+
<object class="DiffGrid" id="grid">
106106
<property name="visible">True</property>
107107
<property name="can_focus">False</property>
108108
<child>
@@ -329,58 +329,6 @@
329329
<property name="height">1</property>
330330
</packing>
331331
</child>
332-
<child>
333-
<object class="GtkLabel" id="label5">
334-
<property name="visible">True</property>
335-
<property name="can_focus">False</property>
336-
<property name="xalign">0</property>
337-
</object>
338-
<packing>
339-
<property name="left_attach">0</property>
340-
<property name="top_attach">0</property>
341-
<property name="width">1</property>
342-
<property name="height">1</property>
343-
</packing>
344-
</child>
345-
<child>
346-
<object class="GtkLabel" id="label6">
347-
<property name="visible">True</property>
348-
<property name="can_focus">False</property>
349-
<property name="xalign">0</property>
350-
</object>
351-
<packing>
352-
<property name="left_attach">2</property>
353-
<property name="top_attach">0</property>
354-
<property name="width">1</property>
355-
<property name="height">1</property>
356-
</packing>
357-
</child>
358-
<child>
359-
<object class="GtkLabel" id="label7">
360-
<property name="visible">True</property>
361-
<property name="can_focus">False</property>
362-
<property name="xalign">0</property>
363-
</object>
364-
<packing>
365-
<property name="left_attach">4</property>
366-
<property name="top_attach">0</property>
367-
<property name="width">1</property>
368-
<property name="height">1</property>
369-
</packing>
370-
</child>
371-
<child>
372-
<object class="GtkLabel" id="label4">
373-
<property name="visible">True</property>
374-
<property name="can_focus">False</property>
375-
<property name="xalign">0</property>
376-
</object>
377-
<packing>
378-
<property name="left_attach">6</property>
379-
<property name="top_attach">0</property>
380-
<property name="width">1</property>
381-
<property name="height">1</property>
382-
</packing>
383-
</child>
384332
<child>
385333
<object class="GtkVBox" id="vbox0">
386334
<property name="visible">True</property>

data/ui/filediff.ui

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@
111111
<signal name="key-press-event" handler="on_key_event" swapped="no"/>
112112
<signal name="key-release-event" handler="on_key_event" swapped="no"/>
113113
<child>
114-
<object class="GtkGrid" id="grid">
114+
<object class="DiffGrid" id="grid">
115115
<property name="visible">True</property>
116116
<property name="can_focus">False</property>
117117
<property name="margin_top">0</property>

meld/diffgrid.py

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
# Copyright (C) 2014 Marco Brito <bcaza@null.net>
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 2 of the License, or (at
6+
# your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful, but
9+
# WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
# General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
from gi.repository import Gtk
17+
from gi.repository import Gdk
18+
19+
20+
class DiffGrid(Gtk.Grid):
21+
__gtype_name__ = "DiffGrid"
22+
23+
def __init__(self):
24+
Gtk.Grid.__init__(self)
25+
self._in_drag = False
26+
self._drag_pos = -1
27+
self._drag_handle = None
28+
self._handle1 = HandleWindow()
29+
self._handle2 = HandleWindow()
30+
31+
def do_realize(self):
32+
Gtk.Grid.do_realize(self)
33+
self._handle1.realize(self)
34+
self._handle2.realize(self)
35+
36+
def do_unrealize(self):
37+
self._handle1.unrealize()
38+
self._handle2.unrealize()
39+
Gtk.Grid.do_unrealize(self)
40+
41+
def do_map(self):
42+
Gtk.Grid.do_map(self)
43+
drag = self.get_child_at(2, 0)
44+
if drag and drag.get_visible():
45+
self._handle1.set_visible(True)
46+
47+
drag = self.get_child_at(4, 0)
48+
if drag and drag.get_visible():
49+
self._handle2.set_visible(True)
50+
51+
def do_unmap(self):
52+
self._handle1.set_visible(False)
53+
self._handle2.set_visible(False)
54+
Gtk.Grid.do_unmap(self)
55+
56+
def _handle_set_prelight(self, window, flag):
57+
if hasattr(window, "handle"):
58+
window.handle.set_prelight(flag)
59+
return True
60+
return False
61+
62+
def do_enter_notify_event(self, event):
63+
return self._handle_set_prelight(event.window, True)
64+
65+
def do_leave_notify_event(self, event):
66+
if not self._in_drag:
67+
return self._handle_set_prelight(event.window, False)
68+
return False
69+
70+
def do_button_press_event(self, event):
71+
if event.button & Gdk.BUTTON_PRIMARY:
72+
self._drag_pos = event.x
73+
self._in_drag = True
74+
return True
75+
return False
76+
77+
def do_button_release_event(self, event):
78+
if event.button & Gdk.BUTTON_PRIMARY:
79+
self._in_drag = False
80+
return True
81+
return False
82+
83+
def do_motion_notify_event(self, event):
84+
if event.state & Gdk.ModifierType.BUTTON1_MASK:
85+
if hasattr(event.window, "handle"):
86+
x, y = event.window.get_position()
87+
pos = round(x + event.x - self._drag_pos)
88+
event.window.handle.set_position(pos)
89+
self._drag_handle = event.window.handle
90+
self.queue_resize_no_redraw()
91+
return True
92+
return False
93+
94+
def _calculate_positions(self, xmin, xmax, wlink1, wlink2,
95+
wpane1, wpane2, wpane3):
96+
wremain = max(0, xmax - xmin - wlink1 - wlink2)
97+
pos1 = self._handle1.get_position(wremain, xmin)
98+
pos2 = self._handle2.get_position(wremain, xmin + wlink1)
99+
100+
if not self._drag_handle:
101+
npanes = 0
102+
if wpane1 > 0:
103+
npanes += 1
104+
if wpane2 > 0:
105+
npanes += 1
106+
if wpane3 > 0:
107+
npanes += 1
108+
wpane = float(wremain) / max(1, npanes)
109+
if wpane1 > 0:
110+
wpane1 = wpane
111+
if wpane2 > 0:
112+
wpane2 = wpane
113+
if wpane3 > 0:
114+
wpane3 = wpane
115+
116+
xminlink1 = xmin + wpane1
117+
xmaxlink2 = xmax - wpane3 - wlink2
118+
wlinkpane = wlink1 + wpane2
119+
120+
if wpane1 == 0:
121+
pos1 = xminlink1
122+
if wpane3 == 0:
123+
pos2 = xmaxlink2
124+
if wpane2 == 0:
125+
if wpane3 == 0:
126+
pos1 = pos2 - wlink2
127+
else:
128+
pos2 = pos1 + wlink1
129+
130+
if self._drag_handle == self._handle2:
131+
xminlink2 = xminlink1 + wlinkpane
132+
pos2 = min(max(xminlink2, pos2), xmaxlink2)
133+
xmaxlink1 = pos2 - wlinkpane
134+
pos1 = min(max(xminlink1, pos1), xmaxlink1)
135+
else:
136+
xmaxlink1 = xmaxlink2 - wlinkpane
137+
pos1 = min(max(xminlink1, pos1), xmaxlink1)
138+
xminlink2 = pos1 + wlinkpane
139+
pos2 = min(max(xminlink2, pos2), xmaxlink2)
140+
141+
self._handle1.set_position(pos1)
142+
self._handle2.set_position(pos2)
143+
return int(round(pos1)), int(round(pos2))
144+
145+
def do_size_allocate(self, allocation):
146+
self.set_allocation(allocation)
147+
wcols, hrows = self._get_min_sizes()
148+
yrows = [allocation.y,
149+
allocation.y + hrows[0],
150+
allocation.y + allocation.height]
151+
152+
wmap1, wpane1, wlink1, wpane2, wlink2, wpane3, wmap2 = wcols
153+
xmin = allocation.x + wmap1
154+
xmax = allocation.x + allocation.width - wmap2
155+
pos1, pos2 = self._calculate_positions(xmin, xmax,
156+
wlink1, wlink2,
157+
wpane1, wpane2, wpane3)
158+
xmap1 = allocation.x
159+
xpane1 = xmin
160+
wpane1 = pos1 - xpane1
161+
xlink1 = pos1
162+
xpane2 = pos1 + wlink1
163+
wpane2 = pos2 - xpane2
164+
xlink2 = pos2
165+
xpane3 = pos2 + wlink2
166+
wpane3 = xmax - xpane3
167+
xmap2 = xmax
168+
self._set_column_allocation(0, xmap1, wmap1, yrows)
169+
self._set_column_allocation(1, xpane1, wpane1, yrows)
170+
self._set_column_allocation(2, xlink1, wlink1, yrows)
171+
self._set_column_allocation(3, xpane2, wpane2, yrows)
172+
self._set_column_allocation(4, xlink2, wlink2, yrows)
173+
self._set_column_allocation(5, xpane3, wpane3, yrows)
174+
self._set_column_allocation(6, xmap2, wmap2, yrows)
175+
176+
if self.get_realized():
177+
mapped = self.get_mapped()
178+
ydrag = yrows[0]
179+
hdrag = yrows[1] - yrows[0]
180+
self._handle1.set_visible(mapped and wlink1 > 0)
181+
self._handle1.move_resize(xlink1, ydrag, wlink1, hdrag)
182+
self._handle2.set_visible(mapped and wlink2 > 0)
183+
self._handle2.move_resize(xlink2, ydrag, wlink2, hdrag)
184+
185+
def _set_column_allocation(self, col, x, width, rows):
186+
for row in range(0, 2):
187+
child = self.get_child_at(col, row)
188+
if child and child.get_visible():
189+
alloc = self.get_allocation()
190+
alloc.x = x
191+
alloc.y = rows[row]
192+
alloc.width = width
193+
alloc.height = rows[row+1] - alloc.y
194+
child.size_allocate(alloc)
195+
196+
def _get_min_sizes(self):
197+
hrows = [0] * 2
198+
wcols = [0] * 7
199+
for row in range(0, 2):
200+
for col in range(0, 7):
201+
child = self.get_child_at(col, row)
202+
if child and child.get_visible():
203+
msize, nsize = child.get_preferred_size()
204+
wcols[col] = max(wcols[col], msize.width, nsize.width)
205+
hrows[row] = max(hrows[row], msize.height, nsize.height)
206+
return wcols, hrows
207+
208+
def do_draw(self, context):
209+
Gtk.Grid.do_draw(self, context)
210+
self._handle1.draw(context)
211+
self._handle2.draw(context)
212+
213+
214+
class HandleWindow():
215+
def __init__(self):
216+
self._widget = None
217+
self._window = None
218+
self._area_x = -1
219+
self._area_y = -1
220+
self._area_width = 1
221+
self._area_height = 1
222+
self._prelit = False
223+
self._pos = 0.0
224+
self._transform = (0, 0)
225+
226+
def get_position(self, width, xtrans):
227+
self._transform = (width, xtrans)
228+
return float(self._pos * width) + xtrans
229+
230+
def set_position(self, pos):
231+
width, xtrans = self._transform
232+
self._pos = float(pos - xtrans) / width
233+
234+
def realize(self, widget):
235+
attr = Gdk.WindowAttr()
236+
attr.window_type = Gdk.WindowType.CHILD
237+
attr.x = self._area_x
238+
attr.y = self._area_y
239+
attr.width = self._area_width
240+
attr.height = self._area_height
241+
attr.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT
242+
attr.event_mask = (widget.get_events() |
243+
Gdk.EventMask.BUTTON_PRESS_MASK |
244+
Gdk.EventMask.BUTTON_RELEASE_MASK |
245+
Gdk.EventMask.ENTER_NOTIFY_MASK |
246+
Gdk.EventMask.LEAVE_NOTIFY_MASK |
247+
Gdk.EventMask.POINTER_MOTION_MASK)
248+
attr.cursor = Gdk.Cursor.new_for_display(widget.get_display(),
249+
Gdk.CursorType.
250+
SB_H_DOUBLE_ARROW)
251+
attr_mask = (Gdk.WindowAttributesType.X |
252+
Gdk.WindowAttributesType.Y |
253+
Gdk.WindowAttributesType.CURSOR)
254+
255+
parent = widget.get_parent_window()
256+
self._window = Gdk.Window(parent, attr, attr_mask)
257+
self._window.handle = self
258+
self._widget = widget
259+
self._widget.register_window(self._window)
260+
261+
def unrealize(self):
262+
self._widget.unregister_window(self._window)
263+
264+
def set_visible(self, visible):
265+
if visible:
266+
self._window.show()
267+
else:
268+
self._window.hide()
269+
270+
def move_resize(self, x, y, width, height):
271+
self._window.move_resize(x, y, width, height)
272+
self._area_x = x
273+
self._area_y = y
274+
self._area_width = width
275+
self._area_height = height
276+
277+
def set_prelight(self, flag):
278+
self._prelit = flag
279+
self._widget.queue_draw_area(self._area_x, self._area_y,
280+
self._area_width, self._area_height)
281+
282+
def draw(self, cairocontext):
283+
alloc = self._widget.get_allocation()
284+
padding = 5
285+
x = self._area_x - alloc.x + padding
286+
y = self._area_y - alloc.y + padding
287+
width = max(0, self._area_width - 2 * padding)
288+
height = max(0, self._area_height - 2 * padding)
289+
290+
if width == 0 or height == 0:
291+
return
292+
293+
stylecontext = self._widget.get_style_context()
294+
state = self._widget.get_state_flags()
295+
if self._widget.is_focus():
296+
state |= Gtk.StateFlags.SELECTED
297+
if self._prelit:
298+
state |= Gtk.StateFlags.PRELIGHT
299+
300+
if Gtk.cairo_should_draw_window(cairocontext, self._window):
301+
stylecontext.save()
302+
stylecontext.set_state(state)
303+
stylecontext.add_class(Gtk.STYLE_CLASS_PANE_SEPARATOR)
304+
color = stylecontext.get_background_color(state)
305+
if color.alpha > 0.0:
306+
Gtk.render_handle(stylecontext, cairocontext,
307+
x, y, width, height)
308+
else:
309+
xcenter = x + width / 2.0
310+
Gtk.render_line(stylecontext, cairocontext,
311+
xcenter, y, xcenter, y + height)
312+
stylecontext.restore()

0 commit comments

Comments
 (0)