diff --git a/include/lgi/common/Dialog.h b/include/lgi/common/Dialog.h --- a/include/lgi/common/Dialog.h +++ b/include/lgi/common/Dialog.h @@ -1,233 +1,233 @@ #pragma once #include #include "lgi/common/Window.h" #include "lgi/common/LgiRes.h" /// \brief A top level dialog window. /// /// LDialog's can either be modal or modeless. A modal dialog blocks the user from accessing /// the parent LWindow until the dialog has been dismissed. A modeless dialog behaves like /// any other top level window in that it doesn't block the user accessing any other top level /// window while it's open. /// /// LDialog's can be created in two different ways. Firstly you can create code to instantiate /// all the various controls and add them manually to the LView::Children list. Or you can load /// all the controls from a resource file. The resource files are in XML format and there is a /// graphical tool LgiRes. /// /// Manual example: /// \code /// #define IDC_STRING 100 /// /// class Example : public LDialog /// { /// public: /// char *Str; /// /// Example(LView *p) /// { /// Str = 0; /// SetParent(p); /// LRect r(0, 0, 400, 300); /// SetPos(p); /// MoveToCenter(); /// /// Children.Insert(new LEdit(IDC_STRING, 10, 10, 200, 20, "")); /// Children.Insert(new LButton(IDOK, 10, 30, 80, 20, "Ok")); /// Children.Insert(new LButton(IDCANCEL, 100, 30, 80, 20, "Cancel")); /// } /// /// ~Example() /// { /// DeleteArray(Str); /// } /// /// int OnNotify(LViewI *c, int Flags) /// { /// switch (c->GetId()) /// { /// case IDOK: /// { /// Str = NewStr(GetCtrlName(IDC_STRING)); /// // fall thru /// } /// case IDCANCEL: /// { /// EndModal(c->GetId()); /// break; /// } /// } /// /// return 0; /// } /// }; /// \endcode /// /// This example shows how to insert child widgets into the window and then process clicks on the buttons /// and finally return data to the caller via a public member variable. It is desirable to pass data using /// this method because a dialog could be running in it's own thread on some systems and therefor should not /// be accessing data structures outside of itself directly without locking them. If your main application' /// doesn't have thread locking in place for it's main data structures then accessing them from the dialog /// wouldn't be thread safe. This is mainly the case with the BeOS port of Lgi, but is good practise for /// the Windows and Linux ports for cross platform compatibility. /// /// The resource file method of creating dialogs is arguably the easier route to take once your've got it /// set up. Firstly create a resource file using LgiRes with the same name as your programs executable, but /// a different extension ("lr8"). When you call the LResourceLoad::LoadFromResource function Lgi will automatically /// look for a file named after the running executable with the right extension and load it. Then it will /// find the dialog resource and instantiate all the controls specified in the resource. All the built in /// Lgi controls are supported directly as tags in the XML file and you can create your own custom controls /// that the resource loader can instantiate as well using the LViewFactory system. /// /// Resource file example: /// \code /// #include "Resdefs.h" // For the dialog defines /// /// class Example : public LDialog /// { /// public: /// char *Str; /// /// Example(LView *p) /// { /// Str = 0; /// SetParent(p); /// if (LoadFromResource(IDD_EXAMPLE)) /// { /// MoveToCenter(); /// } /// } /// /// ~Example() /// { /// DeleteArray(Str); /// } /// /// int OnNotify(LViewI *c, int Flags) /// { /// switch (c->GetId()) /// { /// case IDOK: /// { /// Str = NewStr(GetCtrlName(1)); /// // fall thru /// } /// case IDCANCEL: /// { /// EndModal(c->GetId()); /// break; /// } /// } /// /// return 0; /// } /// }; /// \endcode /// /// This assumes you have created an lr8 file with the resource named 'IDD_EXAMPLE' in it. /// /// Now to actually call either of these dialogs you would use code like this: /// \code /// Example Dlg(MyWindow); /// if (Dlg.DoModal() == IDOK) /// { /// // Do something with Dlg.Str /// } /// \endcode /// /// The built in controls that you can use are: /// class LgiClass LDialog : public LWindow, public LResourceLoad, public ResObject { friend class LControl; private: struct LDialogPriv *d; public: typedef std::function OnClose; /// Constructor LDialog(LViewI *Parent = NULL); /// Destructor ~LDialog(); const char *GetClass() override { return "LDialog"; } /// Load the dialog from a resource bool LoadFromResource ( /// The resource ID int Resource, /// [Optional] tag list to exclude/include various controls via tag char *TagList = 0 ); /// \brief Run the dialog in modal mode. /// /// The user has to dismiss the dialog to continue. virtual void DoModal ( /// Call back to handle post-dialog activity OnClose Callback, /// Optional override parent window handle OsView ParentHnd = NULL ); /// \brief Run the dialog in modeless mode /// /// It will behave like any other top level window. The user doesn't /// have to dismiss the window to continue, it can just move to the back. virtual int DoModeless(); /// Gets the model status virtual bool IsModal(); /// End a modal window. Typically calling in the OnNotify event of the LDialog. virtual void EndModal(int Code = 0); /// End a modeless window. Typically calling in the OnNotify event of the LDialog. virtual void EndModeless(int Code = 0); - LMessage::Result OnEvent(LMessage *Msg); - bool OnRequestClose(bool OsClose); - void OnPosChange(); - void PourAll() {} - void Quit(bool DontDelete = false); + LMessage::Result OnEvent(LMessage *Msg) override; + bool OnRequestClose(bool OsClose) override; + void OnPosChange() override; + void PourAll() override {} + void Quit(bool DontDelete = false) override; /// By default the dialog will finish when a button is pressed. To override this /// behavior you'll have to subclass LDialog and handle the OnNotify yourself. - int OnNotify(LViewI *Ctrl, LNotification n); + int OnNotify(LViewI *Ctrl, LNotification n) override; /// This returns the ID of the button pressed to close the dialog. int GetButtonId(); #if defined(__GTK_H__) friend Gtk::gboolean GtkDialogDestroy(Gtk::GtkWidget *widget, LDialog *This); bool IsResizeable(); void IsResizeable(bool r); bool SetupDialog(bool Modal); #elif defined(LGI_CARBON) void OnPaint(LSurface *pDC); #endif }; diff --git a/include/lgi/common/Window.h b/include/lgi/common/Window.h --- a/include/lgi/common/Window.h +++ b/include/lgi/common/Window.h @@ -1,315 +1,315 @@ #ifndef _LWINDOW_H_ #define _LWINDOW_H_ #include "lgi/common/View.h" /// The available states for a top level window enum LWindowZoom { /// Minimized LZoomMin, /// Restored/Normal LZoomNormal, /// Maximized LZoomMax }; enum LWindowHookType { LNoEvents = 0, /// \sa LWindow::RegisterHook() LMouseEvents = 1, /// \sa LWindow::RegisterHook() LKeyEvents = 2, /// \sa LWindow::RegisterHook() LKeyAndMouseEvents = LMouseEvents | LKeyEvents, }; /// A top level window. class LgiClass LWindow : public LView, // This needs to be second otherwise is causes v-table problems. #ifndef LGI_SDL virtual #endif public LDragDropTarget { friend class BViewRedir; friend class LApp; friend class LView; friend class LButton; friend class LDialog; friend class LWindowPrivate; friend struct LDialogPriv; bool _QuitOnClose; protected: class LWindowPrivate *d; #if WINNATIVE LRect OldPos; LWindow *_Dialog; #elif defined(HAIKU) LWindowZoom _PrevZoom = LZoomNormal; #else OsWindow Wnd; void SetDeleteOnClose(bool i); #endif #if defined __GTK_H__ friend class LMenu; friend void lgi_widget_size_allocate(Gtk::GtkWidget *widget, Gtk::GtkAllocation *allocation); Gtk::GtkWidget *_Root, *_VBox, *_MenuBar; void OnGtkDelete(); Gtk::gboolean OnGtkEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event); #elif defined(LGI_CARBON) friend pascal OSStatus LgiWindowProc(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); void _Delete(); bool _RequestClose(bool os); #elif defined(__OBJC__) public: // This returns the root level content NSView NSView *Handle(); protected: #endif /// The default button LViewI *_Default; /// The menu on the window LMenu *Menu; void SetChildDialog(LDialog *Dlg); void SetDragHandlers(bool On); /// Haiku: This shuts down the window's thread cleanly. int WaitThread(); public: #ifdef _DEBUG LMemDC DebugDC; #endif #ifdef __GTK_H__ LWindow(Gtk::GtkWidget *w = NULL); #elif LGI_CARBON LWindow(WindowRef wr = NULL); #elif LGI_COCOA LWindow(OsWindow wnd = NULL); #else LWindow(); #endif ~LWindow(); const char *GetClass() override { return "LWindow"; } /// Lays out the child views into the client area. virtual void PourAll(); /// Returns the current menu object LMenu *GetMenu() { return Menu; } /// Set the menu object. void SetMenu(LMenu *m) { Menu = m; } /// Set the window's icon bool SetIcon(const char *FileName); /// Gets the "quit on close" setting. bool GetQuitOnClose() { return _QuitOnClose; } /// \brief Sets the "quit on close" setting. /// /// When this is switched on the application will quit the main message /// loop when this LWindow is closed. This is really useful for your /// main application window. Otherwise the UI will disappear but the /// application is still running. void SetQuitOnClose(bool i) { _QuitOnClose = i; } bool GetSnapToEdge(); void SetSnapToEdge(bool b); bool GetAlwaysOnTop(); void SetAlwaysOnTop(bool b); /// Gets the current zoom setting LWindowZoom GetZoom(); /// Sets the current zoom void SetZoom(LWindowZoom i); /// Raises the window to the top of the stack. void Raise(); /// Moves a top level window on screen. void MoveOnScreen(); /// Moves a top level to the center of the screen void MoveToCenter(); /// Moves a top level window to where the mouse is void MoveToMouse(); /// Moves the window to somewhere on the same screen as 'wnd' bool MoveSameScreen(LViewI *wnd); // Focus setting LViewI *GetFocus(); enum FocusType { GainFocus, LoseFocus, ViewDelete }; void SetFocus(LViewI *ctrl, FocusType type); /// Registers a watcher to receive OnView... messages before they /// are passed through to the intended recipient. bool RegisterHook ( /// The target view. LView *Target, /// Combination of: /// #LMouseEvents - Where Target->OnViewMouse(...) is called for each click. /// and /// #LKeyEvents - Where Target->OnViewKey(...) is called for each key. /// OR'd together. LWindowHookType EventType, /// Not implemented int Priority = 0 ); /// Unregisters a hook target bool UnregisterHook(LView *Target); /// Gets the default view LViewI *GetDefault(); /// Sets the default view void SetDefault(LViewI *v); /// Saves/loads the window's state, e.g. position, minimized/maximized etc bool SerializeState ( /// The data store for reading/writing LDom *Store, /// The field name to use for storing settings under const char *FieldName, /// TRUE if loading the settings into the window, FALSE if saving to the store. bool Load ); /// Builds a map of keyboard short cuts. typedef LHashTbl,LViewI*> ShortcutMap; void BuildShortcuts(ShortcutMap &Map, LViewI *v = NULL); ////////////////////// Events /////////////////////////////// /// Called when the window zoom state changes. virtual void OnZoom(LWindowZoom Action) {} /// Called when the tray icon is clicked. (if present) virtual void OnTrayClick(LMouse &m); /// Called when the tray icon menu is about to be displayed. virtual void OnTrayMenu(LSubMenu &m) {} /// Called when the tray icon menu item has been selected. virtual void OnTrayMenuResult(int MenuId) {} /// Called when files are dropped on the window. virtual void OnReceiveFiles(LArray &Files) {} /// Called when a URL is sent to the window virtual void OnUrl(const char *Url) {}; ///////////////// Implementation //////////////////////////// void OnPosChange() override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPaint(LSurface *pDC) override; bool HandleViewMouse(LView *v, LMouse &m); bool HandleViewKey(LView *v, LKey &k); /// Return true to accept application quit bool OnRequestClose(bool OsShuttingDown) override; bool Obscured(); bool Visible() override; void Visible(bool i) override; bool IsActive(); bool SetActive(); LRect &GetPos() override; void SetDecor(bool Visible); LPoint GetDpi(); LPointF GetDpiScale(); void ScaleSizeToDpi(); // D'n'd int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; #if !WINNATIVE bool Attach(LViewI *p) override; // Props #if defined(HAIKU) OsWindow WindowHandle() override; #else OsWindow WindowHandle() override { return Wnd; } #endif bool Name(const char *n) override; const char *Name() override; bool SetPos(LRect &p, bool Repaint = false) override; LRect &GetClient(bool InClientSpace = true) override; // Events void OnChildrenChanged(LViewI *Wnd, bool Attaching) override; void OnCreate() override; virtual void OnFrontSwitch(bool b); #else OsWindow WindowHandle() override { return _View; } #endif #if defined(LGI_SDL) virtual bool PushWindow(LWindow *v); virtual LWindow *PopWindow(); #elif defined __GTK_H__ void OnGtkRealize(); bool IsAttached(); void Quit(bool DontDelete = false); LRect *GetDecorSize(); bool TranslateMouse(LMouse &m); LViewI *WindowFromPoint(int x, int y, bool Debug = false); void _SetDynamic(bool b); void _OnViewDelete(); void SetParent(LViewI *p) override; #elif defined(MAC) - bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0) override; + bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1) override; void Quit(bool DontDelete = false) override; int OnCommand(int Cmd, int Event, OsView Wnd) override; LViewI *WindowFromPoint(int x, int y, int DebugDebug = 0) override; #if defined(LGI_CARBON) OSErr HandlerCallback(DragTrackingMessage *tracking, DragRef theDrag); #endif #endif }; #endif diff --git a/private/mac/AppPriv.h b/private/mac/AppPriv.h --- a/private/mac/AppPriv.h +++ b/private/mac/AppPriv.h @@ -1,58 +1,58 @@ #pragma once #include "lgi/common/Json.h" #include "SymLookup.h" #include "lgi/common/FontCache.h" typedef LArray AppArray; class LAppPrivate { public: LApp *Owner; OsApp NsApp; int RunDepth; // Common LAutoPtr Config; LFileSystem *FileSystem; GdcDevice *GdcSystem; OsAppArguments Args; LLibrary *SkinLib; - LHashTbl,AppArray*> MimeToApp; + LHashTbl,AppArray*> MimeToApp; OsThread GuiThread; OsThreadId GuiThreadId; LSymLookup SymLookup; LAutoString Mime; LAutoString Name; LAutoString UrlArg; /// Any fonts needed for styling the elements LAutoPtr FontCache; LAppPrivate(LApp *owner) : Owner(owner) { NsApp = NULL; RunDepth = 0; FileSystem = 0; GdcSystem = 0; SkinLib = 0; GuiThread = LGetCurrentThread(); GuiThreadId = GetCurrentThreadId(); } ~LAppPrivate() { DeleteObj(SkinLib); for (auto p : MimeToApp) { p.value->DeleteObjects(); DeleteObj(p.value); } } LJson *GetConfig(); bool SaveConfig(); }; diff --git a/src/common/Lgi/ViewCommon.cpp b/src/common/Lgi/ViewCommon.cpp --- a/src/common/Lgi/ViewCommon.cpp +++ b/src/common/Lgi/ViewCommon.cpp @@ -1,2707 +1,2709 @@ /// \file /// \author Matthew Allen #ifdef LINUX #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Button.h" #include "lgi/common/Css.h" #include "lgi/common/LgiRes.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/Popup.h" #include "lgi/common/CssTools.h" #include "ViewPriv.h" #if 0 #define DEBUG_CAPTURE(...) printf(__VA_ARGS__) #else #define DEBUG_CAPTURE(...) #endif ////////////////////////////////////////////////////////////////////////////////////// // Helper LPoint lgi_view_offset(LViewI *v, bool Debug = false) { LPoint Offset; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) break; LRect pos = p->GetPos(); LRect cli = p->GetClient(false); if (Debug) { const char *cls = p->GetClass(); LgiTrace(" Off[%s] += %i,%i = %i,%i (%s)\n", cls, pos.x1, pos.y1, Offset.x + pos.x1, Offset.y + pos.y1, cli.GetStr()); } Offset.x += pos.x1 + cli.x1; Offset.y += pos.y1 + cli.y1; } return Offset; } LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing, bool Debug) { static LMouse Temp; Temp = Info; if (Wnd) { if (Debug #if 0 || Capturing #endif ) LgiTrace("AdjustClick Target=%s -> Wnd=%s, Info=%i,%i\n", Info.Target?Info.Target->GetClass():"", Wnd?Wnd->GetClass():"", Info.x, Info.y); if (Temp.Target != Wnd) { if (Temp.Target) { auto *TargetWnd = Temp.Target->GetWindow(); auto *WndWnd = Wnd->GetWindow(); if (TargetWnd == WndWnd) { LPoint TargetOff = lgi_view_offset(Temp.Target, Debug); if (Debug) LgiTrace(" WndOffset:\n"); LPoint WndOffset = lgi_view_offset(Wnd, Debug); Temp.x += TargetOff.x - WndOffset.x; Temp.y += TargetOff.y - WndOffset.y; #if 0 LRect c = Wnd->GetClient(false); Temp.x -= c.x1; Temp.y -= c.y1; if (Debug) LgiTrace(" CliOff -= %i,%i\n", c.x1, c.y1); #endif Temp.Target = Wnd; } } else { LPoint Offset; Temp.Target = Wnd; if (Wnd->WindowVirtualOffset(&Offset)) { LRect c = Wnd->GetClient(false); Temp.x -= Offset.x + c.x1; Temp.y -= Offset.y + c.y1; } } } } LAssert(Temp.Target != NULL); return Temp; } ////////////////////////////////////////////////////////////////////////////////////// // LView class methods LViewI *LView::_Capturing = 0; LViewI *LView::_Over = 0; #if LGI_VIEW_HASH struct ViewTbl : public LMutex { typedef LHashTbl, int> T; private: T Map; public: ViewTbl() : Map(2000), LMutex("ViewTbl") { } T *Lock() { if (!LMutex::Lock(_FL)) return NULL; return ⤅ } } ViewTblInst; bool LView::LockHandler(LViewI *v, LView::LockOp Op) { ViewTbl::T *m = ViewTblInst.Lock(); if (!m) return false; int Ref = m->Find(v); bool Status = false; switch (Op) { case OpCreate: { if (Ref == 0) Status = m->Add(v, 1); else LAssert(!"Already exists?"); break; } case OpDelete: { if (Ref == 1) Status = m->Delete(v); else LAssert(!"Either locked or missing."); break; } case OpExists: { Status = Ref > 0; break; } } ViewTblInst.Unlock(); return Status; } #endif LView::LView(OsView view) { #ifdef _DEBUG _Debug = false; #endif d = new LViewPrivate(this); #ifdef LGI_SDL _View = this; #elif LGI_VIEW_HANDLE && !defined(HAIKU) _View = view; #endif Pos.ZOff(-1, -1); WndFlags = GWF_VISIBLE; #ifndef LGI_VIEW_HASH #error "LGI_VIEW_HASH needs to be defined" #elif LGI_VIEW_HASH LockHandler(this, OpCreate); // printf("Adding %p to hash\n", (LViewI*)this); #endif } LView::~LView() { if (d->SinkHnd >= 0) { LEventSinkMap::Dispatch.RemoveSink(this); d->SinkHnd = -1; } #if LGI_VIEW_HASH LockHandler(this, OpDelete); #endif for (unsigned i=0; iOwner == this) pu->Owner = NULL; } _Delete(); // printf("%p::~LView delete %p th=%u\n", this, d, GetCurrentThreadId()); DeleteObj(d); // printf("%p::~LView\n", this); } int LView::AddDispatch() { if (d->SinkHnd < 0) d->SinkHnd = LEventSinkMap::Dispatch.AddSink(this); return d->SinkHnd; } LString LView::CssStyles(const char *Set) { if (Set) { d->Styles = Set; d->CssDirty = true; } return d->Styles; } LString::Array *LView::CssClasses() { return &d->Classes; } LArray LView::IterateViews() { LArray a; for (auto c: Children) a.Add(c); return a; } bool LView::AddView(LViewI *v, int Where) { LAssert(!Children.HasItem(v)); bool Add = Children.Insert(v, Where); if (Add) { LView *gv = v->GetGView(); if (gv && gv->_Window != _Window) { LAssert(!_InLock); gv->_Window = _Window; } v->SetParent(this); v->OnAttach(); OnChildrenChanged(v, true); } return Add; } bool LView::DelView(LViewI *v) { bool Has = Children.HasItem(v); bool b = Children.Delete(v); if (Has) OnChildrenChanged(v, false); Has = Children.HasItem(v); LAssert(!Has); return b; } bool LView::HasView(LViewI *v) { return Children.HasItem(v); } OsWindow LView::WindowHandle() { auto w = GetWindow(); - auto h = w ? w->WindowHandle() : NULL; + OsWindow h; + if (w) + h = w->WindowHandle(); return h; } LWindow *LView::GetWindow() { if (!_Window) { // Walk up parent list and find someone who has a window auto *w = d->GetParent(); for (; w; w = w->d ? w->d->GetParent() : NULL) { if (w->_Window) { LAssert(!_InLock); _Window = w->_Window; break; } } } return dynamic_cast(_Window); } bool LView::Lock(const char *file, int line, int TimeOut) { #ifdef HAIKU bool Debug = !Stricmp("LList", GetClass()); if (!d || !d->Hnd) { if (Debug) printf("%s:%i - no handle %p %p\n", _FL, d, d ? d->Hnd : NULL); return false; } if (d->Hnd->Parent() == NULL) { if (Debug) printf("%s:%p - Lock() no parent.\n", GetClass(), this); return true; } if (TimeOut >= 0) { auto r = d->Hnd->LockLooperWithTimeout(TimeOut * 1000); if (r == B_OK) { _InLock++; if (Debug) printf("%s:%p - Lock() cnt=%i par=%p.\n", GetClass(), this, _InLock, d->Hnd->Parent()); return true; } printf("%s:%i - Lock(%i) failed with %x.\n", _FL, TimeOut, r); return false; } auto r = d->Hnd->LockLooper(); if (r) { _InLock++; if (Debug) { auto w = WindowHandle(); printf("%s:%p - Lock() cnt=%i myThread=%i wndThread=%i.\n", GetClass(), this, _InLock, GetCurrentThreadId(), w ? w->Thread() : -1); } return true; } if (Debug) printf("%s:%i - Lock(%s:%i) failed.\n", _FL, file, line); return false; #else if (!_Window) GetWindow(); _InLock++; // LgiTrace("%s::%p Lock._InLock=%i %s:%i\n", GetClass(), this, _InLock, file, line); if (_Window && _Window->_Lock) { if (TimeOut < 0) { return _Window->_Lock->Lock(file, line); } else { return _Window->_Lock->LockWithTimeout(TimeOut, file, line); } } return true; #endif } void LView::Unlock() { #ifdef HAIKU if (!d || !d->Hnd) { printf("%s:%i - Unlock() error, no hnd.\n", _FL); return; } if (!d->Hnd->Parent()) { // printf("%s:%p - Unlock() no parent.\n", GetClass(), this); return; } if (_InLock > 0) { // printf("%s:%p - Calling UnlockLooper: %i.\n", GetClass(), this, _InLock); d->Hnd->UnlockLooper(); _InLock--; // printf("%s:%p - UnlockLooper done: %i.\n", GetClass(), this, _InLock); } else { printf("%s:%i - Unlock() without lock.\n", _FL); } #else if (_Window && _Window->_Lock) { _Window->_Lock->Unlock(); } _InLock--; // LgiTrace("%s::%p Unlock._InLock=%i\n", GetClass(), this, _InLock); #endif } void LView::OnMouseClick(LMouse &m) { } void LView::OnMouseEnter(LMouse &m) { } void LView::OnMouseExit(LMouse &m) { } void LView::OnMouseMove(LMouse &m) { } bool LView::OnMouseWheel(double Lines) { return false; } bool LView::OnKey(LKey &k) { return false; } void LView::OnAttach() { List::I it = Children.begin(); for (LViewI *v = *it; v; v = *++it) { if (!v->GetParent()) v->SetParent(this); } #if 0 // defined __GTK_H__ if (_View && !DropTarget()) { // If one of our parents is drop capable we need to set a dest here LViewI *p; for (p = GetParent(); p; p = p->GetParent()) { if (p->DropTarget()) { break; } } if (p) { Gtk::gtk_drag_dest_set( _View, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); // printf("%s:%i - Drop dest for '%s'\n", _FL, GetClass()); } else { Gtk::gtk_drag_dest_unset(_View); // printf("%s:%i - Not setting drop dest '%s'\n", _FL, GetClass()); } } #endif } void LView::OnCreate() { } void LView::OnDestroy() { } void LView::OnFocus(bool f) { // printf("%s::OnFocus(%i)\n", GetClass(), f); } void LView::OnPulse() { } void LView::OnPosChange() { } bool LView::OnRequestClose(bool OsShuttingDown) { return true; } int LView::OnHitTest(int x, int y) { return -1; } void LView::OnChildrenChanged(LViewI *Wnd, bool Attaching) { } void LView::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } int LView::OnNotify(LViewI *Ctrl, LNotification Data) { if (!Ctrl) return 0; if (Ctrl == (LViewI*)this && Data.Type == LNotifyActivate) { // Default activation is to focus the current control. Focus(true); } else if (d && d->Parent) { // default behaviour is just to pass the // notification up to the parent // FIXME: eventually we need to call the 'LNotification' parent fn... return d->Parent->OnNotify(Ctrl, Data); } return 0; } int LView::OnCommand(int Cmd, int Event, OsView Wnd) { return 0; } void LView::OnNcPaint(LSurface *pDC, LRect &r) { int Border = Sunken() || Raised() ? _BorderSize : 0; if (Border == 2) { LEdge e; if (Sunken()) e = Focus() ? EdgeWin7FocusSunken : DefaultSunkenEdge; else e = DefaultRaisedEdge; #if WINNATIVE if (d->IsThemed) DrawThemeBorder(pDC, r); else #endif LWideBorder(pDC, r, e); } else if (Border == 1) { LThinBorder(pDC, r, Sunken() ? DefaultSunkenEdge : DefaultRaisedEdge); } } #if LGI_COCOA || defined(__GTK_H__) /* uint64 nPaint = 0; uint64 PaintTime = 0; */ void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { /* uint64 StartTs = Update ? LCurrentTime() : 0; d->InPaint = true; */ // Create temp DC if needed... LAutoPtr Local; if (!pDC) { if (!Local.Reset(new LScreenDC(this))) return; pDC = Local; } #if 0 // This is useful for coverage checking pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif // Non-Client drawing LRect r; if (Offset) { r = Pos; r.Offset(Offset); } else { r = GetClient().ZeroTranslate(); } pDC->SetClient(&r); LRect zr1 = r.ZeroTranslate(), zr2 = zr1; OnNcPaint(pDC, zr1); pDC->SetClient(NULL); if (zr2 != zr1) { r.x1 -= zr2.x1 - zr1.x1; r.y1 -= zr2.y1 - zr1.y1; r.x2 -= zr2.x2 - zr1.x2; r.y2 -= zr2.y2 - zr1.y2; } LPoint o(r.x1, r.y1); // Origin of client // Paint this view's contents... pDC->SetClient(&r); #if 0 if (_Debug) { #if defined(__GTK_H__) Gtk::cairo_matrix_t matrix; cairo_get_matrix(pDC->Handle(), &matrix); double ex[4]; cairo_clip_extents(pDC->Handle(), ex+0, ex+1, ex+2, ex+3); ex[0] += matrix.x0; ex[1] += matrix.y0; ex[2] += matrix.x0; ex[3] += matrix.y0; LgiTrace("%s::_Paint, r=%s, clip=%g,%g,%g,%g - %g,%g\n", GetClass(), r.GetStr(), ex[0], ex[1], ex[2], ex[3], matrix.x0, matrix.y0); #elif LGI_COCOA auto Ctx = pDC->Handle(); CGAffineTransform t = CGContextGetCTM(Ctx); LRect cr = CGContextGetClipBoundingBox(Ctx); printf("%s::_Paint() pos=%s transform=%g,%g,%g,%g-%g,%g clip=%s r=%s\n", GetClass(), GetPos().GetStr(), t.a, t.b, t.c, t.d, t.tx, t.ty, cr.GetStr(), r.GetStr()); #endif } #endif OnPaint(pDC); pDC->SetClient(NULL); // Paint all the children... for (auto i : Children) { LView *w = i->GetGView(); if (w && w->Visible()) { if (!w->Pos.Valid()) continue; #if 0 if (w->_Debug) LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), o.x, o.y); #endif w->_Paint(pDC, &o); } } } #else void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { // Create temp DC if needed... LAutoPtr Local; if (!pDC) { Local.Reset(new LScreenDC(this)); pDC = Local; } if (!pDC) { printf("%s:%i - No context to draw in.\n", _FL); return; } #if 0 // This is useful for coverage checking pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif bool HasClient = false; LRect r(0, 0, Pos.X()-1, Pos.Y()-1), Client; LPoint o; if (Offset) o = *Offset; #if WINNATIVE if (!_View) #endif { // Non-Client drawing Client = r; OnNcPaint(pDC, Client); HasClient = GetParent() && (Client != r); if (HasClient) { Client.Offset(o.x, o.y); pDC->SetClient(&Client); } } r.Offset(o.x, o.y); // Paint this view's contents if (Update) { LRect OldClip = pDC->ClipRgn(); pDC->ClipRgn(Update); OnPaint(pDC); pDC->ClipRgn(OldClip.Valid() ? &OldClip : NULL); } else { OnPaint(pDC); } #if PAINT_VIRTUAL_CHILDREN // Paint any virtual children for (auto i : Children) { LView *w = i->GetGView(); if (w && w->Visible()) { #if LGI_VIEW_HANDLE if (!w->Handle()) #endif { LRect p = w->GetPos(); p.Offset(o.x, o.y); if (HasClient) p.Offset(Client.x1 - r.x1, Client.y1 - r.y1); LPoint co(p.x1, p.y1); // LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), p.x1, p.y1); pDC->SetClient(&p); w->_Paint(pDC, &co); pDC->SetClient(NULL); } } } #endif if (HasClient) pDC->SetClient(0); } #endif LViewI *LView::GetParent() { ThreadCheck(); return d ? d->Parent : NULL; } void LView::SetParent(LViewI *p) { ThreadCheck(); d->Parent = p ? p->GetGView() : NULL; d->ParentI = p; } void LView::SendNotify(LNotification note) { LViewI *n = d->Notify ? d->Notify : d->Parent; if (n) { if ( #if LGI_VIEW_HANDLE && !defined(HAIKU) !_View || #endif InThread()) { n->OnNotify(this, note); } else { // We are not in the same thread as the target window. So we post a message // across to the view. if (GetId() <= 0) { // We are going to generate a control Id to help the receiver of the // M_CHANGE message find out view later on. The reason for doing this // instead of sending a pointer to the object, is that the object // _could_ be deleted between the message being sent and being received. // Which would result in an invalid memory access on that object. LViewI *p = GetWindow(); if (!p) { // No window? Find the top most parent we can... p = this; while (p->GetParent()) p = p->GetParent(); } if (p) { // Give the control a valid ID int i; for (i=10; i<1000; i++) { if (!p->FindControl(i)) { printf("Giving the ctrl '%s' the id '%i' for SendNotify\n", GetClass(), i); SetId(i); break; } } } else { // Ok this is really bad... go random (better than nothing) SetId(5000 + LRand(2000)); } } LAssert(GetId() > 0); // We must have a valid ctrl ID at this point, otherwise // the receiver will never be able to find our object. // printf("Post M_CHANGE %i %i\n", GetId(), Data); n->PostEvent(M_CHANGE, (LMessage::Param) GetId(), (LMessage::Param) new LNotification(note)); } } } LViewI *LView::GetNotify() { ThreadCheck(); return d->Notify; } void LView::SetNotify(LViewI *p) { ThreadCheck(); d->Notify = p; } #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 int IsAdjacent(LRect &a, LRect &b) { if ( (a.x1 == b.x2 + 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_LEFT; } if ( (a.x2 == b.x1 - 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_RIGHT; } if ( (a.y1 == b.y2 + 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_UP; } if ( (a.y2 == b.y1 - 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_DOWN; } return 0; } LRect JoinAdjacent(LRect &a, LRect &b, int Adj) { LRect t; switch (Adj) { case ADJ_LEFT: case ADJ_RIGHT: { t.y1 = MAX(a.y1, b.y1); t.y2 = MIN(a.y2, b.y2); t.x1 = MIN(a.x1, b.x1); t.x2 = MAX(a.x2, b.x2); break; } case ADJ_UP: case ADJ_DOWN: { t.y1 = MIN(a.y1, b.y1); t.y2 = MAX(a.y2, b.y2); t.x1 = MAX(a.x1, b.x1); t.x2 = MIN(a.x2, b.x2); break; } } return t; } LRect *LView::FindLargest(LRegion &r) { ThreadCheck(); int Pixels = 0; LRect *Best = 0; static LRect Final; // do initial run through the list to find largest single region for (LRect *i = r.First(); i; i = r.Next()) { int Pix = i->X() * i->Y(); if (Pix > Pixels) { Pixels = Pix; Best = i; } } if (Best) { Final = *Best; Pixels = Final.X() * Final.Y(); int LastPixels = Pixels; LRect LastRgn = Final; int ThisPixels = Pixels; LRect ThisRgn = Final; LRegion TempList; for (LRect *i = r.First(); i; i = r.Next()) { TempList.Union(i); } TempList.Subtract(Best); do { LastPixels = ThisPixels; LastRgn = ThisRgn; // search for adjoining rectangles that maybe we can add for (LRect *i = TempList.First(); i; i = TempList.Next()) { int Adj = IsAdjacent(ThisRgn, *i); if (Adj) { LRect t = JoinAdjacent(ThisRgn, *i, Adj); int Pix = t.X() * t.Y(); if (Pix > ThisPixels) { ThisPixels = Pix; ThisRgn = t; TempList.Subtract(i); } } } } while (LastPixels < ThisPixels); Final = ThisRgn; } else return 0; return &Final; } LRect *LView::FindSmallestFit(LRegion &r, int Sx, int Sy) { ThreadCheck(); int X = 1000000; int Y = 1000000; LRect *Best = 0; for (LRect *i = r.First(); i; i = r.Next()) { if ((i->X() >= Sx && i->Y() >= Sy) && (i->X() < X || i->Y() < Y)) { X = i->X(); Y = i->Y(); Best = i; } } return Best; } LRect *LView::FindLargestEdge(LRegion &r, int Edge) { LRect *Best = 0; ThreadCheck(); for (LRect *i = r.First(); i; i = r.Next()) { if (!Best) { Best = i; } if ( ((Edge & GV_EDGE_TOP) && (i->y1 < Best->y1)) || ((Edge & GV_EDGE_RIGHT) && (i->x2 > Best->x2)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 > Best->y2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 < Best->x1)) ) { Best = i; } if ( ( ((Edge & GV_EDGE_TOP) && (i->y1 == Best->y1)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 == Best->y2)) ) && ( i->X() > Best->X() ) ) { Best = i; } if ( ( ((Edge & GV_EDGE_RIGHT) && (i->x2 == Best->x2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 == Best->x1)) ) && ( i->Y() > Best->Y() ) ) { Best = i; } } return Best; } LViewI *LView::FindReal(LPoint *Offset) { ThreadCheck(); if (Offset) { Offset->x = 0; Offset->y = 0; } #if !LGI_VIEW_HANDLE LViewI *w = GetWindow(); #endif LViewI *p = d->Parent; while (p && #if !LGI_VIEW_HANDLE p != w #else !p->Handle() #endif ) { if (Offset) { Offset->x += Pos.x1; Offset->y += Pos.y1; } p = p->GetParent(); } if (p && #if !LGI_VIEW_HANDLE p == w #else p->Handle() #endif ) { return p; } return NULL; } bool LView::HandleCapture(LView *Wnd, bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::HandleCapture(%i)=%i\n", GetClass(), c, (int)(_Capturing == Wnd)); if (c) { if (_Capturing == Wnd) { DEBUG_CAPTURE(" %s already has capture\n", _Capturing?_Capturing->GetClass():0); } else { DEBUG_CAPTURE(" _Capturing=%s -> %s\n", _Capturing?_Capturing->GetClass():0, Wnd?Wnd->GetClass():0); _Capturing = Wnd; #if defined(__GTK_H__) if (d->CaptureThread) d->CaptureThread->Cancel(); d->CaptureThread = new LCaptureThread(this); #elif WINNATIVE LPoint Offset; LViewI *v = _Capturing->Handle() ? _Capturing : FindReal(&Offset); HWND h = v ? v->Handle() : NULL; if (h) SetCapture(h); else LAssert(0); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_TRUE); #else LAppInst->CaptureMouse(true); #endif #endif } } else if (_Capturing) { DEBUG_CAPTURE(" _Capturing=%s -> NULL\n", _Capturing?_Capturing->GetClass():0); _Capturing = NULL; #if defined(__GTK_H__) if (d->CaptureThread) { d->CaptureThread->Cancel(); d->CaptureThread = NULL; // It'll delete itself... } #elif WINNATIVE ReleaseCapture(); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_FALSE); #else LAppInst->CaptureMouse(false); #endif #endif } return true; } bool LView::IsCapturing() { // DEBUG_CAPTURE("%s::IsCapturing()=%p==%p==%i\n", GetClass(), _Capturing, this, (int)(_Capturing == this)); return _Capturing == this; } bool LView::Capture(bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::Capture(%i)\n", GetClass(), c); return HandleCapture(this, c); } bool LView::Enabled() { ThreadCheck(); #if WINNATIVE if (_View) return IsWindowEnabled(_View) != 0; #endif return !TestFlag(GViewFlags, GWF_DISABLED); } void LView::Enabled(bool i) { ThreadCheck(); if (!i) SetFlag(GViewFlags, GWF_DISABLED); else ClearFlag(GViewFlags, GWF_DISABLED); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE EnableWindow(_View, i); #elif defined LGI_CARBON if (i) { OSStatus e = EnableControl(_View); if (e) printf("%s:%i - Error enabling control (%i)\n", _FL, (int)e); } else { OSStatus e = DisableControl(_View); if (e) printf("%s:%i - Error disabling control (%i)\n", _FL, (int)e); } #endif } #endif Invalidate(); } bool LView::Visible() { // This is a read only operation... which is kinda thread-safe... // ThreadCheck(); #if WINNATIVE if (_View) /* This takes into account all the parent windows as well... Which is kinda not what I want. I want this to reflect just this window. return IsWindowVisible(_View); */ return (GetWindowLong(_View, GWL_STYLE) & WS_VISIBLE) != 0; #endif return TestFlag(GViewFlags, GWF_VISIBLE); } void LView::Visible(bool v) { ThreadCheck(); if (v) SetFlag(GViewFlags, GWF_VISIBLE); else ClearFlag(GViewFlags, GWF_VISIBLE); #if defined(HAIKU) LLocker lck(d->Hnd, _FL); if (!IsAttached() || lck.Lock()) { const int attempts = 3; // printf("%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden()); if (v) { bool parentHidden = false; for (auto p = d->Hnd->Parent(); p; p = p->Parent()) { if (p->IsHidden()) { parentHidden = true; break; } } if (!parentHidden) // Don't try and show if one of the parent's is hidden. { for (int i=0; iHnd->IsHidden(); i++) { // printf("\t%p Show\n", this); d->Hnd->Show(); } if (d->Hnd->IsHidden()) { printf("%s:%i - Failed to show %s.\n", _FL, GetClass()); for (auto p = d->Hnd->Parent(); p; p = p->Parent()) printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden()); } } } else { for (int i=0; iHnd->IsHidden(); i++) { // printf("\t%p Hide\n", this); d->Hnd->Hide(); } if (!d->Hnd->IsHidden()) { printf("%s:%i - Failed to hide %s.\n", _FL, GetClass()); for (auto p = d->Hnd->Parent(); p; p = p->Parent()) printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden()); } } // printf("\t%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden()); } else LgiTrace("%s:%i - Can't lock.\n", _FL); #elif LGI_VIEW_HANDLE if (_View) { #if WINNATIVE ShowWindow(_View, (v) ? SW_SHOWNORMAL : SW_HIDE); #elif LGI_COCOA LAutoPool Pool; [_View.p setHidden:!v]; #elif LGI_CARBON Boolean is = HIViewIsVisible(_View); if (v != is) { OSErr e = HIViewSetVisible(_View, v); if (e) printf("%s:%i - HIViewSetVisible(%p,%i) failed with %i (class=%s)\n", _FL, _View, v, e, GetClass()); } #endif } else #endif { Invalidate(); } } bool LView::Focus() { ThreadCheck(); bool Has = false; #if defined(__GTK_H__) LWindow *w = GetWindow(); if (w) { bool Active = w->IsActive(); if (Active) Has = w->GetFocus() == static_cast(this); } #elif defined(HAIKU) LLocker lck(d->Hnd, _FL); if (lck.Lock()) { Has = d->Hnd->IsFocus(); lck.Unlock(); } #elif defined(WINNATIVE) if (_View) { HWND hFocus = GetFocus(); Has = hFocus == _View; } #elif LGI_COCOA Has = TestFlag(WndFlags, GWF_FOCUS); #elif LGI_CARBON LWindow *w = GetWindow(); if (w) { ControlRef Cur; OSErr e = GetKeyboardFocus(w->WindowHandle(), &Cur); if (e) LgiTrace("%s:%i - GetKeyboardFocus failed with %i\n", _FL, e); else Has = (Cur == _View); } #endif #if !LGI_CARBON if (Has) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); #endif return Has; } void LView::Focus(bool i) { ThreadCheck(); if (i) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); auto *Wnd = GetWindow(); if (Wnd) { Wnd->SetFocus(this, i ? LWindow::GainFocus : LWindow::LoseFocus); } #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) #endif { #if defined(HAIKU) _Focus(i); #elif defined(LGI_SDL) || defined(__GTK_H__) // Nop: Focus is all handled by Lgi's LWindow class. #elif WINNATIVE if (i) { HWND hCur = GetFocus(); if (hCur != _View) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus %p (%-30s)\n", _FL, Handle(), Name()); } SetFocus(_View); } } else { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", _FL, GetDesktopWindow()); } SetFocus(GetDesktopWindow()); } #elif defined LGI_CARBON LViewI *Wnd = GetWindow(); if (Wnd && i) { OSErr e = SetKeyboardFocus(Wnd->WindowHandle(), _View, 1); if (e) { // e = SetKeyboardFocus(Wnd->WindowHandle(), _View, kControlFocusNextPart); // if (e) { HIViewRef p = HIViewGetSuperview(_View); // errCouldntSetFocus printf("%s:%i - SetKeyboardFocus failed: %i (%s, %p)\n", _FL, e, GetClass(), p); } } // else printf("%s:%i - SetFocus v=%p(%s)\n", _FL, _View, GetClass()); } else printf("%s:%i - no window?\n", _FL); #endif } } LDragDropSource *LView::DropSource(LDragDropSource *Set) { if (Set) d->DropSource = Set; return d->DropSource; } LDragDropTarget *LView::DropTarget(LDragDropTarget *Set) { if (Set) d->DropTarget = Set; return d->DropTarget; } #if defined LGI_CARBON extern pascal OSStatus LgiViewDndHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); #endif #if defined __GTK_H__ // Recursively add drag dest to all view and all children bool GtkAddDragDest(LViewI *v, bool IsTarget) { if (!v) return false; LWindow *w = v->GetWindow(); if (!w) return false; auto wid = GtkCast(w->WindowHandle(), gtk_widget, GtkWidget); if (IsTarget) { Gtk::gtk_drag_dest_set( wid, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); } else { Gtk::gtk_drag_dest_unset(wid); } for (LViewI *c: v->IterateViews()) GtkAddDragDest(c, IsTarget); return true; } #endif bool LView::DropTarget(bool t) { ThreadCheck(); bool Status = false; if (t) SetFlag(GViewFlags, GWF_DROP_TARGET); else ClearFlag(GViewFlags, GWF_DROP_TARGET); #if WINNATIVE if (_View) { if (t) { if (!d->DropTarget) DragAcceptFiles(_View, t); else Status = RegisterDragDrop(_View, (IDropTarget*) d->DropTarget) == S_OK; } else { if (_View && d->DropTarget) Status = RevokeDragDrop(_View) == S_OK; } } #elif defined MAC && !defined(LGI_SDL) LWindow *Wnd = dynamic_cast(GetWindow()); if (Wnd) { Wnd->SetDragHandlers(t); if (!d->DropTarget) d->DropTarget = t ? Wnd : 0; } #if LGI_COCOA LWindow *w = GetWindow(); if (w) { OsWindow h = w->WindowHandle(); if (h) { NSMutableArray *a = [[NSMutableArray alloc] init]; if (a) { [a addObject:(NSString*)kUTTypeItem]; for (id item in NSFilePromiseReceiver.readableDraggedTypes) [a addObject:item]; [h.p.contentView registerForDraggedTypes:a]; [a release]; } } } #elif LGI_CARBON if (t) { static EventTypeSpec DragEvents[] = { { kEventClassControl, kEventControlDragEnter }, { kEventClassControl, kEventControlDragWithin }, { kEventClassControl, kEventControlDragLeave }, { kEventClassControl, kEventControlDragReceive }, }; if (!d->DndHandler) { OSStatus e = ::InstallControlEventHandler( _View, NewEventHandlerUPP(LgiViewDndHandler), GetEventTypeCount(DragEvents), DragEvents, (void*)this, &d->DndHandler); if (e) LgiTrace("%s:%i - InstallEventHandler failed (%i)\n", _FL, e); } SetControlDragTrackingEnabled(_View, true); } else { SetControlDragTrackingEnabled(_View, false); } #endif #elif defined __GTK_H__ Status = GtkAddDragDest(this, t); if (Status && !d->DropTarget) d->DropTarget = t ? GetWindow() : 0; #endif return Status; } bool LView::Sunken() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE); #else return TestFlag(GViewFlags, GWF_SUNKEN); #endif } void LView::Sunken(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_CLIENTEDGE); else ClearFlag(d->WndExStyle, WS_EX_CLIENTEDGE); if (_View) SetWindowLong(_View, GWL_EXSTYLE, d->WndExStyle); #else if (i) SetFlag(GViewFlags, GWF_SUNKEN); else ClearFlag(GViewFlags, GWF_SUNKEN); #endif if (i) { if (!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } bool LView::Flat() { // ThreadCheck(); #if WINNATIVE return !TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE) && !TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return !TestFlag(GViewFlags, GWF_SUNKEN) && !TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Flat(bool i) { ThreadCheck(); #if WINNATIVE ClearFlag(d->WndExStyle, (WS_EX_CLIENTEDGE|WS_EX_WINDOWEDGE)); #else ClearFlag(GViewFlags, (GWF_RAISED|GWF_SUNKEN)); #endif } bool LView::Raised() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Raised(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_WINDOWEDGE); else ClearFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else if (i) SetFlag(GViewFlags, GWF_RAISED); else ClearFlag(GViewFlags, GWF_RAISED); #endif if (i) { if (!!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } int LView::GetId() { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. return d->CtrlId; } void LView::SetId(int i) { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. d->CtrlId = i; #if WINNATIVE if (_View) SetWindowLong(_View, GWL_ID, d->CtrlId); #elif defined __GTK_H__ #elif defined MAC #endif } bool LView::GetTabStop() { ThreadCheck(); #if WINNATIVE return TestFlag(d->WndStyle, WS_TABSTOP); #else return d->TabStop; #endif } void LView::SetTabStop(bool b) { ThreadCheck(); #if WINNATIVE if (b) SetFlag(d->WndStyle, WS_TABSTOP); else ClearFlag(d->WndStyle, WS_TABSTOP); #else d->TabStop = b; #endif } int64 LView::GetCtrlValue(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Value() : 0; } void LView::SetCtrlValue(int Id, int64 i) { ThreadCheck(); LViewI *w = FindControl(Id); if (w) w->Value(i); } const char *LView::GetCtrlName(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Name() : 0; } void LView::SetCtrlName(int Id, const char *s) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Name(s); } else { PostEvent( M_SET_CTRL_NAME, (LMessage::Param)Id, (LMessage::Param)new LString(s)); } } bool LView::GetCtrlEnabled(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); return (w) ? w->Enabled() : 0; } void LView::SetCtrlEnabled(int Id, bool Enabled) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Enabled(Enabled); } else { PostEvent( M_SET_CTRL_ENABLE, (LMessage::Param)Id, (LMessage::Param)Enabled); } } bool LView::GetCtrlVisible(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); if (!w) LgiTrace("%s:%i - Ctrl %i not found.\n", _FL, Id); return (w) ? w->Visible() : 0; } void LView::SetCtrlVisible(int Id, bool v) { if (!IsAttached() || InThread()) { if (auto w = FindControl(Id)) w->Visible(v); } else { PostEvent( M_SET_CTRL_VISIBLE, (LMessage::Param)Id, (LMessage::Param)v); } } bool LView::AttachChildren() { for (auto c: Children) { bool a = c->IsAttached(); if (!a) { if (!c->Attach(this)) { LgiTrace("%s:%i - failed to attach %s\n", _FL, c->GetClass()); return false; } } } return true; } LFont *LView::GetFont() { if (!d->Font && d->Css && LResources::GetLoadStyles()) { LFontCache *fc = LAppInst->GetFontCache(); if (fc) { LFont *f = fc->GetFont(d->Css); if (f) { if (d->FontOwnType == GV_FontOwned) DeleteObj(d->Font); d->Font = f; d->FontOwnType = GV_FontCached; } } } return d->Font ? d->Font : LSysFont; } void LView::SetFont(LFont *Font, bool OwnIt) { bool Change = d->Font != Font; if (Change) { if (d->FontOwnType == GV_FontOwned) { LAssert(d->Font != LSysFont); DeleteObj(d->Font); } d->FontOwnType = OwnIt ? GV_FontOwned : GV_FontPtr; d->Font = Font; #if WINNATIVE if (_View) SendMessage(_View, WM_SETFONT, (WPARAM) (Font ? Font->Handle() : 0), 0); #endif for (LViewI *p = GetParent(); p; p = p->GetParent()) { LTableLayout *Tl = dynamic_cast(p); if (Tl) { Tl->InvalidateLayout(); break; } } Invalidate(); } } bool LView::IsOver(LMouse &m) { return (m.x >= 0) && (m.y >= 0) && (m.x < Pos.X()) && (m.y < Pos.Y()); } bool LView::WindowVirtualOffset(LPoint *Offset) { bool Status = false; if (Offset) { Offset->x = 0; Offset->y = 0; for (LViewI *Wnd = this; Wnd; Wnd = Wnd->GetParent()) { #if !LGI_VIEW_HANDLE auto IsWnd = dynamic_cast(Wnd); if (!IsWnd) #else if (!Wnd->Handle()) #endif { LRect r = Wnd->GetPos(); LViewI *Par = Wnd->GetParent(); if (Par) { LRect c = Par->GetClient(false); Offset->x += r.x1 + c.x1; Offset->y += r.y1 + c.y1; } else { Offset->x += r.x1; Offset->y += r.y1; } Status = true; } else break; } } return Status; } LString _ViewDesc(LViewI *v) { LString s; s.Printf("%s/%s/%i", v->GetClass(), v->Name(), v->GetId()); return s; } LViewI *LView::WindowFromPoint(int x, int y, int DebugDepth) { char Tabs[64]; if (DebugDepth) { memset(Tabs, 9, DebugDepth); Tabs[DebugDepth] = 0; LgiTrace("%s%s %i\n", Tabs, _ViewDesc(this).Get(), Children.Length()); } // We iterate over the child in reverse order because if they overlap the // end of the list is on "top". So they should get the click or whatever // before the the lower windows. auto it = Children.rbegin(); int n = (int)Children.Length() - 1; for (LViewI *c = *it; c; c = *--it) { LRect CPos = c->GetPos(); if (CPos.Overlap(x, y) && c->Visible()) { LRect CClient; CClient = c->GetClient(false); int Ox = CPos.x1 + CClient.x1; int Oy = CPos.y1 + CClient.y1; if (DebugDepth) { LgiTrace("%s[%i] %s Pos=%s Client=%s m(%i,%i)->(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), CClient.GetStr(), x, y, x - Ox, y - Oy); } LViewI *Child = c->WindowFromPoint(x - Ox, y - Oy, DebugDepth ? DebugDepth + 1 : 0); if (Child) return Child; } else if (DebugDepth) { LgiTrace("%s[%i] MISSED %s Pos=%s m(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), x, y); } } if (x >= 0 && y >= 0 && x < Pos.X() && y < Pos.Y()) { return this; } return NULL; } LColour LView::StyleColour(int CssPropType, LColour Default, int Depth) { LColour c = Default; if ((CssPropType >> 8) == LCss::TypeColor) { LViewI *v = this; for (int i=0; v && iGetParent()) { auto Style = v->GetCss(); if (Style) { auto Colour = (LCss::ColorDef*) Style->PropAddress((LCss::PropType)CssPropType); if (Colour) { if (Colour->Type == LCss::ColorRgb) { c.Set(Colour->Rgb32, 32); break; } else if (Colour->Type == LCss::ColorTransparent) { c.Empty(); break; } } } if (dynamic_cast(v) || dynamic_cast(v)) break; } } return c; } bool LView::InThread() { #if WINNATIVE HWND Hnd = _View; for (LViewI *p = GetParent(); p && !Hnd; p = p->GetParent()) { Hnd = p->Handle(); } auto CurThreadId = GetCurrentThreadId(); auto GuiThreadId = LAppInst->GetGuiThreadId(); DWORD ViewThread = Hnd ? GetWindowThreadProcessId(Hnd, NULL) : GuiThreadId; return CurThreadId == ViewThread; #elif defined(HAIKU) return true; #else OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = LAppInst ? LAppInst->GetGuiThreadId() : 0; #if 0 if (Gui != Me) LgiTrace("%s:%i - Out of thread:" #ifdef LGI_COCOA "%llx, %llx" #else "%x, %x" #endif "\n", _FL, Gui, Me); #endif return Gui == Me; #endif } bool LView::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b, int64_t timeoutMs) { #ifdef LGI_SDL return LPostEvent(this, Cmd, a, b); #elif defined(HAIKU) if (!d || !d->Hnd) { // printf("%s:%i - Bad pointers %p %p\n", _FL, d, d ? d->Hnd : NULL); return false; } BWindow *bWnd = NULL; LWindow *wnd = dynamic_cast(this); if (wnd) { bWnd = wnd->WindowHandle(); } else { // Look for an attached view to lock... for (LViewI *v = this; v; v = v->GetParent()) { auto vhnd = v->Handle(); if (vhnd && ::IsAttached(vhnd)) { bWnd = vhnd->Window(); break; } } } BMessage m(Cmd); auto r = m.AddInt64(LMessage::PropA, a); if (r != B_OK) printf("%s:%i - AddUInt64 failed.\n", _FL); r = m.AddInt64(LMessage::PropB, b); if (r != B_OK) printf("%s:%i - AddUInt64 failed.\n", _FL); r = m.AddPointer(LMessage::PropView, this); if (r != B_OK) printf("%s:%i - AddPointer failed.\n", _FL); if (bWnd) { r = bWnd->PostMessage(&m); if (r != B_OK) printf("%s:%i - PostMessage failed.\n", _FL); return r == B_OK; } // Not attached yet... d->MsgQue.Add(new BMessage(m)); // printf("%s:%i - PostEvent.MsgQue=%i\n", _FL, (int)d->MsgQue.Length()); return true; #elif WINNATIVE if (!_View) return false; BOOL Res = ::PostMessage(_View, Cmd, a, b); if (!Res) { auto Err = GetLastError(); int asd=0; } return Res != 0; #elif !LGI_VIEW_HANDLE return LAppInst->PostEvent(this, Cmd, a, b); #else if (_View) return LPostEvent(_View, Cmd, a, b); LAssert(0); return false; #endif } bool LView::Invalidate(LRegion *r, bool Repaint, bool NonClient) { if (r) { for (int i=0; iLength(); i++) { bool Last = i == r->Length()-1; Invalidate((*r)[i], Last ? Repaint : false, NonClient); } return true; } return false; } LButton *FindDefault(LViewI *w) { LButton *But = 0; for (auto c: w->IterateViews()) { But = dynamic_cast(c); if (But && But->Default()) { break; } But = FindDefault(c); if (But) { break; } } return But; } bool LView::Name(const char *n) { LBase::Name(n); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE auto Temp = LBase::NameW(); SetWindowTextW(_View, Temp ? Temp : L""); #endif } #endif Invalidate(); return true; } const char *LView::Name() { #if WINNATIVE if (_View) { LView::NameW(); } #endif return LBase::Name(); } bool LView::NameW(const char16 *n) { LBase::NameW(n); #if WINNATIVE if (_View && n) { auto Txt = LBase::NameW(); SetWindowTextW(_View, Txt); } #endif Invalidate(); return true; } const char16 *LView::NameW() { #if WINNATIVE if (_View) { int Length = GetWindowTextLengthW(_View); if (Length > 0) { char16 *Buf = new char16[Length+1]; if (Buf) { Buf[0] = 0; int Chars = GetWindowTextW(_View, Buf, Length+1); Buf[Chars] = 0; LBase::NameW(Buf); } DeleteArray(Buf); } else { LBase::NameW(0); } } #endif return LBase::NameW(); } LViewI *LView::FindControl(int Id) { LAssert(Id != -1); if (GetId() == Id) { return this; } for (auto c : Children) { LViewI *Ctrl = c->FindControl(Id); if (Ctrl) { return Ctrl; } } return 0; } LPoint LView::GetMinimumSize() { return d->MinimumSize; } void LView::SetMinimumSize(LPoint Size) { d->MinimumSize = Size; bool Change = false; LRect p = Pos; if (X() < d->MinimumSize.x) { p.x2 = p.x1 + d->MinimumSize.x - 1; Change = true; } if (Y() < d->MinimumSize.y) { p.y2 = p.y1 + d->MinimumSize.y - 1; Change = true; } if (Change) { SetPos(p); } } bool LView::SetColour(LColour &c, bool Fore) { LCss *css = GetCss(true); if (!css) return false; if (Fore) { if (c.IsValid()) css->Color(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropColor); } else { if (c.IsValid()) css->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropBackgroundColor); } return true; } /* bool LView::SetCssStyle(const char *CssStyle) { if (!d->Css && !d->Css.Reset(new LCss)) return false; const char *Defs = CssStyle; bool b = d->Css->Parse(Defs, LCss::ParseRelaxed); if (b && d->FontOwnType == GV_FontCached) { d->Font = NULL; d->FontOwnType = GV_FontPtr; } return b; } */ void LView::SetCss(LCss *css) { d->Css.Reset(css); } LCss *LView::GetCss(bool Create) { if (Create && !d->Css) d->Css.Reset(new LCss); if (d->CssDirty && d->Css) { const char *Defs = d->Styles; if (d->Css->Parse(Defs, LCss::ParseRelaxed)) d->CssDirty = false; } return d->Css; } LPoint &LView::GetWindowBorderSize() { static LPoint s; ZeroObj(s); #if WINNATIVE if (_View) { RECT Wnd, Client; GetWindowRect(Handle(), &Wnd); GetClientRect(Handle(), &Client); s.x = (Wnd.right-Wnd.left) - (Client.right-Client.left); s.y = (Wnd.bottom-Wnd.top) - (Client.bottom-Client.top); } #elif defined __GTK_H__ #elif defined MAC s.x = 0; s.y = 22; #endif return s; } #ifdef _DEBUG #if defined(LGI_CARBON) void DumpHiview(HIViewRef v, int Depth = 0) { char Sp[256]; memset(Sp, ' ', Depth << 2); Sp[Depth<<2] = 0; printf("%sHIView=%p", Sp, v); if (v) { Boolean vis = HIViewIsVisible(v); Boolean en = HIViewIsEnabled(v, NULL); HIRect pos; HIViewGetFrame(v, &pos); char cls[128]; ZeroObj(cls); GetControlProperty(v, 'meme', 'clas', sizeof(cls), NULL, cls); printf(" vis=%i en=%i pos=%g,%g-%g,%g cls=%s", vis, en, pos.origin.x, pos.origin.y, pos.size.width, pos.size.height, cls); } printf("\n"); for (HIViewRef c = HIViewGetFirstSubview(v); c; c = HIViewGetNextView(c)) { DumpHiview(c, Depth + 1); } } #elif defined(__GTK_H__) void DumpGtk(Gtk::GtkWidget *w, Gtk::gpointer Depth = NULL) { using namespace Gtk; if (!w) return; char Sp[65] = {0}; if (Depth) memset(Sp, ' ', *((int*)Depth)*2); auto *Obj = G_OBJECT(w); LViewI *View = (LViewI*) g_object_get_data(Obj, "LViewI"); GtkAllocation a; gtk_widget_get_allocation(w, &a); LgiTrace("%s%p(%s) = %i,%i-%i,%i\n", Sp, w, View?View->GetClass():G_OBJECT_TYPE_NAME(Obj), a.x, a.y, a.width, a.height); if (GTK_IS_CONTAINER(w)) { auto *c = GTK_CONTAINER(w); if (c) { int Next = Depth ? *((int*)Depth)+1 : 1; gtk_container_foreach(c, DumpGtk, &Next); } } } #elif defined(HAIKU) || defined(WINDOWS) template void _Dump(int Depth, T v) { LString Sp, Name; Sp.Length(Depth<<1); memset(Sp.Get(), ' ', Depth<<1); Sp.Get()[Depth<<1] = 0; #if defined(HAIKU) LRect Frame = v->Frame(); Name = v->Name(); bool IsHidden = v->IsHidden(); #else RECT rc; GetWindowRect(v, &rc); LRect Frame = rc; wchar_t txt[256]; GetWindowTextW(v, txt, CountOf(txt)); Name = txt; bool IsHidden = !(GetWindowLong(v, GWL_STYLE) & WS_VISIBLE); #endif LgiTrace("%s%p::%s frame=%s vis=%i\n", Sp.Get(), v, Name.Get(), Frame.GetStr(), !IsHidden); #if defined(HAIKU) for (int32 i=0; iCountChildren(); i++) _Dump(Depth + 1, v->ChildAt(i)); #else for (auto h = GetWindow(v, GW_CHILD); h; h = GetWindow(h, GW_HWNDNEXT)) _Dump(Depth + 1, h); #endif } #endif void LView::_Dump(int Depth) { char Sp[65] = {0}; memset(Sp, ' ', Depth*2); #if 0 char s[256]; sprintf_s(s, sizeof(s), "%s%p::%s %s (_View=%p)\n", Sp, this, GetClass(), GetPos().GetStr(), _View); LgiTrace(s); List::I i = Children.Start(); for (LViewI *c = *i; c; c = *++i) { LView *v = c->GetGView(); if (v) v->_Dump(Depth+1); } #elif defined(LGI_CARBON) DumpHiview(_View); #elif defined(__GTK_H__) // DumpGtk(_View); - #else + #elif !defined(MAC) #if defined(HAIKU) LLocker lck(WindowHandle(), _FL); if (lck.Lock()) #endif ::_Dump(0, WindowHandle()); #endif } #endif //////////////////////////////////////////////////////////////////////////////////////////////////// static LArray *AllFactories = NULL; #if defined(WIN32) static HANDLE FactoryEvent; #else static pthread_once_t FactoryOnce = PTHREAD_ONCE_INIT; static void GFactoryInitFactories() { AllFactories = new LArray; } #endif LViewFactory::LViewFactory() { #if defined(WIN32) char16 Name[64]; swprintf_s(Name, CountOf(Name), L"LgiFactoryEvent.%i", GetCurrentProcessId()); HANDLE h = CreateEventW(NULL, false, false, Name); DWORD err = GetLastError(); if (err != ERROR_ALREADY_EXISTS) { FactoryEvent = h; AllFactories = new LArray; } else { LAssert(AllFactories != NULL); } #else pthread_once(&FactoryOnce, GFactoryInitFactories); #endif if (AllFactories) AllFactories->Add(this); } LViewFactory::~LViewFactory() { if (AllFactories) { AllFactories->Delete(this); if (AllFactories->Length() == 0) { DeleteObj(AllFactories); #if defined(WIN32) CloseHandle(FactoryEvent); #endif } } } LView *LViewFactory::Create(const char *Class, LRect *Pos, const char *Text) { if (ValidStr(Class) && AllFactories) { for (int i=0; iLength(); i++) { LView *v = (*AllFactories)[i]->NewView(Class, Pos, Text); if (v) { return v; } } } return 0; } #ifdef _DEBUG #if defined(__GTK_H__) using namespace Gtk; #include "LgiWidget.h" #endif void LView::Debug() { _Debug = true; #if defined LGI_COCOA d->ClassName = GetClass(); #endif } #endif diff --git a/src/mac/cocoa/App.mm b/src/mac/cocoa/App.mm --- a/src/mac/cocoa/App.mm +++ b/src/mac/cocoa/App.mm @@ -1,946 +1,946 @@ #include #include #include #include #include #include #import #include "lgi/common/Lgi.h" #include "lgi/common/Process.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Array.h" #include "lgi/common/Thread.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/Menu.h" #include #include #include #include #include #import "LCocoaView.h" #include "AppPriv.h" extern int hndstate(int hnd); struct OsAppArgumentsPriv { LAutoString Str; LArray Ptr; }; OsAppArguments::OsAppArguments(int args, const char **arg) { d = new OsAppArgumentsPriv; Args = args; Arg = arg; } OsAppArguments::~OsAppArguments() { DeleteObj(d); } void OsAppArguments::Set(const char *CmdLine) { d->Str.Reset(); d->Ptr.Length(0); LArray Raw; LArray Offsets; auto Exe = LGetExeFile(); Offsets.Add(0); if (Exe) { Raw.Length(Exe.Length() + 1); strcpy(Raw.AddressOf(), Exe); } else { Raw.Add(0); } if (CmdLine) { for (auto s = CmdLine; *s; ) { while (*s && strchr(WhiteSpace, *s)) s++; const char *e; if (*s == '\'' || *s == '\"') { auto delim = *s++; Offsets.Add(Raw.Length()); e = s; while (*e && *e != delim) { Raw.Add(*e++); } Raw.Add(0); } else { Offsets.Add(Raw.Length()); for (e = s; *e && !strchr(WhiteSpace, *e); e++) { Raw.Add(*e); } Raw.Add(0); } s = *e ? e + 1 : e; } } d->Str.Reset(Raw.Release()); for (int n=0; nPtr[n] = d->Str + Offsets[n]; } Args = (int)d->Ptr.Length(); Arg = (const char**) &d->Ptr[0]; } OsAppArguments &OsAppArguments::operator =(OsAppArguments &a) { LArray Raw; LArray Offsets; for (int i=0; iStr.Reset(new char[Raw.Length()]); memcpy(d->Str, &Raw[0], Raw.Length()); for (int n=0; nPtr[n] = d->Str + Offsets[n]; } Args = (int)d->Ptr.Length(); Arg = (const char**) &d->Ptr[0]; return *this; } //////////////////////////////////////////////////////////////// #if defined(MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 #define SDK_10_12(newSym, oldSym) newSym #else #define SDK_10_12(newSym, oldSym) oldSym #endif void LMouse::SetFromEvent(NSEvent *ev, NSView *view) { auto r = view.frame; auto pt = ev.locationInWindow; x = (int)pt.x; y = (int)(r.size.height - pt.y); SetModifer((uint32)ev.modifierFlags); Event = ev; switch (ev.type) { case SDK_10_12(NSEventTypeLeftMouseDown, NX_LMOUSEDOWN): Down(true); Left(true); break; case SDK_10_12(NSEventTypeLeftMouseUp, NX_LMOUSEUP): Down(false); Left(true); break; case SDK_10_12(NSEventTypeRightMouseDown, NX_RMOUSEDOWN): Down(true); Right(true); break; case SDK_10_12(NSEventTypeRightMouseUp, NX_RMOUSEUP): Down(false); Right(true); break; case SDK_10_12(NSEventTypeOtherMouseDown, NX_OMOUSEDOWN): Down(true); Middle(true); break; case SDK_10_12(NSEventTypeOtherMouseUp, NX_OMOUSEUP): Down(false); Middle(true); break; case SDK_10_12(NSEventTypeMouseMoved, NX_MOUSEMOVED): IsMove(true); break; case SDK_10_12(NSEventTypeLeftMouseDragged, NX_LMOUSEDRAGGED): Down(true); IsMove(true); Left(true); break; case SDK_10_12(NSEventTypeRightMouseDragged, NX_RMOUSEDRAGGED): Down(true); IsMove(true); Right(true); break; default: LAssert(!"Unknown event."); break; } Double(ev.clickCount == 2 && Down()); } void LUiEvent::SetModifer(uint32_t modifierKeys) { System(modifierKeys & SDK_10_12(NSEventModifierFlagCommand, NSCommandKeyMask)); Shift (modifierKeys & SDK_10_12(NSEventModifierFlagShift, NSShiftKeyMask)); Alt (modifierKeys & SDK_10_12(NSEventModifierFlagOption, NSAlternateKeyMask)); Ctrl (modifierKeys & SDK_10_12(NSEventModifierFlagControl, NSControlKeyMask)); } void LMessage::Set(int msg, Param A, Param B) { m = msg; a = A; b = B; } //////////////////////////////////////////////////////////////// void OnSigPipe(int i) { } void OnCrash(int i) { printf("%s:%i - on crash.\n", __FILE__, __LINE__); signal(SIGBUS, 0); signal(SIGSEGV, 0); struct Pipe { int Read; int Write; Pipe() { Read = -1; Write = -1; } }; Pipe Read; Pipe Write; Pipe Error; int Pid; pipe((int*)&Read); pipe((int*)&Error); auto Exe = LGetExeFile(); // Has stdin pipe pipe((int*)&Write); if (!(Pid = fork())) { // stdin -> Write close(0); // close stdin dup(Write.Read); close(Write.Write); // stdout -> Read close(1); // close stdout dup(Read.Write); close(Read.Read); // stderr -> Error close(2); // close stderr dup(Error.Write); close(Error.Read); // setup read & write handles char sPid[32]; sprintf(sPid, "--pid=%i", getpid()); char *Args[] = {sPid, Exe, 0}; // printf("Calling: execv('gdb', '%s', '%s');\n", Exe, sPid); execv("/usr/bin/gdb", Args); // We should never get here printf("%s:%i - execv(gdb) failed.\n", __FILE__, __LINE__); exit(-1); } ssize_t r, Used = 0; char Buf[1025]; bool Capture = false; while ((r = read(Read.Read, Buf + Used, sizeof(Buf) - Used - 1)) > 0) { printf("Got %i bytes\n", (int)r); Used += r; Buf[Used] = 0; printf("Capt=%i Buf='%s'\n", Capture, Buf); if (!Capture) { if (stristr(Buf, "(gdb)")) { char c[] = "info pid\n"; ssize_t w = write(Write.Write, c, strlen(c)); printf("Writing cmd %i bytes\n", (int)w); Capture = true; Used = 0; Buf[0] = 0; } } char *Eol; while ((Eol = strstr(Buf, "\n"))) { *Eol = 0; if (Capture) { printf("Capture '%s'\n", Buf); } Eol += 1; ptrdiff_t Len = Eol - Buf; memmove(Buf, Eol, Used + 1 - Len); Used -= Len; } } exit(-1); } //////////////////////////////////////////////////////////////////////////// @implementation LNsApplication - (id)init { if ((self = [super init]) != nil) { self.d = NULL; } return self; } - (void)setPriv:(nonnull LAppPrivate*)priv { self.d = priv; } - (void)terminate:(nullable id)sender { [super terminate:sender]; } - (void)dealloc { [super dealloc]; } - (void)assert:(LCocoaAssert*)ca { NSAlert *a = [[NSAlert alloc] init]; a.messageText = ca.msg.NsStr(); a.alertStyle = NSAlertStyleCritical; [a addButtonWithTitle:@"Debug"]; [a addButtonWithTitle:@"Ignore"]; [a addButtonWithTitle:@"Abort"]; ca.result = [a runModal]; [a release]; } - (void)onUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)reply { LString s = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; if (self.d && self.d->Owner) self.d->Owner->OnUrl(s); } @end ///////////////////////////////////////////////////////////////////////////// LSkinEngine *LApp::SkinEngine = 0; LApp *TheApp = 0; LMouseHook *LApp::MouseHook = 0; LApp::LApp(OsAppArguments &AppArgs, const char *AppName, LAppArguments *ObjArgs) : OsApplication(AppArgs.Args, AppArgs.Arg) { TheApp = this; d = new LAppPrivate(this); d->Name.Reset(NewStr(AppName)); AppWnd = 0; Name(AppName); if (LIsRelativePath(AppArgs.Arg[0])) { char wd[MAX_PATH_LEN]; char exe[MAX_PATH_LEN]; if (LMakePath(exe, sizeof(exe), getcwd(wd, sizeof(wd)), AppArgs.Arg[0])) LgiArgsAppPath = exe; else printf("%s:%i - LMakePath for Exe failed.\n", _FL); } else LgiArgsAppPath = AppArgs.Arg[0]; // printf("%s:%i - LgiArgsAppPath='%s'\n", _FL, LgiArgsAppPath.Get()); // Catch and ignore SIGPIPE signal(SIGPIPE, OnSigPipe); #if 0 // Crash handler... signal(SIGBUS, OnCrash); signal(SIGSEGV, OnCrash); #endif // We want our printf's NOW! setvbuf(stdout,(char *)NULL,_IONBF,0); // print mesgs immediately. // Connect to the server d->NsApp = [LNsApplication sharedApplication]; [d->NsApp setPriv:d]; // Register to get apple events NSAppleEventManager *em = [NSAppleEventManager sharedAppleEventManager]; [em setEventHandler:d->NsApp andSelector:@selector(onUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL]; // Setup the file and graphics sub-systems d->FileSystem = new LFileSystem; d->GdcSystem = new GdcDevice; srand((unsigned)LCurrentTime()); LColour::OnChange(); SetAppArgs(AppArgs); MouseHook = new LMouseHook; // System font setup SystemNormal = 0; LFontType SysFontType; if (SysFontType.GetSystemFont("System")) { SystemNormal = SysFontType.Create(); if (SystemNormal) { SystemNormal->Transparent(true); } else { printf("%s:%i - Error creating system font.\n", __FILE__, __LINE__); } SystemBold = SysFontType.Create(); if (SystemBold) { SystemBold->Bold(true); SystemBold->Transparent(true); SystemBold->Create(); } else { printf("%s:%i - Error creating bold version of the system font.\n", __FILE__, __LINE__); } } else { printf("%s:%i - Couldn't get system font setting.\n", __FILE__, __LINE__); } if (!SystemNormal) { LgiMsg(0, "Error: Couldn't create system font.", "Lgi Error: LApp::LApp", MB_OK); LExitApp(); } if (!GetOption("noskin")) { extern LSkinEngine *CreateSkinEngine(LApp *App); SkinEngine = CreateSkinEngine(this); } Default.Reset(new LMenu); } LApp::~LApp() { DeleteObj(AppWnd); DeleteObj(SystemNormal); DeleteObj(SystemBold); DeleteObj(MouseHook); DeleteObj(d->FileSystem); DeleteObj(d->GdcSystem); DeleteObj(LFontSystem::Me); DeleteObj(d); TheApp = 0; } OsApp &LApp::Handle() { return d->NsApp; } bool LApp::PostEvent(LViewI *View, int Msg, LMessage::Param A, LMessage::Param B) { if (!View) { printf("%s:%i - No view.\n", _FL); return false; } bool Exists = LView::LockHandler(View, LView::LockOp::OpExists); if (!Exists) { printf("%s:%i - View deleted.\n", _FL); return false; } LWindow *w = View->GetWindow(); if (!w) { // printf("%s:%i - No window.\n", _FL); return false; } auto v = w->Handle(); if (!v) { // printf("%s:%i - No handle.\n", _FL); return false; } auto m = [[LCocoaMsg alloc] init:View msg:Msg a:A b:B]; [v performSelectorOnMainThread:@selector(userEvent:) withObject:m waitUntilDone:false]; return true; } LApp *LApp::ObjInstance() { return TheApp; } bool LApp::IsOk() { bool Status = #if !defined(__clang__) (this != 0) && #endif (d != 0) /* #ifdef XWIN && (XDisplay() != 0) #endif */ ; LAssert(Status); return Status; } LMouseHook *LApp::GetMouseHook() { return MouseHook; } int LApp::GetMetric(LSystemMetric Metric) { switch (Metric) { default: break; case LGI_MET_DECOR_X: { return 0; } case LGI_MET_DECOR_Y: case LGI_MET_DECOR_CAPTION: { if (AppWnd) { #if 0 Rect r; OSStatus e = GetWindowBounds(AppWnd->WindowHandle(), kWindowTitleBarRgn, &r); if (e) printf("%s:%i - GetWindowBounds failed with %i\n", _FL, (int)e); else { int y = r.bottom - r.top; return y; } #endif } return 22; } } return 0; } LViewI *LApp::GetFocus() { auto kw = d->NsApp.p.keyWindow; if (!kw) return NULL; LNsWindow *w = objc_dynamic_cast(LNsWindow, kw); LWindow *gw = w ? [w getWindow] : nil; if (!gw) return NULL; return gw->GetFocus(); } OsThread LApp::_GetGuiThread() { return d->GuiThread; } OsThreadId LApp::GetGuiThreadId() { return d->GuiThreadId; } bool LApp::InThread() { return GetCurrentThreadId() == d->GuiThreadId; } OsProcessId LApp::GetProcessId() { return getpid(); } OsAppArguments *LApp::GetAppArgs() { return IsOk() ? &d->Args : 0; } void LApp::SetAppArgs(OsAppArguments &AppArgs) { if (IsOk()) { d->Args = AppArgs; } } struct IdleGluePtrs { LApp::OnIdleProc Callback; void *Param; }; #define CUSTOM_LOOP 0 #if 0 void IdleGlue(EventLoopTimerRef inTimer, void *inUserData) { IdleGluePtrs *p = (IdleGluePtrs*)inUserData; p->Callback(p->Param); } #endif bool LApp::Run(OnIdleProc IdleCallback, void *IdleParam) { if (!d->NsApp) { LAssert(!"No d->NsApp"); return false; } #if CUSTOM_LOOP // This impl allows for us to exit gracefully. int Depth = ++d->RunDepth; do { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSEvent *event = [ d->NsApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES]; [d->NsApp sendEvent:event]; [d->NsApp updateWindows]; [pool release]; } while (d->RunDepth >= Depth); #else OnCommandLine(); NSApplicationMain(GetArgs(), GetArg()); #endif return true; } bool LApp::Yield() { printf("%s:%i - Yield not supported.\n", _FL); return false; } void LApp::Exit(int Code) { #if CUSTOM_LOOP if (!Code) { if (d->RunDepth > 0) d->RunDepth--; } #else if (!Code) { if (AppWnd) AppWnd->Quit(); [d->NsApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0]; } else #endif { // hard exit ::exit(Code); } } void LApp::OnUrl(const char *Url) { if (AppWnd) AppWnd->OnUrl(Url); else d->UrlArg.Reset(NewStr(Url)); } void LApp::OnReceiveFiles(LArray &Files) { if (AppWnd) AppWnd->OnReceiveFiles(Files); } const char *LApp::GetArgumentAt(int n) { return n >= 0 && n < d->Args.Args ? d->Args.Arg[n] : 0; } bool LApp::GetOption(const char *Option, char *Dest, int DestLen) { LString Buf; if (GetOption(Option, Buf)) { if (Dest) strcpy_s(Dest, DestLen, Buf); return true; } return false; } bool LApp::GetOption(const char *Option, LString &Buf) { if (IsOk() && Option) { size_t OptLen = strlen(Option); for (int i=1; iArgs.Args; i++) { const char *a = d->Args.Arg[i]; if (strchr("-/\\", a[0])) { if (strncmp(a+1, Option, OptLen) == 0) { const char *Arg = 0; if (strlen(a+1+OptLen) > 0) { Arg = a + 1 + OptLen; } else if (i < d->Args.Args - 1) { Arg = d->Args.Arg[i + 1]; } if (Arg) { if (strchr("\'\"", *Arg)) { char Delim = *Arg++; char *End = strchr(Arg, Delim); if (End) { size_t Len = End-Arg; if (Len > 0) { Buf.Set(Arg, Len); } else return false; } else return false; } else { Buf = Arg; } } return true; } } } } return false; } void LApp::OnCommandLine() { LArray Files; for (int i=1; iArgs; i++) { const char *a = GetAppArgs()->Arg[i]; if (LFileExists(a)) { Files.Add(NewStr(a)); } } // call app if (Files.Length() > 0) { OnReceiveFiles(Files); } // clear up Files.DeleteArrays(); } LString MimeFromData(const char *File) { LString Ret; LFile f; if (!f.Open(File, O_READ)) return Ret; LArray b; b.Length(1024); auto r = f.Read(b.AddressOf(), b.Length()); if (r <= 0) return Ret; if (b.Length() >= 8) { if (memcmp(b.AddressOf(), "GIF89a\x01", 7) == 0) Ret = "image/gif"; } return Ret; } LString LApp::GetFileMimeType(const char *File) { LString Ret; if (!LFileExists(File)) { // Look in the path auto p = LString(getenv("PATH")).SplitDelimit(LGI_PATH_SEPARATOR); for (int i=0; i &Apps) +bool LApp::GetAppsForMimeType(const char *Mime, LArray &Apps) { // Use LSCopyApplicationForMIMEType? // Find alternative version of the MIME type (e.g. x-type and type). char AltMime[256]; strcpy(AltMime, Mime); char *s = strchr(AltMime, '/'); if (s) { s++; size_t Len = strlen(s) + 1; if (strnicmp(s, "x-", 2) == 0) { memmove(s, s+2, Len - 2); } else { memmove(s+2, s, Len); s[0] = 'x'; s[1] = '-'; } } if (!d->MimeToApp.Length()) { // printf("%s:%i - Building MimeToApp.\n", __FILE__, __LINE__); } AppArray *p = (AppArray*)d->MimeToApp.Find(Mime); if (p) { for (int i=0; iLength(); i++) { - Apps[i] = (*p)[i]; + Apps[i] = *(*p)[i]; } return true; } return false; } LSymLookup *LApp::GetSymLookup() { return &d->SymLookup; } bool LApp::IsElevated() { return geteuid() == 0; } int LApp::GetCpuCount() { return 1; } LFontCache *LApp::GetFontCache() { if (!d->FontCache) d->FontCache.Reset(new LFontCache(SystemNormal)); return d->FontCache; } diff --git a/src/mac/cocoa/General.mm b/src/mac/cocoa/General.mm --- a/src/mac/cocoa/General.mm +++ b/src/mac/cocoa/General.mm @@ -1,581 +1,602 @@ // Mac Implementation of General LGI functions #include #include #include #include #include // #define _POSIX_TIMERS #include #include "lgi/common/Lgi.h" #include "lgi/common/Process.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/Net.h" #include #include #include //////////////////////////////////////////////////////////////// // Local helper functions +CFStringRef Utf8ToCFString(const char *s, ssize_t len = -1) +{ + if (s && len < 0) + len = strlen(s); + return CFStringCreateWithBytes(kCFAllocatorDefault, (const UInt8*)s, len, kCFStringEncodingUTF8, false); +} + +char *CFStringToUtf8(CFStringRef r) +{ + if (r == NULL) + return 0; + + char *Buffer = 0; + CFRange g = { 0, CFStringGetLength(r) }; + CFIndex Used; + + if (CFStringGetBytes(r, + g, + kCFStringEncodingUTF8, + 0, + false, + 0, + 0, + &Used)) + { + if ((Buffer = new char[Used+1])) + { + CFStringGetBytes(r, + g, + kCFStringEncodingUTF8, + 0, + false, + (UInt8*)Buffer, + Used, + &Used); + + Buffer[Used] = 0; + } + } + + return Buffer; +} + bool _lgi_check_file(char *Path) { if (Path) { if (LFileExists(Path)) { // file is there return true; } else { // shortcut? char *e = Path + strlen(Path); strcpy(e, ".lnk"); if (LFileExists(Path)) { // resolve shortcut char Link[256]; if (LResolveShortcut(Path, Link, sizeof(Link))) { // check destination of link if (LFileExists(Link)) { strcpy(Path, Link); return true; } } } *e = 0; } } return false; } void LSleep(uint32 i) { struct timespec request, remain; ZeroObj(request); ZeroObj(remain); request.tv_sec = i / 1000; request.tv_nsec = (i % 1000) * 1000000; while (nanosleep(&request, &remain) == -1) { request = remain; } } char *p2c(unsigned char *s) { if (s) { s[1+s[0]] = 0; return (char*)s + 1; } return 0; } void c2p255(Str255 &d, char *s) { if (s) { size_t Len = strlen(s); if (Len > 255) Len = 255; d[0] = Len; for (int i=0; iHandle(); [hnd.p performSelectorOnMainThread:@selector(assert:) withObject:ca waitUntilDone:true]; switch (ca.result) { case NSAlertFirstButtonReturn: // Debug/Break Result = 2; break; case NSAlertSecondButtonReturn: // Ingore/Continue Result = 3; break; case NSAlertThirdButtonReturn: // Exit/Abort Result = 1; break; } [ca release]; } #else GAlert a(0, "Assert Failed", Assert.Msg, "Abort", "Debug", "Ignore"); Result = a.DoModal(); #endif switch (Result) { default: { exit(-1); break; } case 2: { // Crash here to bring up the debugger... int *p = 0; *p = 0; break; } case 3: { break; } } #endif Asserting = false; } } //////////////////////////////////////////////////////////////////////// // Implementations LMessage CreateMsg(int m, LMessage::Param a, LMessage::Param b) { static class LMessage Msg(0); Msg.Set(m, a, b); return Msg; } OsView DefaultOsView(LView *v) { return NULL; } LString LGetFileMimeType(const char *File) { return LAppInst ? LAppInst->GetFileMimeType(File) : NULL; } bool _GetIniField(char *Grp, char *Field, char *In, char *Out, int OutSize) { if (ValidStr(In)) { bool InGroup = false; auto t = LString(In).SplitDelimit("\r\n"); for (int i=0; i Ver; LgiGetOs(Ver); if (Ver.Length() > 1) { if (Ver[0] < 10 || Ver[1] < 6) { IsAppBundle = false; } } } */ } struct stat s; int st = stat(File, &s); if (IsAppBundle) { char cmd[512]; if (ValidStr((char*)Args)) snprintf(cmd, sizeof(cmd), "open -a \"%s\" %s", File, Args); else snprintf(cmd, sizeof(cmd), "open -a \"%s\"", File); system(cmd); } else if (st == 0 && S_ISREG(s.st_mode) && (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { // This is an executable file if (!fork()) { if (Dir) chdir(Dir); LArray a; a.Add(File); char *p; while ((p = LTokStr(Args))) { a.Add(p); } a.Add(0); char *env[] = {0}; execve(File, (char*const*)&a[0], env); } return true; } else { // Document #if LGI_CARBON e = FinderLaunch(1, &r); if (e) printf("%s:%i - FinderLaunch faied with %i\n", _FL, (int)e); else Status = true; #elif LGI_COCOA LString file = File; auto url = [[NSURL alloc] initFileURLWithPath:file.NsStr()]; Status = [[NSWorkspace sharedWorkspace] openURL:url]; #endif } } } } return Status; } -CFStringRef Utf8ToCFString(char *s, ssize_t len) -{ - if (s && len < 0) - len = strlen(s); - return CFStringCreateWithBytes(kCFAllocatorDefault, (UInt8*)s, len, kCFStringEncodingUTF8, false); -} - -char *CFStringToUtf8(CFStringRef r) -{ - if (r == NULL) - return 0; - - char *Buffer = 0; - CFRange g = { 0, CFStringGetLength(r) }; - CFIndex Used; - - if (CFStringGetBytes(r, - g, - kCFStringEncodingUTF8, - 0, - false, - 0, - 0, - &Used)) - { - if ((Buffer = new char[Used+1])) - { - CFStringGetBytes(r, - g, - kCFStringEncodingUTF8, - 0, - false, - (UInt8*)Buffer, - Used, - &Used); - - Buffer[Used] = 0; - } - } - - return Buffer; -} - bool LGetMimeTypeExtensions(const char *Mime, LArray &Ext) { size_t Start = Ext.Length(); #define HardCodeExtention(Mime, Ext1, Ext2) \ else if (!stricmp(Mime, Mime)) \ { if (Ext1) Ext.Add(Ext1); \ if (Ext2) Ext.Add(Ext2); } if (!Mime); HardCodeExtention("text/calendar", "ics", (const char*)NULL) HardCodeExtention("text/x-vcard", "vcf", (const char*)NULL) HardCodeExtention("text/mbox", "mbx", "mbox"); return Ext.Length() > Start; } LString LCurrentUserName() { struct passwd *pw = getpwuid(geteuid()); if (pw) return pw->pw_name; return ""; } diff --git a/src/mac/cocoa/Menu.mm b/src/mac/cocoa/Menu.mm --- a/src/mac/cocoa/Menu.mm +++ b/src/mac/cocoa/Menu.mm @@ -1,1451 +1,1442 @@ /*hdr ** FILE: GuiMenu.cpp ** AUTHOR: Matthew Allen ** DATE: 18/7/98 ** DESCRIPTION: Gui menu system ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #define DEBUG_INFO 0 @interface LNSMenuItem : NSMenuItem { } @property LMenuItem* item; - (id)init:(LMenuItem*)it; - (void)activate; - (BOOL)worksWhenModal; @end @implementation LNSMenuItem - (id)init:(LMenuItem*)it { if ((self = [super init]) != nil) { self.item = it; [self setTarget:self]; self.action = @selector(activate); } return self; } - (BOOL)worksWhenModal { return YES; } - (void)activate { printf("activate\n"); self.item->OnActivate(self.item); } @end struct LShortcut { NSString *Str; public: NSString *Key; NSEventModifierFlags Mod; LShortcut(const char *s) { Key = @""; Str = nil; Mod = 0; auto Keys = LString(s).SplitDelimit("+-"); if (Keys.Length() <= 0) return; for (auto k: Keys) { if (stricmp(k, "CtrlCmd") == 0 || stricmp(k, "AltCmd") == 0 || stricmp(k, "Cmd") == 0 || stricmp(k, "Command") == 0) { Mod |= NSEventModifierFlagCommand; } else if (stricmp(k, "Ctrl") == 0 || stricmp(k, "Control") == 0) { Mod |= NSEventModifierFlagControl; } else if (stricmp(k, "Alt") == 0 || stricmp(k, "Option") == 0) { Mod |= NSEventModifierFlagOption; } else if (stricmp(k, "Shift") == 0) { Mod |= NSEventModifierFlagShift; } else if (stricmp(k, "Del") == 0 || stricmp(k, "Delete") == 0) { unichar s[] = {NSDeleteCharacter}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "Ins") == 0 || stricmp(k, "Insert") == 0) { unichar s[] = {NSInsertFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "Home") == 0) { unichar s[] = {NSHomeFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "End") == 0) { unichar s[] = {NSEndFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "PageUp") == 0) { unichar s[] = {NSPageUpFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "PageDown") == 0) { unichar s[] = {NSPageDownFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "Backspace") == 0) { unichar s[] = {NSBackspaceCharacter}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "Space") == 0) { Key = @" "; } else if (k[0] == 'F' && isdigit(k[1])) { int64 index = k.Strip("F").Int(); unichar s[] = {(unichar)(NSF1FunctionKey + index - 1)}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (isalpha(k[0])) { Key = Str = LString(k).Lower().NsStr(); } else if (isdigit(k[0])) { Key = Str = LString(k).NsStr(); } else if (strchr(",.", k(0))) { unichar s[] = {(unichar)k(0)}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else { printf("%s:%i - Unhandled shortcut token '%s'\n", _FL, k.Get()); } } } ~LShortcut() { if (Str) [Str release]; } }; /////////////////////////////////////////////////////////////////////////////////////////////// LSubMenu::LSubMenu(const char *name, bool Popup) { Menu = 0; Parent = 0; Info = NULL; LBase::Name(name); Info.p = [[NSMenu alloc] init]; [Info.p setAutoenablesItems:NO]; } LSubMenu::~LSubMenu() { while (Items.Length()) { LMenuItem *i = Items[0]; if (i->Parent != this) { i->Parent = NULL; Items.Delete(i); } delete i; } if (Info) { [Info.p release]; Info = NULL; } } void LSubMenu::OnAttach(bool Attach) { for (auto i: Items) { i->OnAttach(Attach); } if (Attach && this != Menu && Parent && Parent->Parent) { } } size_t LSubMenu::Length() { return Items.Length(); } LMenuItem *LSubMenu::ItemAt(int Id) { return Items.ItemAt(Id); } LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut) { LMenuItem *i = new LMenuItem(Menu, this, Str, Id, Where, Shortcut); if (!i || !Info) return NULL; Items.Insert(i, Where); auto Index = Items.IndexOf(i); // auto Max = Info.p.numberOfItems; LString s(i->LBase::Name()); auto name = s.NsStr(); if (!name) { delete i; return NULL; } LShortcut sc(Shortcut); i->Info.p = [[LNSMenuItem alloc] init:i]; if (!i->Info) { Items.Delete(i); delete i; return NULL; } [i->Info.p setTitle:name]; i->Info.p.keyEquivalent = sc.Key; i->Info.p.keyEquivalentModifierMask = sc.Mod; i->Id(Id); i->Enabled(Enabled); [Info.p insertItem:i->Info atIndex:Index]; return i; } LMenuItem *LSubMenu::AppendSeparator(int Where) { LMenuItem *i = new LMenuItem; if (i) { i->Parent = this; i->Menu = Menu; i->Id(-2); Items.Insert(i, Where); if (Info) { auto Index = Items.IndexOf(i); // auto Max = Info.p.numberOfItems; // printf("Adding ----- @ %i, %i\n", (int)Index, (int)Max); i->Info = [NSMenuItem separatorItem]; [Info.p insertItem:i->Info atIndex:Index]; } else { printf("%s:%i - No menu to attach item to.\n", _FL); } return i; } return 0; } LSubMenu *LSubMenu::AppendSub(const char *Str, int Where) { LMenuItem *i = new LMenuItem; if (i && Str) { i->Parent = this; i->Menu = Menu; i->Id(-1); Items.Insert(i, Where); if (Info) { i->Child = new LSubMenu(Str); if (i->Child) { i->Child->Parent = i; i->Child->Menu = Menu; i->Child->Window = Window; i->Info.p = [[NSMenuItem alloc] init]; LAssert(i->Info); i->Name(Str); LString s(i->LBase::Name()); [i->Child->Info.p setTitle:s.NsStr()]; [i->Info.p setSubmenu:i->Child->Info.p]; auto Index = Items.IndexOf(i); auto IsMenu = dynamic_cast(this); auto Offset = IsMenu && Where >= 0 ? 1 : 0; // Adjust for 'Root' element "app menu" [Info.p insertItem:i->Info atIndex:Index + Offset]; } } else { printf("%s:%i - No menu to attach item to.\n", __FILE__, __LINE__); } return i->Child; } return 0; } void LSubMenu::Empty() { while (Items[0]) { if (!RemoveItem(Items[0])) break; // Otherwise we'll get an infinite loop. } } bool LSubMenu::RemoveItem(int i) { LMenuItem *Item = Items[i]; if (Item) { return Item->Remove(); } return false; } bool LSubMenu::RemoveItem(LMenuItem *Item) { if (Item && Items.HasItem(Item)) { return Item->Remove(); } return false; } bool LSubMenu::OnKey(LKey &k) { return false; } void LSubMenu::OnActivate(LMenuItem *item) { if (!item) return; if (FloatResult) *FloatResult = item->Id(); else if (Parent) Parent->OnActivate(item); else LAssert(!"Should have a float result OR a parent.."); } int LSubMenu::Float(LView *From, int x, int y, int Btns) { LPoint p(x, y); OsView v = nil; if (From) From->Capture(false); auto w = From ? From->GetWindow() : NULL; if (w) { v = w->Handle(); w->PointToView(p); p = w->Flip(p); } FloatResult.Reset(new int(0)); // auto item = Items[0]; // auto menuitem = item->Info.p; // auto en = menuitem.enabled; NSPoint loc = {(double)p.x, (double)p.y}; [Info.p popUpMenuPositioningItem:nil atLocation:loc inView:v]; return FloatResult ? *FloatResult : 0; } LSubMenu *LSubMenu::FindSubMenu(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return Sub; } else if (Sub) { LSubMenu *m = Sub->FindSubMenu(Id); if (m) { return m; } } } return 0; } LMenuItem *LSubMenu::FindItem(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return i; } else if (Sub) { i = Sub->FindItem(Id); if (i) { return i; } } } return 0; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuItemPrivate { public: LString Shortcut; }; LMenuItem::LMenuItem() { d = new LMenuItemPrivate(); Menu = NULL; Info = NULL; Child = NULL; Parent = NULL; _Icon = -1; _Id = 0; _Flags = 0; } LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *Str, int Id, int Pos, const char *Shortcut) { d = new LMenuItemPrivate(); LBase::Name(Str); Menu = m; Parent = p; Info = NULL; Child = NULL; _Icon = -1; _Id = Id; _Flags = 0; d->Shortcut = Shortcut; Name(Str); ScanForAccel(); } LMenuItem::~LMenuItem() { if (Parent) { Parent->Items.Delete(this); Parent = NULL; } DeleteObj(Child); DeleteObj(d); } void LMenuItem::OnActivate(LMenuItem *item) { if (Parent) Parent->OnActivate(item); else LAssert(!"Should have a parent."); } void LMenuItem::OnAttach(bool Attach) { if (Attach) { if (_Icon >= 0) { Icon(_Icon); } if (Sub()) { Sub()->OnAttach(Attach); } } } // the following 3 functions paint the menus according the to // windows standard. but also allow for correct drawing of menuitem // icons. some implementations of windows force the program back // to the 8-bit palette when specifying the icon graphic, thus removing // control over the colours displayed. these functions remove that // limitation and also provide the application the ability to override // the default painting behaviour if desired. void LMenuItem::_Measure(LPoint &Size) { auto Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; bool BaseMenu = Parent == Menu; // true if attached to a windows menu // else is a submenu int Ht = Font->GetHeight(); // int IconX = BaseMenu ? ((24-Ht)/2)-Ht : 20; int IconX = BaseMenu ? 2 : 16; if (Separator()) { Size.x = 8; Size.y = 8; } else { // remove '&' chars for string measurement char Str[256]; const char *n = Name(), *i = n; char *o = Str; while (i && *i) { if (*i == '&') { if (i[1] == '&') { *o++ = *i++; } } else { *o++ = *i; } i++; } *o++ = 0; // check for accelerators char *Tab = strchr(Str, '\t'); if (Tab) { // string with accel int Mx, Tx; LDisplayString ds(Font, Str, Tab-Str); Mx = ds.X(); LDisplayString ds2(Font, Tab + 1); Tx = ds2.X(); Size.x = IconX + 32 + Mx + Tx; } else { // normal string LDisplayString ds(Font, Str); Size.x = IconX + ds.X() + 4; } if (!BaseMenu) { // leave room for child pointer Size.x += Child ? 8 : 0; } Size.y = MAX(IconX, Ht+2); } } #define Time(a, b) ((double)(b - a) / 1000) void LMenuItem::_PaintText(LSurface *pDC, int x, int y, int Width) { auto n = Name(); if (n) { auto Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; bool Underline = false; const char *e = 0; for (auto s=n; s && *s; s = *e ? e : 0) { switch (*s) { case '&': { if (s[1] == '&') { e = s + 2; LDisplayString d(Font, "&"); d.Draw(pDC, x, y, 0); x += d.X(); } else { Underline = true; e = s + 1; } break; } case '\t': { LDisplayString ds(Font, e + 1); x = Width - ds.X() - 8; e = s + 1; break; } default: { if (Underline) { LgiNextUtf8(e); } else { for (e = s; *e; e++) { if (*e == '\t') break; if (*e == '&') break; } } ptrdiff_t Len = e - s; if (Len > 0) { // paint text till that point LDisplayString d(Font, s, Len); d.Draw(pDC, x, y, 0); if (Underline) { LDisplayString ds(Font, s, 1); int UnderX = ds.X(); int Ascent = (int)ceil(Font->Ascent()); pDC->Colour(Font->Fore()); pDC->Line(x, y+Ascent+1, x+MAX(UnderX-2, 1), y+Ascent+1); Underline = false; } x += d.X(); } break; } } } } } void LMenuItem::_Paint(LSurface *pDC, int Flags) { bool BaseMenu = Parent == Menu; int IconX = BaseMenu ? 5 : 20; bool Selected = TestFlag(Flags, ODS_SELECTED); bool Disabled = TestFlag(Flags, ODS_DISABLED); bool Checked = TestFlag(Flags, ODS_CHECKED); #if defined(WIN32) || defined(MAC) LRect r(0, 0, pDC->X()-1, pDC->Y()-1); #else LRect r = Info->GetClient(); #endif if (Separator()) { // Paint a separator int Cy = r.Y() / 2; pDC->Colour(L_MED); pDC->Rectangle(); pDC->Colour(L_LOW); pDC->Line(0, Cy-1, pDC->X()-1, Cy-1); pDC->Colour(L_LIGHT); pDC->Line(0, Cy, pDC->X()-1, Cy); } else { // Paint a text menu item LColour Fore(L_TEXT); LColour Back(Selected ? L_HIGH : L_MED); int x = IconX; int y = 1; // For a submenu pDC->Colour(Back); pDC->Rectangle(); // Draw the text on top LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; Font->Transparent(true); if (Disabled) { // Disabled text if (!Selected) { Font->Colour(L_LIGHT); _PaintText(pDC, x+1, y+1, r.X()); } // Else selected... don't draw the hilight // "greyed" text... Font->Colour(L_LOW); _PaintText(pDC, x, y, r.X()); } else { // Normal coloured text Font->Fore(Fore); _PaintText(pDC, x, y, r.X()); } auto ImgLst = (Menu && Menu->GetImageList()) ? Menu->GetImageList() : Parent ? Parent->GetImageList() : 0; // Draw icon/check mark if (Checked && IconX > 0) { // it's a check! int x = 4; int y = 6; pDC->Colour(Fore); pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); y++; pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); y++; pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); } else if (ImgLst && _Icon >= 0) { // it's an icon! LColour Bk(L_MED); ImgLst->Draw(pDC, 0, 0, _Icon, Bk); } // Sub menu arrow if (Child && !dynamic_cast(Parent)) { pDC->Colour(L_TEXT); int x = r.x2 - 4; int y = r.y1 + (r.Y()/2); for (int i=0; i<4; i++) { pDC->Line(x, y-i, x, y+i); x--; } } } } bool LMenuItem::ScanForAccel() { if (!d->Shortcut) return false; // printf("d->Shortcut=%s\n", d->Shortcut.Get()); auto Keys = d->Shortcut.SplitDelimit("+-"); if (Keys.Length() > 0) { int Flags = 0; int Vkey = 0; int Chr = 0; for (int i=0; iShortcut.Get()); } if (Vkey || Chr) { if ( ( (Flags & LGI_EF_ALT) != 0 && (Flags & LGI_EF_SYSTEM) == 0 ) || Vkey == LK_BACKSPACE ) { auto Ident = Id(); LAssert(Ident > 0); Menu->Accel.Insert( new LAccelerator(Flags, Vkey, Chr, Ident) ); } } else { printf("%s:%i - Accel scan failed, str='%s'\n", _FL, d->Shortcut.Get()); return false; } } return true; } LSubMenu *LMenuItem::GetParent() { return Parent; } bool LMenuItem::Remove() { if (!Parent) return false; if (Parent->Info && Info) { [Parent->Info.p removeItem:Info]; Parent->Items.Delete(this); Info = NULL; } else { Parent->Items.Delete(this); } return true; } void LMenuItem::Id(int i) { _Id = i; if (Parent && Parent->Info && Info) { #if LGI_COCOA #else SetMenuItemCommandID(Parent->Info, Info, _Id); #endif } } void LMenuItem::Separator(bool s) { if (s) { _Id = -2; } if (Parent) { #if LGI_COCOA #else if (s) ChangeMenuItemAttributes(Parent->Info, Info, kMenuItemAttrSeparator, 0); else ChangeMenuItemAttributes(Parent->Info, Info, 0, kMenuItemAttrSeparator); #endif } } void LMenuItem::Checked(bool c) { if (c) SetFlag(_Flags, ODS_CHECKED); else ClearFlag(_Flags, ODS_CHECKED); if (Info) [Info.p setState: c ? NSControlStateValueOn : NSControlStateValueOff]; } bool LMenuItem::Name(const char *n) { char *Tmp = NewStr(n); if (Tmp) { char *in = Tmp, *out = Tmp; while (*in) { if (*in != '&') *out++ = *in; in++; } *out++ = 0; } bool Status = LBase::Name(Tmp); if (Status && Info) { LString s(Tmp); [Info.p setTitle:s.NsStr()]; } DeleteArray(Tmp); return Status; } void LMenuItem::Enabled(bool e) { #if 1 if (Info && Info.p.enabled ^ e) Info.p.enabled = e; #endif } void LMenuItem::Focus(bool f) { } void LMenuItem::Sub(LSubMenu *s) { Child = s; } void LMenuItem::Icon(int i) { _Icon = i; auto Lst = Menu ? Menu->GetImageList() : Parent->GetImageList(); if (!Lst || !Info) return; if (_Icon < 0 || _Icon >= Lst->GetItems()) return; auto r = Lst->GetIconRect(_Icon); [Info.p setImage: Lst->NsImage(&r)]; } void LMenuItem::Visible(bool i) { } int LMenuItem::Id() { return _Id; } const char *LMenuItem::Name() { return LBase::Name(); } bool LMenuItem::Separator() { return _Id == -2; } bool LMenuItem::Checked() { return TestFlag(_Flags, ODS_CHECKED); } bool LMenuItem::Enabled() { if (Parent) { #if LGI_COCOA #else return IsMenuItemEnabled(Parent->Info, Info); #endif } return true; } bool LMenuItem::Visible() { return true; } bool LMenuItem::Focus() { return 0; } LSubMenu *LMenuItem::Sub() { return Child; } int LMenuItem::Icon() { return _Icon; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuPrivate { public: int PrefId, AboutId; LMenuItem *PrefItem = NULL; LMenuItem *AboutItem = NULL; LMenuPrivate() { PrefId = AboutId = 0; } }; LMenu::LMenu(const char *AppName) : LSubMenu("", false) { Menu = this; d = new LMenuPrivate; auto s = AppendSub("Root"); if (s) { d->AboutItem = s->AppendItem("About", M_ABOUT); s->AppendSeparator(); d->PrefItem = s->AppendItem("Preferences", M_PERFERENCES, true, -1, "Cmd+,"); s->AppendItem("Hide", M_HIDE, true, -1, "Cmd+H"); s->AppendSeparator(); s->AppendItem("Quit", M_QUIT, true, -1, "Cmd+Q"); } } LMenu::~LMenu() { Accel.DeleteObjects(); DeleteObj(d); } void LMenu::OnActivate(LMenuItem *item) { if (!item) { LAssert(0); return; } switch (item->Id()) { case M_ABOUT: if (Window && d->AboutId) Window->PostEvent(M_COMMAND, d->AboutId); break; case M_PERFERENCES: if (Window && d->PrefId) Window->PostEvent(M_COMMAND, d->PrefId); break; case M_HIDE: [[NSApplication sharedApplication] hide:Info]; break; case M_QUIT: LCloseApp(); break; default: if (Window) { printf("%s:%i - post M_COMMAND\n", _FL); Window->PostEvent(M_COMMAND, item->Id()); } break; } } bool LMenu::SetPrefAndAboutItems(int PrefId, int AboutId) { d->PrefId = PrefId; if (d->PrefItem) d->PrefItem->Enabled(d->PrefId > 0); d->AboutId = AboutId; if (d->AboutItem) d->AboutItem->Enabled(d->AboutId > 0); return true; } struct LMenuFont { LFont *f; LMenuFont() { f = NULL; } ~LMenuFont() { DeleteObj(f); } } MenuFont; LFont *LMenu::GetFont() { if (!MenuFont.f) { LFontType Type; if (Type.GetSystemFont("Menu")) { MenuFont.f = Type.Create(); if (MenuFont.f) { #ifndef MAC _Font->CodePage(SysFont->CodePage()); #endif } else { printf("LMenu::GetFont Couldn't create menu font.\n"); } } else { printf("LMenu::GetFont Couldn't get menu typeface.\n"); } if (!MenuFont.f) { MenuFont.f = new LFont; if (MenuFont.f) *MenuFont.f = *LSysFont; } } return MenuFont.f ? MenuFont.f : LSysFont; } bool LMenu::Attach(LViewI *p) { bool Status = false; auto w = dynamic_cast(p); if (w) { Window = p; [NSApplication sharedApplication].mainMenu = Info; if (Info) { OnAttach(true); Status = true; } else { printf("%s:%i - No menu\n", _FL); } } return Status; } bool LMenu::Detach() { bool Status = false; return Status; } bool LMenu::OnKey(LView *v, LKey &k) { if (k.Down()) { k.Trace("MenuKey"); printf("Accel.len=%i\n", (int)Accel.Length()); for (auto a: Accel) { if (a->Match(k)) { Window->OnCommand(a->GetId(), 0, NULL); return true; } } if (k.Alt() && !dynamic_cast(v) && !dynamic_cast(v)) { bool Hide = false; for (auto s: Items) { if (!s->Separator()) { if (Hide) { // s->Info->HideSub(); } else { auto n = s->Name(); if (ValidStr(n)) { char *Amp = strchr(n, '&'); while (Amp && Amp[1] == '&') { Amp = strchr(Amp + 2, '&'); } if (Amp) { char Accel = tolower(Amp[1]); char Press = tolower(k.c16); if (Accel == Press) { Hide = true; } } } if (Hide) { // s->Info->ShowSub(); } else { // s->Info->HideSub(); } } } } if (Hide) { return true; } } } return false; } //////////////////////////////////////////////////////////////////////////// LAccelerator::LAccelerator(int flags, int vkey, int chr, int id) { Flags = flags; Vkey = vkey; Chr = chr; Id = id; } bool LAccelerator::Match(LKey &k) { int Press = (uint) k.c16; auto Up = toupper(Press); bool Match = false; #if 1 printf("LAccelerator::Match %i(%c)%s%s%s = %i(%c)%s%s%s\n", Up, Up>=' '?Up:'.', k.Ctrl()?" ctrl":"", k.Alt()?" alt":"", k.Shift()?" shift":"", Chr, Chr>=' '?Chr:'.', TestFlag(Flags, LGI_EF_CTRL)?" ctrl":"", TestFlag(Flags, LGI_EF_ALT)?" alt":"", TestFlag(Flags, LGI_EF_SHIFT)?" shift":"" ); #endif if (k.Alt() && !k.System() && !k.Ctrl()) { switch (k.vkey) { #define _(k) case LK_##k: \ Match = Vkey == #k[0]; break; _(A) _(B) _(C) _(D) _(E) _(F) _(G) _(H) _(I) _(J) _(K) _(L) _(M) _(N) _(O) _(P) _(Q) _(R) _(S) _(T) _(U) _(V) _(W) _(X) _(Y) _(Z) default: printf("%s:%i - No case for '%i'\n", _FL, k.vkey); break; } } else if (Vkey) { Match = k.vkey == Vkey; } else if (Chr) { Match = Up == (uint)Chr; } if (Match) { if ( ((TestFlag(Flags, LGI_EF_CTRL) ^ k.Ctrl()) == 0) && ((TestFlag(Flags, LGI_EF_ALT) ^ k.Alt()) == 0) && ((TestFlag(Flags, LGI_EF_SHIFT) ^ k.Shift()) == 0) ) { return true; } } return false; } //////////////////////////////////////////////////////////////////////////// LCommand::LCommand() { - Flags = GWF_VISIBLE; - Id = 0; - ToolButton = 0; - MenuItem = 0; - Accelerator = 0; - TipHelp = 0; - PrevValue = false; } LCommand::~LCommand() { - DeleteArray(Accelerator); - DeleteArray(TipHelp); } bool LCommand::Enabled() { if (ToolButton) return ToolButton->Enabled(); if (MenuItem) return MenuItem->Enabled(); return false; } void LCommand::Enabled(bool e) { if (ToolButton) { ToolButton->Enabled(e); } if (MenuItem) { MenuItem->Enabled(e); } } bool LCommand::Value() { bool HasChanged = false; if (ToolButton) { HasChanged |= (ToolButton->Value() != 0) ^ PrevValue; } if (MenuItem) { HasChanged |= (MenuItem->Checked() != 0) ^ PrevValue; } if (HasChanged) { Value(!PrevValue); } return PrevValue; } void LCommand::Value(bool v) { if (ToolButton) { ToolButton->Value(v); } if (MenuItem) { MenuItem->Checked(v); } PrevValue = v; } diff --git a/src/mac/cocoa/Printer.mm b/src/mac/cocoa/Printer.mm --- a/src/mac/cocoa/Printer.mm +++ b/src/mac/cocoa/Printer.mm @@ -1,175 +1,187 @@ #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/Button.h" #include "lgi/common/Printer.h" //////////////////////////////////////////////////////////////////// class LPrinterPrivate { public: LString Printer; LString Err; LPrinterPrivate() { } ~LPrinterPrivate() { } }; //////////////////////////////////////////////////////////////////// LPrinter::LPrinter() { d = new LPrinterPrivate; } LPrinter::~LPrinter() { DeleteObj(d); } bool LPrinter::Browse(LView *Parent) { return false; } bool LPrinter::Serialize(LString &Str, bool Write) { if (Write) { Str = d->Printer; } else { d->Printer = Str; } return true; } LString LPrinter::GetErrorMsg() { return d->Err; } #define ErrCheck(fn) \ if (e != noErr) \ { \ d->Err.Printf("%s:%i - %s failed with %i\n", _FL, fn, e); \ LgiTrace(d->Err); \ goto OnError; \ } +void LPrinter::Print(LPrintEvents *Events, + std::function callback, + const char *PrintJobName, + int MaxPages, + LView *Parent) +{ + int Status = LPrintEvents::OnBeginPrintError; -int LPrinter::Print(LPrintEvents *Events, const char *PrintJobName, int MaxPages, LView *Parent) -{ if (!Events) { LAssert(0); - return false; + if (callback) + callback(Status); + return; } - - - bool Status = false; + + #if LGI_COCOA + + #warning "No Cocoa Printing Impl." + + #elif LGI_CARBON // Carbon printing code? + + PMPrintSession ps = NULL; + PMPageFormat PageFmt = NULL; + PMPrintSettings PrintSettings = NULL; + auto Wnd = Parent ? Parent->GetWindow() : NULL; + Boolean Accepted = false; + Boolean Changed = false; + LAutoPtr dc; + int Pages; + LPrintDcParams Params; + double paperWidth, paperHeight; + PMPaper Paper = NULL; + UInt32 ResCount; + PMPrinter CurrentPrinter = NULL; + + OSStatus e = PMCreateSession(&ps); + ErrCheck("PMCreateSession"); + + e = PMCreatePageFormat(&PageFmt); + ErrCheck("PMCreatePageFormat"); + + e = PMSessionDefaultPageFormat(ps, PageFmt); + ErrCheck("PMSessionDefaultPageFormat"); + + e = PMCreatePrintSettings(&PrintSettings); + ErrCheck("PMCreatePrintSettings"); + #if 0 - PMPrintSession ps = NULL; - PMPageFormat PageFmt = NULL; - PMPrintSettings PrintSettings = NULL; - GWindow *Wnd = Parent ? Parent->GetWindow() : NULL; - Boolean Accepted = false; - Boolean Changed = false; - GAutoPtr dc; - int Pages; - GPrintDcParams Params; - double paperWidth, paperHeight; - PMPaper Paper = NULL; - UInt32 ResCount; - PMPrinter CurrentPrinter = NULL; - - OSStatus e = PMCreateSession(&ps); - ErrCheck("PMCreateSession"); - - e = PMCreatePageFormat(&PageFmt); - ErrCheck("PMCreatePageFormat"); - - e = PMSessionDefaultPageFormat(ps, PageFmt); - ErrCheck("PMSessionDefaultPageFormat"); - - e = PMCreatePrintSettings(&PrintSettings); - ErrCheck("PMCreatePrintSettings"); - -#if 0 - e = PMSessionUseSheets(ps, Wnd ? Wnd->WindowHandle() : NULL, NULL /*PMSheetDoneUPP sheetDoneProc*/); - ErrCheck("PMSessionUseSheets"); -#endif - - e = PMSessionPrintDialog(ps, PrintSettings, PageFmt, &Accepted); - ErrCheck("PMSessionPrintDialog"); - - e = PMSessionValidatePrintSettings(ps, PrintSettings, &Changed); - e = PMSessionValidatePageFormat(ps, PageFmt, &Changed); - - e = PMSessionBeginCGDocumentNoDialog(ps, PrintSettings, PageFmt); - ErrCheck("PMSessionBeginCGDocumentNoDialog"); - - e = PMSessionBeginPageNoDialog(ps, PageFmt, NULL); - ErrCheck("PMSessionBeginPageNoDialog"); - - e = PMGetAdjustedPaperRect(PageFmt, &Params.Page); //PMGetUnadjustedPageRect - ErrCheck("PMGetAdjustedPaperRect"); - - e = PMSessionGetCGGraphicsContext(ps, &Params.Ctx); - ErrCheck("PMSessionGetCGGraphicsContext"); - - e = PMSessionGetCurrentPrinter(ps, &CurrentPrinter); - ErrCheck("PMSessionGetCurrentPrinter"); - - e = PMGetPageFormatPaper(PageFmt, &Paper); - e = PMPaperGetWidth(Paper, &paperWidth); - e = PMPaperGetHeight(Paper, &paperHeight); - - e = PMPrinterGetPrinterResolutionCount(CurrentPrinter, &ResCount); - ErrCheck("PMPrinterGetPrinterResolutionCount"); - - for (unsigned i=0; iOnBeginPrint(dc); - for (int Page = 0; Page < Pages; Page++) - { - if (Page > 0) + e = PMSessionUseSheets(ps, Wnd ? Wnd->WindowHandle() : NULL, NULL /*PMSheetDoneUPP sheetDoneProc*/); + ErrCheck("PMSessionUseSheets"); + #endif + + e = PMSessionPrintDialog(ps, PrintSettings, PageFmt, &Accepted); + ErrCheck("PMSessionPrintDialog"); + + e = PMSessionValidatePrintSettings(ps, PrintSettings, &Changed); + e = PMSessionValidatePageFormat(ps, PageFmt, &Changed); + + e = PMSessionBeginCGDocumentNoDialog(ps, PrintSettings, PageFmt); + ErrCheck("PMSessionBeginCGDocumentNoDialog"); + + e = PMSessionBeginPageNoDialog(ps, PageFmt, NULL); + ErrCheck("PMSessionBeginPageNoDialog"); + + e = PMGetAdjustedPaperRect(PageFmt, &Params.Page); //PMGetUnadjustedPageRect + ErrCheck("PMGetAdjustedPaperRect"); + + e = PMSessionGetCGGraphicsContext(ps, &Params.Ctx); + ErrCheck("PMSessionGetCGGraphicsContext"); + + e = PMSessionGetCurrentPrinter(ps, &CurrentPrinter); + ErrCheck("PMSessionGetCurrentPrinter"); + + e = PMGetPageFormatPaper(PageFmt, &Paper); + e = PMPaperGetWidth(Paper, &paperWidth); + e = PMPaperGetHeight(Paper, &paperHeight); + + e = PMPrinterGetPrinterResolutionCount(CurrentPrinter, &ResCount); + ErrCheck("PMPrinterGetPrinterResolutionCount"); + + for (unsigned i=0; iOnPrintPage(dc, Page); - PMSessionEndPage(ps); - } - - e = PMSessionEndDocumentNoDialog(ps); - ErrCheck("PMSessionEndDocumentNoDialog"); - - return Status; - -OnError: - PMRelease(PrintSettings); - PMRelease(ps); + e = PMPrinterSetOutputResolution(CurrentPrinter, PrintSettings, &Params.Dpi); + ErrCheck("PMPrinterSetOutputResolution"); + + dc.Reset(new GPrintDC(&Params, PrintJobName)); + Pages = Events->OnBeginPrint(dc); + for (int Page = 0; Page < Pages; Page++) + { + if (Page > 0) + { + e = PMSessionBeginPage(ps, PageFmt, NULL); + ErrCheck("PMSessionBeginPage"); + + e = PMSessionGetCGGraphicsContext(ps, &Params.Ctx); + ErrCheck("PMSessionGetCGGraphicsContext"); + + dc.Reset(new GPrintDC(&Params, PrintJobName)); + } + + Status |= Events->OnPrintPage(dc, Page); + PMSessionEndPage(ps); + } + + e = PMSessionEndDocumentNoDialog(ps); + ErrCheck("PMSessionEndDocumentNoDialog"); + + return Status; + + OnError: + PMRelease(PrintSettings); + PMRelease(ps); + #endif - return Status; + if (callback) + callback(Status); } diff --git a/src/mac/cocoa/Widgets.mm b/src/mac/cocoa/Widgets.mm --- a/src/mac/cocoa/Widgets.mm +++ b/src/mac/cocoa/Widgets.mm @@ -1,249 +1,259 @@ /*hdr ** FILE: GWidgets.cpp ** AUTHOR: Matthew Allen ** DATE: 30/12/2006 ** DESCRIPTION: Mac dialog components ** ** Copyright (C) 2006 Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Slider.h" #include "lgi/common/Bitmap.h" #include "lgi/common/TableLayout.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Button.h" /////////////////////////////////////////////////////////////////////////////////////////// #define GreyBackground() struct LDialogPriv { bool IsModal; int ModalStatus; int BtnId; LDialogPriv() { IsModal = false; ModalStatus = -1; BtnId = -1; } }; /////////////////////////////////////////////////////////////////////////////////////////// -LDialog::LDialog() +LDialog::LDialog(LViewI *Parent) : ResObject(Res_Dialog) { d = new LDialogPriv; Name("Dialog"); SetDeleteOnClose(false); + + if (Parent) + SetParent(Parent); } LDialog::~LDialog() { DeleteObj(d); } int LDialog::GetButtonId() { return d->BtnId; } int LDialog::OnNotify(LViewI *Ctrl, LNotification n) { auto b = dynamic_cast(Ctrl); if (b) { d->BtnId = b->GetId(); if (d->IsModal) EndModal(d->BtnId); else EndModeless(d->BtnId); } return 0; } bool LDialog::IsModal() { return d->IsModal; } void LDialog::Quit(bool DontDelete) { if (d->IsModal) EndModal(0); else EndModeless(0); } void LDialog::OnPosChange() { if (Children.Length() == 1) { auto it = Children.begin(); auto t = dynamic_cast((LViewI*)it); if (t) { auto r = GetClient(); r.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(r); } } } bool LDialog::LoadFromResource(int Resource, char *TagList) { LAutoString n; LRect p; bool Status = LResourceLoad::LoadFromResource(Resource, this, &p, &n, TagList); if (Status) { Name(n); SetPos(p); } return Status; } bool LDialog::OnRequestClose(bool OsClose) { if (d->IsModal) { EndModal(0); return false; } return true; } -int LDialog::DoModal(OsView OverideParent) +void LDialog::DoModal(OnClose Callback, OsView OverideParent) { d->ModalStatus = 0; if (Wnd && Attach(0)) { // LAutoPool Pool; LWindow *Owner = GetParent() ? GetParent()->GetWindow() : 0; if (Owner) { auto Pr = Owner->GetPos(); auto Mr = GetPos(); Mr.Offset( Pr.x1 + (Pr.X() - Mr.X()) / 2 - Mr.x1, Pr.y1 + (Pr.Y() - Mr.Y()) / 2 - Mr.y1); SetPos(Mr); Owner->SetChildDialog(this); } d->IsModal = true; AttachChildren(); Visible(true); auto app = LAppInst->Handle(); auto wnd = WindowHandle(); [app runModalForWindow:wnd]; if (Owner) Owner->SetChildDialog(NULL); LWindow::Visible(false); } - return d->ModalStatus; + if (Callback) + { + Callback(this, d->ModalStatus); + } + else + { + delete this; + } } void LDialog::EndModal(int Code) { if (d->IsModal) { // LAutoPool Pool; d->IsModal = false; d->ModalStatus = Code; NSApplication *app = LAppInst->Handle(); [app stopModal]; } else { LAssert(0); } } int LDialog::DoModeless() { d->IsModal = false; if (Attach(0)) { AttachChildren(); Visible(true); } return 0; } void LDialog::EndModeless(int Code) { LWindow::Quit(Code); } extern LButton *FindDefault(LView *w); LMessage::Result LDialog::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_CLOSE: { printf("M_CLOSE received... Fixme!\n"); break; } } return LView::OnEvent(Msg); } /////////////////////////////////////////////////////////////////////////////////////////// LControl::LControl(OsView view) : LView(view) { Pos.ZOff(10, 10); } LControl::~LControl() { } LMessage::Result LControl::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { } return 0; } LPoint LControl::SizeOfStr(const char *Str) { auto Fnt = GetFont(); int y = Fnt->GetHeight(); LPoint Pt(0, 0); if (Str) { const char *e = 0; for (const char *s = Str; s && *s; s = e?e+1:0) { e = strchr(s, '\n'); size_t Len = e ? e-s : strlen(s); LDisplayString ds(Fnt, s, Len); Pt.x = MAX(Pt.x, ds.X()); Pt.y += y; } } return Pt; } diff --git a/src/mac/cocoa/Window.mm b/src/mac/cocoa/Window.mm --- a/src/mac/cocoa/Window.mm +++ b/src/mac/cocoa/Window.mm @@ -1,1424 +1,1424 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Popup.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "LCocoaView.h" extern void NextTabStop(LViewI *v, int dir); extern void SetDefaultFocus(LViewI *v); extern void BuildTabStops(LArray &Stops, LViewI *v); #define DEBUG_KEYS 0 #define DEBUG_SETFOCUS 0 #define DEBUG_LOGGING 0 #if DEBUG_LOGGING #define LOG(...) printf(__VA_ARGS__) #else #define LOG(...) #endif /* Deleting a LWindow senarios: Users clicks close: NSWindowDelegate::windowWillClose GWindowPrivate::OnClose(CloseUser) LNsWindow::onDelete Something deletes the LWindow programmatically: LWindow::~LWindow GWindowPriv::OnClose(CloseDestructor) LNsWindow::onDelete self.close windowWillClose -> block Something calls LWindow::Quit() LNsWindow::onQuit (async) self.close NSWindowDelegate::windowWillClose GWindowPrivate::OnClose(CloseUser) LNsWindow::onDelete */ #if DEBUG_SETFOCUS || DEBUG_KEYS static GString DescribeView(GViewI *v) { if (!v) return GString(); char s[512]; int ch = 0; GArray p; for (GViewI *i = v; i; i = i->GetParent()) { p.Add(i); } for (int n=MIN(3, (int)p.Length()-1); n>=0; n--) { char Buf[256] = ""; if (!stricmp(v->GetClass(), "GMdiChild")) sprintf(Buf, "'%s'", v->Name()); v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, "%s>%s", Buf, v->GetClass()); } return s; } #endif LRect LScreenFlip(LRect r) { LRect screen(0, 0, -1, -1); for (NSScreen *s in [NSScreen screens]) { LRect pos = s.frame; if (r.Overlap(&pos)) { screen = pos; break; } } if (screen.Valid()) { LRect rc = r; rc.Offset(0, (screen.Y() - r.y1 - r.Y()) - r.y1); // printf("%s:%i - Flip %s -> %s (%s)\n", _FL, r.GetStr(), rc.GetStr(), screen.GetStr()); return rc; } else { // printf("%s:%i - No Screen?\n", _FL); r.ZOff(-1, -1); } return r; } /////////////////////////////////////////////////////////////////////// class HookInfo { public: int Flags; LView *Target; }; @interface LWindowDelegate : NSObject { } - (id)init; - (void)dealloc; - (void)windowDidResize:(NSNotification*)aNotification; - (void)windowDidMove:(NSNotification*)aNotification; - (void)windowWillClose:(NSNotification*)aNotification; - (BOOL)windowShouldClose:(id)sender; - (void)windowDidBecomeMain:(NSNotification*)notification; - (void)windowDidResignMain:(NSNotification*)notification; @end LWindowDelegate *Delegate = nil; class LWindowPrivate { public: LWindow *Wnd; LDialog *ChildDlg; LMenu *EmptyMenu; LViewI *Focus; NSView *ContentCache; int Sx, Sy; LKey LastKey; LArray Hooks; uint64 LastMinimize; uint64 LastDragDrop; bool DeleteOnClose; bool SnapToEdge; bool InitVisible; LWindowPrivate(LWindow *wnd) { ContentCache = NULL; Focus = NULL; InitVisible = false; LastMinimize = 0; Wnd = wnd; LastDragDrop = 0; DeleteOnClose = true; ChildDlg = 0; Sx = Sy = -1; SnapToEdge = false; EmptyMenu = 0; } ~LWindowPrivate() { DeleteObj(EmptyMenu); } void OnClose(LCloseContext Ctx) { LOG("GWindowPrivate::OnClose %p/%s\n", Wnd, Wnd?Wnd->GetClass():NULL); auto &osw = Wnd->Wnd; if (!osw) return; LCocoaView *cv = objc_dynamic_cast(LCocoaView, osw.p.contentView); if (cv) cv.w = NULL; LNsWindow *w = objc_dynamic_cast(LNsWindow, osw.p); if (w) [w onDelete:Ctx]; osw.p.delegate = nil; [osw.p autorelease]; osw = nil; if (DeleteOnClose) delete Wnd; } ssize_t GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = 0; return Hooks.Length() - 1; } } return -1; } void OnResize() { NSWindow *wnd = Wnd->WindowHandle().p; Wnd->Pos = wnd.frame; Wnd->OnPosChange(); wnd.contentView.needsLayout = YES; } }; @implementation LNsWindow - (id)init:(LWindowPrivate*)priv Frame:(NSRect)rc { NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; if ((self = [super initWithContentRect:rc styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO ]) != nil) { self.d = priv; self->ReqClose = CSNone; self.contentView = [[LCocoaView alloc] init:priv->Wnd]; [self makeFirstResponder:self.contentView]; self.acceptsMouseMovedEvents = true; self.ignoresMouseEvents = false; // printf("LNsWindow.init\n"); } return self; } - (void)dealloc { if (self.d) self.d->Wnd->OnDealloc(); LCocoaView *cv = objc_dynamic_cast(LCocoaView, self.contentView); cv.w = NULL; [cv release]; self.contentView = NULL; [super dealloc]; // printf("LNsWindow.dealloc.\n"); } - (LWindow*)getWindow { return self.d ? self.d->Wnd : nil; } - (BOOL)canBecomeKeyWindow { return YES; } - (void)onQuit { #if DEBUG_LOGGING LWindow *wnd = self.d ? self.d->Wnd : NULL; auto cls = wnd ? wnd->GetClass() : NULL; #endif LOG("LNsWindow::onQuit %p/%s %i\n", wnd, cls, self->ReqClose); if (self->ReqClose == CSNone) { self->ReqClose = CSInRequest; if (!self.d) LOG("%s:%i - No priv pointer?\n", _FL); if (!self.d || !self.d->Wnd || !self.d->Wnd->OnRequestClose(false)) { LOG(" ::onQuit %p/%s no 'd' or OnReqClose failed\n", wnd, cls); self->ReqClose = CSNone; return; } } else return; LOG(" ::onQuit %p/%s self.close\n", wnd, cls); self->ReqClose = CSClosed; self.d->Wnd->SetPulse(); [self close]; } - (void)onDelete:(LCloseContext)ctx { LOG("LNsWindow::onDelete %p/%s\n", self.d->Wnd, self.d->Wnd->GetClass()); if (ctx == CloseDestructor && self->ReqClose != CSClosed) { // This is called during the ~LWindow destructor to make sure we // closed the window self->ReqClose = CSClosed; LOG(" ::onDelete %p self.close\n", self.d->Wnd); [self close]; } self.d = NULL; } @end @implementation LWindowDelegate - (id)init { if ((self = [super init]) != nil) { } return self; } - (void)dealloc { [super dealloc]; } - (void)windowDidResize:(NSNotification*)event { LNsWindow *w = event.object; if (w && w.d) w.d->OnResize(); } - (void)windowDidMove:(NSNotification*)event { // LNsWindow *w = event.object; // GRect r = LScreenFlip(w.frame); // printf("windowDidMove: %s\n", r.GetStr()); } - (BOOL)windowShouldClose:(NSWindow*)sender { LNsWindow *w = objc_dynamic_cast(LNsWindow, sender); if (w && w.d && w.d->Wnd) return w.d->Wnd->OnRequestClose(false); return YES; } - (void)windowWillClose:(NSNotification*)event { LNsWindow *w = event.object; if (w && w.d) w.d->OnClose(CloseUser); } - (void)windowDidBecomeMain:(NSNotification*)event { LNsWindow *w = event.object; if (w && w.d) w.d->Wnd->OnFrontSwitch(true); } - (void)windowDidResignMain:(NSNotification*)event { LNsWindow *w = event.object; if (w && w.d) w.d->Wnd->OnFrontSwitch(false); } @end /////////////////////////////////////////////////////////////////////// #define GWND_CREATE 0x0010000 #if __has_feature(objc_arc) #error "NO ARC!" #endif LWindow::LWindow(OsWindow wnd) : LView(NULL) { d = new LWindowPrivate(this); _QuitOnClose = false; Wnd = NULL; Menu = 0; _Default = 0; _Window = this; WndFlags |= GWND_CREATE; LView::Visible(false); _Lock = new LMutex("LWindow"); LRect pos(200, 200, 200, 200); NSRect frame = pos; if (wnd) Wnd = wnd; else Wnd.p = [[LNsWindow alloc] init:d Frame:frame]; if (Wnd) { [Wnd.p retain]; if (!Delegate) Delegate = [[LWindowDelegate alloc] init]; //[Wnd.p makeKeyAndOrderFront:NSApp]; Wnd.p.delegate = Delegate; d->ContentCache = Wnd.p.contentView; } } LWindow::~LWindow() { LOG("LWindow::~LWindow %p\n", this); if (LAppInst->AppWnd == this) LAppInst->AppWnd = 0; _Delete(); d->DeleteOnClose = false; // We're already in the destructor, don't redelete. d->OnClose(CloseDestructor); DeleteObj(Menu); DeleteObj(d); DeleteObj(_Lock); } NSView *LWindow::Handle() { if (!InThread()) return d->ContentCache; if (Wnd.p != nil) return Wnd.p.contentView; return NULL; } bool LWindow::SetIcon(const char *FileName) { return false; } LViewI *LWindow::GetFocus() { return d->Focus; } void LWindow::SetFocus(LViewI *ctrl, FocusType type) { const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } switch (type) { case GainFocus: { // Check if the control already has focus if (d->Focus == ctrl) return; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); d->Focus->Invalidate(); #if DEBUG_SETFOCUS auto _foc = DescribeView(d->Focus); LgiTrace(".....defocus: %s\n", _foc.Get()); #endif } d->Focus = ctrl; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags |= GWF_FOCUS; d->Focus->OnFocus(true); d->Focus->Invalidate(); #if DEBUG_SETFOCUS auto _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) focusing\n", _set.Get(), TypeName); #endif } break; } case LoseFocus: { if (ctrl == d->Focus) { LView *v = d->Focus->GetGView(); if (v) { if (v->WndFlags & GWF_FOCUS) { // View thinks it has focus v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); // keep d->Focus pointer, as we want to be able to re-focus the child // view when we get focus again #if DEBUG_SETFOCUS auto _ctrl = DescribeView(ctrl); auto _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) keep_focus: %s\n", _ctrl.Get(), TypeName, _foc.Get()); #endif } // else view doesn't think it has focus anyway... } else { // Non GView handler d->Focus->OnFocus(false); d->Focus->Invalidate(); d->Focus = NULL; } } else { /* LgiTrace("LWindow::SetFocus(%p.%s, %s) error on losefocus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); */ } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LgiTrace("LWindow::SetFocus(%p.%s, %s) delete_focus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); #endif d->Focus = NULL; } break; } } } void LWindow::SetDragHandlers(bool On) { #if 0 if (Wnd && _View) SetAutomaticControlDragTrackingEnabledForWindow(Wnd, On); #endif } void LWindow::Quit(bool DontDelete) { // LAutoPool Pool; if (_QuitOnClose) { _QuitOnClose = false; LCloseApp(); } if (Wnd) SetDragHandlers(false); if (d && DontDelete) { // If DontDelete is true, we should be already in the destructor of the LWindow. // Which means we DON'T call onQuit, as it's too late to ask the user if they don't // want to close the window. The window IS closed come what may, and the object is // going away. Futhermore we can't access the window's memory after it's deleted and // that may happen if the onQuit is processed after ~LWindow. d->DeleteOnClose = false; if (Wnd) [Wnd.p close]; } else if (Wnd) { [Wnd.p performSelectorOnMainThread:@selector(onQuit) withObject:nil waitUntilDone:false]; } } void LWindow::SetChildDialog(LDialog *Dlg) { d->ChildDlg = Dlg; } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool s) { d->SnapToEdge = s; } void LWindow::OnFrontSwitch(bool b) { if (b && Menu) { [NSApplication sharedApplication].mainMenu = Menu->Handle().p; } else { auto m = LAppInst->Default.Get(); [NSApplication sharedApplication].mainMenu = m ? m->Handle() : nil; } // printf("%s:%i - menu for %s is %p\n", _FL, Name(), [NSApplication sharedApplication].mainMenu); } bool LWindow::Visible() { // LAutoPool Pool; if (!Wnd) return false; return [Wnd.p isVisible]; } void LWindow::Visible(bool i) { // LAutoPool Pool; if (!Wnd) return; if (i) { d->InitVisible = true; PourAll(); [Wnd.p makeKeyAndOrderFront:NULL]; [NSApp activateIgnoringOtherApps:YES]; SetDefaultFocus(this); OnPosChange(); } else { [Wnd.p orderOut:Wnd.p]; } } bool LWindow::IsActive() { return Wnd ? [Wnd.p isKeyWindow] : false; } bool LWindow::SetActive() { [[NSApplication sharedApplication] activateIgnoringOtherApps : YES]; return false; } void LWindow::SetDeleteOnClose(bool i) { d->DeleteOnClose = i; } void LWindow::SetAlwaysOnTop(bool b) { } -bool LWindow::PostEvent(int Event, LMessage::Param a, LMessage::Param b) +bool LWindow::PostEvent(int Event, LMessage::Param a, LMessage::Param b, int64_t TimeoutMs) { return LAppInst->PostEvent(this, Event, a, b); } bool LWindow::Attach(LViewI *p) { bool Status = false; if (Wnd) { if (LBase::Name()) Name(LBase::Name()); Status = true; // Setup default button... if (!_Default) { _Default = FindControl(IDOK); if (_Default) _Default->Invalidate(); } OnCreate(); OnAttach(); OnPosChange(); // Set the first control as the focus... NextTabStop(this, 0); } return Status; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return LView::OnRequestClose(OsShuttingDown); } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { if (m.Down()) { bool ParentPopup = false; LViewI *p = m.Target; while (p && p->GetParent()) { if (dynamic_cast(p)) { ParentPopup = true; break; } p = p->GetParent(); } if (!ParentPopup) { for (int i=0; iVisible()) { // printf("Hiding popup %s\n", pu->GetClass()); pu->Visible(false); } } } if (!m.IsMove() && LAppInst) { auto mh = LAppInst->GetMouseHook(); if (mh) mh->TrackClick(v); } } for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { if (!d->Hooks[i].Target->OnViewMouse(v, m)) { return false; } } } return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { bool Status = false; LViewI *Ctrl = NULL; if (!v && d->Focus) v = d->Focus->GetGView(); if (!v) { #if DEBUG_KEYS k.Trace("No focus view to handle key."); #endif return false; } // Give key to popups if (LAppInst && LAppInst->GetMouseHook() && LAppInst->GetMouseHook()->OnViewKey(v, k)) { goto AllDone; } // Allow any hooks to see the key... for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LKeyEvents) { if (d->Hooks[i].Target->OnViewKey(v, k)) { Status = true; #if DEBUG_KEYS printf("Hook ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", k.c16, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } } } // Give the key to the window... if (v->OnKey(k)) { #if DEBUG_KEYS GString vv = DescribeView(v); printf("%s ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", vv.Get(), k.c16, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif Status = true; goto AllDone; } // Window didn't want the key... switch (k.vkey) { case LK_RETURN: case LK_KEYPADENTER: { Ctrl = _Default; break; } case LK_ESCAPE: { Ctrl = FindControl(IDCANCEL); break; } case LK_TAB: { // Go to the next control? if (k.Down()) { LArray Stops; BuildTabStops(Stops, v->GetWindow()); ssize_t Idx = Stops.IndexOf(v); if (Idx >= 0) { if (k.Shift()) { Idx--; if (Idx < 0) Idx = Stops.Length() - 1; } else { Idx++; if (Idx >= Stops.Length()) Idx = 0; } Stops[Idx]->Focus(true); } } return true; } } if (Ctrl && Ctrl->Enabled()) { if (Ctrl->OnKey(k)) { Status = true; #if DEBUG_KEYS printf("Default Button ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", k.c16, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } } if (Menu) { Status = Menu->OnKey(v, k); if (Status) { #if DEBUG_KEYS printf("Menu ate '%c' down=%i alt=%i ctrl=%i sh=%i\n", k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif } } // Command+W closes the window... if it doesn't get nabbed earlier. if (k.Down() && k.System() && tolower(k.c16) == 'w') { // Close Quit(); return true; } AllDone: if (d) d->LastKey = k; else LAssert(!"Window was deleted and we are accessing unallocated mem."); return Status; } void LWindow::Raise() { if (Wnd) { // BringToFront(Wnd); } } LWindowZoom LWindow::GetZoom() { if (Wnd) { #if 0 bool c = IsWindowCollapsed(Wnd); // printf("IsWindowCollapsed=%i\n", c); if (c) return LZoomMin; c = IsWindowInStandardState(Wnd, NULL, NULL); // printf("IsWindowInStandardState=%i\n", c); if (!c) return LZoomMax; #endif } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { #if 0 OSStatus e = 0; switch (i) { case LZoomMin: { e = CollapseWindow(Wnd, true); if (e) printf("%s:%i - CollapseWindow failed with %i\n", _FL, (int)e); // else printf("LZoomMin ok.\n"); break; } default: case LZoomNormal: { e = CollapseWindow(Wnd, false); if (e) printf("%s:%i - [Un]CollapseWindow failed with %i\n", _FL, (int)e); // else printf("LZoomNormal ok.\n"); break; } } #endif } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { if (v && v->GetWindow() == (LViewI*)this) { if (_Default != v) { auto Old = _Default; _Default = v; if (Old) Old->Invalidate(); if (_Default) _Default->Invalidate(); } } else { _Default = 0; } } bool LWindow::Name(const char *n) { // LAutoPool Pool; bool Status = LBase::Name(n); if (Wnd) { NSString *ns = [NSString stringWithCString:n encoding:NSUTF8StringEncoding]; Wnd.p.title = ns; //[ns release]; } return Status; } const char *LWindow::Name() { return LBase::Name(); } LRect &LWindow::GetClient(bool ClientSpace) { // LAutoPool Pool; static LRect r; if (Wnd) { r = Wnd.p.contentView.frame; if (ClientSpace) r.Offset(-r.x1, -r.y1); } else { r.ZOff(-1, -1); } return r; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; if (Load) { LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; auto t = LString(v.Str()).SplitDelimit(";"); for (auto s: t) { auto v = s.SplitDelimit("=", 1); if (v.Length() == 2) { if (v[0].Equals("State")) State = (LWindowZoom)v[1].Int(); else if (v[0].Equals("Pos")) Position.SetStr(v[1]); } else return false; } if (Position.Valid()) { if (Position.X() < 64) Position.x2 = Position.x1 + 63; if (Position.Y() < 64) Position.y2 = Position.y1 + 63; SetPos(Position); } SetZoom(State); } else return false; } else { char s[256]; LWindowZoom State = GetZoom(); sprintf(s, "State=%i;Pos=%s", State, GetPos().GetStr()); LVariant v = s; if (!Store->SetValue(FieldName, v)) return false; } return true; } LPoint LWindow::GetDpi() { return LScreenDpi(); } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); return LPointF(Dpi.x / 100.0, Dpi.y / 100.0); } LRect &LWindow::GetPos() { // LAutoPool Pool; if (Wnd) { Pos = LScreenFlip(Wnd.p.frame); // printf("%s::GetPos %s\n", GetClass(), Pos.GetStr()); } return Pos; } bool LWindow::SetPos(LRect &p, bool Repaint) { // LAutoPool Pool; Pos = p; if (Wnd) { LRect r = LScreenFlip(p); [Wnd.p setFrame:r display:YES]; // printf("%s::SetPos %s\n", GetClass(), Pos.GetStr()); } return true; } void LWindow::OnChildrenChanged(LViewI *Wnd, bool Attaching) { if (dynamic_cast(Wnd)) { printf("%s:%i - Ignoring GPopup in OnChildrenChanged handler.\n", _FL); return; } PourAll(); } void LWindow::OnCreate() { } void LWindow::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } void LWindow::OnPosChange() { LView::OnPosChange(); if (d->Sx != X() || d->Sy != Y()) { PourAll(); d->Sx = X(); d->Sy = Y(); } } #define IsTool(v) \ ( \ dynamic_cast(v) \ && \ dynamic_cast(v)->_IsToolBar \ ) void LWindow::PourAll() { LRect r = GetClient(); // printf("::Pour r=%s\n", r.GetStr()); LRegion Client(r); LRegion Update(Client); bool HasTools = false; LViewI *v; List::I Lst = Children.begin(); { LRegion Tools; for (v = *Lst; v; v = *++Lst) { if (IsTool(v)) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (HasTools) { // 2nd and later toolbars if (v->Pour(Tools)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Tools.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } } else { // First toolbar if (v->Pour(Client)) { HasTools = true; if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { v->Invalidate(); } LRect Bar(v->GetPos()); Bar.x2 = GetClient().x2; Tools = Bar; Tools.Subtract(&v->GetPos()); Client.Subtract(&Bar); Update.Subtract(&Bar); } } } } } Lst = Children.begin(); for (LViewI *v = *Lst; v; v = *++Lst) { if (!IsTool(v)) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } v->Invalidate(); Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // non-pourable } } } for (int i=0; iMsg()) { case M_CLOSE: { if (Wnd) [Wnd.p performSelectorOnMainThread:@selector(onQuit) withObject:nil waitUntilDone:false]; else LAssert(!"No window?"); break; } case M_DESTROY: { delete this; return true; } } return LView::OnEvent(m); } bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority) { bool Status = false; if (Target && EventType) { ssize_t i = d->GetHookIndex(Target, true); if (i >= 0) { d->Hooks[i].Flags = EventType; Status = true; } } return Status; } bool LWindow::UnregisterHook(LView *Target) { ssize_t i = d->GetHookIndex(Target); if (i >= 0) { d->Hooks.DeleteAt(i); return true; } return false; } LViewI *LWindow::WindowFromPoint(int x, int y, int DebugDepth) { for (int i=0; iVisible()) { auto r = p->GetPos(); if (r.Overlap(x, y)) { // printf("WindowFromPoint got %s click (%i,%i)\n", p->GetClass(), x, y); return p->WindowFromPoint(x - r.x1, y - r.y1, DebugDepth ? DebugDepth + 1 : 0); } } } return LView::WindowFromPoint(x, y, DebugDepth ? DebugDepth + 1 : 0); } int LWindow::OnCommand(int Cmd, int Event, OsView SrcCtrl) { #if 0 OsView v; switch (Cmd) { case kHICommandCut: { OSErr e = GetKeyboardFocus(Wnd, (ControlRef*) &v); if (!e) LgiPostEvent(v, M_CUT); break; } case kHICommandCopy: { OSErr e = GetKeyboardFocus(Wnd, (ControlRef*) &v); if (!e) LgiPostEvent(v, M_COPY); break; } case kHICommandPaste: { OSErr e = GetKeyboardFocus(Wnd, (ControlRef*) &v); if (!e) LgiPostEvent(v, M_PASTE); break; } case 'dele': { OSErr e = GetKeyboardFocus(Wnd, (ControlRef*) &v); if (!e) LgiPostEvent(v, M_DELETE); break; } } #endif return 0; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m.x, m.y); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } } bool LWindow::Obscured() { // LAutoPool Pool; if (!Wnd) return false; auto s = [Wnd.p occlusionState]; return !(s & NSWindowOcclusionStateVisible); }