Merge pull request #3720 from b4n/encodings-ui-improvements
[geany-mirror.git] / scintilla / gtk / ScintillaGTK.cxx
blobd2780a959eda4a089d4c94ac67a33458b3e78105
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.
6 #include <cstddef>
7 #include <cstdlib>
8 #include <cstdint>
9 #include <cassert>
10 #include <cstring>
11 #include <cstdio>
12 #include <ctime>
13 #include <cmath>
15 #include <stdexcept>
16 #include <new>
17 #include <string>
18 #include <string_view>
19 #include <vector>
20 #include <map>
21 #include <set>
22 #include <optional>
23 #include <algorithm>
24 #include <memory>
26 #include <glib.h>
27 #include <gmodule.h>
28 #include <gdk/gdk.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 #if defined(GDK_WINDOWING_WAYLAND)
32 #include <gdk/gdkwayland.h>
33 #endif
35 #if defined(_WIN32)
36 // On Win32 use windows.h to access clipboard (rectangular format) and systems parameters
37 #undef NOMINMAX
38 #define NOMINMAX
39 #include <windows.h>
40 #endif
42 #include "ScintillaTypes.h"
43 #include "ScintillaMessages.h"
44 #include "ScintillaStructures.h"
45 #include "ILoader.h"
46 #include "ILexer.h"
48 #include "Debugging.h"
49 #include "Geometry.h"
50 #include "Platform.h"
52 #include "Scintilla.h"
53 #include "ScintillaWidget.h"
54 #include "CharacterCategoryMap.h"
55 #include "Position.h"
56 #include "UniqueString.h"
57 #include "SplitVector.h"
58 #include "Partitioning.h"
59 #include "RunStyles.h"
60 #include "ContractionState.h"
61 #include "CellBuffer.h"
62 #include "CallTip.h"
63 #include "KeyMap.h"
64 #include "Indicator.h"
65 #include "LineMarker.h"
66 #include "Style.h"
67 #include "ViewStyle.h"
68 #include "CharClassify.h"
69 #include "Decoration.h"
70 #include "CaseFolder.h"
71 #include "Document.h"
72 #include "CaseConvert.h"
73 #include "UniConversion.h"
74 #include "Selection.h"
75 #include "PositionCache.h"
76 #include "EditModel.h"
77 #include "MarginView.h"
78 #include "EditView.h"
79 #include "Editor.h"
80 #include "AutoComplete.h"
81 #include "ScintillaBase.h"
83 #include "Wrappers.h"
84 #include "ScintillaGTK.h"
85 #include "scintilla-marshal.h"
86 #include "ScintillaGTKAccessible.h"
87 #include "Converter.h"
89 #define IS_WIDGET_REALIZED(w) (gtk_widget_get_realized(GTK_WIDGET(w)))
90 #define IS_WIDGET_MAPPED(w) (gtk_widget_get_mapped(GTK_WIDGET(w)))
92 #define SC_INDICATOR_INPUT INDICATOR_IME
93 #define SC_INDICATOR_TARGET INDICATOR_IME+1
94 #define SC_INDICATOR_CONVERTED INDICATOR_IME+2
95 #define SC_INDICATOR_UNKNOWN INDICATOR_IME_MAX
97 using namespace Scintilla;
98 using namespace Scintilla::Internal;
100 // From PlatGTK.cxx
101 extern std::string UTF8FromLatin1(std::string_view text);
102 extern void Platform_Initialise();
103 extern void Platform_Finalise();
105 namespace {
107 enum {
108 COMMAND_SIGNAL,
109 NOTIFY_SIGNAL,
110 LAST_SIGNAL
113 gint scintilla_signals[LAST_SIGNAL] = { 0 };
115 enum {
116 TARGET_STRING,
117 TARGET_TEXT,
118 TARGET_COMPOUND_TEXT,
119 TARGET_UTF8_STRING,
120 TARGET_URI
123 const GtkTargetEntry clipboardCopyTargets[] = {
124 { (gchar *) "UTF8_STRING", 0, TARGET_UTF8_STRING },
125 { (gchar *) "STRING", 0, TARGET_STRING },
127 constexpr gint nClipboardCopyTargets = static_cast<gint>(std::size(clipboardCopyTargets));
129 const GtkTargetEntry clipboardPasteTargets[] = {
130 { (gchar *) "text/uri-list", 0, TARGET_URI },
131 { (gchar *) "UTF8_STRING", 0, TARGET_UTF8_STRING },
132 { (gchar *) "STRING", 0, TARGET_STRING },
134 constexpr gint nClipboardPasteTargets = static_cast<gint>(std::size(clipboardPasteTargets));
136 const GdkDragAction actionCopyOrMove = static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE);
138 GtkWidget *PWidget(const Window &w) noexcept {
139 return static_cast<GtkWidget *>(w.GetID());
142 GdkWindow *PWindow(const Window &w) noexcept {
143 GtkWidget *widget = static_cast<GtkWidget *>(w.GetID());
144 return gtk_widget_get_window(widget);
147 void MapWidget(GtkWidget *widget) noexcept {
148 if (widget &&
149 gtk_widget_get_visible(GTK_WIDGET(widget)) &&
150 !IS_WIDGET_MAPPED(widget)) {
151 gtk_widget_map(widget);
155 const guchar *DataOfGSD(GtkSelectionData *sd) noexcept {
156 return gtk_selection_data_get_data(sd);
159 gint LengthOfGSD(GtkSelectionData *sd) noexcept {
160 return gtk_selection_data_get_length(sd);
163 GdkAtom TypeOfGSD(GtkSelectionData *sd) noexcept {
164 return gtk_selection_data_get_data_type(sd);
167 GdkAtom SelectionOfGSD(GtkSelectionData *sd) noexcept {
168 return gtk_selection_data_get_selection(sd);
171 bool SettingGet(GtkSettings *settings, const gchar *name, gpointer value) noexcept {
172 if (!settings) {
173 return false;
175 if (!g_object_class_find_property(G_OBJECT_GET_CLASS(
176 G_OBJECT(settings)), name)) {
177 return false;
179 g_object_get(G_OBJECT(settings), name, value, nullptr);
180 return true;
185 FontOptions::FontOptions(GtkWidget *widget) noexcept {
186 UniquePangoContext pcontext(gtk_widget_create_pango_context(widget));
187 PLATFORM_ASSERT(pcontext);
188 const cairo_font_options_t *options = pango_cairo_context_get_font_options(pcontext.get());
189 // options is owned by the PangoContext so must not be freed.
190 if (options) {
191 // options is NULL on Win32
192 antialias = cairo_font_options_get_antialias(options);
193 order = cairo_font_options_get_subpixel_order(options);
194 hint = cairo_font_options_get_hint_style(options);
198 bool FontOptions::operator==(const FontOptions &other) const noexcept {
199 return antialias == other.antialias &&
200 order == other.order &&
201 hint == other.hint;
204 ScintillaGTK *ScintillaGTK::FromWidget(GtkWidget *widget) noexcept {
205 ScintillaObject *scio = SCINTILLA(widget);
206 return static_cast<ScintillaGTK *>(scio->pscin);
209 ScintillaGTK::ScintillaGTK(_ScintillaObject *sci_) :
210 adjustmentv(nullptr), adjustmenth(nullptr),
211 verticalScrollBarWidth(30), horizontalScrollBarHeight(30),
212 buttonMouse(0),
213 capturedMouse(false), dragWasDropped(false),
214 lastKey(0), rectangularSelectionModifier(SCMOD_CTRL),
215 parentClass(nullptr),
216 atomSought(nullptr),
217 preeditInitialized(false),
218 im_context(nullptr),
219 lastNonCommonScript(G_UNICODE_SCRIPT_INVALID_CODE),
220 settings(nullptr),
221 settingsHandlerId(0),
222 lastWheelMouseTime(0),
223 lastWheelMouseDirection(0),
224 wheelMouseIntensity(0),
225 smoothScrollY(0),
226 smoothScrollX(0),
227 rgnUpdate(nullptr),
228 repaintFullWindow(false),
229 styleIdleID(0),
230 accessibilityEnabled(SC_ACCESSIBILITY_ENABLED),
231 accessible(nullptr) {
232 sci = sci_;
233 wMain = GTK_WIDGET(sci);
235 rectangularSelectionModifier = SCMOD_ALT;
237 #if PLAT_GTK_WIN32
238 // There does not seem to be a real standard for indicating that the clipboard
239 // contains a rectangular selection, so copy Developer Studio.
240 cfColumnSelect = static_cast<CLIPFORMAT>(
241 ::RegisterClipboardFormatW(L"MSDEVColumnSelect"));
243 // Get intellimouse parameters when running on win32; otherwise use
244 // reasonable default
245 #ifndef SPI_GETWHEELSCROLLLINES
246 #define SPI_GETWHEELSCROLLLINES 104
247 #endif
248 ::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0);
249 #else
250 linesPerScroll = 4;
251 #endif
252 primarySelection = false;
254 Init();
257 ScintillaGTK::~ScintillaGTK() {
258 if (styleIdleID) {
259 g_source_remove(styleIdleID);
260 styleIdleID = 0;
262 if (scrollBarIdleID) {
263 g_source_remove(scrollBarIdleID);
264 scrollBarIdleID = 0;
266 ClearPrimarySelection();
267 wPreedit.Destroy();
268 if (settingsHandlerId) {
269 g_signal_handler_disconnect(settings, settingsHandlerId);
273 void ScintillaGTK::RealizeThis(GtkWidget *widget) {
274 //Platform::DebugPrintf("ScintillaGTK::realize this\n");
275 gtk_widget_set_realized(widget, TRUE);
276 GdkWindowAttr attrs {};
277 attrs.window_type = GDK_WINDOW_CHILD;
278 GtkAllocation allocation;
279 gtk_widget_get_allocation(widget, &allocation);
280 attrs.x = allocation.x;
281 attrs.y = allocation.y;
282 attrs.width = allocation.width;
283 attrs.height = allocation.height;
284 attrs.wclass = GDK_INPUT_OUTPUT;
285 attrs.visual = gtk_widget_get_visual(widget);
286 #if !GTK_CHECK_VERSION(3,0,0)
287 attrs.colormap = gtk_widget_get_colormap(widget);
288 #endif
289 attrs.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
290 GdkDisplay *pdisplay = gtk_widget_get_display(widget);
291 GdkCursor *cursor = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
292 attrs.cursor = cursor;
293 #if GTK_CHECK_VERSION(3,0,0)
294 gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attrs,
295 GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_CURSOR));
296 #if GTK_CHECK_VERSION(3,8,0)
297 gtk_widget_register_window(widget, gtk_widget_get_window(widget));
298 #else
299 gdk_window_set_user_data(gtk_widget_get_window(widget), widget);
300 #endif
301 #if !GTK_CHECK_VERSION(3,18,0)
302 gtk_style_context_set_background(gtk_widget_get_style_context(widget),
303 gtk_widget_get_window(widget));
304 #endif
305 gdk_window_show(gtk_widget_get_window(widget));
306 #else
307 widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attrs,
308 GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR);
309 gdk_window_set_user_data(widget->window, widget);
310 widget->style = gtk_style_attach(widget->style, widget->window);
311 gdk_window_set_background(widget->window, &widget->style->bg[GTK_STATE_NORMAL]);
312 gdk_window_show(widget->window);
313 #endif
314 UnRefCursor(cursor);
316 preeditInitialized = false;
317 gtk_widget_realize(PWidget(wPreedit));
318 gtk_widget_realize(PWidget(wPreeditDraw));
320 im_context.reset(gtk_im_multicontext_new());
321 g_signal_connect(G_OBJECT(im_context.get()), "commit",
322 G_CALLBACK(Commit), this);
323 g_signal_connect(G_OBJECT(im_context.get()), "preedit_changed",
324 G_CALLBACK(PreeditChanged), this);
325 g_signal_connect(G_OBJECT(im_context.get()), "retrieve-surrounding",
326 G_CALLBACK(RetrieveSurrounding), this);
327 g_signal_connect(G_OBJECT(im_context.get()), "delete-surrounding",
328 G_CALLBACK(DeleteSurrounding), this);
329 gtk_im_context_set_client_window(im_context.get(), WindowFromWidget(widget));
331 GtkWidget *widtxt = PWidget(wText); // // No code inside the G_OBJECT macro
332 g_signal_connect_after(G_OBJECT(widtxt), "style_set",
333 G_CALLBACK(ScintillaGTK::StyleSetText), nullptr);
334 g_signal_connect_after(G_OBJECT(widtxt), "realize",
335 G_CALLBACK(ScintillaGTK::RealizeText), nullptr);
336 gtk_widget_realize(widtxt);
337 gtk_widget_realize(PWidget(scrollbarv));
338 gtk_widget_realize(PWidget(scrollbarh));
340 cursor = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
341 gdk_window_set_cursor(PWindow(wText), cursor);
342 UnRefCursor(cursor);
344 cursor = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
345 gdk_window_set_cursor(PWindow(scrollbarv), cursor);
346 UnRefCursor(cursor);
348 cursor = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
349 gdk_window_set_cursor(PWindow(scrollbarh), cursor);
350 UnRefCursor(cursor);
352 using NotifyLambda = void (*)(GObject *, GParamSpec *, ScintillaGTK *);
353 if (settings) {
354 settingsHandlerId = g_signal_connect(settings, "notify::gtk-xft-dpi",
355 G_CALLBACK(static_cast<NotifyLambda>([](GObject *, GParamSpec *, ScintillaGTK *sciThis) {
356 sciThis->InvalidateStyleRedraw();
357 })),
358 this);
362 void ScintillaGTK::Realize(GtkWidget *widget) {
363 ScintillaGTK *sciThis = FromWidget(widget);
364 sciThis->RealizeThis(widget);
367 void ScintillaGTK::UnRealizeThis(GtkWidget *widget) {
368 try {
369 if (IS_WIDGET_MAPPED(widget)) {
370 gtk_widget_unmap(widget);
372 gtk_widget_set_realized(widget, FALSE);
373 gtk_widget_unrealize(PWidget(wText));
374 if (PWidget(scrollbarv))
375 gtk_widget_unrealize(PWidget(scrollbarv));
376 if (PWidget(scrollbarh))
377 gtk_widget_unrealize(PWidget(scrollbarh));
378 gtk_widget_unrealize(PWidget(wPreedit));
379 gtk_widget_unrealize(PWidget(wPreeditDraw));
380 im_context.reset();
381 if (GTK_WIDGET_CLASS(parentClass)->unrealize)
382 GTK_WIDGET_CLASS(parentClass)->unrealize(widget);
384 Finalise();
385 } catch (...) {
386 errorStatus = Status::Failure;
390 void ScintillaGTK::UnRealize(GtkWidget *widget) {
391 ScintillaGTK *sciThis = FromWidget(widget);
392 sciThis->UnRealizeThis(widget);
395 void ScintillaGTK::MapThis() {
396 try {
397 //Platform::DebugPrintf("ScintillaGTK::map this\n");
398 gtk_widget_set_mapped(PWidget(wMain), TRUE);
399 MapWidget(PWidget(wText));
400 MapWidget(PWidget(scrollbarh));
401 MapWidget(PWidget(scrollbarv));
402 wMain.SetCursor(Window::Cursor::arrow);
403 scrollbarv.SetCursor(Window::Cursor::arrow);
404 scrollbarh.SetCursor(Window::Cursor::arrow);
405 SetClientRectangle();
406 ChangeSize();
407 gdk_window_show(PWindow(wMain));
408 } catch (...) {
409 errorStatus = Status::Failure;
413 void ScintillaGTK::Map(GtkWidget *widget) {
414 ScintillaGTK *sciThis = FromWidget(widget);
415 sciThis->MapThis();
418 void ScintillaGTK::UnMapThis() {
419 try {
420 //Platform::DebugPrintf("ScintillaGTK::unmap this\n");
421 gtk_widget_set_mapped(PWidget(wMain), FALSE);
422 DropGraphics();
423 gdk_window_hide(PWindow(wMain));
424 gtk_widget_unmap(PWidget(wText));
425 if (PWidget(scrollbarh))
426 gtk_widget_unmap(PWidget(scrollbarh));
427 if (PWidget(scrollbarv))
428 gtk_widget_unmap(PWidget(scrollbarv));
429 } catch (...) {
430 errorStatus = Status::Failure;
434 void ScintillaGTK::UnMap(GtkWidget *widget) {
435 ScintillaGTK *sciThis = FromWidget(widget);
436 sciThis->UnMapThis();
439 void ScintillaGTK::ForAll(GtkCallback callback, gpointer callback_data) {
440 try {
441 (*callback)(PWidget(wText), callback_data);
442 if (PWidget(scrollbarv))
443 (*callback)(PWidget(scrollbarv), callback_data);
444 if (PWidget(scrollbarh))
445 (*callback)(PWidget(scrollbarh), callback_data);
446 } catch (...) {
447 errorStatus = Status::Failure;
451 void ScintillaGTK::MainForAll(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) {
452 ScintillaGTK *sciThis = FromWidget(GTK_WIDGET(container));
454 if (callback && include_internals) {
455 sciThis->ForAll(callback, callback_data);
459 namespace {
461 class PreEditString {
462 public:
463 gchar *str;
464 gint cursor_pos;
465 PangoAttrList *attrs;
466 gboolean validUTF8;
467 glong uniStrLen;
468 gunichar *uniStr;
469 GUnicodeScript pscript;
471 explicit PreEditString(GtkIMContext *im_context) noexcept {
472 gtk_im_context_get_preedit_string(im_context, &str, &attrs, &cursor_pos);
473 validUTF8 = g_utf8_validate(str, strlen(str), nullptr);
474 uniStr = g_utf8_to_ucs4_fast(str, static_cast<glong>(strlen(str)), &uniStrLen);
475 pscript = g_unichar_get_script(uniStr[0]);
477 // Deleted so PreEditString objects can not be copied.
478 PreEditString(const PreEditString&) = delete;
479 PreEditString(PreEditString&&) = delete;
480 PreEditString&operator=(const PreEditString&) = delete;
481 PreEditString&operator=(PreEditString&&) = delete;
482 ~PreEditString() {
483 g_free(str);
484 g_free(uniStr);
485 pango_attr_list_unref(attrs);
491 gint ScintillaGTK::FocusInThis(GtkWidget *) {
492 try {
493 SetFocusState(true);
495 if (im_context) {
496 gtk_im_context_focus_in(im_context.get());
497 PreEditString pes(im_context.get());
498 if (PWidget(wPreedit)) {
499 if (!preeditInitialized) {
500 GtkWidget *top = gtk_widget_get_toplevel(PWidget(wMain));
501 gtk_window_set_transient_for(GTK_WINDOW(PWidget(wPreedit)), GTK_WINDOW(top));
502 preeditInitialized = true;
505 if (strlen(pes.str) > 0) {
506 gtk_widget_show(PWidget(wPreedit));
507 } else {
508 gtk_widget_hide(PWidget(wPreedit));
512 } catch (...) {
513 errorStatus = Status::Failure;
515 return FALSE;
518 gint ScintillaGTK::FocusIn(GtkWidget *widget, GdkEventFocus * /*event*/) {
519 ScintillaGTK *sciThis = FromWidget(widget);
520 return sciThis->FocusInThis(widget);
523 gint ScintillaGTK::FocusOutThis(GtkWidget *) {
524 try {
525 SetFocusState(false);
527 if (PWidget(wPreedit))
528 gtk_widget_hide(PWidget(wPreedit));
529 if (im_context)
530 gtk_im_context_focus_out(im_context.get());
532 } catch (...) {
533 errorStatus = Status::Failure;
535 return FALSE;
538 gint ScintillaGTK::FocusOut(GtkWidget *widget, GdkEventFocus * /*event*/) {
539 ScintillaGTK *sciThis = FromWidget(widget);
540 return sciThis->FocusOutThis(widget);
543 void ScintillaGTK::SizeRequest(GtkWidget *widget, GtkRequisition *requisition) {
544 const ScintillaGTK *sciThis = FromWidget(widget);
545 requisition->width = 1;
546 requisition->height = 1;
547 GtkRequisition child_requisition;
548 #if GTK_CHECK_VERSION(3,0,0)
549 gtk_widget_get_preferred_size(PWidget(sciThis->scrollbarh), nullptr, &child_requisition);
550 gtk_widget_get_preferred_size(PWidget(sciThis->scrollbarv), nullptr, &child_requisition);
551 #else
552 gtk_widget_size_request(PWidget(sciThis->scrollbarh), &child_requisition);
553 gtk_widget_size_request(PWidget(sciThis->scrollbarv), &child_requisition);
554 #endif
557 #if GTK_CHECK_VERSION(3,0,0)
559 void ScintillaGTK::GetPreferredWidth(GtkWidget *widget, gint *minimalWidth, gint *naturalWidth) {
560 GtkRequisition requisition;
561 SizeRequest(widget, &requisition);
562 *minimalWidth = *naturalWidth = requisition.width;
565 void ScintillaGTK::GetPreferredHeight(GtkWidget *widget, gint *minimalHeight, gint *naturalHeight) {
566 GtkRequisition requisition;
567 SizeRequest(widget, &requisition);
568 *minimalHeight = *naturalHeight = requisition.height;
571 #endif
573 void ScintillaGTK::SizeAllocate(GtkWidget *widget, GtkAllocation *allocation) {
574 ScintillaGTK *sciThis = FromWidget(widget);
575 try {
576 gtk_widget_set_allocation(widget, allocation);
577 if (IS_WIDGET_REALIZED(widget))
578 gdk_window_move_resize(WindowFromWidget(widget),
579 allocation->x,
580 allocation->y,
581 allocation->width,
582 allocation->height);
584 sciThis->Resize(allocation->width, allocation->height);
586 } catch (...) {
587 sciThis->errorStatus = Status::Failure;
591 void ScintillaGTK::Init() {
592 parentClass = static_cast<GtkWidgetClass *>(
593 g_type_class_ref(gtk_container_get_type()));
595 gint maskSmooth = 0;
596 #if defined(GDK_WINDOWING_WAYLAND)
597 GdkDisplay *pdisplay = gdk_display_get_default();
598 if (GDK_IS_WAYLAND_DISPLAY(pdisplay)) {
599 // On Wayland, touch pads only produce smooth scroll events
600 maskSmooth = GDK_SMOOTH_SCROLL_MASK;
602 #endif
604 gtk_widget_set_can_focus(PWidget(wMain), TRUE);
605 gtk_widget_set_sensitive(PWidget(wMain), TRUE);
606 gtk_widget_set_events(PWidget(wMain),
607 GDK_EXPOSURE_MASK
608 | GDK_SCROLL_MASK
609 | maskSmooth
610 | GDK_STRUCTURE_MASK
611 | GDK_KEY_PRESS_MASK
612 | GDK_KEY_RELEASE_MASK
613 | GDK_FOCUS_CHANGE_MASK
614 | GDK_LEAVE_NOTIFY_MASK
615 | GDK_BUTTON_PRESS_MASK
616 | GDK_BUTTON_RELEASE_MASK
617 | GDK_POINTER_MOTION_MASK
618 | GDK_POINTER_MOTION_HINT_MASK);
620 wText = gtk_drawing_area_new();
621 gtk_widget_set_parent(PWidget(wText), PWidget(wMain));
622 GtkWidget *widtxt = PWidget(wText); // No code inside the G_OBJECT macro
623 gtk_widget_show(widtxt);
624 #if GTK_CHECK_VERSION(3,0,0)
625 g_signal_connect(G_OBJECT(widtxt), "draw",
626 G_CALLBACK(ScintillaGTK::DrawText), this);
627 #else
628 g_signal_connect(G_OBJECT(widtxt), "expose_event",
629 G_CALLBACK(ScintillaGTK::ExposeText), this);
630 #endif
631 #if GTK_CHECK_VERSION(3,0,0)
632 // we need a runtime check because we don't want double buffering when
633 // running on >= 3.9.2
634 if (gtk_check_version(3, 9, 2) != nullptr /* on < 3.9.2 */)
635 #endif
637 #if !GTK_CHECK_VERSION(3,14,0)
638 // Avoid background drawing flash/missing redraws
639 gtk_widget_set_double_buffered(widtxt, FALSE);
640 #endif
642 gtk_widget_set_events(widtxt, GDK_EXPOSURE_MASK);
643 gtk_widget_set_size_request(widtxt, 100, 100);
644 adjustmentv = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 201.0, 1.0, 20.0, 20.0));
645 #if GTK_CHECK_VERSION(3,0,0)
646 scrollbarv = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(adjustmentv));
647 #else
648 scrollbarv = gtk_vscrollbar_new(GTK_ADJUSTMENT(adjustmentv));
649 #endif
650 gtk_widget_set_can_focus(PWidget(scrollbarv), FALSE);
651 g_signal_connect(G_OBJECT(adjustmentv), "value_changed",
652 G_CALLBACK(ScrollSignal), this);
653 gtk_widget_set_parent(PWidget(scrollbarv), PWidget(wMain));
654 gtk_widget_show(PWidget(scrollbarv));
656 adjustmenth = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 101.0, 1.0, 20.0, 20.0));
657 #if GTK_CHECK_VERSION(3,0,0)
658 scrollbarh = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(adjustmenth));
659 #else
660 scrollbarh = gtk_hscrollbar_new(GTK_ADJUSTMENT(adjustmenth));
661 #endif
662 gtk_widget_set_can_focus(PWidget(scrollbarh), FALSE);
663 g_signal_connect(G_OBJECT(adjustmenth), "value_changed",
664 G_CALLBACK(ScrollHSignal), this);
665 gtk_widget_set_parent(PWidget(scrollbarh), PWidget(wMain));
666 gtk_widget_show(PWidget(scrollbarh));
668 gtk_widget_grab_focus(PWidget(wMain));
670 gtk_drag_dest_set(GTK_WIDGET(PWidget(wMain)),
671 GTK_DEST_DEFAULT_ALL, clipboardPasteTargets, nClipboardPasteTargets,
672 actionCopyOrMove);
674 /* create pre-edit window */
675 wPreedit = gtk_window_new(GTK_WINDOW_POPUP);
676 gtk_window_set_type_hint(GTK_WINDOW(PWidget(wPreedit)), GDK_WINDOW_TYPE_HINT_POPUP_MENU);
677 wPreeditDraw = gtk_drawing_area_new();
678 GtkWidget *predrw = PWidget(wPreeditDraw); // No code inside the G_OBJECT macro
679 #if GTK_CHECK_VERSION(3,0,0)
680 g_signal_connect(G_OBJECT(predrw), "draw",
681 G_CALLBACK(DrawPreedit), this);
682 #else
683 g_signal_connect(G_OBJECT(predrw), "expose_event",
684 G_CALLBACK(ExposePreedit), this);
685 #endif
686 gtk_container_add(GTK_CONTAINER(PWidget(wPreedit)), predrw);
687 gtk_widget_show(predrw);
689 settings = gtk_settings_get_default();
691 // Set caret period based on GTK settings
692 gboolean blinkOn = false;
693 SettingGet(settings, "gtk-cursor-blink", &blinkOn);
694 if (blinkOn) {
695 gint value = 500;
696 if (SettingGet(settings, "gtk-cursor-blink-time", &value)) {
697 caret.period = static_cast<int>(value / 1.75);
699 } else {
700 caret.period = 0;
703 for (size_t tr = static_cast<size_t>(TickReason::caret); tr <= static_cast<size_t>(TickReason::dwell); tr++) {
704 timers[tr].reason = static_cast<TickReason>(tr);
705 timers[tr].scintilla = this;
707 vs.indicators[SC_INDICATOR_UNKNOWN] = Indicator(IndicatorStyle::Hidden, ColourRGBA(0, 0, 0xff));
708 vs.indicators[SC_INDICATOR_INPUT] = Indicator(IndicatorStyle::Dots, ColourRGBA(0, 0, 0xff));
709 vs.indicators[SC_INDICATOR_CONVERTED] = Indicator(IndicatorStyle::CompositionThick, ColourRGBA(0, 0, 0xff));
710 vs.indicators[SC_INDICATOR_TARGET] = Indicator(IndicatorStyle::StraightBox, ColourRGBA(0, 0, 0xff));
712 fontOptionsPrevious = FontOptions(PWidget(wText));
715 void ScintillaGTK::Finalise() {
716 for (size_t tr = static_cast<size_t>(TickReason::caret); tr <= static_cast<size_t>(TickReason::dwell); tr++) {
717 FineTickerCancel(static_cast<TickReason>(tr));
719 if (accessible) {
720 gtk_accessible_set_widget(GTK_ACCESSIBLE(accessible), nullptr);
721 g_object_unref(accessible);
722 accessible = nullptr;
725 ScintillaBase::Finalise();
728 bool ScintillaGTK::AbandonPaint() {
729 if ((paintState == PaintState::painting) && !paintingAllText) {
730 repaintFullWindow = true;
732 return false;
735 void ScintillaGTK::DisplayCursor(Window::Cursor c) {
736 if (cursorMode == CursorShape::Normal)
737 wText.SetCursor(c);
738 else
739 wText.SetCursor(static_cast<Window::Cursor>(cursorMode));
742 bool ScintillaGTK::DragThreshold(Point ptStart, Point ptNow) {
743 return gtk_drag_check_threshold(GTK_WIDGET(PWidget(wMain)),
744 static_cast<gint>(ptStart.x), static_cast<gint>(ptStart.y),
745 static_cast<gint>(ptNow.x), static_cast<gint>(ptNow.y));
748 void ScintillaGTK::StartDrag() {
749 PLATFORM_ASSERT(evbtn);
750 dragWasDropped = false;
751 inDragDrop = DragDrop::dragging;
752 GtkTargetList *tl = gtk_target_list_new(clipboardCopyTargets, nClipboardCopyTargets);
753 #if GTK_CHECK_VERSION(3,10,0)
754 gtk_drag_begin_with_coordinates(GTK_WIDGET(PWidget(wMain)),
756 actionCopyOrMove,
757 buttonMouse,
758 evbtn.get(),
759 -1, -1);
760 #else
761 gtk_drag_begin(GTK_WIDGET(PWidget(wMain)),
763 actionCopyOrMove,
764 buttonMouse,
765 evbtn.get());
766 #endif
769 namespace Scintilla::Internal {
771 std::string ConvertText(const char *s, size_t len, const char *charSetDest,
772 const char *charSetSource, bool transliterations, bool silent) {
773 // s is not const because of different versions of iconv disagreeing about const
774 std::string destForm;
775 Converter conv(charSetDest, charSetSource, transliterations);
776 if (conv) {
777 gsize outLeft = len*3+1;
778 destForm = std::string(outLeft, '\0');
779 // g_iconv does not actually write to its input argument so safe to cast away const
780 char *pin = const_cast<char *>(s);
781 gsize inLeft = len;
782 char *putf = &destForm[0];
783 char *pout = putf;
784 const gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
785 if (conversions == sizeFailure) {
786 if (!silent) {
787 if (len == 1)
788 fprintf(stderr, "iconv %s->%s failed for %0x '%s'\n",
789 charSetSource, charSetDest, static_cast<unsigned char>(*s), s);
790 else
791 fprintf(stderr, "iconv %s->%s failed for %s\n",
792 charSetSource, charSetDest, s);
794 destForm = std::string();
795 } else {
796 destForm.resize(pout - putf);
798 } else {
799 fprintf(stderr, "Can not iconv %s %s\n", charSetDest, charSetSource);
801 return destForm;
805 // Returns the target converted to UTF8.
806 // Return the length in bytes.
807 Sci::Position ScintillaGTK::TargetAsUTF8(char *text) const {
808 const Sci::Position targetLength = targetRange.Length();
809 if (IsUnicodeMode()) {
810 if (text) {
811 pdoc->GetCharRange(text, targetRange.start.Position(), targetLength);
813 } else {
814 // Need to convert
815 const char *charSetBuffer = CharacterSetID();
816 if (*charSetBuffer) {
817 std::string s = RangeText(targetRange.start.Position(), targetRange.end.Position());
818 std::string tmputf = ConvertText(&s[0], targetLength, "UTF-8", charSetBuffer, false);
819 if (text) {
820 memcpy(text, tmputf.c_str(), tmputf.length());
822 return tmputf.length();
823 } else {
824 if (text) {
825 pdoc->GetCharRange(text, targetRange.start.Position(), targetLength);
829 return targetLength;
832 // Translates a nul terminated UTF8 string into the document encoding.
833 // Return the length of the result in bytes.
834 Sci::Position ScintillaGTK::EncodedFromUTF8(const char *utf8, char *encoded) const {
835 const Sci::Position inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8);
836 if (IsUnicodeMode()) {
837 if (encoded) {
838 memcpy(encoded, utf8, inputLength);
840 return inputLength;
841 } else {
842 // Need to convert
843 const char *charSetBuffer = CharacterSetID();
844 if (*charSetBuffer) {
845 std::string tmpEncoded = ConvertText(utf8, inputLength, charSetBuffer, "UTF-8", true);
846 if (encoded) {
847 memcpy(encoded, tmpEncoded.c_str(), tmpEncoded.length());
849 return tmpEncoded.length();
850 } else {
851 if (encoded) {
852 memcpy(encoded, utf8, inputLength);
854 return inputLength;
857 // Fail
858 return 0;
861 bool ScintillaGTK::ValidCodePage(int codePage) const {
862 return codePage == 0
863 || codePage == SC_CP_UTF8
864 || codePage == 932
865 || codePage == 936
866 || codePage == 949
867 || codePage == 950
868 || codePage == 1361;
871 std::string ScintillaGTK::UTF8FromEncoded(std::string_view encoded) const {
872 if (IsUnicodeMode()) {
873 return std::string(encoded);
874 } else {
875 const char *charSetBuffer = CharacterSetID();
876 return ConvertText(encoded.data(), encoded.length(), "UTF-8", charSetBuffer, true);
880 std::string ScintillaGTK::EncodedFromUTF8(std::string_view utf8) const {
881 if (IsUnicodeMode()) {
882 return std::string(utf8);
883 } else {
884 const char *charSetBuffer = CharacterSetID();
885 return ConvertText(utf8.data(), utf8.length(), charSetBuffer, "UTF-8", true);
889 sptr_t ScintillaGTK::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) {
890 try {
891 switch (iMessage) {
893 case Message::GrabFocus:
894 gtk_widget_grab_focus(PWidget(wMain));
895 break;
897 case Message::GetDirectFunction:
898 return reinterpret_cast<sptr_t>(DirectFunction);
900 case Message::GetDirectStatusFunction:
901 return reinterpret_cast<sptr_t>(DirectStatusFunction);
903 case Message::GetDirectPointer:
904 return reinterpret_cast<sptr_t>(this);
906 case Message::TargetAsUTF8:
907 return TargetAsUTF8(CharPtrFromSPtr(lParam));
909 case Message::EncodedFromUTF8:
910 return EncodedFromUTF8(ConstCharPtrFromUPtr(wParam),
911 CharPtrFromSPtr(lParam));
913 case Message::SetRectangularSelectionModifier:
914 rectangularSelectionModifier = static_cast<int>(wParam);
915 break;
917 case Message::GetRectangularSelectionModifier:
918 return rectangularSelectionModifier;
920 case Message::SetReadOnly: {
921 const sptr_t ret = ScintillaBase::WndProc(iMessage, wParam, lParam);
922 if (accessible) {
923 ScintillaGTKAccessible *sciAccessible = ScintillaGTKAccessible::FromAccessible(accessible);
924 if (sciAccessible) {
925 sciAccessible->NotifyReadOnly();
928 return ret;
931 case Message::GetAccessibility:
932 return accessibilityEnabled;
934 case Message::SetAccessibility:
935 accessibilityEnabled = static_cast<int>(wParam);
936 if (accessible) {
937 ScintillaGTKAccessible *sciAccessible = ScintillaGTKAccessible::FromAccessible(accessible);
938 if (sciAccessible) {
939 sciAccessible->SetAccessibility(accessibilityEnabled);
942 break;
944 default:
945 return ScintillaBase::WndProc(iMessage, wParam, lParam);
947 } catch (std::bad_alloc &) {
948 errorStatus = Status::BadAlloc;
949 } catch (...) {
950 errorStatus = Status::Failure;
952 return 0;
955 sptr_t ScintillaGTK::DefWndProc(Message, uptr_t, sptr_t) {
956 return 0;
959 bool ScintillaGTK::FineTickerRunning(TickReason reason) {
960 return timers[static_cast<size_t>(reason)].timer != 0;
963 void ScintillaGTK::FineTickerStart(TickReason reason, int millis, int /* tolerance */) {
964 FineTickerCancel(reason);
965 const size_t reasonIndex = static_cast<size_t>(reason);
966 timers[reasonIndex].timer = gdk_threads_add_timeout(millis, TimeOut, &timers[reasonIndex]);
969 void ScintillaGTK::FineTickerCancel(TickReason reason) {
970 const size_t reasonIndex = static_cast<size_t>(reason);
971 if (timers[reasonIndex].timer) {
972 g_source_remove(timers[reasonIndex].timer);
973 timers[reasonIndex].timer = 0;
977 bool ScintillaGTK::SetIdle(bool on) {
978 if (on) {
979 // Start idler, if it's not running.
980 if (!idler.state) {
981 idler.state = true;
982 idler.idlerID = GUINT_TO_POINTER(
983 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE, IdleCallback, this, nullptr));
985 } else {
986 // Stop idler, if it's running
987 if (idler.state) {
988 idler.state = false;
989 g_source_remove(GPOINTER_TO_UINT(idler.idlerID));
992 return true;
995 void ScintillaGTK::SetMouseCapture(bool on) {
996 if (mouseDownCaptures) {
997 if (on) {
998 gtk_grab_add(GTK_WIDGET(PWidget(wMain)));
999 } else {
1000 gtk_grab_remove(GTK_WIDGET(PWidget(wMain)));
1003 capturedMouse = on;
1006 bool ScintillaGTK::HaveMouseCapture() {
1007 return capturedMouse;
1010 #if GTK_CHECK_VERSION(3,0,0)
1012 namespace {
1014 // Is crcTest completely in crcContainer?
1015 bool CRectContains(const cairo_rectangle_t &crcContainer, const cairo_rectangle_t &crcTest) {
1016 return
1017 (crcTest.x >= crcContainer.x) && ((crcTest.x + crcTest.width) <= (crcContainer.x + crcContainer.width)) &&
1018 (crcTest.y >= crcContainer.y) && ((crcTest.y + crcTest.height) <= (crcContainer.y + crcContainer.height));
1021 // Is crcTest completely in crcListContainer?
1022 // May incorrectly return false if complex shape
1023 bool CRectListContains(const cairo_rectangle_list_t *crcListContainer, const cairo_rectangle_t &crcTest) {
1024 for (int r=0; r<crcListContainer->num_rectangles; r++) {
1025 if (CRectContains(crcListContainer->rectangles[r], crcTest))
1026 return true;
1028 return false;
1033 #endif
1035 bool ScintillaGTK::PaintContains(PRectangle rc) {
1036 // This allows optimization when a rectangle is completely in the update region.
1037 // It is OK to return false when too difficult to determine as that just performs extra drawing
1038 bool contains = true;
1039 if (paintState == PaintState::painting) {
1040 if (!rcPaint.Contains(rc)) {
1041 contains = false;
1042 } else if (rgnUpdate) {
1043 #if GTK_CHECK_VERSION(3,0,0)
1044 cairo_rectangle_t grc = {rc.left, rc.top,
1045 rc.right - rc.left, rc.bottom - rc.top
1047 contains = CRectListContains(rgnUpdate, grc);
1048 #else
1049 GdkRectangle grc = {static_cast<gint>(rc.left), static_cast<gint>(rc.top),
1050 static_cast<gint>(rc.right - rc.left), static_cast<gint>(rc.bottom - rc.top)
1052 if (gdk_region_rect_in(rgnUpdate, &grc) != GDK_OVERLAP_RECTANGLE_IN) {
1053 contains = false;
1055 #endif
1058 return contains;
1061 // Redraw all of text area. This paint will not be abandoned.
1062 void ScintillaGTK::FullPaint() {
1063 wText.InvalidateAll();
1066 void ScintillaGTK::SetClientRectangle() {
1067 rectangleClient = wMain.GetClientPosition();
1070 PRectangle ScintillaGTK::GetClientRectangle() const {
1071 PRectangle rc = rectangleClient;
1072 if (verticalScrollBarVisible)
1073 rc.right -= verticalScrollBarWidth;
1074 if (horizontalScrollBarVisible && !Wrapping())
1075 rc.bottom -= horizontalScrollBarHeight;
1076 // Move to origin
1077 rc.right -= rc.left;
1078 rc.bottom -= rc.top;
1079 if (rc.bottom < 0)
1080 rc.bottom = 0;
1081 if (rc.right < 0)
1082 rc.right = 0;
1083 rc.left = 0;
1084 rc.top = 0;
1085 return rc;
1088 void ScintillaGTK::ScrollText(Sci::Line linesToMove) {
1089 NotifyUpdateUI();
1091 #if GTK_CHECK_VERSION(3,22,0)
1092 Redraw();
1093 #else
1094 GtkWidget *wi = PWidget(wText);
1095 if (IS_WIDGET_REALIZED(wi)) {
1096 const Sci::Line diff = vs.lineHeight * -linesToMove;
1097 gdk_window_scroll(WindowFromWidget(wi), 0, static_cast<gint>(-diff));
1098 gdk_window_process_updates(WindowFromWidget(wi), FALSE);
1100 #endif
1103 void ScintillaGTK::SetVerticalScrollPos() {
1104 DwellEnd(true);
1105 gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmentv), static_cast<gdouble>(topLine));
1108 void ScintillaGTK::SetHorizontalScrollPos() {
1109 DwellEnd(true);
1110 gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmenth), xOffset);
1113 bool ScintillaGTK::ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) {
1114 bool modified = false;
1115 const int pageScroll = static_cast<int>(LinesToScroll());
1117 if (gtk_adjustment_get_upper(adjustmentv) != (nMax + 1) ||
1118 gtk_adjustment_get_page_size(adjustmentv) != nPage ||
1119 gtk_adjustment_get_page_increment(adjustmentv) != pageScroll) {
1120 gtk_adjustment_set_upper(adjustmentv, nMax + 1.0);
1121 gtk_adjustment_set_page_size(adjustmentv, static_cast<gdouble>(nPage));
1122 gtk_adjustment_set_page_increment(adjustmentv, pageScroll);
1123 #if !GTK_CHECK_VERSION(3,18,0)
1124 gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmentv));
1125 #endif
1126 gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmentv), static_cast<gdouble>(topLine));
1127 modified = true;
1130 const PRectangle rcText = GetTextRectangle();
1131 int horizEndPreferred = scrollWidth;
1132 if (horizEndPreferred < 0)
1133 horizEndPreferred = 0;
1134 const unsigned int pageWidth = static_cast<unsigned int>(rcText.Width());
1135 const unsigned int pageIncrement = pageWidth / 3;
1136 const unsigned int charWidth = static_cast<unsigned int>(vs.styles[STYLE_DEFAULT].aveCharWidth);
1137 if (gtk_adjustment_get_upper(adjustmenth) != horizEndPreferred ||
1138 gtk_adjustment_get_page_size(adjustmenth) != pageWidth ||
1139 gtk_adjustment_get_page_increment(adjustmenth) != pageIncrement ||
1140 gtk_adjustment_get_step_increment(adjustmenth) != charWidth) {
1141 gtk_adjustment_set_upper(adjustmenth, horizEndPreferred);
1142 gtk_adjustment_set_page_size(adjustmenth, pageWidth);
1143 gtk_adjustment_set_page_increment(adjustmenth, pageIncrement);
1144 gtk_adjustment_set_step_increment(adjustmenth, charWidth);
1145 #if !GTK_CHECK_VERSION(3,18,0)
1146 gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmenth));
1147 #endif
1148 gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmenth), xOffset);
1149 modified = true;
1151 if (modified && (paintState == PaintState::painting)) {
1152 repaintFullWindow = true;
1155 return modified;
1158 void ScintillaGTK::ReconfigureScrollBars() {
1159 const PRectangle rc = wMain.GetClientPosition();
1160 Resize(static_cast<int>(rc.Width()), static_cast<int>(rc.Height()));
1163 void ScintillaGTK::SetScrollBars() {
1164 if (scrollBarIdleID) {
1165 // Only allow one scroll bar change to be queued
1166 return;
1168 constexpr gint priorityScrollBar = GDK_PRIORITY_REDRAW + 5;
1169 // On GTK, unlike other platforms, modifying scrollbars inside some events including
1170 // resizes causes problems. Deferring the modification to a lower priority (125) idle
1171 // event avoids the problems. This code did not always work when the priority was
1172 // higher than GTK's resize (GTK_PRIORITY_RESIZE=110) or redraw
1173 // (GDK_PRIORITY_REDRAW=120) idle tasks.
1174 scrollBarIdleID = gdk_threads_add_idle_full(priorityScrollBar,
1175 [](gpointer pSci) -> gboolean {
1176 ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(pSci);
1177 sciThis->ChangeScrollBars();
1178 sciThis->scrollBarIdleID = 0;
1179 return FALSE;
1181 this, nullptr);
1184 void ScintillaGTK::NotifyChange() {
1185 g_signal_emit(G_OBJECT(sci), scintilla_signals[COMMAND_SIGNAL], 0,
1186 Platform::LongFromTwoShorts(GetCtrlID(), SCEN_CHANGE), PWidget(wMain));
1189 void ScintillaGTK::NotifyFocus(bool focus) {
1190 if (commandEvents)
1191 g_signal_emit(G_OBJECT(sci), scintilla_signals[COMMAND_SIGNAL], 0,
1192 Platform::LongFromTwoShorts
1193 (GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS), PWidget(wMain));
1194 Editor::NotifyFocus(focus);
1197 void ScintillaGTK::NotifyParent(NotificationData scn) {
1198 scn.nmhdr.hwndFrom = PWidget(wMain);
1199 scn.nmhdr.idFrom = GetCtrlID();
1200 g_signal_emit(G_OBJECT(sci), scintilla_signals[NOTIFY_SIGNAL], 0,
1201 GetCtrlID(), &scn);
1204 void ScintillaGTK::NotifyKey(Keys key, KeyMod modifiers) {
1205 NotificationData scn = {};
1206 scn.nmhdr.code = Notification::Key;
1207 scn.ch = static_cast<int>(key);
1208 scn.modifiers = modifiers;
1210 NotifyParent(scn);
1213 void ScintillaGTK::NotifyURIDropped(const char *list) {
1214 NotificationData scn = {};
1215 scn.nmhdr.code = Notification::URIDropped;
1216 scn.text = list;
1218 NotifyParent(scn);
1221 const char *CharacterSetID(CharacterSet characterSet);
1223 const char *ScintillaGTK::CharacterSetID() const {
1224 return ::CharacterSetID(vs.styles[STYLE_DEFAULT].characterSet);
1227 namespace {
1229 class CaseFolderDBCS : public CaseFolderTable {
1230 const char *charSet;
1231 public:
1232 explicit CaseFolderDBCS(const char *charSet_) noexcept : charSet(charSet_) {
1234 size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) override {
1235 if ((lenMixed == 1) && (sizeFolded > 0)) {
1236 folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
1237 return 1;
1238 } else if (*charSet) {
1239 std::string sUTF8 = ConvertText(mixed, lenMixed,
1240 "UTF-8", charSet, false);
1241 if (!sUTF8.empty()) {
1242 UniqueStr mapped(g_utf8_casefold(sUTF8.c_str(), sUTF8.length()));
1243 size_t lenMapped = strlen(mapped.get());
1244 if (lenMapped < sizeFolded) {
1245 memcpy(folded, mapped.get(), lenMapped);
1246 } else {
1247 folded[0] = '\0';
1248 lenMapped = 1;
1250 return lenMapped;
1253 // Something failed so return a single NUL byte
1254 folded[0] = '\0';
1255 return 1;
1261 std::unique_ptr<CaseFolder> ScintillaGTK::CaseFolderForEncoding() {
1262 if (pdoc->dbcsCodePage == SC_CP_UTF8) {
1263 return std::make_unique<CaseFolderUnicode>();
1264 } else {
1265 const char *charSetBuffer = CharacterSetID();
1266 if (charSetBuffer) {
1267 if (pdoc->dbcsCodePage == 0) {
1268 std::unique_ptr<CaseFolderTable> pcf = std::make_unique<CaseFolderTable>();
1269 // Only for single byte encodings
1270 for (int i=0x80; i<0x100; i++) {
1271 char sCharacter[2] = "A";
1272 sCharacter[0] = i;
1273 // Silent as some bytes have no assigned character
1274 std::string sUTF8 = ConvertText(sCharacter, 1,
1275 "UTF-8", charSetBuffer, false, true);
1276 if (!sUTF8.empty()) {
1277 UniqueStr mapped(g_utf8_casefold(sUTF8.c_str(), sUTF8.length()));
1278 if (mapped) {
1279 std::string mappedBack = ConvertText(mapped.get(), strlen(mapped.get()),
1280 charSetBuffer, "UTF-8", false, true);
1281 if ((mappedBack.length() == 1) && (mappedBack[0] != sCharacter[0])) {
1282 pcf->SetTranslation(sCharacter[0], mappedBack[0]);
1287 return pcf;
1288 } else {
1289 return std::make_unique<CaseFolderDBCS>(charSetBuffer);
1292 return nullptr;
1296 namespace {
1298 struct CaseMapper {
1299 UniqueStr mapped;
1300 CaseMapper(const std::string &sUTF8, bool toUpperCase) noexcept {
1301 if (toUpperCase) {
1302 mapped.reset(g_utf8_strup(sUTF8.c_str(), sUTF8.length()));
1303 } else {
1304 mapped.reset(g_utf8_strdown(sUTF8.c_str(), sUTF8.length()));
1311 std::string ScintillaGTK::CaseMapString(const std::string &s, CaseMapping caseMapping) {
1312 if (s.empty() || (caseMapping == CaseMapping::same))
1313 return s;
1315 if (IsUnicodeMode()) {
1316 std::string retMapped(s.length() * maxExpansionCaseConversion, 0);
1317 const size_t lenMapped = CaseConvertString(&retMapped[0], retMapped.length(), s.c_str(), s.length(),
1318 (caseMapping == CaseMapping::upper) ? CaseConversion::upper : CaseConversion::lower);
1319 retMapped.resize(lenMapped);
1320 return retMapped;
1323 const char *charSetBuffer = CharacterSetID();
1325 if (!*charSetBuffer) {
1326 CaseMapper mapper(s, caseMapping == CaseMapping::upper);
1327 return std::string(mapper.mapped.get());
1328 } else {
1329 // Change text to UTF-8
1330 std::string sUTF8 = ConvertText(s.c_str(), s.length(),
1331 "UTF-8", charSetBuffer, false);
1332 CaseMapper mapper(sUTF8, caseMapping == CaseMapping::upper);
1333 return ConvertText(mapper.mapped.get(), strlen(mapper.mapped.get()), charSetBuffer, "UTF-8", false);
1337 int ScintillaGTK::KeyDefault(Keys key, KeyMod modifiers) {
1338 // Pass up to container in case it is an accelerator
1339 NotifyKey(key, modifiers);
1340 return 0;
1343 void ScintillaGTK::CopyToClipboard(const SelectionText &selectedText) {
1344 SelectionText *clipText = new SelectionText();
1345 clipText->Copy(selectedText);
1346 StoreOnClipboard(clipText);
1349 void ScintillaGTK::Copy() {
1350 if (!sel.Empty()) {
1351 SelectionText *clipText = new SelectionText();
1352 CopySelectionRange(clipText);
1353 StoreOnClipboard(clipText);
1354 #if PLAT_GTK_WIN32
1355 if (sel.IsRectangular()) {
1356 ::OpenClipboard(NULL);
1357 ::SetClipboardData(cfColumnSelect, 0);
1358 ::CloseClipboard();
1360 #endif
1364 namespace {
1366 // Helper class for the asynchronous paste not to risk calling in a destroyed ScintillaGTK
1368 class SelectionReceiver : GObjectWatcher {
1369 ScintillaGTK *sci;
1371 void Destroyed() noexcept override {
1372 sci = nullptr;
1375 public:
1376 SelectionReceiver(ScintillaGTK *sci_) :
1377 GObjectWatcher(G_OBJECT(sci_->MainObject())),
1378 sci(sci_) {
1381 static void ClipboardReceived(GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data) noexcept {
1382 SelectionReceiver *self = static_cast<SelectionReceiver *>(data);
1383 if (self->sci) {
1384 self->sci->ReceivedClipboard(clipboard, selection_data);
1386 delete self;
1392 void ScintillaGTK::RequestSelection(GdkAtom atomSelection) {
1393 atomSought = atomUTF8;
1394 GtkClipboard *clipBoard =
1395 gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), atomSelection);
1396 if (clipBoard) {
1397 gtk_clipboard_request_contents(clipBoard, atomSought,
1398 SelectionReceiver::ClipboardReceived,
1399 new SelectionReceiver(this));
1403 void ScintillaGTK::Paste() {
1404 RequestSelection(GDK_SELECTION_CLIPBOARD);
1407 void ScintillaGTK::CreateCallTipWindow(PRectangle rc) {
1408 if (!ct.wCallTip.Created()) {
1409 ct.wCallTip = gtk_window_new(GTK_WINDOW_POPUP);
1410 gtk_window_set_type_hint(GTK_WINDOW(PWidget(ct.wCallTip)), GDK_WINDOW_TYPE_HINT_TOOLTIP);
1411 ct.wDraw = gtk_drawing_area_new();
1412 GtkWidget *widcdrw = PWidget(ct.wDraw); // // No code inside the G_OBJECT macro
1413 gtk_container_add(GTK_CONTAINER(PWidget(ct.wCallTip)), widcdrw);
1414 #if GTK_CHECK_VERSION(3,0,0)
1415 g_signal_connect(G_OBJECT(widcdrw), "draw",
1416 G_CALLBACK(ScintillaGTK::DrawCT), &ct);
1417 #else
1418 g_signal_connect(G_OBJECT(widcdrw), "expose_event",
1419 G_CALLBACK(ScintillaGTK::ExposeCT), &ct);
1420 #endif
1421 g_signal_connect(G_OBJECT(widcdrw), "button_press_event",
1422 G_CALLBACK(ScintillaGTK::PressCT), this);
1423 gtk_widget_set_events(widcdrw,
1424 GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);
1425 GtkWidget *top = gtk_widget_get_toplevel(PWidget(wMain));
1426 gtk_window_set_transient_for(GTK_WINDOW(PWidget(ct.wCallTip)), GTK_WINDOW(top));
1428 const int width = static_cast<int>(rc.Width());
1429 const int height = static_cast<int>(rc.Height());
1430 gtk_widget_set_size_request(PWidget(ct.wDraw), width, height);
1431 ct.wDraw.Show();
1432 if (PWindow(ct.wCallTip)) {
1433 gdk_window_resize(PWindow(ct.wCallTip), width, height);
1437 void ScintillaGTK::AddToPopUp(const char *label, int cmd, bool enabled) {
1438 GtkWidget *menuItem;
1439 if (label[0])
1440 menuItem = gtk_menu_item_new_with_label(label);
1441 else
1442 menuItem = gtk_separator_menu_item_new();
1443 gtk_menu_shell_append(GTK_MENU_SHELL(popup.GetID()), menuItem);
1444 g_object_set_data(G_OBJECT(menuItem), "CmdNum", GINT_TO_POINTER(cmd));
1445 g_signal_connect(G_OBJECT(menuItem), "activate", G_CALLBACK(PopUpCB), this);
1447 if (cmd) {
1448 if (menuItem)
1449 gtk_widget_set_sensitive(menuItem, enabled);
1453 bool ScintillaGTK::OwnPrimarySelection() {
1454 return primarySelection;
1457 void ScintillaGTK::ClearPrimarySelection() {
1458 if (primarySelection) {
1459 inClearSelection++;
1460 // Calls PrimaryClearSelection: primarySelection -> false
1461 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
1462 inClearSelection--;
1466 void ScintillaGTK::PrimaryGetSelectionThis(GtkClipboard *clip, GtkSelectionData *selection_data, guint info) {
1467 try {
1468 if (SelectionOfGSD(selection_data) == GDK_SELECTION_PRIMARY) {
1469 if (primary.Empty()) {
1470 CopySelectionRange(&primary);
1472 GetSelection(selection_data, info, &primary);
1474 } catch (...) {
1475 errorStatus = Status::Failure;
1479 void ScintillaGTK::PrimaryGetSelection(GtkClipboard *clip, GtkSelectionData *selection_data, guint info, gpointer pSci) {
1480 static_cast<ScintillaGTK *>(pSci)->PrimaryGetSelectionThis(clip, selection_data, info);
1483 void ScintillaGTK::PrimaryClearSelectionThis(GtkClipboard *clip) {
1484 try {
1485 primarySelection = false;
1486 primary.Clear();
1487 if (!inClearSelection) {
1488 // Called because of another application or window claiming primary selection
1489 // so redraw to show selection in secondary colour.
1490 Redraw();
1492 } catch (...) {
1493 errorStatus = Status::Failure;
1497 void ScintillaGTK::PrimaryClearSelection(GtkClipboard *clip, gpointer pSci) {
1498 static_cast<ScintillaGTK *>(pSci)->PrimaryClearSelectionThis(clip);
1501 void ScintillaGTK::ClaimSelection() {
1502 // X Windows has a 'primary selection' as well as the clipboard.
1503 // Whenever the user selects some text, we become the primary selection
1504 ClearPrimarySelection();
1505 if (!sel.Empty()) {
1506 if (gtk_clipboard_set_with_data(
1507 gtk_clipboard_get(GDK_SELECTION_PRIMARY),
1508 clipboardCopyTargets, nClipboardCopyTargets,
1509 PrimaryGetSelection,
1510 PrimaryClearSelection,
1511 this)) {
1512 primarySelection = true;
1517 bool ScintillaGTK::IsStringAtom(GdkAtom type) {
1518 return (type == GDK_TARGET_STRING) || (type == atomUTF8) || (type == atomUTF8Mime);
1521 // Detect rectangular text, convert line ends to current mode, convert from or to UTF-8
1522 void ScintillaGTK::GetGtkSelectionText(GtkSelectionData *selectionData, SelectionText &selText) {
1523 const char *data = reinterpret_cast<const char *>(DataOfGSD(selectionData));
1524 int len = LengthOfGSD(selectionData);
1525 GdkAtom selectionTypeData = TypeOfGSD(selectionData);
1527 // Return empty string if selection is not a string
1528 if (!IsStringAtom(selectionTypeData)) {
1529 selText.Clear();
1530 return;
1533 // Check for "\n\0" ending to string indicating that selection is rectangular
1534 bool isRectangular;
1535 #if PLAT_GTK_WIN32
1536 isRectangular = ::IsClipboardFormatAvailable(cfColumnSelect) != 0;
1537 #else
1538 isRectangular = ((len > 2) && (data[len - 1] == 0 && data[len - 2] == '\n'));
1539 if (isRectangular)
1540 len--; // Forget the extra '\0'
1541 #endif
1543 #if PLAT_GTK_WIN32
1544 // Win32 includes an ending '\0' byte in 'len' for clipboard text from
1545 // external applications; ignore it.
1546 if ((len > 0) && (data[len - 1] == '\0'))
1547 len--;
1548 #endif
1550 std::string dest(data, len);
1551 if (selectionTypeData == GDK_TARGET_STRING) {
1552 if (IsUnicodeMode()) {
1553 // Unknown encoding so assume in Latin1
1554 dest = UTF8FromLatin1(dest);
1555 selText.Copy(dest, CpUtf8, CharacterSet::Ansi, isRectangular, false);
1556 } else {
1557 // Assume buffer is in same encoding as selection
1558 selText.Copy(dest, pdoc->dbcsCodePage,
1559 vs.styles[STYLE_DEFAULT].characterSet, isRectangular, false);
1561 } else { // UTF-8
1562 const char *charSetBuffer = CharacterSetID();
1563 if (!IsUnicodeMode() && *charSetBuffer) {
1564 // Convert to locale
1565 dest = ConvertText(dest.c_str(), dest.length(), charSetBuffer, "UTF-8", true);
1566 selText.Copy(dest, pdoc->dbcsCodePage,
1567 vs.styles[STYLE_DEFAULT].characterSet, isRectangular, false);
1568 } else {
1569 selText.Copy(dest, CpUtf8, CharacterSet::Ansi, isRectangular, false);
1574 void ScintillaGTK::InsertSelection(GtkClipboard *clipBoard, GtkSelectionData *selectionData) {
1575 const gint length = gtk_selection_data_get_length(selectionData);
1576 const GdkAtom selection = gtk_selection_data_get_selection(selectionData);
1577 if (length >= 0) {
1578 SelectionText selText;
1579 GetGtkSelectionText(selectionData, selText);
1581 UndoGroup ug(pdoc);
1582 if (selection == GDK_SELECTION_CLIPBOARD) {
1583 ClearSelection(multiPasteMode == MultiPaste::Each);
1585 if (selection == GDK_SELECTION_PRIMARY) {
1586 SetSelection(posPrimary, posPrimary);
1589 InsertPasteShape(selText.Data(), selText.Length(),
1590 selText.rectangular ? PasteShape::rectangular : PasteShape::stream);
1591 EnsureCaretVisible();
1592 } else {
1593 if (selection == GDK_SELECTION_PRIMARY) {
1594 SetSelection(posPrimary, posPrimary);
1596 GdkAtom target = gtk_selection_data_get_target(selectionData);
1597 if (target == atomUTF8) {
1598 // In case data is actually only stored as text/plain;charset=utf-8 not UTF8_STRING
1599 gtk_clipboard_request_contents(clipBoard, atomUTF8Mime,
1600 SelectionReceiver::ClipboardReceived,
1601 new SelectionReceiver(this)
1605 Redraw();
1608 GObject *ScintillaGTK::MainObject() const noexcept {
1609 return G_OBJECT(PWidget(wMain));
1612 void ScintillaGTK::ReceivedClipboard(GtkClipboard *clipBoard, GtkSelectionData *selection_data) noexcept {
1613 try {
1614 InsertSelection(clipBoard, selection_data);
1615 } catch (...) {
1616 errorStatus = Status::Failure;
1620 void ScintillaGTK::ReceivedSelection(GtkSelectionData *selection_data) {
1621 try {
1622 if ((SelectionOfGSD(selection_data) == GDK_SELECTION_CLIPBOARD) ||
1623 (SelectionOfGSD(selection_data) == GDK_SELECTION_PRIMARY)) {
1624 if ((atomSought == atomUTF8) && (LengthOfGSD(selection_data) <= 0)) {
1625 atomSought = atomString;
1626 gtk_selection_convert(GTK_WIDGET(PWidget(wMain)),
1627 SelectionOfGSD(selection_data), atomSought, GDK_CURRENT_TIME);
1628 } else if ((LengthOfGSD(selection_data) > 0) && IsStringAtom(TypeOfGSD(selection_data))) {
1629 GtkClipboard *clipBoard = gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), SelectionOfGSD(selection_data));
1630 InsertSelection(clipBoard, selection_data);
1633 // else fprintf(stderr, "Target non string %d %d\n", (int)(selection_data->type),
1634 // (int)(atomUTF8));
1635 } catch (...) {
1636 errorStatus = Status::Failure;
1640 void ScintillaGTK::ReceivedDrop(GtkSelectionData *selection_data) {
1641 dragWasDropped = true;
1642 if (TypeOfGSD(selection_data) == atomUriList || TypeOfGSD(selection_data) == atomDROPFILES_DND) {
1643 const char *data = reinterpret_cast<const char *>(DataOfGSD(selection_data));
1644 std::vector<char> drop(data, data + LengthOfGSD(selection_data));
1645 drop.push_back('\0');
1646 NotifyURIDropped(&drop[0]);
1647 } else if (IsStringAtom(TypeOfGSD(selection_data))) {
1648 if (LengthOfGSD(selection_data) > 0) {
1649 SelectionText selText;
1650 GetGtkSelectionText(selection_data, selText);
1651 DropAt(posDrop, selText.Data(), selText.Length(), false, selText.rectangular);
1653 } else if (LengthOfGSD(selection_data) > 0) {
1654 //~ fprintf(stderr, "ReceivedDrop other %p\n", static_cast<void *>(selection_data->type));
1656 Redraw();
1661 void ScintillaGTK::GetSelection(GtkSelectionData *selection_data, guint info, SelectionText *text) {
1662 #if PLAT_GTK_WIN32
1663 // GDK on Win32 expands any \n into \r\n, so make a copy of
1664 // the clip text now with newlines converted to \n. Use { } to hide symbols
1665 // from code below
1666 std::unique_ptr<SelectionText> newline_normalized;
1668 std::string tmpstr = Document::TransformLineEnds(text->Data(), text->Length(), EndOfLine::Lf);
1669 newline_normalized = std::make_unique<SelectionText>();
1670 newline_normalized->Copy(tmpstr, CpUtf8, CharacterSet::Ansi, text->rectangular, false);
1671 text = newline_normalized.get();
1673 #endif
1675 // Convert text to utf8 if it isn't already
1676 std::unique_ptr<SelectionText> converted;
1677 if ((text->codePage != SC_CP_UTF8) && (info == TARGET_UTF8_STRING)) {
1678 const char *charSet = ::CharacterSetID(text->characterSet);
1679 if (*charSet) {
1680 std::string tmputf = ConvertText(text->Data(), text->Length(), "UTF-8", charSet, false);
1681 converted = std::make_unique<SelectionText>();
1682 converted->Copy(tmputf, CpUtf8, CharacterSet::Ansi, text->rectangular, false);
1683 text = converted.get();
1687 // Here is a somewhat evil kludge.
1688 // As I can not work out how to store data on the clipboard in multiple formats
1689 // and need some way to mark the clipping as being stream or rectangular,
1690 // the terminating \0 is included in the length for rectangular clippings.
1691 // All other tested applications behave benignly by ignoring the \0.
1692 // The #if is here because on Windows cfColumnSelect clip entry is used
1693 // instead as standard indicator of rectangularness (so no need to kludge)
1694 const char *textData = text->Data();
1695 gint len = static_cast<gint>(text->Length());
1696 #if PLAT_GTK_WIN32 == 0
1697 if (text->rectangular)
1698 len++;
1699 #endif
1701 if (info == TARGET_UTF8_STRING) {
1702 gtk_selection_data_set_text(selection_data, textData, len);
1703 } else {
1704 gtk_selection_data_set(selection_data,
1705 static_cast<GdkAtom>(GDK_SELECTION_TYPE_STRING),
1706 8, reinterpret_cast<const guchar *>(textData), len);
1710 void ScintillaGTK::StoreOnClipboard(SelectionText *clipText) {
1711 GtkClipboard *clipBoard =
1712 gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), GDK_SELECTION_CLIPBOARD);
1713 if (clipBoard == nullptr) // Occurs if widget isn't in a toplevel
1714 return;
1716 if (gtk_clipboard_set_with_data(clipBoard, clipboardCopyTargets, nClipboardCopyTargets,
1717 ClipboardGetSelection, ClipboardClearSelection, clipText)) {
1718 gtk_clipboard_set_can_store(clipBoard, clipboardCopyTargets, nClipboardCopyTargets);
1722 void ScintillaGTK::ClipboardGetSelection(GtkClipboard *, GtkSelectionData *selection_data, guint info, void *data) {
1723 GetSelection(selection_data, info, static_cast<SelectionText *>(data));
1726 void ScintillaGTK::ClipboardClearSelection(GtkClipboard *, void *data) {
1727 SelectionText *obj = static_cast<SelectionText *>(data);
1728 delete obj;
1731 void ScintillaGTK::UnclaimSelection(GdkEventSelection *selection_event) {
1732 try {
1733 //Platform::DebugPrintf("UnclaimSelection\n");
1734 if (selection_event->selection == GDK_SELECTION_PRIMARY) {
1735 //Platform::DebugPrintf("UnclaimPrimarySelection\n");
1736 if (!OwnPrimarySelection()) {
1737 primary.Clear();
1738 primarySelection = false;
1739 FullPaint();
1742 } catch (...) {
1743 errorStatus = Status::Failure;
1747 void ScintillaGTK::Resize(int width, int height) {
1748 //Platform::DebugPrintf("Resize %d %d\n", width, height);
1749 //printf("Resize %d %d\n", width, height);
1751 // GTK+ 3 warns when we allocate smaller than the minimum allocation,
1752 // so we use these variables to store the minimum scrollbar lengths.
1753 int minVScrollBarHeight, minHScrollBarWidth;
1755 // Not always needed, but some themes can have different sizes of scrollbars
1756 #if GTK_CHECK_VERSION(3,0,0)
1757 GtkRequisition minimum, requisition;
1758 gtk_widget_get_preferred_size(PWidget(scrollbarv), &minimum, &requisition);
1759 minVScrollBarHeight = minimum.height;
1760 verticalScrollBarWidth = requisition.width;
1761 gtk_widget_get_preferred_size(PWidget(scrollbarh), &minimum, &requisition);
1762 minHScrollBarWidth = minimum.width;
1763 horizontalScrollBarHeight = requisition.height;
1764 #else
1765 minVScrollBarHeight = minHScrollBarWidth = 1;
1766 verticalScrollBarWidth = GTK_WIDGET(PWidget(scrollbarv))->requisition.width;
1767 horizontalScrollBarHeight = GTK_WIDGET(PWidget(scrollbarh))->requisition.height;
1768 #endif
1770 // These allocations should never produce negative sizes as they would wrap around to huge
1771 // unsigned numbers inside GTK+ causing warnings.
1772 const bool showSBHorizontal = horizontalScrollBarVisible && !Wrapping();
1774 GtkAllocation alloc = {};
1775 if (showSBHorizontal) {
1776 gtk_widget_show(GTK_WIDGET(PWidget(scrollbarh)));
1777 alloc.x = 0;
1778 alloc.y = height - horizontalScrollBarHeight;
1779 alloc.width = std::max(minHScrollBarWidth, width - verticalScrollBarWidth);
1780 alloc.height = horizontalScrollBarHeight;
1781 gtk_widget_size_allocate(GTK_WIDGET(PWidget(scrollbarh)), &alloc);
1782 } else {
1783 gtk_widget_hide(GTK_WIDGET(PWidget(scrollbarh)));
1784 horizontalScrollBarHeight = 0; // in case horizontalScrollBarVisible is true.
1787 if (verticalScrollBarVisible) {
1788 gtk_widget_show(GTK_WIDGET(PWidget(scrollbarv)));
1789 alloc.x = width - verticalScrollBarWidth;
1790 alloc.y = 0;
1791 alloc.width = verticalScrollBarWidth;
1792 alloc.height = std::max(minVScrollBarHeight, height - horizontalScrollBarHeight);
1793 gtk_widget_size_allocate(GTK_WIDGET(PWidget(scrollbarv)), &alloc);
1794 } else {
1795 gtk_widget_hide(GTK_WIDGET(PWidget(scrollbarv)));
1796 verticalScrollBarWidth = 0;
1798 SetClientRectangle();
1799 if (IS_WIDGET_MAPPED(PWidget(wMain))) {
1800 ChangeSize();
1801 } else {
1802 const PRectangle rcTextArea = GetTextRectangle();
1803 if (wrapWidth != rcTextArea.Width()) {
1804 wrapWidth = rcTextArea.Width();
1805 NeedWrapping();
1809 alloc.x = 0;
1810 alloc.y = 0;
1811 alloc.width = 1;
1812 alloc.height = 1;
1813 #if GTK_CHECK_VERSION(3, 0, 0)
1814 // please GTK 3.20 and ask wText what size it wants, although we know it doesn't really need
1815 // anything special as it's ours.
1816 gtk_widget_get_preferred_size(PWidget(wText), &requisition, nullptr);
1817 alloc.width = requisition.width;
1818 alloc.height = requisition.height;
1819 #endif
1820 alloc.width = std::max(alloc.width, width - verticalScrollBarWidth);
1821 alloc.height = std::max(alloc.height, height - horizontalScrollBarHeight);
1822 gtk_widget_size_allocate(GTK_WIDGET(PWidget(wText)), &alloc);
1825 namespace {
1827 void SetAdjustmentValue(GtkAdjustment *object, int value) noexcept {
1828 GtkAdjustment *adjustment = GTK_ADJUSTMENT(object);
1829 const int maxValue = static_cast<int>(
1830 gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment));
1832 if (value > maxValue)
1833 value = maxValue;
1834 if (value < 0)
1835 value = 0;
1836 gtk_adjustment_set_value(adjustment, value);
1839 int modifierTranslated(int sciModifier) noexcept {
1840 switch (sciModifier) {
1841 case SCMOD_SHIFT:
1842 return GDK_SHIFT_MASK;
1843 case SCMOD_CTRL:
1844 return GDK_CONTROL_MASK;
1845 case SCMOD_ALT:
1846 return GDK_MOD1_MASK;
1847 case SCMOD_SUPER:
1848 return GDK_MOD4_MASK;
1849 default:
1850 return 0;
1854 Point PointOfEvent(const GdkEventButton *event) noexcept {
1855 // Use floor as want to round in the same direction (-infinity) so
1856 // there is no stickiness crossing 0.0.
1857 return Point(static_cast<XYPOSITION>(std::floor(event->x)), static_cast<XYPOSITION>(std::floor(event->y)));
1862 gint ScintillaGTK::PressThis(GdkEventButton *event) {
1863 try {
1864 //Platform::DebugPrintf("Press %x time=%d state = %x button = %x\n",this,event->time, event->state, event->button);
1865 // Do not use GTK+ double click events as Scintilla has its own double click detection
1866 if (event->type != GDK_BUTTON_PRESS)
1867 return FALSE;
1869 evbtn.reset(gdk_event_copy(reinterpret_cast<GdkEvent *>(event)));
1870 buttonMouse = event->button;
1871 const Point pt = PointOfEvent(event);
1872 const PRectangle rcClient = GetClientRectangle();
1873 //Platform::DebugPrintf("Press %0d,%0d in %0d,%0d %0d,%0d\n",
1874 // pt.x, pt.y, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
1875 if ((pt.x > rcClient.right) || (pt.y > rcClient.bottom)) {
1876 Platform::DebugPrintf("Bad location\n");
1877 return FALSE;
1880 const bool shift = (event->state & GDK_SHIFT_MASK) != 0;
1881 bool ctrl = (event->state & GDK_CONTROL_MASK) != 0;
1882 // On X, instead of sending literal modifiers use the user specified
1883 // modifier, defaulting to control instead of alt.
1884 // This is because most X window managers grab alt + click for moving
1885 const bool alt = (event->state & modifierTranslated(rectangularSelectionModifier)) != 0;
1887 gtk_widget_grab_focus(PWidget(wMain));
1888 if (event->button == 1) {
1889 #if PLAT_GTK_MACOSX
1890 const bool meta = ctrl;
1891 // GDK reports the Command modifier key as GDK_MOD2_MASK for button events,
1892 // not GDK_META_MASK like in key events.
1893 ctrl = (event->state & GDK_MOD2_MASK) != 0;
1894 #else
1895 const bool meta = false;
1896 #endif
1897 ButtonDownWithModifiers(pt, event->time, ModifierFlags(shift, ctrl, alt, meta));
1898 } else if (event->button == 2) {
1899 // Grab the primary selection if it exists
1900 posPrimary = SPositionFromLocation(pt, false, false, UserVirtualSpace());
1901 if (OwnPrimarySelection() && primary.Empty())
1902 CopySelectionRange(&primary);
1904 sel.Clear();
1905 RequestSelection(GDK_SELECTION_PRIMARY);
1906 } else if (event->button == 3) {
1907 if (!PointInSelection(pt))
1908 SetEmptySelection(PositionFromLocation(pt));
1909 if (ShouldDisplayPopup(pt)) {
1910 // PopUp menu
1911 // Convert to screen
1912 int ox = 0;
1913 int oy = 0;
1914 gdk_window_get_origin(PWindow(wMain), &ox, &oy);
1915 ContextMenu(Point(pt.x + ox, pt.y + oy));
1916 } else {
1917 #if PLAT_GTK_MACOSX
1918 const bool meta = ctrl;
1919 // GDK reports the Command modifier key as GDK_MOD2_MASK for button events,
1920 // not GDK_META_MASK like in key events.
1921 ctrl = (event->state & GDK_MOD2_MASK) != 0;
1922 #else
1923 const bool meta = false;
1924 #endif
1925 RightButtonDownWithModifiers(pt, event->time, ModifierFlags(shift, ctrl, alt, meta));
1926 return FALSE;
1928 } else if (event->button == 4) {
1929 // Wheel scrolling up (only GTK 1.x does it this way)
1930 if (ctrl)
1931 SetAdjustmentValue(adjustmenth, xOffset - 6);
1932 else
1933 SetAdjustmentValue(adjustmentv, static_cast<int>(topLine) - 3);
1934 } else if (event->button == 5) {
1935 // Wheel scrolling down (only GTK 1.x does it this way)
1936 if (ctrl)
1937 SetAdjustmentValue(adjustmenth, xOffset + 6);
1938 else
1939 SetAdjustmentValue(adjustmentv, static_cast<int>(topLine) + 3);
1941 } catch (...) {
1942 errorStatus = Status::Failure;
1944 return TRUE;
1947 gint ScintillaGTK::Press(GtkWidget *widget, GdkEventButton *event) {
1948 if (event->window != WindowFromWidget(widget))
1949 return FALSE;
1950 ScintillaGTK *sciThis = FromWidget(widget);
1951 return sciThis->PressThis(event);
1954 gint ScintillaGTK::MouseRelease(GtkWidget *widget, GdkEventButton *event) {
1955 ScintillaGTK *sciThis = FromWidget(widget);
1956 try {
1957 //Platform::DebugPrintf("Release %x %d %d\n",sciThis,event->time,event->state);
1958 if (!sciThis->HaveMouseCapture())
1959 return FALSE;
1960 if (event->button == 1) {
1961 Point pt = PointOfEvent(event);
1962 //Platform::DebugPrintf("Up %x %x %d %d %d\n",
1963 // sciThis,event->window,event->time, pt.x, pt.y);
1964 if (event->window != PWindow(sciThis->wMain))
1965 // If mouse released on scroll bar then the position is relative to the
1966 // scrollbar, not the drawing window so just repeat the most recent point.
1967 pt = sciThis->ptMouseLast;
1968 const KeyMod modifiers = ModifierFlags(
1969 (event->state & GDK_SHIFT_MASK) != 0,
1970 (event->state & GDK_CONTROL_MASK) != 0,
1971 (event->state & modifierTranslated(sciThis->rectangularSelectionModifier)) != 0);
1972 sciThis->ButtonUpWithModifiers(pt, event->time, modifiers);
1974 } catch (...) {
1975 sciThis->errorStatus = Status::Failure;
1977 return FALSE;
1980 // win32gtk and GTK >= 2 use SCROLL_* events instead of passing the
1981 // button4/5/6/7 events to the GTK app
1982 gint ScintillaGTK::ScrollEvent(GtkWidget *widget, GdkEventScroll *event) {
1983 ScintillaGTK *sciThis = FromWidget(widget);
1984 try {
1986 if (widget == nullptr || event == nullptr)
1987 return FALSE;
1989 #if defined(GDK_WINDOWING_WAYLAND)
1990 if (event->direction == GDK_SCROLL_SMOOTH && GDK_IS_WAYLAND_WINDOW(event->window)) {
1991 const int smoothScrollFactor = 4;
1992 sciThis->smoothScrollY += event->delta_y * smoothScrollFactor;
1993 sciThis->smoothScrollX += event->delta_x * smoothScrollFactor;;
1994 if (ABS(sciThis->smoothScrollY) >= 1.0) {
1995 const int scrollLines = std::trunc(sciThis->smoothScrollY);
1996 sciThis->ScrollTo(sciThis->topLine + scrollLines);
1997 sciThis->smoothScrollY -= scrollLines;
1999 if (ABS(sciThis->smoothScrollX) >= 1.0) {
2000 const int scrollPixels = std::trunc(sciThis->smoothScrollX);
2001 sciThis->HorizontalScrollTo(sciThis->xOffset + scrollPixels);
2002 sciThis->smoothScrollX -= scrollPixels;
2004 return TRUE;
2006 #endif
2008 // Compute amount and direction to scroll (even tho on win32 there is
2009 // intensity of scrolling info in the native message, gtk doesn't
2010 // support this so we simulate similarly adaptive scrolling)
2011 // Note that this is disabled on macOS (Darwin) with the X11 backend
2012 // where the X11 server already has an adaptive scrolling algorithm
2013 // that fights with this one
2014 int cLineScroll;
2015 #if (defined(__APPLE__) || defined(PLAT_GTK_WIN32)) && !defined(GDK_WINDOWING_QUARTZ)
2016 cLineScroll = sciThis->linesPerScroll;
2017 if (cLineScroll == 0)
2018 cLineScroll = 4;
2019 sciThis->wheelMouseIntensity = cLineScroll;
2020 #else
2021 const gint64 curTime = g_get_monotonic_time();
2022 const gint64 timeDelta = curTime - sciThis->lastWheelMouseTime;
2023 if ((event->direction == sciThis->lastWheelMouseDirection) && (timeDelta < 250000)) {
2024 if (sciThis->wheelMouseIntensity < 12)
2025 sciThis->wheelMouseIntensity++;
2026 cLineScroll = sciThis->wheelMouseIntensity;
2027 } else {
2028 cLineScroll = sciThis->linesPerScroll;
2029 if (cLineScroll == 0)
2030 cLineScroll = 4;
2031 sciThis->wheelMouseIntensity = cLineScroll;
2033 sciThis->lastWheelMouseTime = curTime;
2034 #endif
2035 if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT) {
2036 cLineScroll *= -1;
2038 sciThis->lastWheelMouseDirection = event->direction;
2040 // Note: Unpatched versions of win32gtk don't set the 'state' value so
2041 // only regular scrolling is supported there. Also, unpatched win32gtk
2042 // issues spurious button 2 mouse events during wheeling, which can cause
2043 // problems (a patch for both was submitted by archaeopteryx.com on 13Jun2001)
2045 #if GTK_CHECK_VERSION(3,4,0)
2046 // Smooth scrolling not supported
2047 if (event->direction == GDK_SCROLL_SMOOTH) {
2048 return FALSE;
2050 #endif
2052 // Horizontal scrolling
2053 if (event->direction == GDK_SCROLL_LEFT || event->direction == GDK_SCROLL_RIGHT || event->state & GDK_SHIFT_MASK) {
2054 int hScroll = gtk_adjustment_get_step_increment(sciThis->adjustmenth);
2055 hScroll *= cLineScroll; // scroll by this many characters
2056 sciThis->HorizontalScrollTo(sciThis->xOffset + hScroll);
2058 // Text font size zoom
2059 } else if (event->state & GDK_CONTROL_MASK) {
2060 if (cLineScroll < 0) {
2061 sciThis->KeyCommand(Message::ZoomIn);
2062 } else {
2063 sciThis->KeyCommand(Message::ZoomOut);
2066 // Regular scrolling
2067 } else {
2068 sciThis->ScrollTo(sciThis->topLine + cLineScroll);
2070 return TRUE;
2071 } catch (...) {
2072 sciThis->errorStatus = Status::Failure;
2074 return FALSE;
2077 gint ScintillaGTK::Motion(GtkWidget *widget, GdkEventMotion *event) {
2078 ScintillaGTK *sciThis = FromWidget(widget);
2079 try {
2080 //Platform::DebugPrintf("Motion %x %d\n",sciThis,event->time);
2081 if (event->window != WindowFromWidget(widget))
2082 return FALSE;
2083 int x = 0;
2084 int y = 0;
2085 GdkModifierType state {};
2086 if (event->is_hint) {
2087 #if GTK_CHECK_VERSION(3,0,0)
2088 gdk_window_get_device_position(event->window,
2089 event->device, &x, &y, &state);
2090 #else
2091 gdk_window_get_pointer(event->window, &x, &y, &state);
2092 #endif
2093 } else {
2094 x = static_cast<int>(event->x);
2095 y = static_cast<int>(event->y);
2096 state = static_cast<GdkModifierType>(event->state);
2098 //Platform::DebugPrintf("Move %x %x %d %c %d %d\n",
2099 // sciThis,event->window,event->time,event->is_hint? 'h' :'.', x, y);
2100 const Point pt(static_cast<XYPOSITION>(x), static_cast<XYPOSITION>(y));
2101 const KeyMod modifiers = ModifierFlags(
2102 (event->state & GDK_SHIFT_MASK) != 0,
2103 (event->state & GDK_CONTROL_MASK) != 0,
2104 (event->state & modifierTranslated(sciThis->rectangularSelectionModifier)) != 0);
2105 sciThis->ButtonMoveWithModifiers(pt, event->time, modifiers);
2106 } catch (...) {
2107 sciThis->errorStatus = Status::Failure;
2109 return FALSE;
2112 namespace {
2114 // Map the keypad keys to their equivalent functions
2115 int KeyTranslate(int keyIn) noexcept {
2116 switch (keyIn) {
2117 #if GTK_CHECK_VERSION(3,0,0)
2118 case GDK_KEY_ISO_Left_Tab:
2119 return SCK_TAB;
2120 case GDK_KEY_KP_Down:
2121 return SCK_DOWN;
2122 case GDK_KEY_KP_Up:
2123 return SCK_UP;
2124 case GDK_KEY_KP_Left:
2125 return SCK_LEFT;
2126 case GDK_KEY_KP_Right:
2127 return SCK_RIGHT;
2128 case GDK_KEY_KP_Home:
2129 return SCK_HOME;
2130 case GDK_KEY_KP_End:
2131 return SCK_END;
2132 case GDK_KEY_KP_Page_Up:
2133 return SCK_PRIOR;
2134 case GDK_KEY_KP_Page_Down:
2135 return SCK_NEXT;
2136 case GDK_KEY_KP_Delete:
2137 return SCK_DELETE;
2138 case GDK_KEY_KP_Insert:
2139 return SCK_INSERT;
2140 case GDK_KEY_KP_Enter:
2141 return SCK_RETURN;
2143 case GDK_KEY_Down:
2144 return SCK_DOWN;
2145 case GDK_KEY_Up:
2146 return SCK_UP;
2147 case GDK_KEY_Left:
2148 return SCK_LEFT;
2149 case GDK_KEY_Right:
2150 return SCK_RIGHT;
2151 case GDK_KEY_Home:
2152 return SCK_HOME;
2153 case GDK_KEY_End:
2154 return SCK_END;
2155 case GDK_KEY_Page_Up:
2156 return SCK_PRIOR;
2157 case GDK_KEY_Page_Down:
2158 return SCK_NEXT;
2159 case GDK_KEY_Delete:
2160 return SCK_DELETE;
2161 case GDK_KEY_Insert:
2162 return SCK_INSERT;
2163 case GDK_KEY_Escape:
2164 return SCK_ESCAPE;
2165 case GDK_KEY_BackSpace:
2166 return SCK_BACK;
2167 case GDK_KEY_Tab:
2168 return SCK_TAB;
2169 case GDK_KEY_Return:
2170 return SCK_RETURN;
2171 case GDK_KEY_KP_Add:
2172 return SCK_ADD;
2173 case GDK_KEY_KP_Subtract:
2174 return SCK_SUBTRACT;
2175 case GDK_KEY_KP_Divide:
2176 return SCK_DIVIDE;
2177 case GDK_KEY_Super_L:
2178 return SCK_WIN;
2179 case GDK_KEY_Super_R:
2180 return SCK_RWIN;
2181 case GDK_KEY_Menu:
2182 return SCK_MENU;
2184 #else
2186 case GDK_ISO_Left_Tab:
2187 return SCK_TAB;
2188 case GDK_KP_Down:
2189 return SCK_DOWN;
2190 case GDK_KP_Up:
2191 return SCK_UP;
2192 case GDK_KP_Left:
2193 return SCK_LEFT;
2194 case GDK_KP_Right:
2195 return SCK_RIGHT;
2196 case GDK_KP_Home:
2197 return SCK_HOME;
2198 case GDK_KP_End:
2199 return SCK_END;
2200 case GDK_KP_Page_Up:
2201 return SCK_PRIOR;
2202 case GDK_KP_Page_Down:
2203 return SCK_NEXT;
2204 case GDK_KP_Delete:
2205 return SCK_DELETE;
2206 case GDK_KP_Insert:
2207 return SCK_INSERT;
2208 case GDK_KP_Enter:
2209 return SCK_RETURN;
2211 case GDK_Down:
2212 return SCK_DOWN;
2213 case GDK_Up:
2214 return SCK_UP;
2215 case GDK_Left:
2216 return SCK_LEFT;
2217 case GDK_Right:
2218 return SCK_RIGHT;
2219 case GDK_Home:
2220 return SCK_HOME;
2221 case GDK_End:
2222 return SCK_END;
2223 case GDK_Page_Up:
2224 return SCK_PRIOR;
2225 case GDK_Page_Down:
2226 return SCK_NEXT;
2227 case GDK_Delete:
2228 return SCK_DELETE;
2229 case GDK_Insert:
2230 return SCK_INSERT;
2231 case GDK_Escape:
2232 return SCK_ESCAPE;
2233 case GDK_BackSpace:
2234 return SCK_BACK;
2235 case GDK_Tab:
2236 return SCK_TAB;
2237 case GDK_Return:
2238 return SCK_RETURN;
2239 case GDK_KP_Add:
2240 return SCK_ADD;
2241 case GDK_KP_Subtract:
2242 return SCK_SUBTRACT;
2243 case GDK_KP_Divide:
2244 return SCK_DIVIDE;
2245 case GDK_Super_L:
2246 return SCK_WIN;
2247 case GDK_Super_R:
2248 return SCK_RWIN;
2249 case GDK_Menu:
2250 return SCK_MENU;
2251 #endif
2252 default:
2253 return keyIn;
2259 gboolean ScintillaGTK::KeyThis(GdkEventKey *event) {
2260 try {
2261 //fprintf(stderr, "SC-key: %d %x [%s]\n",
2262 // event->keyval, event->state, (event->length > 0) ? event->string : "empty");
2263 if (gtk_im_context_filter_keypress(im_context.get(), event)) {
2264 return 1;
2266 if (!event->keyval) {
2267 return true;
2270 const bool shift = (event->state & GDK_SHIFT_MASK) != 0;
2271 bool ctrl = (event->state & GDK_CONTROL_MASK) != 0;
2272 const bool alt = (event->state & GDK_MOD1_MASK) != 0;
2273 const bool super = (event->state & GDK_MOD4_MASK) != 0;
2274 guint key = event->keyval;
2275 if ((ctrl || alt) && (key < 128))
2276 key = toupper(key);
2277 #if GTK_CHECK_VERSION(3,0,0)
2278 else if (!ctrl && (key >= GDK_KEY_KP_Multiply && key <= GDK_KEY_KP_9))
2279 #else
2280 else if (!ctrl && (key >= GDK_KP_Multiply && key <= GDK_KP_9))
2281 #endif
2282 key &= 0x7F;
2283 // Hack for keys over 256 and below command keys but makes Hungarian work.
2284 // This will have to change for Unicode
2285 else if (key >= 0xFE00)
2286 key = KeyTranslate(key);
2288 bool consumed = false;
2289 #if !(PLAT_GTK_MACOSX)
2290 const bool meta = false;
2291 #else
2292 const bool meta = ctrl;
2293 ctrl = (event->state & GDK_META_MASK) != 0;
2294 #endif
2295 const bool added = KeyDownWithModifiers(static_cast<Keys>(key), ModifierFlags(shift, ctrl, alt, meta, super), &consumed) != 0;
2296 if (!consumed)
2297 consumed = added;
2298 //fprintf(stderr, "SK-key: %d %x %x\n",event->keyval, event->state, consumed);
2299 if (event->keyval == 0xffffff && event->length > 0) {
2300 ClearSelection();
2301 const Sci::Position lengthInserted = pdoc->InsertString(CurrentPosition(), event->string, strlen(event->string));
2302 if (lengthInserted > 0) {
2303 MovePositionTo(CurrentPosition() + lengthInserted);
2306 return consumed;
2307 } catch (...) {
2308 errorStatus = Status::Failure;
2310 return FALSE;
2313 gboolean ScintillaGTK::KeyPress(GtkWidget *widget, GdkEventKey *event) {
2314 ScintillaGTK *sciThis = FromWidget(widget);
2315 return sciThis->KeyThis(event);
2318 gboolean ScintillaGTK::KeyRelease(GtkWidget *widget, GdkEventKey *event) {
2319 //Platform::DebugPrintf("SC-keyrel: %d %x %3s\n",event->keyval, event->state, event->string);
2320 ScintillaGTK *sciThis = FromWidget(widget);
2321 if (gtk_im_context_filter_keypress(sciThis->im_context.get(), event)) {
2322 return TRUE;
2324 return FALSE;
2327 #if GTK_CHECK_VERSION(3,0,0)
2329 gboolean ScintillaGTK::DrawPreeditThis(GtkWidget *, cairo_t *cr) {
2330 try {
2331 PreEditString pes(im_context.get());
2332 UniquePangoLayout layout(gtk_widget_create_pango_layout(PWidget(wText), pes.str));
2333 pango_layout_set_attributes(layout.get(), pes.attrs);
2335 cairo_move_to(cr, 0, 0);
2336 pango_cairo_show_layout(cr, layout.get());
2337 } catch (...) {
2338 errorStatus = Status::Failure;
2340 return TRUE;
2343 gboolean ScintillaGTK::DrawPreedit(GtkWidget *widget, cairo_t *cr, ScintillaGTK *sciThis) {
2344 return sciThis->DrawPreeditThis(widget, cr);
2347 #else
2349 gboolean ScintillaGTK::ExposePreeditThis(GtkWidget *widget, GdkEventExpose *) {
2350 try {
2351 PreEditString pes(im_context.get());
2352 UniquePangoLayout layout(gtk_widget_create_pango_layout(PWidget(wText), pes.str));
2353 pango_layout_set_attributes(layout.get(), pes.attrs);
2355 UniqueCairo context(gdk_cairo_create(WindowFromWidget(widget)));
2356 cairo_move_to(context.get(), 0, 0);
2357 pango_cairo_show_layout(context.get(), layout.get());
2358 } catch (...) {
2359 errorStatus = Status::Failure;
2361 return TRUE;
2364 gboolean ScintillaGTK::ExposePreedit(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis) {
2365 return sciThis->ExposePreeditThis(widget, ose);
2368 #endif
2370 bool ScintillaGTK::KoreanIME() {
2371 PreEditString pes(im_context.get());
2372 if (pes.pscript != G_UNICODE_SCRIPT_COMMON)
2373 lastNonCommonScript = pes.pscript;
2374 return lastNonCommonScript == G_UNICODE_SCRIPT_HANGUL;
2377 void ScintillaGTK::MoveImeCarets(Sci::Position pos) {
2378 // Move carets relatively by bytes
2379 for (size_t r=0; r<sel.Count(); r++) {
2380 const Sci::Position positionInsert = sel.Range(r).Start().Position();
2381 sel.Range(r).caret.SetPosition(positionInsert + pos);
2382 sel.Range(r).anchor.SetPosition(positionInsert + pos);
2386 void ScintillaGTK::DrawImeIndicator(int indicator, Sci::Position len) {
2387 // Emulate the visual style of IME characters with indicators.
2388 // Draw an indicator on the character before caret by the character bytes of len
2389 // so it should be called after InsertCharacter().
2390 // It does not affect caret positions.
2391 if (indicator < 8 || indicator > INDICATOR_MAX) {
2392 return;
2394 pdoc->DecorationSetCurrentIndicator(indicator);
2395 for (size_t r=0; r<sel.Count(); r++) {
2396 const Sci::Position positionInsert = sel.Range(r).Start().Position();
2397 pdoc->DecorationFillRange(positionInsert - len, 1, len);
2401 namespace {
2403 std::vector<int> MapImeIndicators(PangoAttrList *attrs, const char *u8Str) {
2404 // Map input style to scintilla ime indicator.
2405 // Attrs position points between UTF-8 bytes.
2406 // Indicator index to be returned is character based though.
2407 const glong charactersLen = g_utf8_strlen(u8Str, strlen(u8Str));
2408 std::vector<int> indicator(charactersLen, SC_INDICATOR_UNKNOWN);
2410 PangoAttrIterator *iterunderline = pango_attr_list_get_iterator(attrs);
2411 if (iterunderline) {
2412 do {
2413 const PangoAttribute *attrunderline = pango_attr_iterator_get(iterunderline, PANGO_ATTR_UNDERLINE);
2414 if (attrunderline) {
2415 const glong start = g_utf8_strlen(u8Str, attrunderline->start_index);
2416 const glong end = g_utf8_strlen(u8Str, attrunderline->end_index);
2417 const int ulinevalue = reinterpret_cast<const PangoAttrInt *>(attrunderline)->value;
2418 const PangoUnderline uline = static_cast<PangoUnderline>(ulinevalue);
2419 for (glong i=start; i < end; ++i) {
2420 switch (uline) {
2421 case PANGO_UNDERLINE_NONE:
2422 indicator[i] = SC_INDICATOR_UNKNOWN;
2423 break;
2424 case PANGO_UNDERLINE_SINGLE: // normal input
2425 indicator[i] = SC_INDICATOR_INPUT;
2426 break;
2427 case PANGO_UNDERLINE_DOUBLE:
2428 case PANGO_UNDERLINE_LOW:
2429 case PANGO_UNDERLINE_ERROR:
2430 default:
2431 break;
2435 } while (pango_attr_iterator_next(iterunderline));
2436 pango_attr_iterator_destroy(iterunderline);
2439 PangoAttrIterator *itercolor = pango_attr_list_get_iterator(attrs);
2440 if (itercolor) {
2441 do {
2442 const PangoAttribute *backcolor = pango_attr_iterator_get(itercolor, PANGO_ATTR_BACKGROUND);
2443 if (backcolor) {
2444 const glong start = g_utf8_strlen(u8Str, backcolor->start_index);
2445 const glong end = g_utf8_strlen(u8Str, backcolor->end_index);
2446 for (glong i=start; i < end; ++i) {
2447 indicator[i] = SC_INDICATOR_TARGET; // target converted
2450 } while (pango_attr_iterator_next(itercolor));
2451 pango_attr_iterator_destroy(itercolor);
2453 return indicator;
2458 void ScintillaGTK::SetCandidateWindowPos() {
2459 // Composition box accompanies candidate box.
2460 const Point pt = PointMainCaret();
2461 GdkRectangle imeBox {};
2462 imeBox.x = static_cast<gint>(pt.x);
2463 imeBox.y = static_cast<gint>(pt.y + std::max(4, vs.lineHeight/4));
2464 // prevent overlapping with current line
2465 imeBox.height = vs.lineHeight;
2466 gtk_im_context_set_cursor_location(im_context.get(), &imeBox);
2469 void ScintillaGTK::CommitThis(char *commitStr) {
2470 try {
2471 //~ fprintf(stderr, "Commit '%s'\n", commitStr);
2472 view.imeCaretBlockOverride = false;
2474 if (pdoc->TentativeActive()) {
2475 pdoc->TentativeUndo();
2478 const char *charSetSource = CharacterSetID();
2480 glong uniStrLen = 0;
2481 gunichar *uniStr = g_utf8_to_ucs4_fast(commitStr, static_cast<glong>(strlen(commitStr)), &uniStrLen);
2482 for (glong i = 0; i < uniStrLen; i++) {
2483 gchar u8Char[UTF8MaxBytes+2] = {0};
2484 const gint u8CharLen = g_unichar_to_utf8(uniStr[i], u8Char);
2485 std::string docChar = u8Char;
2486 if (!IsUnicodeMode())
2487 docChar = ConvertText(u8Char, u8CharLen, charSetSource, "UTF-8", true);
2489 InsertCharacter(docChar, CharacterSource::DirectInput);
2491 g_free(uniStr);
2492 ShowCaretAtCurrentPosition();
2493 } catch (...) {
2494 errorStatus = Status::Failure;
2498 void ScintillaGTK::Commit(GtkIMContext *, char *str, ScintillaGTK *sciThis) {
2499 sciThis->CommitThis(str);
2502 void ScintillaGTK::PreeditChangedInlineThis() {
2503 // Copy & paste by johnsonj with a lot of helps of Neil
2504 // Great thanks for my foreruners, jiniya and BLUEnLIVE
2505 try {
2506 if (pdoc->IsReadOnly() || SelectionContainsProtected()) {
2507 gtk_im_context_reset(im_context.get());
2508 return;
2511 view.imeCaretBlockOverride = false; // If backspace.
2513 bool initialCompose = false;
2514 if (pdoc->TentativeActive()) {
2515 pdoc->TentativeUndo();
2516 } else {
2517 // No tentative undo means start of this composition so
2518 // fill in any virtual spaces.
2519 initialCompose = true;
2522 PreEditString preeditStr(im_context.get());
2523 const char *charSetSource = CharacterSetID();
2525 if (!preeditStr.validUTF8 || (charSetSource == nullptr)) {
2526 ShowCaretAtCurrentPosition();
2527 return;
2530 if (preeditStr.uniStrLen == 0) {
2531 ShowCaretAtCurrentPosition();
2532 return;
2535 if (initialCompose) {
2536 ClearBeforeTentativeStart();
2539 SetCandidateWindowPos();
2540 pdoc->TentativeStart(); // TentativeActive() from now on
2542 std::vector<int> indicator = MapImeIndicators(preeditStr.attrs, preeditStr.str);
2544 for (glong i = 0; i < preeditStr.uniStrLen; i++) {
2545 gchar u8Char[UTF8MaxBytes+2] = {0};
2546 const gint u8CharLen = g_unichar_to_utf8(preeditStr.uniStr[i], u8Char);
2547 std::string docChar = u8Char;
2548 if (!IsUnicodeMode())
2549 docChar = ConvertText(u8Char, u8CharLen, charSetSource, "UTF-8", true);
2551 InsertCharacter(docChar, CharacterSource::TentativeInput);
2553 DrawImeIndicator(indicator[i], docChar.size());
2556 // Move caret to ime cursor position.
2557 const int imeEndToImeCaretU32 = preeditStr.cursor_pos - preeditStr.uniStrLen;
2558 const Sci::Position imeCaretPosDoc = pdoc->GetRelativePosition(CurrentPosition(), imeEndToImeCaretU32);
2560 MoveImeCarets(- CurrentPosition() + imeCaretPosDoc);
2562 if (KoreanIME()) {
2563 #if !PLAT_GTK_WIN32
2564 if (preeditStr.cursor_pos > 0) {
2565 int oneCharBefore = pdoc->GetRelativePosition(CurrentPosition(), -1);
2566 MoveImeCarets(- CurrentPosition() + oneCharBefore);
2568 #endif
2569 view.imeCaretBlockOverride = true;
2572 EnsureCaretVisible();
2573 ShowCaretAtCurrentPosition();
2574 } catch (...) {
2575 errorStatus = Status::Failure;
2579 void ScintillaGTK::PreeditChangedWindowedThis() {
2580 try {
2581 PreEditString pes(im_context.get());
2582 if (strlen(pes.str) > 0) {
2583 SetCandidateWindowPos();
2585 UniquePangoLayout layout(gtk_widget_create_pango_layout(PWidget(wText), pes.str));
2586 pango_layout_set_attributes(layout.get(), pes.attrs);
2588 gint w, h;
2589 pango_layout_get_pixel_size(layout.get(), &w, &h);
2591 gint x, y;
2592 gdk_window_get_origin(PWindow(wText), &x, &y);
2594 Point pt = PointMainCaret();
2595 if (pt.x < 0)
2596 pt.x = 0;
2597 if (pt.y < 0)
2598 pt.y = 0;
2600 gtk_window_move(GTK_WINDOW(PWidget(wPreedit)), x + static_cast<gint>(pt.x), y + static_cast<gint>(pt.y));
2601 gtk_window_resize(GTK_WINDOW(PWidget(wPreedit)), w, h);
2602 gtk_widget_show(PWidget(wPreedit));
2603 gtk_widget_queue_draw_area(PWidget(wPreeditDraw), 0, 0, w, h);
2604 } else {
2605 gtk_widget_hide(PWidget(wPreedit));
2607 } catch (...) {
2608 errorStatus = Status::Failure;
2612 void ScintillaGTK::PreeditChanged(GtkIMContext *, ScintillaGTK *sciThis) {
2613 if ((sciThis->imeInteraction == IMEInteraction::Inline) || (sciThis->KoreanIME())) {
2614 sciThis->PreeditChangedInlineThis();
2615 } else {
2616 sciThis->PreeditChangedWindowedThis();
2620 bool ScintillaGTK::RetrieveSurroundingThis(GtkIMContext *context) {
2621 try {
2622 const Sci::Position pos = CurrentPosition();
2623 const int line = pdoc->LineFromPosition(pos);
2624 const Sci::Position startByte = pdoc->LineStart(line);
2625 const Sci::Position endByte = pdoc->LineEnd(line);
2627 std::string utf8Text;
2628 gint cursorIndex; // index of the cursor inside utf8Text, in bytes
2629 const char *charSetBuffer;
2631 if (IsUnicodeMode() || ! *(charSetBuffer = CharacterSetID())) {
2632 utf8Text = RangeText(startByte, endByte);
2633 cursorIndex = pos - startByte;
2634 } else {
2635 // Need to convert
2636 std::string tmpbuf = RangeText(startByte, pos);
2637 utf8Text = ConvertText(&tmpbuf[0], tmpbuf.length(), "UTF-8", charSetBuffer, false);
2638 cursorIndex = utf8Text.length();
2639 if (endByte > pos) {
2640 tmpbuf = RangeText(pos, endByte);
2641 utf8Text += ConvertText(&tmpbuf[0], tmpbuf.length(), "UTF-8", charSetBuffer, false);
2645 gtk_im_context_set_surrounding(context, &utf8Text[0], utf8Text.length(), cursorIndex);
2647 return true;
2648 } catch (...) {
2649 errorStatus = Status::Failure;
2651 return false;
2654 gboolean ScintillaGTK::RetrieveSurrounding(GtkIMContext *context, ScintillaGTK *sciThis) {
2655 return sciThis->RetrieveSurroundingThis(context);
2658 bool ScintillaGTK::DeleteSurroundingThis(GtkIMContext *, gint characterOffset, gint characterCount) {
2659 try {
2660 const Sci::Position startByte = pdoc->GetRelativePosition(CurrentPosition(), characterOffset);
2661 if (startByte == INVALID_POSITION)
2662 return false;
2664 const Sci::Position endByte = pdoc->GetRelativePosition(startByte, characterCount);
2665 if (endByte == INVALID_POSITION)
2666 return false;
2668 return pdoc->DeleteChars(startByte, endByte - startByte);
2669 } catch (...) {
2670 errorStatus = Status::Failure;
2672 return false;
2675 gboolean ScintillaGTK::DeleteSurrounding(GtkIMContext *context, gint characterOffset, gint characterCount, ScintillaGTK *sciThis) {
2676 return sciThis->DeleteSurroundingThis(context, characterOffset, characterCount);
2679 void ScintillaGTK::StyleSetText(GtkWidget *widget, GtkStyle *, void *) {
2680 RealizeText(widget, nullptr);
2683 void ScintillaGTK::RealizeText(GtkWidget *widget, void *) {
2684 // Set NULL background to avoid automatic clearing so Scintilla responsible for all drawing
2685 if (WindowFromWidget(widget)) {
2686 #if GTK_CHECK_VERSION(3,22,0)
2687 // Appears unnecessary
2688 #elif GTK_CHECK_VERSION(3,0,0)
2689 gdk_window_set_background_pattern(WindowFromWidget(widget), nullptr);
2690 #else
2691 gdk_window_set_back_pixmap(WindowFromWidget(widget), nullptr, FALSE);
2692 #endif
2696 static GObjectClass *scintilla_class_parent_class;
2698 void ScintillaGTK::Dispose(GObject *object) {
2699 try {
2700 ScintillaObject *scio = SCINTILLA(object);
2701 ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(scio->pscin);
2703 if (PWidget(sciThis->scrollbarv)) {
2704 gtk_widget_unparent(PWidget(sciThis->scrollbarv));
2705 sciThis->scrollbarv = nullptr;
2708 if (PWidget(sciThis->scrollbarh)) {
2709 gtk_widget_unparent(PWidget(sciThis->scrollbarh));
2710 sciThis->scrollbarh = nullptr;
2713 scintilla_class_parent_class->dispose(object);
2714 } catch (...) {
2715 // Its dying so nowhere to save the status
2719 void ScintillaGTK::Destroy(GObject *object) {
2720 try {
2721 ScintillaObject *scio = SCINTILLA(object);
2723 // This avoids a double destruction
2724 if (!scio->pscin)
2725 return;
2726 ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(scio->pscin);
2727 //Platform::DebugPrintf("Destroying %x %x\n", sciThis, object);
2728 sciThis->Finalise();
2730 delete sciThis;
2731 scio->pscin = nullptr;
2732 scintilla_class_parent_class->finalize(object);
2733 } catch (...) {
2734 // Its dead so nowhere to save the status
2738 void ScintillaGTK::CheckForFontOptionChange() {
2739 const FontOptions fontOptionsNow(PWidget(wText));
2740 if (!(fontOptionsNow == fontOptionsPrevious)) {
2741 // Clear position caches
2742 InvalidateStyleData();
2744 fontOptionsPrevious = fontOptionsNow;
2747 #if GTK_CHECK_VERSION(3,0,0)
2749 gboolean ScintillaGTK::DrawTextThis(cairo_t *cr) {
2750 try {
2751 CheckForFontOptionChange();
2753 paintState = PaintState::painting;
2754 repaintFullWindow = false;
2756 rcPaint = GetClientRectangle();
2758 cairo_rectangle_list_t *oldRgnUpdate = rgnUpdate;
2759 rgnUpdate = cairo_copy_clip_rectangle_list(cr);
2760 if (rgnUpdate && rgnUpdate->status != CAIRO_STATUS_SUCCESS) {
2761 // If not successful then ignore
2762 fprintf(stderr, "DrawTextThis failed to copy update region %d [%d]\n", rgnUpdate->status, rgnUpdate->num_rectangles);
2763 cairo_rectangle_list_destroy(rgnUpdate);
2764 rgnUpdate = nullptr;
2767 double x1, y1, x2, y2;
2768 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
2769 rcPaint.left = x1;
2770 rcPaint.top = y1;
2771 rcPaint.right = x2;
2772 rcPaint.bottom = y2;
2773 PRectangle rcClient = GetClientRectangle();
2774 paintingAllText = rcPaint.Contains(rcClient);
2775 std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(Technology::Default));
2776 surfaceWindow->Init(cr, PWidget(wText));
2777 Paint(surfaceWindow.get(), rcPaint);
2778 surfaceWindow->Release();
2779 if ((paintState == PaintState::abandoned) || repaintFullWindow) {
2780 // Painting area was insufficient to cover new styling or brace highlight positions
2781 FullPaint();
2783 paintState = PaintState::notPainting;
2784 repaintFullWindow = false;
2786 if (rgnUpdate) {
2787 cairo_rectangle_list_destroy(rgnUpdate);
2789 rgnUpdate = oldRgnUpdate;
2790 paintState = PaintState::notPainting;
2791 } catch (...) {
2792 errorStatus = Status::Failure;
2795 return FALSE;
2798 gboolean ScintillaGTK::DrawText(GtkWidget *, cairo_t *cr, ScintillaGTK *sciThis) {
2799 return sciThis->DrawTextThis(cr);
2802 gboolean ScintillaGTK::DrawThis(cairo_t *cr) {
2803 try {
2804 #ifdef GTK_STYLE_CLASS_SCROLLBARS_JUNCTION /* GTK >= 3.4 */
2805 // if both scrollbars are visible, paint the little square on the bottom right corner
2806 if (verticalScrollBarVisible && horizontalScrollBarVisible && !Wrapping()) {
2807 GtkStyleContext *styleContext = gtk_widget_get_style_context(PWidget(wMain));
2808 PRectangle rc = GetClientRectangle();
2810 gtk_style_context_save(styleContext);
2811 gtk_style_context_add_class(styleContext, GTK_STYLE_CLASS_SCROLLBARS_JUNCTION);
2813 gtk_render_background(styleContext, cr, rc.right, rc.bottom,
2814 verticalScrollBarWidth, horizontalScrollBarHeight);
2815 gtk_render_frame(styleContext, cr, rc.right, rc.bottom,
2816 verticalScrollBarWidth, horizontalScrollBarHeight);
2818 gtk_style_context_restore(styleContext);
2820 #endif
2822 gtk_container_propagate_draw(
2823 GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarh), cr);
2824 gtk_container_propagate_draw(
2825 GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarv), cr);
2826 // Starting from the following version, the expose event are not propagated
2827 // for double buffered non native windows, so we need to call it ourselves
2828 // or keep the default handler
2829 #if GTK_CHECK_VERSION(3,0,0)
2830 // we want to forward on any >= 3.9.2 runtime
2831 if (gtk_check_version(3, 9, 2) == nullptr) {
2832 gtk_container_propagate_draw(
2833 GTK_CONTAINER(PWidget(wMain)), PWidget(wText), cr);
2835 #endif
2836 } catch (...) {
2837 errorStatus = Status::Failure;
2839 return FALSE;
2842 gboolean ScintillaGTK::DrawMain(GtkWidget *widget, cairo_t *cr) {
2843 ScintillaGTK *sciThis = FromWidget(widget);
2844 return sciThis->DrawThis(cr);
2847 #else
2849 gboolean ScintillaGTK::ExposeTextThis(GtkWidget * /*widget*/, GdkEventExpose *ose) {
2850 try {
2851 CheckForFontOptionChange();
2853 paintState = PaintState::painting;
2855 rcPaint = PRectangle::FromInts(
2856 ose->area.x,
2857 ose->area.y,
2858 ose->area.x + ose->area.width,
2859 ose->area.y + ose->area.height);
2861 GdkRegion *oldRgnUpdate = rgnUpdate;
2862 rgnUpdate = gdk_region_copy(ose->region);
2863 const PRectangle rcClient = GetClientRectangle();
2864 paintingAllText = rcPaint.Contains(rcClient);
2866 std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(Technology::Default));
2867 UniqueCairo cr(gdk_cairo_create(PWindow(wText)));
2868 surfaceWindow->Init(cr.get(), PWidget(wText));
2869 Paint(surfaceWindow.get(), rcPaint);
2871 if ((paintState == PaintState::abandoned) || repaintFullWindow) {
2872 // Painting area was insufficient to cover new styling or brace highlight positions
2873 FullPaint();
2875 paintState = PaintState::notPainting;
2876 repaintFullWindow = false;
2878 if (rgnUpdate) {
2879 gdk_region_destroy(rgnUpdate);
2881 rgnUpdate = oldRgnUpdate;
2882 } catch (...) {
2883 errorStatus = Status::Failure;
2886 return FALSE;
2889 gboolean ScintillaGTK::ExposeText(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis) {
2890 return sciThis->ExposeTextThis(widget, ose);
2893 gboolean ScintillaGTK::ExposeMain(GtkWidget *widget, GdkEventExpose *ose) {
2894 ScintillaGTK *sciThis = FromWidget(widget);
2895 //Platform::DebugPrintf("Expose Main %0d,%0d %0d,%0d\n",
2896 //ose->area.x, ose->area.y, ose->area.width, ose->area.height);
2897 return sciThis->Expose(widget, ose);
2900 gboolean ScintillaGTK::Expose(GtkWidget *, GdkEventExpose *ose) {
2901 try {
2902 //fprintf(stderr, "Expose %0d,%0d %0d,%0d\n",
2903 //ose->area.x, ose->area.y, ose->area.width, ose->area.height);
2905 // The text is painted in ExposeText
2906 gtk_container_propagate_expose(
2907 GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarh), ose);
2908 gtk_container_propagate_expose(
2909 GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarv), ose);
2911 } catch (...) {
2912 errorStatus = Status::Failure;
2914 return FALSE;
2917 #endif
2919 void ScintillaGTK::ScrollSignal(GtkAdjustment *adj, ScintillaGTK *sciThis) {
2920 try {
2921 sciThis->ScrollTo(static_cast<int>(gtk_adjustment_get_value(adj)), false);
2922 } catch (...) {
2923 sciThis->errorStatus = Status::Failure;
2927 void ScintillaGTK::ScrollHSignal(GtkAdjustment *adj, ScintillaGTK *sciThis) {
2928 try {
2929 sciThis->HorizontalScrollTo(static_cast<int>(gtk_adjustment_get_value(adj)));
2930 } catch (...) {
2931 sciThis->errorStatus = Status::Failure;
2935 void ScintillaGTK::SelectionReceived(GtkWidget *widget,
2936 GtkSelectionData *selection_data, guint) {
2937 ScintillaGTK *sciThis = FromWidget(widget);
2938 //Platform::DebugPrintf("Selection received\n");
2939 sciThis->ReceivedSelection(selection_data);
2942 void ScintillaGTK::SelectionGet(GtkWidget *widget,
2943 GtkSelectionData *selection_data, guint info, guint) {
2944 ScintillaGTK *sciThis = FromWidget(widget);
2945 try {
2946 //Platform::DebugPrintf("Selection get\n");
2947 if (SelectionOfGSD(selection_data) == GDK_SELECTION_PRIMARY) {
2948 if (sciThis->primary.Empty()) {
2949 sciThis->CopySelectionRange(&sciThis->primary);
2951 sciThis->GetSelection(selection_data, info, &sciThis->primary);
2953 } catch (...) {
2954 sciThis->errorStatus = Status::Failure;
2958 gint ScintillaGTK::SelectionClear(GtkWidget *widget, GdkEventSelection *selection_event) {
2959 ScintillaGTK *sciThis = FromWidget(widget);
2960 //Platform::DebugPrintf("Selection clear\n");
2961 sciThis->UnclaimSelection(selection_event);
2962 if (GTK_WIDGET_CLASS(sciThis->parentClass)->selection_clear_event) {
2963 return GTK_WIDGET_CLASS(sciThis->parentClass)->selection_clear_event(widget, selection_event);
2965 return TRUE;
2968 gboolean ScintillaGTK::DragMotionThis(GdkDragContext *context,
2969 gint x, gint y, guint dragtime) {
2970 try {
2971 const Point npt = Point::FromInts(x, y);
2972 SetDragPosition(SPositionFromLocation(npt, false, false, UserVirtualSpace()));
2973 GdkDragAction preferredAction = gdk_drag_context_get_suggested_action(context);
2974 const GdkDragAction actions = gdk_drag_context_get_actions(context);
2975 const SelectionPosition pos = SPositionFromLocation(npt);
2976 if ((inDragDrop == DragDrop::dragging) && (PositionInSelection(pos.Position()))) {
2977 // Avoid dragging selection onto itself as that produces a move
2978 // with no real effect but which creates undo actions.
2979 preferredAction = static_cast<GdkDragAction>(0);
2980 } else if (actions == actionCopyOrMove) {
2981 preferredAction = GDK_ACTION_MOVE;
2983 gdk_drag_status(context, preferredAction, dragtime);
2984 } catch (...) {
2985 errorStatus = Status::Failure;
2987 return FALSE;
2990 gboolean ScintillaGTK::DragMotion(GtkWidget *widget, GdkDragContext *context,
2991 gint x, gint y, guint dragtime) {
2992 ScintillaGTK *sciThis = FromWidget(widget);
2993 return sciThis->DragMotionThis(context, x, y, dragtime);
2996 void ScintillaGTK::DragLeave(GtkWidget *widget, GdkDragContext * /*context*/, guint) {
2997 ScintillaGTK *sciThis = FromWidget(widget);
2998 try {
2999 sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
3000 //Platform::DebugPrintf("DragLeave %x\n", sciThis);
3001 } catch (...) {
3002 sciThis->errorStatus = Status::Failure;
3006 void ScintillaGTK::DragEnd(GtkWidget *widget, GdkDragContext * /*context*/) {
3007 ScintillaGTK *sciThis = FromWidget(widget);
3008 try {
3009 // If drag did not result in drop here or elsewhere
3010 if (!sciThis->dragWasDropped)
3011 sciThis->SetEmptySelection(sciThis->posDrag);
3012 sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
3013 //Platform::DebugPrintf("DragEnd %x %d\n", sciThis, sciThis->dragWasDropped);
3014 sciThis->inDragDrop = DragDrop::none;
3015 } catch (...) {
3016 sciThis->errorStatus = Status::Failure;
3020 gboolean ScintillaGTK::Drop(GtkWidget *widget, GdkDragContext * /*context*/,
3021 gint, gint, guint) {
3022 ScintillaGTK *sciThis = FromWidget(widget);
3023 try {
3024 //Platform::DebugPrintf("Drop %x\n", sciThis);
3025 sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
3026 } catch (...) {
3027 sciThis->errorStatus = Status::Failure;
3029 return FALSE;
3032 void ScintillaGTK::DragDataReceived(GtkWidget *widget, GdkDragContext * /*context*/,
3033 gint, gint, GtkSelectionData *selection_data, guint /*info*/, guint) {
3034 ScintillaGTK *sciThis = FromWidget(widget);
3035 try {
3036 sciThis->ReceivedDrop(selection_data);
3037 sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
3038 } catch (...) {
3039 sciThis->errorStatus = Status::Failure;
3043 void ScintillaGTK::DragDataGet(GtkWidget *widget, GdkDragContext *context,
3044 GtkSelectionData *selection_data, guint info, guint) {
3045 ScintillaGTK *sciThis = FromWidget(widget);
3046 try {
3047 sciThis->dragWasDropped = true;
3048 if (!sciThis->sel.Empty()) {
3049 sciThis->GetSelection(selection_data, info, &sciThis->drag);
3051 const GdkDragAction action = gdk_drag_context_get_selected_action(context);
3052 if (action == GDK_ACTION_MOVE) {
3053 for (size_t r=0; r<sciThis->sel.Count(); r++) {
3054 if (sciThis->posDrop >= sciThis->sel.Range(r).Start()) {
3055 if (sciThis->posDrop > sciThis->sel.Range(r).End()) {
3056 sciThis->posDrop.Add(-sciThis->sel.Range(r).Length());
3057 } else {
3058 sciThis->posDrop.Add(-SelectionRange(sciThis->posDrop, sciThis->sel.Range(r).Start()).Length());
3062 sciThis->ClearSelection();
3064 sciThis->SetDragPosition(SelectionPosition(Sci::invalidPosition));
3065 } catch (...) {
3066 sciThis->errorStatus = Status::Failure;
3070 int ScintillaGTK::TimeOut(gpointer ptt) {
3071 TimeThunk *tt = static_cast<TimeThunk *>(ptt);
3072 tt->scintilla->TickFor(tt->reason);
3073 return 1;
3076 gboolean ScintillaGTK::IdleCallback(gpointer pSci) {
3077 ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(pSci);
3078 // Idler will be automatically stopped, if there is nothing
3079 // to do while idle.
3080 const bool ret = sciThis->Idle();
3081 if (!ret) {
3082 // FIXME: This will remove the idler from GTK, we don't want to
3083 // remove it as it is removed automatically when this function
3084 // returns false (although, it should be harmless).
3085 sciThis->SetIdle(false);
3087 return ret;
3090 gboolean ScintillaGTK::StyleIdle(gpointer pSci) {
3091 ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(pSci);
3092 sciThis->IdleWork();
3093 // Idler will be automatically stopped
3094 return FALSE;
3097 void ScintillaGTK::IdleWork() {
3098 Editor::IdleWork();
3099 styleIdleID = 0;
3102 void ScintillaGTK::QueueIdleWork(WorkItems items, Sci::Position upTo) {
3103 Editor::QueueIdleWork(items, upTo);
3104 if (!styleIdleID) {
3105 // Only allow one style needed to be queued
3106 styleIdleID = gdk_threads_add_idle_full(G_PRIORITY_HIGH_IDLE, StyleIdle, this, nullptr);
3110 void ScintillaGTK::SetDocPointer(Document *document) {
3111 Document *oldDoc = nullptr;
3112 ScintillaGTKAccessible *sciAccessible = nullptr;
3113 if (accessible) {
3114 sciAccessible = ScintillaGTKAccessible::FromAccessible(accessible);
3115 if (sciAccessible && pdoc) {
3116 oldDoc = pdoc;
3117 oldDoc->AddRef();
3121 Editor::SetDocPointer(document);
3123 if (sciAccessible) {
3124 // the accessible needs have the old Document, but also the new one active
3125 sciAccessible->ChangeDocument(oldDoc, pdoc);
3127 if (oldDoc) {
3128 oldDoc->Release();
3132 void ScintillaGTK::PopUpCB(GtkMenuItem *menuItem, ScintillaGTK *sciThis) {
3133 guint const action = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(menuItem), "CmdNum"));
3134 if (action) {
3135 sciThis->Command(action);
3139 gboolean ScintillaGTK::PressCT(GtkWidget *widget, GdkEventButton *event, ScintillaGTK *sciThis) {
3140 try {
3141 if (event->window != WindowFromWidget(widget))
3142 return FALSE;
3143 if (event->type != GDK_BUTTON_PRESS)
3144 return FALSE;
3145 const Point pt = PointOfEvent(event);
3146 sciThis->ct.MouseClick(pt);
3147 sciThis->CallTipClick();
3148 } catch (...) {
3150 return TRUE;
3153 #if GTK_CHECK_VERSION(3,0,0)
3155 gboolean ScintillaGTK::DrawCT(GtkWidget *widget, cairo_t *cr, CallTip *ctip) {
3156 try {
3157 std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(Technology::Default));
3158 surfaceWindow->Init(cr, widget);
3159 surfaceWindow->SetMode(SurfaceMode(ctip->codePage, false));
3160 ctip->PaintCT(surfaceWindow.get());
3161 surfaceWindow->Release();
3162 } catch (...) {
3163 // No pointer back to Scintilla to save status
3165 return TRUE;
3168 #else
3170 gboolean ScintillaGTK::ExposeCT(GtkWidget *widget, GdkEventExpose * /*ose*/, CallTip *ctip) {
3171 try {
3172 std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(Technology::Default));
3173 UniqueCairo cr(gdk_cairo_create(WindowFromWidget(widget)));
3174 surfaceWindow->Init(cr.get(), widget);
3175 surfaceWindow->SetMode(SurfaceMode(ctip->codePage, false));
3176 ctip->PaintCT(surfaceWindow.get());
3177 } catch (...) {
3178 // No pointer back to Scintilla to save status
3180 return TRUE;
3183 #endif
3185 AtkObject *ScintillaGTK::GetAccessibleThis(GtkWidget *widget) {
3186 return ScintillaGTKAccessible::WidgetGetAccessibleImpl(widget, &accessible, scintilla_class_parent_class);
3189 AtkObject *ScintillaGTK::GetAccessible(GtkWidget *widget) {
3190 return FromWidget(widget)->GetAccessibleThis(widget);
3193 sptr_t ScintillaGTK::DirectFunction(
3194 sptr_t ptr, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
3195 ScintillaGTK *sci = reinterpret_cast<ScintillaGTK *>(ptr);
3196 return sci->WndProc(static_cast<Message>(iMessage), wParam, lParam);
3199 sptr_t ScintillaGTK::DirectStatusFunction(
3200 sptr_t ptr, unsigned int iMessage, uptr_t wParam, sptr_t lParam, int *pStatus) {
3201 ScintillaGTK *sci = reinterpret_cast<ScintillaGTK *>(ptr);
3202 const sptr_t returnValue = sci->WndProc(static_cast<Message>(iMessage), wParam, lParam);
3203 *pStatus = static_cast<int>(sci->errorStatus);
3204 return returnValue;
3207 /* legacy name for scintilla_object_send_message */
3208 GEANY_API_SYMBOL
3209 sptr_t scintilla_send_message(ScintillaObject *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
3210 ScintillaGTK *psci = static_cast<ScintillaGTK *>(sci->pscin);
3211 return psci->WndProc(static_cast<Message>(iMessage), wParam, lParam);
3214 GEANY_API_SYMBOL
3215 gintptr scintilla_object_send_message(ScintillaObject *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
3216 return scintilla_send_message(sci, iMessage, wParam, lParam);
3219 static void scintilla_class_init(ScintillaClass *klass);
3220 static void scintilla_init(ScintillaObject *sci);
3222 /* legacy name for scintilla_object_get_type */
3223 GEANY_API_SYMBOL
3224 GType scintilla_get_type() {
3225 static GType scintilla_type = 0;
3226 try {
3228 if (!scintilla_type) {
3229 scintilla_type = g_type_from_name("ScintillaObject");
3230 if (!scintilla_type) {
3231 static GTypeInfo scintilla_info = {
3232 (guint16) sizeof(ScintillaObjectClass),
3233 nullptr, //(GBaseInitFunc)
3234 nullptr, //(GBaseFinalizeFunc)
3235 (GClassInitFunc) scintilla_class_init,
3236 nullptr, //(GClassFinalizeFunc)
3237 nullptr, //gconstpointer data
3238 (guint16) sizeof(ScintillaObject),
3239 0, //n_preallocs
3240 (GInstanceInitFunc) scintilla_init,
3241 nullptr //(GTypeValueTable*)
3243 scintilla_type = g_type_register_static(
3244 GTK_TYPE_CONTAINER, "ScintillaObject", &scintilla_info, (GTypeFlags) 0);
3248 } catch (...) {
3250 return scintilla_type;
3253 GEANY_API_SYMBOL
3254 GType scintilla_object_get_type() {
3255 return scintilla_get_type();
3258 void ScintillaGTK::ClassInit(OBJECT_CLASS *object_class, GtkWidgetClass *widget_class, GtkContainerClass *container_class) {
3259 Platform_Initialise();
3260 atomUTF8 = gdk_atom_intern("UTF8_STRING", FALSE);
3261 atomUTF8Mime = gdk_atom_intern("text/plain;charset=utf-8", FALSE);
3262 atomString = GDK_SELECTION_TYPE_STRING;
3263 atomUriList = gdk_atom_intern("text/uri-list", FALSE);
3264 atomDROPFILES_DND = gdk_atom_intern("DROPFILES_DND", FALSE);
3266 // Define default signal handlers for the class: Could move more
3267 // of the signal handlers here (those that currently attached to wDraw
3268 // in Init() may require coordinate translation?)
3270 object_class->dispose = Dispose;
3271 object_class->finalize = Destroy;
3272 #if GTK_CHECK_VERSION(3,0,0)
3273 widget_class->get_preferred_width = GetPreferredWidth;
3274 widget_class->get_preferred_height = GetPreferredHeight;
3275 #else
3276 widget_class->size_request = SizeRequest;
3277 #endif
3278 widget_class->size_allocate = SizeAllocate;
3279 #if GTK_CHECK_VERSION(3,0,0)
3280 widget_class->draw = DrawMain;
3281 #else
3282 widget_class->expose_event = ExposeMain;
3283 #endif
3284 widget_class->motion_notify_event = Motion;
3285 widget_class->button_press_event = Press;
3286 widget_class->button_release_event = MouseRelease;
3287 widget_class->scroll_event = ScrollEvent;
3288 widget_class->key_press_event = KeyPress;
3289 widget_class->key_release_event = KeyRelease;
3290 widget_class->focus_in_event = FocusIn;
3291 widget_class->focus_out_event = FocusOut;
3292 widget_class->selection_received = SelectionReceived;
3293 widget_class->selection_get = SelectionGet;
3294 widget_class->selection_clear_event = SelectionClear;
3296 widget_class->drag_data_received = DragDataReceived;
3297 widget_class->drag_motion = DragMotion;
3298 widget_class->drag_leave = DragLeave;
3299 widget_class->drag_end = DragEnd;
3300 widget_class->drag_drop = Drop;
3301 widget_class->drag_data_get = DragDataGet;
3303 widget_class->realize = Realize;
3304 widget_class->unrealize = UnRealize;
3305 widget_class->map = Map;
3306 widget_class->unmap = UnMap;
3308 widget_class->get_accessible = GetAccessible;
3310 container_class->forall = MainForAll;
3313 static void scintilla_class_init(ScintillaClass *klass) {
3314 try {
3315 OBJECT_CLASS *object_class = reinterpret_cast<OBJECT_CLASS *>(klass);
3316 GtkWidgetClass *widget_class = reinterpret_cast<GtkWidgetClass *>(klass);
3317 GtkContainerClass *container_class = reinterpret_cast<GtkContainerClass *>(klass);
3319 const GSignalFlags sigflags = static_cast<GSignalFlags>(G_SIGNAL_ACTION | G_SIGNAL_RUN_LAST);
3320 scintilla_signals[COMMAND_SIGNAL] = g_signal_new(
3321 "command",
3322 G_TYPE_FROM_CLASS(object_class),
3323 sigflags,
3324 G_STRUCT_OFFSET(ScintillaClass, command),
3325 nullptr, //(GSignalAccumulator)
3326 nullptr, //(gpointer)
3327 scintilla_marshal_VOID__INT_OBJECT,
3328 G_TYPE_NONE,
3329 2, G_TYPE_INT, GTK_TYPE_WIDGET);
3331 scintilla_signals[NOTIFY_SIGNAL] = g_signal_new(
3332 SCINTILLA_NOTIFY,
3333 G_TYPE_FROM_CLASS(object_class),
3334 sigflags,
3335 G_STRUCT_OFFSET(ScintillaClass, notify),
3336 nullptr, //(GSignalAccumulator)
3337 nullptr, //(gpointer)
3338 scintilla_marshal_VOID__INT_BOXED,
3339 G_TYPE_NONE,
3340 2, G_TYPE_INT, SCINTILLA_TYPE_NOTIFICATION);
3342 klass->command = nullptr;
3343 klass->notify = nullptr;
3344 scintilla_class_parent_class = G_OBJECT_CLASS(g_type_class_peek_parent(klass));
3345 ScintillaGTK::ClassInit(object_class, widget_class, container_class);
3346 } catch (...) {
3350 static void scintilla_init(ScintillaObject *sci) {
3351 try {
3352 gtk_widget_set_can_focus(GTK_WIDGET(sci), TRUE);
3353 sci->pscin = new ScintillaGTK(sci);
3354 } catch (...) {
3358 /* legacy name for scintilla_object_new */
3359 GEANY_API_SYMBOL
3360 GtkWidget *scintilla_new() {
3361 GtkWidget *widget = GTK_WIDGET(g_object_new(scintilla_get_type(), nullptr));
3362 gtk_widget_set_direction(widget, GTK_TEXT_DIR_LTR);
3364 return widget;
3367 GEANY_API_SYMBOL
3368 GtkWidget *scintilla_object_new() {
3369 return scintilla_new();
3372 void scintilla_set_id(ScintillaObject *sci, uptr_t id) {
3373 ScintillaGTK *psci = static_cast<ScintillaGTK *>(sci->pscin);
3374 psci->ctrlID = static_cast<int>(id);
3377 void scintilla_release_resources(void) {
3378 try {
3379 Platform_Finalise();
3380 } catch (...) {
3384 /* Define a dummy boxed type because g-ir-scanner is unable to
3385 * recognize gpointer-derived types. Note that SCNotificaiton
3386 * is always allocated on stack so copying is not appropriate. */
3387 static void *copy_(void *src) { return src; }
3388 static void free_(void *) { }
3390 GEANY_API_SYMBOL
3391 GType scnotification_get_type(void) {
3392 static gsize type_id = 0;
3393 if (g_once_init_enter(&type_id)) {
3394 const gsize id = (gsize) g_boxed_type_register_static(
3395 g_intern_static_string("SCNotification"),
3396 (GBoxedCopyFunc) copy_,
3397 (GBoxedFreeFunc) free_);
3398 g_once_init_leave(&type_id, id);
3400 return (GType) type_id;