geany/scintilla/gtk/ScintillaGTK.cxx
Colomban Wendling 28a99f8188 GTK: Properly ask wText what size it wants to please GTK 3.20
It's not really of any use as we do know any size would do as wText is
ours anyway, but GTK 3.20 doesn't like allocating without querying the
preferred size beforehand, so do it.

As wText has a size_request() of 100x100, this might change how we
allocate in case we used to underallocate it, but AFAIK we don't, and
it is the real minimum size expected.

X-Scintilla-Bug-URL: https://sourceforge.net/p/scintilla/bugs/1825/
X-Scintilla-Commit-ID: d06e3db3e26842cd136328df17eb6f864b3adc02
2016-05-02 15:19:26 +02:00

3225 lines
100 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;
// 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 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) {
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() {
g_source_remove_by_user_data(this);
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
gtk_style_context_set_background(gtk_widget_get_style_context(widget),
gtk_widget_get_window(widget));
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) {
size_t 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);
size_t inLeft = len;
char *putf = &destForm[0];
char *pout = putf;
size_t conversions = conv.Convert(&pin, &inLeft, &pout, &outLeft);
if (conversions == ((size_t)(-1))) {
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 = const_cast<Window &>(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);
gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmentv));
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);
gtk_adjustment_changed(GTK_ADJUSTMENT(adjustmenth));
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);
// Not always needed, but some themes can have different sizes of scrollbars
#if GTK_CHECK_VERSION(3,0,0)
GtkRequisition requisition;
gtk_widget_get_preferred_size(PWidget(scrollbarv), NULL, &requisition);
verticalScrollBarWidth = requisition.width;
gtk_widget_get_preferred_size(PWidget(scrollbarh), NULL, &requisition);
horizontalScrollBarHeight = requisition.height;
#else
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(1, 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(1, 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::QueueIdleWork(WorkNeeded::workItems items, int upTo) {
Editor::QueueIdleWork(items, upTo);
if (!workNeeded.active) {
// Only allow one style needed to be queued
workNeeded.active = true;
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 */
GEANY_API_SYMBOL
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);
}
GEANY_API_SYMBOL
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 */
GEANY_API_SYMBOL
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;
}
GEANY_API_SYMBOL
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;
}
#define SIG_MARSHAL scintilla_marshal_NONE__INT_POINTER
#define MARSHAL_ARGUMENTS G_TYPE_INT, G_TYPE_POINTER
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)
SIG_MARSHAL,
G_TYPE_NONE,
2, MARSHAL_ARGUMENTS);
scintilla_signals[NOTIFY_SIGNAL] = g_signal_new(
SCINTILLA_NOTIFY,
G_TYPE_FROM_CLASS(object_class),
sigflags,
G_STRUCT_OFFSET(ScintillaClass, notify),
NULL,
NULL,
SIG_MARSHAL,
G_TYPE_NONE,
2, MARSHAL_ARGUMENTS);
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 */
GEANY_API_SYMBOL
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;
}
GEANY_API_SYMBOL
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 (...) {
}
}