medit/scintilla/gtk/ScintillaGTK.cxx

3258 lines
104 KiB
C++

// Scintilla source code edit control
// ScintillaGTK.cxx - GTK+ specific subclass of ScintillaBase
// Copyright 1998-2004 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <ctype.h>
#include <stdexcept>
#include <new>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <glib.h>
#include <gmodule.h>
#include <gdk/gdk.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#if defined(__WIN32__) || defined(_MSC_VER)
#include <windows.h>
#endif
#include "Platform.h"
#include "ILexer.h"
#include "Scintilla.h"
#include "ScintillaWidget.h"
#ifdef SCI_LEXER
#include "SciLexer.h"
#endif
#include "StringCopy.h"
#ifdef SCI_LEXER
#include "LexerModule.h"
#endif
#include "Position.h"
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "ContractionState.h"
#include "CellBuffer.h"
#include "CallTip.h"
#include "KeyMap.h"
#include "Indicator.h"
#include "XPM.h"
#include "LineMarker.h"
#include "Style.h"
#include "ViewStyle.h"
#include "CharClassify.h"
#include "Decoration.h"
#include "CaseFolder.h"
#include "Document.h"
#include "CaseConvert.h"
#include "UniConversion.h"
#include "UnicodeFromUTF8.h"
#include "Selection.h"
#include "PositionCache.h"
#include "EditModel.h"
#include "MarginView.h"
#include "EditView.h"
#include "Editor.h"
#include "AutoComplete.h"
#include "ScintillaBase.h"
#ifdef SCI_LEXER
#include "ExternalLexer.h"
#endif
#include "scintilla-marshal.h"
#include "Converter.h"
#if defined(__clang__)
// Clang 3.0 incorrectly displays sentinel warnings. Fixed by clang 3.1.
#pragma GCC diagnostic ignored "-Wsentinel"
#endif
#if GTK_CHECK_VERSION(2,20,0)
#define IS_WIDGET_REALIZED(w) (gtk_widget_get_realized(GTK_WIDGET(w)))
#define IS_WIDGET_MAPPED(w) (gtk_widget_get_mapped(GTK_WIDGET(w)))
#else
#define IS_WIDGET_REALIZED(w) (GTK_WIDGET_REALIZED(w))
#define IS_WIDGET_MAPPED(w) (GTK_WIDGET_MAPPED(w))
#endif
#define SC_INDICATOR_INPUT INDIC_IME
#define SC_INDICATOR_TARGET INDIC_IME+1
#define SC_INDICATOR_CONVERTED INDIC_IME+2
#define SC_INDICATOR_UNKNOWN INDIC_IME_MAX
static GdkWindow *WindowFromWidget(GtkWidget *w) {
return gtk_widget_get_window(w);
}
#ifdef _MSC_VER
// Constant conditional expressions are because of GTK+ headers
#pragma warning(disable: 4127)
// Ignore unreferenced local functions in GTK+ headers
#pragma warning(disable: 4505)
#endif
#define OBJECT_CLASS GObjectClass
#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif
static GdkWindow *PWindow(const Window &w) {
GtkWidget *widget = static_cast<GtkWidget *>(w.GetID());
return gtk_widget_get_window(widget);
}
extern std::string UTF8FromLatin1(const char *s, int len);
class ScintillaGTK : public ScintillaBase {
_ScintillaObject *sci;
Window wText;
Window scrollbarv;
Window scrollbarh;
GtkAdjustment *adjustmentv;
GtkAdjustment *adjustmenth;
int verticalScrollBarWidth;
int horizontalScrollBarHeight;
SelectionText primary;
GdkEventButton *evbtn;
bool capturedMouse;
bool dragWasDropped;
int lastKey;
int rectangularSelectionModifier;
GtkWidgetClass *parentClass;
static GdkAtom atomClipboard;
static GdkAtom atomUTF8;
static GdkAtom atomString;
static GdkAtom atomUriList;
static GdkAtom atomDROPFILES_DND;
GdkAtom atomSought;
#if PLAT_GTK_WIN32
CLIPFORMAT cfColumnSelect;
#endif
Window wPreedit;
Window wPreeditDraw;
GtkIMContext *im_context;
PangoScript lastNonCommonScript;
// Wheel mouse support
unsigned int linesPerScroll;
GTimeVal lastWheelMouseTime;
gint lastWheelMouseDirection;
gint wheelMouseIntensity;
#if GTK_CHECK_VERSION(3,0,0)
cairo_rectangle_list_t *rgnUpdate;
#else
GdkRegion *rgnUpdate;
#endif
bool repaintFullWindow;
guint styleIdleID;
// Private so ScintillaGTK objects can not be copied
ScintillaGTK(const ScintillaGTK &);
ScintillaGTK &operator=(const ScintillaGTK &);
public:
explicit ScintillaGTK(_ScintillaObject *sci_);
virtual ~ScintillaGTK();
static void ClassInit(OBJECT_CLASS* object_class, GtkWidgetClass *widget_class, GtkContainerClass *container_class);
private:
virtual void Initialise();
virtual void Finalise();
virtual bool AbandonPaint();
virtual void DisplayCursor(Window::Cursor c);
virtual bool DragThreshold(Point ptStart, Point ptNow);
virtual void StartDrag();
int TargetAsUTF8(char *text);
int EncodedFromUTF8(char *utf8, char *encoded) const;
virtual bool ValidCodePage(int codePage) const;
public: // Public for scintilla_send_message
virtual sptr_t WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
private:
virtual sptr_t DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam);
struct TimeThunk {
TickReason reason;
ScintillaGTK *scintilla;
guint timer;
TimeThunk() : reason(tickCaret), scintilla(NULL), timer(0) {}
};
TimeThunk timers[tickDwell+1];
virtual bool FineTickerAvailable();
virtual bool FineTickerRunning(TickReason reason);
virtual void FineTickerStart(TickReason reason, int millis, int tolerance);
virtual void FineTickerCancel(TickReason reason);
virtual bool SetIdle(bool on);
virtual void SetMouseCapture(bool on);
virtual bool HaveMouseCapture();
virtual bool PaintContains(PRectangle rc);
void FullPaint();
virtual PRectangle GetClientRectangle() const;
virtual void ScrollText(int linesToMove);
virtual void SetVerticalScrollPos();
virtual void SetHorizontalScrollPos();
virtual bool ModifyScrollBars(int nMax, int nPage);
void ReconfigureScrollBars();
virtual void NotifyChange();
virtual void NotifyFocus(bool focus);
virtual void NotifyParent(SCNotification scn);
void NotifyKey(int key, int modifiers);
void NotifyURIDropped(const char *list);
const char *CharacterSetID() const;
virtual CaseFolder *CaseFolderForEncoding();
virtual std::string CaseMapString(const std::string &s, int caseMapping);
virtual int KeyDefault(int key, int modifiers);
virtual void CopyToClipboard(const SelectionText &selectedText);
virtual void Copy();
virtual void Paste();
virtual void CreateCallTipWindow(PRectangle rc);
virtual void AddToPopUp(const char *label, int cmd = 0, bool enabled = true);
bool OwnPrimarySelection();
virtual void ClaimSelection();
void GetGtkSelectionText(GtkSelectionData *selectionData, SelectionText &selText);
void ReceivedSelection(GtkSelectionData *selection_data);
void ReceivedDrop(GtkSelectionData *selection_data);
static void GetSelection(GtkSelectionData *selection_data, guint info, SelectionText *selected);
void StoreOnClipboard(SelectionText *clipText);
static void ClipboardGetSelection(GtkClipboard* clip, GtkSelectionData *selection_data, guint info, void *data);
static void ClipboardClearSelection(GtkClipboard* clip, void *data);
void UnclaimSelection(GdkEventSelection *selection_event);
void Resize(int width, int height);
// Callback functions
void RealizeThis(GtkWidget *widget);
static void Realize(GtkWidget *widget);
void UnRealizeThis(GtkWidget *widget);
static void UnRealize(GtkWidget *widget);
void MapThis();
static void Map(GtkWidget *widget);
void UnMapThis();
static void UnMap(GtkWidget *widget);
gint FocusInThis(GtkWidget *widget);
static gint FocusIn(GtkWidget *widget, GdkEventFocus *event);
gint FocusOutThis(GtkWidget *widget);
static gint FocusOut(GtkWidget *widget, GdkEventFocus *event);
static void SizeRequest(GtkWidget *widget, GtkRequisition *requisition);
#if GTK_CHECK_VERSION(3,0,0)
static void GetPreferredWidth(GtkWidget *widget, gint *minimalWidth, gint *naturalWidth);
static void GetPreferredHeight(GtkWidget *widget, gint *minimalHeight, gint *naturalHeight);
#endif
static void SizeAllocate(GtkWidget *widget, GtkAllocation *allocation);
#if GTK_CHECK_VERSION(3,0,0)
gboolean DrawTextThis(cairo_t *cr);
static gboolean DrawText(GtkWidget *widget, cairo_t *cr, ScintillaGTK *sciThis);
gboolean DrawThis(cairo_t *cr);
static gboolean DrawMain(GtkWidget *widget, cairo_t *cr);
#else
gboolean ExposeTextThis(GtkWidget *widget, GdkEventExpose *ose);
static gboolean ExposeText(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis);
gboolean Expose(GtkWidget *widget, GdkEventExpose *ose);
static gboolean ExposeMain(GtkWidget *widget, GdkEventExpose *ose);
#endif
void ForAll(GtkCallback callback, gpointer callback_data);
static void MainForAll(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data);
static void ScrollSignal(GtkAdjustment *adj, ScintillaGTK *sciThis);
static void ScrollHSignal(GtkAdjustment *adj, ScintillaGTK *sciThis);
gint PressThis(GdkEventButton *event);
static gint Press(GtkWidget *widget, GdkEventButton *event);
static gint MouseRelease(GtkWidget *widget, GdkEventButton *event);
static gint ScrollEvent(GtkWidget *widget, GdkEventScroll *event);
static gint Motion(GtkWidget *widget, GdkEventMotion *event);
gboolean KeyThis(GdkEventKey *event);
static gboolean KeyPress(GtkWidget *widget, GdkEventKey *event);
static gboolean KeyRelease(GtkWidget *widget, GdkEventKey *event);
#if GTK_CHECK_VERSION(3,0,0)
gboolean DrawPreeditThis(GtkWidget *widget, cairo_t *cr);
static gboolean DrawPreedit(GtkWidget *widget, cairo_t *cr, ScintillaGTK *sciThis);
#else
gboolean ExposePreeditThis(GtkWidget *widget, GdkEventExpose *ose);
static gboolean ExposePreedit(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis);
#endif
bool KoreanIME();
void CommitThis(char *str);
static void Commit(GtkIMContext *context, char *str, ScintillaGTK *sciThis);
void PreeditChangedInlineThis();
void PreeditChangedWindowedThis();
static void PreeditChanged(GtkIMContext *context, ScintillaGTK *sciThis);
void MoveImeCarets(int pos);
void DrawImeIndicator(int indicator, int len);
void SetCandidateWindowPos();
static void StyleSetText(GtkWidget *widget, GtkStyle *previous, void*);
static void RealizeText(GtkWidget *widget, void*);
static void Dispose(GObject *object);
static void Destroy(GObject *object);
static void SelectionReceived(GtkWidget *widget, GtkSelectionData *selection_data,
guint time);
static void ClipboardReceived(GtkClipboard *clipboard, GtkSelectionData *selection_data,
gpointer data);
static void SelectionGet(GtkWidget *widget, GtkSelectionData *selection_data,
guint info, guint time);
static gint SelectionClear(GtkWidget *widget, GdkEventSelection *selection_event);
gboolean DragMotionThis(GdkDragContext *context, gint x, gint y, guint dragtime);
static gboolean DragMotion(GtkWidget *widget, GdkDragContext *context,
gint x, gint y, guint dragtime);
static void DragLeave(GtkWidget *widget, GdkDragContext *context,
guint time);
static void DragEnd(GtkWidget *widget, GdkDragContext *context);
static gboolean Drop(GtkWidget *widget, GdkDragContext *context,
gint x, gint y, guint time);
static void DragDataReceived(GtkWidget *widget, GdkDragContext *context,
gint x, gint y, GtkSelectionData *selection_data, guint info, guint time);
static void DragDataGet(GtkWidget *widget, GdkDragContext *context,
GtkSelectionData *selection_data, guint info, guint time);
static gboolean TimeOut(gpointer ptt);
static gboolean IdleCallback(gpointer pSci);
static gboolean StyleIdle(gpointer pSci);
virtual void IdleWork();
virtual void QueueIdleWork(WorkNeeded::workItems items, int upTo);
static void PopUpCB(GtkMenuItem *menuItem, ScintillaGTK *sciThis);
#if GTK_CHECK_VERSION(3,0,0)
static gboolean DrawCT(GtkWidget *widget, cairo_t *cr, CallTip *ctip);
#else
static gboolean ExposeCT(GtkWidget *widget, GdkEventExpose *ose, CallTip *ct);
#endif
static gboolean PressCT(GtkWidget *widget, GdkEventButton *event, ScintillaGTK *sciThis);
static sptr_t DirectFunction(sptr_t ptr,
unsigned int iMessage, uptr_t wParam, sptr_t lParam);
};
enum {
COMMAND_SIGNAL,
NOTIFY_SIGNAL,
LAST_SIGNAL
};
static gint scintilla_signals[LAST_SIGNAL] = { 0 };
enum {
TARGET_STRING,
TARGET_TEXT,
TARGET_COMPOUND_TEXT,
TARGET_UTF8_STRING,
TARGET_URI
};
GdkAtom ScintillaGTK::atomClipboard = 0;
GdkAtom ScintillaGTK::atomUTF8 = 0;
GdkAtom ScintillaGTK::atomString = 0;
GdkAtom ScintillaGTK::atomUriList = 0;
GdkAtom ScintillaGTK::atomDROPFILES_DND = 0;
static const GtkTargetEntry clipboardCopyTargets[] = {
{ (gchar *) "UTF8_STRING", 0, TARGET_UTF8_STRING },
{ (gchar *) "STRING", 0, TARGET_STRING },
};
static const gint nClipboardCopyTargets = ELEMENTS(clipboardCopyTargets);
static const GtkTargetEntry clipboardPasteTargets[] = {
{ (gchar *) "text/uri-list", 0, TARGET_URI },
{ (gchar *) "UTF8_STRING", 0, TARGET_UTF8_STRING },
{ (gchar *) "STRING", 0, TARGET_STRING },
};
static const gint nClipboardPasteTargets = ELEMENTS(clipboardPasteTargets);
static GtkWidget *PWidget(Window &w) {
return static_cast<GtkWidget *>(w.GetID());
}
static ScintillaGTK *ScintillaFromWidget(GtkWidget *widget) {
ScintillaObject *scio = SCINTILLA(widget);
return static_cast<ScintillaGTK *>(scio->pscin);
}
ScintillaGTK::ScintillaGTK(_ScintillaObject *sci_) :
adjustmentv(0), adjustmenth(0),
verticalScrollBarWidth(30), horizontalScrollBarHeight(30),
evbtn(0), capturedMouse(false), dragWasDropped(false),
lastKey(0), rectangularSelectionModifier(SCMOD_CTRL), parentClass(0),
im_context(NULL), lastNonCommonScript(PANGO_SCRIPT_INVALID_CODE),
lastWheelMouseDirection(0),
wheelMouseIntensity(0),
rgnUpdate(0),
repaintFullWindow(false),
styleIdleID(0) {
sci = sci_;
wMain = GTK_WIDGET(sci);
#if PLAT_GTK_WIN32
rectangularSelectionModifier = SCMOD_ALT;
#else
rectangularSelectionModifier = SCMOD_CTRL;
#endif
#if PLAT_GTK_WIN32
// There does not seem to be a real standard for indicating that the clipboard
// contains a rectangular selection, so copy Developer Studio.
cfColumnSelect = static_cast<CLIPFORMAT>(
::RegisterClipboardFormat("MSDEVColumnSelect"));
// Get intellimouse parameters when running on win32; otherwise use
// reasonable default
#ifndef SPI_GETWHEELSCROLLLINES
#define SPI_GETWHEELSCROLLLINES 104
#endif
::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerScroll, 0);
#else
linesPerScroll = 4;
#endif
lastWheelMouseTime.tv_sec = 0;
lastWheelMouseTime.tv_usec = 0;
Initialise();
}
ScintillaGTK::~ScintillaGTK() {
if (styleIdleID) {
g_source_remove(styleIdleID);
styleIdleID = 0;
}
if (evbtn) {
gdk_event_free(reinterpret_cast<GdkEvent *>(evbtn));
evbtn = 0;
}
wPreedit.Destroy();
}
static void UnRefCursor(GdkCursor *cursor) {
#if GTK_CHECK_VERSION(3,0,0)
g_object_unref(cursor);
#else
gdk_cursor_unref(cursor);
#endif
}
void ScintillaGTK::RealizeThis(GtkWidget *widget) {
//Platform::DebugPrintf("ScintillaGTK::realize this\n");
#if GTK_CHECK_VERSION(2,20,0)
gtk_widget_set_realized(widget, TRUE);
#else
GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
#endif
GdkWindowAttr attrs;
attrs.window_type = GDK_WINDOW_CHILD;
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
attrs.x = allocation.x;
attrs.y = allocation.y;
attrs.width = allocation.width;
attrs.height = allocation.height;
attrs.wclass = GDK_INPUT_OUTPUT;
attrs.visual = gtk_widget_get_visual(widget);
#if !GTK_CHECK_VERSION(3,0,0)
attrs.colormap = gtk_widget_get_colormap(widget);
#endif
attrs.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;
GdkDisplay *pdisplay = gtk_widget_get_display(widget);
GdkCursor *cursor = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
attrs.cursor = cursor;
#if GTK_CHECK_VERSION(3,0,0)
gtk_widget_set_window(widget, gdk_window_new(gtk_widget_get_parent_window(widget), &attrs,
GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_CURSOR));
#if GTK_CHECK_VERSION(3,8,0)
gtk_widget_register_window(widget, gtk_widget_get_window(widget));
#else
gdk_window_set_user_data(gtk_widget_get_window(widget), widget);
#endif
#if !GTK_CHECK_VERSION(3,18,0)
gtk_style_context_set_background(gtk_widget_get_style_context(widget),
gtk_widget_get_window(widget));
#endif
gdk_window_show(gtk_widget_get_window(widget));
UnRefCursor(cursor);
#else
widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attrs,
GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR);
gdk_window_set_user_data(widget->window, widget);
widget->style = gtk_style_attach(widget->style, widget->window);
gdk_window_set_background(widget->window, &widget->style->bg[GTK_STATE_NORMAL]);
gdk_window_show(widget->window);
UnRefCursor(cursor);
#endif
gtk_widget_realize(PWidget(wPreedit));
gtk_widget_realize(PWidget(wPreeditDraw));
im_context = gtk_im_multicontext_new();
g_signal_connect(G_OBJECT(im_context), "commit",
G_CALLBACK(Commit), this);
g_signal_connect(G_OBJECT(im_context), "preedit_changed",
G_CALLBACK(PreeditChanged), this);
gtk_im_context_set_client_window(im_context, WindowFromWidget(widget));
GtkWidget *widtxt = PWidget(wText); // // No code inside the G_OBJECT macro
g_signal_connect_after(G_OBJECT(widtxt), "style_set",
G_CALLBACK(ScintillaGTK::StyleSetText), NULL);
g_signal_connect_after(G_OBJECT(widtxt), "realize",
G_CALLBACK(ScintillaGTK::RealizeText), NULL);
gtk_widget_realize(widtxt);
gtk_widget_realize(PWidget(scrollbarv));
gtk_widget_realize(PWidget(scrollbarh));
cursor = gdk_cursor_new_for_display(pdisplay, GDK_XTERM);
gdk_window_set_cursor(PWindow(wText), cursor);
UnRefCursor(cursor);
cursor = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
gdk_window_set_cursor(PWindow(scrollbarv), cursor);
UnRefCursor(cursor);
cursor = gdk_cursor_new_for_display(pdisplay, GDK_LEFT_PTR);
gdk_window_set_cursor(PWindow(scrollbarh), cursor);
UnRefCursor(cursor);
gtk_selection_add_targets(widget, GDK_SELECTION_PRIMARY,
clipboardCopyTargets, nClipboardCopyTargets);
}
void ScintillaGTK::Realize(GtkWidget *widget) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
sciThis->RealizeThis(widget);
}
void ScintillaGTK::UnRealizeThis(GtkWidget *widget) {
try {
gtk_selection_clear_targets(widget, GDK_SELECTION_PRIMARY);
if (IS_WIDGET_MAPPED(widget)) {
gtk_widget_unmap(widget);
}
#if GTK_CHECK_VERSION(2,20,0)
gtk_widget_set_realized(widget, FALSE);
#else
GTK_WIDGET_UNSET_FLAGS(widget, GTK_REALIZED);
#endif
gtk_widget_unrealize(PWidget(wText));
gtk_widget_unrealize(PWidget(scrollbarv));
gtk_widget_unrealize(PWidget(scrollbarh));
gtk_widget_unrealize(PWidget(wPreedit));
gtk_widget_unrealize(PWidget(wPreeditDraw));
g_object_unref(im_context);
im_context = NULL;
if (GTK_WIDGET_CLASS(parentClass)->unrealize)
GTK_WIDGET_CLASS(parentClass)->unrealize(widget);
Finalise();
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::UnRealize(GtkWidget *widget) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
sciThis->UnRealizeThis(widget);
}
static void MapWidget(GtkWidget *widget) {
if (widget &&
gtk_widget_get_visible(GTK_WIDGET(widget)) &&
!IS_WIDGET_MAPPED(widget)) {
gtk_widget_map(widget);
}
}
void ScintillaGTK::MapThis() {
try {
//Platform::DebugPrintf("ScintillaGTK::map this\n");
#if GTK_CHECK_VERSION(2,20,0)
gtk_widget_set_mapped(PWidget(wMain), TRUE);
#else
GTK_WIDGET_SET_FLAGS(PWidget(wMain), GTK_MAPPED);
#endif
MapWidget(PWidget(wText));
MapWidget(PWidget(scrollbarh));
MapWidget(PWidget(scrollbarv));
wMain.SetCursor(Window::cursorArrow);
scrollbarv.SetCursor(Window::cursorArrow);
scrollbarh.SetCursor(Window::cursorArrow);
ChangeSize();
gdk_window_show(PWindow(wMain));
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::Map(GtkWidget *widget) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
sciThis->MapThis();
}
void ScintillaGTK::UnMapThis() {
try {
//Platform::DebugPrintf("ScintillaGTK::unmap this\n");
#if GTK_CHECK_VERSION(2,20,0)
gtk_widget_set_mapped(PWidget(wMain), FALSE);
#else
GTK_WIDGET_UNSET_FLAGS(PWidget(wMain), GTK_MAPPED);
#endif
DropGraphics(false);
gdk_window_hide(PWindow(wMain));
gtk_widget_unmap(PWidget(wText));
gtk_widget_unmap(PWidget(scrollbarh));
gtk_widget_unmap(PWidget(scrollbarv));
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::UnMap(GtkWidget *widget) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
sciThis->UnMapThis();
}
void ScintillaGTK::ForAll(GtkCallback callback, gpointer callback_data) {
try {
(*callback) (PWidget(wText), callback_data);
if (PWidget(scrollbarv))
(*callback) (PWidget(scrollbarv), callback_data);
if (PWidget(scrollbarh))
(*callback) (PWidget(scrollbarh), callback_data);
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::MainForAll(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) {
ScintillaGTK *sciThis = ScintillaFromWidget((GtkWidget *)container);
if (callback != NULL && include_internals) {
sciThis->ForAll(callback, callback_data);
}
}
namespace {
class PreEditString {
public:
gchar *str;
gint cursor_pos;
PangoAttrList *attrs;
gboolean validUTF8;
glong uniStrLen;
gunichar *uniStr;
PangoScript pscript;
explicit PreEditString(GtkIMContext *im_context) {
gtk_im_context_get_preedit_string(im_context, &str, &attrs, &cursor_pos);
validUTF8 = g_utf8_validate(str, strlen(str), NULL);
uniStr = g_utf8_to_ucs4_fast(str, strlen(str), &uniStrLen);
pscript = pango_script_for_unichar(uniStr[0]);
}
~PreEditString() {
g_free(str);
g_free(uniStr);
pango_attr_list_unref(attrs);
}
};
}
gint ScintillaGTK::FocusInThis(GtkWidget *widget) {
try {
SetFocusState(true);
if (im_context != NULL) {
PreEditString pes(im_context);
if (PWidget(wPreedit) != NULL) {
if (strlen(pes.str) > 0) {
gtk_widget_show(PWidget(wPreedit));
} else {
gtk_widget_hide(PWidget(wPreedit));
}
}
gtk_im_context_focus_in(im_context);
}
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
gint ScintillaGTK::FocusIn(GtkWidget *widget, GdkEventFocus * /*event*/) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
return sciThis->FocusInThis(widget);
}
gint ScintillaGTK::FocusOutThis(GtkWidget *widget) {
try {
SetFocusState(false);
if (PWidget(wPreedit) != NULL)
gtk_widget_hide(PWidget(wPreedit));
if (im_context != NULL)
gtk_im_context_focus_out(im_context);
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
gint ScintillaGTK::FocusOut(GtkWidget *widget, GdkEventFocus * /*event*/) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
return sciThis->FocusOutThis(widget);
}
void ScintillaGTK::SizeRequest(GtkWidget *widget, GtkRequisition *requisition) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
requisition->width = 1;
requisition->height = 1;
GtkRequisition child_requisition;
#if GTK_CHECK_VERSION(3,0,0)
gtk_widget_get_preferred_size(PWidget(sciThis->scrollbarh), NULL, &child_requisition);
gtk_widget_get_preferred_size(PWidget(sciThis->scrollbarv), NULL, &child_requisition);
#else
gtk_widget_size_request(PWidget(sciThis->scrollbarh), &child_requisition);
gtk_widget_size_request(PWidget(sciThis->scrollbarv), &child_requisition);
#endif
}
#if GTK_CHECK_VERSION(3,0,0)
void ScintillaGTK::GetPreferredWidth(GtkWidget *widget, gint *minimalWidth, gint *naturalWidth) {
GtkRequisition requisition;
SizeRequest(widget, &requisition);
*minimalWidth = *naturalWidth = requisition.width;
}
void ScintillaGTK::GetPreferredHeight(GtkWidget *widget, gint *minimalHeight, gint *naturalHeight) {
GtkRequisition requisition;
SizeRequest(widget, &requisition);
*minimalHeight = *naturalHeight = requisition.height;
}
#endif
void ScintillaGTK::SizeAllocate(GtkWidget *widget, GtkAllocation *allocation) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
gtk_widget_set_allocation(widget, allocation);
if (IS_WIDGET_REALIZED(widget))
gdk_window_move_resize(WindowFromWidget(widget),
allocation->x,
allocation->y,
allocation->width,
allocation->height);
sciThis->Resize(allocation->width, allocation->height);
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::Initialise() {
//Platform::DebugPrintf("ScintillaGTK::Initialise\n");
parentClass = reinterpret_cast<GtkWidgetClass *>(
g_type_class_ref(gtk_container_get_type()));
gtk_widget_set_can_focus(PWidget(wMain), TRUE);
gtk_widget_set_sensitive(PWidget(wMain), TRUE);
gtk_widget_set_events(PWidget(wMain),
GDK_EXPOSURE_MASK
| GDK_SCROLL_MASK
| GDK_STRUCTURE_MASK
| GDK_KEY_PRESS_MASK
| GDK_KEY_RELEASE_MASK
| GDK_FOCUS_CHANGE_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK);
wText = gtk_drawing_area_new();
gtk_widget_set_parent(PWidget(wText), PWidget(wMain));
GtkWidget *widtxt = PWidget(wText); // No code inside the G_OBJECT macro
gtk_widget_show(widtxt);
#if GTK_CHECK_VERSION(3,0,0)
g_signal_connect(G_OBJECT(widtxt), "draw",
G_CALLBACK(ScintillaGTK::DrawText), this);
#else
g_signal_connect(G_OBJECT(widtxt), "expose_event",
G_CALLBACK(ScintillaGTK::ExposeText), this);
#endif
#if GTK_CHECK_VERSION(3,0,0)
// we need a runtime check because we don't want double buffering when
// running on >= 3.9.2
if (gtk_check_version(3,9,2) != NULL /* on < 3.9.2 */)
#endif
{
#if !GTK_CHECK_VERSION(3,14,0)
// Avoid background drawing flash/missing redraws
gtk_widget_set_double_buffered(widtxt, FALSE);
#endif
}
gtk_widget_set_events(widtxt, GDK_EXPOSURE_MASK);
gtk_widget_set_size_request(widtxt, 100, 100);
adjustmentv = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 201.0, 1.0, 20.0, 20.0));
#if GTK_CHECK_VERSION(3,0,0)
scrollbarv = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, GTK_ADJUSTMENT(adjustmentv));
#else
scrollbarv = gtk_vscrollbar_new(GTK_ADJUSTMENT(adjustmentv));
#endif
gtk_widget_set_can_focus(PWidget(scrollbarv), FALSE);
g_signal_connect(G_OBJECT(adjustmentv), "value_changed",
G_CALLBACK(ScrollSignal), this);
gtk_widget_set_parent(PWidget(scrollbarv), PWidget(wMain));
gtk_widget_show(PWidget(scrollbarv));
adjustmenth = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 101.0, 1.0, 20.0, 20.0));
#if GTK_CHECK_VERSION(3,0,0)
scrollbarh = gtk_scrollbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_ADJUSTMENT(adjustmenth));
#else
scrollbarh = gtk_hscrollbar_new(GTK_ADJUSTMENT(adjustmenth));
#endif
gtk_widget_set_can_focus(PWidget(scrollbarh), FALSE);
g_signal_connect(G_OBJECT(adjustmenth), "value_changed",
G_CALLBACK(ScrollHSignal), this);
gtk_widget_set_parent(PWidget(scrollbarh), PWidget(wMain));
gtk_widget_show(PWidget(scrollbarh));
gtk_widget_grab_focus(PWidget(wMain));
gtk_drag_dest_set(GTK_WIDGET(PWidget(wMain)),
GTK_DEST_DEFAULT_ALL, clipboardPasteTargets, nClipboardPasteTargets,
static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE));
/* create pre-edit window */
wPreedit = gtk_window_new(GTK_WINDOW_POPUP);
wPreeditDraw = gtk_drawing_area_new();
GtkWidget *predrw = PWidget(wPreeditDraw); // No code inside the G_OBJECT macro
#if GTK_CHECK_VERSION(3,0,0)
g_signal_connect(G_OBJECT(predrw), "draw",
G_CALLBACK(DrawPreedit), this);
#else
g_signal_connect(G_OBJECT(predrw), "expose_event",
G_CALLBACK(ExposePreedit), this);
#endif
gtk_container_add(GTK_CONTAINER(PWidget(wPreedit)), predrw);
gtk_widget_show(predrw);
// Set caret period based on GTK settings
gboolean blinkOn = false;
if (g_object_class_find_property(G_OBJECT_GET_CLASS(
G_OBJECT(gtk_settings_get_default())), "gtk-cursor-blink")) {
g_object_get(G_OBJECT(
gtk_settings_get_default()), "gtk-cursor-blink", &blinkOn, NULL);
}
if (blinkOn &&
g_object_class_find_property(G_OBJECT_GET_CLASS(
G_OBJECT(gtk_settings_get_default())), "gtk-cursor-blink-time")) {
gint value;
g_object_get(G_OBJECT(
gtk_settings_get_default()), "gtk-cursor-blink-time", &value, NULL);
caret.period = gint(value / 1.75);
} else {
caret.period = 0;
}
for (TickReason tr = tickCaret; tr <= tickDwell; tr = static_cast<TickReason>(tr + 1)) {
timers[tr].reason = tr;
timers[tr].scintilla = this;
}
vs.indicators[SC_INDICATOR_UNKNOWN] = Indicator(INDIC_HIDDEN, ColourDesired(0, 0, 0xff));
vs.indicators[SC_INDICATOR_INPUT] = Indicator(INDIC_DOTS, ColourDesired(0, 0, 0xff));
vs.indicators[SC_INDICATOR_CONVERTED] = Indicator(INDIC_COMPOSITIONTHICK, ColourDesired(0, 0, 0xff));
vs.indicators[SC_INDICATOR_TARGET] = Indicator(INDIC_STRAIGHTBOX, ColourDesired(0, 0, 0xff));
}
void ScintillaGTK::Finalise() {
for (TickReason tr = tickCaret; tr <= tickDwell; tr = static_cast<TickReason>(tr + 1)) {
FineTickerCancel(tr);
}
ScintillaBase::Finalise();
}
bool ScintillaGTK::AbandonPaint() {
if ((paintState == painting) && !paintingAllText) {
repaintFullWindow = true;
}
return false;
}
void ScintillaGTK::DisplayCursor(Window::Cursor c) {
if (cursorMode == SC_CURSORNORMAL)
wText.SetCursor(c);
else
wText.SetCursor(static_cast<Window::Cursor>(cursorMode));
}
bool ScintillaGTK::DragThreshold(Point ptStart, Point ptNow) {
return gtk_drag_check_threshold(GTK_WIDGET(PWidget(wMain)),
ptStart.x, ptStart.y, ptNow.x, ptNow.y);
}
void ScintillaGTK::StartDrag() {
PLATFORM_ASSERT(evbtn != 0);
dragWasDropped = false;
inDragDrop = ddDragging;
GtkTargetList *tl = gtk_target_list_new(clipboardCopyTargets, nClipboardCopyTargets);
#if GTK_CHECK_VERSION(3,10,0)
gtk_drag_begin_with_coordinates(GTK_WIDGET(PWidget(wMain)),
tl,
static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE),
evbtn->button,
reinterpret_cast<GdkEvent *>(evbtn),
-1, -1);
#else
gtk_drag_begin(GTK_WIDGET(PWidget(wMain)),
tl,
static_cast<GdkDragAction>(GDK_ACTION_COPY | GDK_ACTION_MOVE),
evbtn->button,
reinterpret_cast<GdkEvent *>(evbtn));
#endif
}
static std::string ConvertText(const char *s, size_t len, const char *charSetDest,
const char *charSetSource, bool transliterations, bool silent=false) {
// s is not const because of different versions of iconv disagreeing about const
std::string destForm;
Converter conv(charSetDest, charSetSource, transliterations);
if (conv) {
gsize outLeft = len*3+1;
destForm = std::string(outLeft, '\0');
// g_iconv does not actually write to its input argument so safe to cast away const
char *pin = const_cast<char *>(s);
gsize inLeft = len;
char *putf = &destForm[0];
char *pout = putf;
gsize conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
if (conversions == sizeFailure) {
if (!silent) {
if (len == 1)
fprintf(stderr, "iconv %s->%s failed for %0x '%s'\n",
charSetSource, charSetDest, (unsigned char)(*s), s);
else
fprintf(stderr, "iconv %s->%s failed for %s\n",
charSetSource, charSetDest, s);
}
destForm = std::string();
} else {
destForm.resize(pout - putf);
}
} else {
fprintf(stderr, "Can not iconv %s %s\n", charSetDest, charSetSource);
}
return destForm;
}
// Returns the target converted to UTF8.
// Return the length in bytes.
int ScintillaGTK::TargetAsUTF8(char *text) {
int targetLength = targetEnd - targetStart;
if (IsUnicodeMode()) {
if (text) {
pdoc->GetCharRange(text, targetStart, targetLength);
}
} else {
// Need to convert
const char *charSetBuffer = CharacterSetID();
if (*charSetBuffer) {
std::string s = RangeText(targetStart, targetEnd);
std::string tmputf = ConvertText(&s[0], targetLength, "UTF-8", charSetBuffer, false);
if (text) {
memcpy(text, tmputf.c_str(), tmputf.length());
}
return tmputf.length();
} else {
if (text) {
pdoc->GetCharRange(text, targetStart, targetLength);
}
}
}
return targetLength;
}
// Translates a nul terminated UTF8 string into the document encoding.
// Return the length of the result in bytes.
int ScintillaGTK::EncodedFromUTF8(char *utf8, char *encoded) const {
int inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8);
if (IsUnicodeMode()) {
if (encoded) {
memcpy(encoded, utf8, inputLength);
}
return inputLength;
} else {
// Need to convert
const char *charSetBuffer = CharacterSetID();
if (*charSetBuffer) {
std::string tmpEncoded = ConvertText(utf8, inputLength, charSetBuffer, "UTF-8", true);
if (encoded) {
memcpy(encoded, tmpEncoded.c_str(), tmpEncoded.length());
}
return tmpEncoded.length();
} else {
if (encoded) {
memcpy(encoded, utf8, inputLength);
}
return inputLength;
}
}
// Fail
return 0;
}
bool ScintillaGTK::ValidCodePage(int codePage) const {
return codePage == 0
|| codePage == SC_CP_UTF8
|| codePage == 932
|| codePage == 936
|| codePage == 949
|| codePage == 950
|| codePage == 1361;
}
sptr_t ScintillaGTK::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
try {
switch (iMessage) {
case SCI_GRABFOCUS:
gtk_widget_grab_focus(PWidget(wMain));
break;
case SCI_GETDIRECTFUNCTION:
return reinterpret_cast<sptr_t>(DirectFunction);
case SCI_GETDIRECTPOINTER:
return reinterpret_cast<sptr_t>(this);
#ifdef SCI_LEXER
case SCI_LOADLEXERLIBRARY:
LexerManager::GetInstance()->Load(reinterpret_cast<const char*>(lParam));
break;
#endif
case SCI_TARGETASUTF8:
return TargetAsUTF8(reinterpret_cast<char*>(lParam));
case SCI_ENCODEDFROMUTF8:
return EncodedFromUTF8(reinterpret_cast<char*>(wParam),
reinterpret_cast<char*>(lParam));
case SCI_SETRECTANGULARSELECTIONMODIFIER:
rectangularSelectionModifier = wParam;
break;
case SCI_GETRECTANGULARSELECTIONMODIFIER:
return rectangularSelectionModifier;
default:
return ScintillaBase::WndProc(iMessage, wParam, lParam);
}
} catch (std::bad_alloc&) {
errorStatus = SC_STATUS_BADALLOC;
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return 0l;
}
sptr_t ScintillaGTK::DefWndProc(unsigned int, uptr_t, sptr_t) {
return 0;
}
/**
* Report that this Editor subclass has a working implementation of FineTickerStart.
*/
bool ScintillaGTK::FineTickerAvailable() {
return true;
}
bool ScintillaGTK::FineTickerRunning(TickReason reason) {
return timers[reason].timer != 0;
}
void ScintillaGTK::FineTickerStart(TickReason reason, int millis, int /* tolerance */) {
FineTickerCancel(reason);
timers[reason].timer = gdk_threads_add_timeout(millis, TimeOut, &timers[reason]);
}
void ScintillaGTK::FineTickerCancel(TickReason reason) {
if (timers[reason].timer) {
g_source_remove(timers[reason].timer);
timers[reason].timer = 0;
}
}
bool ScintillaGTK::SetIdle(bool on) {
if (on) {
// Start idler, if it's not running.
if (!idler.state) {
idler.state = true;
idler.idlerID = reinterpret_cast<IdlerID>(
gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE, IdleCallback, this, NULL));
}
} else {
// Stop idler, if it's running
if (idler.state) {
idler.state = false;
g_source_remove(GPOINTER_TO_UINT(idler.idlerID));
}
}
return true;
}
void ScintillaGTK::SetMouseCapture(bool on) {
if (mouseDownCaptures) {
if (on) {
gtk_grab_add(GTK_WIDGET(PWidget(wMain)));
} else {
gtk_grab_remove(GTK_WIDGET(PWidget(wMain)));
}
}
capturedMouse = on;
}
bool ScintillaGTK::HaveMouseCapture() {
return capturedMouse;
}
#if GTK_CHECK_VERSION(3,0,0)
// Is crcTest completely in crcContainer?
static bool CRectContains(const cairo_rectangle_t &crcContainer, const cairo_rectangle_t &crcTest) {
return
(crcTest.x >= crcContainer.x) && ((crcTest.x + crcTest.width) <= (crcContainer.x + crcContainer.width)) &&
(crcTest.y >= crcContainer.y) && ((crcTest.y + crcTest.height) <= (crcContainer.y + crcContainer.height));
}
// Is crcTest completely in crcListContainer?
// May incorrectly return false if complex shape
static bool CRectListContains(const cairo_rectangle_list_t *crcListContainer, const cairo_rectangle_t &crcTest) {
for (int r=0; r<crcListContainer->num_rectangles; r++) {
if (CRectContains(crcListContainer->rectangles[r], crcTest))
return true;
}
return false;
}
#endif
bool ScintillaGTK::PaintContains(PRectangle rc) {
// This allows optimization when a rectangle is completely in the update region.
// It is OK to return false when too difficult to determine as that just performs extra drawing
bool contains = true;
if (paintState == painting) {
if (!rcPaint.Contains(rc)) {
contains = false;
} else if (rgnUpdate) {
#if GTK_CHECK_VERSION(3,0,0)
cairo_rectangle_t grc = {rc.left, rc.top,
rc.right - rc.left, rc.bottom - rc.top};
contains = CRectListContains(rgnUpdate, grc);
#else
GdkRectangle grc = {static_cast<gint>(rc.left), static_cast<gint>(rc.top),
static_cast<gint>(rc.right - rc.left), static_cast<gint>(rc.bottom - rc.top)};
if (gdk_region_rect_in(rgnUpdate, &grc) != GDK_OVERLAP_RECTANGLE_IN) {
contains = false;
}
#endif
}
}
return contains;
}
// Redraw all of text area. This paint will not be abandoned.
void ScintillaGTK::FullPaint() {
wText.InvalidateAll();
}
PRectangle ScintillaGTK::GetClientRectangle() const {
Window win = wMain;
PRectangle rc = win.GetClientPosition();
if (verticalScrollBarVisible)
rc.right -= verticalScrollBarWidth;
if (horizontalScrollBarVisible && !Wrapping())
rc.bottom -= horizontalScrollBarHeight;
// Move to origin
rc.right -= rc.left;
rc.bottom -= rc.top;
if (rc.bottom < 0)
rc.bottom = 0;
if (rc.right < 0)
rc.right = 0;
rc.left = 0;
rc.top = 0;
return rc;
}
void ScintillaGTK::ScrollText(int linesToMove) {
int diff = vs.lineHeight * -linesToMove;
//Platform::DebugPrintf("ScintillaGTK::ScrollText %d %d %0d,%0d %0d,%0d\n", linesToMove, diff,
// rc.left, rc.top, rc.right, rc.bottom);
GtkWidget *wi = PWidget(wText);
NotifyUpdateUI();
if (IS_WIDGET_REALIZED(wi)) {
gdk_window_scroll(WindowFromWidget(wi), 0, -diff);
gdk_window_process_updates(WindowFromWidget(wi), FALSE);
}
}
void ScintillaGTK::SetVerticalScrollPos() {
DwellEnd(true);
gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmentv), topLine);
}
void ScintillaGTK::SetHorizontalScrollPos() {
DwellEnd(true);
gtk_adjustment_set_value(GTK_ADJUSTMENT(adjustmenth), xOffset);
}
bool ScintillaGTK::ModifyScrollBars(int nMax, int nPage) {
bool modified = false;
int pageScroll = LinesToScroll();
if (gtk_adjustment_get_upper(adjustmentv) != (nMax + 1) ||
gtk_adjustment_get_page_size(adjustmentv) != nPage ||
gtk_adjustment_get_page_increment(adjustmentv) != pageScroll) {
gtk_adjustment_set_upper(adjustmentv, nMax + 1);
gtk_adjustment_set_page_size(adjustmentv, nPage);
gtk_adjustment_set_page_increment(adjustmentv, pageScroll);
#if !GTK_CHECK_VERSION(3,18,0)
gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmentv));
#endif
modified = true;
}
PRectangle rcText = GetTextRectangle();
int horizEndPreferred = scrollWidth;
if (horizEndPreferred < 0)
horizEndPreferred = 0;
unsigned int pageWidth = rcText.Width();
unsigned int pageIncrement = pageWidth / 3;
unsigned int charWidth = vs.styles[STYLE_DEFAULT].aveCharWidth;
if (gtk_adjustment_get_upper(adjustmenth) != horizEndPreferred ||
gtk_adjustment_get_page_size(adjustmenth) != pageWidth ||
gtk_adjustment_get_page_increment(adjustmenth) != pageIncrement ||
gtk_adjustment_get_step_increment(adjustmenth) != charWidth) {
gtk_adjustment_set_upper(adjustmenth, horizEndPreferred);
gtk_adjustment_set_page_size(adjustmenth, pageWidth);
gtk_adjustment_set_page_increment(adjustmenth, pageIncrement);
gtk_adjustment_set_step_increment(adjustmenth, charWidth);
#if !GTK_CHECK_VERSION(3,18,0)
gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmenth));
#endif
modified = true;
}
if (modified && (paintState == painting)) {
repaintFullWindow = true;
}
return modified;
}
void ScintillaGTK::ReconfigureScrollBars() {
PRectangle rc = wMain.GetClientPosition();
Resize(rc.Width(), rc.Height());
}
void ScintillaGTK::NotifyChange() {
g_signal_emit(G_OBJECT(sci), scintilla_signals[COMMAND_SIGNAL], 0,
Platform::LongFromTwoShorts(GetCtrlID(), SCEN_CHANGE), PWidget(wMain));
}
void ScintillaGTK::NotifyFocus(bool focus) {
g_signal_emit(G_OBJECT(sci), scintilla_signals[COMMAND_SIGNAL], 0,
Platform::LongFromTwoShorts
(GetCtrlID(), focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS), PWidget(wMain));
Editor::NotifyFocus(focus);
}
void ScintillaGTK::NotifyParent(SCNotification scn) {
scn.nmhdr.hwndFrom = PWidget(wMain);
scn.nmhdr.idFrom = GetCtrlID();
g_signal_emit(G_OBJECT(sci), scintilla_signals[NOTIFY_SIGNAL], 0,
GetCtrlID(), &scn);
}
void ScintillaGTK::NotifyKey(int key, int modifiers) {
SCNotification scn = {};
scn.nmhdr.code = SCN_KEY;
scn.ch = key;
scn.modifiers = modifiers;
NotifyParent(scn);
}
void ScintillaGTK::NotifyURIDropped(const char *list) {
SCNotification scn = {};
scn.nmhdr.code = SCN_URIDROPPED;
scn.text = list;
NotifyParent(scn);
}
const char *CharacterSetID(int characterSet);
const char *ScintillaGTK::CharacterSetID() const {
return ::CharacterSetID(vs.styles[STYLE_DEFAULT].characterSet);
}
class CaseFolderDBCS : public CaseFolderTable {
const char *charSet;
public:
explicit CaseFolderDBCS(const char *charSet_) : charSet(charSet_) {
StandardASCII();
}
virtual size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) {
if ((lenMixed == 1) && (sizeFolded > 0)) {
folded[0] = mapping[static_cast<unsigned char>(mixed[0])];
return 1;
} else if (*charSet) {
std::string sUTF8 = ConvertText(mixed, lenMixed,
"UTF-8", charSet, false);
if (!sUTF8.empty()) {
gchar *mapped = g_utf8_casefold(sUTF8.c_str(), sUTF8.length());
size_t lenMapped = strlen(mapped);
if (lenMapped < sizeFolded) {
memcpy(folded, mapped, lenMapped);
} else {
folded[0] = '\0';
lenMapped = 1;
}
g_free(mapped);
return lenMapped;
}
}
// Something failed so return a single NUL byte
folded[0] = '\0';
return 1;
}
};
CaseFolder *ScintillaGTK::CaseFolderForEncoding() {
if (pdoc->dbcsCodePage == SC_CP_UTF8) {
return new CaseFolderUnicode();
} else {
const char *charSetBuffer = CharacterSetID();
if (charSetBuffer) {
if (pdoc->dbcsCodePage == 0) {
CaseFolderTable *pcf = new CaseFolderTable();
pcf->StandardASCII();
// Only for single byte encodings
for (int i=0x80; i<0x100; i++) {
char sCharacter[2] = "A";
sCharacter[0] = i;
// Silent as some bytes have no assigned character
std::string sUTF8 = ConvertText(sCharacter, 1,
"UTF-8", charSetBuffer, false, true);
if (!sUTF8.empty()) {
gchar *mapped = g_utf8_casefold(sUTF8.c_str(), sUTF8.length());
if (mapped) {
std::string mappedBack = ConvertText(mapped, strlen(mapped),
charSetBuffer, "UTF-8", false, true);
if ((mappedBack.length() == 1) && (mappedBack[0] != sCharacter[0])) {
pcf->SetTranslation(sCharacter[0], mappedBack[0]);
}
g_free(mapped);
}
}
}
return pcf;
} else {
return new CaseFolderDBCS(charSetBuffer);
}
}
return 0;
}
}
namespace {
struct CaseMapper {
gchar *mapped; // Must be freed with g_free
CaseMapper(const std::string &sUTF8, bool toUpperCase) {
if (toUpperCase) {
mapped = g_utf8_strup(sUTF8.c_str(), sUTF8.length());
} else {
mapped = g_utf8_strdown(sUTF8.c_str(), sUTF8.length());
}
}
~CaseMapper() {
g_free(mapped);
}
};
}
std::string ScintillaGTK::CaseMapString(const std::string &s, int caseMapping) {
if ((s.size() == 0) || (caseMapping == cmSame))
return s;
if (IsUnicodeMode()) {
std::string retMapped(s.length() * maxExpansionCaseConversion, 0);
size_t lenMapped = CaseConvertString(&retMapped[0], retMapped.length(), s.c_str(), s.length(),
(caseMapping == cmUpper) ? CaseConversionUpper : CaseConversionLower);
retMapped.resize(lenMapped);
return retMapped;
}
const char *charSetBuffer = CharacterSetID();
if (!*charSetBuffer) {
CaseMapper mapper(s, caseMapping == cmUpper);
return std::string(mapper.mapped, strlen(mapper.mapped));
} else {
// Change text to UTF-8
std::string sUTF8 = ConvertText(s.c_str(), s.length(),
"UTF-8", charSetBuffer, false);
CaseMapper mapper(sUTF8, caseMapping == cmUpper);
return ConvertText(mapper.mapped, strlen(mapper.mapped), charSetBuffer, "UTF-8", false);
}
}
int ScintillaGTK::KeyDefault(int key, int modifiers) {
// Pass up to container in case it is an accelerator
NotifyKey(key, modifiers);
return 0;
}
void ScintillaGTK::CopyToClipboard(const SelectionText &selectedText) {
SelectionText *clipText = new SelectionText();
clipText->Copy(selectedText);
StoreOnClipboard(clipText);
}
void ScintillaGTK::Copy() {
if (!sel.Empty()) {
SelectionText *clipText = new SelectionText();
CopySelectionRange(clipText);
StoreOnClipboard(clipText);
#if PLAT_GTK_WIN32
if (sel.IsRectangular()) {
::OpenClipboard(NULL);
::SetClipboardData(cfColumnSelect, 0);
::CloseClipboard();
}
#endif
}
}
void ScintillaGTK::ClipboardReceived(GtkClipboard *clipboard, GtkSelectionData *selection_data, gpointer data) {
ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(data);
sciThis->ReceivedSelection(selection_data);
}
void ScintillaGTK::Paste() {
atomSought = atomUTF8;
GtkClipboard *clipBoard =
gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), atomClipboard);
if (clipBoard == NULL)
return;
gtk_clipboard_request_contents(clipBoard, atomSought, ClipboardReceived, this);
}
void ScintillaGTK::CreateCallTipWindow(PRectangle rc) {
if (!ct.wCallTip.Created()) {
ct.wCallTip = gtk_window_new(GTK_WINDOW_POPUP);
ct.wDraw = gtk_drawing_area_new();
GtkWidget *widcdrw = PWidget(ct.wDraw); // // No code inside the G_OBJECT macro
gtk_container_add(GTK_CONTAINER(PWidget(ct.wCallTip)), widcdrw);
#if GTK_CHECK_VERSION(3,0,0)
g_signal_connect(G_OBJECT(widcdrw), "draw",
G_CALLBACK(ScintillaGTK::DrawCT), &ct);
#else
g_signal_connect(G_OBJECT(widcdrw), "expose_event",
G_CALLBACK(ScintillaGTK::ExposeCT), &ct);
#endif
g_signal_connect(G_OBJECT(widcdrw), "button_press_event",
G_CALLBACK(ScintillaGTK::PressCT), static_cast<void *>(this));
gtk_widget_set_events(widcdrw,
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);
}
gtk_widget_set_size_request(PWidget(ct.wDraw), rc.Width(), rc.Height());
ct.wDraw.Show();
if (PWindow(ct.wCallTip)) {
gdk_window_resize(PWindow(ct.wCallTip), rc.Width(), rc.Height());
}
}
void ScintillaGTK::AddToPopUp(const char *label, int cmd, bool enabled) {
GtkWidget *menuItem;
if (label[0])
menuItem = gtk_menu_item_new_with_label(label);
else
menuItem = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(popup.GetID()), menuItem);
g_object_set_data(G_OBJECT(menuItem), "CmdNum", GINT_TO_POINTER(cmd));
g_signal_connect(G_OBJECT(menuItem),"activate", G_CALLBACK(PopUpCB), this);
if (cmd) {
if (menuItem)
gtk_widget_set_sensitive(menuItem, enabled);
}
}
bool ScintillaGTK::OwnPrimarySelection() {
return ((gdk_selection_owner_get(GDK_SELECTION_PRIMARY)
== PWindow(wMain)) &&
(PWindow(wMain) != NULL));
}
void ScintillaGTK::ClaimSelection() {
// X Windows has a 'primary selection' as well as the clipboard.
// Whenever the user selects some text, we become the primary selection
if (!sel.Empty() && IS_WIDGET_REALIZED(GTK_WIDGET(PWidget(wMain)))) {
primarySelection = true;
gtk_selection_owner_set(GTK_WIDGET(PWidget(wMain)),
GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME);
primary.Clear();
} else if (OwnPrimarySelection()) {
primarySelection = true;
if (primary.Empty())
gtk_selection_owner_set(NULL, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME);
} else {
primarySelection = false;
primary.Clear();
}
}
static const guchar *DataOfGSD(GtkSelectionData *sd) { return gtk_selection_data_get_data(sd); }
static gint LengthOfGSD(GtkSelectionData *sd) { return gtk_selection_data_get_length(sd); }
static GdkAtom TypeOfGSD(GtkSelectionData *sd) { return gtk_selection_data_get_data_type(sd); }
static GdkAtom SelectionOfGSD(GtkSelectionData *sd) { return gtk_selection_data_get_selection(sd); }
// Detect rectangular text, convert line ends to current mode, convert from or to UTF-8
void ScintillaGTK::GetGtkSelectionText(GtkSelectionData *selectionData, SelectionText &selText) {
const char *data = reinterpret_cast<const char *>(DataOfGSD(selectionData));
int len = LengthOfGSD(selectionData);
GdkAtom selectionTypeData = TypeOfGSD(selectionData);
// Return empty string if selection is not a string
if ((selectionTypeData != GDK_TARGET_STRING) && (selectionTypeData != atomUTF8)) {
selText.Clear();
return;
}
// Check for "\n\0" ending to string indicating that selection is rectangular
bool isRectangular;
#if PLAT_GTK_WIN32
isRectangular = ::IsClipboardFormatAvailable(cfColumnSelect) != 0;
#else
isRectangular = ((len > 2) && (data[len - 1] == 0 && data[len - 2] == '\n'));
if (isRectangular)
len--; // Forget the extra '\0'
#endif
#if PLAT_GTK_WIN32
// Win32 includes an ending '\0' byte in 'len' for clipboard text from
// external applications; ignore it.
if ((len > 0) && (data[len - 1] == '\0'))
len--;
#endif
std::string dest(data, len);
if (selectionTypeData == GDK_TARGET_STRING) {
if (IsUnicodeMode()) {
// Unknown encoding so assume in Latin1
dest = UTF8FromLatin1(dest.c_str(), dest.length());
selText.Copy(dest, SC_CP_UTF8, 0, isRectangular, false);
} else {
// Assume buffer is in same encoding as selection
selText.Copy(dest, pdoc->dbcsCodePage,
vs.styles[STYLE_DEFAULT].characterSet, isRectangular, false);
}
} else { // UTF-8
const char *charSetBuffer = CharacterSetID();
if (!IsUnicodeMode() && *charSetBuffer) {
// Convert to locale
dest = ConvertText(dest.c_str(), dest.length(), charSetBuffer, "UTF-8", true);
selText.Copy(dest, pdoc->dbcsCodePage,
vs.styles[STYLE_DEFAULT].characterSet, isRectangular, false);
} else {
selText.Copy(dest, SC_CP_UTF8, 0, isRectangular, false);
}
}
}
void ScintillaGTK::ReceivedSelection(GtkSelectionData *selection_data) {
try {
if ((SelectionOfGSD(selection_data) == atomClipboard) ||
(SelectionOfGSD(selection_data) == GDK_SELECTION_PRIMARY)) {
if ((atomSought == atomUTF8) && (LengthOfGSD(selection_data) <= 0)) {
atomSought = atomString;
gtk_selection_convert(GTK_WIDGET(PWidget(wMain)),
SelectionOfGSD(selection_data), atomSought, GDK_CURRENT_TIME);
} else if ((LengthOfGSD(selection_data) > 0) &&
((TypeOfGSD(selection_data) == GDK_TARGET_STRING) || (TypeOfGSD(selection_data) == atomUTF8))) {
SelectionText selText;
GetGtkSelectionText(selection_data, selText);
UndoGroup ug(pdoc);
if (SelectionOfGSD(selection_data) != GDK_SELECTION_PRIMARY) {
ClearSelection(multiPasteMode == SC_MULTIPASTE_EACH);
}
InsertPasteShape(selText.Data(), selText.Length(),
selText.rectangular ? pasteRectangular : pasteStream);
EnsureCaretVisible();
}
}
// else fprintf(stderr, "Target non string %d %d\n", (int)(selection_data->type),
// (int)(atomUTF8));
Redraw();
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::ReceivedDrop(GtkSelectionData *selection_data) {
dragWasDropped = true;
if (TypeOfGSD(selection_data) == atomUriList || TypeOfGSD(selection_data) == atomDROPFILES_DND) {
const char *data = reinterpret_cast<const char *>(DataOfGSD(selection_data));
std::vector<char> drop(data, data + LengthOfGSD(selection_data));
drop.push_back('\0');
NotifyURIDropped(&drop[0]);
} else if ((TypeOfGSD(selection_data) == GDK_TARGET_STRING) || (TypeOfGSD(selection_data) == atomUTF8)) {
if (LengthOfGSD(selection_data) > 0) {
SelectionText selText;
GetGtkSelectionText(selection_data, selText);
DropAt(posDrop, selText.Data(), selText.Length(), false, selText.rectangular);
}
} else if (LengthOfGSD(selection_data) > 0) {
//~ fprintf(stderr, "ReceivedDrop other %p\n", static_cast<void *>(selection_data->type));
}
Redraw();
}
void ScintillaGTK::GetSelection(GtkSelectionData *selection_data, guint info, SelectionText *text) {
#if PLAT_GTK_WIN32
// GDK on Win32 expands any \n into \r\n, so make a copy of
// the clip text now with newlines converted to \n. Use { } to hide symbols
// from code below
SelectionText *newline_normalized = NULL;
{
std::string tmpstr = Document::TransformLineEnds(text->Data(), text->Length(), SC_EOL_LF);
newline_normalized = new SelectionText();
newline_normalized->Copy(tmpstr, SC_CP_UTF8, 0, text->rectangular, false);
text = newline_normalized;
}
#endif
// Convert text to utf8 if it isn't already
SelectionText *converted = 0;
if ((text->codePage != SC_CP_UTF8) && (info == TARGET_UTF8_STRING)) {
const char *charSet = ::CharacterSetID(text->characterSet);
if (*charSet) {
std::string tmputf = ConvertText(text->Data(), text->Length(), "UTF-8", charSet, false);
converted = new SelectionText();
converted->Copy(tmputf, SC_CP_UTF8, 0, text->rectangular, false);
text = converted;
}
}
// Here is a somewhat evil kludge.
// As I can not work out how to store data on the clipboard in multiple formats
// and need some way to mark the clipping as being stream or rectangular,
// the terminating \0 is included in the length for rectangular clippings.
// All other tested aplications behave benignly by ignoring the \0.
// The #if is here because on Windows cfColumnSelect clip entry is used
// instead as standard indicator of rectangularness (so no need to kludge)
const char *textData = text->Data();
int len = text->Length();
#if PLAT_GTK_WIN32 == 0
if (text->rectangular)
len++;
#endif
if (info == TARGET_UTF8_STRING) {
gtk_selection_data_set_text(selection_data, textData, len);
} else {
gtk_selection_data_set(selection_data,
static_cast<GdkAtom>(GDK_SELECTION_TYPE_STRING),
8, reinterpret_cast<const unsigned char *>(textData), len);
}
delete converted;
#if PLAT_GTK_WIN32
delete newline_normalized;
#endif
}
void ScintillaGTK::StoreOnClipboard(SelectionText *clipText) {
GtkClipboard *clipBoard =
gtk_widget_get_clipboard(GTK_WIDGET(PWidget(wMain)), atomClipboard);
if (clipBoard == NULL) // Occurs if widget isn't in a toplevel
return;
if (gtk_clipboard_set_with_data(clipBoard, clipboardCopyTargets, nClipboardCopyTargets,
ClipboardGetSelection, ClipboardClearSelection, clipText)) {
gtk_clipboard_set_can_store(clipBoard, clipboardCopyTargets, nClipboardCopyTargets);
}
}
void ScintillaGTK::ClipboardGetSelection(GtkClipboard *, GtkSelectionData *selection_data, guint info, void *data) {
GetSelection(selection_data, info, static_cast<SelectionText*>(data));
}
void ScintillaGTK::ClipboardClearSelection(GtkClipboard *, void *data) {
SelectionText *obj = static_cast<SelectionText*>(data);
delete obj;
}
void ScintillaGTK::UnclaimSelection(GdkEventSelection *selection_event) {
try {
//Platform::DebugPrintf("UnclaimSelection\n");
if (selection_event->selection == GDK_SELECTION_PRIMARY) {
//Platform::DebugPrintf("UnclaimPrimarySelection\n");
if (!OwnPrimarySelection()) {
primary.Clear();
primarySelection = false;
FullPaint();
}
}
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::Resize(int width, int height) {
//Platform::DebugPrintf("Resize %d %d\n", width, height);
//printf("Resize %d %d\n", width, height);
// GTK+ 3 warns when we allocate smaller than the minimum allocation,
// so we use these variables to store the minimum scrollbar lengths.
int minVScrollBarHeight, minHScrollBarWidth;
// Not always needed, but some themes can have different sizes of scrollbars
#if GTK_CHECK_VERSION(3,0,0)
GtkRequisition minimum, requisition;
gtk_widget_get_preferred_size(PWidget(scrollbarv), &minimum, &requisition);
minVScrollBarHeight = minimum.height;
verticalScrollBarWidth = requisition.width;
gtk_widget_get_preferred_size(PWidget(scrollbarh), &minimum, &requisition);
minHScrollBarWidth = minimum.width;
horizontalScrollBarHeight = requisition.height;
#else
minVScrollBarHeight = minHScrollBarWidth = 1;
verticalScrollBarWidth = GTK_WIDGET(PWidget(scrollbarv))->requisition.width;
horizontalScrollBarHeight = GTK_WIDGET(PWidget(scrollbarh))->requisition.height;
#endif
// These allocations should never produce negative sizes as they would wrap around to huge
// unsigned numbers inside GTK+ causing warnings.
bool showSBHorizontal = horizontalScrollBarVisible && !Wrapping();
GtkAllocation alloc;
if (showSBHorizontal) {
gtk_widget_show(GTK_WIDGET(PWidget(scrollbarh)));
alloc.x = 0;
alloc.y = height - horizontalScrollBarHeight;
alloc.width = Platform::Maximum(minHScrollBarWidth, width - verticalScrollBarWidth);
alloc.height = horizontalScrollBarHeight;
gtk_widget_size_allocate(GTK_WIDGET(PWidget(scrollbarh)), &alloc);
} else {
gtk_widget_hide(GTK_WIDGET(PWidget(scrollbarh)));
horizontalScrollBarHeight = 0; // in case horizontalScrollBarVisible is true.
}
if (verticalScrollBarVisible) {
gtk_widget_show(GTK_WIDGET(PWidget(scrollbarv)));
alloc.x = width - verticalScrollBarWidth;
alloc.y = 0;
alloc.width = verticalScrollBarWidth;
alloc.height = Platform::Maximum(minVScrollBarHeight, height - horizontalScrollBarHeight);
gtk_widget_size_allocate(GTK_WIDGET(PWidget(scrollbarv)), &alloc);
} else {
gtk_widget_hide(GTK_WIDGET(PWidget(scrollbarv)));
verticalScrollBarWidth = 0;
}
if (IS_WIDGET_MAPPED(PWidget(wMain))) {
ChangeSize();
}
alloc.x = 0;
alloc.y = 0;
alloc.width = 1;
alloc.height = 1;
#if GTK_CHECK_VERSION(3, 0, 0)
// please GTK 3.20 and ask wText what size it wants, although we know it doesn't really need
// anything special as it's ours.
gtk_widget_get_preferred_size(PWidget(wText), &requisition, NULL);
alloc.width = requisition.width;
alloc.height = requisition.height;
#endif
alloc.width = Platform::Maximum(alloc.width, width - verticalScrollBarWidth);
alloc.height = Platform::Maximum(alloc.height, height - horizontalScrollBarHeight);
gtk_widget_size_allocate(GTK_WIDGET(PWidget(wText)), &alloc);
}
static void SetAdjustmentValue(GtkAdjustment *object, int value) {
GtkAdjustment *adjustment = GTK_ADJUSTMENT(object);
int maxValue = static_cast<int>(
gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment));
if (value > maxValue)
value = maxValue;
if (value < 0)
value = 0;
gtk_adjustment_set_value(adjustment, value);
}
static int modifierTranslated(int sciModifier) {
switch (sciModifier) {
case SCMOD_SHIFT:
return GDK_SHIFT_MASK;
case SCMOD_CTRL:
return GDK_CONTROL_MASK;
case SCMOD_ALT:
return GDK_MOD1_MASK;
case SCMOD_SUPER:
return GDK_MOD4_MASK;
default:
return 0;
}
}
gint ScintillaGTK::PressThis(GdkEventButton *event) {
try {
//Platform::DebugPrintf("Press %x time=%d state = %x button = %x\n",this,event->time, event->state, event->button);
// Do not use GTK+ double click events as Scintilla has its own double click detection
if (event->type != GDK_BUTTON_PRESS)
return FALSE;
if (evbtn) {
gdk_event_free(reinterpret_cast<GdkEvent *>(evbtn));
evbtn = 0;
}
evbtn = reinterpret_cast<GdkEventButton *>(gdk_event_copy(reinterpret_cast<GdkEvent *>(event)));
Point pt;
pt.x = int(event->x);
pt.y = int(event->y);
PRectangle rcClient = GetClientRectangle();
//Platform::DebugPrintf("Press %0d,%0d in %0d,%0d %0d,%0d\n",
// pt.x, pt.y, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
if ((pt.x > rcClient.right) || (pt.y > rcClient.bottom)) {
Platform::DebugPrintf("Bad location\n");
return FALSE;
}
bool shift = (event->state & GDK_SHIFT_MASK) != 0;
bool ctrl = (event->state & GDK_CONTROL_MASK) != 0;
// On X, instead of sending literal modifiers use the user specified
// modifier, defaulting to control instead of alt.
// This is because most X window managers grab alt + click for moving
bool alt = (event->state & modifierTranslated(rectangularSelectionModifier)) != 0;
gtk_widget_grab_focus(PWidget(wMain));
if (event->button == 1) {
#if PLAT_GTK_MACOSX
bool meta = ctrl;
// GDK reports the Command modifer key as GDK_MOD2_MASK for button events,
// not GDK_META_MASK like in key events.
ctrl = (event->state & GDK_MOD2_MASK) != 0;
#else
bool meta = false;
#endif
ButtonDownWithModifiers(pt, event->time, ModifierFlags(shift, ctrl, alt, meta));
} else if (event->button == 2) {
// Grab the primary selection if it exists
SelectionPosition pos = SPositionFromLocation(pt, false, false, UserVirtualSpace());
if (OwnPrimarySelection() && primary.Empty())
CopySelectionRange(&primary);
sel.Clear();
SetSelection(pos, pos);
atomSought = atomUTF8;
gtk_selection_convert(GTK_WIDGET(PWidget(wMain)), GDK_SELECTION_PRIMARY,
atomSought, event->time);
} else if (event->button == 3) {
if (!PointInSelection(pt))
SetEmptySelection(PositionFromLocation(pt));
if (displayPopupMenu) {
// PopUp menu
// Convert to screen
int ox = 0;
int oy = 0;
gdk_window_get_origin(PWindow(wMain), &ox, &oy);
ContextMenu(Point(pt.x + ox, pt.y + oy));
} else {
return FALSE;
}
} else if (event->button == 4) {
// Wheel scrolling up (only GTK 1.x does it this way)
if (ctrl)
SetAdjustmentValue(adjustmenth, xOffset - 6);
else
SetAdjustmentValue(adjustmentv, topLine - 3);
} else if (event->button == 5) {
// Wheel scrolling down (only GTK 1.x does it this way)
if (ctrl)
SetAdjustmentValue(adjustmenth, xOffset + 6);
else
SetAdjustmentValue(adjustmentv, topLine + 3);
}
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return TRUE;
}
gint ScintillaGTK::Press(GtkWidget *widget, GdkEventButton *event) {
if (event->window != WindowFromWidget(widget))
return FALSE;
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
return sciThis->PressThis(event);
}
gint ScintillaGTK::MouseRelease(GtkWidget *widget, GdkEventButton *event) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
//Platform::DebugPrintf("Release %x %d %d\n",sciThis,event->time,event->state);
if (!sciThis->HaveMouseCapture())
return FALSE;
if (event->button == 1) {
Point pt;
pt.x = int(event->x);
pt.y = int(event->y);
//Platform::DebugPrintf("Up %x %x %d %d %d\n",
// sciThis,event->window,event->time, pt.x, pt.y);
if (event->window != PWindow(sciThis->wMain))
// If mouse released on scroll bar then the position is relative to the
// scrollbar, not the drawing window so just repeat the most recent point.
pt = sciThis->ptMouseLast;
sciThis->ButtonUp(pt, event->time, (event->state & GDK_CONTROL_MASK) != 0);
}
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
// win32gtk and GTK >= 2 use SCROLL_* events instead of passing the
// button4/5/6/7 events to the GTK app
gint ScintillaGTK::ScrollEvent(GtkWidget *widget, GdkEventScroll *event) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
if (widget == NULL || event == NULL)
return FALSE;
// Compute amount and direction to scroll (even tho on win32 there is
// intensity of scrolling info in the native message, gtk doesn't
// support this so we simulate similarly adaptive scrolling)
// Note that this is disabled on OS X (Darwin) with the X11 backend
// where the X11 server already has an adaptive scrolling algorithm
// that fights with this one
int cLineScroll;
#if defined(__APPLE__) && !defined(GDK_WINDOWING_QUARTZ)
cLineScroll = sciThis->linesPerScroll;
if (cLineScroll == 0)
cLineScroll = 4;
sciThis->wheelMouseIntensity = cLineScroll;
#else
int timeDelta = 1000000;
GTimeVal curTime;
g_get_current_time(&curTime);
if (curTime.tv_sec == sciThis->lastWheelMouseTime.tv_sec)
timeDelta = curTime.tv_usec - sciThis->lastWheelMouseTime.tv_usec;
else if (curTime.tv_sec == sciThis->lastWheelMouseTime.tv_sec + 1)
timeDelta = 1000000 + (curTime.tv_usec - sciThis->lastWheelMouseTime.tv_usec);
if ((event->direction == sciThis->lastWheelMouseDirection) && (timeDelta < 250000)) {
if (sciThis->wheelMouseIntensity < 12)
sciThis->wheelMouseIntensity++;
cLineScroll = sciThis->wheelMouseIntensity;
} else {
cLineScroll = sciThis->linesPerScroll;
if (cLineScroll == 0)
cLineScroll = 4;
sciThis->wheelMouseIntensity = cLineScroll;
}
#endif
if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT) {
cLineScroll *= -1;
}
g_get_current_time(&sciThis->lastWheelMouseTime);
sciThis->lastWheelMouseDirection = event->direction;
// Note: Unpatched versions of win32gtk don't set the 'state' value so
// only regular scrolling is supported there. Also, unpatched win32gtk
// issues spurious button 2 mouse events during wheeling, which can cause
// problems (a patch for both was submitted by archaeopteryx.com on 13Jun2001)
// Data zoom not supported
if (event->state & GDK_SHIFT_MASK) {
return FALSE;
}
#if GTK_CHECK_VERSION(3,4,0)
// Smooth scrolling not supported
if (event->direction == GDK_SCROLL_SMOOTH) {
return FALSE;
}
#endif
// Horizontal scrolling
if (event->direction == GDK_SCROLL_LEFT || event->direction == GDK_SCROLL_RIGHT) {
sciThis->HorizontalScrollTo(sciThis->xOffset + cLineScroll);
// Text font size zoom
} else if (event->state & GDK_CONTROL_MASK) {
if (cLineScroll < 0) {
sciThis->KeyCommand(SCI_ZOOMIN);
} else {
sciThis->KeyCommand(SCI_ZOOMOUT);
}
// Regular scrolling
} else {
sciThis->ScrollTo(sciThis->topLine + cLineScroll);
}
return TRUE;
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
gint ScintillaGTK::Motion(GtkWidget *widget, GdkEventMotion *event) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
//Platform::DebugPrintf("Motion %x %d\n",sciThis,event->time);
if (event->window != WindowFromWidget(widget))
return FALSE;
int x = 0;
int y = 0;
GdkModifierType state;
if (event->is_hint) {
#if GTK_CHECK_VERSION(3,0,0)
gdk_window_get_device_position(event->window,
event->device, &x, &y, &state);
#else
gdk_window_get_pointer(event->window, &x, &y, &state);
#endif
} else {
x = static_cast<int>(event->x);
y = static_cast<int>(event->y);
state = static_cast<GdkModifierType>(event->state);
}
//Platform::DebugPrintf("Move %x %x %d %c %d %d\n",
// sciThis,event->window,event->time,event->is_hint? 'h' :'.', x, y);
Point pt(x, y);
int modifiers = ((event->state & GDK_SHIFT_MASK) != 0 ? SCI_SHIFT : 0) |
((event->state & GDK_CONTROL_MASK) != 0 ? SCI_CTRL : 0) |
((event->state & modifierTranslated(sciThis->rectangularSelectionModifier)) != 0 ? SCI_ALT : 0);
sciThis->ButtonMoveWithModifiers(pt, modifiers);
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
// Map the keypad keys to their equivalent functions
static int KeyTranslate(int keyIn) {
switch (keyIn) {
#if GTK_CHECK_VERSION(3,0,0)
case GDK_KEY_ISO_Left_Tab:
return SCK_TAB;
case GDK_KEY_KP_Down:
return SCK_DOWN;
case GDK_KEY_KP_Up:
return SCK_UP;
case GDK_KEY_KP_Left:
return SCK_LEFT;
case GDK_KEY_KP_Right:
return SCK_RIGHT;
case GDK_KEY_KP_Home:
return SCK_HOME;
case GDK_KEY_KP_End:
return SCK_END;
case GDK_KEY_KP_Page_Up:
return SCK_PRIOR;
case GDK_KEY_KP_Page_Down:
return SCK_NEXT;
case GDK_KEY_KP_Delete:
return SCK_DELETE;
case GDK_KEY_KP_Insert:
return SCK_INSERT;
case GDK_KEY_KP_Enter:
return SCK_RETURN;
case GDK_KEY_Down:
return SCK_DOWN;
case GDK_KEY_Up:
return SCK_UP;
case GDK_KEY_Left:
return SCK_LEFT;
case GDK_KEY_Right:
return SCK_RIGHT;
case GDK_KEY_Home:
return SCK_HOME;
case GDK_KEY_End:
return SCK_END;
case GDK_KEY_Page_Up:
return SCK_PRIOR;
case GDK_KEY_Page_Down:
return SCK_NEXT;
case GDK_KEY_Delete:
return SCK_DELETE;
case GDK_KEY_Insert:
return SCK_INSERT;
case GDK_KEY_Escape:
return SCK_ESCAPE;
case GDK_KEY_BackSpace:
return SCK_BACK;
case GDK_KEY_Tab:
return SCK_TAB;
case GDK_KEY_Return:
return SCK_RETURN;
case GDK_KEY_KP_Add:
return SCK_ADD;
case GDK_KEY_KP_Subtract:
return SCK_SUBTRACT;
case GDK_KEY_KP_Divide:
return SCK_DIVIDE;
case GDK_KEY_Super_L:
return SCK_WIN;
case GDK_KEY_Super_R:
return SCK_RWIN;
case GDK_KEY_Menu:
return SCK_MENU;
#else
case GDK_ISO_Left_Tab:
return SCK_TAB;
case GDK_KP_Down:
return SCK_DOWN;
case GDK_KP_Up:
return SCK_UP;
case GDK_KP_Left:
return SCK_LEFT;
case GDK_KP_Right:
return SCK_RIGHT;
case GDK_KP_Home:
return SCK_HOME;
case GDK_KP_End:
return SCK_END;
case GDK_KP_Page_Up:
return SCK_PRIOR;
case GDK_KP_Page_Down:
return SCK_NEXT;
case GDK_KP_Delete:
return SCK_DELETE;
case GDK_KP_Insert:
return SCK_INSERT;
case GDK_KP_Enter:
return SCK_RETURN;
case GDK_Down:
return SCK_DOWN;
case GDK_Up:
return SCK_UP;
case GDK_Left:
return SCK_LEFT;
case GDK_Right:
return SCK_RIGHT;
case GDK_Home:
return SCK_HOME;
case GDK_End:
return SCK_END;
case GDK_Page_Up:
return SCK_PRIOR;
case GDK_Page_Down:
return SCK_NEXT;
case GDK_Delete:
return SCK_DELETE;
case GDK_Insert:
return SCK_INSERT;
case GDK_Escape:
return SCK_ESCAPE;
case GDK_BackSpace:
return SCK_BACK;
case GDK_Tab:
return SCK_TAB;
case GDK_Return:
return SCK_RETURN;
case GDK_KP_Add:
return SCK_ADD;
case GDK_KP_Subtract:
return SCK_SUBTRACT;
case GDK_KP_Divide:
return SCK_DIVIDE;
case GDK_Super_L:
return SCK_WIN;
case GDK_Super_R:
return SCK_RWIN;
case GDK_Menu:
return SCK_MENU;
#endif
default:
return keyIn;
}
}
gboolean ScintillaGTK::KeyThis(GdkEventKey *event) {
try {
//fprintf(stderr, "SC-key: %d %x [%s]\n",
// event->keyval, event->state, (event->length > 0) ? event->string : "empty");
if (gtk_im_context_filter_keypress(im_context, event)) {
return 1;
}
if (!event->keyval) {
return true;
}
bool shift = (event->state & GDK_SHIFT_MASK) != 0;
bool ctrl = (event->state & GDK_CONTROL_MASK) != 0;
bool alt = (event->state & GDK_MOD1_MASK) != 0;
bool super = (event->state & GDK_MOD4_MASK) != 0;
guint key = event->keyval;
if ((ctrl || alt) && (key < 128))
key = toupper(key);
#if GTK_CHECK_VERSION(3,0,0)
else if (!ctrl && (key >= GDK_KEY_KP_Multiply && key <= GDK_KEY_KP_9))
#else
else if (!ctrl && (key >= GDK_KP_Multiply && key <= GDK_KP_9))
#endif
key &= 0x7F;
// Hack for keys over 256 and below command keys but makes Hungarian work.
// This will have to change for Unicode
else if (key >= 0xFE00)
key = KeyTranslate(key);
bool consumed = false;
#if !(PLAT_GTK_MACOSX)
bool meta = false;
#else
bool meta = ctrl;
ctrl = (event->state & GDK_META_MASK) != 0;
#endif
bool added = KeyDownWithModifiers(key, ModifierFlags(shift, ctrl, alt, meta, super), &consumed) != 0;
if (!consumed)
consumed = added;
//fprintf(stderr, "SK-key: %d %x %x\n",event->keyval, event->state, consumed);
if (event->keyval == 0xffffff && event->length > 0) {
ClearSelection();
const int lengthInserted = pdoc->InsertString(CurrentPosition(), event->string, strlen(event->string));
if (lengthInserted > 0) {
MovePositionTo(CurrentPosition() + lengthInserted);
}
}
return consumed;
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
gboolean ScintillaGTK::KeyPress(GtkWidget *widget, GdkEventKey *event) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
return sciThis->KeyThis(event);
}
gboolean ScintillaGTK::KeyRelease(GtkWidget *widget, GdkEventKey *event) {
//Platform::DebugPrintf("SC-keyrel: %d %x %3s\n",event->keyval, event->state, event->string);
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
if (gtk_im_context_filter_keypress(sciThis->im_context, event)) {
return TRUE;
}
return FALSE;
}
#if GTK_CHECK_VERSION(3,0,0)
gboolean ScintillaGTK::DrawPreeditThis(GtkWidget *widget, cairo_t *cr) {
try {
PreEditString pes(im_context);
PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), pes.str);
pango_layout_set_attributes(layout, pes.attrs);
cairo_move_to(cr, 0, 0);
pango_cairo_show_layout(cr, layout);
g_object_unref(layout);
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return TRUE;
}
gboolean ScintillaGTK::DrawPreedit(GtkWidget *widget, cairo_t *cr, ScintillaGTK *sciThis) {
return sciThis->DrawPreeditThis(widget, cr);
}
#else
gboolean ScintillaGTK::ExposePreeditThis(GtkWidget *widget, GdkEventExpose *ose) {
try {
PreEditString pes(im_context);
PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), pes.str);
pango_layout_set_attributes(layout, pes.attrs);
cairo_t *context = gdk_cairo_create(reinterpret_cast<GdkDrawable *>(WindowFromWidget(widget)));
cairo_move_to(context, 0, 0);
pango_cairo_show_layout(context, layout);
cairo_destroy(context);
g_object_unref(layout);
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return TRUE;
}
gboolean ScintillaGTK::ExposePreedit(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis) {
return sciThis->ExposePreeditThis(widget, ose);
}
#endif
bool ScintillaGTK::KoreanIME() {
PreEditString pes(im_context);
if (pes.pscript != PANGO_SCRIPT_COMMON)
lastNonCommonScript = pes.pscript;
return lastNonCommonScript == PANGO_SCRIPT_HANGUL;
}
void ScintillaGTK::MoveImeCarets(int pos) {
// Move carets relatively by bytes
for (size_t r=0; r<sel.Count(); r++) {
int positionInsert = sel.Range(r).Start().Position();
sel.Range(r).caret.SetPosition(positionInsert + pos);
sel.Range(r).anchor.SetPosition(positionInsert + pos);
}
}
void ScintillaGTK::DrawImeIndicator(int indicator, int len) {
// Emulate the visual style of IME characters with indicators.
// Draw an indicator on the character before caret by the character bytes of len
// so it should be called after addCharUTF().
// It does not affect caret positions.
if (indicator < 8 || indicator > INDIC_MAX) {
return;
}
pdoc->decorations.SetCurrentIndicator(indicator);
for (size_t r=0; r<sel.Count(); r++) {
int positionInsert = sel.Range(r).Start().Position();
pdoc->DecorationFillRange(positionInsert - len, 1, len);
}
}
static std::vector<int> MapImeIndicators(PangoAttrList *attrs, const char *u8Str) {
// Map input style to scintilla ime indicator.
// Attrs position points between UTF-8 bytes.
// Indicator index to be returned is character based though.
glong charactersLen = g_utf8_strlen(u8Str, strlen(u8Str));
std::vector<int> indicator(charactersLen, SC_INDICATOR_UNKNOWN);
PangoAttrIterator *iterunderline = pango_attr_list_get_iterator(attrs);
if (iterunderline) {
do {
PangoAttribute *attrunderline = pango_attr_iterator_get(iterunderline, PANGO_ATTR_UNDERLINE);
if (attrunderline) {
glong start = g_utf8_strlen(u8Str, attrunderline->start_index);
glong end = g_utf8_strlen(u8Str, attrunderline->end_index);
PangoUnderline uline = (PangoUnderline)((PangoAttrInt *)attrunderline)->value;
for (glong i=start; i < end; ++i) {
switch (uline) {
case PANGO_UNDERLINE_NONE:
indicator[i] = SC_INDICATOR_UNKNOWN;
break;
case PANGO_UNDERLINE_SINGLE: // normal input
indicator[i] = SC_INDICATOR_INPUT;
break;
case PANGO_UNDERLINE_DOUBLE:
case PANGO_UNDERLINE_LOW:
case PANGO_UNDERLINE_ERROR:
break;
}
}
}
} while (pango_attr_iterator_next(iterunderline));
pango_attr_iterator_destroy(iterunderline);
}
PangoAttrIterator *itercolor = pango_attr_list_get_iterator(attrs);
if (itercolor) {
do {
PangoAttribute *backcolor = pango_attr_iterator_get(itercolor, PANGO_ATTR_BACKGROUND);
if (backcolor) {
glong start = g_utf8_strlen(u8Str, backcolor->start_index);
glong end = g_utf8_strlen(u8Str, backcolor->end_index);
for (glong i=start; i < end; ++i) {
indicator[i] = SC_INDICATOR_TARGET; // target converted
}
}
} while (pango_attr_iterator_next(itercolor));
pango_attr_iterator_destroy(itercolor);
}
return indicator;
}
void ScintillaGTK::SetCandidateWindowPos() {
// Composition box accompanies candidate box.
Point pt = PointMainCaret();
GdkRectangle imeBox = {0}; // No need to set width
imeBox.x = pt.x; // Only need positiion
imeBox.y = pt.y + vs.lineHeight; // underneath the first charater
gtk_im_context_set_cursor_location(im_context, &imeBox);
}
void ScintillaGTK::CommitThis(char *commitStr) {
try {
//~ fprintf(stderr, "Commit '%s'\n", commitStr);
view.imeCaretBlockOverride = false;
if (pdoc->TentativeActive()) {
pdoc->TentativeUndo();
}
const char *charSetSource = CharacterSetID();
glong uniStrLen = 0;
gunichar *uniStr = g_utf8_to_ucs4_fast(commitStr, strlen(commitStr), &uniStrLen);
for (glong i = 0; i < uniStrLen; i++) {
gchar u8Char[UTF8MaxBytes+2] = {0};
gint u8CharLen = g_unichar_to_utf8(uniStr[i], u8Char);
std::string docChar = u8Char;
if (!IsUnicodeMode())
docChar = ConvertText(u8Char, u8CharLen, charSetSource, "UTF-8", true);
AddCharUTF(docChar.c_str(), docChar.size());
}
g_free(uniStr);
ShowCaretAtCurrentPosition();
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::Commit(GtkIMContext *, char *str, ScintillaGTK *sciThis) {
sciThis->CommitThis(str);
}
void ScintillaGTK::PreeditChangedInlineThis() {
// Copy & paste by johnsonj with a lot of helps of Neil
// Great thanks for my foreruners, jiniya and BLUEnLIVE
try {
if (pdoc->IsReadOnly() || SelectionContainsProtected()) {
gtk_im_context_reset(im_context);
return;
}
view.imeCaretBlockOverride = false; // If backspace.
if (pdoc->TentativeActive()) {
pdoc->TentativeUndo();
} else {
// No tentative undo means start of this composition so
// fill in any virtual spaces.
ClearBeforeTentativeStart();
}
PreEditString preeditStr(im_context);
const char *charSetSource = CharacterSetID();
if (!preeditStr.validUTF8 || (charSetSource == NULL)) {
ShowCaretAtCurrentPosition();
return;
}
if (preeditStr.uniStrLen == 0 || preeditStr.uniStrLen > maxLenInputIME) {
//fprintf(stderr, "Do not allow over 200 chars: %i\n", preeditStr.uniStrLen);
ShowCaretAtCurrentPosition();
return;
}
pdoc->TentativeStart(); // TentativeActive() from now on
std::vector<int> indicator = MapImeIndicators(preeditStr.attrs, preeditStr.str);
bool tmpRecordingMacro = recordingMacro;
recordingMacro = false;
for (glong i = 0; i < preeditStr.uniStrLen; i++) {
gchar u8Char[UTF8MaxBytes+2] = {0};
gint u8CharLen = g_unichar_to_utf8(preeditStr.uniStr[i], u8Char);
std::string docChar = u8Char;
if (!IsUnicodeMode())
docChar = ConvertText(u8Char, u8CharLen, charSetSource, "UTF-8", true);
AddCharUTF(docChar.c_str(), docChar.size());
DrawImeIndicator(indicator[i], docChar.size());
}
recordingMacro = tmpRecordingMacro;
// Move caret to ime cursor position.
int imeEndToImeCaretU32 = preeditStr.cursor_pos - preeditStr.uniStrLen;
int imeCaretPosDoc = pdoc->GetRelativePosition(CurrentPosition(), imeEndToImeCaretU32);
MoveImeCarets(- CurrentPosition() + imeCaretPosDoc);
if (KoreanIME()) {
#if !PLAT_GTK_WIN32
if (preeditStr.cursor_pos > 0) {
int oneCharBefore = pdoc->GetRelativePosition(CurrentPosition(), -1);
MoveImeCarets(- CurrentPosition() + oneCharBefore);
}
#endif
view.imeCaretBlockOverride = true;
}
EnsureCaretVisible();
SetCandidateWindowPos();
ShowCaretAtCurrentPosition();
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::PreeditChangedWindowedThis() {
try {
PreEditString pes(im_context);
if (strlen(pes.str) > 0) {
PangoLayout *layout = gtk_widget_create_pango_layout(PWidget(wText), pes.str);
pango_layout_set_attributes(layout, pes.attrs);
gint w, h;
pango_layout_get_pixel_size(layout, &w, &h);
g_object_unref(layout);
gint x, y;
gdk_window_get_origin(PWindow(wText), &x, &y);
Point pt = PointMainCaret();
if (pt.x < 0)
pt.x = 0;
if (pt.y < 0)
pt.y = 0;
gtk_window_move(GTK_WINDOW(PWidget(wPreedit)), x + pt.x, y + pt.y);
gtk_window_resize(GTK_WINDOW(PWidget(wPreedit)), w, h);
gtk_widget_show(PWidget(wPreedit));
gtk_widget_queue_draw_area(PWidget(wPreeditDraw), 0, 0, w, h);
} else {
gtk_widget_hide(PWidget(wPreedit));
}
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::PreeditChanged(GtkIMContext *, ScintillaGTK *sciThis) {
if ((sciThis->imeInteraction == imeInline) || (sciThis->KoreanIME())) {
sciThis->PreeditChangedInlineThis();
} else {
sciThis->PreeditChangedWindowedThis();
}
}
void ScintillaGTK::StyleSetText(GtkWidget *widget, GtkStyle *, void*) {
RealizeText(widget, NULL);
}
void ScintillaGTK::RealizeText(GtkWidget *widget, void*) {
// Set NULL background to avoid automatic clearing so Scintilla responsible for all drawing
if (WindowFromWidget(widget)) {
#if GTK_CHECK_VERSION(3,0,0)
gdk_window_set_background_pattern(WindowFromWidget(widget), NULL);
#else
gdk_window_set_back_pixmap(WindowFromWidget(widget), NULL, FALSE);
#endif
}
}
static GObjectClass *scintilla_class_parent_class;
void ScintillaGTK::Dispose(GObject *object) {
try {
ScintillaObject *scio = reinterpret_cast<ScintillaObject *>(object);
ScintillaGTK *sciThis = reinterpret_cast<ScintillaGTK *>(scio->pscin);
if (PWidget(sciThis->scrollbarv)) {
gtk_widget_unparent(PWidget(sciThis->scrollbarv));
sciThis->scrollbarv = NULL;
}
if (PWidget(sciThis->scrollbarh)) {
gtk_widget_unparent(PWidget(sciThis->scrollbarh));
sciThis->scrollbarh = NULL;
}
scintilla_class_parent_class->dispose(object);
} catch (...) {
// Its dying so nowhere to save the status
}
}
void ScintillaGTK::Destroy(GObject *object) {
try {
ScintillaObject *scio = SCINTILLA(object);
// This avoids a double destruction
if (!scio->pscin)
return;
ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(scio->pscin);
//Platform::DebugPrintf("Destroying %x %x\n", sciThis, object);
sciThis->Finalise();
delete sciThis;
scio->pscin = 0;
scintilla_class_parent_class->finalize(object);
} catch (...) {
// Its dead so nowhere to save the status
}
}
#if GTK_CHECK_VERSION(3,0,0)
gboolean ScintillaGTK::DrawTextThis(cairo_t *cr) {
try {
paintState = painting;
repaintFullWindow = false;
rcPaint = GetClientRectangle();
PLATFORM_ASSERT(rgnUpdate == NULL);
rgnUpdate = cairo_copy_clip_rectangle_list(cr);
if (rgnUpdate && rgnUpdate->status != CAIRO_STATUS_SUCCESS) {
// If not successful then ignore
fprintf(stderr, "DrawTextThis failed to copy update region %d [%d]\n", rgnUpdate->status, rgnUpdate->num_rectangles);
cairo_rectangle_list_destroy(rgnUpdate);
rgnUpdate = 0;
}
double x1, y1, x2, y2;
cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
rcPaint.left = x1;
rcPaint.top = y1;
rcPaint.right = x2;
rcPaint.bottom = y2;
PRectangle rcClient = GetClientRectangle();
paintingAllText = rcPaint.Contains(rcClient);
Surface *surfaceWindow = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
if (surfaceWindow) {
surfaceWindow->Init(cr, PWidget(wText));
Paint(surfaceWindow, rcPaint);
surfaceWindow->Release();
delete surfaceWindow;
}
if ((paintState == paintAbandoned) || repaintFullWindow) {
// Painting area was insufficient to cover new styling or brace highlight positions
FullPaint();
}
paintState = notPainting;
repaintFullWindow = false;
if (rgnUpdate) {
cairo_rectangle_list_destroy(rgnUpdate);
}
rgnUpdate = 0;
paintState = notPainting;
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
gboolean ScintillaGTK::DrawText(GtkWidget *, cairo_t *cr, ScintillaGTK *sciThis) {
return sciThis->DrawTextThis(cr);
}
gboolean ScintillaGTK::DrawThis(cairo_t *cr) {
try {
#ifdef GTK_STYLE_CLASS_SCROLLBARS_JUNCTION /* GTK >= 3.4 */
// if both scrollbars are visible, paint the little square on the bottom right corner
if (verticalScrollBarVisible && horizontalScrollBarVisible && !Wrapping()) {
GtkStyleContext *styleContext = gtk_widget_get_style_context(PWidget(wMain));
PRectangle rc = GetClientRectangle();
gtk_style_context_save(styleContext);
gtk_style_context_add_class(styleContext, GTK_STYLE_CLASS_SCROLLBARS_JUNCTION);
gtk_render_background(styleContext, cr, rc.right, rc.bottom,
verticalScrollBarWidth, horizontalScrollBarHeight);
gtk_render_frame(styleContext, cr, rc.right, rc.bottom,
verticalScrollBarWidth, horizontalScrollBarHeight);
gtk_style_context_restore(styleContext);
}
#endif
gtk_container_propagate_draw(
GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarh), cr);
gtk_container_propagate_draw(
GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarv), cr);
// Starting from the following version, the expose event are not propagated
// for double buffered non native windows, so we need to call it ourselves
// or keep the default handler
#if GTK_CHECK_VERSION(3,0,0)
// we want to forward on any >= 3.9.2 runtime
if (gtk_check_version(3,9,2) == NULL) {
gtk_container_propagate_draw(
GTK_CONTAINER(PWidget(wMain)), PWidget(wText), cr);
}
#endif
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
gboolean ScintillaGTK::DrawMain(GtkWidget *widget, cairo_t *cr) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
return sciThis->DrawThis(cr);
}
#else
gboolean ScintillaGTK::ExposeTextThis(GtkWidget * /*widget*/, GdkEventExpose *ose) {
try {
paintState = painting;
rcPaint.left = ose->area.x;
rcPaint.top = ose->area.y;
rcPaint.right = ose->area.x + ose->area.width;
rcPaint.bottom = ose->area.y + ose->area.height;
PLATFORM_ASSERT(rgnUpdate == NULL);
rgnUpdate = gdk_region_copy(ose->region);
PRectangle rcClient = GetClientRectangle();
paintingAllText = rcPaint.Contains(rcClient);
Surface *surfaceWindow = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
if (surfaceWindow) {
cairo_t *cr = gdk_cairo_create(PWindow(wText));
surfaceWindow->Init(cr, PWidget(wText));
Paint(surfaceWindow, rcPaint);
surfaceWindow->Release();
delete surfaceWindow;
cairo_destroy(cr);
}
if (paintState == paintAbandoned) {
// Painting area was insufficient to cover new styling or brace highlight positions
FullPaint();
}
paintState = notPainting;
if (rgnUpdate) {
gdk_region_destroy(rgnUpdate);
}
rgnUpdate = 0;
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
gboolean ScintillaGTK::ExposeText(GtkWidget *widget, GdkEventExpose *ose, ScintillaGTK *sciThis) {
return sciThis->ExposeTextThis(widget, ose);
}
gboolean ScintillaGTK::ExposeMain(GtkWidget *widget, GdkEventExpose *ose) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
//Platform::DebugPrintf("Expose Main %0d,%0d %0d,%0d\n",
//ose->area.x, ose->area.y, ose->area.width, ose->area.height);
return sciThis->Expose(widget, ose);
}
gboolean ScintillaGTK::Expose(GtkWidget *, GdkEventExpose *ose) {
try {
//fprintf(stderr, "Expose %0d,%0d %0d,%0d\n",
//ose->area.x, ose->area.y, ose->area.width, ose->area.height);
// The text is painted in ExposeText
gtk_container_propagate_expose(
GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarh), ose);
gtk_container_propagate_expose(
GTK_CONTAINER(PWidget(wMain)), PWidget(scrollbarv), ose);
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
#endif
void ScintillaGTK::ScrollSignal(GtkAdjustment *adj, ScintillaGTK *sciThis) {
try {
sciThis->ScrollTo(static_cast<int>(gtk_adjustment_get_value(adj)), false);
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::ScrollHSignal(GtkAdjustment *adj, ScintillaGTK *sciThis) {
try {
sciThis->HorizontalScrollTo(static_cast<int>(gtk_adjustment_get_value(adj)));
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::SelectionReceived(GtkWidget *widget,
GtkSelectionData *selection_data, guint) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
//Platform::DebugPrintf("Selection received\n");
sciThis->ReceivedSelection(selection_data);
}
void ScintillaGTK::SelectionGet(GtkWidget *widget,
GtkSelectionData *selection_data, guint info, guint) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
//Platform::DebugPrintf("Selection get\n");
if (SelectionOfGSD(selection_data) == GDK_SELECTION_PRIMARY) {
if (sciThis->primary.Empty()) {
sciThis->CopySelectionRange(&sciThis->primary);
}
sciThis->GetSelection(selection_data, info, &sciThis->primary);
}
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
}
gint ScintillaGTK::SelectionClear(GtkWidget *widget, GdkEventSelection *selection_event) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
//Platform::DebugPrintf("Selection clear\n");
sciThis->UnclaimSelection(selection_event);
if (GTK_WIDGET_CLASS(sciThis->parentClass)->selection_clear_event) {
return GTK_WIDGET_CLASS(sciThis->parentClass)->selection_clear_event(widget, selection_event);
}
return TRUE;
}
gboolean ScintillaGTK::DragMotionThis(GdkDragContext *context,
gint x, gint y, guint dragtime) {
try {
Point npt(x, y);
SetDragPosition(SPositionFromLocation(npt, false, false, UserVirtualSpace()));
#if GTK_CHECK_VERSION(2,22,0)
GdkDragAction preferredAction = gdk_drag_context_get_suggested_action(context);
GdkDragAction actions = gdk_drag_context_get_actions(context);
#else
GdkDragAction preferredAction = context->suggested_action;
GdkDragAction actions = context->actions;
#endif
SelectionPosition pos = SPositionFromLocation(npt);
if ((inDragDrop == ddDragging) && (PositionInSelection(pos.Position()))) {
// Avoid dragging selection onto itself as that produces a move
// with no real effect but which creates undo actions.
preferredAction = static_cast<GdkDragAction>(0);
} else if (actions == static_cast<GdkDragAction>
(GDK_ACTION_COPY | GDK_ACTION_MOVE)) {
preferredAction = GDK_ACTION_MOVE;
}
gdk_drag_status(context, preferredAction, dragtime);
} catch (...) {
errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
gboolean ScintillaGTK::DragMotion(GtkWidget *widget, GdkDragContext *context,
gint x, gint y, guint dragtime) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
return sciThis->DragMotionThis(context, x, y, dragtime);
}
void ScintillaGTK::DragLeave(GtkWidget *widget, GdkDragContext * /*context*/, guint) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
sciThis->SetDragPosition(SelectionPosition(invalidPosition));
//Platform::DebugPrintf("DragLeave %x\n", sciThis);
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::DragEnd(GtkWidget *widget, GdkDragContext * /*context*/) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
// If drag did not result in drop here or elsewhere
if (!sciThis->dragWasDropped)
sciThis->SetEmptySelection(sciThis->posDrag);
sciThis->SetDragPosition(SelectionPosition(invalidPosition));
//Platform::DebugPrintf("DragEnd %x %d\n", sciThis, sciThis->dragWasDropped);
sciThis->inDragDrop = ddNone;
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
}
gboolean ScintillaGTK::Drop(GtkWidget *widget, GdkDragContext * /*context*/,
gint, gint, guint) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
//Platform::DebugPrintf("Drop %x\n", sciThis);
sciThis->SetDragPosition(SelectionPosition(invalidPosition));
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
return FALSE;
}
void ScintillaGTK::DragDataReceived(GtkWidget *widget, GdkDragContext * /*context*/,
gint, gint, GtkSelectionData *selection_data, guint /*info*/, guint) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
sciThis->ReceivedDrop(selection_data);
sciThis->SetDragPosition(SelectionPosition(invalidPosition));
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
}
void ScintillaGTK::DragDataGet(GtkWidget *widget, GdkDragContext *context,
GtkSelectionData *selection_data, guint info, guint) {
ScintillaGTK *sciThis = ScintillaFromWidget(widget);
try {
sciThis->dragWasDropped = true;
if (!sciThis->sel.Empty()) {
sciThis->GetSelection(selection_data, info, &sciThis->drag);
}
#if GTK_CHECK_VERSION(2,22,0)
GdkDragAction action = gdk_drag_context_get_selected_action(context);
#else
GdkDragAction action = context->action;
#endif
if (action == GDK_ACTION_MOVE) {
for (size_t r=0; r<sciThis->sel.Count(); r++) {
if (sciThis->posDrop >= sciThis->sel.Range(r).Start()) {
if (sciThis->posDrop > sciThis->sel.Range(r).End()) {
sciThis->posDrop.Add(-sciThis->sel.Range(r).Length());
} else {
sciThis->posDrop.Add(-SelectionRange(sciThis->posDrop, sciThis->sel.Range(r).Start()).Length());
}
}
}
sciThis->ClearSelection();
}
sciThis->SetDragPosition(SelectionPosition(invalidPosition));
} catch (...) {
sciThis->errorStatus = SC_STATUS_FAILURE;
}
}
int ScintillaGTK::TimeOut(gpointer ptt) {
TimeThunk *tt = static_cast<TimeThunk *>(ptt);
tt->scintilla->TickFor(tt->reason);
return 1;
}
gboolean ScintillaGTK::IdleCallback(gpointer pSci) {
ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(pSci);
// Idler will be automatically stopped, if there is nothing
// to do while idle.
bool ret = sciThis->Idle();
if (ret == false) {
// FIXME: This will remove the idler from GTK, we don't want to
// remove it as it is removed automatically when this function
// returns false (although, it should be harmless).
sciThis->SetIdle(false);
}
return ret;
}
gboolean ScintillaGTK::StyleIdle(gpointer pSci) {
ScintillaGTK *sciThis = static_cast<ScintillaGTK *>(pSci);
sciThis->IdleWork();
// Idler will be automatically stopped
return FALSE;
}
void ScintillaGTK::IdleWork() {
Editor::IdleWork();
styleIdleID = 0;
}
void ScintillaGTK::QueueIdleWork(WorkNeeded::workItems items, int upTo) {
Editor::QueueIdleWork(items, upTo);
if (!styleIdleID) {
// Only allow one style needed to be queued
styleIdleID = gdk_threads_add_idle_full(G_PRIORITY_HIGH_IDLE, StyleIdle, this, NULL);
}
}
void ScintillaGTK::PopUpCB(GtkMenuItem *menuItem, ScintillaGTK *sciThis) {
guint action = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(menuItem), "CmdNum"));
if (action) {
sciThis->Command(action);
}
}
gboolean ScintillaGTK::PressCT(GtkWidget *widget, GdkEventButton *event, ScintillaGTK *sciThis) {
try {
if (event->window != WindowFromWidget(widget))
return FALSE;
if (event->type != GDK_BUTTON_PRESS)
return FALSE;
Point pt;
pt.x = int(event->x);
pt.y = int(event->y);
sciThis->ct.MouseClick(pt);
sciThis->CallTipClick();
} catch (...) {
}
return TRUE;
}
#if GTK_CHECK_VERSION(3,0,0)
gboolean ScintillaGTK::DrawCT(GtkWidget *widget, cairo_t *cr, CallTip *ctip) {
try {
Surface *surfaceWindow = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
if (surfaceWindow) {
surfaceWindow->Init(cr, widget);
surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == ctip->codePage);
surfaceWindow->SetDBCSMode(ctip->codePage);
ctip->PaintCT(surfaceWindow);
surfaceWindow->Release();
delete surfaceWindow;
}
} catch (...) {
// No pointer back to Scintilla to save status
}
return TRUE;
}
#else
gboolean ScintillaGTK::ExposeCT(GtkWidget *widget, GdkEventExpose * /*ose*/, CallTip *ctip) {
try {
Surface *surfaceWindow = Surface::Allocate(SC_TECHNOLOGY_DEFAULT);
if (surfaceWindow) {
cairo_t *cr = gdk_cairo_create(WindowFromWidget(widget));
surfaceWindow->Init(cr, widget);
surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == ctip->codePage);
surfaceWindow->SetDBCSMode(ctip->codePage);
ctip->PaintCT(surfaceWindow);
surfaceWindow->Release();
delete surfaceWindow;
cairo_destroy(cr);
}
} catch (...) {
// No pointer back to Scintilla to save status
}
return TRUE;
}
#endif
sptr_t ScintillaGTK::DirectFunction(
sptr_t ptr, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
return reinterpret_cast<ScintillaGTK *>(ptr)->WndProc(iMessage, wParam, lParam);
}
/* legacy name for scintilla_object_send_message */
sptr_t scintilla_send_message(ScintillaObject *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
ScintillaGTK *psci = static_cast<ScintillaGTK *>(sci->pscin);
return psci->WndProc(iMessage, wParam, lParam);
}
gintptr scintilla_object_send_message(ScintillaObject *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) {
return scintilla_send_message(sci, iMessage, wParam, lParam);
}
static void scintilla_class_init(ScintillaClass *klass);
static void scintilla_init(ScintillaObject *sci);
extern void Platform_Initialise();
extern void Platform_Finalise();
/* legacy name for scintilla_object_get_type */
GType scintilla_get_type() {
static GType scintilla_type = 0;
try {
if (!scintilla_type) {
scintilla_type = g_type_from_name("ScintillaObject");
if (!scintilla_type) {
static GTypeInfo scintilla_info = {
(guint16) sizeof (ScintillaObjectClass),
NULL, //(GBaseInitFunc)
NULL, //(GBaseFinalizeFunc)
(GClassInitFunc) scintilla_class_init,
NULL, //(GClassFinalizeFunc)
NULL, //gconstpointer data
(guint16) sizeof (ScintillaObject),
0, //n_preallocs
(GInstanceInitFunc) scintilla_init,
NULL //(GTypeValueTable*)
};
scintilla_type = g_type_register_static(
GTK_TYPE_CONTAINER, "ScintillaObject", &scintilla_info, (GTypeFlags) 0);
}
}
} catch (...) {
}
return scintilla_type;
}
GType scintilla_object_get_type() {
return scintilla_get_type();
}
void ScintillaGTK::ClassInit(OBJECT_CLASS* object_class, GtkWidgetClass *widget_class, GtkContainerClass *container_class) {
Platform_Initialise();
#ifdef SCI_LEXER
Scintilla_LinkLexers();
#endif
atomClipboard = gdk_atom_intern("CLIPBOARD", FALSE);
atomUTF8 = gdk_atom_intern("UTF8_STRING", FALSE);
atomString = GDK_SELECTION_TYPE_STRING;
atomUriList = gdk_atom_intern("text/uri-list", FALSE);
atomDROPFILES_DND = gdk_atom_intern("DROPFILES_DND", FALSE);
// Define default signal handlers for the class: Could move more
// of the signal handlers here (those that currently attached to wDraw
// in Initialise() may require coordinate translation?)
object_class->dispose = Dispose;
object_class->finalize = Destroy;
#if GTK_CHECK_VERSION(3,0,0)
widget_class->get_preferred_width = GetPreferredWidth;
widget_class->get_preferred_height = GetPreferredHeight;
#else
widget_class->size_request = SizeRequest;
#endif
widget_class->size_allocate = SizeAllocate;
#if GTK_CHECK_VERSION(3,0,0)
widget_class->draw = DrawMain;
#else
widget_class->expose_event = ExposeMain;
#endif
widget_class->motion_notify_event = Motion;
widget_class->button_press_event = Press;
widget_class->button_release_event = MouseRelease;
widget_class->scroll_event = ScrollEvent;
widget_class->key_press_event = KeyPress;
widget_class->key_release_event = KeyRelease;
widget_class->focus_in_event = FocusIn;
widget_class->focus_out_event = FocusOut;
widget_class->selection_received = SelectionReceived;
widget_class->selection_get = SelectionGet;
widget_class->selection_clear_event = SelectionClear;
widget_class->drag_data_received = DragDataReceived;
widget_class->drag_motion = DragMotion;
widget_class->drag_leave = DragLeave;
widget_class->drag_end = DragEnd;
widget_class->drag_drop = Drop;
widget_class->drag_data_get = DragDataGet;
widget_class->realize = Realize;
widget_class->unrealize = UnRealize;
widget_class->map = Map;
widget_class->unmap = UnMap;
container_class->forall = MainForAll;
}
static void scintilla_class_init(ScintillaClass *klass) {
try {
OBJECT_CLASS *object_class = (OBJECT_CLASS*) klass;
GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
GtkContainerClass *container_class = (GtkContainerClass*) klass;
GSignalFlags sigflags = GSignalFlags(G_SIGNAL_ACTION | G_SIGNAL_RUN_LAST);
scintilla_signals[COMMAND_SIGNAL] = g_signal_new(
"command",
G_TYPE_FROM_CLASS(object_class),
sigflags,
G_STRUCT_OFFSET(ScintillaClass, command),
NULL, //(GSignalAccumulator)
NULL, //(gpointer)
scintilla_marshal_VOID__INT_OBJECT,
G_TYPE_NONE,
2, G_TYPE_INT, GTK_TYPE_WIDGET);
scintilla_signals[NOTIFY_SIGNAL] = g_signal_new(
SCINTILLA_NOTIFY,
G_TYPE_FROM_CLASS(object_class),
sigflags,
G_STRUCT_OFFSET(ScintillaClass, notify),
NULL, //(GSignalAccumulator)
NULL, //(gpointer)
scintilla_marshal_VOID__INT_BOXED,
G_TYPE_NONE,
2, G_TYPE_INT, SCINTILLA_TYPE_NOTIFICATION);
klass->command = NULL;
klass->notify = NULL;
scintilla_class_parent_class = G_OBJECT_CLASS(g_type_class_peek_parent(klass));
ScintillaGTK::ClassInit(object_class, widget_class, container_class);
} catch (...) {
}
}
static void scintilla_init(ScintillaObject *sci) {
try {
gtk_widget_set_can_focus(GTK_WIDGET(sci), TRUE);
sci->pscin = new ScintillaGTK(sci);
} catch (...) {
}
}
/* legacy name for scintilla_object_new */
GtkWidget* scintilla_new() {
GtkWidget *widget = GTK_WIDGET(g_object_new(scintilla_get_type(), NULL));
gtk_widget_set_direction(widget, GTK_TEXT_DIR_LTR);
return widget;
}
GtkWidget *scintilla_object_new() {
return scintilla_new();
}
void scintilla_set_id(ScintillaObject *sci, uptr_t id) {
ScintillaGTK *psci = static_cast<ScintillaGTK *>(sci->pscin);
psci->ctrlID = id;
}
void scintilla_release_resources(void) {
try {
Platform_Finalise();
} catch (...) {
}
}
/* Define a dummy boxed type because g-ir-scanner is unable to
* recognize gpointer-derived types. Note that SCNotificaiton
* is always allocated on stack so copying is not appropriate. */
static void *copy_(void *src) { return src; }
static void free_(void *doc) { }
GType scnotification_get_type(void) {
static gsize type_id = 0;
if (g_once_init_enter(&type_id)) {
gsize id = (gsize) g_boxed_type_register_static(
g_intern_static_string("SCNotification"),
(GBoxedCopyFunc) copy_,
(GBoxedFreeFunc) free_);
g_once_init_leave(&type_id, id);
}
return (GType) type_id;
}