1# tool_line.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 .abstract_classic_tool import AbstractClassicTool
20from .utilities_paths import utilities_add_arrow_triangle
21
22class ToolLine(AbstractClassicTool):
23	__gtype_name__ = 'ToolLine'
24
25	def __init__(self, window, **kwargs):
26		super().__init__('line', _("Line"), 'tool-line-symbolic', window)
27		self.use_operator = True
28
29		self._use_outline = False
30		self._dashes_type = 'none'
31		self._arrow_type = 'none'
32		self._use_gradient = False
33		# Lock the tool to only draw orthogonal lines (multiples of 45°)
34		self._ortholock = False # Unrelated to Maïté's ortolan.
35
36		self.add_tool_action_enum('line_shape', 'round')
37		self.add_tool_action_enum('dashes-type', self._dashes_type)
38		self.add_tool_action_enum('arrow-type', self._arrow_type)
39		self.add_tool_action_boolean('use_gradient', self._use_gradient)
40		self.add_tool_action_boolean('pencil-outline', self._use_outline)
41		self.add_tool_action_boolean('line-ortholock', self._ortholock)
42		self._set_options_attributes() # Not optimal but more readable
43
44	def _set_active_shape(self):
45		state_as_string = self.get_option_value('line_shape')
46		if state_as_string == 'thin':
47			self._cap_id = cairo.LineCap.BUTT
48		else:
49			self._cap_id = cairo.LineCap.ROUND
50
51	def get_options_label(self):
52		return _("Line options")
53
54	def _set_options_attributes(self):
55		self._use_outline = self.get_option_value('pencil-outline')
56		self._dashes_type = self.get_option_value('dashes-type')
57		self._arrow_type = self.get_option_value('arrow-type')
58		self._use_gradient = self.get_option_value('use_gradient')
59		self._ortholock = self.get_option_value('line-ortholock')
60		self._set_active_shape()
61
62	def get_edition_status(self):
63		self._set_options_attributes()
64		is_arrow = self._arrow_type != 'none'
65		use_dashes = self._dashes_type != 'none'
66		label = self.label
67		if is_arrow and use_dashes:
68			label = label + ' - ' + _("Dashed arrow")
69		elif is_arrow:
70			label = label + ' - ' + _("Arrow")
71		elif use_dashes:
72			label = label + ' - ' + _("Dashed")
73		return label
74
75	############################################################################
76
77	def on_press_on_area(self, event, surface, event_x, event_y):
78		self.set_common_values(event.button, event_x, event_y)
79
80	def on_motion_on_area(self, event, surface, event_x, event_y):
81		operation = self.build_operation(event_x, event_y)
82		self.do_tool_operation(operation)
83
84	def on_release_on_area(self, event, surface, event_x, event_y):
85		operation = self.build_operation(event_x, event_y)
86		self.apply_operation(operation)
87
88	############################################################################
89
90	def build_operation(self, event_x, event_y):
91		operation = {
92			'tool_id': self.id,
93			'rgba': self.main_color,
94			'rgba2': self.secondary_color,
95			'antialias': self._use_antialias,
96			'operator': self._operator,
97			'line_width': self.tool_width,
98			'line_cap': self._cap_id,
99			'dashes': self._dashes_type,
100			'arrow': self._arrow_type,
101			'gradient': self._use_gradient,
102			'outline': self._use_outline,
103			'ortholock': self._ortholock,
104			'x_release': event_x,
105			'y_release': event_y,
106			'x_press': self.x_press,
107			'y_press': self.y_press
108		}
109		return operation
110
111	def do_tool_operation(self, operation):
112		cairo_context = self.start_tool_operation(operation)
113		cairo_context.set_operator(operation['operator'])
114		line_width = operation['line_width']
115		cairo_context.set_line_width(line_width)
116		c1 = operation['rgba']
117		c2 = operation['rgba2']
118		x1 = operation['x_press']
119		y1 = operation['y_press']
120		x2 = operation['x_release']
121		y2 = operation['y_release']
122
123		if operation['ortholock']:
124			x1, y1 = int(x1), int(y1)
125			x2, y2 = int(x2), int(y2)
126			delta_x = abs(x1 - x2)
127			delta_y = abs(y1 - y2)
128			if delta_x > 2 * delta_y:
129				# Strictly horizontal line
130				y2 = y1
131			elif delta_x * 2 < delta_y:
132				# Strictly vertical line
133				x2 = x1
134			else:
135				# 45° line
136				delta45 = min(delta_x, delta_y)
137				x2 = x1 + delta45 if (x1 - x2 < 0) else x1 - delta45
138				y2 = y1 + delta45 if (y1 - y2 < 0) else y1 - delta45
139
140		self.set_dashes_and_cap(cairo_context, line_width, \
141		                             operation['dashes'], operation['line_cap'])
142
143		if operation['arrow'] == 'double':
144			utilities_add_arrow_triangle(cairo_context, x1, y1, x2, y2, line_width)
145
146		# We don't memorize the path because all coords are here anyway for the
147		# linear gradient and/or the arrow.
148		cairo_context.move_to(x1, y1)
149		cairo_context.line_to(x2, y2)
150
151		if operation['arrow'] != 'none':
152			utilities_add_arrow_triangle(cairo_context, x2, y2, x1, y1, line_width)
153
154		if operation['outline']:
155			cairo_context.set_source_rgba(c2.red, c2.green, c2.blue, c2.alpha)
156			cairo_context.set_line_width(line_width * 1.2 + 2)
157			cairo_context.stroke_preserve()
158
159		if operation['gradient']:
160			pattern = cairo.LinearGradient(x1, y1, x2, y2)
161			pattern.add_color_stop_rgba(0.1, c1.red, c1.green, c1.blue, c1.alpha)
162			pattern.add_color_stop_rgba(0.9, c2.red, c2.green, c2.blue, c2.alpha)
163			cairo_context.set_source(pattern)
164		else:
165			cairo_context.set_source_rgba(c1.red, c1.green, c1.blue, c1.alpha)
166		cairo_context.set_line_width(line_width)
167		cairo_context.stroke()
168
169	############################################################################
170################################################################################
171
172