# Copyright (C) 2014 Marco Brito # # 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, see . from gi.repository import GObject from gi.repository import Gtk from gi.repository import Gdk class DiffGrid(Gtk.Grid): __gtype_name__ = "DiffGrid" def __init__(self): Gtk.Grid.__init__(self) self._in_drag = False self._drag_pos = -1 self._drag_handle = None self._handle1 = HandleWindow() self._handle2 = HandleWindow() def do_realize(self): Gtk.Grid.do_realize(self) self._handle1.realize(self) self._handle2.realize(self) def do_unrealize(self): self._handle1.unrealize() self._handle2.unrealize() Gtk.Grid.do_unrealize(self) def do_map(self): Gtk.Grid.do_map(self) drag = self.get_child_at(2, 0) if drag and drag.get_visible(): self._handle1.set_visible(True) drag = self.get_child_at(4, 0) if drag and drag.get_visible(): self._handle2.set_visible(True) def do_unmap(self): self._handle1.set_visible(False) self._handle2.set_visible(False) Gtk.Grid.do_unmap(self) def _handle_set_prelight(self, window, flag): if hasattr(window, "handle"): window.handle.set_prelight(flag) return True return False def do_enter_notify_event(self, event): return self._handle_set_prelight(event.window, True) def do_leave_notify_event(self, event): if not self._in_drag: return self._handle_set_prelight(event.window, False) return False def do_button_press_event(self, event): if event.button & Gdk.BUTTON_PRIMARY: self._drag_pos = event.x self._in_drag = True return True return False def do_button_release_event(self, event): if event.button & Gdk.BUTTON_PRIMARY: self._in_drag = False return True return False def do_motion_notify_event(self, event): if event.state & Gdk.ModifierType.BUTTON1_MASK: if hasattr(event.window, "handle"): x, y = event.window.get_position() pos = round(x + event.x - self._drag_pos) event.window.handle.set_position(pos) self._drag_handle = event.window.handle self.queue_resize_no_redraw() return True return False def _calculate_positions(self, xmin, xmax, wlink1, wlink2, wpane1, wpane2, wpane3): wremain = max(0, xmax - xmin - wlink1 - wlink2) pos1 = self._handle1.get_position(wremain, xmin) pos2 = self._handle2.get_position(wremain, xmin + wlink1) if not self._drag_handle: npanes = 0 if wpane1 > 0: npanes += 1 if wpane2 > 0: npanes += 1 if wpane3 > 0: npanes += 1 wpane = float(wremain) / max(1, npanes) if wpane1 > 0: wpane1 = wpane if wpane2 > 0: wpane2 = wpane if wpane3 > 0: wpane3 = wpane xminlink1 = xmin + wpane1 xmaxlink2 = xmax - wpane3 - wlink2 wlinkpane = wlink1 + wpane2 if wpane1 == 0: pos1 = xminlink1 if wpane3 == 0: pos2 = xmaxlink2 if wpane2 == 0: if wpane3 == 0: pos1 = pos2 - wlink2 else: pos2 = pos1 + wlink1 if self._drag_handle == self._handle2: xminlink2 = xminlink1 + wlinkpane pos2 = min(max(xminlink2, pos2), xmaxlink2) xmaxlink1 = pos2 - wlinkpane pos1 = min(max(xminlink1, pos1), xmaxlink1) else: xmaxlink1 = xmaxlink2 - wlinkpane pos1 = min(max(xminlink1, pos1), xmaxlink1) xminlink2 = pos1 + wlinkpane pos2 = min(max(xminlink2, pos2), xmaxlink2) self._handle1.set_position(pos1) self._handle2.set_position(pos2) return int(round(pos1)), int(round(pos2)) def do_size_allocate(self, allocation): self.set_allocation(allocation) wcols, hrows = self._get_min_sizes() yrows = [allocation.y, allocation.y + hrows[0], # Roughly equivalent to hard-coding row 1 to expand=True allocation.y + (allocation.height - hrows[2]), allocation.y + allocation.height] wmap1, wpane1, wlink1, wpane2, wlink2, wpane3, wmap2 = wcols xmin = allocation.x + wmap1 xmax = allocation.x + allocation.width - wmap2 pos1, pos2 = self._calculate_positions(xmin, xmax, wlink1, wlink2, wpane1, wpane2, wpane3) wpane1 = pos1 - (allocation.x + wmap1) wpane2 = pos2 - (pos1 + wlink1) wpane3 = xmax - (pos2 + wlink2) wcols = ( allocation.x, wmap1, wpane1, wlink1, wpane2, wlink2, wpane3, wmap2) columns = [sum(wcols[:i + 1]) for i in range(len(wcols))] def get_child_prop_int(child, name): prop = GObject.Value(int) self.child_get_property(child, name, prop) return prop.get_int() def get_child_attach(child): attach = [ get_child_prop_int(child, 'left-attach'), get_child_prop_int(child, 'top-attach'), get_child_prop_int(child, 'width'), get_child_prop_int(child, 'height'), ] return attach def child_allocate(child): if not child.get_visible(): return attach = get_child_attach(child) left, top, width, height = attach # This is a copy, and we have to do this because there's no Python # access to Gtk.Allocation. child_alloc = self.get_allocation() child_alloc.x = columns[left] child_alloc.y = yrows[top] child_alloc.width = columns[left + width] - columns[left] child_alloc.height = yrows[top + height] - yrows[top] if self.get_direction() == Gtk.TextDirection.RTL: child_alloc.x = ( allocation.x + allocation.width - (child_alloc.x - allocation.x) - child_alloc.width) child.size_allocate(child_alloc) for child in self.get_children(): child_allocate(child) if self.get_realized(): mapped = self.get_mapped() ydrag = yrows[0] hdrag = yrows[1] - yrows[0] self._handle1.set_visible(mapped and wlink1 > 0) self._handle1.move_resize(pos1, ydrag, wlink1, hdrag) self._handle2.set_visible(mapped and wlink2 > 0) self._handle2.move_resize(pos2, ydrag, wlink2, hdrag) def _get_min_sizes(self): hrows = [0] * 3 wcols = [0] * 7 for row in range(0, 3): for col in range(0, 7): child = self.get_child_at(col, row) if child and child.get_visible(): msize, nsize = child.get_preferred_size() # Ignore spanning columns in width calculations; we should # do this properly, but it's difficult. spanning = GObject.Value(int) self.child_get_property(child, 'width', spanning) spanning = spanning.get_int() if spanning == 1: wcols[col] = max(wcols[col], msize.width, nsize.width) hrows[row] = max(hrows[row], msize.height, nsize.height) return wcols, hrows def do_draw(self, context): Gtk.Grid.do_draw(self, context) self._handle1.draw(context) self._handle2.draw(context) class HandleWindow(): def __init__(self): self._widget = None self._window = None self._area_x = -1 self._area_y = -1 self._area_width = 1 self._area_height = 1 self._prelit = False self._pos = 0.0 self._transform = (0, 0) def get_position(self, width, xtrans): self._transform = (width, xtrans) return float(self._pos * width) + xtrans def set_position(self, pos): width, xtrans = self._transform self._pos = float(pos - xtrans) / width def realize(self, widget): attr = Gdk.WindowAttr() attr.window_type = Gdk.WindowType.CHILD attr.x = self._area_x attr.y = self._area_y attr.width = self._area_width attr.height = self._area_height attr.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT attr.event_mask = (widget.get_events() | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.POINTER_MOTION_MASK) attr.cursor = Gdk.Cursor.new_for_display(widget.get_display(), Gdk.CursorType. SB_H_DOUBLE_ARROW) attr_mask = (Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y | Gdk.WindowAttributesType.CURSOR) parent = widget.get_parent_window() self._window = Gdk.Window(parent, attr, attr_mask) self._window.handle = self self._widget = widget self._widget.register_window(self._window) def unrealize(self): self._widget.unregister_window(self._window) def set_visible(self, visible): if visible: self._window.show() else: self._window.hide() def move_resize(self, x, y, width, height): self._window.move_resize(x, y, width, height) self._area_x = x self._area_y = y self._area_width = width self._area_height = height def set_prelight(self, flag): self._prelit = flag self._widget.queue_draw_area(self._area_x, self._area_y, self._area_width, self._area_height) def draw(self, cairocontext): alloc = self._widget.get_allocation() padding = 5 x = self._area_x - alloc.x + padding y = self._area_y - alloc.y + padding width = max(0, self._area_width - 2 * padding) height = max(0, self._area_height - 2 * padding) if width == 0 or height == 0: return stylecontext = self._widget.get_style_context() state = self._widget.get_state_flags() if self._widget.is_focus(): state |= Gtk.StateFlags.SELECTED if self._prelit: state |= Gtk.StateFlags.PRELIGHT if Gtk.cairo_should_draw_window(cairocontext, self._window): stylecontext.save() stylecontext.set_state(state) stylecontext.add_class(Gtk.STYLE_CLASS_PANE_SEPARATOR) color = stylecontext.get_background_color(state) if color.alpha > 0.0: Gtk.render_handle(stylecontext, cairocontext, x, y, width, height) else: xcenter = x + width / 2.0 Gtk.render_line(stylecontext, cairocontext, xcenter, y, xcenter, y + height) stylecontext.restore()