# 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 3 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 . """An X11 window which can be moved and resized""" import gi import cairo import logging gi.require_version("Gtk", "3.0") # pylint: disable=wrong-import-position from gi.repository import Gtk, Gdk # nopep8 log = logging.getLogger(__name__) class DraggableWindow(Gtk.Window): """An X11 window which can be moved and resized""" def __init__(self, pos_x=0, pos_y=0, width=300, height=300, message="Message", settings=None): Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP) self.pos_x = pos_x self.pos_y = pos_y self.width = max(100, width) self.height = max(100, height) self.settings = settings self.message = message self.set_size_request(50, 50) self.connect('draw', self.dodraw) self.connect('motion-notify-event', self.drag) self.connect('button-press-event', self.button_press) self.connect('button-release-event', self.button_release) self.compositing = False # Set RGBA screen = self.get_screen() visual = screen.get_rgba_visual() if visual: # Set the visual even if we can't use it right now self.set_visual(visual) if screen.is_composited(): self.compositing = True self.set_app_paintable(True) self.monitor = 0 self.drag_type = None self.drag_x = 0 self.drag_y = 0 self.force_location() self.show_all() def force_location(self): """ Move the window to previously given co-ords. Also double check sanity on layer & decorations """ self.set_decorated(False) self.set_keep_above(True) self.move(self.pos_x, self.pos_y) self.resize(self.width, self.height) def drag(self, _w, event): """Called by GTK while mouse is moving over window. Used to resize and move""" if event.state & Gdk.ModifierType.BUTTON1_MASK: if self.drag_type == 1: # Center is move self.pos_x = event.x_root - self.drag_x self.pos_y = event.y_root - self.drag_y self.force_location() elif self.drag_type == 2: # Right edge self.width += event.x - self.drag_x self.drag_x = event.x self.force_location() elif self.drag_type == 3: # Bottom edge self.height += event.y - self.drag_y self.drag_y = event.y self.force_location() else: # Bottom Right self.width += event.x - self.drag_x self.height += event.y - self.drag_y self.drag_x = event.x self.drag_y = event.y self.force_location() def button_press(self, _widget, event): """Called when a mouse button is pressed on this window""" (width, height) = self.get_size() if not self.drag_type: self.drag_type = 1 # Where in the window did we press? if event.y > height - 32: self.drag_type += 2 if event.x > width - 32: self.drag_type += 1 self.drag_x = event.x self.drag_y = event.y def button_release(self, _w, _event): """Called when a mouse button is released""" self.drag_type = None def dodraw(self, _widget, context): """Draw our window.""" context.set_source_rgba(1.0, 1.0, 0.0, 0.7) # Don't layer drawing over each other, always replace context.set_operator(cairo.OPERATOR_SOURCE) context.paint() context.set_operator(cairo.OPERATOR_OVER) # Get size of window (window_width, window_height) = self.get_size() # Draw text context.set_source_rgba(0.0, 0.0, 0.0, 1.0) _xb, _yb, text_width, text_height, _dx, _dy = context.text_extents( self.message) context.move_to(window_width / 2 - text_width / 2, window_height / 2 - text_height / 2) context.show_text(self.message) # Draw resizing edges context.set_source_rgba(0.0, 0.0, 1.0, 0.5) context.rectangle(window_width - 32, 0, 32, window_height) context.fill() context.rectangle(0, window_height - 32, window_width, 32) context.fill() def get_coords(self): """Return window position and size""" scale = self.get_scale_factor() (pos_x, pos_y) = self.get_position() (width, height) = self.get_size() pos_x = pos_x / scale pos_y = pos_y / scale return (pos_x, pos_y, width, height)