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