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