716 lines
24 KiB
Python
Executable file
716 lines
24 KiB
Python
Executable file
# This is a port of um-crop-area.c from GNOME’s 'Cheese' application, see
|
||
# https://gitlab.gnome.org/GNOME/cheese/-/blob/3.34.0/libcheese/um-crop-area.c
|
||
#
|
||
# This file is part of Gajim.
|
||
#
|
||
# Gajim 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; version 3 only.
|
||
#
|
||
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
import os
|
||
import logging
|
||
from enum import IntEnum
|
||
from enum import unique
|
||
|
||
from gi.repository import Gdk
|
||
from gi.repository import GdkPixbuf
|
||
from gi.repository import GLib
|
||
from gi.repository import Gtk
|
||
import cairo
|
||
|
||
from gajim.common.const import AvatarSize
|
||
from gajim.common.i18n import _
|
||
from gajim.common.helpers import get_file_path_from_dnd_dropped_uri
|
||
|
||
from .util import scale_with_ratio
|
||
|
||
log = logging.getLogger('gajim.gui.avatar_selector')
|
||
|
||
|
||
@unique
|
||
class Loc(IntEnum):
|
||
OUTSIDE = 0
|
||
INSIDE = 1
|
||
TOP = 2
|
||
TOP_LEFT = 3
|
||
TOP_RIGHT = 4
|
||
BOTTOM = 5
|
||
BOTTOM_LEFT = 6
|
||
BOTTOM_RIGHT = 7
|
||
LEFT = 8
|
||
RIGHT = 9
|
||
|
||
|
||
@unique
|
||
class Range(IntEnum):
|
||
BELOW = 0
|
||
LOWER = 1
|
||
BETWEEN = 2
|
||
UPPER = 3
|
||
ABOVE = 4
|
||
|
||
|
||
class AvatarSelector(Gtk.Box):
|
||
def __init__(self):
|
||
Gtk.Box.__init__(self)
|
||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||
self.get_style_context().add_class('padding-18')
|
||
|
||
uri_entry = Gtk.TargetEntry.new(
|
||
'text/uri-list', Gtk.TargetFlags.OTHER_APP, 80)
|
||
dst_targets = Gtk.TargetList.new([uri_entry])
|
||
|
||
self.drag_dest_set(
|
||
Gtk.DestDefaults.ALL,
|
||
[uri_entry],
|
||
Gdk.DragAction.COPY | Gdk.DragAction.MOVE)
|
||
self.drag_dest_set_target_list(dst_targets)
|
||
self.connect('drag-data-received', self._on_drag_data_received)
|
||
|
||
self._crop_area = CropArea()
|
||
self._crop_area.set_vexpand(True)
|
||
self.add(self._crop_area)
|
||
|
||
self._helper_label = Gtk.Label(
|
||
label=_('Select a picture or drop it here'))
|
||
self._helper_label.get_style_context().add_class('bold')
|
||
self._helper_label.get_style_context().add_class('dim-label')
|
||
self._helper_label.set_vexpand(True)
|
||
self._helper_label.set_no_show_all(True)
|
||
self._helper_label.show()
|
||
self.add(self._helper_label)
|
||
|
||
self.show_all()
|
||
|
||
def prepare_crop_area(self, path):
|
||
pixbuf = self._get_pixbuf_from_path(path)
|
||
self._crop_area.set_pixbuf(pixbuf)
|
||
self._helper_label.hide()
|
||
self._crop_area.show()
|
||
|
||
def _on_drag_data_received(self, _widget, _context, _x_coord, _y_coord,
|
||
selection, target_type, _timestamp):
|
||
if not selection.get_data():
|
||
return
|
||
|
||
if target_type == 80:
|
||
uri_split = selection.get_uris() # Might be more than one
|
||
path = get_file_path_from_dnd_dropped_uri(uri_split[0])
|
||
if not os.path.isfile(path):
|
||
return
|
||
self.prepare_crop_area(path)
|
||
|
||
@staticmethod
|
||
def _get_pixbuf_from_path(path):
|
||
try:
|
||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
|
||
return pixbuf
|
||
except GLib.Error as err:
|
||
log.error('Unable to load file %s: %s', path, str(err))
|
||
return None
|
||
|
||
def get_prepared(self):
|
||
return bool(self._crop_area.get_pixbuf())
|
||
|
||
@staticmethod
|
||
def _scale_for_publish(pixbuf):
|
||
width = pixbuf.get_width()
|
||
height = pixbuf.get_height()
|
||
if width > AvatarSize.PUBLISH or height > AvatarSize.PUBLISH:
|
||
# Scale only down, never up
|
||
width, height = scale_with_ratio(AvatarSize.PUBLISH, width, height)
|
||
pixbuf = pixbuf.scale_simple(width,
|
||
height,
|
||
GdkPixbuf.InterpType.BILINEAR)
|
||
return pixbuf, width, height
|
||
|
||
def get_avatar_surface(self):
|
||
pixbuf = self._crop_area.get_pixbuf()
|
||
if pixbuf is None:
|
||
return None
|
||
scaled, width, height = self._scale_for_publish(pixbuf)
|
||
|
||
return Gdk.cairo_surface_create_from_pixbuf(
|
||
scaled, self.get_scale_factor()), width, height
|
||
|
||
def get_avatar_bytes(self):
|
||
pixbuf = self._crop_area.get_pixbuf()
|
||
if pixbuf is None:
|
||
return False, None, 0, 0
|
||
scaled, width, height = self._scale_for_publish(pixbuf)
|
||
|
||
success, data = scaled.save_to_bufferv('png', [], [])
|
||
return success, data, width, height
|
||
|
||
|
||
class CropArea(Gtk.DrawingArea):
|
||
def __init__(self):
|
||
Gtk.DrawingArea.__init__(self)
|
||
self.set_no_show_all(True)
|
||
self.add_events(
|
||
Gdk.EventMask.BUTTON_PRESS_MASK |
|
||
Gdk.EventMask.BUTTON_RELEASE_MASK |
|
||
Gdk.EventMask.POINTER_MOTION_MASK)
|
||
|
||
self._image = Gdk.Rectangle()
|
||
self._crop = Gdk.Rectangle()
|
||
self._pixbuf = None
|
||
self._browse_pixbuf = None
|
||
self._color_shifted_pixbuf = None
|
||
self._current_cursor = None
|
||
|
||
self._scale = float(0.0)
|
||
self._image.x = 0
|
||
self._image.y = 0
|
||
self._image.width = 0
|
||
self._image.height = 0
|
||
self._active_region = Loc.OUTSIDE
|
||
self._last_press_x = -1
|
||
self._last_press_y = -1
|
||
self._base_width = 10
|
||
self._base_height = 10
|
||
self._aspect = float(1.0)
|
||
|
||
self.set_size_request(self._base_width, self._base_height)
|
||
|
||
self.connect('draw', self._on_draw)
|
||
self.connect('button-press-event', self._on_button_press)
|
||
self.connect('button-release-event', self._on_button_release)
|
||
self.connect('motion-notify-event', self._on_motion_notify)
|
||
|
||
def set_min_size(self, width, height):
|
||
self._base_width = width
|
||
self._base_height = height
|
||
self.set_size_request(self._base_width, self._base_height)
|
||
|
||
if self._aspect > 0:
|
||
self._aspect = self._base_width / self._base_height
|
||
|
||
def set_contstrain_aspect(self, constrain):
|
||
if constrain:
|
||
self._aspect = self._base_width / self._base_height
|
||
else:
|
||
self._aspect = -1
|
||
|
||
def set_pixbuf(self, pixbuf):
|
||
if pixbuf:
|
||
self._browse_pixbuf = pixbuf
|
||
width = pixbuf.get_width()
|
||
height = pixbuf.get_height()
|
||
else:
|
||
width = 0
|
||
height = 0
|
||
|
||
self._crop.width = 2 * self._base_width
|
||
self._crop.height = 2 * self._base_height
|
||
self._crop.x = abs((width - self._crop.width) / 2)
|
||
self._crop.y = abs((height - self._crop.height) / 2)
|
||
|
||
self._scale = 0.0
|
||
self._image.x = 0
|
||
self._image.y = 0
|
||
self._image.width = 0
|
||
self._image.height = 0
|
||
|
||
self.queue_draw()
|
||
|
||
def get_pixbuf(self):
|
||
if self._browse_pixbuf is None:
|
||
return None
|
||
|
||
width = self._browse_pixbuf.get_width()
|
||
height = self._browse_pixbuf.get_height()
|
||
width = min(self._crop.width, width - self._crop.x)
|
||
height = min(self._crop.height, height - self._crop.y)
|
||
|
||
if width <= 0 or height <= 0:
|
||
return None
|
||
|
||
return GdkPixbuf.Pixbuf.new_subpixbuf(
|
||
self._browse_pixbuf, self._crop.x, self._crop.y, width, height)
|
||
|
||
def _on_draw(self, _widget, context):
|
||
if self._browse_pixbuf is None:
|
||
return False
|
||
|
||
self._update_pixbufs()
|
||
|
||
width = self._pixbuf.get_width()
|
||
height = self._pixbuf.get_height()
|
||
crop = self._crop_to_widget()
|
||
|
||
ix = self._image.x
|
||
iy = self._image.y
|
||
|
||
Gdk.cairo_set_source_pixbuf(
|
||
context, self._color_shifted_pixbuf, ix, iy)
|
||
context.rectangle(
|
||
ix,
|
||
iy,
|
||
width,
|
||
crop.y - iy)
|
||
context.rectangle(
|
||
ix,
|
||
crop.y,
|
||
crop.x - ix,
|
||
crop.height)
|
||
context.rectangle(
|
||
crop.x + crop.width,
|
||
crop.y,
|
||
width - crop.width - (crop.x - ix),
|
||
crop.height)
|
||
context.rectangle(
|
||
ix,
|
||
crop.y + crop.height,
|
||
width,
|
||
height - crop.height - (crop.y - iy))
|
||
context.fill()
|
||
|
||
Gdk.cairo_set_source_pixbuf(context, self._pixbuf, ix, iy)
|
||
context.rectangle(crop.x, crop.y, crop.width, crop.height)
|
||
context.fill()
|
||
|
||
if self._active_region != Loc.OUTSIDE:
|
||
context.set_source_rgb(150, 150, 150)
|
||
context.set_line_width(1.0)
|
||
x1 = crop.x + crop.width / 3.0
|
||
x2 = crop.x + 2 * crop.width / 3.0
|
||
y1 = crop.y + crop.height / 3.0
|
||
y2 = crop.y + 2 * crop.height / 3.0
|
||
|
||
context.move_to(x1 + 0.5, crop.y)
|
||
context.line_to(x1 + 0.5, crop.y + crop.height)
|
||
|
||
context.move_to(x2 + 0.5, crop.y)
|
||
context.line_to(x2 + 0.5, crop.y + crop.height)
|
||
|
||
context.move_to(crop.x, y1 + 0.5)
|
||
context.line_to(crop.x + crop.width, y1 + 0.5)
|
||
|
||
context.move_to(crop.x, y2 + 0.5)
|
||
context.line_to(crop.x + crop.width, y2 + 0.5)
|
||
context.stroke()
|
||
|
||
context.set_source_rgb(1, 1, 1)
|
||
context.set_line_width(1.0)
|
||
|
||
context.rectangle(
|
||
crop.x + 0.5,
|
||
crop.y + 0.5,
|
||
crop.width - 1.0,
|
||
crop.height - 1.0)
|
||
context.stroke()
|
||
|
||
context.set_source_rgb(1, 1, 1)
|
||
context.set_line_width(2.0)
|
||
context.rectangle(
|
||
crop.x + 2.0,
|
||
crop.y + 2.0,
|
||
crop.width - 4.0,
|
||
crop.height - 4.0)
|
||
context.stroke()
|
||
|
||
return False
|
||
|
||
def _on_button_press(self, _widget, event):
|
||
if self._browse_pixbuf is None:
|
||
return False
|
||
|
||
crop = self._crop_to_widget()
|
||
|
||
self._last_press_x = (event.x - self._image.x) / self._scale
|
||
self._last_press_y = (event.y - self._image.y) / self._scale
|
||
self._active_region = self._find_location(crop, event.x, event.y)
|
||
|
||
self.queue_draw_area(
|
||
crop.x - 1, crop.y - 1, crop.width + 2, crop.height + 2)
|
||
|
||
return False
|
||
|
||
def _on_button_release(self, _widget, _event):
|
||
if self._browse_pixbuf is None:
|
||
return False
|
||
|
||
crop = self._crop_to_widget()
|
||
self._last_press_x = -1
|
||
self._last_press_y = -1
|
||
self._active_region = Loc.OUTSIDE
|
||
|
||
self.queue_draw_area(
|
||
crop.x - 1, crop.y - 1, crop.width + 2, crop.height + 2)
|
||
|
||
return False
|
||
|
||
def _on_motion_notify(self, _widget, event):
|
||
# pylint: disable=too-many-boolean-expressions
|
||
# pylint: disable=too-many-branches
|
||
# pylint: disable=too-many-statements
|
||
if self._browse_pixbuf is None:
|
||
return False
|
||
|
||
self._update_cursor(event.x, event.y)
|
||
|
||
damage = self._crop_to_widget()
|
||
self.queue_draw_area(
|
||
damage.x - 1, damage.y - 1, damage.width + 2, damage.height + 2)
|
||
|
||
pb_width = self._browse_pixbuf.get_width()
|
||
pb_height = self._browse_pixbuf.get_height()
|
||
|
||
x_coord = int((event.x - self._image.x) / self._scale)
|
||
y_coord = int((event.y - self._image.y) / self._scale)
|
||
|
||
delta_x = int(x_coord - self._last_press_x)
|
||
delta_y = int(y_coord - self._last_press_y)
|
||
self._last_press_x = x_coord
|
||
self._last_press_y = y_coord
|
||
|
||
left = int(self._crop.x)
|
||
right = int(self._crop.x + self._crop.width - 1)
|
||
top = int(self._crop.y)
|
||
bottom = int(self._crop.y + self._crop.height - 1)
|
||
|
||
center_x = float((left + right) / 2.0)
|
||
center_y = float((top + bottom) / 2.0)
|
||
|
||
if self._active_region == Loc.INSIDE:
|
||
width = right - left + 1
|
||
height = bottom - top + 1
|
||
|
||
left += delta_x
|
||
right += delta_x
|
||
top += delta_y
|
||
bottom += delta_y
|
||
|
||
if left < 0:
|
||
left = 0
|
||
if top < 0:
|
||
top = 0
|
||
if right > pb_width:
|
||
right = pb_width
|
||
if bottom > pb_height:
|
||
bottom = pb_height
|
||
|
||
adj_width = int(right - left + 1)
|
||
adj_height = int(bottom - top + 1)
|
||
if adj_width != width:
|
||
if delta_x < 0:
|
||
right = left + width - 1
|
||
else:
|
||
left = right - width + 1
|
||
|
||
if adj_height != height:
|
||
if delta_y < 0:
|
||
bottom = top + height - 1
|
||
else:
|
||
top = bottom - height + 1
|
||
|
||
elif self._active_region == Loc.TOP_LEFT:
|
||
if self._aspect < 0:
|
||
top = y_coord
|
||
left = x_coord
|
||
elif y_coord < self._eval_radial_line(
|
||
center_x, center_y, left, top, x_coord):
|
||
top = y_coord
|
||
new_width = float((bottom - top) * self._aspect)
|
||
left = right - new_width
|
||
else:
|
||
left = x_coord
|
||
new_height = float((right - left) / self._aspect)
|
||
top = bottom - new_height
|
||
|
||
elif self._active_region == Loc.TOP:
|
||
top = y_coord
|
||
if self._aspect > 0:
|
||
new_width = float((bottom - top) * self._aspect)
|
||
right = left + new_width
|
||
|
||
elif self._active_region == Loc.TOP_RIGHT:
|
||
if self._aspect < 0:
|
||
top = y_coord
|
||
right = x_coord
|
||
elif y_coord < self._eval_radial_line(
|
||
center_x, center_y, right, top, x_coord):
|
||
top = y_coord
|
||
new_width = float((bottom - top) * self._aspect)
|
||
right = left + new_width
|
||
else:
|
||
right = x_coord
|
||
new_height = float((right - left) / self._aspect)
|
||
top = bottom - new_height
|
||
|
||
elif self._active_region == Loc.LEFT:
|
||
left = x_coord
|
||
if self._aspect > 0:
|
||
new_height = float((right - left) / self._aspect)
|
||
bottom = top + new_height
|
||
|
||
elif self._active_region == Loc.BOTTOM_LEFT:
|
||
if self._aspect < 0:
|
||
bottom = y_coord
|
||
left = x_coord
|
||
elif y_coord < self._eval_radial_line(
|
||
center_x, center_y, left, bottom, x_coord):
|
||
left = x_coord
|
||
new_height = float((right - left) / self._aspect)
|
||
bottom = top + new_height
|
||
else:
|
||
bottom = y_coord
|
||
new_width = float((bottom - top) * self._aspect)
|
||
left = right - new_width
|
||
|
||
elif self._active_region == Loc.RIGHT:
|
||
right = x_coord
|
||
if self._aspect > 0:
|
||
new_height = float((right - left) / self._aspect)
|
||
bottom = top + new_height
|
||
|
||
elif self._active_region == Loc.BOTTOM_RIGHT:
|
||
if self._aspect < 0:
|
||
bottom = y_coord
|
||
right = x_coord
|
||
elif y_coord < self._eval_radial_line(
|
||
center_x, center_y, right, bottom, x_coord):
|
||
right = x_coord
|
||
new_height = float((right - left) / self._aspect)
|
||
bottom = top + new_height
|
||
else:
|
||
bottom = y_coord
|
||
new_width = float((bottom - top) * self._aspect)
|
||
right = left + new_width
|
||
|
||
elif self._active_region == Loc.BOTTOM:
|
||
bottom = y_coord
|
||
if self._aspect > 0:
|
||
new_width = float((bottom - top) * self._aspect)
|
||
right = left + new_width
|
||
else:
|
||
return False
|
||
|
||
min_width = int(self._base_width / self._scale)
|
||
min_height = int(self._base_height / self._scale)
|
||
|
||
width = right - left + 1
|
||
height = bottom - top + 1
|
||
if self._aspect < 0:
|
||
if left < 0:
|
||
left = 0
|
||
if top < 0:
|
||
top = 0
|
||
if right > pb_width:
|
||
right = pb_width
|
||
if bottom > pb_height:
|
||
bottom = pb_height
|
||
|
||
width = right - left + 1
|
||
height = bottom - top + 1
|
||
|
||
if self._active_region in (
|
||
Loc.LEFT, Loc.TOP_LEFT, Loc.BOTTOM_LEFT):
|
||
if width < min_width:
|
||
left = right - min_width
|
||
elif self._active_region in (
|
||
Loc.RIGHT, Loc.TOP_RIGHT, Loc.BOTTOM_RIGHT):
|
||
if width < min_width:
|
||
right = left + min_width
|
||
|
||
if self._active_region in (
|
||
Loc.TOP, Loc.TOP_LEFT, Loc.TOP_RIGHT):
|
||
if height < min_height:
|
||
top = bottom - min_height
|
||
elif self._active_region in (
|
||
Loc.BOTTOM, Loc.BOTTOM_LEFT, Loc.BOTTOM_RIGHT):
|
||
if height < min_height:
|
||
bottom = top + min_height
|
||
|
||
else:
|
||
if (left < 0 or top < 0 or
|
||
right > pb_width or bottom > pb_height or
|
||
width < min_width or height < min_height):
|
||
left = self._crop.x
|
||
right = self._crop.x + self._crop.width - 1
|
||
top = self._crop.y
|
||
bottom = self._crop.y + self._crop.height - 1
|
||
|
||
self._crop.x = left
|
||
self._crop.y = top
|
||
self._crop.width = right - left + 1
|
||
self._crop.height = bottom - top + 1
|
||
|
||
damage = self._crop_to_widget()
|
||
self.queue_draw_area(
|
||
damage.x - 1, damage.y - 1, damage.width + 2, damage.height + 2)
|
||
|
||
return False
|
||
|
||
def _update_pixbufs(self):
|
||
allocation = self.get_allocation()
|
||
width = self._browse_pixbuf.get_width()
|
||
height = self._browse_pixbuf.get_height()
|
||
|
||
scale = allocation.height / float(height)
|
||
if scale * width > allocation.width:
|
||
scale = allocation.width / float(width)
|
||
|
||
dest_width = width * scale
|
||
dest_height = height * scale
|
||
|
||
if (self._pixbuf is None or
|
||
self._pixbuf.get_width != allocation.width or
|
||
self._pixbuf.get_height != allocation.height):
|
||
|
||
self._pixbuf = GdkPixbuf.Pixbuf.new(
|
||
GdkPixbuf.Colorspace.RGB,
|
||
self._browse_pixbuf.get_has_alpha(),
|
||
8,
|
||
dest_width,
|
||
dest_height)
|
||
self._pixbuf.fill(0x0)
|
||
|
||
self._browse_pixbuf.scale(
|
||
self._pixbuf,
|
||
0,
|
||
0,
|
||
dest_width,
|
||
dest_height,
|
||
0,
|
||
0,
|
||
scale,
|
||
scale,
|
||
GdkPixbuf.InterpType.BILINEAR)
|
||
|
||
self._generate_color_shifted_pixbuf()
|
||
|
||
if self._scale == 0.0:
|
||
scale_to_80 = float(min(
|
||
(self._pixbuf.get_width() * 0.8 / self._base_width),
|
||
(self._pixbuf.get_height() * 0.8 / self._base_height)))
|
||
scale_to_image = float(min(
|
||
(dest_width / self._base_width),
|
||
(dest_height / self._base_height)))
|
||
crop_scale = float(min(scale_to_80, scale_to_image))
|
||
|
||
self._crop.width = crop_scale * self._base_width / scale
|
||
self._crop.height = crop_scale * self._base_height / scale
|
||
self._crop.x = (
|
||
self._browse_pixbuf.get_width() - self._crop.width) / 2
|
||
self._crop.y = (
|
||
self._browse_pixbuf.get_height() - self._crop.height) / 2
|
||
|
||
self._scale = scale
|
||
self._image.x = (allocation.width - dest_width) / 2
|
||
self._image.y = (allocation.height - dest_height) / 2
|
||
self._image.width = dest_width
|
||
self._image.height = dest_height
|
||
|
||
def _crop_to_widget(self):
|
||
crop = Gdk.Rectangle()
|
||
crop.x = self._image.x + self._crop.x * self._scale
|
||
crop.y = self._image.y + self._crop.y * self._scale
|
||
crop.width = self._crop.width * self._scale
|
||
crop.height = self._crop.height * self._scale
|
||
return crop
|
||
|
||
def _update_cursor(self, x_coord, y_coord):
|
||
region = self._active_region
|
||
if self._active_region == Loc.OUTSIDE:
|
||
crop = self._crop_to_widget()
|
||
region = self._find_location(crop, x_coord, y_coord)
|
||
|
||
if region == Loc.TOP_LEFT:
|
||
cursor_type = Gdk.CursorType.TOP_LEFT_CORNER
|
||
elif region == Loc.TOP:
|
||
cursor_type = Gdk.CursorType.TOP_SIDE
|
||
elif region == Loc.TOP_RIGHT:
|
||
cursor_type = Gdk.CursorType.TOP_RIGHT_CORNER
|
||
elif region == Loc.LEFT:
|
||
cursor_type = Gdk.CursorType.LEFT_SIDE
|
||
elif region == Loc.INSIDE:
|
||
cursor_type = Gdk.CursorType.FLEUR
|
||
elif region == Loc.RIGHT:
|
||
cursor_type = Gdk.CursorType.RIGHT_SIDE
|
||
elif region == Loc.BOTTOM_LEFT:
|
||
cursor_type = Gdk.CursorType.BOTTOM_LEFT_CORNER
|
||
elif region == Loc.BOTTOM:
|
||
cursor_type = Gdk.CursorType.BOTTOM_SIDE
|
||
elif region == Loc.BOTTOM_RIGHT:
|
||
cursor_type = Gdk.CursorType.BOTTOM_RIGHT_CORNER
|
||
else: # Loc.OUTSIDE
|
||
cursor_type = Gdk.CursorType.LEFT_PTR
|
||
|
||
if cursor_type is not self._current_cursor:
|
||
cursor = Gdk.Cursor.new_for_display(
|
||
Gdk.Display.get_default(),
|
||
cursor_type)
|
||
self.get_window().set_cursor(cursor)
|
||
self._current_cursor = cursor_type
|
||
|
||
@staticmethod
|
||
def _eval_radial_line(center_x, center_y, bounds_x, bounds_y, user_x):
|
||
slope_y = float(bounds_y - center_y)
|
||
slope_x = bounds_x - center_x
|
||
if slope_y == 0 or slope_x == 0:
|
||
# Prevent division by zero
|
||
return 0
|
||
|
||
decision_slope = slope_y / slope_x
|
||
decision_intercept = - float(decision_slope * bounds_x)
|
||
return int(decision_slope * user_x + decision_intercept)
|
||
|
||
def _find_location(self, rect, x_coord, y_coord):
|
||
# pylint: disable=line-too-long
|
||
location = [
|
||
[Loc.OUTSIDE, Loc.OUTSIDE, Loc.OUTSIDE, Loc.OUTSIDE, Loc.OUTSIDE],
|
||
[Loc.OUTSIDE, Loc.TOP_LEFT, Loc.TOP, Loc.TOP_RIGHT, Loc.OUTSIDE],
|
||
[Loc.OUTSIDE, Loc.LEFT, Loc.INSIDE, Loc.RIGHT, Loc.OUTSIDE],
|
||
[Loc.OUTSIDE, Loc.BOTTOM_LEFT, Loc.BOTTOM, Loc.BOTTOM_RIGHT, Loc.OUTSIDE],
|
||
[Loc.OUTSIDE, Loc.OUTSIDE, Loc.OUTSIDE, Loc.OUTSIDE, Loc.OUTSIDE],
|
||
]
|
||
# pylint: enable=line-too-long
|
||
|
||
x_range = self._find_range(x_coord, rect.x, rect.x + rect.width)
|
||
y_range = self._find_range(y_coord, rect.y, rect.y + rect.height)
|
||
|
||
return location[y_range][x_range]
|
||
|
||
@staticmethod
|
||
def _find_range(coord, min_v, max_v):
|
||
tolerance = 12
|
||
if coord < min_v - tolerance:
|
||
return Range.BELOW
|
||
if coord <= min_v + tolerance:
|
||
return Range.LOWER
|
||
if coord < max_v - tolerance:
|
||
return Range.BETWEEN
|
||
if coord <= max_v + tolerance:
|
||
return Range.UPPER
|
||
return Range.ABOVE
|
||
|
||
def _generate_color_shifted_pixbuf(self):
|
||
# pylint: disable=no-member
|
||
surface = cairo.ImageSurface(
|
||
cairo.Format.ARGB32,
|
||
self._pixbuf.get_width(),
|
||
self._pixbuf.get_height())
|
||
context = cairo.Context(surface)
|
||
# pylint: enable=no-member
|
||
|
||
Gdk.cairo_set_source_pixbuf(context, self._pixbuf, 0, 0)
|
||
context.paint()
|
||
|
||
context.rectangle(0, 0, 1, 1)
|
||
context.set_source_rgba(0, 0, 0, 0.5)
|
||
context.paint()
|
||
|
||
surface = context.get_target()
|
||
self._color_shifted_pixbuf = Gdk.pixbuf_get_from_surface(
|
||
surface, 0, 0, surface.get_width(), surface.get_height())
|