1# abstract_transform_tool.py
2#
3# Copyright 2018-2021 Romain F. T.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18import cairo
19from gi.repository import Gtk, Gdk, GdkPixbuf
20
21from .abstract_tool import AbstractAbstractTool
22
23class AbstractCanvasTool(AbstractAbstractTool):
24	__gtype_name__ = 'AbstractCanvasTool'
25
26	def __init__(self, tool_id, label, icon_name, window, **kwargs):
27		super().__init__(tool_id, label, icon_name, window)
28		self.menu_id = 1
29		self.centered_box = None
30		self.needed_width_for_long = 0
31		self.accept_selection = True
32		self.apply_to_selection = False
33
34	def on_tool_selected(self, *args):
35		super().on_tool_selected()
36		self.apply_to_selection = self.selection_is_active()
37
38	def update_actions_state(self, *args):
39		# Changing this in on_tool_selected would be overridden by image.py
40		super().update_actions_state()
41
42		self.set_action_sensitivity('selection_delete', False)
43		self.set_action_sensitivity('selection_cut', False)
44		self.set_action_sensitivity('unselect', False)
45		self.set_action_sensitivity('select_all', False)
46
47	def give_back_control(self, preserve_selection):
48		if not preserve_selection and self.selection_is_active():
49			self.on_apply_temp_pixbuf_tool_operation()
50			self.window.get_selection_tool().unselect_and_apply()
51		super().give_back_control(preserve_selection)
52
53	############################################################################
54
55	def build_and_do_op(self):
56		operation = self.build_operation()
57		self.do_tool_operation(operation)
58
59	def temp_preview(self, is_selection, local_dx, local_dy):
60		"""Part of the previewing methods shared by all transform tools."""
61		pixbuf = self.get_image().temp_pixbuf
62		if is_selection:
63			cairo_context = self.get_context()
64			cairo_context.set_source_surface(self.get_surface(), 0, 0)
65			cairo_context.paint()
66			x = self.get_selection().selection_x + local_dx
67			y = self.get_selection().selection_y + local_dy
68			Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, x, y)
69			cairo_context.paint()
70		else:
71			cairo_context = self.get_context()
72			# widget_surface = cairo.ImageSurface(cairo.Format.ARGB32, w, h)
73			# cairo_context = cairo.Context(widget_surface)
74			# TODO concerning the scale(/crop)/rotate/skew preview ^
75			cairo_context.set_operator(cairo.Operator.SOURCE)
76			Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, 0, 0)
77			cairo_context.paint()
78			cairo_context.set_operator(cairo.Operator.OVER)
79		self.get_image().update()
80
81	############################################################################
82
83	def on_apply_temp_pixbuf_tool_operation(self, *args):
84		self.restore_pixbuf()
85		operation = self.build_operation()
86		self.apply_operation(operation)
87		if self.apply_to_selection:
88			self.window.force_selection()
89		else:
90			self.window.back_to_previous()
91
92	def apply_operation(self, operation):
93		operation['is_preview'] = False
94		super().apply_operation(operation)
95		self.get_image().reset_temp()
96
97	def common_end_operation(self, op):
98		if op['is_preview']:
99			self.temp_preview(op['is_selection'], op['local_dx'], op['local_dy'])
100		else:
101			self.apply_temp(op['is_selection'], op['local_dx'], op['local_dy'])
102
103	def apply_temp(self, operation_is_selection, ldx, ldy):
104		new_pixbuf = self.get_image().temp_pixbuf.copy()
105		if operation_is_selection:
106			self.get_selection().update_from_transform_tool(new_pixbuf, ldx, ldy)
107		else:
108			self.get_image().set_main_pixbuf(new_pixbuf)
109			self.get_image().use_stable_pixbuf()
110
111	############################################################################
112
113	def get_handle_cursor_name(self, event_x, event_y):
114		"""Return the name of the accurate cursor for tools such as `scale` or
115		`crop`, with or without an active selection, depending on the size and
116		position of the resized/cropped area."""
117		w_left, w_right, h_top, h_bottom = self.get_image().get_nineths_sizes( \
118		                    self.apply_to_selection, int(self._x), int(self._y))
119		# if we're transforming the selection from its top and/or left, coords
120		# to decide the direction depend on local deltas (self._x and self._y)
121
122		# print("get_handle_cursor_name", w_left, w_right, h_top, h_bottom)
123		cursor_name = ''
124		if event_y < h_top:
125			cursor_name = cursor_name + 'n'
126		elif event_y > h_bottom:
127			cursor_name = cursor_name + 's'
128		if event_x < w_left:
129			cursor_name = cursor_name + 'w'
130		elif event_x > w_right:
131			cursor_name = cursor_name + 'e'
132
133		if cursor_name == '':
134			cursor_name = 'not-allowed'
135		else:
136			cursor_name = cursor_name + '-resize'
137		return cursor_name
138
139	def on_draw_above(self, area, cairo_context):
140		pass
141
142	def _draw_temp_pixbuf(self, cairo_context, x, y):
143		pixbuf = self.get_image().temp_pixbuf
144		Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, x, y)
145		cairo_context.paint()
146
147	def get_deformed_surface(self, source_surface, coefs):
148		"""Use cairo.Matrix to apply a transformation to `source_surface` using
149		the coefficients in `coefs` and return a new surface with the result."""
150		p_xx, p_yx, p_xy, p_yy, p_x0, p_y0 = coefs
151
152		source_w = source_surface.get_width()
153		source_h = source_surface.get_height()
154		w = p_xx * source_w + p_xy * source_h + p_x0 * 2
155		h = p_yx * source_w + p_yy * source_h + p_y0 * 2
156
157		new_surface = cairo.ImageSurface(cairo.Format.ARGB32, int(w), int(h))
158		cairo_context = cairo.Context(new_surface)
159		# m = cairo.Matrix(xx=1.0, yx=0.0, xy=0.0, yy=1.0, x0=0.0, y0=0.0)
160		m = cairo.Matrix(xx=p_xx, yx=p_yx, xy=p_xy, yy=p_yy, x0=p_x0, y0=p_y0)
161		try:
162			cairo_context.transform(m)
163		except:
164			self.show_error(_("Error: invalid values"))
165			return source_surface
166		cairo_context.set_source_surface(source_surface, 0, 0)
167		cairo_context.paint()
168		return new_surface
169
170	############################################################################
171################################################################################
172
173