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