diff --git a/uppsrc/Core/Range.h b/uppsrc/Core/Range.h index 3f308b1b9..02ff21c5b 100644 --- a/uppsrc/Core/Range.h +++ b/uppsrc/Core/Range.h @@ -1,11 +1,14 @@ -template -using ValueTypeOf = typename std::remove_reference::type *)0)->begin())>::type; +template +T *DeclPtr__(); template -using IteratorOf = decltype(((typename std::remove_reference::type *)0)->begin()); +using ValueTypeOf = typename std::remove_reference::type>()->begin())>::type; template -using ConstIteratorOf = decltype(((const typename std::remove_reference::type *)0)->begin()); +using IteratorOf = decltype(DeclPtr__::type>()->begin()); + +template +using ConstIteratorOf = decltype(DeclPtr__::type>()->begin()); template class SubRangeClass { @@ -16,7 +19,7 @@ public: typedef typename std::remove_reference::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 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 Iterator; - + Iterator begin() const { return Iterator(*this, 0); } Iterator end() const { return Iterator(*this, count); } @@ -102,14 +105,14 @@ ConstRangeClass ConstRange(int count) template struct ReverseRangeClass { typename std::remove_reference::type& r; - + typedef ValueTypeOf 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 Iterator; typedef ConstIIterator 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 struct ViewRangeClass { typename std::remove_reference::type *r; Vector ndx; - + typedef ValueTypeOf 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 Iterator; typedef ConstIIterator 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 ViewRangeClass SortedRange(BaseRange&& r) { return ViewRangeClass(r, GetSortOrder(r)); -} +} \ No newline at end of file diff --git a/uppsrc/Core/Topt.h b/uppsrc/Core/Topt.h index 2620d49bc..6115da719 100644 --- a/uppsrc/Core/Topt.h +++ b/uppsrc/Core/Topt.h @@ -323,8 +323,11 @@ public: void pop_back() { Drop(); } \ +template +T *DeclPtr__(); + template -using ValueTypeOfArray = typename std::remove_reference::type; +using ValueTypeOfArray = typename std::remove_reference())[0])>::type; template class ConstIIterator { diff --git a/uppsrc/CtrlCore/CocoCtrl.h b/uppsrc/CtrlCore/CocoCtrl.h index 230480e7d..7288050fb 100644 --- a/uppsrc/CtrlCore/CocoCtrl.h +++ b/uppsrc/CtrlCore/CocoCtrl.h @@ -10,7 +10,7 @@ private: static Ptr 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() {} diff --git a/uppsrc/CtrlCore/CocoMM.h b/uppsrc/CtrlCore/CocoMM.h index f0b45d6c0..900081660 100644 --- a/uppsrc/CtrlCore/CocoMM.h +++ b/uppsrc/CtrlCore/CocoMM.h @@ -76,9 +76,11 @@ struct RectCG { RectCG(); }; +NSRect DesktopRect(const Upp::Rect& r); + } -@interface CocoView : NSView +@interface CocoView : NSView { @public Upp::Ptr ctrl; diff --git a/uppsrc/CtrlCore/CocoProc.mm b/uppsrc/CtrlCore/CocoProc.mm index 49e237f35..c81a8063e 100644 --- a/uppsrc/CtrlCore/CocoProc.mm +++ b/uppsrc/CtrlCore/CocoProc.mm @@ -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 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 diff --git a/uppsrc/CtrlCore/CocoWin.mm b/uppsrc/CtrlCore/CocoWin.mm index 927d299db..bb432d6f9 100644 --- a/uppsrc/CtrlCore/CocoWin.mm +++ b/uppsrc/CtrlCore/CocoWin.mm @@ -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(); diff --git a/uppsrc/CtrlCore/Ctrl.cpp b/uppsrc/CtrlCore/Ctrl.cpp index 80cefea74..ca00a80be 100644 --- a/uppsrc/CtrlCore/Ctrl.cpp +++ b/uppsrc/CtrlCore/Ctrl.cpp @@ -339,12 +339,14 @@ void Ctrl::SetModify() { GuiLock __; modify = true; + CancelMyPreedit(); } void Ctrl::ClearModify() { GuiLock __; modify = false; + CancelMyPreedit(); } void Ctrl::ClearModifyDeep() diff --git a/uppsrc/CtrlCore/CtrlCore.h b/uppsrc/CtrlCore/CtrlCore.h index 134fad2cf..f73cc663d 100644 --- a/uppsrc/CtrlCore/CtrlCore.h +++ b/uppsrc/CtrlCore/CtrlCore.h @@ -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(); } diff --git a/uppsrc/CtrlCore/CtrlCore.upp b/uppsrc/CtrlCore/CtrlCore.upp index 219f960dd..8ecb2a825 100644 --- a/uppsrc/CtrlCore/CtrlCore.upp +++ b/uppsrc/CtrlCore/CtrlCore.upp @@ -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, diff --git a/uppsrc/CtrlCore/CtrlKbd.cpp b/uppsrc/CtrlCore/CtrlKbd.cpp index fab0196d4..d46778fad 100644 --- a/uppsrc/CtrlCore/CtrlKbd.cpp +++ b/uppsrc/CtrlCore/CtrlKbd.cpp @@ -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() diff --git a/uppsrc/CtrlCore/Gtk.h b/uppsrc/CtrlCore/Gtk.h index 281464c20..3334cd3c3 100644 --- a/uppsrc/CtrlCore/Gtk.h +++ b/uppsrc/CtrlCore/Gtk.h @@ -183,6 +183,8 @@ Vector 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; \ //$ } diff --git a/uppsrc/CtrlCore/GtkApp.cpp b/uppsrc/CtrlCore/GtkApp.cpp index a45bb9676..0d2a7f992 100644 --- a/uppsrc/CtrlCore/GtkApp.cpp +++ b/uppsrc/CtrlCore/GtkApp.cpp @@ -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 diff --git a/uppsrc/CtrlCore/GtkCreate.cpp b/uppsrc/CtrlCore/GtkCreate.cpp index 9f4d52bc0..f03617ee9 100644 --- a/uppsrc/CtrlCore/GtkCreate.cpp +++ b/uppsrc/CtrlCore/GtkCreate.cpp @@ -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 \ No newline at end of file diff --git a/uppsrc/CtrlCore/GtkCtrl.h b/uppsrc/CtrlCore/GtkCtrl.h index c63c248bb..f4c25923e 100644 --- a/uppsrc/CtrlCore/GtkCtrl.h +++ b/uppsrc/CtrlCore/GtkCtrl.h @@ -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(); diff --git a/uppsrc/CtrlCore/GtkEvent.cpp b/uppsrc/CtrlCore/GtkEvent.cpp index 55b56d254..163c0ed9b 100644 --- a/uppsrc/CtrlCore/GtkEvent.cpp +++ b/uppsrc/CtrlCore/GtkEvent.cpp @@ -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 \ No newline at end of file diff --git a/uppsrc/CtrlCore/GtkWnd.cpp b/uppsrc/CtrlCore/GtkWnd.cpp index a58cc4f1e..ccae03f1c 100644 --- a/uppsrc/CtrlCore/GtkWnd.cpp +++ b/uppsrc/CtrlCore/GtkWnd.cpp @@ -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) diff --git a/uppsrc/CtrlCore/Preedit.cpp b/uppsrc/CtrlCore/Preedit.cpp new file mode 100644 index 000000000..dd5a1029b --- /dev/null +++ b/uppsrc/CtrlCore/Preedit.cpp @@ -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(); + 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(); + 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(); + if(p.IsOpen()) { + p.Close(); + p.owner = NULL; + } +} + +void Ctrl::PreeditSync(void (*enable_preedit)(Ctrl *top, bool enable)) +{ // enables / disables preedit + static Ptr 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; +} + +}; \ No newline at end of file diff --git a/uppsrc/CtrlCore/Win32Ctrl.h b/uppsrc/CtrlCore/Win32Ctrl.h index 59b0663d7..b429fe49d 100644 --- a/uppsrc/CtrlCore/Win32Ctrl.h +++ b/uppsrc/CtrlCore/Win32Ctrl.h @@ -21,6 +21,7 @@ private: static void RenderFormat(int format); static void RenderAllFormats(); static void DestroyClipboard(); + static void DoCancelPreedit(); void UpdateDHCtrls(); diff --git a/uppsrc/CtrlCore/Win32Proc.cpp b/uppsrc/CtrlCore/Win32Proc.cpp index a18a72d83..1079ada78 100644 --- a/uppsrc/CtrlCore/Win32Proc.cpp +++ b/uppsrc/CtrlCore/Win32Proc.cpp @@ -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 _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 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) { diff --git a/uppsrc/CtrlCore/Win32Wnd.cpp b/uppsrc/CtrlCore/Win32Wnd.cpp index 894e4fd6c..8a0887095 100644 --- a/uppsrc/CtrlCore/Win32Wnd.cpp +++ b/uppsrc/CtrlCore/Win32Wnd.cpp @@ -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 " <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; diff --git a/uppsrc/CtrlLib/DocEdit.cpp b/uppsrc/CtrlLib/DocEdit.cpp index 0fc85fcaa..fac2d3a8e 100644 --- a/uppsrc/CtrlLib/DocEdit.cpp +++ b/uppsrc/CtrlLib/DocEdit.cpp @@ -254,6 +254,7 @@ void DocEdit::PlaceCaret(int64 newpos, bool select) { SelectionChanged(); if(IsSelection()) SetSelectionSource(ClipFmtsText()); + CancelMyPreedit(); } int DocEdit::GetMousePos(Point p) diff --git a/uppsrc/CtrlLib/EditField.cpp b/uppsrc/CtrlLib/EditField.cpp index c7277f5d7..e79a5a46b 100644 --- a/uppsrc/CtrlLib/EditField.cpp +++ b/uppsrc/CtrlLib/EditField.cpp @@ -427,6 +427,7 @@ void EditField::SelSource() SetSelectionSource(ClipFmtsText()); else fsell = fselh = -1; + CancelMyPreedit(); } void EditField::GotFocus() diff --git a/uppsrc/CtrlLib/LineEdit.cpp b/uppsrc/CtrlLib/LineEdit.cpp index efa771ff1..b0ac537a6 100644 --- a/uppsrc/CtrlLib/LineEdit.cpp +++ b/uppsrc/CtrlLib/LineEdit.cpp @@ -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); diff --git a/uppsrc/CtrlLib/TextEdit.h b/uppsrc/CtrlLib/TextEdit.h index df5298f63..1c7754213 100644 --- a/uppsrc/CtrlLib/TextEdit.h +++ b/uppsrc/CtrlLib/TextEdit.h @@ -298,6 +298,7 @@ public: virtual void DragLeave(); virtual void Layout(); virtual void RefreshLine(int i); + virtual Font GetPreeditFont(); protected: virtual void SetSb(); diff --git a/uppsrc/PdfDraw/PdfDraw.cpp b/uppsrc/PdfDraw/PdfDraw.cpp index bdaf7bf3e..c3dffbae9 100644 --- a/uppsrc/PdfDraw/PdfDraw.cpp +++ b/uppsrc/PdfDraw/PdfDraw.cpp @@ -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]); diff --git a/uppsrc/RichEdit/Cursor.cpp b/uppsrc/RichEdit/Cursor.cpp index bd5136bea..cea3c4275 100644 --- a/uppsrc/RichEdit/Cursor.cpp +++ b/uppsrc/RichEdit/Cursor.cpp @@ -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) diff --git a/uppsrc/RichEdit/Formating.cpp b/uppsrc/RichEdit/Formating.cpp index 091a1a6c2..1c0efe375 100644 --- a/uppsrc/RichEdit/Formating.cpp +++ b/uppsrc/RichEdit/Formating.cpp @@ -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()) diff --git a/uppsrc/RichEdit/RichEdit.h b/uppsrc/RichEdit/RichEdit.h index 7b9da8331..657f941b6 100644 --- a/uppsrc/RichEdit/RichEdit.h +++ b/uppsrc/RichEdit/RichEdit.h @@ -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); }