Input Method support (Win32, MacOS, Linux)

This commit is contained in:
Mirek Fidler 2022-02-26 18:31:33 +01:00
parent 7ec93f4a40
commit 21eebae3db
28 changed files with 523 additions and 52 deletions

View file

@ -1,11 +1,14 @@
template <class Range>
using ValueTypeOf = typename std::remove_reference<decltype(*((typename std::remove_reference<Range>::type *)0)->begin())>::type;
template <class T>
T *DeclPtr__();
template <class Range>
using IteratorOf = decltype(((typename std::remove_reference<Range>::type *)0)->begin());
using ValueTypeOf = typename std::remove_reference<decltype(*DeclPtr__<typename std::remove_reference<Range>::type>()->begin())>::type;
template <class Range>
using ConstIteratorOf = decltype(((const typename std::remove_reference<Range>::type *)0)->begin());
using IteratorOf = decltype(DeclPtr__<typename std::remove_reference<Range>::type>()->begin());
template <class Range>
using ConstIteratorOf = decltype(DeclPtr__<const typename std::remove_reference<Range>::type>()->begin());
template <class I>
class SubRangeClass {
@ -16,7 +19,7 @@ public:
typedef typename std::remove_reference<decltype(*l)>::type value_type;
int GetCount() const { return count; }
SubRangeClass& Write() { return *this; }
value_type& operator[](int i) const { ASSERT(i >= 0 && i < count); return l[i]; }
@ -61,15 +64,15 @@ template <class T>
struct ConstRangeClass {
T value;
int count;
typedef T value_type;
typedef value_type ValueType;
const value_type& operator[](int i) const { return value; }
int GetCount() const { return count; }
typedef ConstIIterator<ConstRangeClass> Iterator;
Iterator begin() const { return Iterator(*this, 0); }
Iterator end() const { return Iterator(*this, count); }
@ -102,14 +105,14 @@ ConstRangeClass<T> ConstRange(int count)
template <class BaseRange>
struct ReverseRangeClass {
typename std::remove_reference<BaseRange>::type& r;
typedef ValueTypeOf<BaseRange> value_type;
typedef value_type ValueType;
const value_type& operator[](int i) const { return r[r.GetCount() - i - 1]; }
value_type& operator[](int i) { return r[r.GetCount() - i - 1]; }
int GetCount() const { return r.GetCount(); }
typedef IIterator<ReverseRangeClass> Iterator;
typedef ConstIIterator<ReverseRangeClass> ConstIterator;
@ -117,7 +120,7 @@ struct ReverseRangeClass {
ConstIterator begin() const { return ConstIterator(*this, 0); }
ConstIterator end() const { return ConstIterator(*this, r.GetCount()); }
Iterator begin() { return Iterator(*this, 0); }
Iterator end() { return Iterator(*this, r.GetCount()); }
@ -144,14 +147,14 @@ template <class BaseRange>
struct ViewRangeClass {
typename std::remove_reference<BaseRange>::type *r;
Vector<int> ndx;
typedef ValueTypeOf<BaseRange> value_type;
typedef value_type ValueType;
const value_type& operator[](int i) const { return (*r)[ndx[i]]; }
value_type& operator[](int i) { return (*r)[ndx[i]]; }
int GetCount() const { return ndx.GetCount(); }
typedef IIterator<ViewRangeClass> Iterator;
typedef ConstIIterator<ViewRangeClass> ConstIterator;
@ -159,7 +162,7 @@ struct ViewRangeClass {
ConstIterator begin() const { return ConstIterator(*this, 0); }
ConstIterator end() const { return ConstIterator(*this, ndx.GetCount()); }
Iterator begin() { return Iterator(*this, 0); }
Iterator end() { return Iterator(*this, ndx.GetCount()); }
@ -198,4 +201,4 @@ template <class BaseRange>
ViewRangeClass<BaseRange> SortedRange(BaseRange&& r)
{
return ViewRangeClass<BaseRange>(r, GetSortOrder(r));
}
}

View file

@ -323,8 +323,11 @@ public:
void pop_back() { Drop(); } \
template <class T>
T *DeclPtr__();
template <class Range>
using ValueTypeOfArray = typename std::remove_reference<decltype((*((Range *)0))[0])>::type;
using ValueTypeOfArray = typename std::remove_reference<decltype((*DeclPtr__<Range>())[0])>::type;
template <class V>
class ConstIIterator {

View file

@ -10,7 +10,7 @@ private:
static Ptr<Ctrl> lastActive;
friend void CocoInit(int argc, const char **argv, const char **envptr);
protected:
virtual void MMClose() {}
@ -18,6 +18,7 @@ protected:
static void SetNSAppImage(const Image& img);
static void SyncAppIcon();
static void ResetCocoaMouse();
static void DoCancelPreedit();
public:
static void EndSession() {}

View file

@ -76,9 +76,11 @@ struct RectCG {
RectCG();
};
NSRect DesktopRect(const Upp::Rect& r);
}
@interface CocoView : NSView <NSWindowDelegate>
@interface CocoView : NSView <NSWindowDelegate, NSTextInputClient>
{
@public
Upp::Ptr<Upp::Ctrl> ctrl;

View file

@ -102,6 +102,8 @@ struct MMImp {
static bool MouseEvent(CocoView *view, NSEvent *e, int event, double zd = 0)
{
if(!view->ctrl)
return false;
Flags(e);
sCurrentMouseEvent__ = e;
if((event & Ctrl::ACTION) == Ctrl::UP && Ctrl::ignoreclick) {
@ -167,6 +169,8 @@ struct MMImp {
static bool MouseDownEvent(CocoView *view, NSEvent *e, int button)
{
if(!view->ctrl)
return false;
Upp::Ctrl::lastActive = view->ctrl;
if(Ctrl::ignoremouseup) {
Ctrl::KillRepeat();
@ -196,11 +200,15 @@ struct MMImp {
static void Paint(Upp::Ctrl *ctrl, Upp::SystemDraw& w, const Rect& r)
{
if(!ctrl)
return;
ctrl->fullrefresh = false;
ctrl->UpdateArea(w, r);
}
static bool KeyEvent(Upp::Ctrl *ctrl, NSEvent *e, int up) {
if(!ctrl)
return false;
Flags(e);
if(!ctrl->IsEnabled())
return false;
@ -232,11 +240,13 @@ struct MMImp {
ctrl->DispatchKey(k, 1);
if(!up && !(k & (K_CTRL|K_ALT))) {
WString x = ToWString((CFStringRef)(e.characters));
#if 0 // this is now done by NSTextInputClient
for(wchar c : x) {
if(c < 0xF700 &&
(c > 32 && c != 127 || /*c == 9 && !GetOption() || */c == 32 && !GetShift()))
ctrl->DispatchKey(c, 1);
}
#endif
if(e.keyCode == kVK_ANSI_KeypadEnter && *x != 13)
ctrl->DispatchKey(13, 1);
}
@ -245,6 +255,8 @@ struct MMImp {
static void BecomeKey(Upp::Ctrl *ctrl)
{
if(!ctrl)
return;
LLOG("Become key " << Upp::Name(ctrl));
Upp::Ctrl::lastActive = ctrl;
ctrl->ActivateWnd();
@ -260,6 +272,8 @@ struct MMImp {
static void ResignKey(Upp::Ctrl *ctrl)
{
if(!ctrl)
return;
LLOG("Resign key " << Upp::Name(ctrl));
ctrl->KillFocusWnd();
Upp::Ctrl::ReleaseCtrlCapture();
@ -267,12 +281,16 @@ struct MMImp {
static void DoClose(Upp::Ctrl *ctrl)
{
if(!ctrl)
return;
ctrl->MMClose();
Upp::Ctrl::ReleaseCtrlCapture();
}
static int DnD(Upp::Ctrl *ctrl, id<NSDraggingInfo> info, bool paste = false)
{
if(!ctrl)
return false;
NSView *nsview = (NSView *)ctrl->GetNSView();
PasteClip clip;
clip.nspasteboard = info.draggingPasteboard;
@ -299,6 +317,31 @@ struct MMImp {
{
Ctrl::DoCursorShape();
}
static void ShowPreedit(Ctrl *ctrl, const WString& text)
{
if(ctrl)
ctrl->GetTopCtrl()->ShowPreedit(text, INT_MAX);
}
static NSRect PreeditRect(Ctrl *ctrl)
{
if(ctrl)
return DesktopRect(ctrl->GetTopCtrl()->GetPreeditScreenRect());
return NSRect();
}
static void PreeditText(Ctrl *ctrl, const WString& s)
{
if(ctrl)
for(Upp::wchar ch : s)
ctrl->DispatchKey(ch, 1);
}
static void CancelPreedit()
{
Ctrl::HidePreedit();
}
};
};
@ -357,6 +400,7 @@ struct MMImp {
}
- (void)keyDown:(NSEvent *)e {
[self interpretKeyEvents: [NSArray arrayWithObject: e]];
if(!Upp::MMImp::KeyEvent(ctrl, e, 0))
[super keyDown:e];
}
@ -447,6 +491,69 @@ struct MMImp {
[ta release];
}
// NSTextInputClient methods
-(void)insertText:(id)aString replacementRange:(NSRange)replacementRange
{
(void) replacementRange;
NSString* pInsert = [aString isMemberOfClass: [NSAttributedString class]] ? [aString string] : aString;
if(pInsert)
Upp::MMImp::PreeditText(ctrl, Upp::ToWString(pInsert));
}
- (NSRange)markedRange
{
return NSMakeRange( NSNotFound, 0 );
}
- (NSRange)selectedRange
{
return NSMakeRange( NSNotFound, 0 );
}
- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
{
return Upp::MMImp::PreeditRect(ctrl);
}
- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
{
if(![aString isKindOfClass:[NSAttributedString class]] )
aString = [[[NSAttributedString alloc] initWithString:aString] autorelease];
Upp::MMImp::ShowPreedit(ctrl, Upp::ToWString([aString string]));
}
- (BOOL)hasMarkedText
{
return true;
}
- (void)unmarkText
{
Upp::MMImp::CancelPreedit();
}
- (NSArray *)validAttributesForMarkedText
{
return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, nil];
}
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
{
(void) aRange;
(void) actualRange;
return nil;
}
- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
{
(void)thePoint;
return 0;
}
@end
#endif

View file

@ -97,8 +97,15 @@ void *Ctrl::GetNSView() const
return top && top->coco ? top->coco->view : NULL;
}
void Ctrl::DoCancelPreedit()
{
[[NSTextInputContext currentInputContext] discardMarkedText];
}
void Ctrl::Create(Ctrl *owner, dword style, bool active)
{
cancel_preedit = DoCancelPreedit; // We really need this just once, but whatever..
if(owner)
owner = owner->GetTopCtrl();

View file

@ -339,12 +339,14 @@ void Ctrl::SetModify()
{
GuiLock __;
modify = true;
CancelMyPreedit();
}
void Ctrl::ClearModify()
{
GuiLock __;
modify = false;
CancelMyPreedit();
}
void Ctrl::ClearModifyDeep()

View file

@ -642,6 +642,12 @@ private:
void SetInfoPart(int i, const char *txt);
String GetInfoPart(int i) const;
Rect GetPreeditScreenRect();
void SyncPreedit();
void ShowPreedit(const WString& text, int cursor = INT_MAX);
static void HidePreedit();
static void PreeditSync(void (*enable_preedit)(Ctrl *top, bool enable));
// System window interface...
void WndShow(bool b);
void WndSetPos(const Rect& rect);
@ -695,6 +701,8 @@ private:
static bool IsNoLayoutZoom;
static void Csizeinit();
static void (*skin)();
static void (*cancel_preedit)();
friend void InitRichTextZoom();
friend void AvoidPaintingCheck__();
@ -875,6 +883,9 @@ public:
virtual void MouseLeave();
virtual void Pen(Point p, const PenInfo& pen, dword keyflags);
virtual Point GetPreedit();
virtual Font GetPreeditFont();
virtual void DragAndDrop(Point p, PasteClip& d);
virtual void FrameDragAndDrop(Point p, PasteClip& d);
@ -1122,6 +1133,10 @@ public:
void SetCaret(const Rect& r);
Rect GetCaret() const;
void KillCaret();
static void CancelPreedit();
void CancelMyPreedit() { if(HasFocus()) CancelPreedit(); }
static Ctrl *GetFocusCtrl() { return FocusCtrl(); }

View file

@ -6,7 +6,7 @@ uses
RichText,
Painter;
library(WIN32) "advapi32 comdlg32 comctl32";
library(WIN32) "advapi32 comdlg32 comctl32 Imm32 user32 gdi32";
pkg_config(POSIX !OSX !VIRTUALGUI) "freetype2 x11 xinerama xrender xft xdmcp fontconfig xcb xext";
@ -29,6 +29,7 @@ file
CtrlTimer.cpp,
CtrlClip.cpp,
LocalLoop.cpp,
Preedit.cpp,
CtrlCoreInit.cpp,
TopWindow.h,
TopWindow.cpp,

View file

@ -60,7 +60,7 @@ bool Ctrl::DispatchKey(dword keycode, int count)
return true;
dword k = keycode;
word l = LOWORD(keycode);
if(!(k & K_DELTA) && l >= 32 && l != 127 && GetDefaultCharset() != CHARSET_UTF8)
if(GetDefaultCharset() != CHARSET_UTF8 && !(k & K_DELTA) && l >= 32 && l != 127)
k = MAKELONG((word)FromUnicode(l, CHARSET_DEFAULT), HIWORD(keycode));
if(!focusCtrl)
return false;
@ -306,6 +306,7 @@ void Ctrl::KillFocusWnd()
focusCtrl = focusCtrlWnd = NULL;
DoKillFocus(pfocusCtrl, NULL);
}
CancelPreedit();
}
void Ctrl::ClickActivateWnd()

View file

@ -183,6 +183,8 @@ Vector<int> GetPropertyInts(GdkWindow *w, const char *property);
#define GUIPLATFORM_CTRL_TOP_DECLS \
GtkWidget *window; \
GtkIMContext *im_context; \
GtkIMContext *im_context_simple; \
GtkIMContext *im_context_multi; \
int64 cursor_id; \
int id; \
//$ }

View file

@ -55,6 +55,7 @@ int Ctrl::scale;
void InitGtkApp(int argc, char **argv, const char **envptr)
{
LLOG(rmsecs() << " InitGtkApp");
#if GTK_CHECK_VERSION(3, 10, 0)
gdk_set_allowed_backends("x11"); // this fixes wayland issues
#endif

View file

@ -18,7 +18,7 @@ void Ctrl::Create(Ctrl *owner, bool popup)
top = new Top;
top->window = gtk_window_new(popup && owner ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
top->owner = owner;
static int id;
top->id = ++id;
@ -57,7 +57,7 @@ void Ctrl::Create(Ctrl *owner, bool popup)
gtk_window_resize(gtk(), LSC(r.GetWidth()), LSC(r.GetHeight()));
gtk_widget_realize(top->window);
w.gdk = gtk_widget_get_window(top->window);
if(owner && owner->top)
@ -68,20 +68,23 @@ void Ctrl::Create(Ctrl *owner, bool popup)
top->im_context = gtk_im_multicontext_new();
gtk_im_context_set_client_window(top->im_context, gdk());
gtk_im_context_set_use_preedit(top->im_context, false);
gtk_im_context_set_use_preedit(top->im_context, true);
g_signal_connect(top->im_context, "preedit-changed", G_CALLBACK(IMPreedit), (gpointer)(uintptr_t)top->id);
g_signal_connect(top->im_context, "preedit-start", G_CALLBACK(IMPreedit), (gpointer)(uintptr_t)top->id);
g_signal_connect(top->im_context, "preedit-end", G_CALLBACK(IMPreeditEnd), (gpointer)(uintptr_t)top->id);
g_signal_connect(top->im_context, "commit", G_CALLBACK(IMCommit), (gpointer)(uintptr_t)top->id);
WndShow(IsShown());
SweepConfigure(true);
FocusSync();
if(!popup)
SetWndFocus();
activeCtrl = this;
DndInit();
StateH(OPEN);
GdkModifierType mod;
@ -89,7 +92,7 @@ void Ctrl::Create(Ctrl *owner, bool popup)
r = GetWndScreenRect().GetSize();
if(r.Contains(m))
DispatchMouse(MOUSEMOVE, m);
RefreshLayoutDeep();
}
@ -108,7 +111,8 @@ void Ctrl::WndDestroy()
if(HasFocusDeep() || !GetFocusCtrl())
activeCtrl = owner;
}
g_object_unref(top->im_context);
if(top->im_context)
g_object_unref(top->im_context);
gtk_widget_destroy(top->window);
isopen = false;
popup = false;
@ -167,4 +171,4 @@ void Ctrl::PopUp(Ctrl *owner, bool savebits, bool activate, bool, bool)
}
#endif
#endif

View file

@ -4,6 +4,9 @@
static gboolean GtkProc(GtkWidget *widget, GdkEvent *event, gpointer user_data);
static void IMCommit(GtkIMContext *context, gchar *str, gpointer user_data);
static void IMPreedit(GtkIMContext *context, gpointer user_data);
static void IMPreeditEnd(GtkIMContext *context, gpointer user_data);
static void IMLocation(Ctrl *w);
static int DoButtonEvent(GdkEvent *event, bool press);
static void AddEvent(gpointer user_data, int type, const Value& value, GdkEvent *event);
@ -118,6 +121,7 @@ _DBG_
static void StopGrabPopup();
static void StartGrabPopup();
static bool ReleaseWndCapture0();
static void DoCancelPreedit();
static Rect frameMargins;
static Rect GetFrameMargins();

View file

@ -130,8 +130,10 @@ gboolean Ctrl::GtkEvent(GtkWidget *widget, GdkEvent *event, gpointer user_data)
switch(event->type) {
case GDK_DELETE:
p->CancelPreedit();
break;
case GDK_FOCUS_CHANGE:
p->CancelPreedit();
if(p) {
if(((GdkEventFocus *)event)->in)
gtk_im_context_focus_in(p->top->im_context);
@ -144,16 +146,19 @@ gboolean Ctrl::GtkEvent(GtkWidget *widget, GdkEvent *event, gpointer user_data)
case GDK_LEAVE_NOTIFY:
break;
case GDK_BUTTON_PRESS:
p->CancelPreedit();
value = DoButtonEvent(event, true);
if(IsNull(value))
return false;
break;
case GDK_2BUTTON_PRESS:
p->CancelPreedit();
value = DoButtonEvent(event, true);
if(IsNull(value))
return false;
break;
case GDK_BUTTON_RELEASE:
p->CancelPreedit();
value = DoButtonEvent(event, false);
if(IsNull(value))
return false;
@ -288,7 +293,7 @@ void Ctrl::AddEvent(gpointer user_data, int type, const Value& value, GdkEvent *
e.count = 1;
e.event = NULL;
#if GTK_CHECK_VERSION(3, 22, 0)
GdkDevice *d = gdk_event_get_source_device(event);
GdkDevice *d = event ? gdk_event_get_source_device(event) : NULL;
if(d && findarg(gdk_device_get_source(d), GDK_SOURCE_PEN, GDK_SOURCE_TOUCHSCREEN) >= 0) {
e.pen = true;
e.pen_barrel = MouseState & GDK_BUTTON3_MASK;
@ -328,6 +333,48 @@ void Ctrl::IMCommit(GtkIMContext *context, gchar *str, gpointer user_data)
AddEvent(user_data, EVENT_TEXT, ToUtf32(str), NULL);
}
void Ctrl::IMLocation(Ctrl *w)
{
if(w && w->HasFocusDeep() && focusCtrl && !IsNull(focusCtrl->GetPreedit())) {
GdkRectangle r;
Rect e = w->GetPreeditScreenRect();
Rect q = w->GetScreenRect();
GdkRectangle gr;
gr.x = LSC(e.left - q.left);
gr.y = LSC(e.top - q.top);
gr.width = LSC(e.GetWidth());
gr.height = LSC(e.GetHeight());
gtk_im_context_set_cursor_location(w->top->im_context, &gr);
}
}
void Ctrl::IMPreedit(GtkIMContext *context, gpointer user_data)
{
GuiLock __;
Ctrl *w = GetTopCtrlFromId((uint32)(uintptr_t)user_data);
if(w && w->HasFocusDeep() && focusCtrl && !IsNull(focusCtrl->GetPreedit())) {
PangoAttrList *attrs;
gchar *str;
gint cursor_pos;
gtk_im_context_get_preedit_string(context, &str, &attrs, &cursor_pos);
WString text = ToUtf32(str);
g_free(str);
pango_attr_list_unref(attrs);
w->ShowPreedit(text, cursor_pos);
IMLocation(w);
}
else
CancelPreedit();
}
void Ctrl::IMPreeditEnd(GtkIMContext *context, gpointer user_data)
{
GuiLock __;
Ctrl *w = GetTopCtrlFromId((uint32)(uintptr_t)user_data);
if(w && w->HasFocusDeep() && focusCtrl && !IsNull(focusCtrl->GetPreedit()))
w->HidePreedit();
}
bool Ctrl::ProcessInvalids()
{
GuiLock __;
@ -367,7 +414,7 @@ bool Ctrl::IsWaitingEvent()
struct ProcStop {
TimeStop tm;
String ev;
~ProcStop() { LOG("* " << ev << " elapsed " << tm); }
};
@ -439,7 +486,7 @@ void Ctrl::Proc()
pen.pressure = CurrentEvent.pen_pressure;
pen.rotation = CurrentEvent.pen_rotation;
pen.tilt = CurrentEvent.pen_tilt;
is_pen_event = CurrentEvent.pen;
auto DoPen = [&](Point p) {
@ -452,7 +499,7 @@ void Ctrl::Proc()
}
else
for(Ctrl *t = q; t; t=q->ChildFromPoint(p)) q = t;
q->Pen(p, pen, GetMouseFlags());
SyncCaret();
Image m = CursorOverride();
@ -464,7 +511,7 @@ void Ctrl::Proc()
findarg(CurrentEvent.type, GDK_MOTION_NOTIFY, GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE) >= 0)
{
pen.action = decode(CurrentEvent.type, GDK_BUTTON_PRESS, PEN_DOWN, GDK_BUTTON_RELEASE, PEN_UP, 0);
DoPen(GetMousePos() - GetScreenRect().TopLeft());
}
#endif
@ -490,7 +537,7 @@ void Ctrl::Proc()
ignoreclick = false;
ignoremouseup = false;
}
if(!ignoreclick) {
bool dbl = msecs(clicktime) < 250;
clicktime = dbl ? clicktime - 1000 : msecs();
@ -609,7 +656,7 @@ void Ctrl::Proc()
kv |= K_CTRL;
if(GetAlt() && kv != K_ALT_KEY)
kv |= K_ALT;
LLOG(GetKeyDesc(kv) << ", pressed: " << pressed << ", count: " << CurrentEvent.count);
DLOG(GetKeyDesc(kv) << ", pressed: " << pressed << ", count: " << CurrentEvent.count);
#ifdef GDK_WINDOWING_X11
if(pressed)
for(int i = 0; i < hotkey.GetCount(); i++) {
@ -619,6 +666,7 @@ void Ctrl::Proc()
}
}
#endif
DDUMPHEX(kv);
DispatchKey(!pressed * K_KEYUP + kv, CurrentEvent.count);
}
break;
@ -693,8 +741,10 @@ bool Ctrl::ProcessEvent0(bool *quit, bool fetch)
Ctrl *w = GetTopCtrlFromId(e.windowid);
FocusSync();
CaptureSync();
if(w)
if(w) {
IMLocation(w);
w->Proc();
}
r = true;
}
if(quit)
@ -752,6 +802,9 @@ void WakeUpGuiThread()
void Ctrl::EventLoop(Ctrl *ctrl)
{
GuiLock __;
cancel_preedit = DoCancelPreedit; // We really need this just once, but whatever..
ASSERT_(IsMainThread(), "EventLoop can only run in the main thread");
ASSERT(LoopLevel == 0 || ctrl);
LoopLevel++;
@ -793,4 +846,4 @@ void Ctrl::GuiSleep(int ms)
}
#endif
#endif

View file

@ -396,6 +396,19 @@ bool Ctrl::SetWndFocus()
return true;
}
void Ctrl::DoCancelPreedit()
{
if(!focusCtrl)
return;
if(focusCtrl->top)
focusCtrl->HidePreedit();
if(focusCtrl->top) {
gtk_im_context_reset(focusCtrl->top->im_context);
gtk_im_context_focus_out(focusCtrl->top->im_context);
gtk_im_context_focus_in(focusCtrl->top->im_context);
}
}
void WakeUpGuiThread();
void Ctrl::WndInvalidateRect(const Rect& r)

131
uppsrc/CtrlCore/Preedit.cpp Normal file
View file

@ -0,0 +1,131 @@
#include "CtrlCore.h"
#define LLOG(x) DLOG(x)
namespace Upp {
void (*Ctrl::cancel_preedit)(); // hook for implementation to hide cancel host preedit
void Ctrl::CancelPreedit()
{
HidePreedit();
if(cancel_preedit)
cancel_preedit();
}
struct PreeditCtrl : Ctrl {
WString text;
Font font;
Ctrl *owner = NULL;
int cursor = INT_MAX;
virtual void Paint(Draw& w) {
Size sz = GetSize();
w.DrawRect(GetSize(), SWhite());
w.DrawText(DPI(2), sz.cy - font.GetCy(), text, font, SBlack());
if(cursor < text.GetCount())
w.DrawRect(DPI(2) + GetTextSize(text.Mid(0, cursor), font).cx, 0, DPI(1), sz.cy, InvertColor);
}
PreeditCtrl() { SetFrame(BlackFrame()); }
};
Rect Ctrl::GetPreeditScreenRect()
{ // preedit position in screen coordinates, zero width
if(HasFocusDeep()) {
Point p = focusCtrl->GetPreedit();
if(!IsNull(p)) {
p += focusCtrl->GetScreenView().TopLeft();
return RectC(p.x, p.y - 1, 0, focusCtrl->GetPreeditFont().GetCy() + 1);
}
}
return Null;
}
Point Ctrl::GetPreedit()
{
if(HasFocus()) {
Rect r = GetCaret();
if(r.GetHeight() > 0)
return r.TopRight();
}
return Null;
}
Font Ctrl::GetPreeditFont()
{
static int pheight = -1;
static Font pfont;
if(!focusCtrl)
return StdFont();
int height = max(focusCtrl->GetCaret().GetHeight(), DPI(7));
if(height != pheight) {
pheight = height;
while(pheight > 0) {
pfont = StdFont(height);
if(pfont.GetCy() < pheight)
break;
height--;
}
if(height == 0)
pfont = StdFont();
}
return pfont;
}
void Ctrl::SyncPreedit()
{
PreeditCtrl& p = Single<PreeditCtrl>();
if(p.owner == this && focusCtrl) {
Rect r = GetPreeditScreenRect();
p.font = focusCtrl->GetPreeditFont();
r.right = r.left + GetTextSize(p.text, p.font).cx + DPI(4);
int wr = GetWorkArea().right;
if(r.right > wr) {
int w = r.GetWidth();
r.right = min(wr, r.left - DPI(2));
r.left = r.right - w;
}
p.SetRect(r);
}
}
void Ctrl::ShowPreedit(const WString& text, int cursor)
{
if(text.GetCount() == 0) {
HidePreedit();
return;
}
PreeditCtrl& p = Single<PreeditCtrl>();
p.text = text;
p.cursor = cursor;
p.owner = this;
SyncPreedit();
if(!p.IsOpen())
p.PopUp(this, true, false, true);
p.Refresh();
}
void Ctrl::HidePreedit()
{
PreeditCtrl& p = Single<PreeditCtrl>();
if(p.IsOpen()) {
p.Close();
p.owner = NULL;
}
}
void Ctrl::PreeditSync(void (*enable_preedit)(Ctrl *top, bool enable))
{ // enables / disables preedit
static Ptr<Ctrl> preedit;
Ctrl *fw = focusCtrl && !IsNull(focusCtrl->GetPreedit()) ? focusCtrl->GetTopCtrl() : nullptr;
if(fw != preedit) {
if(preedit)
enable_preedit(preedit, false);
if(fw)
enable_preedit(fw, true);
}
preedit = fw;
}
};

View file

@ -21,6 +21,7 @@ private:
static void RenderFormat(int format);
static void RenderAllFormats();
static void DestroyClipboard();
static void DoCancelPreedit();
void UpdateDHCtrls();

View file

@ -64,6 +64,21 @@ Point GetMousePos() {
bool PassWindowsKey(int wParam);
void Ctrl::DoCancelPreedit()
{
if(!focusCtrlWnd)
return;
if(focusCtrlWnd->top)
focusCtrl->HidePreedit();
if(focusCtrlWnd->top && focusCtrlWnd->top->hwnd) {
HIMC himc = ImmGetContext(focusCtrlWnd->top->hwnd);
if(himc && ImmGetOpenStatus(himc)) {
ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
ImmReleaseContext(focusCtrlWnd->top->hwnd, himc);
}
}
}
LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
GuiLock __;
eventid++;
@ -71,6 +86,8 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
Ptr<Ctrl> _this = this;
HWND hwnd = GetHWND();
cancel_preedit = DoCancelPreedit; // We really need this just once, but whatever..
is_pen_event = (GetMessageExtraInfo() & 0xFFFFFF00) == 0xFF515700;
POINT p;
@ -83,6 +100,19 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
::ClientToScreen(hwnd, CurrentMousePos);
return p;
};
bool has_preedit = HasFocusDeep() && focusCtrl && !IsNull(focusCtrl->GetPreedit());
auto StopPreedit = [&] {
HidePreedit();
if(HasFocusDeep())
CancelPreedit();
};
auto ClickActivate = [&] {
ClickActivateWnd();
StopPreedit();
};
switch(message) {
case WM_POINTERDOWN:
@ -174,7 +204,7 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
case WM_POINTERDOWN:
pendown=true;
pen.action = PEN_DOWN;
ClickActivateWnd();
ClickActivate();
break;
case WM_POINTERUP:
pendown=false;
@ -235,7 +265,7 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
if(ignoremouse) return HTTRANSPARENT;
break;
case WM_LBUTTONDOWN:
ClickActivateWnd();
ClickActivate();
if(ignoreclick) return 0L;
DoMouse(LEFTDOWN, MousePos(), 0);
if(_this) PostInput();
@ -248,13 +278,13 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
if(_this) PostInput();
return 0L;
case WM_LBUTTONDBLCLK:
ClickActivateWnd();
ClickActivate();
if(ignoreclick) return 0L;
DoMouse(LEFTDOUBLE, MousePos(), 0);
if(_this) PostInput();
return 0L;
case WM_RBUTTONDOWN:
ClickActivateWnd();
ClickActivate();
if(ignoreclick) return 0L;
DoMouse(RIGHTDOWN, MousePos());
if(_this) PostInput();
@ -267,13 +297,13 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
if(_this) PostInput();
return 0L;
case WM_RBUTTONDBLCLK:
ClickActivateWnd();
ClickActivate();
if(ignoreclick) return 0L;
DoMouse(RIGHTDOUBLE, MousePos());
if(_this) PostInput();
return 0L;
case WM_MBUTTONDOWN:
ClickActivateWnd();
ClickActivate();
if(ignoreclick) return 0L;
DoMouse(MIDDLEDOWN, MousePos());
if(_this) PostInput();
@ -286,7 +316,7 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
if(_this) PostInput();
return 0L;
case WM_MBUTTONDBLCLK:
ClickActivateWnd();
ClickActivate();
if(ignoreclick) return 0L;
DoMouse(MIDDLEDOUBLE, MousePos());
if(_this) PostInput();
@ -294,7 +324,7 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
case WM_NCLBUTTONDOWN:
case WM_NCRBUTTONDOWN:
case WM_NCMBUTTONDOWN:
ClickActivateWnd();
ClickActivate();
IgnoreMouseUp();
break;
case WM_MOUSEMOVE:
@ -409,6 +439,59 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
break;
// case WM_GETDLGCODE:
// return wantfocus ? 0 : DLGC_STATIC;
case WM_IME_STARTCOMPOSITION:
case WM_IME_COMPOSITION:
if(has_preedit) {
HIMC himc = ImmGetContext(GetHWND());
if(!himc)
break;
Rect pr = GetPreeditScreenRect();
Point p = pr.TopLeft() - GetScreenRect().TopLeft();
CANDIDATEFORM cf;
cf.dwIndex = 0;
cf.dwStyle = CFS_EXCLUDE;
cf.ptCurrentPos.x = p.x;
cf.ptCurrentPos.y = p.y;
cf.rcArea.left = p.x;
cf.rcArea.top = p.y;
cf.rcArea.right = p.x + DPI(20); // DPI(20) is sort of hack, but candidate windows are above or bellow anyway...
cf.rcArea.bottom = p.y + pr.GetHeight();
ImmSetCandidateWindow(himc, &cf);
/* // todo: SetCaretPos too
Rect r;
::CreateCaret(hwnd, NULL, 1, pr.Height());
::ShowCaret(hwnd);
::SetCaretPos(p.x, p.y);
*/
auto ReadString = [&](int type) -> WString {
int len = ImmGetCompositionStringW (himc, type, NULL, 0);
if(len > 0) {
Buffer<char16> sw(len / 2);
ImmGetCompositionStringW(himc, type, sw, len);
return ToUtf32(sw, len / 2);
}
return Null;
};
if(lParam & GCS_COMPSTR) {
ShowPreedit(ReadString(GCS_COMPSTR), ImmGetCompositionString(himc, GCS_CURSORPOS, 0, 0));
}
if(lParam & GCS_RESULTSTR) {
WString h = ReadString(GCS_RESULTSTR);
for(wchar c : h)
DispatchKey(c, 1);
HidePreedit();
SyncCaret();
}
ImmReleaseContext(GetHWND(), himc);
return 0L;
}
break;
case WM_IME_ENDCOMPOSITION:
if(has_preedit) {
HidePreedit();
return 0L;
}
break;
case WM_XBUTTONDOWN: {
UINT button = GET_XBUTTON_WPARAM(wParam);
if(button == XBUTTON2)
@ -463,7 +546,8 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
if(!IsEnabled()) {
if(lastActiveWnd && lastActiveWnd->IsEnabled()) {
if(focusCtrl) { // this closes popup
LLOG("WM_MOUSEACTIVATE -> ClickActivateWnd for " << UPP::Name(lastActiveWnd));
LLOG("WM_MOUSEACTIVATE -> ClickActivate for " << UPP::Name(lastActiveWnd));
StopPreedit();
lastActiveWnd->ClickActivateWnd();
}
else { // this makes child dialog active when clicked on disabled parent
@ -497,6 +581,7 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
caretCtrl = NULL;
SyncCaret();
#endif
SyncPreedit();
}
return 0L;
case WM_HELP:
@ -541,6 +626,7 @@ LRESULT Ctrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
KillFocusWnd();
}
LLOG("//WM_KILLFOCUS " << (void *)(HWND)wParam << ", focusCtrlWnd = " << UPP::Name(focusCtrlWnd) << ", raw = " << (void *)::GetFocus());
StopPreedit();
return 0L;
case WM_ENABLE:
if(!!wParam != enabled) {

View file

@ -489,6 +489,7 @@ void Ctrl::Create(HWND parent, DWORD style, DWORD exstyle, bool savebits, int sh
StateH(OPEN);
LLOG(LOG_END << "//Ctrl::Create in " <<UPP::Name(this));
RegisterDragDrop(top->hwnd, (LPDROPTARGET) (top->dndtgt = NewUDropTarget(this)));
::ImmAssociateContextEx(top->hwnd, NULL, 0);
CancelMode();
RefreshLayoutDeep();
}
@ -755,6 +756,10 @@ bool Ctrl::ProcessEvent(bool *quit)
// LLOG(GetSysTime() << " % " << (unsigned)msecs() % 10000 << ": //sProcessMSG " << FormatIntHex(msg.message));
DefferedFocusSync();
SyncCaret();
PreeditSync([](Ctrl *top, bool enable) {
if(HWND hwnd = top->GetHWND())
::ImmAssociateContextEx(hwnd, NULL, enable * IACE_DEFAULT);
});
return true;
}
return false;

View file

@ -254,6 +254,7 @@ void DocEdit::PlaceCaret(int64 newpos, bool select) {
SelectionChanged();
if(IsSelection())
SetSelectionSource(ClipFmtsText());
CancelMyPreedit();
}
int DocEdit::GetMousePos(Point p)

View file

@ -427,6 +427,7 @@ void EditField::SelSource()
SetSelectionSource(ClipFmtsText());
else
fsell = fselh = -1;
CancelMyPreedit();
}
void EditField::GotFocus()

View file

@ -32,6 +32,11 @@ LineEdit::LineEdit() {
LineEdit::~LineEdit() {}
Font LineEdit::GetPreeditFont()
{
return font;
}
void LineEdit::MouseWheel(Point, int zdelta, dword keyflags) {
if(keyflags & K_SHIFT)
sb.WheelX(zdelta);

View file

@ -298,6 +298,7 @@ public:
virtual void DragLeave();
virtual void Layout();
virtual void RefreshLine(int i);
virtual Font GetPreeditFont();
protected:
virtual void SetSb();

View file

@ -364,7 +364,6 @@ void PdfDraw::DrawTextOp(int x, int y, int angle, const wchar *s, Font fnt,
auto Fmt = [](double x) { return FormatF(x, 5); };
if(fnt.GetFaceInfo() & Font::COLORIMG) {
int fi = -1;
Pointf prev(0, 0);
for(int i = 0; i < n; i++) {
CGlyph cg = ColorGlyph(fnt, s[i]);

View file

@ -107,6 +107,7 @@ void RichEdit::MoveNG(int newpos, bool select)
if(select)
SetSelectionSource(String().Cat() << "text/QTF;Rich Text Format;text/rtf;application/rtf;"
<< ClipFmtsText());
CancelMyPreedit();
}
void RichEdit::Move(int newpos, bool select)

View file

@ -2,6 +2,26 @@
namespace Upp {
Point RichEdit::GetPreedit()
{
Rect r = GetCaretRect();
if(formatinfo.sscript == 2) {
Point p = r.BottomRight();
p.y -= 3 * r.GetHeight() / 5;
return p;
}
return r.TopRight();
}
Font RichEdit::GetPreeditFont()
{
Font fnt = formatinfo;
int h = abs(fnt.GetHeight());
if(formatinfo.sscript)
h = 3 * h / 5;
return fnt(max(GetZoom() * abs(h), 1));
}
void RichEdit::ApplyFormat(dword charvalid, dword paravalid)
{
if(IsReadOnly())

View file

@ -223,7 +223,8 @@ public:
virtual void DragRepeat(Point p);
virtual void DragLeave();
virtual String GetSelectionData(const String& fmt) const;
virtual Point GetPreedit();
virtual Font GetPreeditFont();
private:
virtual int GetCharAt(int64 i) const { return GetChar((int)i); }