ultimatepp/uppsrc/CtrlCore/CocoProc.mm
2026-04-29 11:48:46 +02:00

602 lines
15 KiB
Text

#include "CocoMM.h"
#ifdef GUI_COCOA
NSEvent *sCurrentMouseEvent__; // needed for drag operation
#define LLOG(x) // DLOG(x)
static Upp::Point coco_mouse_pos;
static bool coco_mouse_left;
static bool coco_mouse_right;
static int coco_flags;
static Upp::Ptr<Upp::Ctrl> coco_capture;
Upp::Ptr<Upp::Ctrl> Upp::Ctrl::lastActive;
namespace Upp {
extern id menubar;
bool GetShift() { return coco_flags & NSEventModifierFlagShift; }
bool GetCtrl() { return coco_flags & NSEventModifierFlagCommand; }
bool GetAlt() { return coco_flags & NSEventModifierFlagControl; }
bool GetOption() { return coco_flags & NSEventModifierFlagOption; }
bool GetCapsLock() { return coco_flags & NSEventModifierFlagCapsLock; }
/*
NSEventModifierFlagOption
NSEventModifierFlagNumericPad
NSEventModifierFlagHelp
NSEventModifierFlagFunction
NSEventModifierFlagDeviceIndependentFlagsMask
*/
bool GetMouseLeft() { return coco_mouse_left; }
bool GetMouseRight() { return coco_mouse_right; }
bool GetMouseMiddle() { return false; } // TODO
Point GetMousePos() { return coco_mouse_pos; } // TODO: read it if no mouse events
void Ctrl::WndEnable(bool)
{
GuiLock __;
// empty - we are testing the status in events....
}
bool Ctrl::SetWndCapture()
{
GuiLock __;
LLOG("SetWndCapture");
coco_capture = this;
return true; // TODO: Always?
}
bool Ctrl::HasWndCapture() const
{
GuiLock __;
return coco_capture == this;
}
bool Ctrl::ReleaseWndCapture()
{
GuiLock __;
LLOG("ReleaseWndCapture");
coco_capture = NULL;
return true;
}
void Ctrl::ResetCocoaMouse()
{
ReleaseCtrlCapture();
coco_mouse_left = coco_mouse_right = false;
}
struct MMImp {
static bool KeyFlags(Upp::Ctrl *ctrl, NSEvent *e) {
bool alt = GetAlt();
bool ctl = GetCtrl();
bool sht = GetShift();
bool opt = GetOption();
Flags(e);
if(!ctrl->IsEnabled())
return false;
if(alt != GetAlt())
ctrl->DispatchKey(K_ALT_KEY|(alt * K_KEYUP), 1);
if(ctl != GetCtrl())
ctrl->DispatchKey(K_CTRL_KEY|(ctl * K_KEYUP), 1);
if(sht != GetShift())
ctrl->DispatchKey(K_SHIFT_KEY|(sht * K_KEYUP), 1);
if(opt != GetOption())
ctrl->DispatchKey(K_OPTION_KEY|(opt * K_KEYUP), 1);
return true;
}
static void Flags(NSEvent *e)
{
coco_flags = [e modifierFlags];
}
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) {
Ctrl::KillRepeat();
Ctrl::ignoreclick = false;
Ctrl::ignoremouseup = false;
return false;
}
if(GetAlt()) { // Emulate right click
if(event == Ctrl::LEFTDOWN) {
coco_mouse_left = false;
coco_mouse_right = true;
event = Ctrl::RIGHTDOWN;
}
if(event == Ctrl::LEFTUP) {
coco_mouse_right = false;
event = Ctrl::RIGHTUP;
}
}
NSPoint np = [view convertPoint:[e locationInWindow] fromView:nil];
Rect r = view->ctrl->GetRect();
Upp::Point p(Upp::Ctrl::SCL(np.x), Upp::Ctrl::SCL(np.y));
coco_mouse_pos = p + r.TopLeft();
if(event == Ctrl::MOUSEMOVE) {
static Point coco_mouse_pos_old(-10000, -10000);
if(coco_mouse_pos_old == coco_mouse_pos) { // duplicate for another window, ignore
sCurrentMouseEvent__ = NULL;
return false;
}
coco_mouse_pos_old = coco_mouse_pos;
if(coco_capture)
coco_capture->DispatchMouse(event, coco_mouse_pos - coco_capture->GetScreenRect().TopLeft(), 120 * sgn(zd));
else {
Vector<Ctrl *> t = Ctrl::GetTopCtrls(); // Find window that contains the mouse, from the top
for(NSNumber *num in [NSWindow windowNumbersWithOptions:0]) { // All app windows
NSWindow *win = [NSApp windowWithWindowNumber:[num integerValue]];
if(win) {
int q = FindMatch(t, [&](Ctrl *t) { return t->GetNSWindow() == win; });
if(q >= 0) {
Ctrl *w = t[q];
Rect r = w->GetRect(); // same as ScreenRect
if(w->IsEnabled() && r.Contains(coco_mouse_pos)) {
w->DispatchMouse(event, coco_mouse_pos - r.TopLeft(), 120 * sgn(zd));
break;
}
}
}
}
}
}
else
if(view->ctrl->IsEnabled() && (view->ctrl->HasWndCapture() || r.Contains(coco_mouse_pos))) {
if((event & Ctrl::ACTION) == Ctrl::DOWN && !view->ctrl->HasFocusDeep() && view->ctrl->IsWantFocus())
view->ctrl->SetFocus();
view->ctrl->DispatchMouse(event, p, 120 * sgn(zd));
}
sCurrentMouseEvent__ = NULL;
return false;
}
static bool MouseDownEvent(CocoView *view, NSEvent *e, int button)
{
if(!view->ctrl)
return false;
Upp::Ctrl::lastActive = view->ctrl;
if(Ctrl::ignoremouseup) {
Ctrl::KillRepeat();
Ctrl::ignoreclick = false;
Ctrl::ignoremouseup = false;
}
if(Ctrl::ignoreclick)
return false;
static int clicktime = msecs() - 100000;
static CocoView *dbl_view;
static NSPoint dbl_pos;
NSPoint np = [view convertPoint:[e locationInWindow] fromView:nil];
bool b;
if(msecs(clicktime) < GUI_DblClickTime() && view == dbl_view &&
max(abs(np.x - dbl_pos.x), abs(np.y - dbl_pos.y)) < 3) {
b = MouseEvent(view, e, button|Upp::Ctrl::DOUBLE);
clicktime = msecs() - 999999;
}
else {
b = MouseEvent(view, e, button|Upp::Ctrl::DOWN);
clicktime = msecs();
}
dbl_view = view;
dbl_pos = np;
return b;
}
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;
Upp::dword k = e.keyCode;
WString x = ToWString((CFStringRef)(e.charactersIgnoringModifiers));
if(x.GetCount() == 1)
switch(ToUpper(x[0])) {
#define KEY(c) case #c[0]: k = kVK_ANSI_##c; break;
KEY(A); KEY(B); KEY(C); KEY(D); KEY(E); KEY(F); KEY(G); KEY(H); KEY(I); KEY(J);
KEY(K); KEY(L); KEY(M); KEY(N); KEY(O); KEY(P); KEY(Q); KEY(R); KEY(S); KEY(T);
KEY(U); KEY(V); KEY(W); KEY(X); KEY(Y); KEY(Z);
}
#undef KEY
k = decode(k, kVK_ANSI_KeypadEnter, K_ENTER, kVK_Tab, K_TAB, K_DELTA|k)|up;
if(GetCtrl())
k |= K_CTRL;
if(GetShift())
k |= K_SHIFT;
if(GetAlt())
k |= K_ALT;
if(GetOption())
k |= K_OPTION;
if(e.keyCode == kVK_Help) // TODO: This is Insert key, but all this is dubious
ctrl->DispatchKey(k & ~K_KEYUP, 1);
ctrl->DispatchKey(k, 1);
if(!up && !(k & (K_CTRL|K_ALT))) {
WString x = ToWString((CFStringRef)(e.characters));
if(e.keyCode == kVK_ANSI_KeypadEnter && *x != 13)
ctrl->DispatchKey(13, 1);
if(e.keyCode == kVK_Space && !(k & K_SHIFT))
ctrl->DispatchKey(' ', 1);
}
return true;
}
static void DispatchKey(Upp::Ctrl *ctrl, int k)
{ // just to solve prive by MMImp being friend...
ctrl->DispatchKey(k, 1);
}
static void BecomeKey(Upp::Ctrl *ctrl)
{
if(!ctrl)
return;
LLOG("Become key " << Upp::Name(ctrl));
Upp::Ctrl::lastActive = ctrl;
ctrl->ActivateWnd();
auto tw = dynamic_cast<TopWindow *>(ctrl);
if(tw && tw->placefocus) {
tw->PlaceFocus();
tw->placefocus = false;
}
Upp::Ctrl::SyncAppIcon();
Upp::TopWindow::SyncMainMenu(false);
Upp::Ctrl::ReleaseCtrlCapture();
}
static void ResignKey(Upp::Ctrl *ctrl)
{
if(!ctrl)
return;
LLOG("Resign key " << Upp::Name(ctrl));
ctrl->KillFocusWnd();
Upp::Ctrl::ReleaseCtrlCapture();
}
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;
clip.paste = paste;
clip.accepted = false;
clip.allowed = DND_MOVE|DND_COPY; // TODO: Use draggingSourceOperationMask
clip.action = info.draggingSourceOperationMask & NSDragOperationMove ? DND_MOVE
: DND_COPY;
NSPoint np = [nsview convertPoint:[info draggingLocation] fromView:nil];
coco_mouse_pos = Upp::Point(Upp::Ctrl::SCL(np.x), Upp::Ctrl::SCL(np.y)) + ctrl->GetScreenRect().TopLeft();
ctrl->DnD(coco_mouse_pos, clip);
if(paste && clip.IsAccepted() && clip.GetAction() == DND_COPY)
Ctrl::local_dnd_copy = true;
return clip.IsAccepted() ? clip.GetAction() == DND_MOVE ? NSDragOperationMove
: NSDragOperationCopy
: NSDragOperationNone;
}
static void DnDLeave(Ctrl *ctrl)
{
ctrl->DnDLeave();
}
static void DoCursorShape()
{
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)
if(ch >= 32 && ch != 127 && ch != ' ')
ctrl->DispatchKey(ch, 1);
}
static void CancelPreedit()
{
Ctrl::HidePreedit();
}
};
};
@implementation CocoView
-(BOOL)isFlipped {
return YES;
}
-(void)drawRect:(NSRect)r {
Upp::GuiLock __;
if(ctrl) {
Upp::SystemDraw w([[NSGraphicsContext currentContext] CGContext], self);
Upp::MMImp::Paint(ctrl, w, MakeRect(r, Upp::Ctrl::SCL(1)));
}
}
- (void)mouseDown:(NSEvent *)e {
Upp::GuiLock __;
coco_mouse_left = true;
if(!Upp::MMImp::MouseDownEvent(self, e, Upp::Ctrl::LEFT))
[super mouseDown:e];
}
- (void)otherMouseDown:(NSEvent*)e {
Upp::GuiLock __;
int m = decode([e buttonNumber], 3, Upp::K_MOUSE_BACKWARD, 4, Upp::K_MOUSE_FORWARD, 0);
if(m)
Upp::MMImp::DispatchKey(ctrl, m);
else
[super otherMouseDown:e];
}
- (void)mouseUp:(NSEvent *)e {
Upp::GuiLock __;
coco_mouse_left = false;
if(!Upp::MMImp::MouseEvent(self, e, Upp::Ctrl::LEFTUP))
[super mouseUp:e];
Upp::Ctrl::ReleaseCtrlCapture();
}
- (void)mouseMoved:(NSEvent *)e {
Upp::GuiLock __;
if(!Upp::MMImp::MouseEvent(self, e, Upp::Ctrl::MOUSEMOVE))
[super mouseMoved:e];
}
- (void)mouseDragged:(NSEvent *)e {
Upp::GuiLock __;
if(!Upp::MMImp::MouseEvent(self, e, Upp::Ctrl::MOUSEMOVE))
[super mouseDragged:e];
}
- (void)rightMouseDown:(NSEvent*)e {
Upp::GuiLock __;
coco_mouse_right = true;
if(!Upp::MMImp::MouseDownEvent(self, e, Upp::Ctrl::RIGHT))
[super rightMouseDown:e];
}
- (void)rightMouseUp:(NSEvent*)e {
Upp::GuiLock __;
coco_mouse_right = false;
if(!Upp::MMImp::MouseEvent(self, e, Upp::Ctrl::RIGHTUP))
[super rightMouseUp:e];
}
- (void)scrollWheel:(NSEvent *)e {
Upp::GuiLock __;
int dy = [e deltaY];
int dx = [e deltaX];
if(!(dy && Upp::MMImp::MouseEvent(self, e, Upp::Ctrl::MOUSEWHEEL, dy)) &&
!(dx && Upp::MMImp::MouseEvent(self, e, Upp::Ctrl::MOUSEHWHEEL, dx)))
[super scrollWheel:e];
}
- (void)keyDown:(NSEvent *)e {
Upp::GuiLock __;
[self interpretKeyEvents: [NSArray arrayWithObject: e]];
if(!Upp::MMImp::KeyEvent(ctrl, e, 0))
[super keyDown:e];
}
- (void)keyUp:(NSEvent *)e {
Upp::GuiLock __;
if(!Upp::MMImp::KeyEvent(ctrl, e, Upp::K_KEYUP))
[super keyUp:e];
}
- (void)flagsChanged:(NSEvent *)e {
Upp::GuiLock __;
if(!Upp::MMImp::KeyFlags(ctrl, e))
[super flagsChanged:e];
}
- (void)cursorUpdate:(NSEvent *)event {
Upp::GuiLock __;
Upp::MMImp::Flags(event);
Upp::MMImp::DoCursorShape();
}
- (BOOL)windowShouldClose:(NSWindow *)sender {
Upp::GuiLock __;
if(ctrl->IsEnabled())
Upp::MMImp::DoClose(ctrl);
return NO;
}
- (void)windowDidResize:(NSNotification *)notification {
Upp::GuiLock __;
Upp::MMCtrl::SyncRect(self);
}
- (void)windowDidMove:(NSNotification *)notification {
Upp::GuiLock __;
Upp::MMCtrl::SyncRect(self);
}
- (void)windowDidBecomeKey:(NSNotification *)notification {
Upp::GuiLock __;
Upp::MMImp::BecomeKey(ctrl);
}
- (void)windowDidResignKey:(NSNotification *)notification {
Upp::GuiLock __;
Upp::MMImp::ResignKey(ctrl);
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (BOOL)canBecomeKeyView {
Upp::GuiLock __;
return ctrl->IsEnabled();
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
Upp::GuiLock __;
return Upp::MMImp::DnD(ctrl, sender);
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
Upp::GuiLock __;
return Upp::MMImp::DnD(ctrl, sender);
}
- (void)draggingEnded:(id <NSDraggingInfo>)sender
{
Upp::GuiLock __;
Upp::MMImp::DnDLeave(ctrl);
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
Upp::GuiLock __;
Upp::MMImp::DnDLeave(ctrl);
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
Upp::GuiLock __;
return Upp::MMImp::DnD(ctrl, sender, true) != NSDragOperationNone;
}
- (void)updateTrackingAreas
{
Upp::GuiLock __;
for(NSTrackingArea *t in [self trackingAreas])
[self removeTrackingArea:t];
Upp::Size sz = ctrl->GetScreenRect().GetSize();
NSTrackingArea *ta = [[NSTrackingArea alloc]
initWithRect:NSMakeRect(0, 0, sz.cx, sz.cy)
options:NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways|
NSTrackingInVisibleRect|NSTrackingMouseMoved|NSTrackingCursorUpdate
owner:self
userInfo:nil];
[self addTrackingArea:ta];
[ta release];
}
// NSTextInputClient methods
-(void)insertText:(id)aString replacementRange:(NSRange)replacementRange
{
Upp::GuiLock __;
(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
{
Upp::GuiLock __;
return Upp::MMImp::PreeditRect(ctrl);
}
- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
{
Upp::GuiLock __;
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::GuiLock __;
Upp::MMImp::CancelPreedit();
}
- (NSArray *)validAttributesForMarkedText
{
Upp::GuiLock __;
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