1 /*
2 Copyright (C) 2012 2014 2015 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Cairo;
16
17 namespace BirdFont {
18
19 /** Guide */
20 public class Line : GLib.Object {
21 public const bool VERTICAL = true;
22 public const bool HORIZONTAL = false;
23
24 public bool dashed { get; set; }
25
26 public string label;
27 public string translated_label;
28 bool vertical;
29 string metrics;
30
31 public double pos;
32
33 bool active = false;
34 bool move = false;
35
36 public signal void queue_draw_area (int x, int y, int w, int h);
37 public signal void position_updated (double pos);
38
39 double r;
40 double g;
41 double b;
42 double a;
43
44 bool visible = true;
45 bool moveable = true;
46
47 public bool rsb = false;
48 public bool lsb = false;
49
50 public Line (string label = "No label",
51 string translated_label = "No label",
52 double position = 10,
53 bool vertical = false) {
54
55 this.label = label;
56 this.translated_label = translated_label;
57 this.vertical = vertical;
58 this.pos = position;
59
60 dashed = false;
61 metrics = "";
62
63 set_color_theme ("Guide 1");
64 }
65
66 public Line copy () {
67 Line l = new Line (label, translated_label, pos, vertical);
68
69 l.r = r;
70 l.g = g;
71 l.b = b;
72 l.a = a;
73
74 l.visible = visible;
75 l.dashed = dashed;
76
77 return l;
78 }
79
80 public void set_metrics (double m) {
81 string t = @"$m";
82 string s = "";
83
84 int i;
85 unichar c;
86
87 i = 0;
88 while (t.get_next_char (ref i, out c)) {
89 s = s + (!) c.to_string ();
90
91 if (i >= 5) {
92 break;
93 }
94 }
95
96 metrics = s;
97 }
98
99 public void set_visible (bool v) {
100 visible = v;
101 }
102
103 public bool is_visible () {
104 return visible;
105 }
106
107 public void set_moveable (bool m) {
108 moveable = m;
109 }
110
111 public void set_color_theme (string color) {
112 Color c = Theme.get_color (color);
113
114 r = c.r;
115 g = c.g;
116 b = c.b;
117 a = c.a;
118 }
119
120 public void set_color (double r, double g, double b, double a) {
121 this.r = r;
122 this.g = g;
123 this.b = b;
124 this.a = a;
125 }
126
127 public bool is_moving () {
128 return move;
129 }
130
131 public bool set_move (bool moving) {
132 bool r = move;
133 move = moving;
134 return (r == moving);
135 }
136
137 public bool button_press (uint button) {
138 Glyph g;
139 TextListener listener;
140 string position;
141 bool text_input = false;
142
143 if (get_active ()) {
144 if (button == 3 || KeyBindings.has_shift ()) {
145 move = false;
146 text_input = true;
147
148 g = MainWindow.get_current_glyph ();
149 if (lsb) {
150 position = @"$(g.get_left_side_bearing ())";
151 } else if (rsb) {
152 position = @"$(g.get_right_side_bearing ())";
153 } else {
154 position = @"$pos";
155 }
156
157 listener = new TextListener (t_("Position"), position, t_("Move"));
158
159 listener.signal_text_input.connect ((text) => {
160 string submitted_value;
161 double parsed_value;
162 Glyph glyph;
163 double x1, y1, x2, y2;
164
165 glyph = MainWindow.get_current_glyph ();
166 submitted_value = text.replace (",", ".");
167 parsed_value = double.parse (submitted_value);
168
169 if (lsb) {
170 if (glyph.get_boundaries (out x1, out y1, out x2, out y2)) {
171 parsed_value = x1 - parsed_value;
172 } else {
173 parsed_value = glyph.right_limit - parsed_value;
174 }
175 } else if (rsb) {
176 if (glyph.get_boundaries (out x1, out y1, out x2, out y2)) {
177 parsed_value += x2;
178 } else {
179 parsed_value = glyph.left_limit - parsed_value;
180 }
181 }
182
183 pos = parsed_value;
184 position_updated (parsed_value);
185 GlyphCanvas.redraw ();
186 });
187
188 listener.signal_submit.connect (() => {
189 TabContent.hide_text_input ();
190 });
191
192 TabContent.show_text_input (listener);
193 } else {
194 move = true;
195 }
196
197 g = MainWindow.get_current_glyph ();
198 g.store_undo_state ();
199 } else {
200 move = false;
201 active = false;
202 }
203
204 return move || text_input;
205 }
206
207 void redraw_line () {
208 GlyphCanvas.redraw ();
209 }
210
211 public bool event_move_to (int x, int y, WidgetAllocation allocation) {
212 double p, c;
213 bool a = false;
214 Glyph g = MainWindow.get_current_glyph ();
215 double ivz = 1/g.view_zoom;
216 double margin = 10;
217 double none = 0;
218
219 if (!moveable) {
220 return false;
221 }
222
223 if (is_vertical ()) { // over line handle (y)
224 if (y > g.allocation.height - 10 * MainWindow.units
225 || y < 10 * MainWindow.units) {
226
227 p = pos;
228 c = Glyph.path_coordinate_x (x);
229 a = (p - margin * ivz <= c <= p + margin * ivz);
230 }
231
232 if (a != get_active ()) {
233 redraw_line ();
234 }
235
236 set_active (a);
237
238 } else { // over line handle (x)
239 if (x > g.allocation.width - 10 * MainWindow.units
240 || x < 10 * MainWindow.units) {
241
242 p = pos;
243 c = Glyph.path_coordinate_y (y);
244 a = (p - margin * ivz <= c <= p + margin * ivz);
245 }
246
247 if (a != get_active ()) {
248 redraw_line ();
249 }
250
251 set_active (a);
252 }
253
254 // move the line
255 if (move) {
256 double np = pos;
257 redraw_line (); // clear old position
258
259 if (is_vertical ()) {
260 pos = Glyph.path_coordinate_x (x);
261
262 if (GridTool.is_visible ()) {
263 GridTool.tie_coordinate (ref pos, ref none);
264 }
265 redraw_line (); // draw at new position
266 } else if (!GridTool.lock_grid) {
267 pos = Glyph.path_coordinate_y (y);
268
269 if (GridTool.is_visible ()) {
270 GridTool.tie_coordinate (ref none, ref pos);
271 }
272 redraw_line ();
273 }
274
275 if (Math.fabs (np - pos) > 10) {
276 queue_draw_area (0, 0, g.allocation.width, g.allocation.height);
277 }
278
279 position_updated (pos); // signal update
280
281 BirdFont.get_current_font ().touch ();
282 }
283
284 if (GridTool.is_visible ()) {
285 GridTool.update_lines ();
286 }
287
288 return move;
289 }
290
291 public bool get_active () {
292 return active;
293 }
294
295 public void set_active (bool active) {
296 Glyph g;
297
298 if (active) {
299 g = MainWindow.get_current_glyph ();
300
301 if (lsb) {
302 set_metrics (g.get_left_side_bearing ());
303 } else if (rsb) {
304 set_metrics (g.get_right_side_bearing ());
305 }
306 }
307
308 this.active = active;
309 }
310
311 public string get_label () {
312 return label;
313 }
314
315 public bool is_vertical () {
316 return vertical;
317 }
318
319 public int get_position_pixel () {
320 if (is_vertical ()) {
321 return Glyph.reverse_path_coordinate_x (pos);
322 }
323
324 return Glyph.reverse_path_coordinate_y (pos) ;
325 }
326
327 public double get_pos () {
328 return pos;
329 }
330
331 public void draw (Context cr, WidgetAllocation allocation) {
332 Glyph g = MainWindow.get_current_glyph ();
333 double p, h, w;
334 double size = (active) ? 8 : 5;
335 Text glyph_metrics;
336 Text line_label;
337
338 if (!visible) {
339 return;
340 }
341
342 cr.save ();
343 cr.set_line_width (1);
344
345 if (dashed) {
346 cr.set_dash ({20, 20}, 0);
347 }
348
349 if (active) {
350 Theme.color (cr, "Highlighted Guide");
351 } else {
352 cr.set_source_rgba (r, this.g, b, a);
353 }
354
355 // Line
356 if (is_vertical ()) {
357 p = Glyph.reverse_path_coordinate_x (pos);
358 h = g.allocation.height;
359
360 cr.move_to (p, 0);
361 cr.line_to (p, h);
362 cr.stroke ();
363
364 cr.scale (1, 1);
365
366 if (moveable) {
367 cr.new_path ();
368 cr.move_to (p - size, h);
369 cr.line_to (p, h - size);
370 cr.line_to (p + size, h);
371 cr.close_path();
372 cr.fill ();
373
374 cr.new_path ();
375 cr.move_to (p - size, 0);
376 cr.line_to (p, size);
377 cr.line_to (p + size, 0);
378 cr.close_path();
379 cr.fill ();
380
381 if (get_active ()) {
382 glyph_metrics = new Text (metrics, 17);
383 Theme.text_color (glyph_metrics, "Highlighted Guide");
384 glyph_metrics.widget_x = p + 10;
385 glyph_metrics.widget_y = h - 25;
386 glyph_metrics.draw (cr);
387 }
388 }
389
390 } else {
391 p = Glyph.reverse_path_coordinate_y (pos);
392 w = g.allocation.width;
393
394 cr.move_to (0, p);
395 cr.line_to (w, p);
396 cr.stroke ();
397
398 if (moveable) {
399 cr.new_path ();
400 cr.move_to (w, p - size);
401 cr.line_to (w - size, p);
402 cr.line_to (w, p + size);
403 cr.close_path();
404 cr.fill ();
405
406 cr.new_path ();
407 cr.move_to (0, p - size);
408 cr.line_to (0 + size, p);
409 cr.line_to (0, p + size);
410 cr.close_path();
411 cr.fill ();
412 }
413 }
414
415 // Label
416 if (get_active ()) {
417 line_label = new Text (translated_label, 19 * MainWindow.units);
418
419 if (is_vertical ()) {
420 line_label.widget_x = p + 8 * MainWindow.units;
421 line_label.widget_y = allocation.height - 55 * MainWindow.units;
422 } else {
423 line_label.widget_x = g.allocation.width
424 - 10 * MainWindow.units
425 - line_label.get_extent ();
426
427 line_label.widget_y = p + 10 * MainWindow.units;
428 }
429
430 if (active) {
431 Theme.text_color (line_label, "Highlighted Guide");
432 } else {
433 line_label.set_source_rgba (r, this.g, b, a);
434 }
435
436 line_label.draw (cr);
437 }
438
439 cr.restore ();
440 }
441 }
442
443 }
444