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