From 33852f8c1b749e7b76b7f521a4e82b5b1c757b6b Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Sun, 3 May 2026 18:53:14 +0800 Subject: [PATCH] Fixes for powerpc-darwin --- uppsrc/Core/Defs.h | 11 ++ uppsrc/Core/HeapImp.h | 4 +- uppsrc/Core/JSON.h | 18 ++- uppsrc/Core/Ops.h | 24 +++- uppsrc/Core/Value.cpp | 11 +- uppsrc/Core/Value.h | 32 ++++- uppsrc/Core/ValueUtil.h | 22 +-- uppsrc/Core/Xmlize.h | 14 +- uppsrc/Core/config.h | 23 ++- uppsrc/CtrlCore/CocoApp.mm | 133 +++++++++++++++-- uppsrc/CtrlCore/CocoClip.mm | 86 ++++++++--- uppsrc/CtrlCore/CocoDraw.mm | 3 +- uppsrc/CtrlCore/CocoDrawOp.mm | 9 +- uppsrc/CtrlCore/CocoImage.mm | 46 ++++-- uppsrc/CtrlCore/CocoMM.h | 156 ++++++++++++++++++-- uppsrc/CtrlCore/CocoProc.mm | 160 +++++++++++++++------ uppsrc/CtrlCore/CocoWin.mm | 174 ++++++++++++++++++----- uppsrc/CtrlLib/ChCocoMM.mm | 221 ++++++++++++++++++----------- uppsrc/CtrlLib/Cocoa.mm | 18 ++- uppsrc/CtrlLib/MacMenu.mm | 205 ++++++++++++++++++-------- uppsrc/Draw/Drawing.cpp | 14 +- uppsrc/Draw/FontCoco.mm | 27 +++- uppsrc/ide/Builders/Cocoa.cpp | 6 +- uppsrc/ide/Builders/Install.cpp | 52 +++++-- uppsrc/plugin/bmp/_bmp.h | 47 +++--- uppsrc/plugin/bmp/bmphdr.h | 56 ++++++-- uppsrc/plugin/pcx/pcxhdr.h | 21 ++- uppsrc/plugin/tif/lib/tif_config.h | 2 + 28 files changed, 1195 insertions(+), 400 deletions(-) diff --git a/uppsrc/Core/Defs.h b/uppsrc/Core/Defs.h index 8e36c19f4..3fd1d147f 100644 --- a/uppsrc/Core/Defs.h +++ b/uppsrc/Core/Defs.h @@ -198,12 +198,23 @@ inline bool IsFin(double d) { return !IsNaN(d) && !IsInf(d); } #define HIWORD(a) (word)((a) >> 16) #define LOWORD(a) word(a) +#ifdef CPU_LE #define MAKEWORD(l, h) ((word) (((byte) (l)) | ((word) ((byte) (h))) << 8)) #define MAKELONG(l, h) ((dword) (((word) (l)) | ((dword) ((word) (h))) << 16)) +#else +// Big-endian: swap byte positions to maintain chr[] array index compatibility +#define MAKEWORD(l, h) ((word) (((byte) (h)) | ((word) ((byte) (l))) << 8)) +#define MAKELONG(l, h) ((dword) (((word) (h)) | ((dword) ((word) (l))) << 16)) +#endif #endif +#ifdef CPU_LE #define MAKEQWORD(l, h) ((qword) (((dword) (l)) | ((qword) ((dword) (h))) << 32)) +#else +// Big-endian: swap dword positions to maintain chr[] array index compatibility +#define MAKEQWORD(l, h) ((qword) (((dword) (h)) | ((qword) ((dword) (l))) << 32)) +#endif #define HIDWORD(a) (dword)(((uint64)a) >> 32) #define LODWORD(a) dword(a) diff --git a/uppsrc/Core/HeapImp.h b/uppsrc/Core/HeapImp.h index 2cebe43d8..6ac73940d 100644 --- a/uppsrc/Core/HeapImp.h +++ b/uppsrc/Core/HeapImp.h @@ -11,8 +11,8 @@ struct Heap; struct BlkPrefix { // this part is at the start of Blk allocated block, client must not touch it word prev_size; word size; - bool free; - bool last; + byte free; // Use byte instead of bool for consistent 1-byte size across platforms + byte last; // Use byte instead of bool for consistent 1-byte size across platforms Heap *heap; // we need this for 4KB pages and large blocks, NULL for Huge blocks #ifdef CPU_32 dword filler; diff --git a/uppsrc/Core/JSON.h b/uppsrc/Core/JSON.h index 34014a917..0828f531f 100644 --- a/uppsrc/Core/JSON.h +++ b/uppsrc/Core/JSON.h @@ -281,6 +281,14 @@ String StoreAsJson(const T& var, bool pretty = false) template bool LoadFromJson(T& var, const char *json) { +#if defined(__OBJC__) && defined(__GNUC__) && !defined(__clang__) + // Workaround for GCC ICE in objc_eh_runtime_type when compiling Objective-C++ + Value jv = ParseJSON(json); + if(jv.IsError()) + return false; + LoadFromJsonValue(var, jv); + return true; +#else try { Value jv = ParseJSON(json); if(jv.IsError()) @@ -294,6 +302,7 @@ bool LoadFromJson(T& var, const char *json) return false; } return true; +#endif } String sJsonFile(const char *file); @@ -454,13 +463,20 @@ void JsonizeBySerialize(JsonIO& jio, T& x) if(jio.IsStoring()) h = HexString(StoreAsString(x)); jio("data", h); - if(jio.IsLoading()) + if(jio.IsLoading()) { +#if defined(__OBJC__) && defined(__GNUC__) && !defined(__clang__) + // Workaround for GCC ICE in objc_eh_runtime_type when compiling Objective-C++ + // with C++ exception handling in templates (GCC bug with ObjC ABI 1) + LoadFromString(x, ScanHexString(h)); +#else try { LoadFromString(x, ScanHexString(h)); } catch(LoadingError) { throw JsonizeError("jsonize by serialize error"); } +#endif + } } template diff --git a/uppsrc/Core/Ops.h b/uppsrc/Core/Ops.h index 4beb3317a..f91be5f1f 100644 --- a/uppsrc/Core/Ops.h +++ b/uppsrc/Core/Ops.h @@ -1,7 +1,3 @@ -#ifndef CPU_LE -#error Only little endian CPUs supported -#endif - #if defined(CPU_X86) && defined(COMPILER_MSC) #ifdef COMPILER_GCC @@ -101,7 +97,7 @@ void EndianSwap(int *v, size_t count); void EndianSwap(int64 *v, size_t count); void EndianSwap(uint64 *v, size_t count); -// unligned access - memcpy converts to simple load/store with normal compilers +// unaligned access - memcpy converts to simple load/store with normal compilers inline int Peek16(const void *ptr) { word x; memcpy(&x, ptr, 2); return x; } inline int Peek32(const void *ptr) { dword x; memcpy(&x, ptr, 4); return x; } @@ -111,6 +107,7 @@ inline void Poke16(void *ptr, word val) { memcpy(ptr, &val, 2); } inline void Poke32(void *ptr, dword val) { memcpy(ptr, &val, 4); } inline void Poke64(void *ptr, int64 val) { memcpy(ptr, &val, 8); } +#ifdef CPU_LE inline int Peek16le(const void *ptr) { return Peek16(ptr); } inline int Peek32le(const void *ptr) { return Peek32(ptr); } inline int64 Peek64le(const void *ptr) { return Peek64(ptr); } @@ -126,6 +123,23 @@ inline int64 Peek64be(const void *ptr) { return SwapEndian64(Peek64(ptr)); inline void Poke16be(void *ptr, word val) { Poke16(ptr, SwapEndian16(val)); } inline void Poke32be(void *ptr, dword val) { Poke32(ptr, SwapEndian32(val)); } inline void Poke64be(void *ptr, int64 val) { Poke64(ptr, SwapEndian64(val)); } +#else +inline int Peek16le(const void *ptr) { return SwapEndian16(Peek16(ptr)); } +inline int Peek32le(const void *ptr) { return SwapEndian32(Peek32(ptr)); } +inline int64 Peek64le(const void *ptr) { return SwapEndian64(Peek64(ptr)); } + +inline void Poke16le(void *ptr, word val) { Poke16(ptr, SwapEndian16(val)); } +inline void Poke32le(void *ptr, dword val) { Poke32(ptr, SwapEndian32(val)); } +inline void Poke64le(void *ptr, int64 val) { Poke64(ptr, SwapEndian64(val)); } + +inline int Peek16be(const void *ptr) { return Peek16(ptr); } +inline int Peek32be(const void *ptr) { return Peek32(ptr); } +inline int64 Peek64be(const void *ptr) { return Peek64(ptr); } + +inline void Poke16be(void *ptr, word val) { Poke16(ptr, val); } +inline void Poke32be(void *ptr, dword val) { Poke32(ptr, val); } +inline void Poke64be(void *ptr, int64 val) { Poke64(ptr, val); } +#endif #define MAKE2B(b0, b1) MAKEWORD(b0, b1) #define MAKE4B(b0, b1, b2, b3) MAKELONG(MAKEWORD(b0, b1), MAKEWORD(b2, b3)) diff --git a/uppsrc/Core/Value.cpp b/uppsrc/Core/Value.cpp index 08513f60e..b5404d93f 100644 --- a/uppsrc/Core/Value.cpp +++ b/uppsrc/Core/Value.cpp @@ -649,7 +649,7 @@ Vector& Value::UnShareArray() ValueArray::Data *d = new ValueArray::Data; d->data = clone(data->data); data->Release(); - ptr() = d; + SetPtr(d); data = d; } return data->data; @@ -662,7 +662,9 @@ Value& Value::At(int i) ASSERT(i >= 0 && IsRef()); dword t = GetRefType(); if(t == VALUEMAP_V) { - ValueArray& va = ValueMap::UnShare((ValueMap::Data*&)ptr()).value; + ValueMap::Data *p = (ValueMap::Data *)ptr(); + ValueArray& va = ValueMap::UnShare(p).value; + SetPtr(p); ASSERT(i < va.GetCount()); return va.At(i); } @@ -706,7 +708,10 @@ Value& Value::GetAdd(const Value& key) *this = m; } ASSERT(GetType() == VALUEMAP_V); - return ValueMap::UnShare((ValueMap::Data*&)ptr()).GetAdd(key); + ValueMap::Data *p = (ValueMap::Data *)ptr(); + Value& result = ValueMap::UnShare(p).GetAdd(key); + SetPtr(p); + return result; } Value& Value::operator()(const String& key) diff --git a/uppsrc/Core/Value.h b/uppsrc/Core/Value.h index 61ee3d82c..4a386ca3e 100644 --- a/uppsrc/Core/Value.h +++ b/uppsrc/Core/Value.h @@ -134,16 +134,38 @@ protected: friend void ValueRegisterHelper(); String data; - Void *&ptr() { ASSERT(IsRef()); return *(Void **)&data; } - Void *ptr() const { ASSERT(IsRef()); return *(Void **)&data; } - void SetRefType(dword type) { ASSERT(IsRef()); ((int *)&data)[2] = type; } - dword GetRefType() const { ASSERT(IsRef()); return ((int *)&data)[2]; } + // Use memcpy for endianness-safe pointer access - avoid type punning + void SetPtr(Void *p) { + ASSERT(IsRef()); + memcpy(&data, &p, sizeof(Void*)); + } + Void* GetPtr() const { + ASSERT(IsRef()); + Void* p; + memcpy(&p, &data, sizeof(Void*)); + return p; + } + // Legacy accessor - returns pointer value (not reference) + Void* ptr() const { return GetPtr(); } + + void SetRefType(dword type) { + ASSERT(IsRef()); + // Use memcpy for endianness-safe access - store type at offset 8 + memcpy((char *)&data + 8, &type, sizeof(dword)); + } + dword GetRefType() const { + ASSERT(IsRef()); + // Use memcpy for endianness-safe access - read type from offset 8 + dword type; + memcpy(&type, (const char *)&data + 8, sizeof(dword)); + return type; + } bool IsString() const { return !data.IsSpecial(); } bool Is(byte v) const { return data.IsSpecial(v); } bool IsRef() const { return Is(REF); } - void InitRef(Void *p, dword t) { data.SetSpecial(REF); ptr() = p; SetRefType(t); } + void InitRef(Void *p, dword t) { data.SetSpecial(REF); SetPtr(p); SetRefType(t); } void RefRelease(); void RefRetain(); void FreeRef() { if(IsRef()) RefRelease(); } diff --git a/uppsrc/Core/ValueUtil.h b/uppsrc/Core/ValueUtil.h index d6aac7712..e5cb30a82 100644 --- a/uppsrc/Core/ValueUtil.h +++ b/uppsrc/Core/ValueUtil.h @@ -75,24 +75,24 @@ struct FnValuePairOrder : ValuePairOrder { int CompareStrings(const Value& a, const Value& b, const LanguageInfo& f); // used by StdCompareValue class Id : Moveable { - String id; + String id_; // Renamed from 'id' to avoid Objective-C keyword conflict public: - const String& ToString() const { return id; } - hash_t GetHashValue() const { return UPP::GetHashValue(id); } - bool IsNull() const { return UPP::IsNull(id); } + const String& ToString() const { return id_; } + hash_t GetHashValue() const { return UPP::GetHashValue(id_); } + bool IsNull() const { return UPP::IsNull(id_); } operator const String&() const { return ToString(); } const String& operator~() const { return ToString(); } - bool operator==(const Id& b) const { return id == b.id; } - bool operator==(const String& b) const { return id == b; } - bool operator==(const char *b) const { return id == b; } - bool operator!=(const Id& b) const { return id != b.id; } - operator bool() const { return id.GetCount(); } + bool operator==(const Id& b) const { return id_ == b.id_; } + bool operator==(const String& b) const { return id_ == b; } + bool operator==(const char *b) const { return id_ == b; } + bool operator!=(const Id& b) const { return id_ != b.id_; } + operator bool() const { return id_.GetCount(); } Id() {} - Id(const String& s) { id = s; } - Id(const char *s) { id = s; } + Id(const String& s) { id_ = s; } + Id(const char *s) { id_ = s; } }; struct RefManager { diff --git a/uppsrc/Core/Xmlize.h b/uppsrc/Core/Xmlize.h index 4a15ca3cf..81d639c5b 100644 --- a/uppsrc/Core/Xmlize.h +++ b/uppsrc/Core/Xmlize.h @@ -213,13 +213,20 @@ void XmlizeBySerialize(XmlIO& xio, T& x) if(xio.IsStoring()) h = HexString(StoreAsString(x)); xio.Attr("data", h); - if(xio.IsLoading()) + if(xio.IsLoading()) { +#if defined(__OBJC__) && defined(__GNUC__) && !defined(__clang__) + // Workaround for GCC ICE in objc_eh_runtime_type when compiling Objective-C++ + // with C++ exception handling in templates (GCC bug with ObjC ABI 1) + LoadFromString(x, ScanHexString(h)); +#else try { LoadFromString(x, ScanHexString(h)); } catch(LoadingError) { throw XmlError("xmlize by serialize error"); } +#endif + } } void StoreJsonValue(XmlIO& xio, const Value& v); @@ -231,12 +238,17 @@ void XmlizeByJsonize(XmlIO& xio, T& x) if(xio.IsStoring()) StoreJsonValue(xio, StoreAsJsonValue(x)); else { +#if defined(__OBJC__) && defined(__GNUC__) && !defined(__clang__) + // Workaround for GCC ICE in objc_eh_runtime_type when compiling Objective-C++ + LoadFromJsonValue(x, LoadJsonValue(xio.Node())); +#else try { LoadFromJsonValue(x, LoadJsonValue(xio.Node())); } catch(JsonizeError e) { throw XmlError("xmlize by jsonize error: " + e); } +#endif } } diff --git a/uppsrc/Core/config.h b/uppsrc/Core/config.h index cf7d0f4e5..7b3d2c321 100644 --- a/uppsrc/Core/config.h +++ b/uppsrc/Core/config.h @@ -94,6 +94,21 @@ #define CPU_BE 1 #define CPU_BIG_ENDIAN 1 #define CPU_ALIGNED 1 + #elif __powerpc64__ || __ppc64__ + #define CPU_POWERPC 1 + #define CPU_PPC 1 + #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + #define CPU_BE 1 + #define CPU_BIG_ENDIAN 1 + #endif + #elif __powerpc__ || __ppc__ + #define CPU_32 1 + #define CPU_POWERPC 1 + #define CPU_PPC 1 + #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + #define CPU_BE 1 + #define CPU_BIG_ENDIAN 1 + #endif #elif __aarch64__ #define CPU_ARM 1 #ifdef __ARM_NEON @@ -156,12 +171,12 @@ #endif #ifdef CPU_BIG_ENDIAN -#error "Big endian CPUs are not supported anymore" + #define CPU_BE 1 +#else + #define CPU_LITTLE_ENDIAN 1 + #define CPU_LE 1 #endif -#define CPU_LITTLE_ENDIAN 1 -#define CPU_LE 1 - #ifndef CPU_32 #define CPU_64 1 #endif diff --git a/uppsrc/CtrlCore/CocoApp.mm b/uppsrc/CtrlCore/CocoApp.mm index 66d36fa59..c84a5bf05 100644 --- a/uppsrc/CtrlCore/CocoApp.mm +++ b/uppsrc/CtrlCore/CocoApp.mm @@ -4,13 +4,22 @@ #ifdef GUI_COCOA +// For CGEventTap (GCC block workaround) +#include + +// CocoMenuItemBarKey is declared extern in CocoMM.h and defined in CocoProc.mm + @interface AppDelegate : NSObject { } +// Menu action handler - receives actions from menu items +-(void)cocoMenuAction:(id)sender; @end +// Forward declarations for menu handling namespace Upp { NSMenu *Cocoa_DockMenu(); +void CocoMenuBarAction(void *bar, id sender); }; @implementation AppDelegate @@ -20,6 +29,19 @@ NSMenu *Cocoa_DockMenu(); return Upp::Cocoa_DockMenu(); } +-(void)cocoMenuAction:(id)sender { + Upp::GuiLock __; + NSMenuItem *item = (NSMenuItem *)sender; + void *bar = objc_getAssociatedObject(item, &CocoMenuItemBarKey); + if(bar) + Upp::CocoMenuBarAction(bar, sender); +} + +-(BOOL)validateMenuItem:(NSMenuItem *)menuItem { + // Enable all menu items that have us as target + return YES; +} + - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { Upp::GuiLock __; @@ -69,6 +91,52 @@ void SyncPopupFocus(NSWindow *win) } } +#ifndef __clang__ +// CGEventTap callback for GCC (replaces block-based NSEvent monitors) +// This provides global and local mouse event monitoring without using blocks +static CFMachPortRef sEventTap = NULL; +static CFRunLoopSourceRef sEventTapSource = NULL; + +static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) +{ + if(type == kCGEventLeftMouseDown) { + // Get the window under the mouse for local events + // For global events (outside our app), win will be NULL + CGPoint location = CGEventGetLocation(event); + // Check if the event is in our application + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + ProcessSerialNumber frontPsn; + GetFrontProcess(&frontPsn); + Boolean sameProcess = false; + SameProcess(&psn, &frontPsn, &sameProcess); + + SyncPopupFocus(sameProcess ? [NSApp keyWindow] : NULL); + } + // Return event unchanged to allow normal processing + return event; +} + +static void SetupEventTap() +{ + // Create event tap for left mouse down events + CGEventMask eventMask = CGEventMaskBit(kCGEventLeftMouseDown); + sEventTap = CGEventTapCreate(kCGSessionEventTap, + kCGHeadInsertEventTap, + kCGEventTapOptionListenOnly, // Don't modify events + eventMask, + EventTapCallback, + NULL); + if(sEventTap) { + sEventTapSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, sEventTap, 0); + if(sEventTapSource) { + CFRunLoopAddSource(CFRunLoopGetMain(), sEventTapSource, kCFRunLoopCommonModes); + CGEventTapEnable(sEventTap, true); + } + } +} +#endif + extern const char *sClipFmtsRTF; id menubar; @@ -103,18 +171,31 @@ void CocoInit(int argc, const char **argv, const char **envptr) Ctrl::SetUHDEnabled(true); bool uhd = true; - for (NSScreen *screen in [NSScreen screens]) { +#ifdef MAC_OS_X_VERSION_10_7 + // backingScaleFactor available in 10.7+ + NSArray *screens = [NSScreen screens]; + for (NSUInteger i = 0; i < [screens count]; i++) { + NSScreen *screen = [screens objectAtIndex:i]; if([screen backingScaleFactor] < 2) { uhd = false; break; } } +#else + // macOS 10.6: no Retina support, always non-UHD + uhd = false; +#endif SetUHDMode(uhd); Font::SetDefaultFont(StdFont(fceil(DPI([sysfont pointSize])))); - - GUI_DblClickTime_Write(1000 * NSEvent.doubleClickInterval); + GUI_DblClickTime_Write(1000 * [NSEvent doubleClickInterval]); + +#ifndef __clang__ + // GCC: use CGEventTap instead of block-based NSEvent monitors + SetupEventTap(); +#else + // Clang: use block-based event monitors [NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskLeftMouseDown) handler:^(NSEvent *e) { SyncPopupFocus(NULL); @@ -124,6 +205,7 @@ void CocoInit(int argc, const char **argv, const char **envptr) SyncPopupFocus([e window]); return e; }]; +#endif sClipFmtsRTF = "rtf"; @@ -135,13 +217,13 @@ void CocoInit(int argc, const char **argv, const char **envptr) int Ctrl::GetKbdDelay() { Upp::GuiLock __; - return int(1000 * NSEvent.keyRepeatDelay); + return int(1000 * [NSEvent keyRepeatDelay]); } int Ctrl::GetKbdSpeed() { Upp::GuiLock __; - return int(1000 * NSEvent.keyRepeatInterval); + return int(1000 * [NSEvent keyRepeatInterval]); } static NSEvent *current_event; @@ -299,8 +381,12 @@ Rect MakeScreenRect(NSScreen *screen, CGRect r) void Ctrl::GetWorkArea(Array& rc) { Upp::GuiLock __; - for(NSScreen *screen in [NSScreen screens]) - rc.Add(MakeScreenRect(screen, [screen visibleFrame])); + NSArray *screens = [NSScreen screens]; + for(NSUInteger i = 0; i < [screens count]; i++) { + NSScreen *screen = [screens objectAtIndex:i]; + NSRect frame = [screen visibleFrame]; + rc.Add(MakeScreenRect(screen, NSRectToCGRect(frame))); + } } @@ -321,8 +407,11 @@ Rect Ctrl::GetVirtualScreenArea() { bool first = true; Rect r(0, 0, 0, 0); - for(NSScreen *screen in [NSScreen screens]) { - Rect sr = MakeScreenRect(screen, [screen frame]); + NSArray *screens = [NSScreen screens]; + for(NSUInteger i = 0; i < [screens count]; i++) { + NSScreen *screen = [screens objectAtIndex:i]; + NSRect frame = [screen frame]; + Rect sr = MakeScreenRect(screen, NSRectToCGRect(frame)); if(first) r = sr; else @@ -342,8 +431,11 @@ Rect Ctrl::GetPrimaryWorkArea() Rect Ctrl::GetScreenArea(Point pt) { Upp::GuiLock __; - for(NSScreen *screen in [NSScreen screens]) { - Rect rc = MakeScreenRect(screen, [screen frame]); + NSArray *screens = [NSScreen screens]; + for(NSUInteger i = 0; i < [screens count]; i++) { + NSScreen *screen = [screens objectAtIndex:i]; + NSRect frame = [screen frame]; + Rect rc = MakeScreenRect(screen, NSRectToCGRect(frame)); if(rc.Contains(pt)) return rc; } @@ -352,8 +444,12 @@ Rect Ctrl::GetScreenArea(Point pt) Rect Ctrl::GetPrimaryScreenArea() { - for (NSScreen *screen in [NSScreen screens]) - return MakeScreenRect(screen, [screen frame]); + NSArray *screens = [NSScreen screens]; + if([screens count] > 0) { + NSScreen *screen = [screens objectAtIndex:0]; + NSRect frame = [screen frame]; + return MakeScreenRect(screen, NSRectToCGRect(frame)); + } return Rect(0, 0, 1024, 768); } @@ -384,7 +480,10 @@ void Ctrl::GuiPlatformGetTopRect(Rect& r) const void MMCtrl::SyncRect(CocoView *view) { NSWindow *win = [view window]; - view->ctrl->SetWndRect(MakeScreenRect([win screen], [win contentRectForFrameRect: [win frame]])); + NSScreen *screen = [win screen]; + NSRect winFrame = [win frame]; + NSRect contentRect = [win contentRectForFrameRect:winFrame]; + CocoViewGetCtrl(view)->SetWndRect(MakeScreenRect(screen, NSRectToCGRect(contentRect))); } TopFrameDraw::TopFrameDraw(Ctrl *ctrl, const Rect& r) @@ -394,7 +493,11 @@ TopFrameDraw::TopFrameDraw(Ctrl *ctrl, const Rect& r) ASSERT(ctrl->GetTop()->coco); Rect tr = ctrl->GetScreenRect(); NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithWindow:ctrl->GetTop()->coco->window]; +#ifdef MAC_OS_X_VERSION_10_10 Init([gc CGContext], NULL); +#else + Init((CGContextRef)[gc graphicsPort], NULL); +#endif CGContextTranslateCTM(cgHandle, 0, tr.GetHeight()); CGContextScaleCTM(cgHandle, 1, -1); @@ -423,7 +526,7 @@ String GetSpecialDirectory(int i) if(auto *h = FindTuple(map, __countof(map), i)) { NSArray * paths = NSSearchPathForDirectoriesInDomains(h->b, NSUserDomainMask, YES); - if(paths.count) + if([paths count]) return ToString([paths objectAtIndex:0]); } diff --git a/uppsrc/CtrlCore/CocoClip.mm b/uppsrc/CtrlCore/CocoClip.mm index 2363dd6ec..e7e58c27f 100644 --- a/uppsrc/CtrlCore/CocoClip.mm +++ b/uppsrc/CtrlCore/CocoClip.mm @@ -8,6 +8,17 @@ extern NSEvent *sCurrentMouseEvent__; namespace Upp { +// macOS 10.6 SDK compatibility - these constants added in 10.13 +#ifndef NSPasteboardTypeFileURL +#define NSPasteboardTypeFileURL NSFilenamesPboardType +#endif +#ifndef NSPasteboardTypeURL +#define NSPasteboardTypeURL NSURLPboardType +#endif +#ifndef NSPasteboardNameDrag +#define NSPasteboardNameDrag NSDragPboard +#endif + NSString *PasteboardType(const String& fmt) { return decode(fmt, "text", NSPasteboardTypeString, "png", NSPasteboardTypePNG, @@ -32,25 +43,47 @@ NSPasteboard *Pasteboard(bool dnd = false) @interface CocoClipboardOwner : NSObject { @public - Upp::VectorMap data; - Upp::Ptr source; + // Use pointers to avoid GCC ObjC runtime issues with C++ object construction + Upp::VectorMap *data; + Upp::Ctrl *source; // Raw pointer - GCC ObjC runtime doesn't properly construct C++ objects bool dnd; } +- (id)init; +- (void)dealloc; @end @implementation CocoClipboardOwner + +- (id)init { + self = [super init]; + if(self) { + data = new Upp::VectorMap(); + source = NULL; + dnd = false; + } + return self; +} + +- (void)dealloc { + delete data; + [super dealloc]; +} + +// Helper method to render clipboard data - replaces lambda (GCC ICE workaround) +-(Upp::String)renderFormat:(const Upp::String&)fmt +{ + int q = data->Find(fmt); + if(q < 0) + return Upp::Null; + return (*data)[q].Render(); +} + -(void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSString *)type { RLOG(Upp::ToString(type)); - + Upp::GuiLock __; - auto render = [&](const Upp::String& fmt) -> Upp::String { - int q = data.Find(fmt); - if(q < 0) - return Upp::Null; - return data[q].Render(); - }; - + NSPasteboard *pasteboard = Upp::Pasteboard(dnd); if(Upp::IsStandardPasteboardType(type)) { RLOG("Standard type - clearning contents!"); @@ -58,7 +91,7 @@ NSPasteboard *Pasteboard(bool dnd = false) } if([type isEqual:NSPasteboardTypeString]) { - Upp::String raw = render("text"); + Upp::String raw = [self renderFormat:"text"]; if(raw.GetCount() == 0 && source) raw = source->GetDropData("text"); [pasteboard setString:[NSString stringWithUTF8String:raw] @@ -66,7 +99,7 @@ NSPasteboard *Pasteboard(bool dnd = false) return; } else if([type isEqual:NSPasteboardTypeFileURL]) { - Upp::String raw = render("files"); + Upp::String raw = [self renderFormat:"files"]; Upp::Value v = ParseJSON(raw); if(!IsValueArray(v)) return; @@ -85,7 +118,7 @@ NSPasteboard *Pasteboard(bool dnd = false) bool is_rtf = [type isEqual:NSPasteboardTypeRTF]; Upp::String fmt = is_png ? "png" : is_rtf ? "rtf" : Upp::ToString(type); - Upp::String raw = render(fmt); + Upp::String raw = [self renderFormat:fmt]; if(raw.GetCount() == 0 && source) raw = source->GetDropData(fmt); [pasteboard setData:[NSData dataWithBytes:~raw length:raw.GetCount()] forType:type]; @@ -117,7 +150,7 @@ void ClearClipboard(bool dnd) { GuiLock __; [Pasteboard(dnd) clearContents]; - ClipboardOwner()->data.Clear(); + ClipboardOwner()->data->Clear(); } void ClearClipboard() @@ -137,14 +170,14 @@ void AppendClipboard(bool dnd, const char *format, const Value& value, String (* { GuiLock __; - auto& data = ClipboardOwner(dnd)->data; + auto& dataMap = *ClipboardOwner(dnd)->data; for(String fmt : Split(format, ';')) - data.GetAdd(fmt) = ClipData(value, render); + dataMap.GetAdd(fmt) = ClipData(value, render); AutoreleasePool ___; - [Pasteboard(dnd) declareTypes:[PasteboardTypes(data.GetKeys()) allObjects] + [Pasteboard(dnd) declareTypes:[PasteboardTypes(dataMap.GetKeys()) allObjects] owner:ClipboardOwner(dnd)]; } @@ -195,16 +228,23 @@ bool IsFormatAvailable(NSPasteboard *pasteboard, const char *fmt) String ReadFormat(NSPasteboard *pasteboard, const char *fmt) { - if(bool is_files = String(fmt) == "files"; is_files || String(fmt) == "url") { + bool is_files = String(fmt) == "files"; + if(is_files || String(fmt) == "url") { JsonArray array; - - NSArray *urls = [pasteboard readObjectsForClasses:@[[NSURL class]] options:nil]; - for (NSURL *url : urls) { - array << String(is_files ? [url.path UTF8String] : [url.absoluteString UTF8String]); + +#ifdef MAC_OS_X_VERSION_10_6 + // readObjectsForClasses:options: available in 10.6+ but @[] literal not supported by GCC + // Use NSArray arrayWithObject: instead of @[] literal + NSArray *classArray = [NSArray arrayWithObject:[NSURL class]]; + NSArray *urls = [pasteboard readObjectsForClasses:classArray options:nil]; + for(NSUInteger i = 0; i < [urls count]; i++) { + NSURL *url = [urls objectAtIndex:i]; + array << String(is_files ? [[url path] UTF8String] : [[url absoluteString] UTF8String]); } +#endif return ~array; } - + NSData *data = [pasteboard dataForType:PasteboardType(fmt)]; return String((const char *)[data bytes], [data length]); } diff --git a/uppsrc/CtrlCore/CocoDraw.mm b/uppsrc/CtrlCore/CocoDraw.mm index e6c5c20e9..cb6455b90 100644 --- a/uppsrc/CtrlCore/CocoDraw.mm +++ b/uppsrc/CtrlCore/CocoDraw.mm @@ -151,8 +151,9 @@ bool SystemDraw::IsPaintingOp(const Rect& r) const cr.Intersect(GetClip()); if(cr.IsEmpty()) return false; + // Note: needsToDrawRect check disabled for 10.6 compatibility (RectCG vs NSRect issue) + // The previous line already returns true unconditionally return true; - return nsview ? [(NSView *)nsview needsToDrawRect:MakeRectCG(1.0 / DPI(1) * cr)] : true; } Rect SystemDraw::GetPaintRect() const diff --git a/uppsrc/CtrlCore/CocoDrawOp.mm b/uppsrc/CtrlCore/CocoDrawOp.mm index 0a6f0e76b..329ec9082 100644 --- a/uppsrc/CtrlCore/CocoDrawOp.mm +++ b/uppsrc/CtrlCore/CocoDrawOp.mm @@ -6,10 +6,11 @@ namespace Upp { void SystemDraw::Stroke(int width, Color color, bool fill) { - static double dash[] = { 18, 6 }; - static double dot[] = { 3, 3 }; - static double dashdot[] = { 9, 6, 3, 6 }; - static double dashdotdot[] = { 9, 3, 3, 3, 3, 3 }; + // Use CGFloat for CGContextSetLineDash compatibility (float on 32-bit, double on 64-bit) + static CGFloat dash[] = { 18, 6 }; + static CGFloat dot[] = { 3, 3 }; + static CGFloat dashdot[] = { 9, 6, 3, 6 }; + static CGFloat dashdotdot[] = { 9, 3, 3, 3, 3, 3 }; if(IsNull(width)) width = PEN_NULL; switch(width) { diff --git a/uppsrc/CtrlCore/CocoImage.mm b/uppsrc/CtrlCore/CocoImage.mm index ee5cfd5ae..a8a296337 100644 --- a/uppsrc/CtrlCore/CocoImage.mm +++ b/uppsrc/CtrlCore/CocoImage.mm @@ -6,17 +6,25 @@ namespace Upp { +// Callback for CGDataProviderCreateWithData - releases the Image data +static void ReleaseImageData(void *info, const void *data, size_t size) +{ + delete (Image *)info; +} + CGImageRef createCGImage(const Image& img) { if(IsNull(img)) return NULL; Image *km = new Image(img); // to keep data alive CGDataProvider *dataProvider = CGDataProviderCreateWithData(km, ~img, img.GetLength() * sizeof(RGBA), - [](void *info, const void *data, size_t size) { delete (Image *)info; }); + ReleaseImageData); static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // TODO: This is probably wrong... Upp::Size isz = img.GetSize(); + // On big-endian (PowerPC), we need to specify byte order explicitly + CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; CGImageRef cg_img = CGImageCreate(isz.cx, isz.cy, 8, 32, isz.cx * sizeof(RGBA), - colorSpace, kCGImageAlphaPremultipliedFirst, + colorSpace, bitmapInfo, dataProvider, 0, false, kCGRenderingIntentDefault); CGDataProviderRelease(dataProvider); return cg_img; @@ -325,9 +333,19 @@ void ImageDraw::Init(int cx, int cy) static CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + // On big-endian (PowerPC), we need to specify byte order explicitly + // kCGBitmapByteOrder32Host resolves to the correct order for the platform + CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && !defined(__ppc__) + // CGBitmapContextCreateWithData available in 10.6+, but may have issues on PPC SystemDraw::Init(CGBitmapContextCreateWithData(~ib, cx, cy, 8, cx * sizeof(RGBA), - colorSpace, kCGImageAlphaPremultipliedFirst, + colorSpace, bitmapInfo, NULL, NULL), NULL); +#else + // Fallback for 10.5 and PowerPC 10.6: use CGBitmapContextCreate + SystemDraw::Init(CGBitmapContextCreate(~ib, cx, cy, 8, cx * sizeof(RGBA), + colorSpace, bitmapInfo), NULL); +#endif CGContextTranslateCTM(cgHandle, 0, cy); if(IsUHDMode()) { CGContextScaleCTM(cgHandle, 2, -2); @@ -395,19 +413,23 @@ Image GetIconForFile(const char *path) NSImage *image; CFRef fexe = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8); - image = [[NSWorkspace sharedWorkspace]iconForFile:(__bridge NSString *)~fexe]; -/* - } - else { // not used any more - CFRef fext = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8); - image = *ext == '*' ? [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] - : [[NSWorkspace sharedWorkspace]iconForFileType:(__bridge NSString *)~fext]; - } -*/ + // __bridge is ARC-only (10.7+), use direct cast for non-ARC + image = [[NSWorkspace sharedWorkspace] iconForFile:(NSString *)~fexe]; + +#ifdef MAC_OS_X_VERSION_10_10 NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:cg flipped:YES]; +#else + // macOS 10.6-10.9: use graphicsContextWithGraphicsPort:flipped: + NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithGraphicsPort:cg flipped:YES]; +#endif NSGraphicsContext* cgc = [NSGraphicsContext currentContext]; [NSGraphicsContext setCurrentContext:gc]; +#ifdef MAC_OS_X_VERSION_10_9 [image drawInRect:NSMakeRect(0, 0, DPI(16), DPI(16))]; +#else + // macOS 10.6-10.8: drawInRect: without extra params not available + [image drawInRect:NSMakeRect(0, 0, DPI(16), DPI(16)) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; +#endif [NSGraphicsContext setCurrentContext:cgc]; return iw; diff --git a/uppsrc/CtrlCore/CocoMM.h b/uppsrc/CtrlCore/CocoMM.h index 900081660..51f21938d 100644 --- a/uppsrc/CtrlCore/CocoMM.h +++ b/uppsrc/CtrlCore/CocoMM.h @@ -5,6 +5,13 @@ #if defined(PLATFORM_COCOA) && !defined(VIRTUALGUI) +// Disable old-style Carbon assertion macros (check, verify, require, etc.) +// to avoid conflicts with identifiers in U++ code (e.g., IMAGE_ID(check)) +// See: https://github.com/opencv/opencv/issues/6047 +#ifndef __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES +#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0 +#endif + #define Point NS_Point #define Rect NS_Rect #define Size NS_Size @@ -13,6 +20,112 @@ #undef Rect #undef Size +// macOS 10.6 SDK compatibility - modern names for older constants +#ifndef MAC_OS_X_VERSION_10_12 + #define NSEventModifierFlagShift NSShiftKeyMask + #define NSEventModifierFlagControl NSControlKeyMask + #define NSEventModifierFlagOption NSAlternateKeyMask + #define NSEventModifierFlagCommand NSCommandKeyMask + #define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask + #define NSEventModifierFlagNumericPad NSNumericPadKeyMask + + #define NSEventTypeKeyDown NSKeyDown + #define NSEventTypeKeyUp NSKeyUp + #define NSEventTypeFlagsChanged NSFlagsChanged + #define NSEventTypeLeftMouseDown NSLeftMouseDown + #define NSEventTypeLeftMouseUp NSLeftMouseUp + #define NSEventTypeRightMouseDown NSRightMouseDown + #define NSEventTypeRightMouseUp NSRightMouseUp + #define NSEventTypeOtherMouseDown NSOtherMouseDown + #define NSEventTypeOtherMouseUp NSOtherMouseUp + #define NSEventTypeLeftMouseDragged NSLeftMouseDragged + #define NSEventTypeRightMouseDragged NSRightMouseDragged + #define NSEventTypeOtherMouseDragged NSOtherMouseDragged + #define NSEventTypeMouseMoved NSMouseMoved + #define NSEventTypeScrollWheel NSScrollWheel + #define NSEventTypeMouseEntered NSMouseEntered + #define NSEventTypeMouseExited NSMouseExited + #define NSEventTypeApplicationDefined NSApplicationDefined + + #define NSWindowStyleMaskTitled NSTitledWindowMask + #define NSWindowStyleMaskClosable NSClosableWindowMask + #define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask + #define NSWindowStyleMaskResizable NSResizableWindowMask + #define NSWindowStyleMaskBorderless NSBorderlessWindowMask + + #define NSEventMaskAny NSAnyEventMask + #define NSEventMaskLeftMouseDown NSLeftMouseDownMask + + // NSControlSize constants renamed in 10.12 + #define NSControlSizeRegular NSRegularControlSize + #define NSControlSizeSmall NSSmallControlSize + #define NSControlSizeMini NSMiniControlSize + + // NSButtonType constants renamed in 10.12 + #define NSButtonTypeMomentaryLight NSMomentaryLightButton + #define NSButtonTypePushOnPushOff NSPushOnPushOffButton + #define NSButtonTypeToggle NSToggleButton + #define NSButtonTypeSwitch NSSwitchButton + #define NSButtonTypeRadio NSRadioButton + #define NSButtonTypeMomentaryChange NSMomentaryChangeButton + #define NSButtonTypeOnOff NSOnOffButton + #define NSButtonTypeMomentaryPushIn NSMomentaryPushInButton + + // NSBezelStyle constants renamed in 10.12 + #define NSBezelStyleRounded NSRoundedBezelStyle + #define NSBezelStyleRegularSquare NSRegularSquareBezelStyle + #define NSBezelStyleShadowlessSquare NSShadowlessSquareBezelStyle + #define NSBezelStyleSmallSquare NSSmallSquareBezelStyle + #define NSBezelStyleRoundedDisclosure NSRoundedDisclosureBezelStyle + #define NSBezelStyleInline NSInlineBezelStyle +#endif + +#ifndef MAC_OS_X_VERSION_10_9 + #define NSModalResponseOK NSOKButton + #define NSModalResponseCancel NSCancelButton +#endif + +#ifndef MAC_OS_X_VERSION_10_13 + #define NSControlStateValueOn NSOnState + #define NSControlStateValueOff NSOffState + #define NSControlStateValueMixed NSMixedState +#endif + +// macOS 10.7+ scroller styles - not available on 10.6 +#ifndef MAC_OS_X_VERSION_10_7 + #define NSScrollerStyleLegacy 0 + #define NSScrollerStyleOverlay 1 + #define NSScrollerKnobStyleDefault 0 + #define NSScrollerKnobStyleDark 1 + #define NSScrollerKnobStyleLight 2 +#endif + +// NSControlStateValue type alias (was NSInteger before 10.10) +#ifndef MAC_OS_X_VERSION_10_10 + typedef NSInteger NSControlStateValue; +#endif + +// Helper function: Convert NSColor to CGColorRef (for 10.6 compatibility) +// NSColor.CGColor property not available until macOS 10.8 +inline CGColorRef NSColorToCGColor(NSColor *nscolor) { + NSColor *rgbColor = [nscolor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + if(!rgbColor) + rgbColor = [nscolor colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + if(!rgbColor) { + CGFloat components[4] = {1.0, 1.0, 1.0, 1.0}; + CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + CGColorRef cgColor = CGColorCreate(colorSpace, components); + CGColorSpaceRelease(colorSpace); + return cgColor; + } + CGFloat components[4]; + [rgbColor getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]]; + CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + CGColorRef cgColor = CGColorCreate(colorSpace, components); + CGColorSpaceRelease(colorSpace); + return cgColor; +} + #endif #include "CtrlCore.h" @@ -80,20 +193,43 @@ NSRect DesktopRect(const Upp::Rect& r); } +// Use objc_setAssociatedObject/objc_getAssociatedObject to store data +// This avoids GCC ObjC runtime issues with ivars +#import + +// Keys for associated objects - declared extern, defined in CocoProc.mm +// Must be unique addresses across all translation units +extern char CocoViewCtrlKey; +extern char CocoWindowCtrlKey; +extern char CocoWindowActiveKey; +extern char CocoMenuItemBarKey; + @interface CocoView : NSView -{ - @public - Upp::Ptr ctrl; -} @end -@interface CocoWindow : NSWindow -{ - @public - Upp::Ptr ctrl; - bool active; +// Use NSWindow directly - GCC ObjC runtime has issues with subclassing +// canBecomeKeyWindow/canBecomeMainWindow are handled via method swizzling in CocoWin.mm +typedef NSWindow CocoWindow; + +// Helper functions to get/set associated ctrl pointer +inline Upp::Ctrl* CocoViewGetCtrl(CocoView *view) { + return (Upp::Ctrl*)objc_getAssociatedObject(view, &CocoViewCtrlKey); +} +inline void CocoViewSetCtrl(CocoView *view, Upp::Ctrl *c) { + objc_setAssociatedObject(view, &CocoViewCtrlKey, (id)c, OBJC_ASSOCIATION_ASSIGN); +} +inline Upp::Ctrl* CocoWindowGetCtrl(CocoWindow *win) { + return (Upp::Ctrl*)objc_getAssociatedObject(win, &CocoWindowCtrlKey); +} +inline void CocoWindowSetCtrl(CocoWindow *win, Upp::Ctrl *c) { + objc_setAssociatedObject(win, &CocoWindowCtrlKey, (id)c, OBJC_ASSOCIATION_ASSIGN); +} +inline bool CocoWindowGetActive(CocoWindow *win) { + return objc_getAssociatedObject(win, &CocoWindowActiveKey) != nil; +} +inline void CocoWindowSetActive(CocoWindow *win, bool a) { + objc_setAssociatedObject(win, &CocoWindowActiveKey, a ? (id)1 : nil, OBJC_ASSOCIATION_ASSIGN); } -@end struct Upp::MMCtrl { static void SyncRect(CocoView *view); diff --git a/uppsrc/CtrlCore/CocoProc.mm b/uppsrc/CtrlCore/CocoProc.mm index 84d0ad282..ace03c742 100644 --- a/uppsrc/CtrlCore/CocoProc.mm +++ b/uppsrc/CtrlCore/CocoProc.mm @@ -2,6 +2,12 @@ #ifdef GUI_COCOA +// Define associated object keys - declared extern in CocoMM.h +char CocoViewCtrlKey; +char CocoWindowCtrlKey; +char CocoWindowActiveKey; +char CocoMenuItemBarKey; + NSEvent *sCurrentMouseEvent__; // needed for drag operation #define LLOG(x) // DLOG(x) @@ -15,9 +21,12 @@ static Upp::Ptr coco_capture; Upp::Ptr Upp::Ctrl::lastActive; namespace Upp { - + extern id menubar; +// Declared in CocoApp.mm - syncs popup focus on mouse events +void SyncPopupFocus(NSWindow *win); + bool GetShift() { return coco_flags & NSEventModifierFlagShift; } bool GetCtrl() { return coco_flags & NSEventModifierFlagCommand; } bool GetAlt() { return coco_flags & NSEventModifierFlagControl; } @@ -101,7 +110,8 @@ struct MMImp { static bool MouseEvent(CocoView *view, NSEvent *e, int event, double zd = 0) { - if(!view->ctrl) + Ctrl *ctrl = CocoViewGetCtrl(view); + if(!ctrl) return false; Flags(e); sCurrentMouseEvent__ = e; @@ -123,7 +133,7 @@ struct MMImp { } } NSPoint np = [view convertPoint:[e locationInWindow] fromView:nil]; - Rect r = view->ctrl->GetRect(); + Rect r = ctrl->GetRect(); Upp::Point p(DPI(np.x), DPI(np.y)); coco_mouse_pos = p + r.TopLeft(); @@ -139,7 +149,9 @@ struct MMImp { coco_capture->DispatchMouse(event, coco_mouse_pos - coco_capture->GetScreenRect().TopLeft(), 120 * sgn(zd)); else { Vector t = Ctrl::GetTopCtrls(); // Find window that contains the mouse, from the top - for(NSNumber *num in [NSWindow windowNumbersWithOptions:0]) { // All app windows + NSArray *windowNumbers = [NSWindow windowNumbersWithOptions:0]; // All app windows + for(NSUInteger i = 0; i < [windowNumbers count]; i++) { + NSNumber *num = [windowNumbers objectAtIndex:i]; NSWindow *win = [NSApp windowWithWindowNumber:[num integerValue]]; if(win) { int q = FindMatch(t, [&](Ctrl *t) { return t->GetNSWindow() == win; }); @@ -156,21 +168,24 @@ struct MMImp { } } 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)); + if(ctrl->IsEnabled() && (ctrl->HasWndCapture() || r.Contains(coco_mouse_pos))) { + if((event & Ctrl::ACTION) == Ctrl::DOWN && !ctrl->HasFocusDeep() && ctrl->IsWantFocus()) + ctrl->SetFocus(); + ctrl->DispatchMouse(event, p, 120 * sgn(zd)); } - + sCurrentMouseEvent__ = NULL; return false; } static bool MouseDownEvent(CocoView *view, NSEvent *e, int button) { - if(!view->ctrl) + Ctrl *ctrl = CocoViewGetCtrl(view); + if(!ctrl) return false; - Upp::Ctrl::lastActive = view->ctrl; + // Sync popup focus on mouse down (replaces block-based event monitor for GCC) + SyncPopupFocus([e window]); + Upp::Ctrl::lastActive = ctrl; if(Ctrl::ignoremouseup) { Ctrl::KillRepeat(); Ctrl::ignoreclick = false; @@ -212,8 +227,8 @@ struct MMImp { Flags(e); if(!ctrl->IsEnabled()) return false; - Upp::dword k = e.keyCode; - WString x = ToWString((CFStringRef)(e.charactersIgnoringModifiers)); + 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; @@ -234,15 +249,15 @@ struct MMImp { if(GetOption()) k |= K_OPTION; - if(e.keyCode == kVK_Help) // TODO: This is Insert key, but all this is dubious + 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) + 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)) + if([e keyCode] == kVK_Space && !(k & K_SHIFT)) ctrl->DispatchKey(' ', 1); } return true; @@ -335,9 +350,10 @@ struct MMImp { static void PreeditText(Ctrl *ctrl, const WString& s) { if(ctrl) - for(Upp::wchar ch : s) + for(Upp::wchar ch : s) { if(ch >= 32 && ch != 127 && ch != ' ') ctrl->DispatchKey(ch, 1); + } } static void CancelPreedit() @@ -346,6 +362,9 @@ struct MMImp { } }; +// Forward declaration for menu action handler (defined in MacMenu.mm) +void CocoMenuBarAction(void *bar, id sender); + }; @implementation CocoView @@ -356,9 +375,18 @@ struct MMImp { -(void)drawRect:(NSRect)r { Upp::GuiLock __; - if(ctrl) { - Upp::SystemDraw w([[NSGraphicsContext currentContext] CGContext], self); - Upp::MMImp::Paint(ctrl, w, MakeRect(r, Upp::DPI(1))); + Upp::Ctrl *c = CocoViewGetCtrl(self); + if(c) { +#ifdef MAC_OS_X_VERSION_10_10 + CGContextRef cg = [[NSGraphicsContext currentContext] CGContext]; +#else + // macOS 10.6-10.9: use graphicsPort instead of CGContext + CGContextRef cg = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; +#endif + Upp::SystemDraw w(cg, self); + // Convert NSRect to CGRect for MakeRect + CGRect cgr = CGRectMake(r.origin.x, r.origin.y, r.size.width, r.size.height); + Upp::MMImp::Paint(c, w, MakeRect(cgr, Upp::DPI(1))); } } @@ -373,7 +401,7 @@ struct MMImp { 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); + Upp::MMImp::DispatchKey(CocoViewGetCtrl(self), m); else [super otherMouseDown:e]; } @@ -423,20 +451,40 @@ struct MMImp { - (void)keyDown:(NSEvent *)e { Upp::GuiLock __; - [self interpretKeyEvents: [NSArray arrayWithObject: e]]; - if(!Upp::MMImp::KeyEvent(ctrl, e, 0)) + NSString *chars = [e characters]; + + // On macOS 10.6, interpretKeyEvents may not call insertText for simple characters + // So we handle character input directly here for printable characters + if(chars && [chars length] > 0) { + unichar ch = [chars characterAtIndex:0]; + // Check if it's a printable character (not a control character) + // and no command key is pressed + if(ch >= 32 && ch != 127 && !([e modifierFlags] & NSEventModifierFlagCommand)) { + Upp::Ctrl *ctrl = CocoViewGetCtrl(self); + if(ctrl) { + // Dispatch the character directly using MMImp helper (DispatchKey is private) + Upp::MMImp::DispatchKey(ctrl, ch); + } + } + } + + // Still call interpretKeyEvents for IME support + [self interpretKeyEvents: [NSArray arrayWithObject: e]]; + + // And KeyEvent for special keys (arrows, function keys, etc.) + if(!Upp::MMImp::KeyEvent(CocoViewGetCtrl(self), e, 0)) [super keyDown:e]; } - (void)keyUp:(NSEvent *)e { Upp::GuiLock __; - if(!Upp::MMImp::KeyEvent(ctrl, e, Upp::K_KEYUP)) + if(!Upp::MMImp::KeyEvent(CocoViewGetCtrl(self), e, Upp::K_KEYUP)) [super keyUp:e]; } - (void)flagsChanged:(NSEvent *)e { Upp::GuiLock __; - if(!Upp::MMImp::KeyFlags(ctrl, e)) + if(!Upp::MMImp::KeyFlags(CocoViewGetCtrl(self), e)) [super flagsChanged:e]; } @@ -446,10 +494,12 @@ struct MMImp { Upp::MMImp::DoCursorShape(); } -- (BOOL)windowShouldClose:(NSWindow *)sender { +// Use 'id' type for sender to match NSWindowDelegate protocol (pre-10.7) +- (BOOL)windowShouldClose:(id)sender { Upp::GuiLock __; - if(ctrl->IsEnabled()) - Upp::MMImp::DoClose(ctrl); + Upp::Ctrl *c = CocoViewGetCtrl(self); + if(c && c->IsEnabled()) + Upp::MMImp::DoClose(c); return NO; } @@ -465,12 +515,12 @@ struct MMImp { - (void)windowDidBecomeKey:(NSNotification *)notification { Upp::GuiLock __; - Upp::MMImp::BecomeKey(ctrl); + Upp::MMImp::BecomeKey(CocoViewGetCtrl(self)); } - (void)windowDidResignKey:(NSNotification *)notification { Upp::GuiLock __; - Upp::MMImp::ResignKey(ctrl); + Upp::MMImp::ResignKey(CocoViewGetCtrl(self)); } - (BOOL)acceptsFirstResponder { @@ -479,46 +529,52 @@ struct MMImp { - (BOOL)canBecomeKeyView { Upp::GuiLock __; - return ctrl->IsEnabled(); + Upp::Ctrl *c = CocoViewGetCtrl(self); + return c && c->IsEnabled(); } - (NSDragOperation)draggingEntered:(id )sender { Upp::GuiLock __; - return Upp::MMImp::DnD(ctrl, sender); + return Upp::MMImp::DnD(CocoViewGetCtrl(self), sender); } - (NSDragOperation)draggingUpdated:(id )sender { Upp::GuiLock __; - return Upp::MMImp::DnD(ctrl, sender); + return Upp::MMImp::DnD(CocoViewGetCtrl(self), sender); } - (void)draggingEnded:(id )sender { Upp::GuiLock __; - Upp::MMImp::DnDLeave(ctrl); + Upp::MMImp::DnDLeave(CocoViewGetCtrl(self)); } - (void)draggingExited:(id )sender { Upp::GuiLock __; - Upp::MMImp::DnDLeave(ctrl); + Upp::MMImp::DnDLeave(CocoViewGetCtrl(self)); } - (BOOL)performDragOperation:(id )sender { Upp::GuiLock __; - return Upp::MMImp::DnD(ctrl, sender, true) != NSDragOperationNone; + return Upp::MMImp::DnD(CocoViewGetCtrl(self), sender, true) != NSDragOperationNone; } - (void)updateTrackingAreas { Upp::GuiLock __; - for(NSTrackingArea *t in [self trackingAreas]) + NSArray *areas = [self trackingAreas]; + for(NSUInteger i = 0; i < [areas count]; i++) { + NSTrackingArea *t = [areas objectAtIndex:i]; [self removeTrackingArea:t]; + } - Upp::Size sz = ctrl->GetScreenRect().GetSize(); + Upp::Ctrl *c = CocoViewGetCtrl(self); + if(!c) return; + Upp::Size sz = c->GetScreenRect().GetSize(); NSTrackingArea *ta = [[NSTrackingArea alloc] initWithRect:NSMakeRect(0, 0, sz.cx, sz.cy) options:NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways| @@ -539,7 +595,7 @@ struct MMImp { NSString* pInsert = [aString isMemberOfClass: [NSAttributedString class]] ? [aString string] : aString; if(pInsert) - Upp::MMImp::PreeditText(ctrl, Upp::ToWString(pInsert)); + Upp::MMImp::PreeditText(CocoViewGetCtrl(self), Upp::ToWString(pInsert)); } - (NSRange)markedRange @@ -555,7 +611,7 @@ struct MMImp { - (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { Upp::GuiLock __; - return Upp::MMImp::PreeditRect(ctrl); + return Upp::MMImp::PreeditRect(CocoViewGetCtrl(self)); } - (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange @@ -564,7 +620,7 @@ struct MMImp { if(![aString isKindOfClass:[NSAttributedString class]] ) aString = [[[NSAttributedString alloc] initWithString:aString] autorelease]; - Upp::MMImp::ShowPreedit(ctrl, Upp::ToWString([aString string])); + Upp::MMImp::ShowPreedit(CocoViewGetCtrl(self), Upp::ToWString([aString string])); } - (BOOL)hasMarkedText @@ -597,6 +653,26 @@ struct MMImp { return 0; } +- (void)doCommandBySelector:(SEL)aSelector +{ + // Required by NSTextInputClient protocol + // Do nothing - U++ handles key events directly +} + +// Menu action handler - called when menu items with nil target are clicked +// This is in the responder chain as the first responder (key window's content view) +- (void)cocoMenuAction:(id)sender +{ + Upp::GuiLock __; + NSMenuItem *item = (NSMenuItem *)sender; + // Get the bar pointer from the menu item's associated object + void *barPtr = objc_getAssociatedObject(item, &CocoMenuItemBarKey); + if(barPtr) { + // Forward to the Upp menu action handler (defined in MacMenu.mm) + Upp::CocoMenuBarAction(barPtr, sender); + } +} + @end #endif diff --git a/uppsrc/CtrlCore/CocoWin.mm b/uppsrc/CtrlCore/CocoWin.mm index 1323964ba..9ab967708 100644 --- a/uppsrc/CtrlCore/CocoWin.mm +++ b/uppsrc/CtrlCore/CocoWin.mm @@ -4,32 +4,85 @@ #define LLOG(x) -@implementation CocoWindow +// Method swizzling for NSWindow to override canBecomeKeyWindow/canBecomeMainWindow +// This avoids subclassing NSWindow which causes crashes with GCC's ObjC runtime -- (void)becomeKeyWindow { - [super becomeKeyWindow]; -} +static IMP sOriginalCanBecomeKeyWindow = NULL; +static IMP sOriginalCanBecomeMainWindow = NULL; -- (BOOL)canBecomeKeyWindow { - Upp::GuiLock __; - return active && ctrl && ctrl->IsEnabled(); +// Replacement for canBecomeKeyWindow - checks if our Ctrl is enabled +// Note: The 'active' flag is only used for popup menus/tooltips that should never become key +// Dialogs (TopWindow) should always be able to become key if enabled +static BOOL Swizzled_canBecomeKeyWindow(id self, SEL _cmd) +{ + Upp::Ctrl *ctrl = CocoWindowGetCtrl((CocoWindow*)self); + // If this is our window, use our logic + if(ctrl) { + Upp::GuiLock __; + // Check if it's a TopWindow (dialog) - these should always be able to become key if enabled + // PopUp windows (menus, tooltips) have active=false and should not become key + bool active = CocoWindowGetActive((CocoWindow*)self); + Upp::TopWindow *tw = dynamic_cast(ctrl); + bool isTopWindow = (tw != NULL); + BOOL result = (active || isTopWindow) && ctrl->IsEnabled(); + NSLog(@"canBecomeKeyWindow: ctrl=%p active=%d tw=%p isTopWindow=%d enabled=%d result=%d", + ctrl, (int)active, tw, (int)isTopWindow, (int)ctrl->IsEnabled(), (int)result); + return result; + } + // Otherwise call original + NSLog(@"canBecomeKeyWindow: no ctrl, calling original"); + if(sOriginalCanBecomeKeyWindow) + return ((BOOL(*)(id, SEL))sOriginalCanBecomeKeyWindow)(self, _cmd); + return YES; // NSWindow default } -- (BOOL)canBecomeMainWindow { - Upp::GuiLock __; - LLOG("canBecomeMainWindow " << Upp::Name(ctrl) << ", owner " << Upp::Name(ctrl->GetOwner())); - return active && ctrl && ctrl->IsEnabled() && dynamic_cast(~ctrl) && !ctrl->GetOwner(); +// Replacement for canBecomeMainWindow - checks if TopWindow without owner +static BOOL Swizzled_canBecomeMainWindow(id self, SEL _cmd) +{ + Upp::Ctrl *ctrl = CocoWindowGetCtrl((CocoWindow*)self); + // If this is our window, use our logic + if(ctrl) { + Upp::GuiLock __; + // Main window must be a TopWindow, enabled, and without owner + // The 'active' flag is not relevant for main window status + Upp::TopWindow *tw = dynamic_cast(ctrl); + return tw && ctrl->IsEnabled() && !ctrl->GetOwner(); + } + // Otherwise call original + if(sOriginalCanBecomeMainWindow) + return ((BOOL(*)(id, SEL))sOriginalCanBecomeMainWindow)(self, _cmd); + return YES; // NSWindow default } -- (NSMenu *)applicationDockMenu:(NSApplication *)sender { - Upp::GuiLock __; - NSMenu *menu = [[[NSMenu alloc] initWithTitle:@"DocTile Menu"] autorelease]; - NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:@"Hello" action:@selector(hello) keyEquivalent:@"k"] autorelease]; - [menu addItem:item]; - return menu; -} +static void SwizzleNSWindowMethods() +{ + static bool swizzled = false; + if(swizzled) return; + swizzled = true; + + NSLog(@"SwizzleNSWindowMethods: swizzling NSWindow methods"); + Class windowClass = [NSWindow class]; + + // Swizzle canBecomeKeyWindow + Method origKey = class_getInstanceMethod(windowClass, @selector(canBecomeKeyWindow)); + if(origKey) { + sOriginalCanBecomeKeyWindow = method_getImplementation(origKey); + method_setImplementation(origKey, (IMP)Swizzled_canBecomeKeyWindow); + NSLog(@"SwizzleNSWindowMethods: canBecomeKeyWindow swizzled, orig=%p new=%p", sOriginalCanBecomeKeyWindow, Swizzled_canBecomeKeyWindow); + } else { + NSLog(@"SwizzleNSWindowMethods: canBecomeKeyWindow NOT FOUND"); + } -@end + // Swizzle canBecomeMainWindow + Method origMain = class_getInstanceMethod(windowClass, @selector(canBecomeMainWindow)); + if(origMain) { + sOriginalCanBecomeMainWindow = method_getImplementation(origMain); + method_setImplementation(origMain, (IMP)Swizzled_canBecomeMainWindow); + NSLog(@"SwizzleNSWindowMethods: canBecomeMainWindow swizzled"); + } else { + NSLog(@"SwizzleNSWindowMethods: canBecomeMainWindow NOT FOUND"); + } +} namespace Upp { @@ -46,9 +99,11 @@ Ctrl *Ctrl::GetOwner() Ctrl *Ctrl::GetActiveCtrl() { GuiLock __; - for(Ctrl *p : mmtopctrl) - if(p && p->top && p->GetTop()->coco && p->GetTop()->coco->window.keyWindow) + for(int i = 0; i < mmtopctrl.GetCount(); i++) { + Ctrl *p = mmtopctrl[i]; + if(p && p->top && p->GetTop()->coco && [p->GetTop()->coco->window isKeyWindow]) return p; + } return lastActive; } @@ -110,6 +165,9 @@ void Ctrl::DoCancelPreedit() void Ctrl::Create(Ctrl *owner, dword style, bool active) { cancel_preedit = DoCancelPreedit; // We really need this just once, but whatever.. + + // Swizzle NSWindow methods for canBecomeKeyWindow/canBecomeMainWindow (GCC compatibility) + SwizzleNSWindowMethods(); if(owner) owner = owner->GetTopCtrl(); @@ -125,20 +183,23 @@ void Ctrl::Create(Ctrl *owner, dword style, bool active) if(owner && owner->top && owner->GetTop()->coco) [owner->GetTop()->coco->window addChildWindow:window ordered:NSWindowAbove]; - window->ctrl = this; - window->active = active; - window.backgroundColor = [NSColor clearColor]; + CocoWindowSetCtrl(window, this); + CocoWindowSetActive(window, active); + [window setBackgroundColor:[NSColor clearColor]]; isopen = true; - + CocoView *view = [[[CocoView alloc] initWithFrame:frame] autorelease]; - view->ctrl = this; + CocoViewSetCtrl(view, this); GetTop()->coco->view = view; [window setContentView:view]; [window setDelegate:view]; [window setAcceptsMouseMovedEvents:YES]; - [window makeFirstResponder:view]; + BOOL frResult = [window makeFirstResponder:view]; + NSLog(@"Create: window=%p view=%p makeFirstResponder=%d", window, view, (int)frResult); [window makeKeyAndOrderFront:window]; + NSLog(@"Create: after makeKeyAndOrderFront, isKeyWindow=%d firstResponder=%p", + (int)[window isKeyWindow], [window firstResponder]); ONCELOCK { [NSApp activateIgnoringOtherApps:YES]; @@ -155,6 +216,9 @@ void Ctrl::Create(Ctrl *owner, dword style, bool active) void Ctrl::WndDestroy() { LLOG("WndDestroy " << Name()); + NSLog(@"WndDestroy: ctrl=%p window=%p isTopWindow=%d", + this, top ? GetTop()->coco->window : nil, + dynamic_cast(this) != NULL); if(!top) return; bool focus = HasFocusDeep(); @@ -163,9 +227,36 @@ void Ctrl::WndDestroy() auto* coco = GetTop()->coco; auto* window = coco->window; - [window setCollectionBehavior:NSWindowCollectionBehaviorTransient]; + NSLog(@"WndDestroy: before close, window=%p isVisible=%d retainCount=%lu parentWindow=%p", + window, (int)[window isVisible], (unsigned long)[window retainCount], [window parentWindow]); + + // Remove from parent window's child list first + NSWindow *parent = [window parentWindow]; + if(parent) { + NSLog(@"WndDestroy: removing from parent window %p", parent); + [parent removeChildWindow:window]; + } + + // Clear the delegate to prevent callbacks during close + [window setDelegate:nil]; + + // Clear the content view's ctrl reference to prevent further callbacks + CocoViewSetCtrl(coco->view, NULL); + + // On macOS 10.6, we need to ensure the window is truly hidden + // setContentView:nil helps force release of the view + [window setContentView:nil]; + + // Order out and close + [window orderOut:nil]; + + // Force display update to ensure window disappears immediately + [window display]; + [window close]; + NSLog(@"WndDestroy: after close, isVisible=%d", (int)[window isVisible]); + delete coco; DeleteTop(); @@ -191,7 +282,8 @@ void Ctrl::WndInvalidateRect(const Rect& r) { GuiLock __; if(top) { - NSRect nsr = (NSRect)CGRectDPI(r.Inflated(10, 10)); + CGRect cgr = CGRectDPI(r.Inflated(10, 10)); + NSRect nsr = NSMakeRect(cgr.origin.x, cgr.origin.y, cgr.size.width, cgr.size.height); if(IsMainThread()) [GetTop()->coco->view setNeedsDisplayInRect:nsr]; else { @@ -304,7 +396,8 @@ void TopWindow::SyncCaption() SyncTitle(); NSWindow* window = GetTop()->coco->window; - NSWindowStyleMask mask = [window styleMask]; + // NSWindowStyleMask is typedef'd in 10.12+; use NSUInteger for 10.6 compatibility + NSUInteger mask = [window styleMask]; mask = minimizebox ? (mask | NSWindowStyleMaskMiniaturizable) : (mask & ~NSWindowStyleMaskMiniaturizable); mask = maximizebox ? (mask | NSWindowStyleMaskResizable) @@ -318,8 +411,9 @@ void TopWindow::SyncCaption() CGSize MMFrameSize(Size sz, dword style) { double scale = 1.0 / DPI(1); - return [NSWindow frameRectForContentRect: - (NSRect)CGRectMake(100, 100, scale * sz.cx, scale * sz.cy) styleMask:style].size; + NSRect contentRect = NSMakeRect(100, 100, scale * sz.cx, scale * sz.cy); + NSRect frameRect = [NSWindow frameRectForContentRect:contentRect styleMask:style]; + return CGSizeMake(frameRect.size.width, frameRect.size.height); } void TopWindow::SyncSizeHints() @@ -329,8 +423,10 @@ void TopWindow::SyncSizeHints() NSWindow *window = GetTop()->coco->window; dword style = GetMMStyle(); Size sz = GetRect().GetSize(); - [window setMinSize:MMFrameSize(sizeable ? GetMinSize() : sz, style)]; - [window setMaxSize:MMFrameSize(sizeable ? GetMaxSize() : sz, style)]; + CGSize minSz = MMFrameSize(sizeable ? GetMinSize() : sz, style); + CGSize maxSz = MMFrameSize(sizeable ? GetMaxSize() : sz, style); + [window setMinSize:NSMakeSize(minSz.width, minSz.height)]; + [window setMaxSize:NSMakeSize(maxSz.width, maxSz.height)]; } } @@ -364,7 +460,7 @@ void TopWindow::SerializePlacement(Stream& s, bool reminimize) void TopWindow::Maximize(bool effect) { state = MAXIMIZED; - if(top && GetTop()->coco && GetTop()->coco->window && !GetTop()->coco->window.zoomed) { + if(top && GetTop()->coco && GetTop()->coco->window && ![GetTop()->coco->window isZoomed]) { if(effect) [GetTop()->coco->window performZoom:GetTop()->coco->window]; else @@ -375,7 +471,7 @@ void TopWindow::Maximize(bool effect) void TopWindow::Minimize(bool effect) { state = MINIMIZED; - if(top && GetTop()->coco && GetTop()->coco->window && !GetTop()->coco->window.miniaturized) { + if(top && GetTop()->coco && GetTop()->coco->window && ![GetTop()->coco->window isMiniaturized]) { if(effect) [GetTop()->coco->window performMiniaturize:GetTop()->coco->window]; else @@ -386,15 +482,15 @@ void TopWindow::Minimize(bool effect) void TopWindow::Overlap(bool effect) { state = OVERLAPPED; - if(top && GetTop()->coco && GetTop()->coco->window && GetTop()->coco->window.zoomed) + if(top && GetTop()->coco && GetTop()->coco->window && [GetTop()->coco->window isZoomed]) [GetTop()->coco->window zoom:GetTop()->coco->window]; - if(top && GetTop()->coco && GetTop()->coco->window && GetTop()->coco->window.miniaturized) + if(top && GetTop()->coco && GetTop()->coco->window && [GetTop()->coco->window isMiniaturized]) [GetTop()->coco->window deminiaturize:GetTop()->coco->window]; } bool Ctrl::IsCocoActive() const { - return top && GetTop()->coco && GetTop()->coco->window && GetTop()->coco->window->active; + return top && GetTop()->coco && GetTop()->coco->window && CocoWindowGetActive(GetTop()->coco->window); } } diff --git a/uppsrc/CtrlLib/ChCocoMM.mm b/uppsrc/CtrlLib/ChCocoMM.mm index 96e5b968c..4062f64bd 100644 --- a/uppsrc/CtrlLib/ChCocoMM.mm +++ b/uppsrc/CtrlLib/ChCocoMM.mm @@ -2,7 +2,11 @@ #ifdef PLATFORM_COCOA -#include +// Disable old-style Carbon assertion macros (check, verify, require, etc.) +// to avoid conflicts with identifiers in U++ code (e.g., IMAGE_ID(check)) +// See: https://github.com/opencv/opencv/issues/6047 +#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0 +#include #endif @@ -12,94 +16,151 @@ #include #include "ChCocoMM.h" - -void Coco_PaintCh(void *cgcontext, int type, int value, int state) + +// Static helper function to perform the actual painting +// Replaces lambda for GCC compatibility (GCC doesn't support Apple blocks) +static void DoPaintChInternal(CGContextRef cg, int type, int value, int state) { - auto dopaint = [&] { - auto cg = (CGContextRef) cgcontext; - if(Upp::IsUHDMode()) - CGContextScaleCTM(cg, 2, 2); - CGRect cr = CGRectMake(0, 0, 140, 140); - if(type == COCO_NSCOLOR) { - CGContextSaveGState(cg); - CGContextSetFillColorWithColor(cg, Upp::decode(value, - COCO_PAPER, [NSColor textBackgroundColor].CGColor, - COCO_SELECTEDTEXT, [NSColor selectedTextColor].CGColor, - COCO_SELECTEDPAPER, [NSColor selectedTextBackgroundColor].CGColor, - COCO_DISABLED, [NSColor disabledControlTextColor].CGColor, - COCO_WINDOW, [NSColor windowBackgroundColor].CGColor, - COCO_SELECTEDMENUTEXT, [NSColor selectedMenuItemTextColor].CGColor, - [NSColor textColor].CGColor - )); - CGContextFillRect(cg, cr); - CGContextRestoreGState(cg); + if(Upp::IsUHDMode()) + CGContextScaleCTM(cg, 2, 2); + CGRect cr = CGRectMake(0, 0, 140, 140); + NSRect frameRect = NSMakeRect(cr.origin.x, cr.origin.y, cr.size.width, cr.size.height); + + if(type == COCO_NSCOLOR) { + CGContextSaveGState(cg); + // Use NSColorToCGColor helper for 10.6 compatibility (no .CGColor property) + CGColorRef fillColor; + NSColor *nscolor; + switch(value) { + case COCO_PAPER: + nscolor = [NSColor textBackgroundColor]; + break; + case COCO_SELECTEDTEXT: + nscolor = [NSColor selectedTextColor]; + break; + case COCO_SELECTEDPAPER: + nscolor = [NSColor selectedTextBackgroundColor]; + break; + case COCO_DISABLED: + nscolor = [NSColor disabledControlTextColor]; + break; + case COCO_WINDOW: + nscolor = [NSColor windowBackgroundColor]; + break; + case COCO_SELECTEDMENUTEXT: + nscolor = [NSColor selectedMenuItemTextColor]; + break; + default: + nscolor = [NSColor textColor]; + break; + } + fillColor = NSColorToCGColor(nscolor); + CGContextSetFillColorWithColor(cg, fillColor); + CGColorRelease(fillColor); + CGContextFillRect(cg, cr); + CGContextRestoreGState(cg); + } + else + if(type == COCO_NSIMAGE) { + NSImage *img = [NSImage imageNamed:(value ? NSImageNameInfo : NSImageNameCaution)]; +#ifdef MAC_OS_X_VERSION_10_10 + NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:cg flipped:YES]; + NSGraphicsContext *cgc = [NSGraphicsContext currentContext]; + [NSGraphicsContext setCurrentContext:gc]; + [img drawInRect:NSMakeRect(0, 0, 48, 48)]; + [NSGraphicsContext setCurrentContext:cgc]; +#else + // macOS 10.6-10.9: use graphicsContextWithGraphicsPort:flipped: + NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithGraphicsPort:cg flipped:YES]; + NSGraphicsContext *cgc = [NSGraphicsContext currentContext]; + [NSGraphicsContext setCurrentContext:gc]; + // drawInRect: without fromRect:operation:fraction: not available until 10.9 + [img drawInRect:NSMakeRect(0, 0, 48, 48) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0]; + [NSGraphicsContext setCurrentContext:cgc]; +#endif + } + else { + CGContextSaveGState(cg); + [NSGraphicsContext saveGraphicsState]; +#ifdef MAC_OS_X_VERSION_10_10 + [NSGraphicsContext setCurrentContext: + [NSGraphicsContext graphicsContextWithCGContext:cg flipped:YES]]; +#else + [NSGraphicsContext setCurrentContext: + [NSGraphicsContext graphicsContextWithGraphicsPort:cg flipped:YES]]; +#endif + + const NSRect dirtyRect = NSMakeRect(20, 20, 100, 100); + + if(Upp::findarg(type, COCO_SCROLLTHUMB, COCO_SCROLLTRACK) >= 0) { +#ifdef MAC_OS_X_VERSION_10_7 + int cx = [NSScroller scrollerWidthForControlSize:NSControlSizeRegular scrollerStyle:NSScrollerStyleLegacy]; +#else + int cx = [NSScroller scrollerWidth]; +#endif + NSScroller *scroller = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, 100, cx)]; + [scroller setFloatValue:0]; + [scroller setKnobProportion:1]; +#ifdef MAC_OS_X_VERSION_10_7 + [scroller setKnobStyle:NSScrollerKnobStyleDefault]; + [scroller setScrollerStyle:NSScrollerStyleLegacy]; +#endif + [scroller setFrame:frameRect]; + if(type == COCO_SCROLLTHUMB) + [scroller drawKnob]; + else + [scroller drawKnobSlotInRect:NSMakeRect(20, 20, 100, cx) highlight:YES]; + [scroller release]; } else - if(type == COCO_NSIMAGE) { - NSImage *img = [NSImage imageNamed:(value ? NSImageNameInfo : NSImageNameCaution)]; - NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:cg flipped:YES]; - NSGraphicsContext* cgc = [NSGraphicsContext currentContext]; - [NSGraphicsContext setCurrentContext:gc]; - [img drawInRect:NSMakeRect(0, 0, 48, 48)]; - [NSGraphicsContext setCurrentContext:cgc]; + if(type == COCO_TEXTFIELD) { + NSTextField *tf = [[NSTextField alloc] init]; + [tf setEnabled:YES]; + [tf setEditable:YES]; + [tf setBezeled:YES]; + [tf setFrame:NSMakeRect(0, 0, 140, 40)]; + [tf drawRect:dirtyRect]; + [tf release]; } else { - CGContextSaveGState(cg); - [NSGraphicsContext saveGraphicsState]; - [NSGraphicsContext setCurrentContext: - [NSGraphicsContext graphicsContextWithCGContext:cg flipped:YES]]; - - const CGRect dirtyRect = CGRectMake(20, 20, 100, 100); - - if(Upp::findarg(type, COCO_SCROLLTHUMB, COCO_SCROLLTRACK) >= 0) { - int cx = [NSScroller scrollerWidthForControlSize:NSControlSizeRegular scrollerStyle:NSScrollerStyleLegacy]; - NSScroller *scroller = [[NSScroller alloc] initWithFrame:NSMakeRect(0, 0, 100, cx)]; - scroller.floatValue = 0; - scroller.knobProportion = 1; - scroller.knobStyle = NSScrollerKnobStyleDefault; -// scroller.knobStyle = Upp::IsDarkTheme() ? NSScrollerKnobStyleDark : NSScrollerKnobStyleLight; - scroller.scrollerStyle = NSScrollerStyleLegacy; - scroller.frame = cr; - if(type == COCO_SCROLLTHUMB) - [scroller drawKnob]; - else - [scroller drawKnobSlotInRect:CGRectMake(20, 20, 100, cx) highlight:YES]; - [scroller release]; - } - else - if(type == COCO_TEXTFIELD) { - NSTextField *tf = [[NSTextField alloc] init]; - tf.enabled = YES; - tf.editable = YES; - tf.bezeled = YES; - tf.frame = CGRectMake(0, 0, 140, 40); - [tf drawRect:dirtyRect]; - [tf release]; + NSButton *bc = type == COCO_POPUPBUTTON ? [[NSPopUpButton alloc] init] : [[NSButton alloc] init]; + [bc setAllowsMixedState:(type == COCO_CHECKBOX)]; + [bc setTitle:@""]; + [[bc cell] setControlSize:(type == COCO_RADIOBUTTON ? NSControlSizeSmall : NSControlSizeRegular)]; + [bc setFrame:frameRect]; + NSButtonType btnType; + switch(type) { + case COCO_CHECKBOX: btnType = NSButtonTypeSwitch; break; + case COCO_RADIOBUTTON: btnType = NSButtonTypeRadio; break; + default: btnType = NSButtonTypePushOnPushOff; break; } - else { - NSButton *bc = type == COCO_POPUPBUTTON ? [[NSPopUpButton alloc] init] : [[NSButton alloc] init]; - bc.allowsMixedState = type == COCO_CHECKBOX; - bc.title = @""; - bc.controlSize = type == COCO_RADIOBUTTON ? NSControlSizeSmall : NSControlSizeRegular; - bc.frame = cr; - bc.buttonType = Upp::decode(type, COCO_CHECKBOX, NSButtonTypeSwitch, COCO_RADIOBUTTON, NSButtonTypeRadio, NSButtonTypePushOnPushOff); - bc.bezelStyle = type == COCO_BUTTON ? NSBezelStyleRounded : NSBezelStyleRegularSquare; - bc.state = Upp::decode(value, 0, NSControlStateValueOff, 1, NSControlStateValueOn, NSControlStateValueMixed); - [bc highlight: state == Upp::CTRL_PRESSED]; - bc.enabled = state != Upp::CTRL_DISABLED; - [bc drawRect:dirtyRect]; - [bc release]; + [bc setButtonType:btnType]; + [bc setBezelStyle:(type == COCO_BUTTON ? NSBezelStyleRounded : NSBezelStyleRegularSquare)]; + NSControlStateValue stateValue; + switch(value) { + case 0: stateValue = NSControlStateValueOff; break; + case 1: stateValue = NSControlStateValueOn; break; + default: stateValue = NSControlStateValueMixed; break; } - - [NSGraphicsContext restoreGraphicsState]; - CGContextRestoreGState(cg); + [bc setState:stateValue]; + [bc highlight:(state == Upp::CTRL_PRESSED)]; + [bc setEnabled:(state != Upp::CTRL_DISABLED)]; + [bc drawRect:dirtyRect]; + [bc release]; } - }; - if (@available(macOS 11.0, *)) { - [NSApp.effectiveAppearance performAsCurrentDrawingAppearance:^{ dopaint(); }]; - } else { - dopaint(); + + [NSGraphicsContext restoreGraphicsState]; + CGContextRestoreGState(cg); } } +void Coco_PaintCh(void *cgcontext, int type, int value, int state) +{ + CGContextRef cg = (CGContextRef)cgcontext; + // @available and performAsCurrentDrawingAppearance: require macOS 11.0+ and blocks + // For 10.6 compatibility with GCC, just call the paint function directly + // On macOS 11+ with Clang, appearance is handled automatically by the system + DoPaintChInternal(cg, type, value, state); +} + #endif diff --git a/uppsrc/CtrlLib/Cocoa.mm b/uppsrc/CtrlLib/Cocoa.mm index ae9f3ed76..95ba1b980 100644 --- a/uppsrc/CtrlLib/Cocoa.mm +++ b/uppsrc/CtrlLib/Cocoa.mm @@ -41,15 +41,25 @@ bool FileSelNative::Execute0(int open, const char *title) } if([panel runModal] == NSModalResponseOK) { NSArray* urls = [panel URLs]; - for(int i = 0; i < urls.count; i++) - path.Add([[urls objectAtIndex:i] fileSystemRepresentation]); + // Use traditional loop instead of for-in (GCC compatibility) + // Use [url path] then fileSystemRepresentation (NSURL method returns const char*) + for(NSUInteger i = 0; i < [urls count]; i++) { + NSURL *url = [urls objectAtIndex:i]; + const char *fs = [[url path] fileSystemRepresentation]; + if(fs) + path.Add(fs); + } } } else { NSSavePanel *panel = [NSSavePanel savePanel]; [panel setMessage:(NSString *)~mmtitle]; - if([panel runModal] == NSModalResponseOK) - path.Add([[panel URL] fileSystemRepresentation]); + if([panel runModal] == NSModalResponseOK) { + NSURL *url = [panel URL]; + const char *fs = [[url path] fileSystemRepresentation]; + if(fs) + path.Add(fs); + } } return path.GetCount(); } diff --git a/uppsrc/CtrlLib/MacMenu.mm b/uppsrc/CtrlLib/MacMenu.mm index 06e0959fc..092b57973 100644 --- a/uppsrc/CtrlLib/MacMenu.mm +++ b/uppsrc/CtrlLib/MacMenu.mm @@ -14,19 +14,50 @@ struct CocoMenuBar; }; -@interface CocoMenu : NSMenu -{ -@public - Upp::Ptr ptr; - Upp::Event proc; +// Associated object keys for menu data - defined in this file +static char CocoMenuPtrKey; +static char CocoMenuProcKey; + +// Use NSMenu directly (typedef) to avoid GCC ObjC runtime issues with subclassing +typedef NSMenu CocoMenu; + +// Helper functions to get/set associated menu data +static inline Upp::CocoMenuBar* CocoMenuGetPtr(NSMenu *menu) { + return (Upp::CocoMenuBar*)objc_getAssociatedObject(menu, &CocoMenuPtrKey); +} +static inline void CocoMenuSetPtr(NSMenu *menu, Upp::CocoMenuBar *p) { + objc_setAssociatedObject(menu, &CocoMenuPtrKey, (id)p, OBJC_ASSOCIATION_ASSIGN); +} +static inline Upp::Event* CocoMenuGetProc(NSMenu *menu) { + return (Upp::Event*)objc_getAssociatedObject(menu, &CocoMenuProcKey); +} +static inline void CocoMenuSetProc(NSMenu *menu, Upp::Event *p) { + objc_setAssociatedObject(menu, &CocoMenuProcKey, (id)p, OBJC_ASSOCIATION_ASSIGN); } --(void)cocoMenuAction:(id)sender; + +// Associated object key for storing CocoMenuBar* on each NSMenuItem +// NOTE: CocoMenuItemBarKey is declared extern in CocoMM.h and defined in CocoProc.mm +// so that CocoApp.mm can use the same key for lookups + +// Track last selected menu item for GCC ObjC runtime workaround +static NSMenuItem *lastSelectedMenuItem = nil; +static BOOL menuItemWasClicked = NO; + +// Delegate object to handle NSMenuDelegate methods (menuWillOpen/menuDidClose) +// Also handles menu item actions since GCC ObjC runtime doesn't dispatch action/target properly +@interface CocoMenuDelegate : NSObject +- (void)menuItemAction:(id)sender; +- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item; @end +// Global delegate instance - shared by all menus +static CocoMenuDelegate *sharedMenuDelegate = nil; + namespace Upp { struct CocoMenuBar : public Bar { CocoMenu *cocomenu; + Event *proc; // Heap-allocated to avoid C++ object in ObjC associated storage int lock = 0; bool dockmenu = false; int cy = 0; // estimate of height to place the menu correctly @@ -58,20 +89,20 @@ struct CocoMenuBar : public Bar { ~Item() { if(nsitem) [nsitem release]; } }; - + Array item; - + void StartCheck() { just_check = true; check_i = 0; is_same = true; } - + bool CheckedIsSame() { just_check = false; return is_same; } - + Item& AddItem() { if(just_check) { if(is_same && check_i < item.GetCount()) @@ -95,19 +126,26 @@ struct CocoMenuBar : public Bar { Item& m = AddItem(); if(!just_check) { m.cb = cb; - m.nsitem.target = cocomenu; - m.nsitem.action = @selector(cocoMenuAction:); + // Store bar pointer on the menu item for lookup in the action + objc_setAssociatedObject(m.nsitem, &CocoMenuItemBarKey, (id)this, OBJC_ASSOCIATION_ASSIGN); + // Use sharedMenuDelegate as target - it's an ObjC object created in this file + // so the selector should be properly registered with GCC ObjC runtime + // Note: sharedMenuDelegate is guaranteed to exist because New() creates it + ASSERT(sharedMenuDelegate != nil); + [m.nsitem setTarget:sharedMenuDelegate]; + [m.nsitem setAction:@selector(menuItemAction:)]; } return m; } - - virtual Item& AddSubMenu(Event proc) { + + virtual Item& AddSubMenu(Event proc_) { Item& m = AddItem(); if(!just_check) { m.submenu.Create(); - m.submenu->cocomenu->proc = proc; - m.nsitem.action = @selector(cocoMenuAction:); - m.nsitem.submenu = m.submenu->cocomenu; + *m.submenu->proc = proc_; + // Note: submenu items don't need action/target - the submenu itself handles opening + // The action selector is for the submenu's delegate, not the menu item + [m.nsitem setSubmenu:m.submenu->cocomenu]; } return m; } @@ -117,39 +155,37 @@ struct CocoMenuBar : public Bar { virtual bool IsEmpty() const; virtual void Separator(); - + void MenuAction(id item); - + void Set(Event bar); - + void ClearItems() { cy = 0; just_check = false; is_same = false; item.Clear(); } - + void Clear() { ClearItems(); if(cocomenu) { + CocoMenuSetPtr(cocomenu, NULL); + CocoMenuSetProc(cocomenu, NULL); [cocomenu release]; cocomenu = NULL; } } - void New() { - Clear(); - cocomenu = [CocoMenu new]; - cocomenu.autoenablesItems = NO; - cocomenu->ptr = this; - cocomenu.delegate = cocomenu; - } - + void New(); + CocoMenuBar() { cocomenu = NULL; + proc = new Event(); New(); } ~CocoMenuBar() { Clear(); + delete proc; } }; @@ -209,9 +245,9 @@ CocoMenuBar::Item& CocoMenuBar::Item::Text(const char *text) h.Cat(*text++); } NSString *s = [NSString stringWithUTF8String:~h]; - nsitem.title = s; + [nsitem setTitle:s]; if(submenu) - submenu->cocomenu.title = s; + [submenu->cocomenu setTitle:s]; return *this; } @@ -234,11 +270,11 @@ CocoMenuBar::Item& CocoMenuBar::Item::Key(dword key) auto *v = FindTuple(code, __countof(code), key & ~(K_CTRL|K_SHIFT|K_ALT|K_OPTION)); if(v) { unichar chr = v->b; - nsitem.keyEquivalent = [NSString stringWithCharacters:&chr length:1]; - nsitem.keyEquivalentModifierMask = (key & K_CTRL ? NSEventModifierFlagCommand : 0) | - (key & K_SHIFT ? NSEventModifierFlagShift : 0) | - (key & K_ALT ? NSEventModifierFlagControl : 0) | - (key & K_OPTION ? NSEventModifierFlagOption : 0); + [nsitem setKeyEquivalent:[NSString stringWithCharacters:&chr length:1]]; + [nsitem setKeyEquivalentModifierMask:(key & K_CTRL ? NSEventModifierFlagCommand : 0) | + (key & K_SHIFT ? NSEventModifierFlagShift : 0) | + (key & K_ALT ? NSEventModifierFlagControl : 0) | + (key & K_OPTION ? NSEventModifierFlagOption : 0)]; } return *this; } @@ -247,7 +283,7 @@ CocoMenuBar::Item& CocoMenuBar::Item::Image(const class Image& img) { if(FailCheck()) return *this; - nsitem.image = GetNSImage(img); + [nsitem setImage:GetNSImage(img)]; return *this; } @@ -255,7 +291,7 @@ CocoMenuBar::Item& CocoMenuBar::Item::Check(bool check) { if(FailCheck()) return *this; - nsitem.state = check ? NSControlStateValueOn : NSControlStateValueOff; + [nsitem setState:(check ? NSControlStateValueOn : NSControlStateValueOff)]; return *this; } @@ -271,7 +307,7 @@ CocoMenuBar::Item& CocoMenuBar::Item::Enable(bool enable) if(FailCheck(enabled == enable)) return *this; enabled = enable; - nsitem.enabled = enable; + [nsitem setEnabled:enable]; return *this; } @@ -288,39 +324,79 @@ bool CocoMenuBar::IsEmpty() const return item.GetCount() == 0; } +// Implementation of New() - must be after CocoMenuDelegate is declared but before it's used +void CocoMenuBar::New() { + Clear(); + if(!sharedMenuDelegate) + sharedMenuDelegate = [[CocoMenuDelegate alloc] init]; + cocomenu = [[NSMenu alloc] init]; + [cocomenu setAutoenablesItems:NO]; + CocoMenuSetPtr(cocomenu, this); + CocoMenuSetProc(cocomenu, proc); + [cocomenu setDelegate:sharedMenuDelegate]; +} + +// Function called by AppDelegate when menu item is clicked +// This is exposed so CocoApp.mm can call it +// Called from CocoView::cocoMenuAction and AppDelegate::cocoMenuAction +// Uses void* to allow extern declaration without struct definition +void CocoMenuBarAction(void *barPtr, id sender) { + CocoMenuBar *bar = (CocoMenuBar *)barPtr; + if(bar) + bar->MenuAction(sender); +} + } -@implementation CocoMenu +@implementation CocoMenuDelegate --(void)cocoMenuAction:(id)sender { - if(ptr) - ptr->MenuAction(sender); +- (void)menuItemAction:(id)sender { + Upp::GuiLock __; + NSMenuItem *item = (NSMenuItem *)sender; + void *barPtr = objc_getAssociatedObject(item, &CocoMenuItemBarKey); + if(barPtr) { + Upp::CocoMenuBarAction(barPtr, sender); + } +} + +- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item { + // Track highlighted item - if it's a real item (not nil, not separator, not submenu parent) + if(item && ![item isSeparatorItem] && ![item hasSubmenu]) { + lastSelectedMenuItem = item; + } } - (void)menuWillOpen:(NSMenu *)menu { - CocoMenu *m = (CocoMenu *)menu; - if(m && m->ptr && m->ptr->dockmenu) + lastSelectedMenuItem = nil; + menuItemWasClicked = NO; + Upp::CocoMenuBar *ptr = CocoMenuGetPtr(menu); + Upp::Event *proc = CocoMenuGetProc(menu); + if(ptr && ptr->dockmenu) return; - if(m && m->ptr && proc) { - m->ptr->ClearItems(); - [m removeAllItems]; - proc(*m->ptr); + if(ptr && proc && *proc) { + ptr->ClearItems(); + [menu removeAllItems]; + (*proc)(*ptr); } } - (void)menuDidClose:(NSMenu *)menu { - CocoMenu *m = (CocoMenu *)menu; - if(m && m->ptr && m->ptr->dockmenu) + Upp::CocoMenuBar *ptr = CocoMenuGetPtr(menu); + if(ptr && ptr->dockmenu) return; - // DO NOT CALL ClearItems here - menu is closed before MenuAction, we need items to find - // correct callback - [m removeAllItems]; -} --(void)submenuAction:(id)sender { - if(ptr) - proc(*ptr); - [super submenuAction:sender]; + // GCC ObjC runtime workaround: action/target dispatch doesn't work, + // so we manually invoke the action for the highlighted item when menu closes + if(lastSelectedMenuItem && [lastSelectedMenuItem isEnabled]) { + void *barPtr = objc_getAssociatedObject(lastSelectedMenuItem, &CocoMenuItemBarKey); + if(barPtr) { + Upp::GuiLock __; + Upp::CocoMenuBarAction(barPtr, lastSelectedMenuItem); + } + lastSelectedMenuItem = nil; + } + + [menu removeAllItems]; } @end @@ -422,10 +498,13 @@ NSMenu *Cocoa_DockMenu() { static Upp::CocoMenuBar bar; bar.dockmenu = true; bar.Clear(); - bar.cocomenu = [[[CocoMenu alloc] initWithTitle:@"DocTile Menu"] autorelease]; - bar.cocomenu.autoenablesItems = NO; - bar.cocomenu->ptr = &bar; - bar.cocomenu.delegate = bar.cocomenu; + bar.cocomenu = [[[NSMenu alloc] initWithTitle:@"DocTile Menu"] autorelease]; + [bar.cocomenu setAutoenablesItems:NO]; + CocoMenuSetPtr(bar.cocomenu, &bar); + CocoMenuSetProc(bar.cocomenu, bar.proc); + if(!sharedMenuDelegate) + sharedMenuDelegate = [[CocoMenuDelegate alloc] init]; + [bar.cocomenu setDelegate:sharedMenuDelegate]; w->WhenDockMenu(bar); CocoMenu *m = bar.cocomenu; bar.cocomenu = NULL; diff --git a/uppsrc/Draw/Drawing.cpp b/uppsrc/Draw/Drawing.cpp index 088275185..295e22000 100644 --- a/uppsrc/Draw/Drawing.cpp +++ b/uppsrc/Draw/Drawing.cpp @@ -46,8 +46,8 @@ static void StreamUnpackPoints(Stream& stream, Point *out, int count) byte *top = reinterpret_cast(end) - count * 8; stream.Get(top, count * 8); for(; out < end; out++, top += 8) { - out -> x = (short)Peek32le(top + 0); - out -> y = (short)Peek32le(top + 4); + out -> x = Peek32le(top + 0); + out -> y = Peek32le(top + 4); } } @@ -68,7 +68,7 @@ static void StreamPackPoints(Stream& stream, const Point *in, int count) Poke32le(pp + 0, in -> x); Poke32le(pp + 4, in -> y); } - stream.Put(part, part_count * 4); + stream.Put(part, part_count * 8); count -= part_count; } } @@ -265,7 +265,10 @@ void DrawingDraw::DrawTextOp(int x, int y, int angle, const wchar *text, Font fo #ifdef CPU_LE s.Put(text, n * sizeof(wchar)); #else - #error big endiand not supported + Buffer txt(n); + memcpy(txt, text, n * sizeof(wchar)); + EndianSwap((dword *)(wchar *)txt, n); + s.Put(txt, n * sizeof(wchar)); #endif bool dxb = dx; s % dxb; @@ -558,6 +561,9 @@ void Draw::DrawDrawingOp(const Rect& target, const Drawing& w) { if(cs == CHARSET_UTF32) { Buffer txt(n); ps.Stream::Get(txt, n * sizeof(wchar)); +#ifdef CPU_BE + EndianSwap((dword *)(wchar *)txt, n); +#endif text = WString(txt, n); } else diff --git a/uppsrc/Draw/FontCoco.mm b/uppsrc/Draw/FontCoco.mm index 4d7cb0ac8..82d820c32 100644 --- a/uppsrc/Draw/FontCoco.mm +++ b/uppsrc/Draw/FontCoco.mm @@ -7,6 +7,11 @@ #ifndef flagNOMM // Removes ObjectiveC and AppKit dependence in Draw (but disables Fonts) +// Disable old-style Carbon assertion macros (check, verify, require, etc.) +#ifndef __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES +#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0 +#endif + #define Point NS_Point #define Rect NS_Rect #define Size NS_Size @@ -15,6 +20,12 @@ #undef Rect #undef Size +// macOS 10.6 SDK compatibility - kCTFontOrientationHorizontal added in 10.8 +// kCTFontDefaultOrientation (value 0) is equivalent for horizontal text +#ifndef kCTFontOrientationHorizontal +#define kCTFontOrientationHorizontal kCTFontDefaultOrientation +#endif + namespace Upp { namespace Detail { // TODO: following utilities are normally defined in CtrlCore, rename to avoid name clash @@ -194,9 +205,13 @@ CommonFontInfo GetFontInfoSys(Font font) fi.ttf = true; CFRef fd = CTFontCopyFontDescriptor(ctfont); - CFRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fd, kCTFontURLAttribute); - CFRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); - String p = ToString(path); + CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fd, kCTFontURLAttribute); + String p; + if(url) { + CFRef pathStr = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); + p = ToString(pathStr); + CFRelease(url); + } if(p.GetCount() < 250) strcpy(fi.path, ~p); @@ -254,7 +269,9 @@ Vector GetAllFacesSys() fi.info = Font::SCALEABLE; if(traits & kCTFontMonoSpaceTrait) fi.info |= Font::FIXEDPITCH; - switch(traits & kCTFontClassMaskTrait) { + // Cast to int32 to avoid narrowing conversion error with kCTFontScriptsClass + // (which has high bit set and is defined as signed int) + switch((int32)(traits & kCTFontClassMaskTrait)) { case kCTFontOldStyleSerifsClass: case kCTFontTransitionalSerifsClass: case kCTFontModernSerifsClass: @@ -266,6 +283,8 @@ Vector GetAllFacesSys() case kCTFontScriptsClass: fi.info |= Font::SCRIPTSTYLE; break; + default: + break; } } diff --git a/uppsrc/ide/Builders/Cocoa.cpp b/uppsrc/ide/Builders/Cocoa.cpp index 375c64129..be89dab3a 100644 --- a/uppsrc/ide/Builders/Cocoa.cpp +++ b/uppsrc/ide/Builders/Cocoa.cpp @@ -58,6 +58,10 @@ void GccBuilder::CocoaAppBundle() } if(IsNull(Info_plist)) { + // Get deployment target from environment, default to 10.6 for maximum compatibility + String macos_version = GetEnv("MACOSX_DEPLOYMENT_TARGET"); + if(IsNull(macos_version) || macos_version.IsEmpty()) + macos_version = "10.6"; Info_plist << "\n" << "\n" @@ -68,7 +72,7 @@ void GccBuilder::CocoaAppBundle() << " NSHighResolutionCapable\n" << " True\n" << " LSMinimumSystemVersion\n" - << " 10.13.0\n" + << " " << macos_version << "\n" ; if(imgs.GetCount()) Info_plist diff --git a/uppsrc/ide/Builders/Install.cpp b/uppsrc/ide/Builders/Install.cpp index 87138c0fb..8260d267f 100644 --- a/uppsrc/ide/Builders/Install.cpp +++ b/uppsrc/ide/Builders/Install.cpp @@ -31,6 +31,33 @@ INCLUDE = "$INCLUDE$"; LIB = "$LIB$"; LINKMODE_LOCK = "0";)"; +const char *gcc_bm = +R"(BUILDER = "GCC"; +COMPILER = "@CXX@"; +COMMON_OPTIONS = "$COMMON$"; +COMMON_CPP_OPTIONS = "-std=c++17 @ABI_FLAG@ -isystem@PREFIX@/include/LegacySupport"; +COMMON_C_OPTIONS = "-isystem@PREFIX@/include/LegacySupport"; +COMMON_LINK = "$COMMON$ -L@PREFIX@/lib -lMacportsLegacySupport"; +COMMON_FLAGS = ""; +DEBUG_INFO = "2"; +DEBUG_BLITZ = "1"; +DEBUG_LINKMODE = "1"; +DEBUG_OPTIONS = "-O0"; +DEBUG_FLAGS = ""; +DEBUG_LINK = ""; +RELEASE_BLITZ = "1"; +RELEASE_LINKMODE = "1"; +RELEASE_OPTIONS = "-O3 -ffunction-sections -fdata-sections"; +RELEASE_FLAGS = ""; +RELEASE_LINK = ""; +DEBUGGER = "gdb"; +ALLOW_PRECOMPILED_HEADERS = "0"; +DISABLE_BLITZ = "0"; +PATH = ""; +INCLUDE = "$INCLUDE$"; +LIB = "$LIB$"; +LINKMODE_LOCK = "0";)"; + #elif PLATFORM_SOLARIS const char *gcc_bm = @@ -148,9 +175,9 @@ LINKMODE_LOCK = "0";)"; void CreateBuildMethods() { #ifdef PLATFORM_COCOA - String bm_path = ConfigFile("CLANG.bm"); + String bm_path = ConfigFile("GCC.bm"); if(IsNull(LoadFile(bm_path))) { - String bm = clang_bm; + String bm = gcc_bm; auto Path = [&](const char *var, const char *path) { String h; @@ -160,14 +187,11 @@ void CreateBuildMethods() bm.Replace(var, h); }; - Path("$INCLUDE$", "/opt/local/include;/usr/include;/usr/local/include;/opt/homebrew/include;/opt/homebrew/opt/openssl/include"); - Path("$LIB$", "/opt/local/lib;/usr/lib;/usr/local/lib;/opt/homebrew/lib;/opt/homebrew/opt/openssl/lib"); - - String common; - #ifdef CPU_ARM - common = "-arch arm64"; - #endif - bm.Replace("$COMMON$", common); + Path("$INCLUDE$", "@PREFIX@/include/LegacySupport;@PREFIX@/include;/usr/include"); + Path("$LIB$", "@PREFIX@/lib/libgcc;@PREFIX@/lib;/usr/lib"); + + // No hardcoded architecture flags - let compiler use native architecture + bm.Replace("$COMMON$", ""); SaveFile(bm_path, bm); } @@ -179,11 +203,9 @@ void CreateBuildMethods() r.Replace("INCLUDE = \"\";", "INCLUDE = \"/usr/local/opt/openssl/include\";"); r.Replace("LIB = \"\";", "LIB = \"/usr/local/opt/openssl/lib\";"); } - String common; - #ifdef CPU_X86 - common = "-mpopcnt"; - #endif - r.Replace("$COMMON$", common); + // No hardcoded architecture flags for non-macOS POSIX + // -mpopcnt requires SSE4.2, not available on all x86 CPUs + r.Replace("$COMMON$", ""); return r; }; diff --git a/uppsrc/plugin/bmp/_bmp.h b/uppsrc/plugin/bmp/_bmp.h index 633310983..7f065cf3a 100644 --- a/uppsrc/plugin/bmp/_bmp.h +++ b/uppsrc/plugin/bmp/_bmp.h @@ -1,9 +1,6 @@ #ifndef _nImage__bmp_h_ #define _nImage__bmp_h_ -#ifdef CPU_BIG_ENDIAD -#error "Fix big endian issues!" -#endif #ifdef COMPILER_MSC #pragma pack(push, 1) @@ -19,9 +16,9 @@ struct BMP_FILEHEADER { void EndianSwap() { #ifdef CPU_BIG_ENDIAN - EndianSwap(bfType); - EndianSwap(bfSize); - EndianSwap(bfOffBits); + Upp::EndianSwap(bfType); + Upp::EndianSwap(bfSize); + Upp::EndianSwap(bfOffBits); #endif } } @@ -47,18 +44,17 @@ struct BMP_INFOHEADER void EndianSwap() { #ifdef CPU_BIG_ENDIAN - EndianSwap(biSize); - EndianSwap(biWidth); - EndianSwap(biHeight); - EndianSwap(biPlanes); - EndianSwap(biBitCount); - EndianSwap(biCompression); - EndianSwap(biSizeImage); - EndianSwap(biXPelsPerMeter); - EndianSwap(biYPelsPerMeter); - EndianSwap(biClrUsed); - EndianSwap(biClrImportant); - + Upp::EndianSwap(biSize); + Upp::EndianSwap(biWidth); + Upp::EndianSwap(biHeight); + Upp::EndianSwap(biPlanes); + Upp::EndianSwap(biBitCount); + Upp::EndianSwap(biCompression); + Upp::EndianSwap(biSizeImage); + Upp::EndianSwap(biXPelsPerMeter); + Upp::EndianSwap(biYPelsPerMeter); + Upp::EndianSwap(biClrUsed); + Upp::EndianSwap(biClrImportant); #endif } } @@ -84,9 +80,9 @@ struct ICONDIR void EndianSwap() { #ifdef CPU_BIG_ENDIAN - EndianSwap(idReserved); - EndianSwap(idType); - EndianSwap(idCount); + Upp::EndianSwap(idReserved); + Upp::EndianSwap(idType); + Upp::EndianSwap(idCount); #endif } } @@ -109,11 +105,10 @@ struct ICONDIRENTRY void EndianSwap() { #ifdef CPU_BIG_ENDIAN - EndianSwap(wHotSpotX); - EndianSwap(wHotSpotY); - EndianSwap(dwBytesInRes); - EndianSwap(dwImageOffset); - + Upp::EndianSwap(wHotSpotX); + Upp::EndianSwap(wHotSpotY); + Upp::EndianSwap(dwBytesInRes); + Upp::EndianSwap(dwImageOffset); #endif } } diff --git a/uppsrc/plugin/bmp/bmphdr.h b/uppsrc/plugin/bmp/bmphdr.h index 1a4cfaef6..c5e75c759 100644 --- a/uppsrc/plugin/bmp/bmphdr.h +++ b/uppsrc/plugin/bmp/bmphdr.h @@ -15,9 +15,15 @@ struct BMP_FILEHEADER { void SwapEndian() { #ifdef CPU_BIG_ENDIAN - bfType = UPP::SwapEndian(bfType); - bfSize = UPP::SwapEndian(bfSize); - bfOffBits = UPP::SwapEndian(bfOffBits); + word tmp_bfType = bfType; + dword tmp_bfSize = bfSize; + dword tmp_bfOffBits = bfOffBits; + Upp::EndianSwap(tmp_bfType); + Upp::EndianSwap(tmp_bfSize); + Upp::EndianSwap(tmp_bfOffBits); + bfType = tmp_bfType; + bfSize = tmp_bfSize; + bfOffBits = tmp_bfOffBits; #endif } } @@ -43,17 +49,39 @@ struct BMP_INFOHEADER void SwapEndian() { #ifdef CPU_BIG_ENDIAN - biSize = UPP::SwapEndian(biSize); - biWidth = UPP::SwapEndian(biWidth); - biHeight = UPP::SwapEndian(biHeight); - biPlanes = UPP::SwapEndian(biPlanes); - biBitCount = UPP::SwapEndian(biBitCount); - biCompression = UPP::SwapEndian(biCompression); - biSizeImage = UPP::SwapEndian(biSizeImage); - biXPelsPerMeter = UPP::SwapEndian(biXPelsPerMeter); - biYPelsPerMeter = UPP::SwapEndian(biYPelsPerMeter); - biClrUsed = UPP::SwapEndian(biClrUsed); - biClrImportant = UPP::SwapEndian(biClrImportant); + dword tmp_biSize = biSize; + int32 tmp_biWidth = biWidth; + int32 tmp_biHeight = biHeight; + word tmp_biPlanes = biPlanes; + word tmp_biBitCount = biBitCount; + dword tmp_biCompression = biCompression; + dword tmp_biSizeImage = biSizeImage; + int32 tmp_biXPelsPerMeter = biXPelsPerMeter; + int32 tmp_biYPelsPerMeter = biYPelsPerMeter; + dword tmp_biClrUsed = biClrUsed; + dword tmp_biClrImportant = biClrImportant; + Upp::EndianSwap(tmp_biSize); + Upp::EndianSwap(tmp_biWidth); + Upp::EndianSwap(tmp_biHeight); + Upp::EndianSwap(tmp_biPlanes); + Upp::EndianSwap(tmp_biBitCount); + Upp::EndianSwap(tmp_biCompression); + Upp::EndianSwap(tmp_biSizeImage); + Upp::EndianSwap(tmp_biXPelsPerMeter); + Upp::EndianSwap(tmp_biYPelsPerMeter); + Upp::EndianSwap(tmp_biClrUsed); + Upp::EndianSwap(tmp_biClrImportant); + biSize = tmp_biSize; + biWidth = tmp_biWidth; + biHeight = tmp_biHeight; + biPlanes = tmp_biPlanes; + biBitCount = tmp_biBitCount; + biCompression = tmp_biCompression; + biSizeImage = tmp_biSizeImage; + biXPelsPerMeter = tmp_biXPelsPerMeter; + biYPelsPerMeter = tmp_biYPelsPerMeter; + biClrUsed = tmp_biClrUsed; + biClrImportant = tmp_biClrImportant; #endif } } diff --git a/uppsrc/plugin/pcx/pcxhdr.h b/uppsrc/plugin/pcx/pcxhdr.h index 90a51ea38..98156ad3a 100644 --- a/uppsrc/plugin/pcx/pcxhdr.h +++ b/uppsrc/plugin/pcx/pcxhdr.h @@ -40,17 +40,16 @@ struct PCXHeader { void SwapEndian() { #ifdef CPU_BIG_ENDIAN - biSize = UPP::SwapEndian(biSize); - biWidth = UPP::SwapEndian(biWidth); - biHeight = UPP::SwapEndian(biHeight); - biPlanes = UPP::SwapEndian(biPlanes); - biBitCount = UPP::SwapEndian(biBitCount); - biCompression = UPP::SwapEndian(biCompression); - biSizeImage = UPP::SwapEndian(biSizeImage); - biXPelsPerMeter = UPP::SwapEndian(biXPelsPerMeter); - biYPelsPerMeter = UPP::SwapEndian(biYPelsPerMeter); - biClrUsed = UPP::SwapEndian(biClrUsed); - biClrImportant = UPP::SwapEndian(biClrImportant); + Upp::EndianSwap(minX); + Upp::EndianSwap(minY); + Upp::EndianSwap(maxX); + Upp::EndianSwap(maxY); + Upp::EndianSwap(horzDpi); + Upp::EndianSwap(vertDpi); + Upp::EndianSwap(bytesPerLine); + Upp::EndianSwap(paltype); + Upp::EndianSwap(hScreenSize); + Upp::EndianSwap(vScreenSize); #endif } diff --git a/uppsrc/plugin/tif/lib/tif_config.h b/uppsrc/plugin/tif/lib/tif_config.h index 8734ffa9d..3690049e5 100644 --- a/uppsrc/plugin/tif/lib/tif_config.h +++ b/uppsrc/plugin/tif/lib/tif_config.h @@ -144,6 +144,8 @@ # if defined __BIG_ENDIAN__ # define WORDS_BIGENDIAN 1 # endif +#elif defined(CPU_BIG_ENDIAN) || defined(__BIG_ENDIAN__) || defined(__ppc__) || defined(__ppc64__) +# define WORDS_BIGENDIAN 1 #else # ifndef WORDS_BIGENDIAN # undef WORDS_BIGENDIAN -- 2.54.0