1 #include "XournalppCursor.h"
2
3 #include <cmath>
4
5 #include "control/Control.h"
6
7 #include "Util.h"
8 #include "XournalView.h"
9 #include "pixbuf-utils.h"
10
11
12 // NOTE: Every cursor change must result in the setting of this->currentCursor to the new cursor type even for custom
13 // cursors.
14 // Custom cursors can also compare then set this->currentCursorFlavour to notice changes in colour or size etc. The
15 // flavour calculation is specific to each cursor type and is calculated differently within each type.
16
17
18 // All the cursors we want to use. WARNING Make sure to set their css names in cssCursors[] below WARNING
19 enum AVAILABLECURSORS {
20 CRSR_nullptr = 0, // <--- Do Not Modify
21 CRSR_BUSY,
22 CRSR_MOVE,
23 CRSR_MOVING,
24 CRSR_GRAB,
25 CRSR_GRABBING,
26 CRSR_TOP_LEFT_CORNER,
27 CRSR_TOP_RIGHT_CORNER,
28 CRSR_BOTTOM_LEFT_CORNER,
29 CRSR_BOTTOM_RIGHT_CORNER,
30 CRSR_SB_H_DOUBLE_ARROW,
31 CRSR_EXCHANGE,
32 CRSR_PIRATE,
33 CRSR_SB_V_DOUBLE_ARROW,
34 CRSR_ARROW,
35 CRSR_BLANK_CURSOR,
36 CRSR_XTERM,
37 CRSR_DEFAULT,
38 CRSR_HAND2,
39 CRSR_TCROSS,
40 CRSR_PENORHIGHLIGHTER,
41 CRSR_ERASER,
42 CRSR_DRAWDIRNONE, // drawdir* keep these consecutive and in order: none,shift,ctrl,shiftctrl
43 CRSR_DRAWDIRSHIFT, // "
44 CRSR_DRAWDIRCTRL, // "
45 CRSR_DRAWDIRSHIFTCTRL, // "
46 CRSR_RESIZE,
47
48 CRSR_END_OF_CURSORS
49 };
50
51
52 struct cursorStruct {
53 const gchar* cssName;
54 const gchar* cssBackupName;
55 };
56
57
58 // our enum mapped to css name and a place to store the cursor pointer.
59 // Note: including enum as error check (for now?)
60 cursorStruct cssCursors[CRSR_END_OF_CURSORS];
61
62
XournalppCursor(Control * control)63 XournalppCursor::XournalppCursor(Control* control): control(control) {
64 // clang-format off
65 // NOTE: Go ahead and use a fancy css cursor... but specify a common backup cursor.
66 cssCursors[CRSR_nullptr ] = {"",""};
67 cssCursors[CRSR_BUSY ] = {"wait", "" };
68 cssCursors[CRSR_MOVE ] = {"all-scroll", "" };
69 cssCursors[CRSR_MOVING ] = {"grabbing", "" };
70 cssCursors[CRSR_GRAB ] = {"grab", "" };
71 cssCursors[CRSR_GRABBING ] = {"grabbing", "" };
72 cssCursors[CRSR_TOP_LEFT_CORNER ] = {"nw-resize", "" };
73 cssCursors[CRSR_TOP_RIGHT_CORNER ] = {"ne-resize", "" };
74 cssCursors[CRSR_BOTTOM_LEFT_CORNER ] = {"sw-resize", "" };
75 cssCursors[CRSR_BOTTOM_RIGHT_CORNER ] = {"se-resize", "" };
76 cssCursors[CRSR_SB_H_DOUBLE_ARROW ] = {"ew-resize", "" };
77 cssCursors[CRSR_EXCHANGE ] = {"exchange", "arrow" };
78 cssCursors[CRSR_PIRATE ] = {"pirate", "arrow" };
79 cssCursors[CRSR_SB_V_DOUBLE_ARROW ] = {"ns-resize", "" };
80 cssCursors[CRSR_ARROW ] = {"default", "" };
81 cssCursors[CRSR_BLANK_CURSOR ] = {"none", "" };
82 cssCursors[CRSR_XTERM ] = {"text", "" };
83 cssCursors[CRSR_DEFAULT ] = {"default", "" };
84 cssCursors[CRSR_HAND2 ] = {"hand2", "" };
85 cssCursors[CRSR_TCROSS ] = {"crosshair", "" };
86 cssCursors[CRSR_PENORHIGHLIGHTER ] = {"",""}; // custom cursors - enum used only for check
87 cssCursors[CRSR_ERASER ] = {"",""}; // "
88 cssCursors[CRSR_DRAWDIRNONE ] = {"",""}; // "
89 cssCursors[CRSR_DRAWDIRSHIFT ] = {"",""}; // "
90 cssCursors[CRSR_DRAWDIRCTRL ] = {"",""}; // "
91 cssCursors[CRSR_DRAWDIRSHIFTCTRL ] = {"",""}; // "
92 cssCursors[CRSR_RESIZE ] = {"",""}; // "
93 };
94 // clang-format on
95
96 constexpr auto RESIZE_CURSOR_SIZE = 16;
97 constexpr auto DELTA_ANGLE_ARROW_HEAD = M_PI / 6.0;
98 constexpr auto LENGTH_ARROW_HEAD = 0.7;
99 constexpr auto RESIZE_CURSOR_HASH_PRECISION = 1000;
100
101
102 XournalppCursor::~XournalppCursor() = default;
103
104
setInputDeviceClass(InputDeviceClass device)105 void XournalppCursor::setInputDeviceClass(InputDeviceClass device) { this->inputDevice = device; }
106
107
108 // pen or hi-light cursor will be a DrawDir cursor instead
activateDrawDirCursor(bool enable,bool shift,bool ctrl)109 void XournalppCursor::activateDrawDirCursor(bool enable, bool shift, bool ctrl) {
110 this->drawDirActive = enable;
111 this->drawDirShift = shift;
112 this->drawDirCtrl = ctrl;
113 }
114
115
setMouseDown(bool mouseDown)116 void XournalppCursor::setMouseDown(bool mouseDown) {
117 if (this->mouseDown == mouseDown) {
118 return;
119 }
120
121 this->mouseDown = mouseDown;
122 ToolHandler* handler = control->getToolHandler();
123 ToolType type = handler->getToolType();
124
125 // Not always an update is needed
126 if (type == TOOL_HAND || type == TOOL_VERTICAL_SPACE) {
127 updateCursor();
128 }
129 }
130
131
setMouseSelectionType(CursorSelectionType selectionType)132 void XournalppCursor::setMouseSelectionType(CursorSelectionType selectionType) {
133 if (this->selectionType == selectionType) {
134 return;
135 }
136 this->selectionType = selectionType;
137 updateCursor();
138 }
139
setRotationAngle(double angle)140 void XournalppCursor::setRotationAngle(double angle) { this->angle = angle; }
setMirror(bool mirror)141 void XournalppCursor::setMirror(bool mirror) { this->mirror = mirror; }
142
143 /*This handles setting the busy cursor for the main window and calls
144 * updateCursor to set the busy cursor for the XournalWidget region.
145 */
setCursorBusy(bool busy)146 void XournalppCursor::setCursorBusy(bool busy) {
147 MainWindow* win = control->getWindow();
148 if (!win) {
149 return;
150 }
151
152 if (this->busy == busy) {
153 return;
154 }
155
156 this->busy = busy;
157
158 if (busy) {
159 GdkWindow* window = gtk_widget_get_window(win->getWindow());
160 GdkCursor* cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[CRSR_BUSY].cssName);
161 gdk_window_set_cursor(window, cursor);
162 g_object_unref(cursor);
163 } else {
164 if (gtk_widget_get_window(win->getWindow())) {
165 gdk_window_set_cursor(gtk_widget_get_window(win->getWindow()), nullptr);
166 }
167 }
168
169 updateCursor();
170 }
171
172
setInsidePage(bool insidePage)173 void XournalppCursor::setInsidePage(bool insidePage) {
174 if (this->insidePage == insidePage) {
175 return;
176 }
177
178 this->insidePage = insidePage;
179
180 updateCursor();
181 }
182
183
setInvisible(bool invisible)184 void XournalppCursor::setInvisible(bool invisible) {
185 if (this->invisible == invisible) {
186 return;
187 }
188
189 this->invisible = invisible;
190
191 updateCursor();
192 }
193
194
updateCursor()195 void XournalppCursor::updateCursor() {
196 MainWindow* win = control->getWindow();
197 if (!win) {
198 return;
199 }
200
201 XournalView* xournal = win->getXournal();
202 if (!xournal) {
203 return;
204 }
205
206 GdkCursor* cursor = nullptr;
207
208
209 if (this->busy) {
210 setCursor(CRSR_BUSY);
211 } else {
212 ToolHandler* handler = control->getToolHandler();
213 ToolType type = handler->getToolType();
214
215
216 if (type == TOOL_HAND) {
217 if (this->mouseDown) {
218 setCursor(CRSR_GRABBING);
219 } else {
220 setCursor(CRSR_GRAB);
221 }
222 } else if (!this->insidePage) {
223 setCursor(CRSR_DEFAULT);
224 } else if (this->selectionType) {
225 switch (this->selectionType) {
226 case CURSOR_SELECTION_MOVE:
227 if (this->mouseDown) {
228 setCursor(CRSR_MOVING);
229 } else {
230 setCursor(CRSR_MOVE);
231 }
232 break;
233 case CURSOR_SELECTION_TOP_LEFT:
234 [[fallthrough]];
235 case CURSOR_SELECTION_BOTTOM_RIGHT:
236 cursor = getResizeCursor(45);
237 break;
238 case CURSOR_SELECTION_TOP_RIGHT:
239 [[fallthrough]];
240 case CURSOR_SELECTION_BOTTOM_LEFT:
241 cursor = getResizeCursor(135);
242 break;
243 case CURSOR_SELECTION_LEFT:
244 [[fallthrough]];
245 case CURSOR_SELECTION_RIGHT:
246 cursor = getResizeCursor(180);
247 break;
248 case CURSOR_SELECTION_TOP:
249 [[fallthrough]];
250 case CURSOR_SELECTION_BOTTOM:
251 cursor = getResizeCursor(90);
252 break;
253 case CURSOR_SELECTION_ROTATE:
254 setCursor(CRSR_EXCHANGE);
255 break;
256 case CURSOR_SELECTION_DELETE:
257 setCursor(CRSR_PIRATE);
258 break;
259 default:
260 break;
261 }
262 } else if (type == TOOL_PEN || type == TOOL_HIGHLIGHTER) {
263 if (this->inputDevice == INPUT_DEVICE_MOUSE && !this->mouseDown) // mouse and not pressed
264 {
265 setCursor(CRSR_ARROW);
266 } else {
267 if (type == TOOL_PEN) {
268 cursor = getPenCursor();
269 } else // must be: if (type == TOOL_HIGHLIGHTER)
270 {
271 cursor = getHighlighterCursor();
272 }
273 }
274 } else if (type == TOOL_ERASER) {
275 cursor = getEraserCursor();
276 }
277
278 else if (type == TOOL_TEXT) {
279 if (this->invisible) {
280 setCursor(CRSR_BLANK_CURSOR);
281 } else {
282 setCursor(CRSR_XTERM);
283 }
284 } else if (type == TOOL_IMAGE) {
285 setCursor(CRSR_DEFAULT);
286 } else if (type == TOOL_FLOATING_TOOLBOX) {
287 setCursor(CRSR_DEFAULT);
288 } else if (type == TOOL_VERTICAL_SPACE) {
289 if (this->mouseDown) {
290 setCursor(CRSR_SB_V_DOUBLE_ARROW);
291 }
292 } else if (type == TOOL_SELECT_OBJECT) {
293 setCursor(CRSR_DEFAULT);
294 } else if (type == TOOL_PLAY_OBJECT) {
295 setCursor(CRSR_HAND2);
296 } else // other selections are handled before anyway, because you can move a selection with every tool
297 {
298 setCursor(CRSR_TCROSS);
299 }
300 }
301
302 GdkWindow* window = gtk_widget_get_window(xournal->getWidget());
303 if (window) {
304 if (cursor != nullptr) {
305 gdk_window_set_cursor(window, cursor);
306 }
307 gtk_widget_set_sensitive(xournal->getWidget(), !this->busy);
308 }
309
310 gdk_display_sync(gdk_display_get_default());
311
312 if (cursor != nullptr) {
313 g_object_unref(cursor);
314 }
315 }
316
getResizeCursor(double deltaAngle)317 auto XournalppCursor::getResizeCursor(double deltaAngle) -> GdkCursor* {
318 if (this->mirror) {
319 deltaAngle = -deltaAngle;
320 }
321 gulong flavour = static_cast<gulong>(RESIZE_CURSOR_HASH_PRECISION * fmod(angle + deltaAngle, 180.0));
322 if (CRSR_RESIZE == this->currentCursor && flavour == this->currentCursorFlavour) {
323 return nullptr;
324 }
325 this->currentCursor = CRSR_RESIZE;
326 this->currentCursorFlavour = flavour;
327
328 double a = (this->angle + deltaAngle) * M_PI / 180;
329 cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, RESIZE_CURSOR_SIZE, RESIZE_CURSOR_SIZE);
330 cairo_t* cr = cairo_create(crCursor);
331 cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1);
332 cairo_translate(cr, RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
333 cairo_scale(cr, RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
334 cairo_set_line_width(cr, 0.2);
335 // draw double headed arrow rotated accordingly
336 cairo_move_to(cr, cos(a), sin(a));
337 cairo_line_to(cr, -cos(a), -sin(a));
338 cairo_stroke(cr);
339 // head and tail
340 for (auto s: {-1, 1}) {
341 cairo_move_to(cr, s * cos(a), s * sin(a));
342 cairo_rel_line_to(cr, s * cos(a + M_PI + DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD,
343 s * sin(a + M_PI + DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD);
344 cairo_move_to(cr, s * cos(a), s * sin(a));
345 cairo_rel_line_to(cr, s * cos(a + M_PI - DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD,
346 s * sin(a + M_PI - DELTA_ANGLE_ARROW_HEAD) * LENGTH_ARROW_HEAD);
347 cairo_stroke(cr);
348 }
349
350 cairo_destroy(cr);
351 GdkPixbuf* pixbuf = xoj_pixbuf_get_from_surface(crCursor, 0, 0, RESIZE_CURSOR_SIZE, RESIZE_CURSOR_SIZE);
352 cairo_surface_destroy(crCursor);
353 GdkCursor* cursor =
354 gdk_cursor_new_from_pixbuf(gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf,
355 RESIZE_CURSOR_SIZE / 2, RESIZE_CURSOR_SIZE / 2);
356 g_object_unref(pixbuf);
357 return cursor;
358 }
359
getEraserCursor()360 auto XournalppCursor::getEraserCursor() -> GdkCursor* {
361
362 // Eraser's size follow a quadratic increment, so the cursor will do the same
363 double cursorSize = control->getToolHandler()->getThickness() * 2.0 * control->getZoomControl()->getZoom();
364 gulong flavour = static_cast<gulong>(64 * cursorSize);
365
366 if (CRSR_ERASER == this->currentCursor && flavour == this->currentCursorFlavour) {
367 return nullptr; // cursor already set
368 }
369 this->currentCursor = CRSR_ERASER;
370 this->currentCursorFlavour = flavour;
371
372 cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, cursorSize, cursorSize);
373 cairo_t* cr = cairo_create(surface);
374 cairo_rectangle(cr, 0, 0, cursorSize, cursorSize);
375 cairo_set_source_rgb(cr, 1, 1, 1);
376 cairo_fill(cr);
377 cairo_rectangle(cr, 0, 0, cursorSize, cursorSize);
378 cairo_set_source_rgb(cr, 0, 0, 0);
379 cairo_stroke(cr);
380 cairo_destroy(cr);
381 GdkCursor* cursor =
382 gdk_cursor_new_from_surface(gdk_display_get_default(), surface, cursorSize / 2.0, cursorSize / 2.0);
383 cairo_surface_destroy(surface);
384 return cursor;
385 }
386
387
getHighlighterCursor()388 auto XournalppCursor::getHighlighterCursor() -> GdkCursor* {
389 if (this->drawDirActive) {
390 return createCustomDrawDirCursor(48, this->drawDirShift, this->drawDirCtrl);
391 }
392
393 return createHighlighterOrPenCursor(5, 120 / 255.0);
394 }
395
396
getPenCursor()397 auto XournalppCursor::getPenCursor() -> GdkCursor* {
398 if (control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_NONE) {
399 setCursor(CRSR_BLANK_CURSOR);
400 return nullptr;
401 }
402 if (this->drawDirActive) {
403 return createCustomDrawDirCursor(48, this->drawDirShift, this->drawDirCtrl);
404 }
405
406 return createHighlighterOrPenCursor(3, 1.0);
407 }
408
409
createHighlighterOrPenCursor(int size,double alpha)410 auto XournalppCursor::createHighlighterOrPenCursor(int size, double alpha) -> GdkCursor* {
411 auto irgb = control->getToolHandler()->getColor();
412 auto drgb = Util::rgb_to_GdkRGBA(irgb);
413 bool big = control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_BIG;
414 bool bright = control->getSettings()->isHighlightPosition();
415 int height = size;
416 int width = size;
417
418 // create a hash of variables so we notice if one changes despite being the same cursor type:
419 gulong flavour = (big ? 1 : 0) | (bright ? 2 : 0) | static_cast<gulong>(64 * alpha) << 2 |
420 static_cast<gulong>(size) << 9 | static_cast<gulong>(irgb) << 14;
421
422 if (CRSR_PENORHIGHLIGHTER == this->currentCursor && flavour == this->currentCursorFlavour) {
423 return nullptr;
424 }
425 this->currentCursor = CRSR_PENORHIGHLIGHTER;
426 this->currentCursorFlavour = flavour;
427
428 if (big || bright) {
429 height = width = 60;
430 }
431
432 // We change the drawing method, now the center with the colored dot of the pen
433 // is at the center of the cairo surface, and when we load the cursor, we load it
434 // with the relative offset
435 int centerX = width / 2;
436 int centerY = height / 2;
437 cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
438 cairo_t* cr = cairo_create(crCursor);
439
440 if (big) {
441 // When using highlighter, paint the icon with the current color
442 if (size == 5) {
443 gdk_cairo_set_source_rgba(cr, &drgb);
444 } else {
445 cairo_set_source_rgb(cr, 1, 1, 1);
446 }
447 cairo_set_line_width(cr, 1.2);
448
449 // Starting point
450 cairo_move_to(cr, centerX + 2, centerY);
451 // Pencil cursor
452 cairo_line_to(cr, centerX + 2, centerY - 4);
453 cairo_line_to(cr, centerX + 15, centerY - 17.5);
454 cairo_line_to(cr, centerX + 19, centerY - 14);
455 cairo_line_to(cr, centerX + 6, centerY);
456
457 cairo_close_path(cr);
458 cairo_fill_preserve(cr);
459 cairo_set_source_rgb(cr, 0, 0, 0);
460 cairo_stroke(cr);
461
462 cairo_fill_preserve(cr);
463 }
464
465 if (bright) {
466 // Highlight cursor with a circle
467 auto&& color = Util::argb_to_GdkRGBA(control->getSettings()->getCursorHighlightColor());
468 gdk_cairo_set_source_rgba(cr, &color);
469 cairo_arc(cr, centerX, centerY, control->getSettings()->getCursorHighlightRadius(), 0, 2 * M_PI);
470 cairo_fill_preserve(cr);
471 auto&& borderColor = Util::argb_to_GdkRGBA(control->getSettings()->getCursorHighlightBorderColor());
472 gdk_cairo_set_source_rgba(cr, &borderColor);
473 cairo_set_line_width(cr, control->getSettings()->getCursorHighlightBorderWidth());
474 cairo_stroke(cr);
475 }
476
477 auto drgbCopy = drgb;
478 drgbCopy.alpha = alpha;
479 gdk_cairo_set_source_rgba(cr, &drgbCopy);
480 double cursorSize = control->getToolHandler()->getThickness() * control->getZoomControl()->getZoom();
481 cairo_arc(cr, centerX, centerY, cursorSize / 2., 0, 2. * M_PI);
482 cairo_fill(cr);
483 cairo_destroy(cr);
484 GdkPixbuf* pixbuf = xoj_pixbuf_get_from_surface(crCursor, 0, 0, width, height);
485 cairo_surface_destroy(crCursor);
486 GdkCursor* cursor = gdk_cursor_new_from_pixbuf(
487 gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf, centerX, centerY);
488 g_object_unref(pixbuf);
489 return cursor;
490 }
491
492
setCursor(int cursorID)493 void XournalppCursor::setCursor(int cursorID) {
494 if (cursorID == this->currentCursor) {
495 return;
496 }
497
498 MainWindow* win = control->getWindow();
499 if (!win) {
500 return;
501 }
502
503 XournalView* xournal = win->getXournal();
504 if (!xournal) {
505 return;
506 }
507
508 GdkWindow* window = gtk_widget_get_window(xournal->getWidget());
509 if (!window) {
510 return;
511 }
512
513 GdkCursor* cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[cursorID].cssName);
514 if (cursor == nullptr) // failed to get a cursor, try backup cursor.
515 {
516 if (cursorID != CRSR_nullptr) {
517 cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), cssCursors[cursorID].cssBackupName);
518
519 // Null cursor is ok but not wanted ... warn user
520 if (cursor == nullptr) {
521 if (CRSR_nullptr == this->currentCursor) {
522 return; // We've already been here
523 }
524 g_warning("CSS Cursor and backup not valid '%s', '%s'", cssCursors[cursorID].cssName,
525 cssCursors[cursorID].cssBackupName);
526 }
527 }
528 cursorID = cursor == nullptr ? CRSR_nullptr : cursorID;
529 }
530
531 this->currentCursor = cursorID;
532 gdk_window_set_cursor(gtk_widget_get_window(xournal->getWidget()), cursor);
533 gdk_window_set_cursor(window, cursor);
534 if (cursor) {
535 g_object_unref(cursor);
536 }
537 }
538
539
createCustomDrawDirCursor(int size,bool shift,bool ctrl)540 auto XournalppCursor::createCustomDrawDirCursor(int size, bool shift, bool ctrl) -> GdkCursor* {
541 bool big = control->getSettings()->getStylusCursorType() == STYLUS_CURSOR_BIG;
542 bool bright = control->getSettings()->isHighlightPosition();
543
544 int newCursorID = CRSR_DRAWDIRNONE + (shift ? 1 : 0) + (ctrl ? 2 : 0);
545 gulong flavour =
546 (big ? 1 : 0) | (bright ? 2 : 0) | static_cast<gulong>(size) << 2; // hash of variables for comparison only
547
548 if (newCursorID == this->currentCursor && flavour == this->currentCursorFlavour) {
549 return nullptr;
550 }
551 this->currentCursor = newCursorID;
552 this->currentCursorFlavour = flavour;
553
554 int height = size;
555 int width = size;
556 int fontSize = 8;
557 if (big || bright) {
558 height = width = 60;
559 fontSize = 12;
560 }
561 int centerX = width - width / 4;
562 int centerY = height - height / 4;
563
564
565 cairo_surface_t* crCursor = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
566 cairo_t* cr = cairo_create(crCursor);
567 cairo_set_line_width(cr, 1.2);
568
569 // Starting point
570 cairo_move_to(cr, centerX, height / 2);
571 cairo_line_to(cr, centerX, height);
572 cairo_stroke(cr);
573
574 cairo_move_to(cr, width / 2, centerY);
575 cairo_line_to(cr, width, centerY);
576 cairo_stroke(cr);
577
578 if (ctrl) {
579 cairo_text_extents_t extents;
580 const char* utf8 = "CONTROL";
581 double x = NAN, y = NAN;
582 cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
583 cairo_set_font_size(cr, fontSize);
584 cairo_text_extents(cr, utf8, &extents);
585 x = 0;
586 y = extents.height;
587 cairo_move_to(cr, x, y);
588 cairo_show_text(cr, utf8);
589 }
590
591 if (shift) {
592 cairo_text_extents_t extents;
593 const char* utf8 = "SHIFT";
594 double x = NAN, y = NAN;
595 cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
596 cairo_set_font_size(cr, fontSize);
597 cairo_text_extents(cr, utf8, &extents);
598 x = 0;
599 y = extents.height * 2.5;
600 cairo_move_to(cr, x, y);
601 cairo_show_text(cr, utf8);
602 }
603
604 cairo_destroy(cr);
605 GdkPixbuf* pixbuf = xoj_pixbuf_get_from_surface(crCursor, 0, 0, width, height);
606 cairo_surface_destroy(crCursor);
607 GdkCursor* cursor = gdk_cursor_new_from_pixbuf(
608 gtk_widget_get_display(control->getWindow()->getXournal()->getWidget()), pixbuf, centerX, centerY);
609 g_object_unref(pixbuf);
610
611 return cursor;
612 }
613