1 // Scintilla source code edit control
2 // ScintillaGTK.cxx - GTK+ specific subclass of ScintillaBase
3 // Copyright 1998-2004 by Neil Hodgson <neilh@scintilla.org>
4 // The License.txt file describes the conditions under which this software may be distributed.
5 
6 #include <cstddef>
7 #include <cstdlib>
8 #include <cassert>
9 #include <cstring>
10 #include <cstdio>
11 #include <ctime>
12 #include <cmath>
13 
14 #include <stdexcept>
15 #include <new>
16 #include <string>
17 #include <vector>
18 #include <map>
19 #include <algorithm>
20 #include <memory>
21 
22 #include <glib.h>
23 #include <gmodule.h>
24 #include <gdk/gdk.h>
25 #include <gtk/gtk.h>
26 #include <gdk/gdkkeysyms.h>
27 #if defined(GDK_WINDOWING_WAYLAND)
28 #include <gdk/gdkwayland.h>
29 #endif
30 
31 #if defined(_WIN32)
32 // On Win32 use windows.h to access clipboard (rectangular format) and systems parameters
33 #undef NOMINMAX
34 #define NOMINMAX
35 #include <windows.h>
36 #endif
37 
38 #include "Platform.h"
39 
40 #include "ILoader.h"
41 #include "ILexer.h"
42 #include "Scintilla.h"
43 #include "ScintillaWidget.h"
44 #include "StringCopy.h"
45 #include "CharacterCategory.h"
46 #include "Position.h"
47 #include "UniqueString.h"
48 #include "SplitVector.h"
49 #include "Partitioning.h"
50 #include "RunStyles.h"
51 #include "ContractionState.h"
52 #include "CellBuffer.h"
53 #include "CallTip.h"
54 #include "KeyMap.h"
55 #include "Indicator.h"
56 #include "LineMarker.h"
57 #include "Style.h"
58 #include "ViewStyle.h"
59 #include "CharClassify.h"
60 #include "Decoration.h"
61 #include "CaseFolder.h"
62 #include "Document.h"
63 #include "CaseConvert.h"
64 #include "UniConversion.h"
65 #include "Selection.h"
66 #include "PositionCache.h"
67 #include "EditModel.h"
68 #include "MarginView.h"
69 #include "EditView.h"
70 #include "Editor.h"
71 #include "AutoComplete.h"
72 #include "ScintillaBase.h"
73 
74 #include "ScintillaGTK.h"
75 #include "scintilla-marshal.h"
76 #include "ScintillaGTKAccessible.h"
77 
78 #include "Converter.h"
79 
80 #define IS_WIDGET_REALIZED(w) (gtk_widget_get_realized(GTK_WIDGET(w)))
81 #define IS_WIDGET_MAPPED(w) (gtk_widget_get_mapped(GTK_WIDGET(w)))
82 
83 #define SC_INDICATOR_INPUT INDICATOR_IME
84 #define SC_INDICATOR_TARGET INDICATOR_IME+1
85 #define SC_INDICATOR_CONVERTED INDICATOR_IME+2
86 #define SC_INDICATOR_UNKNOWN INDICATOR_IME_MAX
87 
WindowFromWidget(GtkWidget * w)88 static GdkWindow *WindowFromWidget(GtkWidget *w) noexcept {
89 	return gtk_widget_get_window(w);
90 }
91 
92 #ifdef _MSC_VER
93 // Constant conditional expressions are because of GTK+ headers
94 #pragma warning(disable: 4127)
95 // Ignore unreferenced local functions in GTK+ headers
96 #pragma warning(disable: 4505)
97 #endif
98 
99 using namespace Scintilla;
100 
PWindow(const Window & w)101 static GdkWindow *PWindow(const Window &w) noexcept {
102 	GtkWidget *widget = static_cast<GtkWidget *>(w.GetID());
103 	return gtk_widget_get_window(widget);
104 }
105 
106 extern std::string UTF8FromLatin1(const char *s, int len);
107 
108 enum {
109 	COMMAND_SIGNAL,
110 	NOTIFY_SIGNAL,
111 	LAST_SIGNAL
112 };
113 
114 static gint scintilla_signals[LAST_SIGNAL] = { 0 };
115 
116 enum {
117 	TARGET_STRING,
118 	TARGET_TEXT,
119 	TARGET_COMPOUND_TEXT,
120 	TARGET_UTF8_STRING,
121 	TARGET_URI
122 };
123 
124 GdkAtom ScintillaGTK::atomUTF8 = nullptr;
125 GdkAtom ScintillaGTK::atomUTF8Mime = nullptr;
126 GdkAtom ScintillaGTK::atomString = nullptr;
127 GdkAtom ScintillaGTK::atomUriList = nullptr;
128 GdkAtom ScintillaGTK::atomDROPFILES_DND = nullptr;
129 
130 static const GtkTargetEntry clipboardCopyTargets[] = {
131 	{ (gchar *) "UTF8_STRING", 0, TARGET_UTF8_STRING },
132 	{ (gchar *) "STRING", 0, TARGET_STRING },
133 };
134 static constexpr gint nClipboardCopyTargets = ELEMENTS(clipboardCopyTargets);
135 
136 static const GtkTargetEntry clipboardPasteTargets[] = {
137 	{ (gchar *) "text/uri-list", 0, TARGET_URI },
138 	{ (gchar *) "UTF8_STRING", 0, TARGET_UTF8_STRING },
139 	{ (gchar *) "STRING", 0, TARGET_STRING },
140 };
141 static constexpr gint nClipboardPasteTargets = ELEMENTS(clipboardPasteTargets);
142 
143 static const GdkDragAction actionCopyOrMove = static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE);
144 
PWidget(const Window & w)145 static GtkWidget *PWidget(const Window &w) noexcept {
146 	return static_cast<GtkWidget *>(w.GetID());
147 }
148 
FromWidget(GtkWidget * widget)149 ScintillaGTK *ScintillaGTK::FromWidget(GtkWidget *widget) noexcept {
150 	ScintillaObject *scio = SCINTILLA(widget);
151 	return static_cast<ScintillaGTK *>(scio->pscin);
152 }
153 
ScintillaGTK(_ScintillaObject * sci_)154 ScintillaGTK::ScintillaGTK(_ScintillaObject *sci_) :
155 	adjustmentv(nullptr), adjustmenth(nullptr),
156 	verticalScrollBarWidth(30), horizontalScrollBarHeight(30),
157 	evbtn(nullptr),
158 	buttonMouse(0),
159 	capturedMouse(false), dragWasDropped(false),
160 	lastKey(0), rectangularSelectionModifier(SCMOD_CTRL),
161 	parentClass(nullptr),
162 	atomSought(nullptr),
163 	preeditInitialized(false),
164 	im_context(nullptr),
165 	lastNonCommonScript(G_UNICODE_SCRIPT_INVALID_CODE),
166 	lastWheelMouseTime(0),
167 	lastWheelMouseDirection(0),
168 	wheelMouseIntensity(0),
169 	smoothScrollY(0),
170 	smoothScrollX(0),
171 	rgnUpdate(nullptr),
172 	repaintFullWindow(false),
173 	styleIdleID(0),
174 	accessibilityEnabled(SC_ACCESSIBILITY_ENABLED),
175 	accessible(nullptr) {
176 	sci = sci_;
177 	wMain = GTK_WIDGET(sci);
178 
179 	rectangularSelectionModifier = SCMOD_ALT;
180 
181 #if PLAT_GTK_WIN32
182 	// There does not seem to be a real standard for indicating that the clipboard
183 	// contains a rectangular selection, so copy Developer Studio.
184 	cfColumnSelect = static_cast<CLIPFORMAT>(
185 				 ::RegisterClipboardFormat("MSDEVColumnSelect"));
186 
187 	// Get intellimouse parameters when running on win32; otherwise use
188 	// reasonable default
189 #ifndef SPI_GETWHEELSCROLLLINES
190 #define SPI_GETWHEELSCROLLLINES   104
191 #endif
192 	::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0);
193 #else
194 	linesPerScroll = 4;
195 #endif
196 
197 	Init();
198 }
199 
~ScintillaGTK()200 ScintillaGTK::~ScintillaGTK() {
201 	if (styleIdleID) {
202 		g_source_remove(styleIdleID);
203 		styleIdleID = 0;
204 	}
205 	if (evbtn) {
206 		gdk_event_free(evbtn);
207 		evbtn = nullptr;
208 	}
209 	wPreedit.Destroy();
210 }
211 
UnRefCursor(GdkCursor * cursor)212 static void UnRefCursor(GdkCursor *cursor) noexcept {
213 #if GTK_CHECK_VERSION(3,0,0)
214 	g_object_unref(cursor);
215 #else
216 	gdk_cursor_unref(cursor);
217 #endif
218 }
219 
RealizeThis(GtkWidget * widget)220 void ScintillaGTK::RealizeThis(GtkWidget *widget) {
221 	//Platform::DebugPrintf("ScintillaGTK::realize this\n");
222 	gtk_widget_set_realized(widget, TRUE);
223 	GdkWindowAttr attrs;
224 	attrs.window_type = GDK_WINDOW_CHILD;
225 	GtkAllocation allocation;
226 	gtk_widget_get_allocation(widget, &allocation);
227 	attrs.x = allocation.x;
228 	attrs.y = allocation.y;
229 	attrs.width = allocation.width;
230 	attrs.height = allocation.height;
231 	attrs.wclass = GDK_INPUT_OUTPUT;
232 	attrs.visual = gtk_widget_get_visual(widget);
233 #if !GTK_CHECK_VERSION(3,0,0)
234 	attrs.colormap = gtk_widget_get_colormap(widget);
235 #endif
236 	attrs.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
237 	GdkDisplay *pdisplay = gtk_widget_get_display(widget);
238 	GdkCursor *cursor = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
239 	attrs.cursor = cursor;
240 #if GTK_CHECK_VERSION(3,0,0)
241 	gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attrs,
242 			      GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_CURSOR));
243 #if GTK_CHECK_VERSION(3,8,0)
244 	gtk_widget_register_window(widget, gtk_widget_get_window(widget));
245 #else
246 	gdk_window_set_user_data(gtk_widget_get_window(widget), widget);
247 #endif
248 #if !GTK_CHECK_VERSION(3,18,0)
249 	gtk_style_context_set_background(gtk_widget_get_style_context(widget),
250 					 gtk_widget_get_window(widget));
251 #endif
252 	gdk_window_show(gtk_widget_get_window(widget));
253 	UnRefCursor(cursor);
254 #else
255 	widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attrs,
256 					GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR);
257 	gdk_window_set_user_data(widget->window, widget);
258 	widget->style = gtk_style_attach(widget->style, widget->window);
259 	gdk_window_set_background(widget->window, &widget->style->bg[GTK_STATE_NORMAL]);
260 	gdk_window_show(widget->window);
261 	UnRefCursor(cursor);
262 #endif
263 
264 	preeditInitialized = false;
265 	gtk_widget_realize(PWidget(wPreedit));
266 	gtk_widget_realize(PWidget(wPreeditDraw));
267 
268 	im_context = gtk_im_multicontext_new();
269 	g_signal_connect(G_OBJECT(im_context), "commit",
270 			 G_CALLBACK(Commit), this);
271 	g_signal_connect(G_OBJECT(im_context), "preedit_changed",
272 			 G_CALLBACK(PreeditChanged), this);
273 	gtk_im_context_set_client_window(im_context, WindowFromWidget(widget));
274 
275 	GtkWidget *widtxt = PWidget(wText);	//	// No code inside the G_OBJECT macro
276 	g_signal_connect_after(G_OBJECT(widtxt), "style_set",
277 			       G_CALLBACK(ScintillaGTK::StyleSetText), nullptr);
278 	g_signal_connect_after(G_OBJECT(widtxt), "realize",
279 			       G_CALLBACK(ScintillaGTK::RealizeText), nullptr);
280 	gtk_widget_realize(widtxt);
281 	gtk_widget_realize(PWidget(scrollbarv));
282 	gtk_widget_realize(PWidget(scrollbarh));
283 
284 	cursor = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
285 	gdk_window_set_cursor(PWindow(wText), cursor);
286 	UnRefCursor(cursor);
287 
288 	cursor = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
289 	gdk_window_set_cursor(PWindow(scrollbarv), cursor);
290 	UnRefCursor(cursor);
291 
292 	cursor = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
293 	gdk_window_set_cursor(PWindow(scrollbarh), cursor);
294 	UnRefCursor(cursor);
295 
296 	wSelection = gtk_invisible_new();
297 	g_signal_connect(PWidget(wSelection), "selection_get", G_CALLBACK(PrimarySelection), (gpointer) this);
298 	g_signal_connect(PWidget(wSelection), "selection_clear_event", G_CALLBACK(PrimaryClear), (gpointer) this);
299 	gtk_selection_add_targets(PWidget(wSelection), GDK_SELECTION_PRIMARY,
300 				  clipboardCopyTargets, nClipboardCopyTargets);
301 }
302 
Realize(GtkWidget * widget)303 void ScintillaGTK::Realize(GtkWidget *widget) {
304 	ScintillaGTK *sciThis = FromWidget(widget);
305 	sciThis->RealizeThis(widget);
306 }
307 
UnRealizeThis(GtkWidget * widget)308 void ScintillaGTK::UnRealizeThis(GtkWidget *widget) {
309 	try {
310 		gtk_selection_clear_targets(PWidget(wSelection), GDK_SELECTION_PRIMARY);
311 		wSelection.Destroy();
312 
313 		if (IS_WIDGET_MAPPED(widget)) {
314 			gtk_widget_unmap(widget);
315 		}
316 		gtk_widget_set_realized(widget, FALSE);
317 		gtk_widget_unrealize(PWidget(wText));
318 		if (PWidget(scrollbarv))
319 			gtk_widget_unrealize(PWidget(scrollbarv));
320 		if (PWidget(scrollbarh))
321 			gtk_widget_unrealize(PWidget(scrollbarh));
322 		gtk_widget_unrealize(PWidget(wPreedit));
323 		gtk_widget_unrealize(PWidget(wPreeditDraw));
324 		g_object_unref(im_context);
325 		im_context = nullptr;
326 		if (GTK_WIDGET_CLASS(parentClass)->unrealize)
327 			GTK_WIDGET_CLASS(parentClass)->unrealize(widget);
328 
329 		Finalise();
330 	} catch (...) {
331 		errorStatus = SC_STATUS_FAILURE;
332 	}
333 }
334 
UnRealize(GtkWidget * widget)335 void ScintillaGTK::UnRealize(GtkWidget *widget) {
336 	ScintillaGTK *sciThis = FromWidget(widget);
337 	sciThis->UnRealizeThis(widget);
338 }
339 
MapWidget(GtkWidget * widget)340 static void MapWidget(GtkWidget *widget) noexcept {
341 	if (widget &&
342 			gtk_widget_get_visible(GTK_WIDGET(widget)) &&
343 			!IS_WIDGET_MAPPED(widget)) {
344 		gtk_widget_map(widget);
345 	}
346 }
347 
MapThis()348 void ScintillaGTK::MapThis() {
349 	try {
350 		//Platform::DebugPrintf("ScintillaGTK::map this\n");
351 		gtk_widget_set_mapped(PWidget(wMain), TRUE);
352 		MapWidget(PWidget(wText));
353 		MapWidget(PWidget(scrollbarh));
354 		MapWidget(PWidget(scrollbarv));
355 		wMain.SetCursor(Window::cursorArrow);
356 		scrollbarv.SetCursor(Window::cursorArrow);
357 		scrollbarh.SetCursor(Window::cursorArrow);
358 		ChangeSize();
359 		gdk_window_show(PWindow(wMain));
360 	} catch (...) {
361 		errorStatus = SC_STATUS_FAILURE;
362 	}
363 }
364 
Map(GtkWidget * widget)365 void ScintillaGTK::Map(GtkWidget *widget) {
366 	ScintillaGTK *sciThis = FromWidget(widget);
367 	sciThis->MapThis();
368 }
369 
UnMapThis()370 void ScintillaGTK::UnMapThis() {
371 	try {
372 		//Platform::DebugPrintf("ScintillaGTK::unmap this\n");
373 		gtk_widget_set_mapped(PWidget(wMain), FALSE);
374 		DropGraphics(false);
375 		gdk_window_hide(PWindow(wMain));
376 		gtk_widget_unmap(PWidget(wText));
377 		if (PWidget(scrollbarh))
378 			gtk_widget_unmap(PWidget(scrollbarh));
379 		if (PWidget(scrollbarv))
380 			gtk_widget_unmap(PWidget(scrollbarv));
381 	} catch (...) {
382 		errorStatus = SC_STATUS_FAILURE;
383 	}
384 }
385 
UnMap(GtkWidget * widget)386 void ScintillaGTK::UnMap(GtkWidget *widget) {
387 	ScintillaGTK *sciThis = FromWidget(widget);
388 	sciThis->UnMapThis();
389 }
390 
ForAll(GtkCallback callback,gpointer callback_data)391 void ScintillaGTK::ForAll(GtkCallback callback, gpointer callback_data) {
392 	try {
393 		(*callback)(PWidget(wText), callback_data);
394 		if (PWidget(scrollbarv))
395 			(*callback)(PWidget(scrollbarv), callback_data);
396 		if (PWidget(scrollbarh))
397 			(*callback)(PWidget(scrollbarh), callback_data);
398 	} catch (...) {
399 		errorStatus = SC_STATUS_FAILURE;
400 	}
401 }
402 
MainForAll(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)403 void ScintillaGTK::MainForAll(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) {
404 	ScintillaGTK *sciThis = FromWidget((GtkWidget *)container);
405 
406 	if (callback && include_internals) {
407 		sciThis->ForAll(callback, callback_data);
408 	}
409 }
410 
411 namespace {
412 
413 class PreEditString {
414 public:
415 	gchar *str;
416 	gint cursor_pos;
417 	PangoAttrList *attrs;
418 	gboolean validUTF8;
419 	glong uniStrLen;
420 	gunichar *uniStr;
421 	GUnicodeScript pscript;
422 
PreEditString(GtkIMContext * im_context)423 	explicit PreEditString(GtkIMContext *im_context) noexcept {
424 		gtk_im_context_get_preedit_string(im_context, &str, &attrs, &cursor_pos);
425 		validUTF8 = g_utf8_validate(str, strlen(str), nullptr);
426 		uniStr = g_utf8_to_ucs4_fast(str, strlen(str), &uniStrLen);
427 		pscript = g_unichar_get_script(uniStr[0]);
428 	}
429 	// Deleted so PreEditString objects can not be copied.
430 	PreEditString(const PreEditString&) = delete;
431 	PreEditString(PreEditString&&) = delete;
432 	PreEditString&operator=(const PreEditString&) = delete;
433 	PreEditString&operator=(PreEditString&&) = delete;
~PreEditString()434 	~PreEditString() {
435 		g_free(str);
436 		g_free(uniStr);
437 		pango_attr_list_unref(attrs);
438 	}
439 };
440 
441 }
442 
FocusInThis(GtkWidget *)443 gint ScintillaGTK::FocusInThis(GtkWidget *) {
444 	try {
445 		SetFocusState(true);
446 
447 		if (im_context) {
448 			gtk_im_context_focus_in(im_context);
449 			PreEditString pes(im_context);
450 			if (PWidget(wPreedit)) {
451 				if (!preeditInitialized) {
452 					GtkWidget *top = gtk_widget_get_toplevel(PWidget(wMain));
453 					gtk_window_set_transient_for(GTK_WINDOW(PWidget(wPreedit)), GTK_WINDOW(top));
454 					preeditInitialized = true;
455 				}
456 
457 				if (strlen(pes.str) > 0) {
458 					gtk_widget_show(PWidget(wPreedit));
459 				} else {
460 					gtk_widget_hide(PWidget(wPreedit));
461 				}
462 			}
463 		}
464 	} catch (...) {
465 		errorStatus = SC_STATUS_FAILURE;
466 	}
467 	return FALSE;
468 }
469 
FocusIn(GtkWidget * widget,GdkEventFocus *)470 gint ScintillaGTK::FocusIn(GtkWidget *widget, GdkEventFocus * /*event*/) {
471 	ScintillaGTK *sciThis = FromWidget(widget);
472 	return sciThis->FocusInThis(widget);
473 }
474 
FocusOutThis(GtkWidget *)475 gint ScintillaGTK::FocusOutThis(GtkWidget *) {
476 	try {
477 		SetFocusState(false);
478 
479 		if (PWidget(wPreedit))
480 			gtk_widget_hide(PWidget(wPreedit));
481 		if (im_context)
482 			gtk_im_context_focus_out(im_context);
483 
484 	} catch (...) {
485 		errorStatus = SC_STATUS_FAILURE;
486 	}
487 	return FALSE;
488 }
489 
FocusOut(GtkWidget * widget,GdkEventFocus *)490 gint ScintillaGTK::FocusOut(GtkWidget *widget, GdkEventFocus * /*event*/) {
491 	ScintillaGTK *sciThis = FromWidget(widget);
492 	return sciThis->FocusOutThis(widget);
493 }
494 
SizeRequest(GtkWidget * widget,GtkRequisition * requisition)495 void ScintillaGTK::SizeRequest(GtkWidget *widget, GtkRequisition *requisition) {
496 	ScintillaGTK *sciThis = FromWidget(widget);
497 	requisition->width = 1;
498 	requisition->height = 1;
499 	GtkRequisition child_requisition;
500 #if GTK_CHECK_VERSION(3,0,0)
501 	gtk_widget_get_preferred_size(PWidget(sciThis->scrollbarh), nullptr, &child_requisition);
502 	gtk_widget_get_preferred_size(PWidget(sciThis->scrollbarv), nullptr, &child_requisition);
503 #else
504 	gtk_widget_size_request(PWidget(sciThis->scrollbarh), &child_requisition);
505 	gtk_widget_size_request(PWidget(sciThis->scrollbarv), &child_requisition);
506 #endif
507 }
508 
509 #if GTK_CHECK_VERSION(3,0,0)
510 
GetPreferredWidth(GtkWidget * widget,gint * minimalWidth,gint * naturalWidth)511 void ScintillaGTK::GetPreferredWidth(GtkWidget *widget, gint *minimalWidth, gint *naturalWidth) {
512 	GtkRequisition requisition;
513 	SizeRequest(widget, &requisition);
514 	*minimalWidth = *naturalWidth = requisition.width;
515 }
516 
GetPreferredHeight(GtkWidget * widget,gint * minimalHeight,gint * naturalHeight)517 void ScintillaGTK::GetPreferredHeight(GtkWidget *widget, gint *minimalHeight, gint *naturalHeight) {
518 	GtkRequisition requisition;
519 	SizeRequest(widget, &requisition);
520 	*minimalHeight = *naturalHeight = requisition.height;
521 }
522 
523 #endif
524 
SizeAllocate(GtkWidget * widget,GtkAllocation * allocation)525 void ScintillaGTK::SizeAllocate(GtkWidget *widget, GtkAllocation *allocation) {
526 	ScintillaGTK *sciThis = FromWidget(widget);
527 	try {
528 		gtk_widget_set_allocation(widget, allocation);
529 		if (IS_WIDGET_REALIZED(widget))
530 			gdk_window_move_resize(WindowFromWidget(widget),
531 					       allocation->x,
532 					       allocation->y,
533 					       allocation->width,
534 					       allocation->height);
535 
536 		sciThis->Resize(allocation->width, allocation->height);
537 
538 	} catch (...) {
539 		sciThis->errorStatus = SC_STATUS_FAILURE;
540 	}
541 }
542 
Init()543 void ScintillaGTK::Init() {
544 	parentClass = static_cast<GtkWidgetClass *>(
545 			      g_type_class_ref(gtk_container_get_type()));
546 
547 	gint maskSmooth = 0;
548 #if defined(GDK_WINDOWING_WAYLAND)
549 	GdkDisplay *pdisplay = gdk_display_get_default();
550 	if (GDK_IS_WAYLAND_DISPLAY(pdisplay)) {
551 		// On Wayland, touch pads only produce smooth scroll events
552 		maskSmooth = GDK_SMOOTH_SCROLL_MASK;
553 	}
554 #endif
555 
556 	gtk_widget_set_can_focus(PWidget(wMain), TRUE);
557 	gtk_widget_set_sensitive(PWidget(wMain), TRUE);
558 	gtk_widget_set_events(PWidget(wMain),
559 			      GDK_EXPOSURE_MASK
560 			      | GDK_SCROLL_MASK
561 			      | maskSmooth
562 			      | GDK_STRUCTURE_MASK
563 			      | GDK_KEY_PRESS_MASK
564 			      | GDK_KEY_RELEASE_MASK
565 			      | GDK_FOCUS_CHANGE_MASK
566 			      | GDK_LEAVE_NOTIFY_MASK
567 			      | GDK_BUTTON_PRESS_MASK
568 			      | GDK_BUTTON_RELEASE_MASK
569 			      | GDK_POINTER_MOTION_MASK
570 			      | GDK_POINTER_MOTION_HINT_MASK);
571 
572 	wText = gtk_drawing_area_new();
573 	gtk_widget_set_parent(PWidget(wText), PWidget(wMain));
574 	GtkWidget *widtxt = PWidget(wText);	// No code inside the G_OBJECT macro
575 	gtk_widget_show(widtxt);
576 #if GTK_CHECK_VERSION(3,0,0)
577 	g_signal_connect(G_OBJECT(widtxt), "draw",
578 			 G_CALLBACK(ScintillaGTK::DrawText), this);
579 #else
580 	g_signal_connect(G_OBJECT(widtxt), "expose_event",
581 			 G_CALLBACK(ScintillaGTK::ExposeText), this);
582 #endif
583 #if GTK_CHECK_VERSION(3,0,0)
584 	// we need a runtime check because we don't want double buffering when
585 	// running on >= 3.9.2
586 	if (gtk_check_version(3, 9, 2) != nullptr /* on < 3.9.2 */)
587 #endif
588 	{
589 #if !GTK_CHECK_VERSION(3,14,0)
590 		// Avoid background drawing flash/missing redraws
591 		gtk_widget_set_double_buffered(widtxt, FALSE);
592 #endif
593 	}
594 	gtk_widget_set_events(widtxt, GDK_EXPOSURE_MASK);
595 	gtk_widget_set_size_request(widtxt, 100, 100);
596 	adjustmentv = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 201.0, 1.0, 20.0, 20.0));
597 #if GTK_CHECK_VERSION(3,0,0)
598 	scrollbarv = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(adjustmentv));
599 #else
600 	scrollbarv = gtk_vscrollbar_new(GTK_ADJUSTMENT(adjustmentv));
601 #endif
602 	gtk_widget_set_can_focus(PWidget(scrollbarv), FALSE);
603 	g_signal_connect(G_OBJECT(adjustmentv), "value_changed",
604 			 G_CALLBACK(ScrollSignal), this);
605 	gtk_widget_set_parent(PWidget(scrollbarv), PWidget(wMain));
606 	gtk_widget_show(PWidget(scrollbarv));
607 
608 	adjustmenth = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 101.0, 1.0, 20.0, 20.0));
609 #if GTK_CHECK_VERSION(3,0,0)
610 	scrollbarh = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(adjustmenth));
611 #else
612 	scrollbarh = gtk_hscrollbar_new(GTK_ADJUSTMENT(adjustmenth));
613 #endif
614 	gtk_widget_set_can_focus(PWidget(scrollbarh), FALSE);
615 	g_signal_connect(G_OBJECT(adjustmenth), "value_changed",
616 			 G_CALLBACK(ScrollHSignal), this);
617 	gtk_widget_set_parent(PWidget(scrollbarh), PWidget(wMain));
618 	gtk_widget_show(PWidget(scrollbarh));
619 
620 	gtk_widget_grab_focus(PWidget(wMain));
621 
622 	gtk_drag_dest_set(GTK_WIDGET(PWidget(wMain)),
623 			  GTK_DEST_DEFAULT_ALL, clipboardPasteTargets, nClipboardPasteTargets,
624 			  actionCopyOrMove);
625 
626 	/* create pre-edit window */
627 	wPreedit = gtk_window_new(GTK_WINDOW_POPUP);
628 	wPreeditDraw = gtk_drawing_area_new();
629 	GtkWidget *predrw = PWidget(wPreeditDraw);      // No code inside the G_OBJECT macro
630 #if GTK_CHECK_VERSION(3,0,0)
631 	g_signal_connect(G_OBJECT(predrw), "draw",
632 			 G_CALLBACK(DrawPreedit), this);
633 #else
634 	g_signal_connect(G_OBJECT(predrw), "expose_event",
635 			 G_CALLBACK(ExposePreedit), this);
636 #endif
637 	gtk_container_add(GTK_CONTAINER(PWidget(wPreedit)), predrw);
638 	gtk_widget_show(predrw);
639 
640 	// Set caret period based on GTK settings
641 	gboolean blinkOn = false;
642 	if (g_object_class_find_property(G_OBJECT_GET_CLASS(
643 			G_OBJECT(gtk_settings_get_default())), "gtk-cursor-blink")) {
644 		g_object_get(G_OBJECT(
645 				     gtk_settings_get_default()), "gtk-cursor-blink", &blinkOn, nullptr);
646 	}
647 	if (blinkOn &&
648 			g_object_class_find_property(G_OBJECT_GET_CLASS(
649 						G_OBJECT(gtk_settings_get_default())), "gtk-cursor-blink-time")) {
650 		gint value;
651 		g_object_get(G_OBJECT(
652 				     gtk_settings_get_default()), "gtk-cursor-blink-time", &value, nullptr);
653 		caret.period = static_cast<int>(value / 1.75);
654 	} else {
655 		caret.period = 0;
656 	}
657 
658 	for (TickReason tr = tickCaret; tr <= tickDwell; tr = static_cast<TickReason>(tr + 1)) {
659 		timers[tr].reason = tr;
660 		timers[tr].scintilla = this;
661 	}
662 	vs.indicators[SC_INDICATOR_UNKNOWN] = Indicator(INDIC_HIDDEN, ColourDesired(0, 0, 0xff));
663 	vs.indicators[SC_INDICATOR_INPUT] = Indicator(INDIC_DOTS, ColourDesired(0, 0, 0xff));
664 	vs.indicators[SC_INDICATOR_CONVERTED] = Indicator(INDIC_COMPOSITIONTHICK, ColourDesired(0, 0, 0xff));
665 	vs.indicators[SC_INDICATOR_TARGET] = Indicator(INDIC_STRAIGHTBOX, ColourDesired(0, 0, 0xff));
666 }
667 
Finalise()668 void ScintillaGTK::Finalise() {
669 	for (TickReason tr = tickCaret; tr <= tickDwell; tr = static_cast<TickReason>(tr + 1)) {
670 		FineTickerCancel(tr);
671 	}
672 	if (accessible) {
673 		gtk_accessible_set_widget(GTK_ACCESSIBLE(accessible), nullptr);
674 		g_object_unref(accessible);
675 		accessible = nullptr;
676 	}
677 
678 	ScintillaBase::Finalise();
679 }
680 
AbandonPaint()681 bool ScintillaGTK::AbandonPaint() {
682 	if ((paintState == painting) && !paintingAllText) {
683 		repaintFullWindow = true;
684 	}
685 	return false;
686 }
687 
DisplayCursor(Window::Cursor c)688 void ScintillaGTK::DisplayCursor(Window::Cursor c) {
689 	if (cursorMode == SC_CURSORNORMAL)
690 		wText.SetCursor(c);
691 	else
692 		wText.SetCursor(static_cast<Window::Cursor>(cursorMode));
693 }
694 
DragThreshold(Point ptStart,Point ptNow)695 bool ScintillaGTK::DragThreshold(Point ptStart, Point ptNow) {
696 	return gtk_drag_check_threshold(GTK_WIDGET(PWidget(wMain)),
697 					ptStart.x, ptStart.y, ptNow.x, ptNow.y);
698 }
699 
StartDrag()700 void ScintillaGTK::StartDrag() {
701 	PLATFORM_ASSERT(evbtn);
702 	dragWasDropped = false;
703 	inDragDrop = ddDragging;
704 	GtkTargetList *tl = gtk_target_list_new(clipboardCopyTargets, nClipboardCopyTargets);
705 #if GTK_CHECK_VERSION(3,10,0)
706 	gtk_drag_begin_with_coordinates(GTK_WIDGET(PWidget(wMain)),
707 					tl,
708 					actionCopyOrMove,
709 					buttonMouse,
710 					evbtn,
711 					-1, -1);
712 #else
713 	gtk_drag_begin(GTK_WIDGET(PWidget(wMain)),
714 		       tl,
715 		       actionCopyOrMove,
716 		       buttonMouse,
717 		       evbtn);
718 #endif
719 }
720 
721 namespace Scintilla {
ConvertText(const char * s,size_t len,const char * charSetDest,const char * charSetSource,bool transliterations,bool silent)722 std::string ConvertText(const char *s, size_t len, const char *charSetDest,
723 			const char *charSetSource, bool transliterations, bool silent) {
724 	// s is not const because of different versions of iconv disagreeing about const
725 	std::string destForm;
726 	Converter conv(charSetDest, charSetSource, transliterations);
727 	if (conv) {
728 		gsize outLeft = len*3+1;
729 		destForm = std::string(outLeft, '\0');
730 		// g_iconv does not actually write to its input argument so safe to cast away const
731 		char *pin = const_cast<char *>(s);
732 		gsize inLeft = len;
733 		char *putf = &destForm[0];
734 		char *pout = putf;
735 		const gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
736 		if (conversions == sizeFailure) {
737 			if (!silent) {
738 				if (len == 1)
739 					fprintf(stderr, "iconv %s->%s failed for %0x '%s'\n",
740 						charSetSource, charSetDest, (unsigned char)(*s), s);
741 				else
742 					fprintf(stderr, "iconv %s->%s failed for %s\n",
743 						charSetSource, charSetDest, s);
744 			}
745 			destForm = std::string();
746 		} else {
747 			destForm.resize(pout - putf);
748 		}
749 	} else {
750 		fprintf(stderr, "Can not iconv %s %s\n", charSetDest, charSetSource);
751 	}
752 	return destForm;
753 }
754 }
755 
756 // Returns the target converted to UTF8.
757 // Return the length in bytes.
TargetAsUTF8(char * text) const758 Sci::Position ScintillaGTK::TargetAsUTF8(char *text) const {
759 	const Sci::Position targetLength = targetRange.Length();
760 	if (IsUnicodeMode()) {
761 		if (text) {
762 			pdoc->GetCharRange(text, targetRange.start.Position(), targetLength);
763 		}
764 	} else {
765 		// Need to convert
766 		const char *charSetBuffer = CharacterSetID();
767 		if (*charSetBuffer) {
768 			std::string s = RangeText(targetRange.start.Position(), targetRange.end.Position());
769 			std::string tmputf = ConvertText(&s[0], targetLength, "UTF-8", charSetBuffer, false);
770 			if (text) {
771 				memcpy(text, tmputf.c_str(), tmputf.length());
772 			}
773 			return tmputf.length();
774 		} else {
775 			if (text) {
776 				pdoc->GetCharRange(text, targetRange.start.Position(), targetLength);
777 			}
778 		}
779 	}
780 	return targetLength;
781 }
782 
783 // Translates a nul terminated UTF8 string into the document encoding.
784 // Return the length of the result in bytes.
EncodedFromUTF8(const char * utf8,char * encoded) const785 Sci::Position ScintillaGTK::EncodedFromUTF8(const char *utf8, char *encoded) const {
786 	const Sci::Position inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8);
787 	if (IsUnicodeMode()) {
788 		if (encoded) {
789 			memcpy(encoded, utf8, inputLength);
790 		}
791 		return inputLength;
792 	} else {
793 		// Need to convert
794 		const char *charSetBuffer = CharacterSetID();
795 		if (*charSetBuffer) {
796 			std::string tmpEncoded = ConvertText(utf8, inputLength, charSetBuffer, "UTF-8", true);
797 			if (encoded) {
798 				memcpy(encoded, tmpEncoded.c_str(), tmpEncoded.length());
799 			}
800 			return tmpEncoded.length();
801 		} else {
802 			if (encoded) {
803 				memcpy(encoded, utf8, inputLength);
804 			}
805 			return inputLength;
806 		}
807 	}
808 	// Fail
809 	return 0;
810 }
811 
ValidCodePage(int codePage) const812 bool ScintillaGTK::ValidCodePage(int codePage) const {
813 	return codePage == 0
814 	       || codePage == SC_CP_UTF8
815 	       || codePage == 932
816 	       || codePage == 936
817 	       || codePage == 949
818 	       || codePage == 950
819 	       || codePage == 1361;
820 }
821 
WndProc(unsigned int iMessage,uptr_t wParam,sptr_t lParam)822 sptr_t ScintillaGTK::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
823 	try {
824 		switch (iMessage) {
825 
826 		case SCI_GRABFOCUS:
827 			gtk_widget_grab_focus(PWidget(wMain));
828 			break;
829 
830 		case SCI_GETDIRECTFUNCTION:
831 			return reinterpret_cast<sptr_t>(DirectFunction);
832 
833 		case SCI_GETDIRECTPOINTER:
834 			return reinterpret_cast<sptr_t>(this);
835 
836 		case SCI_TARGETASUTF8:
837 			return TargetAsUTF8(CharPtrFromSPtr(lParam));
838 
839 		case SCI_ENCODEDFROMUTF8:
840 			return EncodedFromUTF8(ConstCharPtrFromUPtr(wParam),
841 					       CharPtrFromSPtr(lParam));
842 
843 		case SCI_SETRECTANGULARSELECTIONMODIFIER:
844 			rectangularSelectionModifier = wParam;
845 			break;
846 
847 		case SCI_GETRECTANGULARSELECTIONMODIFIER:
848 			return rectangularSelectionModifier;
849 
850 		case SCI_SETREADONLY: {
851 				const sptr_t ret = ScintillaBase::WndProc(iMessage, wParam, lParam);
852 				if (accessible) {
853 					ScintillaGTKAccessible *sciAccessible = ScintillaGTKAccessible::FromAccessible(accessible);
854 					if (sciAccessible) {
855 						sciAccessible->NotifyReadOnly();
856 					}
857 				}
858 				return ret;
859 			}
860 
861 		case SCI_GETACCESSIBILITY:
862 			return accessibilityEnabled;
863 
864 		case SCI_SETACCESSIBILITY:
865 			accessibilityEnabled = wParam;
866 			if (accessible) {
867 				ScintillaGTKAccessible *sciAccessible = ScintillaGTKAccessible::FromAccessible(accessible);
868 				if (sciAccessible) {
869 					sciAccessible->SetAccessibility(accessibilityEnabled);
870 				}
871 			}
872 			break;
873 
874 		default:
875 			return ScintillaBase::WndProc(iMessage, wParam, lParam);
876 		}
877 	} catch (std::bad_alloc &) {
878 		errorStatus = SC_STATUS_BADALLOC;
879 	} catch (...) {
880 		errorStatus = SC_STATUS_FAILURE;
881 	}
882 	return 0;
883 }
884 
DefWndProc(unsigned int,uptr_t,sptr_t)885 sptr_t ScintillaGTK::DefWndProc(unsigned int, uptr_t, sptr_t) {
886 	return 0;
887 }
888 
FineTickerRunning(TickReason reason)889 bool ScintillaGTK::FineTickerRunning(TickReason reason) {
890 	return timers[reason].timer != 0;
891 }
892 
FineTickerStart(TickReason reason,int millis,int)893 void ScintillaGTK::FineTickerStart(TickReason reason, int millis, int /* tolerance */) {
894 	FineTickerCancel(reason);
895 	timers[reason].timer = gdk_threads_add_timeout(millis, TimeOut, &timers[reason]);
896 }
897 
FineTickerCancel(TickReason reason)898 void ScintillaGTK::FineTickerCancel(TickReason reason) {
899 	if (timers[reason].timer) {
900 		g_source_remove(timers[reason].timer);
901 		timers[reason].timer = 0;
902 	}
903 }
904 
SetIdle(bool on)905 bool ScintillaGTK::SetIdle(bool on) {
906 	if (on) {
907 		// Start idler, if it's not running.
908 		if (!idler.state) {
909 			idler.state = true;
910 			idler.idlerID = GUINT_TO_POINTER(
911 						gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE, IdleCallback, this, nullptr));
912 		}
913 	} else {
914 		// Stop idler, if it's running
915 		if (idler.state) {
916 			idler.state = false;
917 			g_source_remove(GPOINTER_TO_UINT(idler.idlerID));
918 		}
919 	}
920 	return true;
921 }
922 
SetMouseCapture(bool on)923 void ScintillaGTK::SetMouseCapture(bool on) {
924 	if (mouseDownCaptures) {
925 		if (on) {
926 			gtk_grab_add(GTK_WIDGET(PWidget(wMain)));
927 		} else {
928 			gtk_grab_remove(GTK_WIDGET(PWidget(wMain)));
929 		}
930 	}
931 	capturedMouse = on;
932 }
933 
HaveMouseCapture()934 bool ScintillaGTK::HaveMouseCapture() {
935 	return capturedMouse;
936 }
937 
938 #if GTK_CHECK_VERSION(3,0,0)
939 
940 // Is crcTest completely in crcContainer?
CRectContains(const cairo_rectangle_t & crcContainer,const cairo_rectangle_t & crcTest)941 static bool CRectContains(const cairo_rectangle_t &crcContainer, const cairo_rectangle_t &crcTest) {
942 	return
943 		(crcTest.x >= crcContainer.x) && ((crcTest.x + crcTest.width) <= (crcContainer.x + crcContainer.width)) &&
944 		(crcTest.y >= crcContainer.y) && ((crcTest.y + crcTest.height) <= (crcContainer.y + crcContainer.height));
945 }
946 
947 // Is crcTest completely in crcListContainer?
948 // May incorrectly return false if complex shape
CRectListContains(const cairo_rectangle_list_t * crcListContainer,const cairo_rectangle_t & crcTest)949 static bool CRectListContains(const cairo_rectangle_list_t *crcListContainer, const cairo_rectangle_t &crcTest) {
950 	for (int r=0; r<crcListContainer->num_rectangles; r++) {
951 		if (CRectContains(crcListContainer->rectangles[r], crcTest))
952 			return true;
953 	}
954 	return false;
955 }
956 
957 #endif
958 
PaintContains(PRectangle rc)959 bool ScintillaGTK::PaintContains(PRectangle rc) {
960 	// This allows optimization when a rectangle is completely in the update region.
961 	// It is OK to return false when too difficult to determine as that just performs extra drawing
962 	bool contains = true;
963 	if (paintState == painting) {
964 		if (!rcPaint.Contains(rc)) {
965 			contains = false;
966 		} else if (rgnUpdate) {
967 #if GTK_CHECK_VERSION(3,0,0)
968 			cairo_rectangle_t grc = {rc.left, rc.top,
969 						 rc.right - rc.left, rc.bottom - rc.top
970 						};
971 			contains = CRectListContains(rgnUpdate, grc);
972 #else
973 			GdkRectangle grc = {static_cast<gint>(rc.left), static_cast<gint>(rc.top),
974 					    static_cast<gint>(rc.right - rc.left), static_cast<gint>(rc.bottom - rc.top)
975 					   };
976 			if (gdk_region_rect_in(rgnUpdate, &grc) != GDK_OVERLAP_RECTANGLE_IN) {
977 				contains = false;
978 			}
979 #endif
980 		}
981 	}
982 	return contains;
983 }
984 
985 // Redraw all of text area. This paint will not be abandoned.
FullPaint()986 void ScintillaGTK::FullPaint() {
987 	wText.InvalidateAll();
988 }
989 
GetClientRectangle() const990 PRectangle ScintillaGTK::GetClientRectangle() const {
991 	PRectangle rc = wMain.GetClientPosition();
992 	if (verticalScrollBarVisible)
993 		rc.right -= verticalScrollBarWidth;
994 	if (horizontalScrollBarVisible && !Wrapping())
995 		rc.bottom -= horizontalScrollBarHeight;
996 	// Move to origin
997 	rc.right -= rc.left;
998 	rc.bottom -= rc.top;
999 	if (rc.bottom < 0)
1000 		rc.bottom = 0;
1001 	if (rc.right < 0)
1002 		rc.right = 0;
1003 	rc.left = 0;
1004 	rc.top = 0;
1005 	return rc;
1006 }
1007 
ScrollText(Sci::Line linesToMove)1008 void ScintillaGTK::ScrollText(Sci::Line linesToMove) {
1009 	NotifyUpdateUI();
1010 
1011 #if GTK_CHECK_VERSION(3,22,0)
1012 	Redraw();
1013 #else
1014 	GtkWidget *wi = PWidget(wText);
1015 	if (IS_WIDGET_REALIZED(wi)) {
1016 		const int diff = vs.lineHeight * -linesToMove;
1017 		gdk_window_scroll(WindowFromWidget(wi), 0, -diff);
1018 		gdk_window_process_updates(WindowFromWidget(wi), FALSE);
1019 	}
1020 #endif
1021 }
1022 
SetVerticalScrollPos()1023 void ScintillaGTK::SetVerticalScrollPos() {
1024 	DwellEnd(true);
1025 	gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmentv), topLine);
1026 }
1027 
SetHorizontalScrollPos()1028 void ScintillaGTK::SetHorizontalScrollPos() {
1029 	DwellEnd(true);
1030 	gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmenth), xOffset);
1031 }
1032 
ModifyScrollBars(Sci::Line nMax,Sci::Line nPage)1033 bool ScintillaGTK::ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) {
1034 	bool modified = false;
1035 	const int pageScroll = LinesToScroll();
1036 
1037 	if (gtk_adjustment_get_upper(adjustmentv) != (nMax + 1) ||
1038 			gtk_adjustment_get_page_size(adjustmentv) != nPage ||
1039 			gtk_adjustment_get_page_increment(adjustmentv) != pageScroll) {
1040 		gtk_adjustment_set_upper(adjustmentv, nMax + 1);
1041 		gtk_adjustment_set_page_size(adjustmentv, nPage);
1042 		gtk_adjustment_set_page_increment(adjustmentv, pageScroll);
1043 #if !GTK_CHECK_VERSION(3,18,0)
1044 		gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmentv));
1045 #endif
1046 		modified = true;
1047 	}
1048 
1049 	const PRectangle rcText = GetTextRectangle();
1050 	int horizEndPreferred = scrollWidth;
1051 	if (horizEndPreferred < 0)
1052 		horizEndPreferred = 0;
1053 	const unsigned int pageWidth = static_cast<unsigned int>(rcText.Width());
1054 	const unsigned int pageIncrement = pageWidth / 3;
1055 	const unsigned int charWidth = vs.styles[STYLE_DEFAULT].aveCharWidth;
1056 	if (gtk_adjustment_get_upper(adjustmenth) != horizEndPreferred ||
1057 			gtk_adjustment_get_page_size(adjustmenth) != pageWidth ||
1058 			gtk_adjustment_get_page_increment(adjustmenth) != pageIncrement ||
1059 			gtk_adjustment_get_step_increment(adjustmenth) != charWidth) {
1060 		gtk_adjustment_set_upper(adjustmenth, horizEndPreferred);
1061 		gtk_adjustment_set_page_size(adjustmenth, pageWidth);
1062 		gtk_adjustment_set_page_increment(adjustmenth, pageIncrement);
1063 		gtk_adjustment_set_step_increment(adjustmenth, charWidth);
1064 #if !GTK_CHECK_VERSION(3,18,0)
1065 		gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmenth));
1066 #endif
1067 		modified = true;
1068 	}
1069 	if (modified && (paintState == painting)) {
1070 		repaintFullWindow = true;
1071 	}
1072 
1073 	return modified;
1074 }
1075 
ReconfigureScrollBars()1076 void ScintillaGTK::ReconfigureScrollBars() {
1077 	const PRectangle rc = wMain.GetClientPosition();
1078 	Resize(static_cast<int>(rc.Width()), static_cast<int>(rc.Height()));
1079 }
1080 
NotifyChange()1081 void ScintillaGTK::NotifyChange() {
1082 	g_signal_emit(G_OBJECT(sci), scintilla_signals[COMMAND_SIGNAL], 0,
1083 		      Platform::LongFromTwoShorts(GetCtrlID(), SCEN_CHANGE), PWidget(wMain));
1084 }
1085 
NotifyFocus(bool focus)1086 void ScintillaGTK::NotifyFocus(bool focus) {
1087 	if (commandEvents)
1088 		g_signal_emit(G_OBJECT(sci), scintilla_signals[COMMAND_SIGNAL], 0,
1089 			      Platform::LongFromTwoShorts
1090 			      (GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS), PWidget(wMain));
1091 	Editor::NotifyFocus(focus);
1092 }
1093 
NotifyParent(SCNotification scn)1094 void ScintillaGTK::NotifyParent(SCNotification scn) {
1095 	scn.nmhdr.hwndFrom = PWidget(wMain);
1096 	scn.nmhdr.idFrom = GetCtrlID();
1097 	g_signal_emit(G_OBJECT(sci), scintilla_signals[NOTIFY_SIGNAL], 0,
1098 		      GetCtrlID(), &scn);
1099 }
1100 
NotifyKey(int key,int modifiers)1101 void ScintillaGTK::NotifyKey(int key, int modifiers) {
1102 	SCNotification scn = {};
1103 	scn.nmhdr.code = SCN_KEY;
1104 	scn.ch = key;
1105 	scn.modifiers = modifiers;
1106 
1107 	NotifyParent(scn);
1108 }
1109 
NotifyURIDropped(const char * list)1110 void ScintillaGTK::NotifyURIDropped(const char *list) {
1111 	SCNotification scn = {};
1112 	scn.nmhdr.code = SCN_URIDROPPED;
1113 	scn.text = list;
1114 
1115 	NotifyParent(scn);
1116 }
1117 
1118 const char *CharacterSetID(int characterSet);
1119 
CharacterSetID() const1120 const char *ScintillaGTK::CharacterSetID() const {
1121 	return ::CharacterSetID(vs.styles[STYLE_DEFAULT].characterSet);
1122 }
1123 
1124 class CaseFolderDBCS : public CaseFolderTable {
1125 	const char *charSet;
1126 public:
CaseFolderDBCS(const char * charSet_)1127 	explicit CaseFolderDBCS(const char *charSet_) : charSet(charSet_) {
1128 		StandardASCII();
1129 	}
Fold(char * folded,size_t sizeFolded,const char * mixed,size_t lenMixed)1130 	size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) override {
1131 		if ((lenMixed == 1) && (sizeFolded > 0)) {
1132 			folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
1133 			return 1;
1134 		} else if (*charSet) {
1135 			std::string sUTF8 = ConvertText(mixed, lenMixed,
1136 							"UTF-8", charSet, false);
1137 			if (!sUTF8.empty()) {
1138 				gchar *mapped = g_utf8_casefold(sUTF8.c_str(), sUTF8.length());
1139 				size_t lenMapped = strlen(mapped);
1140 				if (lenMapped < sizeFolded) {
1141 					memcpy(folded, mapped,  lenMapped);
1142 				} else {
1143 					folded[0] = '\0';
1144 					lenMapped = 1;
1145 				}
1146 				g_free(mapped);
1147 				return lenMapped;
1148 			}
1149 		}
1150 		// Something failed so return a single NUL byte
1151 		folded[0] = '\0';
1152 		return 1;
1153 	}
1154 };
1155 
CaseFolderForEncoding()1156 CaseFolder *ScintillaGTK::CaseFolderForEncoding() {
1157 	if (pdoc->dbcsCodePage == SC_CP_UTF8) {
1158 		return new CaseFolderUnicode();
1159 	} else {
1160 		const char *charSetBuffer = CharacterSetID();
1161 		if (charSetBuffer) {
1162 			if (pdoc->dbcsCodePage == 0) {
1163 				CaseFolderTable *pcf = new CaseFolderTable();
1164 				pcf->StandardASCII();
1165 				// Only for single byte encodings
1166 				for (int i=0x80; i<0x100; i++) {
1167 					char sCharacter[2] = "A";
1168 					sCharacter[0] = i;
1169 					// Silent as some bytes have no assigned character
1170 					std::string sUTF8 = ConvertText(sCharacter, 1,
1171 									"UTF-8", charSetBuffer, false, true);
1172 					if (!sUTF8.empty()) {
1173 						gchar *mapped = g_utf8_casefold(sUTF8.c_str(), sUTF8.length());
1174 						if (mapped) {
1175 							std::string mappedBack = ConvertText(mapped, strlen(mapped),
1176 											     charSetBuffer, "UTF-8", false, true);
1177 							if ((mappedBack.length() == 1) && (mappedBack[0] != sCharacter[0])) {
1178 								pcf->SetTranslation(sCharacter[0], mappedBack[0]);
1179 							}
1180 							g_free(mapped);
1181 						}
1182 					}
1183 				}
1184 				return pcf;
1185 			} else {
1186 				return new CaseFolderDBCS(charSetBuffer);
1187 			}
1188 		}
1189 		return nullptr;
1190 	}
1191 }
1192 
1193 namespace {
1194 
1195 struct CaseMapper {
1196 	gchar *mapped;	// Must be freed with g_free
CaseMapper__anon3eaa4a570411::CaseMapper1197 	CaseMapper(const std::string &sUTF8, bool toUpperCase) {
1198 		if (toUpperCase) {
1199 			mapped = g_utf8_strup(sUTF8.c_str(), sUTF8.length());
1200 		} else {
1201 			mapped = g_utf8_strdown(sUTF8.c_str(), sUTF8.length());
1202 		}
1203 	}
1204 	// Deleted so CaseMapper objects can not be copied.
1205 	CaseMapper(const CaseMapper&) = delete;
1206 	CaseMapper(CaseMapper&&) = delete;
1207 	CaseMapper&operator=(const CaseMapper&) = delete;
1208 	CaseMapper&operator=(CaseMapper&&) = delete;
~CaseMapper__anon3eaa4a570411::CaseMapper1209 	~CaseMapper() {
1210 		g_free(mapped);
1211 	}
1212 };
1213 
1214 }
1215 
CaseMapString(const std::string & s,int caseMapping)1216 std::string ScintillaGTK::CaseMapString(const std::string &s, int caseMapping) {
1217 	if ((s.size() == 0) || (caseMapping == cmSame))
1218 		return s;
1219 
1220 	if (IsUnicodeMode()) {
1221 		std::string retMapped(s.length() * maxExpansionCaseConversion, 0);
1222 		const size_t lenMapped = CaseConvertString(&retMapped[0], retMapped.length(), s.c_str(), s.length(),
1223 					 (caseMapping == cmUpper) ? CaseConversionUpper : CaseConversionLower);
1224 		retMapped.resize(lenMapped);
1225 		return retMapped;
1226 	}
1227 
1228 	const char *charSetBuffer = CharacterSetID();
1229 
1230 	if (!*charSetBuffer) {
1231 		CaseMapper mapper(s, caseMapping == cmUpper);
1232 		return std::string(mapper.mapped, strlen(mapper.mapped));
1233 	} else {
1234 		// Change text to UTF-8
1235 		std::string sUTF8 = ConvertText(s.c_str(), s.length(),
1236 						"UTF-8", charSetBuffer, false);
1237 		CaseMapper mapper(sUTF8, caseMapping == cmUpper);
1238 		return ConvertText(mapper.mapped, strlen(mapper.mapped), charSetBuffer, "UTF-8", false);
1239 	}
1240 }
1241 
KeyDefault(int key,int modifiers)1242 int ScintillaGTK::KeyDefault(int key, int modifiers) {
1243 	// Pass up to container in case it is an accelerator
1244 	NotifyKey(key, modifiers);
1245 	return 0;
1246 }
1247 
CopyToClipboard(const SelectionText & selectedText)1248 void ScintillaGTK::CopyToClipboard(const SelectionText &selectedText) {
1249 	SelectionText *clipText = new SelectionText();
1250 	clipText->Copy(selectedText);
1251 	StoreOnClipboard(clipText);
1252 }
1253 
Copy()1254 void ScintillaGTK::Copy() {
1255 	if (!sel.Empty()) {
1256 		SelectionText *clipText = new SelectionText();
1257 		CopySelectionRange(clipText);
1258 		StoreOnClipboard(clipText);
1259 #if PLAT_GTK_WIN32
1260 		if (sel.IsRectangular()) {
1261 			::OpenClipboard(NULL);
1262 			::SetClipboardData(cfColumnSelect, 0);
1263 			::CloseClipboard();
1264 		}
1265 #endif
1266 	}
1267 }
1268 
1269 namespace {
1270 
1271 // Helper class for the asynchronous paste not to risk calling in a destroyed ScintillaGTK
1272 
1273 class SelectionReceiver : GObjectWatcher {
1274 	ScintillaGTK *sci;
1275 
Destroyed()1276 	void Destroyed() noexcept override {
1277 		sci = nullptr;
1278 	}
1279 
1280 public:
SelectionReceiver(ScintillaGTK * sci_)1281 	SelectionReceiver(ScintillaGTK *sci_) :
1282 		GObjectWatcher(G_OBJECT(sci_->MainObject())),
1283 		sci(sci_) {
1284 	}
1285 
ClipboardReceived(GtkClipboard * clipboard,GtkSelectionData * selection_data,gpointer data)1286 	static void ClipboardReceived(GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data) {
1287 		SelectionReceiver *self = static_cast<SelectionReceiver *>(data);
1288 		if (self->sci) {
1289 			self->sci->ReceivedClipboard(clipboard, selection_data);
1290 		}
1291 		delete self;
1292 	}
1293 };
1294 
1295 }
1296 
RequestSelection(GdkAtom atomSelection)1297 void ScintillaGTK::RequestSelection(GdkAtom atomSelection) {
1298 	atomSought = atomUTF8;
1299 	GtkClipboard *clipBoard =
1300 		gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), atomSelection);
1301 	if (clipBoard) {
1302 		gtk_clipboard_request_contents(clipBoard, atomSought,
1303 					       SelectionReceiver::ClipboardReceived,
1304 					       new SelectionReceiver(this));
1305 	}
1306 }
1307 
Paste()1308 void ScintillaGTK::Paste() {
1309 	RequestSelection(GDK_SELECTION_CLIPBOARD);
1310 }
1311 
CreateCallTipWindow(PRectangle rc)1312 void ScintillaGTK::CreateCallTipWindow(PRectangle rc) {
1313 	if (!ct.wCallTip.Created()) {
1314 		ct.wCallTip = gtk_window_new(GTK_WINDOW_POPUP);
1315 		ct.wDraw = gtk_drawing_area_new();
1316 		GtkWidget *widcdrw = PWidget(ct.wDraw);	//	// No code inside the G_OBJECT macro
1317 		gtk_container_add(GTK_CONTAINER(PWidget(ct.wCallTip)), widcdrw);
1318 #if GTK_CHECK_VERSION(3,0,0)
1319 		g_signal_connect(G_OBJECT(widcdrw), "draw",
1320 				 G_CALLBACK(ScintillaGTK::DrawCT), &ct);
1321 #else
1322 		g_signal_connect(G_OBJECT(widcdrw), "expose_event",
1323 				 G_CALLBACK(ScintillaGTK::ExposeCT), &ct);
1324 #endif
1325 		g_signal_connect(G_OBJECT(widcdrw), "button_press_event",
1326 				 G_CALLBACK(ScintillaGTK::PressCT), this);
1327 		gtk_widget_set_events(widcdrw,
1328 				      GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);
1329 		GtkWidget *top = gtk_widget_get_toplevel(PWidget(wMain));
1330 		gtk_window_set_transient_for(GTK_WINDOW(PWidget(ct.wCallTip)), GTK_WINDOW(top));
1331 	}
1332 	const int width = static_cast<int>(rc.Width());
1333 	const int height = static_cast<int>(rc.Height());
1334 	gtk_widget_set_size_request(PWidget(ct.wDraw), width, height);
1335 	ct.wDraw.Show();
1336 	if (PWindow(ct.wCallTip)) {
1337 		gdk_window_resize(PWindow(ct.wCallTip), width, height);
1338 	}
1339 }
1340 
AddToPopUp(const char * label,int cmd,bool enabled)1341 void ScintillaGTK::AddToPopUp(const char *label, int cmd, bool enabled) {
1342 	GtkWidget *menuItem;
1343 	if (label[0])
1344 		menuItem = gtk_menu_item_new_with_label(label);
1345 	else
1346 		menuItem = gtk_separator_menu_item_new();
1347 	gtk_menu_shell_append(GTK_MENU_SHELL(popup.GetID()), menuItem);
1348 	g_object_set_data(G_OBJECT(menuItem), "CmdNum", GINT_TO_POINTER(cmd));
1349 	g_signal_connect(G_OBJECT(menuItem), "activate", G_CALLBACK(PopUpCB), this);
1350 
1351 	if (cmd) {
1352 		if (menuItem)
1353 			gtk_widget_set_sensitive(menuItem, enabled);
1354 	}
1355 }
1356 
OwnPrimarySelection()1357 bool ScintillaGTK::OwnPrimarySelection() {
1358 	return (wSelection.Created() &&
1359 		(gdk_selection_owner_get(GDK_SELECTION_PRIMARY) == PWindow(wSelection)) &&
1360 		(PWindow(wSelection) != nullptr));
1361 }
1362 
ClaimSelection()1363 void ScintillaGTK::ClaimSelection() {
1364 	// X Windows has a 'primary selection' as well as the clipboard.
1365 	// Whenever the user selects some text, we become the primary selection
1366 	if (!sel.Empty() && wSelection.Created() && IS_WIDGET_REALIZED(GTK_WIDGET(PWidget(wSelection)))) {
1367 		primarySelection = true;
1368 		gtk_selection_owner_set(GTK_WIDGET(PWidget(wSelection)),
1369 					GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME);
1370 		primary.Clear();
1371 	} else if (OwnPrimarySelection()) {
1372 		primarySelection = true;
1373 		if (primary.Empty())
1374 			gtk_selection_owner_set(nullptr, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME);
1375 	} else {
1376 		primarySelection = false;
1377 		primary.Clear();
1378 	}
1379 }
1380 
IsStringAtom(GdkAtom type)1381 bool ScintillaGTK::IsStringAtom(GdkAtom type) {
1382 	return (type == GDK_TARGET_STRING) || (type == atomUTF8) || (type == atomUTF8Mime);
1383 }
1384 
DataOfGSD(GtkSelectionData * sd)1385 static const guchar *DataOfGSD(GtkSelectionData *sd) noexcept { return gtk_selection_data_get_data(sd); }
LengthOfGSD(GtkSelectionData * sd)1386 static gint LengthOfGSD(GtkSelectionData *sd) noexcept { return gtk_selection_data_get_length(sd); }
TypeOfGSD(GtkSelectionData * sd)1387 static GdkAtom TypeOfGSD(GtkSelectionData *sd) noexcept { return gtk_selection_data_get_data_type(sd); }
SelectionOfGSD(GtkSelectionData * sd)1388 static GdkAtom SelectionOfGSD(GtkSelectionData *sd) noexcept { return gtk_selection_data_get_selection(sd); }
1389 
1390 // Detect rectangular text, convert line ends to current mode, convert from or to UTF-8
GetGtkSelectionText(GtkSelectionData * selectionData,SelectionText & selText)1391 void ScintillaGTK::GetGtkSelectionText(GtkSelectionData *selectionData, SelectionText &selText) {
1392 	const char *data = reinterpret_cast<const char *>(DataOfGSD(selectionData));
1393 	int len = LengthOfGSD(selectionData);
1394 	GdkAtom selectionTypeData = TypeOfGSD(selectionData);
1395 
1396 	// Return empty string if selection is not a string
1397 	if (!IsStringAtom(selectionTypeData)) {
1398 		selText.Clear();
1399 		return;
1400 	}
1401 
1402 	// Check for "\n\0" ending to string indicating that selection is rectangular
1403 	bool isRectangular;
1404 #if PLAT_GTK_WIN32
1405 	isRectangular = ::IsClipboardFormatAvailable(cfColumnSelect) != 0;
1406 #else
1407 	isRectangular = ((len > 2) && (data[len - 1] == 0 && data[len - 2] == '\n'));
1408 	if (isRectangular)
1409 		len--;	// Forget the extra '\0'
1410 #endif
1411 
1412 #if PLAT_GTK_WIN32
1413 	// Win32 includes an ending '\0' byte in 'len' for clipboard text from
1414 	// external applications; ignore it.
1415 	if ((len > 0) && (data[len - 1] == '\0'))
1416 		len--;
1417 #endif
1418 
1419 	std::string dest(data, len);
1420 	if (selectionTypeData == GDK_TARGET_STRING) {
1421 		if (IsUnicodeMode()) {
1422 			// Unknown encoding so assume in Latin1
1423 			dest = UTF8FromLatin1(dest.c_str(), dest.length());
1424 			selText.Copy(dest, SC_CP_UTF8, 0, isRectangular, false);
1425 		} else {
1426 			// Assume buffer is in same encoding as selection
1427 			selText.Copy(dest, pdoc->dbcsCodePage,
1428 				     vs.styles[STYLE_DEFAULT].characterSet, isRectangular, false);
1429 		}
1430 	} else {	// UTF-8
1431 		const char *charSetBuffer = CharacterSetID();
1432 		if (!IsUnicodeMode() && *charSetBuffer) {
1433 			// Convert to locale
1434 			dest = ConvertText(dest.c_str(), dest.length(), charSetBuffer, "UTF-8", true);
1435 			selText.Copy(dest, pdoc->dbcsCodePage,
1436 				     vs.styles[STYLE_DEFAULT].characterSet, isRectangular, false);
1437 		} else {
1438 			selText.Copy(dest, SC_CP_UTF8, 0, isRectangular, false);
1439 		}
1440 	}
1441 }
1442 
InsertSelection(GtkClipboard * clipBoard,GtkSelectionData * selectionData)1443 void ScintillaGTK::InsertSelection(GtkClipboard *clipBoard, GtkSelectionData *selectionData) {
1444 	const gint length = gtk_selection_data_get_length(selectionData);
1445 	if (length >= 0) {
1446 		GdkAtom selection = gtk_selection_data_get_selection(selectionData);
1447 		SelectionText selText;
1448 		GetGtkSelectionText(selectionData, selText);
1449 
1450 		UndoGroup ug(pdoc);
1451 		if (selection == GDK_SELECTION_CLIPBOARD) {
1452 			ClearSelection(multiPasteMode == SC_MULTIPASTE_EACH);
1453 		}
1454 
1455 		InsertPasteShape(selText.Data(), selText.Length(),
1456 				 selText.rectangular ? pasteRectangular : pasteStream);
1457 		EnsureCaretVisible();
1458 	} else {
1459 		GdkAtom target = gtk_selection_data_get_target(selectionData);
1460 		if (target == atomUTF8) {
1461 			// In case data is actually only stored as text/plain;charset=utf-8 not UTF8_STRING
1462 			gtk_clipboard_request_contents(clipBoard, atomUTF8Mime,
1463 					 SelectionReceiver::ClipboardReceived,
1464 					 new SelectionReceiver(this)
1465 			);
1466 		}
1467 	}
1468 	Redraw();
1469 }
1470 
MainObject() const1471 GObject *ScintillaGTK::MainObject() const noexcept {
1472 	return G_OBJECT(PWidget(wMain));
1473 }
1474 
ReceivedClipboard(GtkClipboard * clipBoard,GtkSelectionData * selection_data)1475 void ScintillaGTK::ReceivedClipboard(GtkClipboard *clipBoard, GtkSelectionData *selection_data) noexcept {
1476 	try {
1477 		InsertSelection(clipBoard, selection_data);
1478 	} catch (...) {
1479 		errorStatus = SC_STATUS_FAILURE;
1480 	}
1481 }
1482 
ReceivedSelection(GtkSelectionData * selection_data)1483 void ScintillaGTK::ReceivedSelection(GtkSelectionData *selection_data) {
1484 	try {
1485 		if ((SelectionOfGSD(selection_data) == GDK_SELECTION_CLIPBOARD) ||
1486 				(SelectionOfGSD(selection_data) == GDK_SELECTION_PRIMARY)) {
1487 			if ((atomSought == atomUTF8) && (LengthOfGSD(selection_data) <= 0)) {
1488 				atomSought = atomString;
1489 				gtk_selection_convert(GTK_WIDGET(PWidget(wMain)),
1490 						      SelectionOfGSD(selection_data), atomSought, GDK_CURRENT_TIME);
1491 			} else if ((LengthOfGSD(selection_data) > 0) && IsStringAtom(TypeOfGSD(selection_data))) {
1492 				GtkClipboard *clipBoard = gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), SelectionOfGSD(selection_data));
1493 				InsertSelection(clipBoard, selection_data);
1494 			}
1495 		}
1496 //	else fprintf(stderr, "Target non string %d %d\n", (int)(selection_data->type),
1497 //		(int)(atomUTF8));
1498 	} catch (...) {
1499 		errorStatus = SC_STATUS_FAILURE;
1500 	}
1501 }
1502 
ReceivedDrop(GtkSelectionData * selection_data)1503 void ScintillaGTK::ReceivedDrop(GtkSelectionData *selection_data) {
1504 	dragWasDropped = true;
1505 	if (TypeOfGSD(selection_data) == atomUriList || TypeOfGSD(selection_data) == atomDROPFILES_DND) {
1506 		const char *data = reinterpret_cast<const char *>(DataOfGSD(selection_data));
1507 		std::vector<char> drop(data, data + LengthOfGSD(selection_data));
1508 		drop.push_back('\0');
1509 		NotifyURIDropped(&drop[0]);
1510 	} else if (IsStringAtom(TypeOfGSD(selection_data))) {
1511 		if (LengthOfGSD(selection_data) > 0) {
1512 			SelectionText selText;
1513 			GetGtkSelectionText(selection_data, selText);
1514 			DropAt(posDrop, selText.Data(), selText.Length(), false, selText.rectangular);
1515 		}
1516 	} else if (LengthOfGSD(selection_data) > 0) {
1517 		//~ fprintf(stderr, "ReceivedDrop other %p\n", static_cast<void *>(selection_data->type));
1518 	}
1519 	Redraw();
1520 }
1521 
1522 
1523 
GetSelection(GtkSelectionData * selection_data,guint info,SelectionText * text)1524 void ScintillaGTK::GetSelection(GtkSelectionData *selection_data, guint info, SelectionText *text) {
1525 #if PLAT_GTK_WIN32
1526 	// GDK on Win32 expands any \n into \r\n, so make a copy of
1527 	// the clip text now with newlines converted to \n.  Use { } to hide symbols
1528 	// from code below
1529 	std::unique_ptr<SelectionText> newline_normalized;
1530 	{
1531 		std::string tmpstr = Document::TransformLineEnds(text->Data(), text->Length(), SC_EOL_LF);
1532 		newline_normalized.reset(new SelectionText());
1533 		newline_normalized->Copy(tmpstr, SC_CP_UTF8, 0, text->rectangular, false);
1534 		text = newline_normalized.get();
1535 	}
1536 #endif
1537 
1538 	// Convert text to utf8 if it isn't already
1539 	std::unique_ptr<SelectionText> converted;
1540 	if ((text->codePage != SC_CP_UTF8) && (info == TARGET_UTF8_STRING)) {
1541 		const char *charSet = ::CharacterSetID(text->characterSet);
1542 		if (*charSet) {
1543 			std::string tmputf = ConvertText(text->Data(), text->Length(), "UTF-8", charSet, false);
1544 			converted = Sci::make_unique<SelectionText>();
1545 			converted->Copy(tmputf, SC_CP_UTF8, 0, text->rectangular, false);
1546 			text = converted.get();
1547 		}
1548 	}
1549 
1550 	// Here is a somewhat evil kludge.
1551 	// As I can not work out how to store data on the clipboard in multiple formats
1552 	// and need some way to mark the clipping as being stream or rectangular,
1553 	// the terminating \0 is included in the length for rectangular clippings.
1554 	// All other tested applications behave benignly by ignoring the \0.
1555 	// The #if is here because on Windows cfColumnSelect clip entry is used
1556 	// instead as standard indicator of rectangularness (so no need to kludge)
1557 	const char *textData = text->Data();
1558 	int len = text->Length();
1559 #if PLAT_GTK_WIN32 == 0
1560 	if (text->rectangular)
1561 		len++;
1562 #endif
1563 
1564 	if (info == TARGET_UTF8_STRING) {
1565 		gtk_selection_data_set_text(selection_data, textData, len);
1566 	} else {
1567 		gtk_selection_data_set(selection_data,
1568 				       static_cast<GdkAtom>(GDK_SELECTION_TYPE_STRING),
1569 				       8, reinterpret_cast<const guchar *>(textData), len);
1570 	}
1571 }
1572 
StoreOnClipboard(SelectionText * clipText)1573 void ScintillaGTK::StoreOnClipboard(SelectionText *clipText) {
1574 	GtkClipboard *clipBoard =
1575 		gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), GDK_SELECTION_CLIPBOARD);
1576 	if (clipBoard == nullptr) // Occurs if widget isn't in a toplevel
1577 		return;
1578 
1579 	if (gtk_clipboard_set_with_data(clipBoard, clipboardCopyTargets, nClipboardCopyTargets,
1580 					ClipboardGetSelection, ClipboardClearSelection, clipText)) {
1581 		gtk_clipboard_set_can_store(clipBoard, clipboardCopyTargets, nClipboardCopyTargets);
1582 	}
1583 }
1584 
ClipboardGetSelection(GtkClipboard *,GtkSelectionData * selection_data,guint info,void * data)1585 void ScintillaGTK::ClipboardGetSelection(GtkClipboard *, GtkSelectionData *selection_data, guint info, void *data) {
1586 	GetSelection(selection_data, info, static_cast<SelectionText *>(data));
1587 }
1588 
ClipboardClearSelection(GtkClipboard *,void * data)1589 void ScintillaGTK::ClipboardClearSelection(GtkClipboard *, void *data) {
1590 	SelectionText *obj = static_cast<SelectionText *>(data);
1591 	delete obj;
1592 }
1593 
UnclaimSelection(GdkEventSelection * selection_event)1594 void ScintillaGTK::UnclaimSelection(GdkEventSelection *selection_event) {
1595 	try {
1596 		//Platform::DebugPrintf("UnclaimSelection\n");
1597 		if (selection_event->selection == GDK_SELECTION_PRIMARY) {
1598 			//Platform::DebugPrintf("UnclaimPrimarySelection\n");
1599 			if (!OwnPrimarySelection()) {
1600 				primary.Clear();
1601 				primarySelection = false;
1602 				FullPaint();
1603 			}
1604 		}
1605 	} catch (...) {
1606 		errorStatus = SC_STATUS_FAILURE;
1607 	}
1608 }
1609 
PrimarySelection(GtkWidget *,GtkSelectionData * selection_data,guint info,guint,ScintillaGTK * sciThis)1610 void ScintillaGTK::PrimarySelection(GtkWidget *, GtkSelectionData *selection_data, guint info, guint, ScintillaGTK *sciThis) {
1611 	try {
1612 		if (SelectionOfGSD(selection_data) == GDK_SELECTION_PRIMARY) {
1613 			if (sciThis->primary.Empty()) {
1614 				sciThis->CopySelectionRange(&sciThis->primary);
1615 			}
1616 			sciThis->GetSelection(selection_data, info, &sciThis->primary);
1617 		}
1618 	} catch (...) {
1619 		sciThis->errorStatus = SC_STATUS_FAILURE;
1620 	}
1621 }
1622 
PrimaryClear(GtkWidget * widget,GdkEventSelection * event,ScintillaGTK * sciThis)1623 gboolean ScintillaGTK::PrimaryClear(GtkWidget *widget, GdkEventSelection *event, ScintillaGTK *sciThis) {
1624 	sciThis->UnclaimSelection(event);
1625 	if (GTK_WIDGET_CLASS(sciThis->parentClass)->selection_clear_event) {
1626 		return GTK_WIDGET_CLASS(sciThis->parentClass)->selection_clear_event(widget, event);
1627 	}
1628 	return TRUE;
1629 }
1630 
Resize(int width,int height)1631 void ScintillaGTK::Resize(int width, int height) {
1632 	//Platform::DebugPrintf("Resize %d %d\n", width, height);
1633 	//printf("Resize %d %d\n", width, height);
1634 
1635 	// GTK+ 3 warns when we allocate smaller than the minimum allocation,
1636 	// so we use these variables to store the minimum scrollbar lengths.
1637 	int minVScrollBarHeight, minHScrollBarWidth;
1638 
1639 	// Not always needed, but some themes can have different sizes of scrollbars
1640 #if GTK_CHECK_VERSION(3,0,0)
1641 	GtkRequisition minimum, requisition;
1642 	gtk_widget_get_preferred_size(PWidget(scrollbarv), &minimum, &requisition);
1643 	minVScrollBarHeight = minimum.height;
1644 	verticalScrollBarWidth = requisition.width;
1645 	gtk_widget_get_preferred_size(PWidget(scrollbarh), &minimum, &requisition);
1646 	minHScrollBarWidth = minimum.width;
1647 	horizontalScrollBarHeight = requisition.height;
1648 #else
1649 	minVScrollBarHeight = minHScrollBarWidth = 1;
1650 	verticalScrollBarWidth = GTK_WIDGET(PWidget(scrollbarv))->requisition.width;
1651 	horizontalScrollBarHeight = GTK_WIDGET(PWidget(scrollbarh))->requisition.height;
1652 #endif
1653 
1654 	// These allocations should never produce negative sizes as they would wrap around to huge
1655 	// unsigned numbers inside GTK+ causing warnings.
1656 	const bool showSBHorizontal = horizontalScrollBarVisible && !Wrapping();
1657 
1658 	GtkAllocation alloc = {};
1659 	if (showSBHorizontal) {
1660 		gtk_widget_show(GTK_WIDGET(PWidget(scrollbarh)));
1661 		alloc.x = 0;
1662 		alloc.y = height - horizontalScrollBarHeight;
1663 		alloc.width = std::max(minHScrollBarWidth, width - verticalScrollBarWidth);
1664 		alloc.height = horizontalScrollBarHeight;
1665 		gtk_widget_size_allocate(GTK_WIDGET(PWidget(scrollbarh)), &alloc);
1666 	} else {
1667 		gtk_widget_hide(GTK_WIDGET(PWidget(scrollbarh)));
1668 		horizontalScrollBarHeight = 0; // in case horizontalScrollBarVisible is true.
1669 	}
1670 
1671 	if (verticalScrollBarVisible) {
1672 		gtk_widget_show(GTK_WIDGET(PWidget(scrollbarv)));
1673 		alloc.x = width - verticalScrollBarWidth;
1674 		alloc.y = 0;
1675 		alloc.width = verticalScrollBarWidth;
1676 		alloc.height = std::max(minVScrollBarHeight, height - horizontalScrollBarHeight);
1677 		gtk_widget_size_allocate(GTK_WIDGET(PWidget(scrollbarv)), &alloc);
1678 	} else {
1679 		gtk_widget_hide(GTK_WIDGET(PWidget(scrollbarv)));
1680 		verticalScrollBarWidth = 0;
1681 	}
1682 	if (IS_WIDGET_MAPPED(PWidget(wMain))) {
1683 		ChangeSize();
1684 	}
1685 
1686 	alloc.x = 0;
1687 	alloc.y = 0;
1688 	alloc.width = 1;
1689 	alloc.height = 1;
1690 #if GTK_CHECK_VERSION(3, 0, 0)
1691 	// please GTK 3.20 and ask wText what size it wants, although we know it doesn't really need
1692 	// anything special as it's ours.
1693 	gtk_widget_get_preferred_size(PWidget(wText), &requisition, nullptr);
1694 	alloc.width = requisition.width;
1695 	alloc.height = requisition.height;
1696 #endif
1697 	alloc.width = std::max(alloc.width, width - verticalScrollBarWidth);
1698 	alloc.height = std::max(alloc.height, height - horizontalScrollBarHeight);
1699 	gtk_widget_size_allocate(GTK_WIDGET(PWidget(wText)), &alloc);
1700 }
1701 
1702 namespace {
1703 
SetAdjustmentValue(GtkAdjustment * object,int value)1704 void SetAdjustmentValue(GtkAdjustment *object, int value) noexcept {
1705 	GtkAdjustment *adjustment = GTK_ADJUSTMENT(object);
1706 	const int maxValue = static_cast<int>(
1707 				     gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment));
1708 
1709 	if (value > maxValue)
1710 		value = maxValue;
1711 	if (value < 0)
1712 		value = 0;
1713 	gtk_adjustment_set_value(adjustment, value);
1714 }
1715 
modifierTranslated(int sciModifier)1716 int modifierTranslated(int sciModifier) noexcept {
1717 	switch (sciModifier) {
1718 	case SCMOD_SHIFT:
1719 		return GDK_SHIFT_MASK;
1720 	case SCMOD_CTRL:
1721 		return GDK_CONTROL_MASK;
1722 	case SCMOD_ALT:
1723 		return GDK_MOD1_MASK;
1724 	case SCMOD_SUPER:
1725 		return GDK_MOD4_MASK;
1726 	default:
1727 		return 0;
1728 	}
1729 }
1730 
PointOfEvent(const GdkEventButton * event)1731 Point PointOfEvent(const GdkEventButton *event) noexcept {
1732 	// Use floor as want to round in the same direction (-infinity) so
1733 	// there is no stickiness crossing 0.0.
1734 	return Point(static_cast<XYPOSITION>(std::floor(event->x)), static_cast<XYPOSITION>(std::floor(event->y)));
1735 }
1736 
1737 }
1738 
PressThis(GdkEventButton * event)1739 gint ScintillaGTK::PressThis(GdkEventButton *event) {
1740 	try {
1741 		//Platform::DebugPrintf("Press %x time=%d state = %x button = %x\n",this,event->time, event->state, event->button);
1742 		// Do not use GTK+ double click events as Scintilla has its own double click detection
1743 		if (event->type != GDK_BUTTON_PRESS)
1744 			return FALSE;
1745 
1746 		if (evbtn) {
1747 			gdk_event_free(evbtn);
1748 		}
1749 		evbtn = gdk_event_copy(reinterpret_cast<GdkEvent *>(event));
1750 		buttonMouse = event->button;
1751 		const Point pt = PointOfEvent(event);
1752 		const PRectangle rcClient = GetClientRectangle();
1753 		//Platform::DebugPrintf("Press %0d,%0d in %0d,%0d %0d,%0d\n",
1754 		//	pt.x, pt.y, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
1755 		if ((pt.x > rcClient.right) || (pt.y > rcClient.bottom)) {
1756 			Platform::DebugPrintf("Bad location\n");
1757 			return FALSE;
1758 		}
1759 
1760 		const bool shift = (event->state & GDK_SHIFT_MASK) != 0;
1761 		bool ctrl = (event->state & GDK_CONTROL_MASK) != 0;
1762 		// On X, instead of sending literal modifiers use the user specified
1763 		// modifier, defaulting to control instead of alt.
1764 		// This is because most X window managers grab alt + click for moving
1765 		const bool alt = (event->state & modifierTranslated(rectangularSelectionModifier)) != 0;
1766 
1767 		gtk_widget_grab_focus(PWidget(wMain));
1768 		if (event->button == 1) {
1769 #if PLAT_GTK_MACOSX
1770 			const bool meta = ctrl;
1771 			// GDK reports the Command modifier key as GDK_MOD2_MASK for button events,
1772 			// not GDK_META_MASK like in key events.
1773 			ctrl = (event->state & GDK_MOD2_MASK) != 0;
1774 #else
1775 			const bool meta = false;
1776 #endif
1777 			ButtonDownWithModifiers(pt, event->time, ModifierFlags(shift, ctrl, alt, meta));
1778 		} else if (event->button == 2) {
1779 			// Grab the primary selection if it exists
1780 			const SelectionPosition pos = SPositionFromLocation(pt, false, false, UserVirtualSpace());
1781 			if (OwnPrimarySelection() && primary.Empty())
1782 				CopySelectionRange(&primary);
1783 
1784 			sel.Clear();
1785 			SetSelection(pos, pos);
1786 			RequestSelection(GDK_SELECTION_PRIMARY);
1787 		} else if (event->button == 3) {
1788 			if (!PointInSelection(pt))
1789 				SetEmptySelection(PositionFromLocation(pt));
1790 			if (ShouldDisplayPopup(pt)) {
1791 				// PopUp menu
1792 				// Convert to screen
1793 				int ox = 0;
1794 				int oy = 0;
1795 				gdk_window_get_origin(PWindow(wMain), &ox, &oy);
1796 				ContextMenu(Point(pt.x + ox, pt.y + oy));
1797 			} else {
1798 #if PLAT_GTK_MACOSX
1799 				const bool meta = ctrl;
1800 				// GDK reports the Command modifier key as GDK_MOD2_MASK for button events,
1801 				// not GDK_META_MASK like in key events.
1802 				ctrl = (event->state & GDK_MOD2_MASK) != 0;
1803 #else
1804 				const bool meta = false;
1805 #endif
1806 				RightButtonDownWithModifiers(pt, event->time, ModifierFlags(shift, ctrl, alt, meta));
1807 				return FALSE;
1808 			}
1809 		} else if (event->button == 4) {
1810 			// Wheel scrolling up (only GTK 1.x does it this way)
1811 			if (ctrl)
1812 				SetAdjustmentValue(adjustmenth, xOffset - 6);
1813 			else
1814 				SetAdjustmentValue(adjustmentv, topLine - 3);
1815 		} else if (event->button == 5) {
1816 			// Wheel scrolling down (only GTK 1.x does it this way)
1817 			if (ctrl)
1818 				SetAdjustmentValue(adjustmenth, xOffset + 6);
1819 			else
1820 				SetAdjustmentValue(adjustmentv, topLine + 3);
1821 		}
1822 	} catch (...) {
1823 		errorStatus = SC_STATUS_FAILURE;
1824 	}
1825 	return TRUE;
1826 }
1827 
Press(GtkWidget * widget,GdkEventButton * event)1828 gint ScintillaGTK::Press(GtkWidget *widget, GdkEventButton *event) {
1829 	if (event->window != WindowFromWidget(widget))
1830 		return FALSE;
1831 	ScintillaGTK *sciThis = FromWidget(widget);
1832 	return sciThis->PressThis(event);
1833 }
1834 
MouseRelease(GtkWidget * widget,GdkEventButton * event)1835 gint ScintillaGTK::MouseRelease(GtkWidget *widget, GdkEventButton *event) {
1836 	ScintillaGTK *sciThis = FromWidget(widget);
1837 	try {
1838 		//Platform::DebugPrintf("Release %x %d %d\n",sciThis,event->time,event->state);
1839 		if (!sciThis->HaveMouseCapture())
1840 			return FALSE;
1841 		if (event->button == 1) {
1842 			Point pt = PointOfEvent(event);
1843 			//Platform::DebugPrintf("Up %x %x %d %d %d\n",
1844 			//	sciThis,event->window,event->time, pt.x, pt.y);
1845 			if (event->window != PWindow(sciThis->wMain))
1846 				// If mouse released on scroll bar then the position is relative to the
1847 				// scrollbar, not the drawing window so just repeat the most recent point.
1848 				pt = sciThis->ptMouseLast;
1849 			const int modifiers = ModifierFlags(
1850 						      (event->state & GDK_SHIFT_MASK) != 0,
1851 						      (event->state & GDK_CONTROL_MASK) != 0,
1852 						      (event->state & modifierTranslated(sciThis->rectangularSelectionModifier)) != 0);
1853 			sciThis->ButtonUpWithModifiers(pt, event->time, modifiers);
1854 		}
1855 	} catch (...) {
1856 		sciThis->errorStatus = SC_STATUS_FAILURE;
1857 	}
1858 	return FALSE;
1859 }
1860 
1861 // win32gtk and GTK >= 2 use SCROLL_* events instead of passing the
1862 // button4/5/6/7 events to the GTK app
ScrollEvent(GtkWidget * widget,GdkEventScroll * event)1863 gint ScintillaGTK::ScrollEvent(GtkWidget *widget, GdkEventScroll *event) {
1864 	ScintillaGTK *sciThis = FromWidget(widget);
1865 	try {
1866 
1867 		if (widget == nullptr || event == nullptr)
1868 			return FALSE;
1869 
1870 #if defined(GDK_WINDOWING_WAYLAND)
1871 		if (event->direction == GDK_SCROLL_SMOOTH && GDK_IS_WAYLAND_WINDOW(event->window)) {
1872 			const int smoothScrollFactor = 4;
1873 			sciThis->smoothScrollY += event->delta_y * smoothScrollFactor;
1874 			sciThis->smoothScrollX += event->delta_x * smoothScrollFactor;;
1875 			if (ABS(sciThis->smoothScrollY) >= 1.0) {
1876 				const int scrollLines = std::trunc(sciThis->smoothScrollY);
1877 				sciThis->ScrollTo(sciThis->topLine + scrollLines);
1878 				sciThis->smoothScrollY -= scrollLines;
1879 			}
1880 			if (ABS(sciThis->smoothScrollX) >= 1.0) {
1881 				const int scrollPixels = std::trunc(sciThis->smoothScrollX);
1882 				sciThis->HorizontalScrollTo(sciThis->xOffset + scrollPixels);
1883 				sciThis->smoothScrollX -= scrollPixels;
1884 			}
1885 			return TRUE;
1886 		}
1887 #endif
1888 
1889 		// Compute amount and direction to scroll (even tho on win32 there is
1890 		// intensity of scrolling info in the native message, gtk doesn't
1891 		// support this so we simulate similarly adaptive scrolling)
1892 		// Note that this is disabled on OS X (Darwin) with the X11 backend
1893 		// where the X11 server already has an adaptive scrolling algorithm
1894 		// that fights with this one
1895 		int cLineScroll;
1896 #if defined(__APPLE__) && !defined(GDK_WINDOWING_QUARTZ)
1897 		cLineScroll = sciThis->linesPerScroll;
1898 		if (cLineScroll == 0)
1899 			cLineScroll = 4;
1900 		sciThis->wheelMouseIntensity = cLineScroll;
1901 #else
1902 		const gint64 curTime = g_get_monotonic_time();
1903 		const gint64 timeDelta = curTime - sciThis->lastWheelMouseTime;
1904 		if ((event->direction == sciThis->lastWheelMouseDirection) && (timeDelta < 250000)) {
1905 			if (sciThis->wheelMouseIntensity < 12)
1906 				sciThis->wheelMouseIntensity++;
1907 			cLineScroll = sciThis->wheelMouseIntensity;
1908 		} else {
1909 			cLineScroll = sciThis->linesPerScroll;
1910 			if (cLineScroll == 0)
1911 				cLineScroll = 4;
1912 			sciThis->wheelMouseIntensity = cLineScroll;
1913 		}
1914 		sciThis->lastWheelMouseTime = curTime;
1915 #endif
1916 		if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT) {
1917 			cLineScroll *= -1;
1918 		}
1919 		sciThis->lastWheelMouseDirection = event->direction;
1920 
1921 		// Note:  Unpatched versions of win32gtk don't set the 'state' value so
1922 		// only regular scrolling is supported there.  Also, unpatched win32gtk
1923 		// issues spurious button 2 mouse events during wheeling, which can cause
1924 		// problems (a patch for both was submitted by archaeopteryx.com on 13Jun2001)
1925 
1926 		// Data zoom not supported
1927 		if (event->state & GDK_SHIFT_MASK) {
1928 			return FALSE;
1929 		}
1930 
1931 #if GTK_CHECK_VERSION(3,4,0)
1932 		// Smooth scrolling not supported
1933 		if (event->direction == GDK_SCROLL_SMOOTH) {
1934 			return FALSE;
1935 		}
1936 #endif
1937 
1938 		// Horizontal scrolling
1939 		if (event->direction == GDK_SCROLL_LEFT || event->direction == GDK_SCROLL_RIGHT) {
1940 			sciThis->HorizontalScrollTo(sciThis->xOffset + cLineScroll);
1941 
1942 			// Text font size zoom
1943 		} else if (event->state & GDK_CONTROL_MASK) {
1944 			if (cLineScroll < 0) {
1945 				sciThis->KeyCommand(SCI_ZOOMIN);
1946 			} else {
1947 				sciThis->KeyCommand(SCI_ZOOMOUT);
1948 			}
1949 
1950 			// Regular scrolling
1951 		} else {
1952 			sciThis->ScrollTo(sciThis->topLine + cLineScroll);
1953 		}
1954 		return TRUE;
1955 	} catch (...) {
1956 		sciThis->errorStatus = SC_STATUS_FAILURE;
1957 	}
1958 	return FALSE;
1959 }
1960 
Motion(GtkWidget * widget,GdkEventMotion * event)1961 gint ScintillaGTK::Motion(GtkWidget *widget, GdkEventMotion *event) {
1962 	ScintillaGTK *sciThis = FromWidget(widget);
1963 	try {
1964 		//Platform::DebugPrintf("Motion %x %d\n",sciThis,event->time);
1965 		if (event->window != WindowFromWidget(widget))
1966 			return FALSE;
1967 		int x = 0;
1968 		int y = 0;
1969 		GdkModifierType state {};
1970 		if (event->is_hint) {
1971 #if GTK_CHECK_VERSION(3,0,0)
1972 			gdk_window_get_device_position(event->window,
1973 						       event->device, &x, &y, &state);
1974 #else
1975 			gdk_window_get_pointer(event->window, &x, &y, &state);
1976 #endif
1977 		} else {
1978 			x = static_cast<int>(event->x);
1979 			y = static_cast<int>(event->y);
1980 			state = static_cast<GdkModifierType>(event->state);
1981 		}
1982 		//Platform::DebugPrintf("Move %x %x %d %c %d %d\n",
1983 		//	sciThis,event->window,event->time,event->is_hint? 'h' :'.', x, y);
1984 		const Point pt(static_cast<XYPOSITION>(x), static_cast<XYPOSITION>(y));
1985 		const int modifiers = ModifierFlags(
1986 					      (event->state & GDK_SHIFT_MASK) != 0,
1987 					      (event->state & GDK_CONTROL_MASK) != 0,
1988 					      (event->state & modifierTranslated(sciThis->rectangularSelectionModifier)) != 0);
1989 		sciThis->ButtonMoveWithModifiers(pt, event->time, modifiers);
1990 	} catch (...) {
1991 		sciThis->errorStatus = SC_STATUS_FAILURE;
1992 	}
1993 	return FALSE;
1994 }
1995 
1996 // Map the keypad keys to their equivalent functions
KeyTranslate(int keyIn)1997 static int KeyTranslate(int keyIn) noexcept {
1998 	switch (keyIn) {
1999 #if GTK_CHECK_VERSION(3,0,0)
2000 	case GDK_KEY_ISO_Left_Tab:
2001 		return SCK_TAB;
2002 	case GDK_KEY_KP_Down:
2003 		return SCK_DOWN;
2004 	case GDK_KEY_KP_Up:
2005 		return SCK_UP;
2006 	case GDK_KEY_KP_Left:
2007 		return SCK_LEFT;
2008 	case GDK_KEY_KP_Right:
2009 		return SCK_RIGHT;
2010 	case GDK_KEY_KP_Home:
2011 		return SCK_HOME;
2012 	case GDK_KEY_KP_End:
2013 		return SCK_END;
2014 	case GDK_KEY_KP_Page_Up:
2015 		return SCK_PRIOR;
2016 	case GDK_KEY_KP_Page_Down:
2017 		return SCK_NEXT;
2018 	case GDK_KEY_KP_Delete:
2019 		return SCK_DELETE;
2020 	case GDK_KEY_KP_Insert:
2021 		return SCK_INSERT;
2022 	case GDK_KEY_KP_Enter:
2023 		return SCK_RETURN;
2024 
2025 	case GDK_KEY_Down:
2026 		return SCK_DOWN;
2027 	case GDK_KEY_Up:
2028 		return SCK_UP;
2029 	case GDK_KEY_Left:
2030 		return SCK_LEFT;
2031 	case GDK_KEY_Right:
2032 		return SCK_RIGHT;
2033 	case GDK_KEY_Home:
2034 		return SCK_HOME;
2035 	case GDK_KEY_End:
2036 		return SCK_END;
2037 	case GDK_KEY_Page_Up:
2038 		return SCK_PRIOR;
2039 	case GDK_KEY_Page_Down:
2040 		return SCK_NEXT;
2041 	case GDK_KEY_Delete:
2042 		return SCK_DELETE;
2043 	case GDK_KEY_Insert:
2044 		return SCK_INSERT;
2045 	case GDK_KEY_Escape:
2046 		return SCK_ESCAPE;
2047 	case GDK_KEY_BackSpace:
2048 		return SCK_BACK;
2049 	case GDK_KEY_Tab:
2050 		return SCK_TAB;
2051 	case GDK_KEY_Return:
2052 		return SCK_RETURN;
2053 	case GDK_KEY_KP_Add:
2054 		return SCK_ADD;
2055 	case GDK_KEY_KP_Subtract:
2056 		return SCK_SUBTRACT;
2057 	case GDK_KEY_KP_Divide:
2058 		return SCK_DIVIDE;
2059 	case GDK_KEY_Super_L:
2060 		return SCK_WIN;
2061 	case GDK_KEY_Super_R:
2062 		return SCK_RWIN;
2063 	case GDK_KEY_Menu:
2064 		return SCK_MENU;
2065 
2066 #else
2067 
2068 	case GDK_ISO_Left_Tab:
2069 		return SCK_TAB;
2070 	case GDK_KP_Down:
2071 		return SCK_DOWN;
2072 	case GDK_KP_Up:
2073 		return SCK_UP;
2074 	case GDK_KP_Left:
2075 		return SCK_LEFT;
2076 	case GDK_KP_Right:
2077 		return SCK_RIGHT;
2078 	case GDK_KP_Home:
2079 		return SCK_HOME;
2080 	case GDK_KP_End:
2081 		return SCK_END;
2082 	case GDK_KP_Page_Up:
2083 		return SCK_PRIOR;
2084 	case GDK_KP_Page_Down:
2085 		return SCK_NEXT;
2086 	case GDK_KP_Delete:
2087 		return SCK_DELETE;
2088 	case GDK_KP_Insert:
2089 		return SCK_INSERT;
2090 	case GDK_KP_Enter:
2091 		return SCK_RETURN;
2092 
2093 	case GDK_Down:
2094 		return SCK_DOWN;
2095 	case GDK_Up:
2096 		return SCK_UP;
2097 	case GDK_Left:
2098 		return SCK_LEFT;
2099 	case GDK_Right:
2100 		return SCK_RIGHT;
2101 	case GDK_Home:
2102 		return SCK_HOME;
2103 	case GDK_End:
2104 		return SCK_END;
2105 	case GDK_Page_Up:
2106 		return SCK_PRIOR;
2107 	case GDK_Page_Down:
2108 		return SCK_NEXT;
2109 	case GDK_Delete:
2110 		return SCK_DELETE;
2111 	case GDK_Insert:
2112 		return SCK_INSERT;
2113 	case GDK_Escape:
2114 		return SCK_ESCAPE;
2115 	case GDK_BackSpace:
2116 		return SCK_BACK;
2117 	case GDK_Tab:
2118 		return SCK_TAB;
2119 	case GDK_Return:
2120 		return SCK_RETURN;
2121 	case GDK_KP_Add:
2122 		return SCK_ADD;
2123 	case GDK_KP_Subtract:
2124 		return SCK_SUBTRACT;
2125 	case GDK_KP_Divide:
2126 		return SCK_DIVIDE;
2127 	case GDK_Super_L:
2128 		return SCK_WIN;
2129 	case GDK_Super_R:
2130 		return SCK_RWIN;
2131 	case GDK_Menu:
2132 		return SCK_MENU;
2133 #endif
2134 	default:
2135 		return keyIn;
2136 	}
2137 }
2138 
KeyThis(GdkEventKey * event)2139 gboolean ScintillaGTK::KeyThis(GdkEventKey *event) {
2140 	try {
2141 		//fprintf(stderr, "SC-key: %d %x [%s]\n",
2142 		//	event->keyval, event->state, (event->length > 0) ? event->string : "empty");
2143 		if (gtk_im_context_filter_keypress(im_context, event)) {
2144 			return 1;
2145 		}
2146 		if (!event->keyval) {
2147 			return true;
2148 		}
2149 
2150 		const bool shift = (event->state & GDK_SHIFT_MASK) != 0;
2151 		bool ctrl = (event->state & GDK_CONTROL_MASK) != 0;
2152 		const bool alt = (event->state & GDK_MOD1_MASK) != 0;
2153 		const bool super = (event->state & GDK_MOD4_MASK) != 0;
2154 		guint key = event->keyval;
2155 		if ((ctrl || alt) && (key < 128))
2156 			key = toupper(key);
2157 #if GTK_CHECK_VERSION(3,0,0)
2158 		else if (!ctrl && (key >= GDK_KEY_KP_Multiply && key <= GDK_KEY_KP_9))
2159 #else
2160 		else if (!ctrl && (key >= GDK_KP_Multiply && key <= GDK_KP_9))
2161 #endif
2162 			key &= 0x7F;
2163 		// Hack for keys over 256 and below command keys but makes Hungarian work.
2164 		// This will have to change for Unicode
2165 		else if (key >= 0xFE00)
2166 			key = KeyTranslate(key);
2167 
2168 		bool consumed = false;
2169 #if !(PLAT_GTK_MACOSX)
2170 		const bool meta = false;
2171 #else
2172 		const bool meta = ctrl;
2173 		ctrl = (event->state & GDK_META_MASK) != 0;
2174 #endif
2175 		const bool added = KeyDownWithModifiers(key, ModifierFlags(shift, ctrl, alt, meta, super), &consumed) != 0;
2176 		if (!consumed)
2177 			consumed = added;
2178 		//fprintf(stderr, "SK-key: %d %x %x\n",event->keyval, event->state, consumed);
2179 		if (event->keyval == 0xffffff && event->length > 0) {
2180 			ClearSelection();
2181 			const int lengthInserted = pdoc->InsertString(CurrentPosition(), event->string, strlen(event->string));
2182 			if (lengthInserted > 0) {
2183 				MovePositionTo(CurrentPosition() + lengthInserted);
2184 			}
2185 		}
2186 		return consumed;
2187 	} catch (...) {
2188 		errorStatus = SC_STATUS_FAILURE;
2189 	}
2190 	return FALSE;
2191 }
2192 
KeyPress(GtkWidget * widget,GdkEventKey * event)2193 gboolean ScintillaGTK::KeyPress(GtkWidget *widget, GdkEventKey *event) {
2194 	ScintillaGTK *sciThis = FromWidget(widget);
2195 	return sciThis->KeyThis(event);
2196 }
2197 
KeyRelease(GtkWidget * widget,GdkEventKey * event)2198 gboolean ScintillaGTK::KeyRelease(GtkWidget *widget, GdkEventKey *event) {
2199 	//Platform::DebugPrintf("SC-keyrel: %d %x %3s\n",event->keyval, event->state, event->string);
2200 	ScintillaGTK *sciThis = FromWidget(widget);
2201 	if (gtk_im_context_filter_keypress(sciThis->im_context, event)) {
2202 		return TRUE;
2203 	}
2204 	return FALSE;
2205 }
2206 
2207 #if GTK_CHECK_VERSION(3,0,0)
2208 
DrawPreeditThis(GtkWidget *,cairo_t * cr)2209 gboolean ScintillaGTK::DrawPreeditThis(GtkWidget *, cairo_t *cr) {
2210 	try {
2211 		PreEditString pes(im_context);
2212 		PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), pes.str);
2213 		pango_layout_set_attributes(layout, pes.attrs);
2214 
2215 		cairo_move_to(cr, 0, 0);
2216 		pango_cairo_show_layout(cr, layout);
2217 
2218 		g_object_unref(layout);
2219 	} catch (...) {
2220 		errorStatus = SC_STATUS_FAILURE;
2221 	}
2222 	return TRUE;
2223 }
2224 
DrawPreedit(GtkWidget * widget,cairo_t * cr,ScintillaGTK * sciThis)2225 gboolean ScintillaGTK::DrawPreedit(GtkWidget *widget, cairo_t *cr, ScintillaGTK *sciThis) {
2226 	return sciThis->DrawPreeditThis(widget, cr);
2227 }
2228 
2229 #else
2230 
ExposePreeditThis(GtkWidget * widget,GdkEventExpose *)2231 gboolean ScintillaGTK::ExposePreeditThis(GtkWidget *widget, GdkEventExpose *) {
2232 	try {
2233 		PreEditString pes(im_context);
2234 		PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), pes.str);
2235 		pango_layout_set_attributes(layout, pes.attrs);
2236 
2237 		cairo_t *context = gdk_cairo_create(reinterpret_cast<GdkDrawable *>(WindowFromWidget(widget)));
2238 		cairo_move_to(context, 0, 0);
2239 		pango_cairo_show_layout(context, layout);
2240 		cairo_destroy(context);
2241 		g_object_unref(layout);
2242 	} catch (...) {
2243 		errorStatus = SC_STATUS_FAILURE;
2244 	}
2245 	return TRUE;
2246 }
2247 
ExposePreedit(GtkWidget * widget,GdkEventExpose * ose,ScintillaGTK * sciThis)2248 gboolean ScintillaGTK::ExposePreedit(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis) {
2249 	return sciThis->ExposePreeditThis(widget, ose);
2250 }
2251 
2252 #endif
2253 
KoreanIME()2254 bool ScintillaGTK::KoreanIME() {
2255 	PreEditString pes(im_context);
2256 	if (pes.pscript != G_UNICODE_SCRIPT_COMMON)
2257 		lastNonCommonScript = pes.pscript;
2258 	return lastNonCommonScript == G_UNICODE_SCRIPT_HANGUL;
2259 }
2260 
MoveImeCarets(int pos)2261 void ScintillaGTK::MoveImeCarets(int pos) {
2262 	// Move carets relatively by bytes
2263 	for (size_t r=0; r<sel.Count(); r++) {
2264 		const Sci::Position positionInsert = sel.Range(r).Start().Position();
2265 		sel.Range(r).caret.SetPosition(positionInsert + pos);
2266 		sel.Range(r).anchor.SetPosition(positionInsert + pos);
2267 	}
2268 }
2269 
DrawImeIndicator(int indicator,int len)2270 void ScintillaGTK::DrawImeIndicator(int indicator, int len) {
2271 	// Emulate the visual style of IME characters with indicators.
2272 	// Draw an indicator on the character before caret by the character bytes of len
2273 	// so it should be called after InsertCharacter().
2274 	// It does not affect caret positions.
2275 	if (indicator < 8 || indicator > INDICATOR_MAX) {
2276 		return;
2277 	}
2278 	pdoc->DecorationSetCurrentIndicator(indicator);
2279 	for (size_t r=0; r<sel.Count(); r++) {
2280 		const Sci::Position positionInsert = sel.Range(r).Start().Position();
2281 		pdoc->DecorationFillRange(positionInsert - len, 1, len);
2282 	}
2283 }
2284 
MapImeIndicators(PangoAttrList * attrs,const char * u8Str)2285 static std::vector<int> MapImeIndicators(PangoAttrList *attrs, const char *u8Str) {
2286 	// Map input style to scintilla ime indicator.
2287 	// Attrs position points between UTF-8 bytes.
2288 	// Indicator index to be returned is character based though.
2289 	const glong charactersLen = g_utf8_strlen(u8Str, strlen(u8Str));
2290 	std::vector<int> indicator(charactersLen, SC_INDICATOR_UNKNOWN);
2291 
2292 	PangoAttrIterator *iterunderline = pango_attr_list_get_iterator(attrs);
2293 	if (iterunderline) {
2294 		do {
2295 			PangoAttribute  *attrunderline = pango_attr_iterator_get(iterunderline, PANGO_ATTR_UNDERLINE);
2296 			if (attrunderline) {
2297 				const glong start = g_utf8_strlen(u8Str, attrunderline->start_index);
2298 				const glong end = g_utf8_strlen(u8Str, attrunderline->end_index);
2299 				const PangoUnderline uline = (PangoUnderline)((PangoAttrInt *)attrunderline)->value;
2300 				for (glong i=start; i < end; ++i) {
2301 					switch (uline) {
2302 					case PANGO_UNDERLINE_NONE:
2303 						indicator[i] = SC_INDICATOR_UNKNOWN;
2304 						break;
2305 					case PANGO_UNDERLINE_SINGLE: // normal input
2306 						indicator[i] = SC_INDICATOR_INPUT;
2307 						break;
2308 					case PANGO_UNDERLINE_DOUBLE:
2309 					case PANGO_UNDERLINE_LOW:
2310 					case PANGO_UNDERLINE_ERROR:
2311 						break;
2312 					}
2313 				}
2314 			}
2315 		} while (pango_attr_iterator_next(iterunderline));
2316 		pango_attr_iterator_destroy(iterunderline);
2317 	}
2318 
2319 	PangoAttrIterator *itercolor = pango_attr_list_get_iterator(attrs);
2320 	if (itercolor) {
2321 		do {
2322 			const PangoAttribute *backcolor = pango_attr_iterator_get(itercolor, PANGO_ATTR_BACKGROUND);
2323 			if (backcolor) {
2324 				const glong start = g_utf8_strlen(u8Str, backcolor->start_index);
2325 				const glong end = g_utf8_strlen(u8Str, backcolor->end_index);
2326 				for (glong i=start; i < end; ++i) {
2327 					indicator[i] = SC_INDICATOR_TARGET;  // target converted
2328 				}
2329 			}
2330 		} while (pango_attr_iterator_next(itercolor));
2331 		pango_attr_iterator_destroy(itercolor);
2332 	}
2333 	return indicator;
2334 }
2335 
SetCandidateWindowPos()2336 void ScintillaGTK::SetCandidateWindowPos() {
2337 	// Composition box accompanies candidate box.
2338 	const Point pt = PointMainCaret();
2339 	GdkRectangle imeBox = {0}; // No need to set width
2340 	imeBox.x = static_cast<gint>(pt.x);
2341 	imeBox.y = static_cast<gint>(pt.y + std::max(4, vs.lineHeight/4));
2342 	// prevent overlapping with current line
2343 	imeBox.height = vs.lineHeight;
2344 	gtk_im_context_set_cursor_location(im_context, &imeBox);
2345 }
2346 
CommitThis(char * commitStr)2347 void ScintillaGTK::CommitThis(char *commitStr) {
2348 	try {
2349 		//~ fprintf(stderr, "Commit '%s'\n", commitStr);
2350 		view.imeCaretBlockOverride = false;
2351 
2352 		if (pdoc->TentativeActive()) {
2353 			pdoc->TentativeUndo();
2354 		}
2355 
2356 		const char *charSetSource = CharacterSetID();
2357 
2358 		glong uniStrLen = 0;
2359 		gunichar *uniStr = g_utf8_to_ucs4_fast(commitStr, strlen(commitStr), &uniStrLen);
2360 		for (glong i = 0; i < uniStrLen; i++) {
2361 			gchar u8Char[UTF8MaxBytes+2] = {0};
2362 			const gint u8CharLen = g_unichar_to_utf8(uniStr[i], u8Char);
2363 			std::string docChar = u8Char;
2364 			if (!IsUnicodeMode())
2365 				docChar = ConvertText(u8Char, u8CharLen, charSetSource, "UTF-8", true);
2366 
2367 			InsertCharacter(docChar.c_str(), docChar.size(), CharacterSource::directInput);
2368 		}
2369 		g_free(uniStr);
2370 		ShowCaretAtCurrentPosition();
2371 	} catch (...) {
2372 		errorStatus = SC_STATUS_FAILURE;
2373 	}
2374 }
2375 
Commit(GtkIMContext *,char * str,ScintillaGTK * sciThis)2376 void ScintillaGTK::Commit(GtkIMContext *, char  *str, ScintillaGTK *sciThis) {
2377 	sciThis->CommitThis(str);
2378 }
2379 
PreeditChangedInlineThis()2380 void ScintillaGTK::PreeditChangedInlineThis() {
2381 	// Copy & paste by johnsonj with a lot of helps of Neil
2382 	// Great thanks for my foreruners, jiniya and BLUEnLIVE
2383 	try {
2384 		if (pdoc->IsReadOnly() || SelectionContainsProtected()) {
2385 			gtk_im_context_reset(im_context);
2386 			return;
2387 		}
2388 
2389 		view.imeCaretBlockOverride = false; // If backspace.
2390 
2391 		bool initialCompose = false;
2392 		if (pdoc->TentativeActive()) {
2393 			pdoc->TentativeUndo();
2394 		} else {
2395 			// No tentative undo means start of this composition so
2396 			// fill in any virtual spaces.
2397 			initialCompose = true;
2398 		}
2399 
2400 		PreEditString preeditStr(im_context);
2401 		const char *charSetSource = CharacterSetID();
2402 
2403 		if (!preeditStr.validUTF8 || (charSetSource == nullptr)) {
2404 			ShowCaretAtCurrentPosition();
2405 			return;
2406 		}
2407 
2408 		if (preeditStr.uniStrLen == 0 || preeditStr.uniStrLen > maxLenInputIME) {
2409 			//fprintf(stderr, "Do not allow over 200 chars: %i\n", preeditStr.uniStrLen);
2410 			ShowCaretAtCurrentPosition();
2411 			return;
2412 		}
2413 
2414 		if (initialCompose) {
2415 			ClearBeforeTentativeStart();
2416 		}
2417 
2418 		SetCandidateWindowPos();
2419 		pdoc->TentativeStart(); // TentativeActive() from now on
2420 
2421 		std::vector<int> indicator = MapImeIndicators(preeditStr.attrs, preeditStr.str);
2422 
2423 		for (glong i = 0; i < preeditStr.uniStrLen; i++) {
2424 			gchar u8Char[UTF8MaxBytes+2] = {0};
2425 			const gint u8CharLen = g_unichar_to_utf8(preeditStr.uniStr[i], u8Char);
2426 			std::string docChar = u8Char;
2427 			if (!IsUnicodeMode())
2428 				docChar = ConvertText(u8Char, u8CharLen, charSetSource, "UTF-8", true);
2429 
2430 			InsertCharacter(docChar.c_str(), docChar.size(), CharacterSource::tentativeInput);
2431 
2432 			DrawImeIndicator(indicator[i], docChar.size());
2433 		}
2434 
2435 		// Move caret to ime cursor position.
2436 		const int imeEndToImeCaretU32 = preeditStr.cursor_pos - preeditStr.uniStrLen;
2437 		const int imeCaretPosDoc = pdoc->GetRelativePosition(CurrentPosition(), imeEndToImeCaretU32);
2438 
2439 		MoveImeCarets(- CurrentPosition() + imeCaretPosDoc);
2440 
2441 		if (KoreanIME()) {
2442 #if !PLAT_GTK_WIN32
2443 			if (preeditStr.cursor_pos > 0) {
2444 				int oneCharBefore = pdoc->GetRelativePosition(CurrentPosition(), -1);
2445 				MoveImeCarets(- CurrentPosition() + oneCharBefore);
2446 			}
2447 #endif
2448 			view.imeCaretBlockOverride = true;
2449 		}
2450 
2451 		EnsureCaretVisible();
2452 		ShowCaretAtCurrentPosition();
2453 	} catch (...) {
2454 		errorStatus = SC_STATUS_FAILURE;
2455 	}
2456 }
2457 
PreeditChangedWindowedThis()2458 void ScintillaGTK::PreeditChangedWindowedThis() {
2459 	try {
2460 		PreEditString pes(im_context);
2461 		if (strlen(pes.str) > 0) {
2462 			SetCandidateWindowPos();
2463 
2464 			PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), pes.str);
2465 			pango_layout_set_attributes(layout, pes.attrs);
2466 
2467 			gint w, h;
2468 			pango_layout_get_pixel_size(layout, &w, &h);
2469 			g_object_unref(layout);
2470 
2471 			gint x, y;
2472 			gdk_window_get_origin(PWindow(wText), &x, &y);
2473 
2474 			Point pt = PointMainCaret();
2475 			if (pt.x < 0)
2476 				pt.x = 0;
2477 			if (pt.y < 0)
2478 				pt.y = 0;
2479 
2480 			gtk_window_move(GTK_WINDOW(PWidget(wPreedit)), x + pt.x, y + pt.y);
2481 			gtk_window_resize(GTK_WINDOW(PWidget(wPreedit)), w, h);
2482 			gtk_widget_show(PWidget(wPreedit));
2483 			gtk_widget_queue_draw_area(PWidget(wPreeditDraw), 0, 0, w, h);
2484 		} else {
2485 			gtk_widget_hide(PWidget(wPreedit));
2486 		}
2487 	} catch (...) {
2488 		errorStatus = SC_STATUS_FAILURE;
2489 	}
2490 }
2491 
PreeditChanged(GtkIMContext *,ScintillaGTK * sciThis)2492 void ScintillaGTK::PreeditChanged(GtkIMContext *, ScintillaGTK *sciThis) {
2493 	if ((sciThis->imeInteraction == imeInline) || (sciThis->KoreanIME())) {
2494 		sciThis->PreeditChangedInlineThis();
2495 	} else {
2496 		sciThis->PreeditChangedWindowedThis();
2497 	}
2498 }
2499 
StyleSetText(GtkWidget * widget,GtkStyle *,void *)2500 void ScintillaGTK::StyleSetText(GtkWidget *widget, GtkStyle *, void *) {
2501 	RealizeText(widget, nullptr);
2502 }
2503 
RealizeText(GtkWidget * widget,void *)2504 void ScintillaGTK::RealizeText(GtkWidget *widget, void *) {
2505 	// Set NULL background to avoid automatic clearing so Scintilla responsible for all drawing
2506 	if (WindowFromWidget(widget)) {
2507 #if GTK_CHECK_VERSION(3,22,0)
2508 		// Appears unnecessary
2509 #elif GTK_CHECK_VERSION(3,0,0)
2510 		gdk_window_set_background_pattern(WindowFromWidget(widget), nullptr);
2511 #else
2512 		gdk_window_set_back_pixmap(WindowFromWidget(widget), nullptr, FALSE);
2513 #endif
2514 	}
2515 }
2516 
2517 static GObjectClass *scintilla_class_parent_class;
2518 
Dispose(GObject * object)2519 void ScintillaGTK::Dispose(GObject *object) {
2520 	try {
2521 		ScintillaObject *scio = SCINTILLA(object);
2522 		ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(scio->pscin);
2523 
2524 		if (PWidget(sciThis->scrollbarv)) {
2525 			gtk_widget_unparent(PWidget(sciThis->scrollbarv));
2526 			sciThis->scrollbarv = nullptr;
2527 		}
2528 
2529 		if (PWidget(sciThis->scrollbarh)) {
2530 			gtk_widget_unparent(PWidget(sciThis->scrollbarh));
2531 			sciThis->scrollbarh = nullptr;
2532 		}
2533 
2534 		scintilla_class_parent_class->dispose(object);
2535 	} catch (...) {
2536 		// Its dying so nowhere to save the status
2537 	}
2538 }
2539 
Destroy(GObject * object)2540 void ScintillaGTK::Destroy(GObject *object) {
2541 	try {
2542 		ScintillaObject *scio = SCINTILLA(object);
2543 
2544 		// This avoids a double destruction
2545 		if (!scio->pscin)
2546 			return;
2547 		ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(scio->pscin);
2548 		//Platform::DebugPrintf("Destroying %x %x\n", sciThis, object);
2549 		sciThis->Finalise();
2550 
2551 		delete sciThis;
2552 		scio->pscin = nullptr;
2553 		scintilla_class_parent_class->finalize(object);
2554 	} catch (...) {
2555 		// Its dead so nowhere to save the status
2556 	}
2557 }
2558 
2559 #if GTK_CHECK_VERSION(3,0,0)
2560 
DrawTextThis(cairo_t * cr)2561 gboolean ScintillaGTK::DrawTextThis(cairo_t *cr) {
2562 	try {
2563 		paintState = painting;
2564 		repaintFullWindow = false;
2565 
2566 		rcPaint = GetClientRectangle();
2567 
2568 		PLATFORM_ASSERT(rgnUpdate == nullptr);
2569 		rgnUpdate = cairo_copy_clip_rectangle_list(cr);
2570 		if (rgnUpdate && rgnUpdate->status != CAIRO_STATUS_SUCCESS) {
2571 			// If not successful then ignore
2572 			fprintf(stderr, "DrawTextThis failed to copy update region %d [%d]\n", rgnUpdate->status, rgnUpdate->num_rectangles);
2573 			cairo_rectangle_list_destroy(rgnUpdate);
2574 			rgnUpdate = 0;
2575 		}
2576 
2577 		double x1, y1, x2, y2;
2578 		cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
2579 		rcPaint.left = x1;
2580 		rcPaint.top = y1;
2581 		rcPaint.right = x2;
2582 		rcPaint.bottom = y2;
2583 		PRectangle rcClient = GetClientRectangle();
2584 		paintingAllText = rcPaint.Contains(rcClient);
2585 		std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(SC_TECHNOLOGY_DEFAULT));
2586 		surfaceWindow->Init(cr, PWidget(wText));
2587 		Paint(surfaceWindow.get(), rcPaint);
2588 		surfaceWindow->Release();
2589 		if ((paintState == paintAbandoned) || repaintFullWindow) {
2590 			// Painting area was insufficient to cover new styling or brace highlight positions
2591 			FullPaint();
2592 		}
2593 		paintState = notPainting;
2594 		repaintFullWindow = false;
2595 
2596 		if (rgnUpdate) {
2597 			cairo_rectangle_list_destroy(rgnUpdate);
2598 		}
2599 		rgnUpdate = 0;
2600 		paintState = notPainting;
2601 	} catch (...) {
2602 		errorStatus = SC_STATUS_FAILURE;
2603 	}
2604 
2605 	return FALSE;
2606 }
2607 
DrawText(GtkWidget *,cairo_t * cr,ScintillaGTK * sciThis)2608 gboolean ScintillaGTK::DrawText(GtkWidget *, cairo_t *cr, ScintillaGTK *sciThis) {
2609 	return sciThis->DrawTextThis(cr);
2610 }
2611 
DrawThis(cairo_t * cr)2612 gboolean ScintillaGTK::DrawThis(cairo_t *cr) {
2613 	try {
2614 #ifdef GTK_STYLE_CLASS_SCROLLBARS_JUNCTION /* GTK >= 3.4 */
2615 		// if both scrollbars are visible, paint the little square on the bottom right corner
2616 		if (verticalScrollBarVisible && horizontalScrollBarVisible && !Wrapping()) {
2617 			GtkStyleContext *styleContext = gtk_widget_get_style_context(PWidget(wMain));
2618 			PRectangle rc = GetClientRectangle();
2619 
2620 			gtk_style_context_save(styleContext);
2621 			gtk_style_context_add_class(styleContext, GTK_STYLE_CLASS_SCROLLBARS_JUNCTION);
2622 
2623 			gtk_render_background(styleContext, cr, rc.right, rc.bottom,
2624 					      verticalScrollBarWidth, horizontalScrollBarHeight);
2625 			gtk_render_frame(styleContext, cr, rc.right, rc.bottom,
2626 					 verticalScrollBarWidth, horizontalScrollBarHeight);
2627 
2628 			gtk_style_context_restore(styleContext);
2629 		}
2630 #endif
2631 
2632 		gtk_container_propagate_draw(
2633 			GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarh), cr);
2634 		gtk_container_propagate_draw(
2635 			GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarv), cr);
2636 // Starting from the following version, the expose event are not propagated
2637 // for double buffered non native windows, so we need to call it ourselves
2638 // or keep the default handler
2639 #if GTK_CHECK_VERSION(3,0,0)
2640 		// we want to forward on any >= 3.9.2 runtime
2641 		if (gtk_check_version(3, 9, 2) == nullptr) {
2642 			gtk_container_propagate_draw(
2643 				GTK_CONTAINER(PWidget(wMain)), PWidget(wText), cr);
2644 		}
2645 #endif
2646 	} catch (...) {
2647 		errorStatus = SC_STATUS_FAILURE;
2648 	}
2649 	return FALSE;
2650 }
2651 
DrawMain(GtkWidget * widget,cairo_t * cr)2652 gboolean ScintillaGTK::DrawMain(GtkWidget *widget, cairo_t *cr) {
2653 	ScintillaGTK *sciThis = FromWidget(widget);
2654 	return sciThis->DrawThis(cr);
2655 }
2656 
2657 #else
2658 
ExposeTextThis(GtkWidget *,GdkEventExpose * ose)2659 gboolean ScintillaGTK::ExposeTextThis(GtkWidget * /*widget*/, GdkEventExpose *ose) {
2660 	try {
2661 		paintState = painting;
2662 
2663 		rcPaint = PRectangle::FromInts(
2664 				  ose->area.x,
2665 				  ose->area.y,
2666 				  ose->area.x + ose->area.width,
2667 				  ose->area.y + ose->area.height);
2668 
2669 		PLATFORM_ASSERT(rgnUpdate == nullptr);
2670 		rgnUpdate = gdk_region_copy(ose->region);
2671 		const PRectangle rcClient = GetClientRectangle();
2672 		paintingAllText = rcPaint.Contains(rcClient);
2673 		std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(SC_TECHNOLOGY_DEFAULT));
2674 		cairo_t *cr = gdk_cairo_create(PWindow(wText));
2675 		surfaceWindow->Init(cr, PWidget(wText));
2676 		Paint(surfaceWindow.get(), rcPaint);
2677 		surfaceWindow->Release();
2678 		cairo_destroy(cr);
2679 		if ((paintState == paintAbandoned) || repaintFullWindow) {
2680 			// Painting area was insufficient to cover new styling or brace highlight positions
2681 			FullPaint();
2682 		}
2683 		paintState = notPainting;
2684 		repaintFullWindow = false;
2685 
2686 		if (rgnUpdate) {
2687 			gdk_region_destroy(rgnUpdate);
2688 		}
2689 		rgnUpdate = nullptr;
2690 	} catch (...) {
2691 		errorStatus = SC_STATUS_FAILURE;
2692 	}
2693 
2694 	return FALSE;
2695 }
2696 
ExposeText(GtkWidget * widget,GdkEventExpose * ose,ScintillaGTK * sciThis)2697 gboolean ScintillaGTK::ExposeText(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis) {
2698 	return sciThis->ExposeTextThis(widget, ose);
2699 }
2700 
ExposeMain(GtkWidget * widget,GdkEventExpose * ose)2701 gboolean ScintillaGTK::ExposeMain(GtkWidget *widget, GdkEventExpose *ose) {
2702 	ScintillaGTK *sciThis = FromWidget(widget);
2703 	//Platform::DebugPrintf("Expose Main %0d,%0d %0d,%0d\n",
2704 	//ose->area.x, ose->area.y, ose->area.width, ose->area.height);
2705 	return sciThis->Expose(widget, ose);
2706 }
2707 
Expose(GtkWidget *,GdkEventExpose * ose)2708 gboolean ScintillaGTK::Expose(GtkWidget *, GdkEventExpose *ose) {
2709 	try {
2710 		//fprintf(stderr, "Expose %0d,%0d %0d,%0d\n",
2711 		//ose->area.x, ose->area.y, ose->area.width, ose->area.height);
2712 
2713 		// The text is painted in ExposeText
2714 		gtk_container_propagate_expose(
2715 			GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarh), ose);
2716 		gtk_container_propagate_expose(
2717 			GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarv), ose);
2718 
2719 	} catch (...) {
2720 		errorStatus = SC_STATUS_FAILURE;
2721 	}
2722 	return FALSE;
2723 }
2724 
2725 #endif
2726 
ScrollSignal(GtkAdjustment * adj,ScintillaGTK * sciThis)2727 void ScintillaGTK::ScrollSignal(GtkAdjustment *adj, ScintillaGTK *sciThis) {
2728 	try {
2729 		sciThis->ScrollTo(static_cast<int>(gtk_adjustment_get_value(adj)), false);
2730 	} catch (...) {
2731 		sciThis->errorStatus = SC_STATUS_FAILURE;
2732 	}
2733 }
2734 
ScrollHSignal(GtkAdjustment * adj,ScintillaGTK * sciThis)2735 void ScintillaGTK::ScrollHSignal(GtkAdjustment *adj, ScintillaGTK *sciThis) {
2736 	try {
2737 		sciThis->HorizontalScrollTo(static_cast<int>(gtk_adjustment_get_value(adj)));
2738 	} catch (...) {
2739 		sciThis->errorStatus = SC_STATUS_FAILURE;
2740 	}
2741 }
2742 
SelectionReceived(GtkWidget * widget,GtkSelectionData * selection_data,guint)2743 void ScintillaGTK::SelectionReceived(GtkWidget *widget,
2744 				     GtkSelectionData *selection_data, guint) {
2745 	ScintillaGTK *sciThis = FromWidget(widget);
2746 	//Platform::DebugPrintf("Selection received\n");
2747 	sciThis->ReceivedSelection(selection_data);
2748 }
2749 
SelectionGet(GtkWidget * widget,GtkSelectionData * selection_data,guint info,guint)2750 void ScintillaGTK::SelectionGet(GtkWidget *widget,
2751 				GtkSelectionData *selection_data, guint info, guint) {
2752 	ScintillaGTK *sciThis = FromWidget(widget);
2753 	try {
2754 		//Platform::DebugPrintf("Selection get\n");
2755 		if (SelectionOfGSD(selection_data) == GDK_SELECTION_PRIMARY) {
2756 			if (sciThis->primary.Empty()) {
2757 				sciThis->CopySelectionRange(&sciThis->primary);
2758 			}
2759 			sciThis->GetSelection(selection_data, info, &sciThis->primary);
2760 		}
2761 	} catch (...) {
2762 		sciThis->errorStatus = SC_STATUS_FAILURE;
2763 	}
2764 }
2765 
SelectionClear(GtkWidget * widget,GdkEventSelection * selection_event)2766 gint ScintillaGTK::SelectionClear(GtkWidget *widget, GdkEventSelection *selection_event) {
2767 	ScintillaGTK *sciThis = FromWidget(widget);
2768 	//Platform::DebugPrintf("Selection clear\n");
2769 	sciThis->UnclaimSelection(selection_event);
2770 	if (GTK_WIDGET_CLASS(sciThis->parentClass)->selection_clear_event) {
2771 		return GTK_WIDGET_CLASS(sciThis->parentClass)->selection_clear_event(widget, selection_event);
2772 	}
2773 	return TRUE;
2774 }
2775 
DragMotionThis(GdkDragContext * context,gint x,gint y,guint dragtime)2776 gboolean ScintillaGTK::DragMotionThis(GdkDragContext *context,
2777 				      gint x, gint y, guint dragtime) {
2778 	try {
2779 		const Point npt = Point::FromInts(x, y);
2780 		SetDragPosition(SPositionFromLocation(npt, false, false, UserVirtualSpace()));
2781 		GdkDragAction preferredAction = gdk_drag_context_get_suggested_action(context);
2782 		const GdkDragAction actions = gdk_drag_context_get_actions(context);
2783 		const SelectionPosition pos = SPositionFromLocation(npt);
2784 		if ((inDragDrop == ddDragging) && (PositionInSelection(pos.Position()))) {
2785 			// Avoid dragging selection onto itself as that produces a move
2786 			// with no real effect but which creates undo actions.
2787 			preferredAction = static_cast<GdkDragAction>(0);
2788 		} else if (actions == actionCopyOrMove) {
2789 			preferredAction = GDK_ACTION_MOVE;
2790 		}
2791 		gdk_drag_status(context, preferredAction, dragtime);
2792 	} catch (...) {
2793 		errorStatus = SC_STATUS_FAILURE;
2794 	}
2795 	return FALSE;
2796 }
2797 
DragMotion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint dragtime)2798 gboolean ScintillaGTK::DragMotion(GtkWidget *widget, GdkDragContext *context,
2799 				  gint x, gint y, guint dragtime) {
2800 	ScintillaGTK *sciThis = FromWidget(widget);
2801 	return sciThis->DragMotionThis(context, x, y, dragtime);
2802 }
2803 
DragLeave(GtkWidget * widget,GdkDragContext *,guint)2804 void ScintillaGTK::DragLeave(GtkWidget *widget, GdkDragContext * /*context*/, guint) {
2805 	ScintillaGTK *sciThis = FromWidget(widget);
2806 	try {
2807 		sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
2808 		//Platform::DebugPrintf("DragLeave %x\n", sciThis);
2809 	} catch (...) {
2810 		sciThis->errorStatus = SC_STATUS_FAILURE;
2811 	}
2812 }
2813 
DragEnd(GtkWidget * widget,GdkDragContext *)2814 void ScintillaGTK::DragEnd(GtkWidget *widget, GdkDragContext * /*context*/) {
2815 	ScintillaGTK *sciThis = FromWidget(widget);
2816 	try {
2817 		// If drag did not result in drop here or elsewhere
2818 		if (!sciThis->dragWasDropped)
2819 			sciThis->SetEmptySelection(sciThis->posDrag);
2820 		sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
2821 		//Platform::DebugPrintf("DragEnd %x %d\n", sciThis, sciThis->dragWasDropped);
2822 		sciThis->inDragDrop = ddNone;
2823 	} catch (...) {
2824 		sciThis->errorStatus = SC_STATUS_FAILURE;
2825 	}
2826 }
2827 
Drop(GtkWidget * widget,GdkDragContext *,gint,gint,guint)2828 gboolean ScintillaGTK::Drop(GtkWidget *widget, GdkDragContext * /*context*/,
2829 			    gint, gint, guint) {
2830 	ScintillaGTK *sciThis = FromWidget(widget);
2831 	try {
2832 		//Platform::DebugPrintf("Drop %x\n", sciThis);
2833 		sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
2834 	} catch (...) {
2835 		sciThis->errorStatus = SC_STATUS_FAILURE;
2836 	}
2837 	return FALSE;
2838 }
2839 
DragDataReceived(GtkWidget * widget,GdkDragContext *,gint,gint,GtkSelectionData * selection_data,guint,guint)2840 void ScintillaGTK::DragDataReceived(GtkWidget *widget, GdkDragContext * /*context*/,
2841 				    gint, gint, GtkSelectionData *selection_data, guint /*info*/, guint) {
2842 	ScintillaGTK *sciThis = FromWidget(widget);
2843 	try {
2844 		sciThis->ReceivedDrop(selection_data);
2845 		sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
2846 	} catch (...) {
2847 		sciThis->errorStatus = SC_STATUS_FAILURE;
2848 	}
2849 }
2850 
DragDataGet(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint)2851 void ScintillaGTK::DragDataGet(GtkWidget *widget, GdkDragContext *context,
2852 			       GtkSelectionData *selection_data, guint info, guint) {
2853 	ScintillaGTK *sciThis = FromWidget(widget);
2854 	try {
2855 		sciThis->dragWasDropped = true;
2856 		if (!sciThis->sel.Empty()) {
2857 			sciThis->GetSelection(selection_data, info, &sciThis->drag);
2858 		}
2859 		const GdkDragAction action = gdk_drag_context_get_selected_action(context);
2860 		if (action == GDK_ACTION_MOVE) {
2861 			for (size_t r=0; r<sciThis->sel.Count(); r++) {
2862 				if (sciThis->posDrop >= sciThis->sel.Range(r).Start()) {
2863 					if (sciThis->posDrop > sciThis->sel.Range(r).End()) {
2864 						sciThis->posDrop.Add(-sciThis->sel.Range(r).Length());
2865 					} else {
2866 						sciThis->posDrop.Add(-SelectionRange(sciThis->posDrop, sciThis->sel.Range(r).Start()).Length());
2867 					}
2868 				}
2869 			}
2870 			sciThis->ClearSelection();
2871 		}
2872 		sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
2873 	} catch (...) {
2874 		sciThis->errorStatus = SC_STATUS_FAILURE;
2875 	}
2876 }
2877 
TimeOut(gpointer ptt)2878 int ScintillaGTK::TimeOut(gpointer ptt) {
2879 	TimeThunk *tt = static_cast<TimeThunk *>(ptt);
2880 	tt->scintilla->TickFor(tt->reason);
2881 	return 1;
2882 }
2883 
IdleCallback(gpointer pSci)2884 gboolean ScintillaGTK::IdleCallback(gpointer pSci) {
2885 	ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(pSci);
2886 	// Idler will be automatically stopped, if there is nothing
2887 	// to do while idle.
2888 	const bool ret = sciThis->Idle();
2889 	if (ret == false) {
2890 		// FIXME: This will remove the idler from GTK, we don't want to
2891 		// remove it as it is removed automatically when this function
2892 		// returns false (although, it should be harmless).
2893 		sciThis->SetIdle(false);
2894 	}
2895 	return ret;
2896 }
2897 
StyleIdle(gpointer pSci)2898 gboolean ScintillaGTK::StyleIdle(gpointer pSci) {
2899 	ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(pSci);
2900 	sciThis->IdleWork();
2901 	// Idler will be automatically stopped
2902 	return FALSE;
2903 }
2904 
IdleWork()2905 void ScintillaGTK::IdleWork() {
2906 	Editor::IdleWork();
2907 	styleIdleID = 0;
2908 }
2909 
QueueIdleWork(WorkNeeded::workItems items,Sci::Position upTo)2910 void ScintillaGTK::QueueIdleWork(WorkNeeded::workItems items, Sci::Position upTo) {
2911 	Editor::QueueIdleWork(items, upTo);
2912 	if (!styleIdleID) {
2913 		// Only allow one style needed to be queued
2914 		styleIdleID = gdk_threads_add_idle_full(G_PRIORITY_HIGH_IDLE, StyleIdle, this, nullptr);
2915 	}
2916 }
2917 
SetDocPointer(Document * document)2918 void ScintillaGTK::SetDocPointer(Document *document) {
2919 	Document *oldDoc = nullptr;
2920 	ScintillaGTKAccessible *sciAccessible = nullptr;
2921 	if (accessible) {
2922 		sciAccessible = ScintillaGTKAccessible::FromAccessible(accessible);
2923 		if (sciAccessible && pdoc) {
2924 			oldDoc = pdoc;
2925 			oldDoc->AddRef();
2926 		}
2927 	}
2928 
2929 	Editor::SetDocPointer(document);
2930 
2931 	if (sciAccessible) {
2932 		// the accessible needs have the old Document, but also the new one active
2933 		sciAccessible->ChangeDocument(oldDoc, pdoc);
2934 	}
2935 	if (oldDoc) {
2936 		oldDoc->Release();
2937 	}
2938 }
2939 
PopUpCB(GtkMenuItem * menuItem,ScintillaGTK * sciThis)2940 void ScintillaGTK::PopUpCB(GtkMenuItem *menuItem, ScintillaGTK *sciThis) {
2941 	guint const action = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(menuItem), "CmdNum"));
2942 	if (action) {
2943 		sciThis->Command(action);
2944 	}
2945 }
2946 
PressCT(GtkWidget * widget,GdkEventButton * event,ScintillaGTK * sciThis)2947 gboolean ScintillaGTK::PressCT(GtkWidget *widget, GdkEventButton *event, ScintillaGTK *sciThis) {
2948 	try {
2949 		if (event->window != WindowFromWidget(widget))
2950 			return FALSE;
2951 		if (event->type != GDK_BUTTON_PRESS)
2952 			return FALSE;
2953 		const Point pt = PointOfEvent(event);
2954 		sciThis->ct.MouseClick(pt);
2955 		sciThis->CallTipClick();
2956 	} catch (...) {
2957 	}
2958 	return TRUE;
2959 }
2960 
2961 #if GTK_CHECK_VERSION(3,0,0)
2962 
DrawCT(GtkWidget * widget,cairo_t * cr,CallTip * ctip)2963 gboolean ScintillaGTK::DrawCT(GtkWidget *widget, cairo_t *cr, CallTip *ctip) {
2964 	try {
2965 		std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(SC_TECHNOLOGY_DEFAULT));
2966 		surfaceWindow->Init(cr, widget);
2967 		surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == ctip->codePage);
2968 		surfaceWindow->SetDBCSMode(ctip->codePage);
2969 		ctip->PaintCT(surfaceWindow.get());
2970 		surfaceWindow->Release();
2971 	} catch (...) {
2972 		// No pointer back to Scintilla to save status
2973 	}
2974 	return TRUE;
2975 }
2976 
2977 #else
2978 
ExposeCT(GtkWidget * widget,GdkEventExpose *,CallTip * ctip)2979 gboolean ScintillaGTK::ExposeCT(GtkWidget *widget, GdkEventExpose * /*ose*/, CallTip *ctip) {
2980 	try {
2981 		std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(SC_TECHNOLOGY_DEFAULT));
2982 		cairo_t *cr = gdk_cairo_create(WindowFromWidget(widget));
2983 		surfaceWindow->Init(cr, widget);
2984 		surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == ctip->codePage);
2985 		surfaceWindow->SetDBCSMode(ctip->codePage);
2986 		ctip->PaintCT(surfaceWindow.get());
2987 		surfaceWindow->Release();
2988 		cairo_destroy(cr);
2989 	} catch (...) {
2990 		// No pointer back to Scintilla to save status
2991 	}
2992 	return TRUE;
2993 }
2994 
2995 #endif
2996 
GetAccessibleThis(GtkWidget * widget)2997 AtkObject *ScintillaGTK::GetAccessibleThis(GtkWidget *widget) {
2998 	return ScintillaGTKAccessible::WidgetGetAccessibleImpl(widget, &accessible, scintilla_class_parent_class);
2999 }
3000 
GetAccessible(GtkWidget * widget)3001 AtkObject *ScintillaGTK::GetAccessible(GtkWidget *widget) {
3002 	return FromWidget(widget)->GetAccessibleThis(widget);
3003 }
3004 
DirectFunction(sptr_t ptr,unsigned int iMessage,uptr_t wParam,sptr_t lParam)3005 sptr_t ScintillaGTK::DirectFunction(
3006 	sptr_t ptr, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
3007 	return reinterpret_cast<ScintillaGTK *>(ptr)->WndProc(iMessage, wParam, lParam);
3008 }
3009 
3010 /* legacy name for scintilla_object_send_message */
3011 GEANY_API_SYMBOL
scintilla_send_message(ScintillaObject * sci,unsigned int iMessage,uptr_t wParam,sptr_t lParam)3012 sptr_t scintilla_send_message(ScintillaObject *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
3013 	ScintillaGTK *psci = static_cast<ScintillaGTK *>(sci->pscin);
3014 	return psci->WndProc(iMessage, wParam, lParam);
3015 }
3016 
3017 GEANY_API_SYMBOL
scintilla_object_send_message(ScintillaObject * sci,unsigned int iMessage,uptr_t wParam,sptr_t lParam)3018 gintptr scintilla_object_send_message(ScintillaObject *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
3019 	return scintilla_send_message(sci, iMessage, wParam, lParam);
3020 }
3021 
3022 static void scintilla_class_init(ScintillaClass *klass);
3023 static void scintilla_init(ScintillaObject *sci);
3024 
3025 extern void Platform_Initialise();
3026 extern void Platform_Finalise();
3027 
3028 /* legacy name for scintilla_object_get_type */
3029 GEANY_API_SYMBOL
scintilla_get_type()3030 GType scintilla_get_type() {
3031 	static GType scintilla_type = 0;
3032 	try {
3033 
3034 		if (!scintilla_type) {
3035 			scintilla_type = g_type_from_name("ScintillaObject");
3036 			if (!scintilla_type) {
3037 				static GTypeInfo scintilla_info = {
3038 					(guint16) sizeof(ScintillaObjectClass),
3039 					nullptr, //(GBaseInitFunc)
3040 					nullptr, //(GBaseFinalizeFunc)
3041 					(GClassInitFunc) scintilla_class_init,
3042 					nullptr, //(GClassFinalizeFunc)
3043 					nullptr, //gconstpointer data
3044 					(guint16) sizeof(ScintillaObject),
3045 					0, //n_preallocs
3046 					(GInstanceInitFunc) scintilla_init,
3047 					nullptr //(GTypeValueTable*)
3048 				};
3049 				scintilla_type = g_type_register_static(
3050 							 GTK_TYPE_CONTAINER, "ScintillaObject", &scintilla_info, (GTypeFlags) 0);
3051 			}
3052 		}
3053 
3054 	} catch (...) {
3055 	}
3056 	return scintilla_type;
3057 }
3058 
3059 GEANY_API_SYMBOL
scintilla_object_get_type()3060 GType scintilla_object_get_type() {
3061 	return scintilla_get_type();
3062 }
3063 
ClassInit(OBJECT_CLASS * object_class,GtkWidgetClass * widget_class,GtkContainerClass * container_class)3064 void ScintillaGTK::ClassInit(OBJECT_CLASS *object_class, GtkWidgetClass *widget_class, GtkContainerClass *container_class) {
3065 	Platform_Initialise();
3066 	atomUTF8 = gdk_atom_intern("UTF8_STRING", FALSE);
3067 	atomUTF8Mime = gdk_atom_intern("text/plain;charset=utf-8", FALSE);
3068 	atomString = GDK_SELECTION_TYPE_STRING;
3069 	atomUriList = gdk_atom_intern("text/uri-list", FALSE);
3070 	atomDROPFILES_DND = gdk_atom_intern("DROPFILES_DND", FALSE);
3071 
3072 	// Define default signal handlers for the class:  Could move more
3073 	// of the signal handlers here (those that currently attached to wDraw
3074 	// in Init() may require coordinate translation?)
3075 
3076 	object_class->dispose = Dispose;
3077 	object_class->finalize = Destroy;
3078 #if GTK_CHECK_VERSION(3,0,0)
3079 	widget_class->get_preferred_width = GetPreferredWidth;
3080 	widget_class->get_preferred_height = GetPreferredHeight;
3081 #else
3082 	widget_class->size_request = SizeRequest;
3083 #endif
3084 	widget_class->size_allocate = SizeAllocate;
3085 #if GTK_CHECK_VERSION(3,0,0)
3086 	widget_class->draw = DrawMain;
3087 #else
3088 	widget_class->expose_event = ExposeMain;
3089 #endif
3090 	widget_class->motion_notify_event = Motion;
3091 	widget_class->button_press_event = Press;
3092 	widget_class->button_release_event = MouseRelease;
3093 	widget_class->scroll_event = ScrollEvent;
3094 	widget_class->key_press_event = KeyPress;
3095 	widget_class->key_release_event = KeyRelease;
3096 	widget_class->focus_in_event = FocusIn;
3097 	widget_class->focus_out_event = FocusOut;
3098 	widget_class->selection_received = SelectionReceived;
3099 	widget_class->selection_get = SelectionGet;
3100 	widget_class->selection_clear_event = SelectionClear;
3101 
3102 	widget_class->drag_data_received = DragDataReceived;
3103 	widget_class->drag_motion = DragMotion;
3104 	widget_class->drag_leave = DragLeave;
3105 	widget_class->drag_end = DragEnd;
3106 	widget_class->drag_drop = Drop;
3107 	widget_class->drag_data_get = DragDataGet;
3108 
3109 	widget_class->realize = Realize;
3110 	widget_class->unrealize = UnRealize;
3111 	widget_class->map = Map;
3112 	widget_class->unmap = UnMap;
3113 
3114 	widget_class->get_accessible = GetAccessible;
3115 
3116 	container_class->forall = MainForAll;
3117 }
3118 
scintilla_class_init(ScintillaClass * klass)3119 static void scintilla_class_init(ScintillaClass *klass) {
3120 	try {
3121 		OBJECT_CLASS *object_class = (OBJECT_CLASS *) klass;
3122 		GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
3123 		GtkContainerClass *container_class = (GtkContainerClass *) klass;
3124 
3125 		const GSignalFlags sigflags = GSignalFlags(G_SIGNAL_ACTION | G_SIGNAL_RUN_LAST);
3126 		scintilla_signals[COMMAND_SIGNAL] = g_signal_new(
3127 				"command",
3128 				G_TYPE_FROM_CLASS(object_class),
3129 				sigflags,
3130 				G_STRUCT_OFFSET(ScintillaClass, command),
3131 				nullptr, //(GSignalAccumulator)
3132 				nullptr, //(gpointer)
3133 				scintilla_marshal_VOID__INT_OBJECT,
3134 				G_TYPE_NONE,
3135 				2, G_TYPE_INT, GTK_TYPE_WIDGET);
3136 
3137 		scintilla_signals[NOTIFY_SIGNAL] = g_signal_new(
3138 				SCINTILLA_NOTIFY,
3139 				G_TYPE_FROM_CLASS(object_class),
3140 				sigflags,
3141 				G_STRUCT_OFFSET(ScintillaClass, notify),
3142 				nullptr, //(GSignalAccumulator)
3143 				nullptr, //(gpointer)
3144 				scintilla_marshal_VOID__INT_BOXED,
3145 				G_TYPE_NONE,
3146 				2, G_TYPE_INT, SCINTILLA_TYPE_NOTIFICATION);
3147 
3148 		klass->command = nullptr;
3149 		klass->notify = nullptr;
3150 		scintilla_class_parent_class = G_OBJECT_CLASS(g_type_class_peek_parent(klass));
3151 		ScintillaGTK::ClassInit(object_class, widget_class, container_class);
3152 	} catch (...) {
3153 	}
3154 }
3155 
scintilla_init(ScintillaObject * sci)3156 static void scintilla_init(ScintillaObject *sci) {
3157 	try {
3158 		gtk_widget_set_can_focus(GTK_WIDGET(sci), TRUE);
3159 		sci->pscin = new ScintillaGTK(sci);
3160 	} catch (...) {
3161 	}
3162 }
3163 
3164 /* legacy name for scintilla_object_new */
3165 GEANY_API_SYMBOL
scintilla_new()3166 GtkWidget *scintilla_new() {
3167 	GtkWidget *widget = GTK_WIDGET(g_object_new(scintilla_get_type(), nullptr));
3168 	gtk_widget_set_direction(widget, GTK_TEXT_DIR_LTR);
3169 
3170 	return widget;
3171 }
3172 
3173 GEANY_API_SYMBOL
scintilla_object_new()3174 GtkWidget *scintilla_object_new() {
3175 	return scintilla_new();
3176 }
3177 
scintilla_set_id(ScintillaObject * sci,uptr_t id)3178 void scintilla_set_id(ScintillaObject *sci, uptr_t id) {
3179 	ScintillaGTK *psci = static_cast<ScintillaGTK *>(sci->pscin);
3180 	psci->ctrlID = id;
3181 }
3182 
scintilla_release_resources(void)3183 void scintilla_release_resources(void) {
3184 	try {
3185 		Platform_Finalise();
3186 	} catch (...) {
3187 	}
3188 }
3189 
3190 /* Define a dummy boxed type because g-ir-scanner is unable to
3191  * recognize gpointer-derived types. Note that SCNotificaiton
3192  * is always allocated on stack so copying is not appropriate. */
copy_(void * src)3193 static void *copy_(void *src) { return src; }
free_(void *)3194 static void free_(void *) { }
3195 
3196 GEANY_API_SYMBOL
scnotification_get_type(void)3197 GType scnotification_get_type(void) {
3198 	static gsize type_id = 0;
3199 	if (g_once_init_enter(&type_id)) {
3200 		const gsize id = (gsize) g_boxed_type_register_static(
3201 					 g_intern_static_string("SCNotification"),
3202 					 (GBoxedCopyFunc) copy_,
3203 					 (GBoxedFreeFunc) free_);
3204 		g_once_init_leave(&type_id, id);
3205 	}
3206 	return (GType) type_id;
3207 }
3208