diff --git a/include/lgi/common/View.h b/include/lgi/common/View.h --- a/include/lgi/common/View.h +++ b/include/lgi/common/View.h @@ -1,1009 +1,1011 @@ #pragma once #if defined(LGI_CARBON) LgiFunc void DumpHnd(HIViewRef v, int depth = 0); #elif LGI_COCOA && defined(__OBJC__) #include "LCocoaView.h" #endif /// \brief The base class for all windows in the GUI. /// /// This is the core object that all on screen windows inherit from. It encapsulates /// a HWND on Win32, a GtkWidget on Linux, and a NSView for Mac OS X. Used by /// itself it's not a top level window, for that see the LWindow class. /// /// To create a top level window see LWindow or LDialog. /// /// For a LView with scroll bars use LLayout. /// class LgiClass LView : virtual public LViewI, virtual public LBase { friend class LWindow; friend class LLayout; friend class LControl; friend class LMenu; friend class LSubMenu; friend class LScrollBar; friend class LDialog; friend class LDragDropTarget; friend class LPopup; friend class LWindowPrivate; friend class LViewPrivate; friend bool SysOnKey(LView *w, LMessage *m); #if defined(__GTK_H__) friend Gtk::gboolean lgi_widget_draw(Gtk::GtkWidget *widget, Gtk::cairo_t *cr); friend Gtk::gboolean lgi_widget_click(Gtk::GtkWidget *widget, Gtk::GdkEventButton *ev); friend Gtk::gboolean lgi_widget_motion(Gtk::GtkWidget *widget, Gtk::GdkEventMotion *ev); friend Gtk::gboolean GViewCallback(Gtk::GtkWidget *widget, Gtk::GdkEvent *event, LView *view); friend Gtk::gboolean PopupEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event, class LPopup *This); friend Gtk::gboolean GtkViewCallback(Gtk::GtkWidget *widget, Gtk::GdkEvent *event, LView *This); virtual Gtk::gboolean OnGtkEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event); public: virtual void OnGtkRealize(); virtual void OnGtkDelete(); private: #endif #if defined WIN32 friend class LWindowsClass; friend class LCombo; friend LRESULT CALLBACK DlgRedir(OsView hWnd, UINT m, WPARAM a, LPARAM b); static void CALLBACK TimerProc(OsView hwnd, UINT uMsg, UINT_PTR idEvent, uint32_t dwTime); #elif defined MAC #if LGI_COCOA #elif LGI_CARBON friend OSStatus LgiWindowProc(EventHandlerCallRef, EventRef, void *); friend OSStatus LgiRootCtrlProc(EventHandlerCallRef, EventRef, void *); friend OSStatus CarbonControlProc(EventHandlerCallRef, EventRef, void *); friend OSStatus GViewProc(EventHandlerCallRef, EventRef, void *); friend OSStatus LgiViewDndHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); #endif #endif #if defined(HAIKU) || defined(MAC) template friend class LBView; static bool RecentlyDeleted(LViewI *v); #endif #if defined(LGI_SDL) friend Uint32 SDL_PulseCallback(Uint32 interval, LView *v); friend class LApp; #endif LRect Pos; int _InLock = 0; OsThreadId _LockingThread = -1; protected: class LViewPrivate *d = NULL; #if LGI_VIEW_HANDLE && !defined(HAIKU) OsView _View; // OS specific handle to view object #endif LView *_Window = NULL; #ifndef HAIKU LMutex *_Lock = NULL; #endif uint16 _BorderSize = 0; uint16 _IsToolBar = 0; int WndFlags = 0; static LViewI *_Capturing; static LViewI *_Over; #ifndef LGI_VIEW_HASH #error "Define LGI_VIEW_HASH to 0 or 1" #endif #if LGI_VIEW_HASH public: enum LockOp { OpCreate, OpDelete, OpExists, }; static bool LockHandler(LViewI *v, LockOp Op); #endif protected: #if defined WINNATIVE uint32_t GetStyle(); void SetStyle(uint32_t i); uint32_t GetExStyle(); void SetExStyle(uint32_t i); uint32_t GetDlgCode(); void SetDlgCode(uint32_t i); /// \brief Gets the win32 class passed to CreateWindowEx() const char *GetClassW32(); /// \brief Sets the win32 class passed to CreateWindowEx() void SetClassW32(const char *s); /// \brief Creates a class to pass to CreateWindowEx(). If this methed is not /// explicitly called then the string from GetClass() is used to create a class, /// which is usually the name of the object. LWindowsClass *CreateClassW32(const char *Class = NULL, HICON Icon = 0, int AddStyles = 0); virtual int SysOnNotify(int Msg, int Code) { return 0; } #elif defined MAC bool _Attach(LViewI *parent); #if LGI_COCOA public: LPoint Flip(LPoint p); LRect Flip(LRect p); virtual void OnDealloc(); #elif LGI_CARBON OsView _CreateCustomView(); virtual bool _OnGetInfo(HISize &size, HISize &line, HIRect &bounds, HIPoint &origin) { return false; } virtual void _OnScroll(HIPoint &origin) {} #endif #endif #if !WINNATIVE LView *&PopupChild(); virtual bool _Mouse(LMouse &m, bool Move); void _Focus(bool f); #endif #if LGI_COCOA protected: #endif // Complex Region searches /// Finds the largest rectangle in the region LRect *FindLargest(LRegion &r); /// Finds the smallest rectangle that would fit a window 'Sx' by 'Sy' LRect *FindSmallestFit(LRegion &r, int Sx, int Sy); /// Finds the largest rectangle on the specified LRect *FindLargestEdge ( /// The region to search LRegion &r, /// The edge to look at: /// \sa GV_EDGE_TOP, GV_EDGE_RIGHT, GV_EDGE_BOTTOM or GV_EDGE_LEFT int Edge ); virtual void _Delete(); LViewI *FindReal(LPoint *Offset = 0); bool HandleCapture(LView *Wnd, bool c); virtual bool OnViewMouse(LView *v, LMouse &m) override { return true; } virtual bool OnViewKey(LView *v, LKey &k) override { return false; } virtual void OnNcPaint(LSurface *pDC, LRect &r); /// List of children views. List Children; #if defined(LGI_SDL) || defined(LGI_COCOA) public: #endif virtual void _Paint(LSurface *pDC = NULL, LPoint *Offset = NULL, LRect *Update = NULL); public: /// \brief Creates a view/window. /// /// On non-Win32 platforms the default argument is the class that redirects the /// C++ virtual event handlers to the LView handlers. Which is usually the /// 'DefaultOsView' class. If you pass NULL in a DefaultOsView will be created to /// do the job. LView ( /// The handle that the OS knows the window by OsView wnd = NULL ); /// Destructor virtual ~LView(); /// Returns the OS handle of the view #if defined(HAIKU) OsView Handle() const; static void HandleInThreadMessage(BMessage *Msg); #elif LGI_VIEW_HANDLE OsView Handle() const { return _View; } #endif /// Returns the ptr to a LView LView *GetGView() override { return this; } /// Returns the OS handle of the top level window OsWindow WindowHandle() override; // Attaching windows / heirarchy bool AddView(LViewI *v, int Where = -1) override; bool DelView(LViewI *v) override; bool HasView(LViewI *v) override; LArray IterateViews() override; /// \brief Attaches the view to a parent view. /// /// Each LView starts in an un-attached state. When you attach it to a Parent LView /// the view gains a OS-specific handle and becomes visible on the screen (if the /// Visible() property is TRUE). However if a view is inserted into the Children list /// of a LView and it's parent pointer is set correctly it will still paint on the /// screen without the OS knowing about it. This is known in Lgi as a "virtual window" /// and is primarily used to cut down on windowing resources. Mouse clicks are handled /// by the parent window and passed down to the virtual children. Virtual children /// are somewhat limited. They can't receive focus, or participate in drag and drop /// operations. If you want to see an example have a look at the LToolBar code. virtual bool Attach ( /// The parent view or NULL for a top level window LViewI *p ) override; /// Attachs all the views in the Children list if not already attached. virtual bool AttachChildren() override; /// Detachs a window from it's parent. virtual bool Detach() override; /// Returns true if the window is attached virtual bool IsAttached() override; /// Destroys the window async virtual void Quit(bool DontDelete = false) override; // Properties /// Gets the top level window that this view belongs to LWindow *GetWindow() override; /// Gets the parent view. LViewI *GetParent() override; /// \brief Sets the parent view. /// /// This doesn't attach the window so that it will display. You should use LView::Attach for that. virtual void SetParent(LViewI *p) override; /// Sends a notification to the notify target or the parent chain void SendNotify(LNotification n = LNotifyValueChanged) override; /// Gets the window that receives event notifications LViewI *GetNotify() override; /// \brief Sets the view to receive event notifications. /// /// The notify window will receive events when this view changes. By /// default the parent view receives the events. virtual void SetNotify(LViewI *n) override; /// \brief Each top level window (LWindow) has a lock. By calling this function /// you lock the whole LWindow and all it's children. bool Lock ( /// The file name of the caller const char *file, /// The line number of the caller int line, /// The timeout in milli-seconds or -1 to block until locked. int TimeOut = -1 ) override; /// Unlocks the LWindow and that this view belongs to. void Unlock() override; /// Add this view to the event target sink dispatch hash table. /// This allows you to use PostThreadEvent with a handle. Which /// is safe even if the object is deleted (unlike the PostEvent /// member function). /// /// Calling this multiple times only adds the view once, but it /// returns the same handle each time. /// The view is automatically removed from the dispatch on /// deletion. /// /// \returns the handle for PostThreadEvent. int AddDispatch() override; /// Called to process every message received by this window. /// This also calls CommonEvents which is for events common to all /// platforms. LMessage::Result OnEvent(LMessage *Msg) override; /// Implements handlers for events common to all platforms. bool CommonEvents(LMessage::Result &result, LMessage *Msg); /// true if the view is enabled bool Enabled() override; /// Sets the enabled state void Enabled(bool e) override; /// true if the view is visible bool Visible() override; /// Hides/Shows the view void Visible ( /// True if you want to show the view, False to hide the view/ bool v ) override; /// true if the view has keyboard focus bool Focus() override; /// Sets the keyboard focus state on the view. void Focus(bool f) override; /// Get/Set the drop source LDragDropSource *DropSource(LDragDropSource *Set = NULL) override; /// Get/Set the drop target LDragDropTarget *DropTarget(LDragDropTarget *Set = NULL) override; /// Sets the drop target state of this view bool DropTarget(bool t) override; /// \brief Gives this view a 1 or 2 px sunken border. /// /// The size is set by the _BorderSize member variable. This border is /// not considered part of the client area. Mouse and drawing coordinates /// do not take it into account. bool Sunken() override; /// Sets a sunken border around the control void Sunken(bool i) override; /// true if the view has a flat border bool Flat() override; /// Sets the flat border state void Flat(bool i) override; /// \brief true if the view has a raised border /// /// The size is set by the _BorderSize member variable. This border is /// not considered part of the client area. Mouse and drawing coordinates /// do not take it into account. bool Raised() override; /// Sets the raised border state void Raised(bool i) override; /// Draws an OS themed border void DrawThemeBorder(LSurface *pDC, LRect &r); /// \brief true if the control is currently executing in the GUI thread /// /// Some OS functions are not thread safe, and can only be called in the GUI /// thread. In the Linux implementation the GUI thread can change from time /// to time. On Win32 it stays the same. In any case if this function returns /// true it's safe to do just about anything. bool InThread() override; protected: class CallbackStore : public LMutex { LHashTbl, std::function*> map; public: CallbackStore() : LMutex("CallbackStore") { } int Add(std::function cb) { if (!LockWithTimeout(4000, _FL)) { printf(LPrintfInt64 ": CbStore.Add lock timed out...\n", (int64_t) LCurrentThreadId()); return -1; } // Find a free slot... int id = 0; for (; map.Find(id); id++) ; map.Add(id, new std::function(std::move(cb))); // printf("%i: CbStore.Add %i\n", LCurrentThreadId(), id); Unlock(); return id; } bool Call(int id) { if (!LockWithTimeout(4000, _FL)) { printf(LPrintfInt64 ": CbStore.Call(%i) lock timed out...\n", (int64_t) LCurrentThreadId(), id); return false; } bool status = false; auto cb = map.Find(id); if (cb) { (*cb)(); if (map.Delete(id)) { delete cb; status = true; // printf("%i: CbStore.Call %i\n", LCurrentThreadId(), id); } } Unlock(); return status; } bool Delete(int id) { if (!LockWithTimeout(4000, _FL)) { printf(LPrintfInt64 ": CbStore.Call(%i) lock timed out...\n", (int64_t) LCurrentThreadId(), id); return false; } bool status = false; auto cb = map.Find(id); if (cb && map.Delete(id)) { status = true; // printf("%i: CbStore.Delete %i\n", LCurrentThreadId(), id); delete cb; } Unlock(); return status; } CallbackStore &operator =(const CallbackStore &cs) { return *this; } } CbStore; public: /// Run some code in the UI thread... /// But don't wait for any sort of response. /// \returns true if the callback was sent (but not necessarily processed). bool RunCallback(std::function Callback) { if (!Callback) { LgiTrace("%s:%i - No callback.\n", _FL); return false; } int id = CbStore.Add(std::move(Callback)); return PostEvent(M_VIEW_RUN_CALLBACK, (LMessage::Param) id); } /// Run some code in the UI thread... /// This will block while waiting for the UI event loop to respond or a timeout /// to occur. Negative timeoutMs is "wait forever". /// /// This code needs to handle 5 cases: /// - There is a parameter error and the code returns without sending a message. /// - RunCallback sends a message to the view but it's not processed. (view deleted in the meantime?) /// - RunCallback sends a message, but the caller times out before it's processed. /// - RunCallback sends a message, but the cancel object is set before a response happens. /// - RunCallback sends a message and it's processed. /// /// The life time of the variables accessed by the M_VIEW_RUN_CALLBACK event handler can't be on the /// stack. If the timeout occurs and RunCallback exits before M_VIEW_RUN_CALLBACK is processed the /// stack variable will go out of scope and crash. /// /// So the variables are stored in the CallbackParams object on the heap. template LAutoPtr RunCallback(std::function Callback, int timeoutMs = -1, LCancel *cancel = NULL) { LAutoPtr result; if (!Callback) { LgiTrace("%s:%i - No callback.\n", _FL); return result; } int id = CbStore.Add([Callback, &result]() { result.Reset(new T(Callback())); }); printf(LPrintfThreadId ": post M_VIEW_RUN_CALLBACK for %i...\n", LCurrentThreadId(), id); if (!PostEvent(M_VIEW_RUN_CALLBACK, id)) { LgiTrace("%s:%i - RunCallback PostEvent failed.\n", _FL); return result; } auto StartTs = LCurrentTime(); auto Report = StartTs; while (!result) { auto Now = LCurrentTime(); if (Now - Report > 500) { Report = Now; printf(LPrintfThreadId ": Waiting for M_VIEW_RUN_CALLBACK %i\n", LCurrentThreadId(), (int)(Now-StartTs)); } if (timeoutMs >= 0 && (Now-StartTs) > timeoutMs) { printf(LPrintfThreadId ": RunCallback timed out.\n", LCurrentThreadId()); break; } if (cancel && cancel->IsCancelled()) { printf(LPrintfThreadId ": RunCallback cancelled.\n", LCurrentThreadId()); break; } LSleep(10); } printf(LPrintfThreadId ": RunCallback finished: %p\n", LCurrentThreadId(), result.Get()); CbStore.Delete(id); return result; } /// \brief Asyncronously posts an event to be received by this view virtual bool PostEvent ( /// The command ID. /// \sa Should be M_USER or higher for custom events. int Cmd, /// The first 32-bits of data. Equivalent to wParam on Win32. LMessage::Param a = 0, /// The second 32-bits of data. Equivalent to lParam on Win32. LMessage::Param b = 0, /// Optional timeout in milliseconds int64_t TimeoutMs = -1 ) override; template bool PostEvent(int Cmd, T *Ptr) { return PostEvent(Cmd, (LMessage::Param)Ptr, 0); } /// \brief Sets the utf-8 text associated with this view /// /// Name and NameW are interchangable. Using them in any order will convert the /// text between utf-8 and wide to satify any requirement. Generally once the opposing /// version of the string is required both the utf-8 and wide copies of the string /// remain cached in RAM until the Name is changed. bool Name(const char *n) override; /// Returns the utf-8 text associated with this view const char *Name() override; /// Sets the wide char text associated with this view bool NameW(const char16 *n) override; /// \brief Returns the wide char text associated with this view /// /// On Win32 the wide characters are 16 bits, on unix systems they are 32-bit /// characters. const char16 *NameW() override; /// \brief Gets the font this control should draw with. /// /// The default font is the system font, owned by the LApp object. virtual LFont *GetFont() override; /// \brief Sets the font for this control /// /// The lifetime of the font passed in is the responsibility of the caller. /// The LView object assumes the pointer will be valid at all times. virtual void SetFont(LFont *Fnt, bool OwnIt = false) override; /// Returns the cursor that should be displayed for the given location /// \returns a cursor type. i.e. LCUR_Normal from LgiDefs.h LCursor GetCursor(int x, int y) override; /// \brief Get the position of the view relitive to it's parent. virtual LRect &GetPos() override { return Pos; } /// Get the client region of the window relitive to itself (ie always 0,0-x,y) virtual LRect &GetClient(bool InClientSpace = true) override; /// Set the position of the view in terms of it's parent virtual bool SetPos(LRect &p, bool Repaint = false) override; /// Gets the width of the view in pixels int X() override { return Pos.X(); } /// Gets the height of the view in pixels. int Y() override { return Pos.Y(); } /// Gets the minimum size of the view LPoint GetMinimumSize() override; /// \brief Set the minimum size of the view. /// /// Only works for top level windows. void SetMinimumSize(LPoint Size) override; /// Gets the style of the control class LCss *GetCss(bool Create = false) override; /// Resolve a CSS colour, e.g.: /// auto Back = StyleColour(LCss::PropBackgroundColor, LColour(L_MED)); LColour StyleColour(int CssPropType, LColour Default, int Depth = 5); /// Sets the style of the control (will take ownership of 'css') void SetCss(LCss *css) override; /// Sets the CSS foreground or background colour bool SetColour(LColour &c, bool Fore) override; /// The class' name. Should be overriden in child classes to return the /// right class name. Mostly used for debugging, but in the win32 port it /// is also the default WIN32 class name passed to RegisterClass() in /// LView::CreateClass(). /// /// \returns the Class' name for debugging const char *GetClass() override; /// The array of CSS class names. LString::Array *CssClasses() override; /// Any element level styles LString CssStyles(const char *Set = NULL) override; /// \brief Captures all mouse events to this view /// /// Once you have mouse capture all mouse events will be passed to this /// view. i.e. during a mouse click. bool Capture(bool c) override; /// true if this view is capturing mouse events. bool IsCapturing() override; /// \brief Gets the current mouse location /// \return true on success bool GetMouse ( /// The mouse location information returned LMouse &m, /// Get the location in screen coordinates bool ScreenCoords = false ) override; /// \brief Gets the ID associated with the view /// /// The ID of a view is designed to associate controls defined in resource /// files with a object at runtime via a C header file define. int GetId() override; /// Sets the view's ID. void SetId(int i) override; /// true if this control is a tab stop. bool GetTabStop() override; /// \brief Sets whether this control is a tab stop. /// /// A top stop is a control that receives focus if the user scrolls through the controls /// with the tab key. void SetTabStop(bool b) override; /// Gets the integer representation of the view's contents virtual int64 Value() override { return 0; } /// Sets the integer representation of the view's contents virtual void Value(int64 i) override {} #if LGI_VIEW_HANDLE /// Find a view by it's os handle virtual LViewI *FindControl(OsView hnd); #endif /// Returns the view by it's ID virtual LViewI *FindControl ( // The ID to look for int Id ) override; /// Gets the value of the control identified by the ID int64 GetCtrlValue(int Id) override; /// Sets the value of the control identified by the ID void SetCtrlValue(int Id, int64 i) override; /// Gets the name (text) of the control identified by the ID const char *GetCtrlName(int Id) override; /// Sets the name (text) of the control identified by the ID void SetCtrlName(int Id, const char *s) override; /// Gets the enabled state of the control identified by the ID bool GetCtrlEnabled(int Id) override; /// Sets the enabled state of the control identified by the ID void SetCtrlEnabled(int Id, bool Enabled) override; /// Gets the visible state of the control identified by the ID bool GetCtrlVisible(int Id) override; /// Sets the visible state of the control identified by the ID void SetCtrlVisible(int Id, bool Visible) override; /// Causes the given area of the view to be repainted to update the screen bool Invalidate ( /// The rectangle of the view to repaint, or NULL for the entire view LRect *r = NULL, /// true if you want to wait for the update to happen bool Repaint = false, /// false to update in client coordinates, true to update the non client region bool NonClient = false ) override; /// Causes the given area of the view to be repainted to update the screen bool Invalidate ( /// The region of the view to repaint LRegion *r, /// true if you want to wait for the update to happen bool Repaint = false, /// false to update in client coordinates, true to update the non client region bool NonClient = false ) override; /// true if the mouse event is over the view bool IsOver(LMouse &m) override; /// returns the sub window located at the point x,y LViewI *WindowFromPoint(int x, int y, int DebugDepth = 0) override; /// Sets a timer to call the OnPulse() event void SetPulse ( /// The milliseconds between calls to OnPulse() or -1 to disable int Ms = -1 ) override; /// Convert a point form view coordinates to screen coordinates bool PointToScreen(LPoint &p) override; /// Convert a point form screen coordinates to view coordinates bool PointToView(LPoint &p) override; /// Get the x,y offset from the virtual window to the first real view in the parent chain bool WindowVirtualOffset(LPoint *Offset) override; /// Get the size of the window borders LPoint &GetWindowBorderSize() override; /// Layout all the child views virtual bool Pour ( /// The available space to lay out the views into LRegion &r ) override { return false; } /// The mouse was clicked over this view void OnMouseClick ( /// The event parameters LMouse &m ) override; /// Mouse moves into the area over the control void OnMouseEnter ( /// The event parameters LMouse &m ) override; /// Mouse leaves the area over the control void OnMouseExit ( /// The event parameters LMouse &m ) override; /// The mouse moves over the control void OnMouseMove ( /// The event parameters LMouse &m ) override; /// The mouse wheel was scrolled. bool OnMouseWheel ( /// The amount scrolled double Lines ) override; /// A key was pressed while this view has focus bool OnKey(LKey &k) override; /// The view is attached void OnCreate() override; /// The view is detached void OnDestroy() override; /// The view gains or loses the keyboard focus void OnFocus ( /// True if the control is receiving focus bool f ) override; /// \brief Called every so often by the timer system. /// \sa SetPulse() void OnPulse() override; /// Called when the view position changes void OnPosChange() override; /// Called on a top level window when something requests to close the window /// \returns true if it's ok to continue shutting down. bool OnRequestClose ( /// True if the operating system is shutting down. bool OsShuttingDown ) override; /// Return the type of cursor that should be visible when the mouse is at x,y /// e.g. #LCUR_Normal int OnHitTest ( /// The x coordinate in view coordinates int x, /// The y coordinate in view coordinates int y ) override; /// Called when the contents of the Children list have changed. void OnChildrenChanged(LViewI *Wnd, bool Attaching) override; /// Called to paint the on screen representation of the view void OnPaint(LSurface *pDC) override; /// \brief Called when a child view or view with it's SetNotify() set to this window changes. /// /// The event by default will bubble up to the LWindow at the top of the window hierarchy visiting /// each LView on the way. If it reaches a LView that processes it then the event stops propagating /// up the hierarchy. int OnNotify(LViewI *Ctrl, LNotification n) override; /// Called when a menu command is activated by the user. int OnCommand(int Cmd, int Event, OsView Wnd) override; /// Called after the view is attached to a new parent void OnAttach() override; /// Called to get layout information for the control. It's called /// up to 3 times to collect various dimensions: /// 1) PreLayout: Get the maximum width, and optionally the minimum width. /// Called with both widths set to zero. /// Must fill out Inf.Width.Max. Use -1 to fill all available space. /// Optionally fill out the min width. /// 2) Layout: Called to work out row height. /// Called with: /// - Width.Min unchanged from previous call. /// - Width.Max is limited to Cell size. /// Must fill out Inf.Height.Max. /// Min height currently not used. /// 3) PostLayout: Called to position view in cell. /// Not called. bool OnLayout(LViewLayoutInfo &Inf) override { return false; } /// This class allows some task to tap into the events the view receives. class LgiClass ViewEventTarget : public LEventSinkI, public LEventTargetI { friend class LView; protected: LView *view = NULL; LHashTbl,bool> Msgs; public: + constexpr static int OBJ_DELETED = -1000; + ViewEventTarget ( /// The view to attach to. LView *View, /// The message to handle... more can be added to 'Msgs'. /// Pass 0 to get all messages, at the cost of slowing the /// App down a bunch. Not recommended. int Msg ); ~ViewEventTarget(); // Post events to the view, which will return to the OnEvent handler.. bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1); // Receive OnPulse events. virtual void OnPulse() {} // Impl LMessage::Result OnEvent(LMessage *Msg) in your subclass }; #if defined(_DEBUG) bool _Debug; void Debug(); void _Dump(int Depth = 0); #endif }; LgiFunc LView *LViewFromHandle(OsView hWnd); /// \brief Factory for creating view's by name. /// /// Inherit from this to add a new factory to create objects. Override /// NewView() to create your control. class LgiClass LViewFactory { /** \brief Create a view by name \code if (strcmp(Class, "MyControl") == 0) { return new MyControl; } \endcode */ virtual LView *NewView ( /// The name of the class to create const char *Class, /// The initial position of the view LRect *Pos, /// The initial text of the view const char *Text ) = 0; public: LViewFactory(); virtual ~LViewFactory(); /// Create a view by name. static LView *Create(const char *Class, LRect *Pos = NULL, const char *Text = NULL); }; #define DeclFactory(CLS) \ class CLS ## Factory : public LViewFactory \ { \ LView *NewView(const char *Name, LRect *Pos, const char *Text) \ { \ if (!_stricmp(Name, #CLS)) return new CLS; \ return NULL; \ } \ } CLS ## FactoryInst; #define DeclFactoryParam1(CLS, Param1) \ class CLS ## Factory : public LViewFactory \ { \ LView *NewView(const char *Name, LRect *Pos, const char *Text) \ { \ if (!_stricmp(Name, #CLS)) return new CLS(Param1); \ return NULL; \ } \ } CLS ## FactoryInst; /// Control widget base class class LgiClass LControl : public LView { friend class LDialog; protected: #if WINNATIVE bool *SetOnDelete = NULL; LWindowsClass *SubClass = NULL; #endif LPoint SizeOfStr(const char *Str); public: #if WINNATIVE LControl(const char *SubClassName = NULL); #else LControl(OsView view = NULL); #endif ~LControl(); LMessage::Result OnEvent(LMessage *Msg); }; diff --git a/src/linux/Lgi/View.cpp b/src/linux/Lgi/View.cpp --- a/src/linux/Lgi/View.cpp +++ b/src/linux/Lgi/View.cpp @@ -1,1097 +1,1100 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 23/4/98 ** DESCRIPTION: Linux LView Implementation ** ** Copyright (C) 1998-2003, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/Css.h" #include "lgi/common/EventTargetThread.h" #include "ViewPriv.h" using namespace Gtk; #include "LgiWidget.h" #define DEBUG_MOUSE_EVENTS 0 #if 0 #define DEBUG_INVALIDATE(...) printf(__VA_ARGS__) #else #define DEBUG_INVALIDATE(...) #endif #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 #if GtkVer(2, 14) #else #define gtk_widget_get_window(widget) ((widget)->window) #endif struct CursorInfo { public: LRect Pos; LPoint HotSpot; } CursorMetrics[] = { // up arrow { LRect(0, 0, 8, 15), LPoint(4, 0) }, // cross hair { LRect(20, 0, 38, 18), LPoint(29, 9) }, // hourglass { LRect(40, 0, 51, 15), LPoint(45, 8) }, // I beam { LRect(60, 0, 66, 17), LPoint(63, 8) }, // N-S arrow { LRect(80, 0, 91, 16), LPoint(85, 8) }, // E-W arrow { LRect(100, 0, 116, 11), LPoint(108, 5) }, // NW-SE arrow { LRect(120, 0, 132, 12), LPoint(126, 6) }, // NE-SW arrow { LRect(140, 0, 152, 12), LPoint(146, 6) }, // 4 way arrow { LRect(160, 0, 178, 18), LPoint(169, 9) }, // Blank { LRect(0, 0, 0, 0), LPoint(0, 0) }, // Vertical split { LRect(180, 0, 197, 16), LPoint(188, 8) }, // Horizontal split { LRect(200, 0, 216, 17), LPoint(208, 8) }, // Hand { LRect(220, 0, 233, 13), LPoint(225, 0) }, // No drop { LRect(240, 0, 258, 18), LPoint(249, 9) }, // Copy drop { LRect(260, 0, 279, 19), LPoint(260, 0) }, // Move drop { LRect(280, 0, 299, 19), LPoint(280, 0) }, }; // CursorData is a bitmap in an array of uint32's. This is generated from a graphics file: // ./Code/cursors.png // // The pixel values are turned into C code by a program called i.Mage: // http://www.memecode.com/image.php // // Load the graphic into i.Mage and then go Edit->CopyAsCode // Then paste the text into the CursorData variable at the bottom of this file. // // This saves a lot of time finding and loading an external resouce, and even having to // bundle extra files with your application. Which have a tendancy to get lost along the // way etc. extern uint32_t CursorData[]; LInlineBmp Cursors = { 300, 20, 8, CursorData }; LCaptureThread *LViewPrivate::CaptureThread = NULL; //////////////////////////////////////////////////////////////////////////// bool LgiIsKeyDown(int Key) { LAssert(0); return false; } /////////////////////////////////////////////////////////////////////////////////////////////////// LCaptureThread::LCaptureThread(LView *v) : LThread("CaptureThread") { name = v->GetClass(); view = v->AddDispatch(); DeleteOnExit = true; Run(); } LCaptureThread::~LCaptureThread() { Cancel(); // Don't wait.. the thread will exit and delete itself asnyronously. } int LCaptureThread::Main() { while (!IsCancelled()) { LSleep(EventMs); if (!IsCancelled()) PostThreadEvent(view, M_CAPTURE_PULSE); } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// LViewPrivate::LViewPrivate(LView *view) : View(view) { PrevMouse.x = PrevMouse.y = -1; } LViewPrivate::~LViewPrivate() { LAssert(PulseThread == 0); while (EventTargets.Length()) delete EventTargets[0]; if (Font && FontOwnType == GV_FontOwned) DeleteObj(Font); } void LView::OnGtkRealize() { if (!d->GotOnCreate) { d->GotOnCreate = true; if (d->WantsFocus) { d->WantsFocus = false; if (GetWindow()) GetWindow()->SetFocus(this, LWindow::GainFocus); } OnCreate(); } for (auto c : Children) { auto gv = c->GetGView(); if (gv) gv->OnGtkRealize(); } } void LView::_Focus(bool f) { ThreadCheck(); if (f) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); OnFocus(f); Invalidate(); if (f) { auto w = GetWindow(); if (w && w->_Root) gtk_widget_grab_focus(w->_Root); else d->WantsFocus = f; } } void LView::_Delete() { ThreadCheck(); SetPulse(); // Remove static references to myself if (_Over == this) _Over = NULL; if (_Capturing == this) _Capturing = NULL; auto *Wnd = GetWindow(); if (Wnd && Wnd->GetFocus() == static_cast(this)) Wnd->SetFocus(this, LWindow::ViewDelete); if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; // Hierarchy LViewI *c; while ((c = Children[0])) { if (c->GetParent() != (LViewI*)this) { printf("%s:%i - ~LView error, %s not attached correctly: %p(%s) Parent: %p(%s)\n", _FL, c->GetClass(), c, c->Name(), c->GetParent(), c->GetParent() ? c->GetParent()->Name() : ""); Children.Delete(c); } DeleteObj(c); } Detach(); // Misc Pos.ZOff(-1, -1); } LView *&LView::PopupChild() { return d->Popup; } void LgiToGtkCursor(LViewI *v, LCursor c) { static LCursor CurrentCursor = LCUR_Normal; if (!v || c == CurrentCursor) return; CurrentCursor = c; GdkCursorType type = GDK_ARROW; switch (c) { // No cursor #ifdef GDK_BLANK_CURSOR case LCUR_Blank: type = GDK_BLANK_CURSOR; break; #endif /// Normal arrow case LCUR_Normal: type = GDK_ARROW; break; /// Upwards arrow case LCUR_UpArrow: type = GDK_SB_UP_ARROW; break; /// Downwards arrow case LCUR_DownArrow: type = GDK_SB_DOWN_ARROW; break; /// Left arrow case LCUR_LeftArrow: type = GDK_SB_LEFT_ARROW; break; /// Right arrow case LCUR_RightArrow: type = GDK_SB_RIGHT_ARROW; break; /// Crosshair case LCUR_Cross: type = GDK_CROSSHAIR; break; /// Hourglass/watch case LCUR_Wait: type = GDK_WATCH; break; /// Ibeam/text entry case LCUR_Ibeam: type = GDK_XTERM; break; /// Vertical resize (|) case LCUR_SizeVer: type = GDK_DOUBLE_ARROW; break; /// Horizontal resize (-) case LCUR_SizeHor: type = GDK_SB_H_DOUBLE_ARROW; break; /// Diagonal resize (/) case LCUR_SizeBDiag: type = GDK_BOTTOM_LEFT_CORNER; break; /// Diagonal resize (\) case LCUR_SizeFDiag: type = GDK_BOTTOM_RIGHT_CORNER; break; /// All directions resize case LCUR_SizeAll: type = GDK_FLEUR; break; /// A pointing hand case LCUR_PointingHand: type = GDK_HAND2; break; /// A slashed circle case LCUR_Forbidden: type = GDK_X_CURSOR; break; default: type = GDK_ARROW; break; /* case LCUR_SplitV: case LCUR_SplitH: case LCUR_DropCopy: case LCUR_DropMove: LAssert(0); break; */ } OsView h = NULL; auto *w = v->GetWindow(); if (w) h = GTK_WIDGET(w->WindowHandle()); LAssert(v->InThread()); auto wnd = gtk_widget_get_window(h); // LAssert(wnd); if (wnd) { if (type == GDK_ARROW) gdk_window_set_cursor(wnd, NULL); else { GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), type); if (cursor) gdk_window_set_cursor(wnd, cursor); else gdk_window_set_cursor(wnd, NULL); } } } bool LView::_Mouse(LMouse &m, bool Move) { ThreadCheck(); #if DEBUG_MOUSE_EVENTS if (!Move) LgiTrace("%s:%i - _Mouse([%i,%i], %i) View=%p/%s\n", _FL, m.x, m.y, Move, _Over, _Over ? _Over->GetClass() : NULL); #endif if (GetWindow() && !GetWindow()->HandleViewMouse(this, m)) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - HandleViewMouse consumed event, cls=%s\n", _FL, GetClass()); #endif return false; } #if DEBUG_MOUSE_EVENTS // if (!Move) LgiTrace("%s:%i - _Capturing=%p/%s\n", _FL, _Capturing, _Capturing ? _Capturing->GetClass() : NULL); #endif if (Move) { auto *o = m.Target; if (_Over != o) { #if DEBUG_MOUSE_EVENTS // if (!o) WindowFromPoint(m.x, m.y, true); LgiTrace("%s:%i - _Over changing from %p/%s to %p/%s\n", _FL, _Over, _Over ? _Over->GetClass() : NULL, o, o ? o->GetClass() : NULL); #endif if (_Over) _Over->OnMouseExit(lgi_adjust_click(m, _Over)); _Over = o; if (_Over) _Over->OnMouseEnter(lgi_adjust_click(m, _Over)); } } LView *Target = NULL; if (_Capturing) Target = dynamic_cast(_Capturing); else Target = dynamic_cast(_Over ? _Over : this); if (!Target) return false; LRect Client = Target->LView::GetClient(false); m = lgi_adjust_click(m, Target, !Move); if (!Client.Valid() || Client.Overlap(m.x, m.y) || _Capturing) { LgiToGtkCursor(Target, Target->GetCursor(m.x, m.y)); if (Move) { Target->OnMouseMove(m); } else { #if 0 if (!Move) { char Msg[256]; sprintf(Msg, "_Mouse Target %s", Target->GetClass()); m.Trace(Msg); } #endif Target->OnMouseClick(m); } } else if (!Move) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - Click outside %s %s %i,%i\n", _FL, Target->GetClass(), Client.GetStr(), m.x, m.y); #endif } return true; } const char *EventTypeToString(int i) { switch (i) { case GDK_DELETE: return "GDK_DELETE"; case GDK_DESTROY: return "GDK_DESTROY"; case GDK_EXPOSE: return "GDK_EXPOSE"; case GDK_MOTION_NOTIFY: return "GDK_MOTION_NOTIFY"; case GDK_BUTTON_PRESS: return "GDK_BUTTON_PRESS"; case GDK_2BUTTON_PRESS: return "GDK_2BUTTON_PRESS"; case GDK_3BUTTON_PRESS: return "GDK_3BUTTON_PRESS"; case GDK_BUTTON_RELEASE: return "GDK_BUTTON_RELEASE"; case GDK_KEY_PRESS: return "GDK_KEY_PRESS"; case GDK_KEY_RELEASE: return "GDK_KEY_RELEASE"; case GDK_ENTER_NOTIFY: return "GDK_ENTER_NOTIFY"; case GDK_LEAVE_NOTIFY: return "GDK_LEAVE_NOTIFY"; case GDK_FOCUS_CHANGE: return "GDK_FOCUS_CHANGE"; case GDK_CONFIGURE: return "GDK_CONFIGURE"; case GDK_MAP: return "GDK_MAP"; case GDK_UNMAP: return "GDK_UNMAP"; case GDK_PROPERTY_NOTIFY: return "GDK_PROPERTY_NOTIFY"; case GDK_SELECTION_CLEAR: return "GDK_SELECTION_CLEAR"; case GDK_SELECTION_REQUEST: return "GDK_SELECTION_REQUEST"; case GDK_SELECTION_NOTIFY: return "GDK_SELECTION_NOTIFY"; case GDK_PROXIMITY_IN: return "GDK_PROXIMITY_IN"; case GDK_PROXIMITY_OUT: return "GDK_PROXIMITY_OUT"; case GDK_DRAG_ENTER: return "GDK_DRAG_ENTER"; case GDK_DRAG_LEAVE: return "GDK_DRAG_LEAVE"; case GDK_DRAG_MOTION: return "GDK_DRAG_MOTION"; case GDK_DRAG_STATUS: return "GDK_DRAG_STATUS"; case GDK_DROP_START: return "GDK_DROP_START"; case GDK_DROP_FINISHED: return "GDK_DROP_FINISHED"; case GDK_CLIENT_EVENT: return "GDK_CLIENT_EVENT"; case GDK_VISIBILITY_NOTIFY: return "GDK_VISIBILITY_NOTIFY"; case GDK_SCROLL: return "GDK_SCROLL"; case GDK_WINDOW_STATE: return "GDK_WINDOW_STATE"; case GDK_SETTING: return "GDK_SETTING"; case GDK_OWNER_CHANGE: return "GDK_OWNER_CHANGE"; case GDK_GRAB_BROKEN: return "GDK_GRAB_BROKEN"; case GDK_DAMAGE: return "GDK_DAMAGE"; case GDK_TOUCH_BEGIN: return "GDK_TOUCH_BEGIN"; case GDK_TOUCH_UPDATE: return "GDK_TOUCH_UPDATE"; case GDK_TOUCH_END: return "GDK_TOUCH_END"; case GDK_TOUCH_CANCEL: return "GDK_TOUCH_CANCEL"; case GDK_TOUCHPAD_SWIPE: return "GDK_TOUCHPAD_SWIPE"; case GDK_TOUCHPAD_PINCH: return "GDK_TOUCHPAD_PINCH"; #ifdef GDK_PAD_BUTTON_PRESS case GDK_PAD_BUTTON_PRESS: return "GDK_PAD_BUTTON_PRESS"; #endif #ifdef GDK_PAD_BUTTON_RELEASE case GDK_PAD_BUTTON_RELEASE: return "GDK_PAD_BUTTON_RELEASE"; #endif #ifdef GDK_PAD_RING case GDK_PAD_RING: return "GDK_PAD_RING"; #endif #ifdef GDK_PAD_STRIP case GDK_PAD_STRIP: return "GDK_PAD_STRIP"; #endif #ifdef GDK_PAD_GROUP_MODE case GDK_PAD_GROUP_MODE: return "GDK_PAD_GROUP_MODE"; #endif } return "#error"; } gboolean GtkViewCallback(GtkWidget *widget, GdkEvent *event, LView *This) { #if 0 LgiTrace("GtkViewCallback, Event=%s, This=%p(%s\"%s\")\n", EventTypeToString(event->type), This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0); #endif if (event->type < 0 || (int)event->type > 1000) { printf("%s:%i - CORRUPT EVENT %i\n", _FL, event->type); return false; } return This->OnGtkEvent(widget, event); } gboolean LView::OnGtkEvent(GtkWidget *widget, GdkEvent *event) { printf("LView::OnGtkEvent ?????\n"); return false; } LRect &LView::GetClient(bool ClientSpace) { int Edge = (Sunken() || Raised()) ? _BorderSize : 0; static LRect c; if (ClientSpace) { c.ZOff(Pos.X() - 1 - (Edge<<1), Pos.Y() - 1 - (Edge<<1)); } else { c.ZOff(Pos.X()-1, Pos.Y()-1); c.Inset(Edge, Edge); } return c; } void LView::Quit(bool DontDelete) { ThreadCheck(); if (DontDelete) { Visible(false); } else { delete this; } } bool LView::SetPos(LRect &p, bool Repaint) { if (Pos != p) { Pos = p; OnPosChange(); } return true; } LRect GtkGetPos(GtkWidget *w) { GtkAllocation a = {0}; gtk_widget_get_allocation(w, &a); return a; } bool LView::Invalidate(LRect *rc, bool Repaint, bool Frame) { #if 0 // Debug where lots of invalidate calls are from static uint64_t StartTs = 0; static int Count = 0; static LHashTbl,int> Classes; Count++; int count = Classes.Find(GetClass()); Classes.Add(GetClass(), count + 1); auto Now = LCurrentTime(); if (Now - StartTs >= 1000) { printf("Inval=%i:", Count); for (auto p: Classes) printf(" %s=%i", p.key, p.value); printf("\n"); StartTs = Now; Count = 0; Classes.Empty(); if (!Stricmp(GetClass(), "LMdiChild")) { int asd=0; } } #endif auto *ParWnd = GetWindow(); if (!ParWnd) return false; // Nothing we can do till we attach if (!InThread()) { DEBUG_INVALIDATE("%s::Invalidate out of thread\n", GetClass()); return PostEvent(M_INVALIDATE, (LMessage::Param)NULL, (LMessage::Param)this); } LRect r; if (rc) { r = *rc; } else { if (Frame) r = Pos.ZeroTranslate(); else r = GetClient().ZeroTranslate(); } DEBUG_INVALIDATE("%s::Invalidate r=%s frame=%i\n", GetClass(), r.GetStr(), Frame); if (!Frame) r.Offset(_BorderSize, _BorderSize); LPoint Offset; WindowVirtualOffset(&Offset); r.Offset(Offset.x, Offset.y); DEBUG_INVALIDATE(" voffset=%i,%i = %s\n", Offset.x, Offset.y, r.GetStr()); if (!r.Valid()) { DEBUG_INVALIDATE(" error: invalid\n"); return false; } static bool Repainting = false; if (!Repainting) { Repainting = true; GtkWidget *w = GTK_WIDGET(ParWnd->WindowHandle()); if (w) { GdkWindow *h; if (gtk_widget_get_has_window(w) && (h = gtk_widget_get_window(w))) { GdkRectangle grc = r; DEBUG_INVALIDATE(" gdk_window_invalidate_rect %i,%i %i,%i\n", grc.x, grc.y, grc.width, grc.height); gdk_window_invalidate_rect(h, &grc, true); } else { DEBUG_INVALIDATE(" gtk_widget_queue_draw\n"); gtk_widget_queue_draw(w); } } Repainting = false; } else { DEBUG_INVALIDATE(" error: repainting\n"); } return true; } void LView::SetPulse(int Length) { ThreadCheck(); DeleteObj(d->PulseThread); if (Length > 0) d->PulseThread = new LPulseThread(this, Length); } LMessage::Param LView::OnEvent(LMessage *Msg) { ThreadCheck(); for (auto target: d->EventTargets) { if (target->Msgs.Length() == 0 || target->Msgs.Find(Msg->Msg())) - target->OnEvent(Msg); + { + if (target->OnEvent(Msg) == ViewEventTarget::OBJ_DELETED) + return 0; + } } int Id; switch (Id = Msg->Msg()) { case M_INVALIDATE: { if ((LView*)this == (LView*)Msg->B()) { LAutoPtr r((LRect*)Msg->A()); Invalidate(r); } break; } case M_CAPTURE_PULSE: { auto wnd = GetWindow(); if (!wnd) { printf("%s:%i - No window.\n", _FL); break; } LMouse m; if (!wnd->GetMouse(m, true)) { printf("%s:%i - GetMouse failed.\n", _FL); break; } auto c = wnd->GetPos(); if (c.Overlap(m)) break; // Over the window, so we'll get normal events... if (!_Capturing) break; // Don't care now, there is no window capturing... if (d->PrevMouse.x < 0) { d->PrevMouse = m; // Initialize the previous mouse var. break; } int mask = LGI_EF_LEFT | LGI_EF_MIDDLE | LGI_EF_RIGHT; bool coordsChanged = m.x != d->PrevMouse.x || m.y != d->PrevMouse.y; bool buttonsChanged = (m.Flags & mask) != (d->PrevMouse.Flags & mask); if (!coordsChanged && !buttonsChanged) break; // Nothing changed since last poll.. int prevFlags = d->PrevMouse.Flags; d->PrevMouse = m; // Convert coords to local view... m.Target = _Capturing; #if 0 printf("%s:%i - Mouse(%i,%i) outside window(%s, %s).. %s\n", _FL, m.x, m.y, c.GetStr(), wnd->Name(), m.ToString().Get()); #endif m.ToView(); // Process events... if (coordsChanged) { // printf("Move event: %s\n", m.ToString().Get()); _Capturing->OnMouseMove(m); } /* This seems to happen anyway? Ok then... whatevs if (buttonsChanged) { m.Flags &= ~mask; // clear any existing m.Flags |= prevFlags & mask; m.Down(false); printf("Click event: %s, mask=%x flags=%x\n", m.ToString().Get(), mask, m.Flags); _Capturing->OnMouseClick(m); } */ break; } case M_CHANGE: { LViewI *Ctrl; if (GetViewById(Msg->A(), Ctrl)) return OnNotify(Ctrl, (LNotifyType)Msg->B()); break; } case M_COMMAND: { return OnCommand(Msg->A(), 0, (OsView) Msg->B()); } } LMessage::Result result; if (CommonEvents(result, Msg)) return result; return 0; } LPoint GtkGetOrigin(LWindow *w) { auto Hnd = w->WindowHandle(); if (Hnd) { auto Wnd = gtk_widget_get_window(GTK_WIDGET(Hnd)); if (Wnd) { GdkRectangle rect; gdk_window_get_frame_extents(Wnd, &rect); return LPoint(rect.x, rect.y); /* gint x = 0, y = 0; gdk_window_get_origin(Wnd, &x, &y); gdk_window_get_root_origin(Wnd, &x, &y); return LPoint(x, y); */ } else { LgiTrace("%s:%i - can't get Wnd for %s\n", _FL, G_OBJECT_TYPE_NAME(Hnd)); } } return LPoint(); } bool LView::PointToScreen(LPoint &p) { ThreadCheck(); LPoint Offset; WindowVirtualOffset(&Offset); p += Offset; auto w = GetWindow(); if (!w) return false; auto wnd = w->WindowHandle(); if (!wnd) return false; auto wid = GTK_WIDGET(wnd); auto hnd = gtk_widget_get_window(wid); gint x = 0, y = 0; gdk_window_get_origin(hnd, &x, &y); p.x += x; p.y += y; return true; } bool LView::PointToView(LPoint &p) { ThreadCheck(); LPoint Offset; WindowVirtualOffset(&Offset); p -= Offset; auto w = GetWindow(); if (!w) return false; auto wnd = w->WindowHandle(); if (!wnd) return false; auto wid = GTK_WIDGET(wnd); auto hnd = gtk_widget_get_window(wid); gint x = 0, y = 0; gdk_window_get_origin(hnd, &x, &y); p.x -= x; p.y -= y; return true; } bool LView::GetMouse(LMouse &m, bool ScreenCoords) { bool Status = true; ThreadCheck(); auto *w = GetWindow(); GdkModifierType mask = (GdkModifierType)0; auto display = gdk_display_get_default(); auto seat = gdk_display_get_default_seat(display); auto device = gdk_seat_get_pointer(seat); // auto deviceManager = gdk_display_get_device_manager(display); // auto device = gdk_device_manager_get_client_pointer(deviceManager); gdouble axes[2] = {0}; if (gdk_device_get_axis_use(device, 0) != GDK_AXIS_X) gdk_device_set_axis_use(device, 0, GDK_AXIS_X); if (gdk_device_get_axis_use(device, 1) != GDK_AXIS_Y) gdk_device_set_axis_use(device, 1, GDK_AXIS_Y); if (ScreenCoords || !w) { gdk_device_get_state(device, gdk_get_default_root_window(), axes, &mask); m.x = (int)axes[0]; m.y = (int)axes[1]; } else if (auto widget = GTK_WIDGET(w->WindowHandle())) { auto wnd = gtk_widget_get_window(widget); gdk_device_get_state(device, wnd, axes, &mask); if (axes[0] == 0.0 || axes[0] > 10000) { // LgiTrace("%s:%i - gdk_device_get_state failed.\n", _FL); Status = false; } else { LPoint p; WindowVirtualOffset(&p); m.x = (int)axes[0] - p.x - _BorderSize; m.y = (int)axes[1] - p.y - _BorderSize; // printf("GetMs %g,%g %i,%i %i,%i device=%p wnd=%p\n", axes[0], axes[1], p.x, p.y, m.x, m.y, device, wnd); } } m.Target = this; m.SetModifer(mask); m.Left((mask & GDK_BUTTON1_MASK) != 0); m.Middle((mask & GDK_BUTTON2_MASK) != 0); m.Right((mask & GDK_BUTTON3_MASK) != 0); m.Down(m.Left() || m.Middle() || m.Right()); m.ViewCoords = !ScreenCoords; return Status; } bool LView::IsAttached() { auto w = GetWindow(); if (!w) return false; w = dynamic_cast(this); if (!w) { auto p = GetParent(); return d->GotOnCreate && p && p->HasView(this); } return w->IsAttached(); } const char *LView::GetClass() { return "LView"; } bool LView::Attach(LViewI *parent) { ThreadCheck(); bool Status = false; LView *Parent = d->GetParent(); LAssert(Parent == NULL || Parent == parent); SetParent(parent); Parent = d->GetParent(); auto WndNull = _Window == NULL; _Window = Parent ? Parent->_Window : this; if (parent) { auto w = GetWindow(); if (w && TestFlag(WndFlags, GWF_FOCUS)) w->SetFocus(this, LWindow::GainFocus); Status = true; if (!Parent->HasView(this)) { OnAttach(); if (!d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); } if (_Window) OnGtkRealize(); } return Status; } bool LView::Detach() { ThreadCheck(); // Detach view if (_Window) { auto *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); _Window = NULL; } LViewI *Par = GetParent(); if (Par) { // Events Par->DelView(this); Par->OnChildrenChanged(this, false); Par->Invalidate(&Pos); } d->Parent = 0; d->ParentI = 0; #if 0 // Windows is not doing a deep detach... so we shouldn't either? { int Count = Children.Length(); if (Count) { int Detached = 0; LViewI *c, *prev = NULL; while ((c = Children[0])) { LAssert(!prev || c != prev); if (c->GetParent()) c->Detach(); else Children.Delete(c); Detached++; prev = c; } LAssert(Count == Detached); } } #endif return true; } LCursor LView::GetCursor(int x, int y) { return LCUR_Normal; } void LView::OnGtkDelete() { List::I it = Children.begin(); for (LViewI *c = *it; c; c = *++it) { LView *v = c->GetGView(); if (v) v->OnGtkDelete(); } } /////////////////////////////////////////////////////////////////// bool LgiIsMounted(char *Name) { return false; } bool LgiMountVolume(char *Name) { return false; } //////////////////////////////// uint32_t CursorData[] = { 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00010001, 0x01000001, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x00000100, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x01000001, 0x02020101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01010000, 0x02020101, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x00000102, 0x02020100, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02010102, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000102, 0x00000001, 0x02020201, 0x00010202, 0x02020100, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x01000001, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x00000101, 0x02020100, 0x00010202, 0x02010000, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x00010202, 0x02010000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000102, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x01020202, 0x01000000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x00010202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x00000001, 0x01020201, 0x02010000, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x01000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x02020201, 0x00000102, 0x00010100, 0x02010000, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x01000001, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x00000102, 0x02020201, 0x00010202, 0x00010000, 0x02020100, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00000000, 0x01000100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x02020100, 0x01020202, 0x00000000, 0x02020100, 0x02010001, 0x00000102, 0x01020201, 0x01010000, 0x01000001, 0x02010001, 0x00000102, 0x01020201, 0x01010100, 0x01000001, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01010202, 0x01010101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x02020101, 0x00000102, 0x01020201, 0x00000100, 0x01000100, 0x02020101, 0x00000102, 0x01020201, 0x01000100, 0x01000100, 0x01020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000000, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000100, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x01020201, 0x01010000, 0x01000001, 0x02020202, 0x01020202, 0x01020201, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, }; diff --git a/src/linux/Lgi/Window.cpp b/src/linux/Lgi/Window.cpp --- a/src/linux/Lgi/Window.cpp +++ b/src/linux/Lgi/Window.cpp @@ -1,1999 +1,1975 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Token.h" #include "lgi/common/Popup.h" #include "lgi/common/Panel.h" #include "lgi/common/Notifications.h" #include "lgi/common/Menu.h" #include "ViewPriv.h" using namespace Gtk; #undef Status #include "LgiWidget.h" #define DEBUG_SETFOCUS 0 #define DEBUG_HANDLEVIEWKEY 0 extern Gtk::GdkDragAction EffectToDragAction(int Effect); /////////////////////////////////////////////////////////////////////// class HookInfo { public: LWindowHookType Flags; LView *Target; }; enum LAttachState { LUnattached, LAttaching, LAttached, LDetaching, }; class LWindowPrivate { public: int Sx = 0, Sy = 0; LKey LastKey; LArray Hooks; bool SnapToEdge = false; LString Icon; LRect Decor; gulong DestroySig = 0; LAutoPtr IconImg; LAttachState AttachState = LUnattached; bool ShowTitleBar = true; bool WillFocus = true; // State GdkWindowState State; bool HadCreateEvent = false; // Focus stuff OsView FirstFocus = NULL; LViewI *Focus = NULL; bool Active = false; LWindowPrivate() { Decor.ZOff(-1, -1); State = (Gtk::GdkWindowState)0; LastKey.vkey = 0; LastKey.c16 = 0; LastKey.Data = 0; LastKey.IsChar = 0; } int GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = LNoEvents; return Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////// #define GWND_CREATE 0x0010000 LWindow::LWindow(GtkWidget *w) : LView(0) { d = new LWindowPrivate; _QuitOnClose = false; Wnd = GTK_WINDOW(w); if (Wnd) { gtk_window_set_decorated(Wnd, d->ShowTitleBar); g_object_set_data(G_OBJECT(Wnd), "LViewI", (LViewI*)this); } _RootAlloc.ZOff(-1, -1); _Window = this; WndFlags |= GWND_CREATE; ClearFlag(WndFlags, GWF_VISIBLE); _Lock = new LMutex("LWindow"); } LWindow::~LWindow() { d->AttachState = LDetaching; if (Wnd && d->DestroySig > 0) { // As we are already in the destructor, we don't want // GtkWindowDestroy to try and delete the object again. g_signal_handler_disconnect(Wnd, d->DestroySig); } if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; if (_Root) { lgi_widget_detach(_Root); _Root = NULL; } // This needs to be before the 'gtk_widget_destroy' because that will delete the menu's widgets. DeleteObj(Menu); if (Wnd) { gtk_widget_destroy(GTK_WIDGET(Wnd)); Wnd = NULL; } d->AttachState = LUnattached; DeleteObj(d); DeleteObj(_Lock); } int LWindow::WaitThread() { return 0; // Nop for linux } bool LWindow::SetIcon(const char *FileName) { LString a; if (Wnd) { if (!LFileExists(FileName)) { if (a = LFindFile(FileName)) FileName = a; } if (!LFileExists(FileName)) { LgiTrace("%s:%i - SetIcon failed to find '%s'\n", _FL, FileName); return false; } else { #if defined(LINUX) LAppInst->SetApplicationIcon(FileName); #endif #if _MSC_VER GError *error = NULL; if (gtk_window_set_icon_from_file(Wnd, FileName, &error)) return true; #else // On windows this is giving a red for blue channel swap error... if (d->IconImg.Reset(GdcD->Load(a))) gtk_window_set_icon(Wnd, d->IconImg->CreatePixBuf()); #endif } } if (FileName != d->Icon.Get()) d->Icon = FileName; return d->Icon != NULL; } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool s) { d->SnapToEdge = s; } bool LWindow::IsActive() { return d->Active; } bool LWindow::SetActive() { if (!Wnd) return false; gtk_window_present(Wnd); return true; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool i) { ThreadCheck(); auto w = GTK_WIDGET(Wnd); if (!w) return; if (i) gtk_widget_show(w); else gtk_widget_hide(w); } bool LWindow::Obscured() { return d->State == GDK_WINDOW_STATE_WITHDRAWN || d->State == GDK_WINDOW_STATE_ICONIFIED; } void LWindow::_OnViewDelete() { delete this; } void LWindow::OnGtkRealize() { d->AttachState = LAttached; LView::OnGtkRealize(); } void LWindow::OnGtkDelete() { // Delete everything we own... // DeleteObj(Menu); #if 0 while (Children.Length()) { LViewI *c = Children.First(); c->Detach(); } #else for (unsigned i=0; iGetGView(); if (v) v->OnGtkDelete(); } #endif // These will be destroyed by GTK after returning from LWindowCallback Wnd = NULL; #ifndef __GTK_H__ _View = NULL; #endif } LRect *LWindow::GetDecorSize() { return d->Decor.x2 >= 0 ? &d->Decor : NULL; } void LWindow::SetDecor(bool Visible) { if (Wnd) gtk_window_set_decorated (Wnd, Visible); else LgiTrace("%s:%i - No window to set decor.\n", _FL); } LViewI *LWindow::WindowFromPoint(int x, int y, bool Debug) { if (!_Root) return NULL; auto rpos = GtkGetPos(_Root).ZeroTranslate(); if (!rpos.Overlap(x, y)) return NULL; return LView::WindowFromPoint(x - rpos.x1, y - rpos.y1, Debug); } bool LWindow::TranslateMouse(LMouse &m) { m.Target = WindowFromPoint(m.x, m.y, false); if (!m.Target) return false; LViewI *w = this; for (auto p = m.Target; p; p = p->GetParent()) { if (p == w) { auto ppos = GtkGetPos(GTK_WIDGET(WindowHandle())); m.x -= ppos.x1; m.y -= ppos.y1; break; } else { auto pos = p->GetPos(); m.x -= pos.x1; m.y -= pos.y1; } } return true; } gboolean LWindow::OnGtkEvent(GtkWidget *widget, GdkEvent *event) { if (!event) { printf("%s:%i - No event.\n", _FL); return FALSE; } #if 0 if (event->type != 28) LgiTrace("%s::OnGtkEvent(%i) name=%s\n", GetClass(), event->type, Name()); #endif switch (event->type) { case GDK_DELETE: { bool Close = OnRequestClose(false); if (Close) OnGtkDelete(); return !Close; } case GDK_DESTROY: { delete this; return true; } case GDK_KEY_PRESS: case GDK_KEY_RELEASE: { auto ModFlags = LAppInst->GetKeyModFlags(); auto e = &event->key; #define KEY(name) GDK_KEY_##name LKey k; k.Down(e->type == GDK_KEY_PRESS); k.c16 = k.vkey = e->keyval; k.Shift((e->state & ModFlags->Shift) != 0); k.Ctrl((e->state & ModFlags->Ctrl) != 0); k.Alt((e->state & ModFlags->Alt) != 0); k.System((e->state & ModFlags->System) != 0); #if 0//def _DEBUG if (k.vkey == GDK_KEY_Meta_L || k.vkey == GDK_KEY_Meta_R) break; #endif k.IsChar = !k.Ctrl() && !k.Alt() && !k.System() && (k.c16 >= ' ') && (k.c16 >> 8 != 0xff); if (e->keyval > 0xff && e->string != NULL) { // Convert string to unicode char auto *i = e->string; ptrdiff_t len = strlen(i); k.c16 = LgiUtf8To32((uint8_t *&) i, len); } switch (k.vkey) { case GDK_KEY_ISO_Left_Tab: case KEY(Tab): k.IsChar = true; k.c16 = k.vkey = LK_TAB; break; case KEY(Return): case KEY(KP_Enter): k.IsChar = true; k.c16 = k.vkey = LK_RETURN; break; case GDK_KEY_BackSpace: k.c16 = k.vkey = LK_BACKSPACE; k.IsChar = !k.Ctrl() && !k.Alt() && !k.System(); break; case KEY(Left): k.vkey = k.c16 = LK_LEFT; break; case KEY(Right): k.vkey = k.c16 = LK_RIGHT; break; case KEY(Up): k.vkey = k.c16 = LK_UP; break; case KEY(Down): k.vkey = k.c16 = LK_DOWN; break; case KEY(Page_Up): k.vkey = k.c16 = LK_PAGEUP; break; case KEY(Page_Down): k.vkey = k.c16 = LK_PAGEDOWN; break; case KEY(Home): k.vkey = k.c16 = LK_HOME; break; case KEY(End): k.vkey = k.c16 = LK_END; break; case KEY(Delete): k.vkey = k.c16 = LK_DELETE; break; #define KeyPadMap(gdksym, ch, is) \ case gdksym: k.c16 = ch; k.IsChar = is; break; KeyPadMap(KEY(KP_0), '0', true) KeyPadMap(KEY(KP_1), '1', true) KeyPadMap(KEY(KP_2), '2', true) KeyPadMap(KEY(KP_3), '3', true) KeyPadMap(KEY(KP_4), '4', true) KeyPadMap(KEY(KP_5), '5', true) KeyPadMap(KEY(KP_6), '6', true) KeyPadMap(KEY(KP_7), '7', true) KeyPadMap(KEY(KP_8), '8', true) KeyPadMap(KEY(KP_9), '9', true) KeyPadMap(KEY(KP_Space), ' ', true) KeyPadMap(KEY(KP_Tab), '\t', true) KeyPadMap(KEY(KP_F1), LK_F1, false) KeyPadMap(KEY(KP_F2), LK_F2, false) KeyPadMap(KEY(KP_F3), LK_F3, false) KeyPadMap(KEY(KP_F4), LK_F4, false) KeyPadMap(KEY(KP_Home), LK_HOME, false) KeyPadMap(KEY(KP_Left), LK_LEFT, false) KeyPadMap(KEY(KP_Up), LK_UP, false) KeyPadMap(KEY(KP_Right), LK_RIGHT, false) KeyPadMap(KEY(KP_Down), LK_DOWN, false) KeyPadMap(KEY(KP_Page_Up), LK_PAGEUP, false) KeyPadMap(KEY(KP_Page_Down), LK_PAGEDOWN, false) KeyPadMap(KEY(KP_End), LK_END, false) KeyPadMap(KEY(KP_Begin), LK_HOME, false) KeyPadMap(KEY(KP_Insert), LK_INSERT, false) KeyPadMap(KEY(KP_Delete), LK_DELETE, false) KeyPadMap(KEY(KP_Equal), '=', true) KeyPadMap(KEY(KP_Multiply), '*', true) KeyPadMap(KEY(KP_Add), '+', true) KeyPadMap(KEY(KP_Separator), '|', true) // is this right? KeyPadMap(KEY(KP_Subtract), '-', true) KeyPadMap(KEY(KP_Decimal), '.', true) KeyPadMap(KEY(KP_Divide), '/', true) } if (ModFlags->Debug) { :: LString Msg; Msg.Printf("e->state=%x %s", e->state, ModFlags->FlagsToString(e->state).Get()); k.Trace(Msg); } auto v = d->Focus ? d->Focus : this; if (!HandleViewKey(v->GetGView(), k)) { if (!k.Down()) return false; if (k.vkey == LK_TAB || k.vkey == KEY(ISO_Left_Tab)) { // Do tab between controls LArray a; BuildTabStops(this, a); int idx = a.IndexOf((LViewI*)v); if (idx >= 0) { idx += k.Shift() ? -1 : 1; int next_idx = idx == 0 ? a.Length() -1 : idx % a.Length(); LViewI *next = a[next_idx]; if (next) { // LgiTrace("Setting focus to %i of %i: %s, %s, %i\n", next_idx, a.Length(), next->GetClass(), next->GetPos().GetStr(), next->GetId()); next->Focus(true); } } } else if (k.System()) { if (ToLower(k.c16) == 'q') { auto AppWnd = LAppInst->AppWnd; auto Wnd = AppWnd ? AppWnd : this; if (Wnd->OnRequestClose(false)) { Wnd->Quit(); return true; } } } else return false; } break; } case GDK_CONFIGURE: { GdkEventConfigure *c = &event->configure; Pos.Set(c->x, c->y, c->x+c->width-1, c->y+c->height-1); // printf("%s::GDK_CONFIGURE %s\n", GetClass(), Pos.GetStr()); OnPosChange(); return FALSE; break; } case GDK_FOCUS_CHANGE: { d->Active = event->focus_change.in; #if 0 printf("%s/%s::GDK_FOCUS_CHANGE(%i)\n", GetClass(), Name(), event->focus_change.in); #endif break; } case GDK_WINDOW_STATE: { d->State = event->window_state.new_window_state; break; } case GDK_PROPERTY_NOTIFY: { gchar *Name = gdk_atom_name (event->property.atom); if (!Name) break; if (!_stricmp(Name, "_NET_FRAME_EXTENTS")) { // printf("PropChange: %i - %s\n", event->property.atom, Name); unsigned long *extents = NULL; if (gdk_property_get(event->property.window, gdk_atom_intern ("_NET_FRAME_EXTENTS", FALSE), gdk_atom_intern ("CARDINAL", FALSE), 0, sizeof (unsigned long) * 4, FALSE, NULL, NULL, NULL, (guchar **)&extents)) { d->Decor.Set(extents[0], extents[2], extents[1], extents[3]); g_free(extents); } else printf("%s:%i - Error: gdk_property_get failed.\n", _FL); } g_free(Name); break; } case GDK_UNMAP: { // LgiTrace("%s:%i - Unmap %s\n", _FL, GetClass()); break; } case GDK_VISIBILITY_NOTIFY: { // LgiTrace("%s:%i - Visible %s\n", _FL, GetClass()); break; } case GDK_DRAG_ENTER: { LgiTrace("%s:%i - GDK_DRAG_ENTER\n", _FL); break; } case GDK_DRAG_LEAVE: { LgiTrace("%s:%i - GDK_DRAG_LEAVE\n", _FL); break; } case GDK_DRAG_MOTION: { LgiTrace("%s:%i - GDK_DRAG_MOTION\n", _FL); break; } case GDK_DRAG_STATUS: { LgiTrace("%s:%i - GDK_DRAG_STATUS\n", _FL); break; } case GDK_DROP_START: { LgiTrace("%s:%i - GDK_DROP_START\n", _FL); break; } case GDK_DROP_FINISHED: { LgiTrace("%s:%i - GDK_DROP_FINISHED\n", _FL); break; } default: { printf("%s:%i - Unknown event %i\n", _FL, event->type); return false; } } return true; } static gboolean GtkWindowDestroy(GtkWidget *widget, LWindow *This) { delete This; return true; } static void GtkWindowRealize(GtkWidget *widget, LWindow *This) { #if 0 LgiTrace("GtkWindowRealize, This=%p(%s\"%s\")\n", This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0); #endif This->OnGtkRealize(); } void GtkRootResize(GtkWidget *widget, GdkRectangle *r, LView *This) { LWindow *w = This->GetWindow(); if (w) { if (r) { w->_RootAlloc = *r; #ifdef _DEBUG // printf("%s got root alloc: %s\n", w->GetClass(), w->_RootAlloc.GetStr()); #endif } else LgiTrace("%s:%i - no alloc rect param?\n", _FL); w->PourAll(); } } void LWindowUnrealize(GtkWidget *widget, LWindow *wnd) { // printf("%s:%i - LWindowUnrealize %s\n", _FL, wnd->GetClass()); } bool DndPointMap(LViewI *&v, LPoint &p, LDragDropTarget *&t, LWindow *Wnd, int x, int y) { LRect cli = Wnd->GetClient(); t = NULL; v = Wnd->WindowFromPoint(x - cli.x1, y - cli.y1, false); if (!v) { LgiTrace("%s:%i - @ %i,%i\n", _FL, x, y); return false; } v->WindowVirtualOffset(&p); p.x = x - p.x; p.y = y - p.y; for (LViewI *view = v; !t && view; view = view->GetParent()) t = view->DropTarget(); if (t) return true; LgiTrace("%s:%i - No target for %s\n", _FL, v->GetClass()); return false; } void LWindowDragBegin(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataDelete(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataGet(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataReceived(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time, LWindow *Wnd) { LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) { LgiTrace("%s:%i - DndPointMap false.\n", _FL); return; } bool matched = false; auto type = gdk_atom_name(gtk_selection_data_get_data_type(data)); for (auto &d: t->Data) { if (d.Format.Equals(type)) { matched = true; gint length = 0; auto ptr = gtk_selection_data_get_data_with_length(data, &length); if (ptr) { LgiTrace("%s:%i - LWindowDragDataReceived got data '%s'.\n", _FL, type); d.Data[0].SetBinary(length, (void*)ptr, false); } else { LgiTrace("%s:%i - LWindowDragDataReceived: gtk_selection_data_get_data_with_length failed for '%s'.\n", _FL, type); } break; } } if (!matched) { LgiTrace("%s:%i - LWindowDragDataReceived: no matching data '%s'.\n", _FL, type); } Wnd->PostEvent(M_DND_DATA_RECEIVED); } int GetAcceptFmts(LString::Array &Formats, GdkDragContext *context, LDragDropTarget *t, LPoint &p) { int KeyState = 0; LDragFormats Fmts(true); int Flags = DROPEFFECT_NONE; GList *targets = gdk_drag_context_list_targets(context); Gtk::GList *i = targets; while (i) { auto a = gdk_atom_name((GdkAtom)i->data); if (a) Fmts.Supports(a); i = i->next; } Fmts.SetSource(false); Flags = t->WillAccept(Fmts, p, KeyState); auto Sup = Fmts.GetSupported(); for (auto &s: Sup) Formats.New() = s; return Flags; } struct LGtkDrop : public LView::ViewEventTarget { uint64_t Start = 0; LPoint p; LViewI *v = NULL; LDragDropTarget *t = NULL; LArray Data; LString::Array Formats; int KeyState = 0; int Flags = 0; const char *GetClass() { return "LGtkDrop"; } LGtkDrop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) : LView::ViewEventTarget(Wnd, M_DND_DATA_RECEIVED) { Start = LCurrentTime(); // Map the point to a view... if (!DndPointMap(v, p, t, Wnd, x, y)) return; t->Data.Length(0); // Request the data... Flags = GetAcceptFmts(Formats, context, t, p); for (auto f: Formats) { t->Data.New().Format = f; gtk_drag_get_data(widget, context, gdk_atom_intern(f, true), time); } // Callbacks should be received by LWindowDragDataReceived // Once they have all arrived or the timeout has been reached we call OnComplete LgiTrace("%s:%i - LGtkDrop created...\n", _FL); } ~LGtkDrop() { LgiTrace("%s:%i - LGtkDrop deleted.\n", _FL); } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_DND_DATA_RECEIVED: { size_t HasData = 0; for (auto d: t->Data) if (d.Data.Length() > 0) HasData++; LgiTrace("%s:%i - Got M_DND_DATA_RECEIVED %i of %i\n", _FL, (int)HasData, (int)Formats.Length()); if (HasData >= Formats.Length()) { LgiTrace("%s:%i - LWindowDragDataDrop got all the formats.\n", _FL); - OnComplete(true); + OnComplete(false); + return OBJ_DELETED; } break; } } return 0; } void OnPulse() { auto now = LCurrentTime(); if (now - Start >= 5000) { OnComplete(true); } } void OnComplete(bool isTimeout) { auto Result = t->OnDrop(t->Data, p, KeyState); // if (Flags != DROPEFFECT_NONE) // gdk_drag_status(context, EffectToDragAction(Flags), time); // return Result != DROPEFFECT_NONE; delete this; } }; gboolean LWindowDragDataDrop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) { - new LGtkDrop(widget, context, x, y, time, Wnd); - return true; - - /* - // Wait for the data to arrive... - auto Start = LCurrentTime(); - while (true) - { - size_t HasData = 0; - for (auto d: t->Data) - if (d.Data.Length() > 0) - HasData++; - - if (HasData >= Formats.Length()) - { - LgiTrace("%s:%i - LWindowDragDataDrop got all the formats.\n", _FL); - break; - } - - if (LCurrentTime()-Start >= 5000) - { - LgiTrace("%s:%i - LWindowDragDataDrop timeout.\n", _FL); - break; - } - } - - */ + auto obj = new LGtkDrop(widget, context, x, y, time, Wnd); + return obj != NULL; } void LWindowDragEnd(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } gboolean LWindowDragFailed(GtkWidget *widget, GdkDragContext *context, GtkDragResult result, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); return false; } void LWindowDragLeave(GtkWidget *widget, GdkDragContext *context, guint time, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } gboolean LWindowDragMotion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) { LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return false; LString::Array Formats; int Flags = GetAcceptFmts(Formats, context, t, p); if (Flags != DROPEFFECT_NONE) gdk_drag_status(context, EffectToDragAction(Flags), time); return Flags != DROPEFFECT_NONE; } bool LWindow::Attach(LViewI *p) { bool Status = false; ThreadCheck(); // Setup default button... if (!_Default) _Default = FindControl(IDOK); // Create a window if needed.. if (!Wnd) Wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); if (Wnd) { SetTitleBar(d->ShowTitleBar); SetWillFocus(d->WillFocus); auto Widget = GTK_WIDGET(Wnd); LView *i = this; if (Pos.X() > 0 && Pos.Y() > 0) gtk_window_resize(Wnd, Pos.X(), Pos.Y()); auto Obj = G_OBJECT(Wnd); g_object_set_data(Obj, "LViewI", (LViewI*)this); d->DestroySig = g_signal_connect(Obj, "destroy", G_CALLBACK(GtkWindowDestroy), this); g_signal_connect(Obj, "delete_event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "key-press-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "key-release-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "focus-in-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "focus-out-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "window-state-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "property-notify-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "configure-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "unmap-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "visibility-notify-event",G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "realize", G_CALLBACK(GtkWindowRealize), i); g_signal_connect(Obj, "unrealize", G_CALLBACK(LWindowUnrealize), i); g_signal_connect(Obj, "drag-begin", G_CALLBACK(LWindowDragBegin), i); g_signal_connect(Obj, "drag-data-delete", G_CALLBACK(LWindowDragDataDelete), i); g_signal_connect(Obj, "drag-data-get", G_CALLBACK(LWindowDragDataGet), i); g_signal_connect(Obj, "drag-data-received", G_CALLBACK(LWindowDragDataReceived), i); g_signal_connect(Obj, "drag-drop", G_CALLBACK(LWindowDragDataDrop), i); g_signal_connect(Obj, "drag-end", G_CALLBACK(LWindowDragEnd), i); g_signal_connect(Obj, "drag-failed", G_CALLBACK(LWindowDragFailed), i); g_signal_connect(Obj, "drag-leave", G_CALLBACK(LWindowDragLeave), i); g_signal_connect(Obj, "drag-motion", G_CALLBACK(LWindowDragMotion), i); #if 0 g_signal_connect(Obj, "button-press-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "button-release-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "motion-notify-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "scroll-event", G_CALLBACK(GtkViewCallback), i); #endif gtk_widget_add_events( Widget, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK); gtk_window_set_title(Wnd, LBase::Name()); d->AttachState = LAttaching; // g_action_map_add_action_entries (G_ACTION_MAP(Wnd), app_entries, G_N_ELEMENTS (app_entries), Wnd); // This call sets up the GdkWindow handle _Root = lgi_widget_new(this, true); gtk_widget_realize(Widget); gtk_window_move(Wnd, Pos.x1, Pos.y1); if (_Root) { g_signal_connect(_Root, "size-allocate", G_CALLBACK(GtkRootResize), i); auto container = GTK_CONTAINER(Wnd); LAssert(container != NULL); if (!gtk_widget_get_parent(_Root)) gtk_container_add(container, _Root); gtk_widget_show(_Root); } // Do a rough layout of child windows PourAll(); // Add icon if (d->Icon) { SetIcon(d->Icon); d->Icon.Empty(); } auto p = GetParent(); if (p) { auto pHnd = p->WindowHandle(); if (!pHnd) LgiTrace("%s:%i - SetParent() - no pHnd from %s.\n", _FL, p->GetClass()); else gtk_window_set_transient_for(GTK_WINDOW(Wnd), pHnd); } Status = true; } return Status; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return LView::OnRequestClose(OsShuttingDown); } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { if (m.Down() && !m.IsMove()) { bool InPopup = false; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) { InPopup = true; break; } } if (!InPopup && LPopup::CurrentPopups.Length()) { for (int i=0; iVisible()) p->Visible(false); } } } 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 = 0; #if DEBUG_HANDLEVIEWKEY bool Debug = 1; // k.vkey == LK_RETURN; char SafePrint = k.c16 < ' ' ? ' ' : k.c16; // if (Debug) { LgiTrace("%s/%p::HandleViewKey=%i ischar=%i %s%s%s%s (d->Focus=%s/%p)\n", v->GetClass(), v, k.c16, k.IsChar, (char*)(k.Down()?" Down":" Up"), (char*)(k.Shift()?" Shift":""), (char*)(k.Alt()?" Alt":""), (char*)(k.Ctrl()?" Ctrl":""), d->Focus?d->Focus->GetClass():0, d->Focus); } #endif // Any window in a popup always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tSending key to popup\n"); #endif return v->OnKey(k); } } // Give key to popups if (LAppInst && LAppInst->GetMouseHook() && LAppInst->GetMouseHook()->OnViewKey(v, k)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tMouseHook got key\n"); #endif goto AllDone; } // Allow any hooks to see the key... for (int i=0; iHooks.Length(); i++) { #if DEBUG_HANDLEVIEWKEY // if (Debug) LgiTrace("\tHook[%i]\n", i); #endif if (d->Hooks[i].Flags & LKeyEvents) { LView *Target = d->Hooks[i].Target; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tHook[%i].Target=%p %s\n", i, Target, Target->GetClass()); #endif if (Target->OnViewKey(v, k)) { Status = true; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tHook[%i] ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", i, SafePrint, 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_HANDLEVIEWKEY if (Debug) LgiTrace("\tView ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif Status = true; goto AllDone; } #if DEBUG_HANDLEVIEWKEY else if (Debug) LgiTrace("\t%s didn't eat '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", v->GetClass(), SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif // Window didn't want the key... switch (k.vkey) { case LK_RETURN: #ifdef LK_KEYPADENTER case LK_KEYPADENTER: #endif { Ctrl = _Default; break; } case LK_ESCAPE: { Ctrl = FindControl(IDCANCEL); break; } } // printf("Ctrl=%p\n", Ctrl); if (Ctrl) { if (Ctrl->Enabled()) { if (Ctrl->OnKey(k)) { Status = true; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tDefault Button ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } // else printf("OnKey()=false\n"); } // else printf("Ctrl=disabled\n"); } #if DEBUG_HANDLEVIEWKEY else if (Debug) LgiTrace("\tNo default ctrl to handle key.\n"); #endif if (Menu) { Status = Menu->OnKey(v, k); if (Status) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tMenu ate '%c' down=%i alt=%i ctrl=%i sh=%i\n", k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif } } // Tab through controls if (k.vkey == LK_TAB && k.Down() && !k.IsChar) { LViewI *Wnd = GetNextTabStop(v, k.Shift()); #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tTab moving focus shift=%i Wnd=%p\n", k.Shift(), Wnd); #endif if (Wnd) Wnd->Focus(true); } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } AllDone: if (d) d->LastKey = k; return Status; } void LWindow::Raise() { if (Wnd) gtk_window_present(Wnd); } LWindowZoom LWindow::GetZoom() { switch (d->State) { case GDK_WINDOW_STATE_ICONIFIED: return LZoomMin; case GDK_WINDOW_STATE_MAXIMIZED: return LZoomMax; default: break; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (!Wnd) { // LgiTrace("%s:%i - No window.\n", _FL); return; } ThreadCheck(); switch (i) { case LZoomMin: { gtk_window_iconify(Wnd); break; } case LZoomNormal: { gtk_window_deiconify(Wnd); gtk_window_unmaximize(Wnd); break; } case LZoomMax: { gtk_window_maximize(Wnd); break; } default: { LgiTrace("%s:%i - Error: unsupported zoom.\n", _FL); break; } } } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { if (v && v->GetWindow() == this) { if (_Default != v) { LViewI *Old = _Default; _Default = v; if (Old) Old->Invalidate(); if (_Default) _Default->Invalidate(); } } else { _Default = 0; } } bool LWindow::SetTitleBar(bool ShowTitleBar) { d->ShowTitleBar = ShowTitleBar; if (Wnd) { ThreadCheck(); gtk_window_set_decorated(Wnd, ShowTitleBar); } return true; } bool LWindow::Name(const char *n) { if (Wnd) { ThreadCheck(); gtk_window_set_title(Wnd, n); } return LBase::Name(n); } const char *LWindow::Name() { return LBase::Name(); } struct CallbackParams { LRect Menu; int Depth; CallbackParams() { Menu.ZOff(-1, -1); Depth = 0; } }; void ClientCallback(GtkWidget *w, CallbackParams *p) { const char *Name = gtk_widget_get_name(w); if (Name && !_stricmp(Name, "GtkMenuBar")) { GtkAllocation alloc = {0}; gtk_widget_get_allocation(w, &alloc); p->Menu.ZOff(alloc.width-1, alloc.height-1); // LgiTrace("GtkMenuBar = %s\n", p->Menu.GetStr()); } if (!p->Menu.Valid()) { p->Depth++; if (GTK_IS_CONTAINER(w)) gtk_container_forall(GTK_CONTAINER(w), (GtkCallback)ClientCallback, p); p->Depth--; } } LPoint LWindow::GetDpi() const { return LScreenDpi(); } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); return LPointF((double)Dpi.x/96.0, (double)Dpi.y/96.0); } LRect &LWindow::GetClient(bool ClientSpace) { static LRect r; if (_RootAlloc.Valid()) { if (ClientSpace) r = _RootAlloc.ZeroTranslate(); else r = _RootAlloc; } else { // Use something vaguely plausible before we're mapped r = LView::GetClient(ClientSpace); } #if 0 if (Wnd) { CallbackParams p; gtk_container_forall(GTK_CONTAINER(Wnd), (GtkCallback)ClientCallback, &p); if (p.Menu.Valid()) { if (ClientSpace) r.y2 -= p.Menu.Y(); else r.y1 += p.Menu.Y(); } } #endif 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; // printf("SerializeState load %s\n", v.Str()); LToken t(v.Str(), ";"); for (int i=0; iName()); v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, "%s>%s", Buf, v->GetClass()); } return LAutoString(NewStr(s)); } #endif void LWindow::SetFocus(LViewI *ctrl, FocusType type) { #if DEBUG_SETFOCUS const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } #endif switch (type) { case GainFocus: { if (d->Focus == ctrl) { #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LgiTrace("SetFocus(%s, %s) already has focus.\n", _ctrl.Get(), TypeName); #endif return; } if (d->Focus) { LView *gv = d->Focus->GetGView(); if (gv) { #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus LView: %s\n", _foc.Get()); #endif gv->_Focus(false); } else if (IsActive()) { #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus view: %s (active=%i)\n", _foc.Get(), IsActive()); #endif d->Focus->OnFocus(false); d->Focus->Invalidate(); } } d->Focus = ctrl; if (d->Focus) { #if DEBUG_SETFOCUS static int Count = 0; #endif LView *gv = d->Focus->GetGView(); if (gv) { #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) %i focusing LView\n", _set.Get(), TypeName, Count++); #endif gv->_Focus(true); } else if (IsActive()) { #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) %i focusing nonGView (active=%i)\n", _set.Get(), TypeName, Count++, IsActive()); #endif d->Focus->OnFocus(true); d->Focus->Invalidate(); } } break; } case LoseFocus: { #if DEBUG_SETFOCUS LAutoString _Ctrl = DescribeView(d->Focus); LAutoString _Focus = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) d->Focus=%s\n", _Ctrl.Get(), TypeName, _Focus.Get()); #endif if (ctrl == d->Focus) { d->Focus = NULL; } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LAutoString _Ctrl = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) on delete\n", _Ctrl.Get(), TypeName); #endif d->Focus = NULL; } break; } } } bool LWindow::SetWillFocus(bool f) { d->WillFocus = f; if (Wnd) { if (f) { } else { gtk_window_set_type_hint(Wnd, GDK_WINDOW_TYPE_HINT_POPUP_MENU); // printf("%s:%i - gtk_window_set_type_hint=GDK_WINDOW_TYPE_HINT_POPUP_MENU.\n", _FL); } } return true; } void LWindow::SetDragHandlers(bool On) { } void LWindow::SetParent(LViewI *p) { LView::SetParent(p); if (p && Wnd) { auto pHnd = p->WindowHandle(); if (!pHnd) LgiTrace("%s:%i - SetParent() - no pHnd from %s.\n", _FL, p->GetClass()); else if (!GTK_IS_WINDOW(Wnd)) LgiTrace("%s:%i - SetParent() - GTK_IS_WINDOW failed.\n", _FL); else gtk_window_set_transient_for(GTK_WINDOW(Wnd), pHnd); } } bool LWindow::IsAttached() { return d->AttachState == LAttaching || d->AttachState == LAttached; } 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); } } } void LWindow::Quit(bool DontDelete) { ThreadCheck(); if (Wnd) { d->AttachState = LDetaching; auto wnd = Wnd; Wnd = NULL; gtk_widget_destroy(GTK_WIDGET(wnd)); } } void LWindow::SetAlwaysOnTop(bool b) { }