diff --git a/Lgi.xml b/Lgi.xml --- a/Lgi.xml +++ b/Lgi.xml @@ -1,599 +1,599 @@ - - - - + - + + + + Makefile.linux Makefile.win64 Makefile.macosx gcc 0 ./include ./private/common ./include ./private/common ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux ./include/lgi/win ./private/win ./include/lgi/win ./private/win ./include/lgi/haiku ./private/haiku ./include/lgi/haiku ./private/haiku /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` -static-libgcc gnu network be -static-libgcc gnu network be lgi-gtk3 lgi-gtk3 DynamicLibrary LGI_LIBRARY LGI_LIBRARY POSIX _GNU_SOURCE POSIX _GNU_SOURCE diff --git a/include/lgi/common/Tree.h b/include/lgi/common/Tree.h --- a/include/lgi/common/Tree.h +++ b/include/lgi/common/Tree.h @@ -1,307 +1,305 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief A tree/heirarchy control #ifndef __GTREE2_H #define __GTREE2_H #include "lgi/common/ItemContainer.h" #include enum LTreeItemRect { TreeItemPos, TreeItemThumb, TreeItemText, TreeItemIcon }; class LTreeItem; class LgiClass LTreeNode { protected: class LTree *Tree; LTreeItem *Parent; List Items; virtual LTreeItem *Item() { return 0; } virtual LRect *Pos() { return 0; } virtual void _ClearDs(int Col); void _Visible(bool v); void SetLayoutDirty(); public: LTreeNode(); virtual ~LTreeNode(); /// Inserts a tree item as a child at 'Pos' LTreeItem *Insert(LTreeItem *Obj = NULL, ssize_t Pos = -1); /// Removes this node from it's parent, for permanent separation. void Remove(); /// Detachs the item from the tree so it can be re-inserted else where. void Detach(); /// Gets the node after this one at the same level. LTreeItem *GetNext(); /// Gets the node before this one at the same level. LTreeItem *GetPrev(); /// Gets the first child node. LTreeItem *GetChild(); /// Gets the parent of this node. LTreeItem *GetParent() { return Parent; } /// Gets the owning tree. May be NULL if not attached to a tree. LTree *GetTree() { return Tree; } /// Returns true if this is the root node. bool IsRoot(); /// Returns the index of this node in the list of item owned by it's parent. ssize_t IndexOf(); /// \returns number of child. size_t Length(); /// \returns if the object is in the tree bool HasItem(LTreeItem *obj, bool recurse = true); List::I begin() { return Items.begin(); } List::I end() { return Items.end(); } /// Sorts the child items template bool Sort(int (*Compare)(LTreeItem*, LTreeItem*, T user_param), T user_param = 0) { if (!Compare) return false; Items.Sort(Compare, user_param); SetLayoutDirty(); return true; } /// Calls a f(n) for each int ForEach(std::function Fn); virtual bool Expanded() { return false; } virtual void Expanded(bool b) {} virtual void OnVisible(bool v) {} }; /// The item class for a tree. This defines a node in the heirarchy. class LgiClass LTreeItem : public LItem, public LTreeNode { friend class LTree; friend class LTreeNode; protected: class LTreeItemPrivate *d; // Private methods void _RePour(); void _Pour(LPoint *Limit, int ColumnPx, int Depth, bool Visible); void _Remove(); void _MouseClick(LMouse &m); void _SetTreePtr(LTree *t); LTreeItem *_HitTest(int x, int y, bool Debug = false); LRect *_GetRect(LTreeItemRect Which); LPoint _ScrollPos(); LTreeItem *Item() override { return this; } LRect *Pos() override; virtual void _PourText(LPoint &Size); virtual void _PaintText(LItem::ItemPaintCtx &Ctx); void _ClearDs(int Col) override; virtual void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c); int GetColumnSize(int Col); protected: LString::Array Str; int Sys_Image = -1; public: LTreeItem(); virtual ~LTreeItem(); LItemContainer *GetContainer() override; /// \brief Get the text for the node /// /// You can either return a string stored internally to your /// object by implementing this function in your item class /// or use the SetText function to store the string in this /// class. const char *GetText(int i = 0) override; /// \brief Sets the text for the node. /// /// This will allocate and store the string in this class. bool SetText(const char *s, int i=0) override; /// Returns the icon index into the parent tree's LImageList. int GetImage(int Flags = 0) override; /// Sets the icon index into the parent tree's LImageList. void SetImage(int i) override; /// Tells the item to update itself on the screen when the /// LTreeItem::GetText data has changed. void Update() override; /// Returns true if the tree item is currently selected. bool Select() override; /// Selects or deselects the tree item. void Select(bool b) override; /// Returns true if the node has children and is open. bool Expanded() override; /// Opens or closes the node to show or hide the children. void Expanded(bool b) override; /// Scrolls the tree view so this node is visible. void ScrollTo() override; /// Gets the bounding box of the item. LRect *GetPos(int Col = -1) override; /// True if the node is the drop target bool IsDropTarget(); /// Called when the node expands/contracts to show or hide it's children. virtual void OnExpand(bool b); /// Paints the item void OnPaint(ItemPaintCtx &Ctx) override; void OnPaint(LSurface *pDC) override { LAssert(0); } }; /// A tree control. class LgiClass LTree : public LItemContainer, public ResObject, public LTreeNode { friend class LTreeItem; friend class LTreeNode; class LTreePrivate *d; // Private methods void _Pour(); void _OnSelect(LTreeItem *Item); void _Update(LRect *r = 0, bool Now = false); void _UpdateBelow(int y, bool Now = false); void _UpdateScrollBars(); List *GetSelLst(); protected: // Options bool Lines; bool Buttons; bool LinesAtRoot; bool EditLabels; LRect rItems; LPoint _ScrollPos(); LTreeItem *GetAdjacent(LTreeItem *From, bool Down); void OnDragEnter(); void OnDragExit(); void ClearDs(int Col) override; public: LTree(int id, int x = 0, int y = 0, int cx = 100, int cy = 100, const char *name = NULL); ~LTree(); const char *GetClass() override { return "LTree"; } /// Called when an item is clicked virtual void OnItemClick(LTreeItem *Item, LMouse &m); /// Called when an item is dragged from it's position virtual void OnItemBeginDrag(LTreeItem *Item, LMouse &m); /// Called when an item is expanded/contracted to show or hide it's children virtual void OnItemExpand(LTreeItem *Item, bool Expand); /// Called when an item is selected virtual void OnItemSelect(LTreeItem *Item); // Implementation void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnMouseWheel(double Lines) override; void OnPaint(LSurface *pDC) override; void OnFocus(bool b) override; void OnPosChange() override; bool OnKey(LKey &k) override; int OnNotify(LViewI *Ctrl, LNotification n) override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPulse() override; int GetContentSize(int ColumnIdx) override; LCursor GetCursor(int x, int y) override; - bool Lock(const char *file, int line, int TimeOut = -1) override; - void Unlock() override; /// Add a item to the tree LTreeItem *Insert(LTreeItem *Obj = 0, ssize_t Pos = -1); /// Remove and delete an item bool Delete(LTreeItem *Obj); /// Remove but don't delete an item bool Remove(LTreeItem *Obj); /// Gets the item at an index LTreeItem *ItemAt(size_t Pos) { return Items[Pos]; } /// \returns if the object is in the tree bool HasItem(LTreeItem *obj, bool recurse = true); /// Select the item 'Obj' bool Select(LTreeItem *Obj); /// Returns the first selected item LTreeItem *Selection(); /// Gets the whole selection and puts it in 'n' template bool GetSelection(LArray &n) { n.Empty(); auto s = GetSelLst(); for (auto i : *s) { T *ptr = dynamic_cast(i); if (ptr) n.Add(ptr); } return n.Length() > 0; } /// Gets an array of all items template bool GetAll(LArray &n) { n.Empty(); return ForAllItems([&n](LTreeItem *item) { T *t = dynamic_cast(item); if (t) n.Add(t); }); } /// Call a function for every item bool ForAllItems(std::function Callback); /// Returns the item at an x,y location LTreeItem *ItemAtPoint(int x, int y, bool Debug = false); /// Temporarily selects one of the items as the drop target during a /// drag and drop operation. Call SelectDropTarget(0) when done. void SelectDropTarget(LTreeItem *Item); /// Delete all items (frees the items) void Empty(); /// Remove reference to items (doesn't free the items) void RemoveAll(); /// Call 'Update' on all tree items void UpdateAllItems() override; // Visual style enum ThumbStyle { TreePlus, TreeTriangle }; void SetVisualStyle(ThumbStyle Btns, bool JoiningLines); }; #endif diff --git a/include/lgi/haiku/LgiOsDefs.h b/include/lgi/haiku/LgiOsDefs.h --- a/include/lgi/haiku/LgiOsDefs.h +++ b/include/lgi/haiku/LgiOsDefs.h @@ -1,518 +1,518 @@ /** \file \author Matthew Allen \brief Haiku defs. Debugging memory issues in Haiku: LD_PRELOAD=/boot/system/lib/libroot_debug.so MALLOC_DEBUG=g ./my_app */ #ifndef __LGI_OS_DEFS_H #define __LGI_OS_DEFS_H #include #include #include #include #include #include #include #include #include #include #include #include #include "lgi/common/LgiDefs.h" #include #define XP_CTRLS 1 #define POSIX 1 #define LGI_64BIT 1 #define LGI_VIEW_HANDLE 1 #define LGI_VIEW_HASH 1 #define LGI_HAIKU 1 #undef stricmp #include "lgi/common/LgiInc.h" class LgiClass OsAppArguments { struct OsAppArgumentsPriv *d; public: int Args; const char **Arg; OsAppArguments(int args, const char **arg); ~OsAppArguments(); void Set(char *CmdLine); bool Get(const char *Name, const char **Val = NULL); OsAppArguments &operator =(OsAppArguments &a); }; // Process typedef int OsProcess; typedef int OsProcessId; typedef BView *OsView; typedef BWindow *OsWindow; typedef char OsChar; typedef BView *OsPainter; typedef BFont *OsFont; typedef BBitmap *OsBitmap; #define LGetCurrentProcess getpid class OsApplication { class OsApplicationPriv *d; protected: static OsApplication *Inst; public: OsApplication(int Args, const char **Arg); ~OsApplication(); static OsApplication *GetInst() { LAssert(Inst != NULL); return Inst; } }; // Threads typedef pthread_t OsThread; typedef pid_t OsThreadId; typedef pthread_mutex_t OsSemaphore; -#define LGetCurrentThread() pthread_self() +#define LGetCurrentThread() find_thread(NULL) LgiFunc OsThreadId GetCurrentThreadId(); #include "lgi/common/Message.h" // Sockets #define ValidSocket(s) ((s)>=0) #ifndef WIN32 #define INVALID_SOCKET -1 #endif typedef int OsSocket; /// Sleep the current thread for i milliseconds. #ifdef WIN32 LgiFunc void LSleep(DWORD i); #else LgiFunc void LSleep(uint32_t i); #endif #ifndef WIN32 #define atoi64 atoll #else #define atoi64 _atoi64 #endif #define _snprintf snprintf #define _vsnprintf vsnprintf #define wcscpy_s(dst, len, src) wcsncpy(dst, src, len) /// Drag and drop format for a file #define LGI_FileDropFormat "text/uri-list" #define LGI_StreamDropFormat "application/x-file-stream" // FIXME #define LGI_IllegalFileNameChars "\t\r\n/\\:*?\"<>|" #ifdef WINDOWS #define LGI_WideCharset "ucs-2" #else #define LGI_WideCharset "utf-32" #endif #ifdef _MSC_VER #define _MSC_VER_VS2019 2000 // MSVC++ 16.0 (speculative) #define _MSC_VER_VS2017 1910 // MSVC++ 15.0 #define _MSC_VER_VS2015 1900 // MSVC++ 14.0 #define _MSC_VER_VS2013 1800 // MSVC++ 12.0 #define _MSC_VER_VS2012 1700 // MSVC++ 11.0 #define _MSC_VER_VS2010 1600 // MSVC++ 10.0 #define _MSC_VER_VS2008 1500 // MSVC++ 9.0 #define _MSC_VER_VS2005 1400 // MSVC++ 8.0 #define _MSC_VER_VS2003 1310 // MSVC++ 7.1 #define _MSC_VER_VC7 1300 // MSVC++ 7.0 #define _MSC_VER_VC6 1200 // MSVC++ 6.0 #define _MSC_VER_VC5 1100 // MSVC++ 5.0 #if _MSC_VER >= _MSC_VER_VS2015 #define _MSC_VER_STR "14" #elif _MSC_VER >= _MSC_VER_VS2013 #define _MSC_VER_STR "12" #elif _MSC_VER >= _MSC_VER_VS2012 #define _MSC_VER_STR "11" #elif _MSC_VER >= _MSC_VER_VS2010 #define _MSC_VER_STR "10" #else #define _MSC_VER_STR "9" #endif #define LPrintfInt64 "%I64i" #define LPrintfHex64 "%I64x" #if LGI_64BIT #define LPrintfSizeT "%I64u" #define LPrintfSSizeT "%I64d" #else #define LPrintfSizeT "%u" #define LPrintfSSizeT "%d" #endif #else #define LPrintfInt64 "%lld" #define LPrintfHex64 "%llx" #define LPrintfSizeT "%zu" #define LPrintfSSizeT "%zi" #endif #ifndef SND_ASYNC #define SND_ASYNC 0x0001 #endif #define DOUBLE_CLICK_THRESHOLD 5 #define DOUBLE_CLICK_TIME 400 // Window flags #define GWF_VISIBLE 0x00000001 #define GWF_DISABLED 0x00000002 #define GWF_FOCUS 0x00000004 #define GWF_OVER 0x00000008 #define GWF_DROP_TARGET 0x00000010 #define GWF_SUNKEN 0x00000020 #define GWF_FLAT 0x00000040 #define GWF_RAISED 0x00000080 #define GWF_BORDER 0x00000100 #define GWF_DIALOG 0x00000200 #define GWF_DESTRUCTOR 0x00000400 #define GWF_QUIT_WND 0x00000800 // Menu flags #ifndef WIN32 #define ODS_SELECTED 0x1 #define ODS_DISABLED 0x2 #define ODS_CHECKED 0x4 #endif /// Edge type: Sunken #define SUNKEN 1 /// Edge type: Raised #define RAISED 2 /// Edge type: Chiseled #define CHISEL 3 /// Edge type: Flat #define FLAT 4 #ifdef WIN32 /// The directory separator character on Linux as a char #define DIR_CHAR '\\' /// The directory separator character on Linux as a string #define DIR_STR "\\" /// The path list separator character for Linux #define LGI_PATH_SEPARATOR ";" /// The pattern that matches all files in Linux #define LGI_ALL_FILES "*.*" /// The stardard extension for dynamically linked code #define LGI_LIBRARY_EXT "dll" /// The standard executable extension #define LGI_EXECUTABLE_EXT ".exe" #else /// The directory separator character on Linux as a char #define DIR_CHAR '/' /// The directory separator character on Linux as a string #define DIR_STR "/" /// The path list separator character for Linux #define LGI_PATH_SEPARATOR ":" /// The pattern that matches all files in Linux #define LGI_ALL_FILES "*" /// The stardard extension for dynamically linked code #ifdef MAC #define LGI_LIBRARY_EXT "dylib" #else #define LGI_LIBRARY_EXT "so" #endif /// The standard executable extension #define LGI_EXECUTABLE_EXT "" #endif /// The standard end of line string for Linux #define EOL_SEQUENCE "\n" /// Tests a char for being a slash #define IsSlash(c) (((c)=='/')||((c)=='\\')) /// Tests a char for being a quote #define IsQuote(c) (((c)=='\"')||((c)=='\'')) #ifndef WIN32 /// ID's returned by LgiMsg. /// \sa LgiMsg enum MessageBoxResponse { IDOK = 1, IDCANCEL = 2, IDABORT = 3, IDRETRY = 4, IDIGNORE = 5, IDYES = 6, IDNO = 7, IDTRYAGAIN = 10, IDCONTINUE = 11, }; /// Standard message box types. /// \sa LgiMsg enum MessageBoxType { MB_OK = 0, MB_OKCANCEL = 1, MB_ABORTRETRYIGNORE = 2, MB_YESNOCANCEL = 3, MB_YESNO = 4, MB_RETRYCANCEL = 5, MB_CANCELTRYCONTINUE = 6 }; #define MB_SYSTEMMODAL 0x1000 #endif /// The CTRL key is pressed /// \sa LKey #define LGI_VKEY_CTRL 0x001 /// The ALT key is pressed /// \sa LKey #define LGI_VKEY_ALT 0x002 /// The SHIFT key is pressed /// \sa LKey #define LGI_VKEY_SHIFT 0x004 /// The left mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_LEFT 0x008 /// The middle mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_MIDDLE 0x010 /// The right mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_RIGHT 0x020 /// The ctrl key is pressed /// \sa LMouse #define LGI_VMOUSE_CTRL 0x040 /// The alt key is pressed /// \sa LMouse #define LGI_VMOUSE_ALT 0x080 /// The shift key is pressed /// \sa LMouse #define LGI_VMOUSE_SHIFT 0x100 /// The mouse event is a down click /// \sa LMouse #define LGI_VMOUSE_DOWN 0x200 /// The mouse event is a double click /// \sa LMouse #define LGI_VMOUSE_DOUBLE 0x400 enum LgiKey { /* The keyboard syms have been cleverly chosen to map to ASCII */ LK_UNKNOWN = 0, LK_FIRST = 0, LK_BACKSPACE = B_BACKSPACE, LK_TAB = B_TAB, LK_RETURN = B_RETURN, LK_ESCAPE = B_ESCAPE, LK_SPACE = B_SPACE, LK_EXCLAIM = '!', LK_QUOTEDBL = '\"', LK_HASH = '#', LK_DOLLAR = '$', LK_AMPERSAND = '&', LK_QUOTE = '\'', LK_LEFTPAREN = '(', LK_RIGHTPAREN = ')', LK_ASTERISK = '*', LK_PLUS = '+', LK_COMMA = ',', LK_MINUS = '-', LK_PERIOD = '.', LK_SLASH = '/', LK_0 = '0', LK_1 = '1', LK_2 = '2', LK_3 = '3', LK_4 = '4', LK_5 = '5', LK_6 = '6', LK_7 = '7', LK_8 = '8', LK_9 = '9', LK_COLON = ':', LK_SEMICOLON = ';', LK_LESS = '<', LK_EQUALS = '=', LK_GREATER = '>', LK_QUESTION = '?', LK_AT = '@', /* Skip uppercase letters */ LK_LEFTBRACKET = '[', LK_BACKSLASH = '\\', LK_RIGHTBRACKET = ']', LK_CARET = '^', LK_UNDERSCORE = '_', LK_BACKQUOTE = 96, LK_a = 'a', LK_b = 'b', LK_c = 'c', LK_d = 'd', LK_e = 'e', LK_f = 'f', LK_g = 'g', LK_h = 'h', LK_i = 'i', LK_j = 'j', LK_k = 'k', LK_l = 'l', LK_m = 'm', LK_n = 'n', LK_o = 'o', LK_p = 'p', LK_q = 'q', LK_r = 'r', LK_s = 's', LK_t = 't', LK_u = 'u', LK_v = 'v', LK_w = 'w', LK_x = 'x', LK_y = 'y', LK_z = 'z', /* End of ASCII mapped keysyms */ /* Arrows + Home/End pad */ LK_HOME = B_HOME, LK_LEFT = B_LEFT_ARROW, LK_UP = B_UP_ARROW, LK_RIGHT = B_RIGHT_ARROW, LK_DOWN = B_DOWN_ARROW, LK_PAGEUP = B_PAGE_UP, LK_PAGEDOWN = B_PAGE_DOWN, LK_END = B_END, LK_INSERT = B_INSERT, LK_DELETE = B_DELETE, /* Numeric keypad */ LK_KEYPADENTER , LK_KP0 , LK_KP1 , LK_KP2 , LK_KP3 , LK_KP4 , LK_KP5 , LK_KP6 , LK_KP7 , LK_KP8 , LK_KP9 , LK_KP_PERIOD , LK_KP_DELETE , LK_KP_MULTIPLY , LK_KP_PLUS , LK_KP_MINUS , LK_KP_DIVIDE , LK_KP_EQUALS , /* Function keys */ LK_F1, LK_F2 , LK_F3 , LK_F4 , LK_F5 , LK_F6 , LK_F7 , LK_F8 , LK_F9 , LK_F10 , LK_F11 , LK_F12 , LK_F13 , LK_F14 , LK_F15 , /* Key state modifier keys */ LK_NUMLOCK , LK_CAPSLOCK , LK_SCROLLOCK , LK_LSHIFT , LK_RSHIFT , LK_LCTRL , LK_RCTRL , LK_LALT , LK_RALT , LK_LMETA , LK_RMETA , LK_LSUPER , /* "Windows" key */ LK_RSUPER , /* Miscellaneous function keys */ LK_HELP , LK_PRINT , LK_SYSREQ , LK_BREAK , LK_MENU , LK_UNDO , LK_REDO , LK_EURO , /* Some european keyboards */ LK_COMPOSE , /* Multi-key compose key */ LK_MODE , /* "Alt Gr" key (could be wrong) */ LK_POWER , /* Power Macintosh power key */ LK_CONTEXTKEY , /* Add any other keys here */ LK_LAST }; ///////////////////////////////////////////////////////////////////////////////////// // Externs #define swprintf_s swprintf #ifndef _MSC_VER #define vsprintf_s vsnprintf #endif #ifndef WINNATIVE // __CYGWIN__ #ifdef _MSC_VER #else // LgiFunc char *strnistr(char *a, char *b, int n); #define _strnicmp strncasecmp // LgiFunc int _strnicmp(char *a, char *b, int i); LgiFunc char *strupr(char *a); LgiFunc char *strlwr(char *a); LgiFunc int stricmp(const char *a, const char *b); #define _stricmp strcasecmp // LgiFunc int _stricmp(const char *a, const char *b); #endif #define sprintf_s snprintf #if __BIG_ENDIAN__ #define htonll(x) (x) #define ntohll(x) (x) #else #define htonll(x) ( ((uint64)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32) ) #define ntohll(x) ( ((uint64)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32) ) #endif #else LgiFunc class LViewI *GWindowFromHandle(OsView hWnd); LgiFunc int GetMouseWheelLines(); LgiFunc int WinPointToHeight(int Pt, HDC hDC = NULL); LgiFunc int WinHeightToPoint(int Ht, HDC hDC = NULL); #if _MSC_VER >= 1400 #define snprintf sprintf_s #endif /// Convert a string d'n'd format to an OS dependant integer. LgiFunc int FormatToInt(char *s); /// Convert a Os dependant integer d'n'd format to a string. LgiFunc char *FormatToStr(int f); #endif #endif diff --git a/src/common/Gdc2/Font/DisplayString.cpp b/src/common/Gdc2/Font/DisplayString.cpp --- a/src/common/Gdc2/Font/DisplayString.cpp +++ b/src/common/Gdc2/Font/DisplayString.cpp @@ -1,2275 +1,2269 @@ ////////////////////////////////////////////////////////////////////// // Includes #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/FontSelect.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/DisplayString.h" #include "lgi/common/PixelRops.h" #include "lgi/common/UnicodeString.h" #ifdef FontChange #undef FontChange #endif #ifdef LGI_SDL #include "ftsynth.h" #endif #if WINNATIVE static OsChar GDisplayStringDots[] = {'.', '.', '.', 0}; #endif //345678123456781234567812345678 // 2nd #define DEBUG_CHAR_AT 0 #define DEBUG_BOUNDS 0 #if defined(__GTK_H__) || (defined(MAC) && !defined(LGI_SDL)) #define DISPLAY_STRING_FRACTIONAL_NATIVE 1 #else #define DISPLAY_STRING_FRACTIONAL_NATIVE 0 #endif #if defined(__GTK_H__) struct Block : public LRect { /// This points to somewhere in Ds->Str OsChar *Str = NULL; /// Bytes in this block int Bytes = 0; /// Utf-8 characters in this block int Chars = 0; /// Alternative font to get characters from (NULL if using the display string's font) LFont *Fnt = NULL; /// Layout for this block. Shouldn't ever be NULL. But shouldn't crash otherwise. Gtk::PangoLayout *Hnd = NULL; ~Block() { if (Hnd) g_object_unref(Hnd); } }; struct GDisplayStringPriv { LDisplayString *Ds; LArray Blocks; bool Debug; int LastTabOffset; GDisplayStringPriv(LDisplayString *str) : Ds(str) { #if 0 Debug = Stristr(Ds->Str, "(Jumping).wma") != 0; #else Debug = false; #endif LastTabOffset = -1; } ~GDisplayStringPriv() { } void Create(Gtk::GtkPrintContext *PrintCtx) { auto *Fs = LFontSystem::Inst(); auto *Fnt = Ds->Font; auto Tbl = Fnt->GetGlyphMap(); int Chars = 0; LUtf8Ptr p(Ds->Str); auto *Start = p.GetPtr(); if (Tbl) { int32 w; Block *b = NULL; auto DisplayCtx = LFontSystem::Inst()->GetContext(); while ((w = (int32)p)) { LFont *f; if (w >= 0x80 && !_HasUnicodeGlyph(Tbl, w)) f = Fs->GetGlyph(w, Ds->Font); else f = Ds->Font; if (!b || (f != NULL && f != Fnt)) { // Finish old block if (b) { b->Bytes = p.GetPtr() - Start; b->Chars = Chars; Chars = 0; } Start = p.GetPtr(); if (f) { // Start new block... b = &Blocks.New(); b->Str = (char*)Start; b->Bytes = -1; // unknown at this point if (f != Ds->Font) // External font b->Fnt = f; // Create a pango layout if (PrintCtx) b->Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else b->Hnd = Gtk::pango_layout_new(DisplayCtx); } // else no font supports glyph Fnt = f; } // else no change in font p++; Chars++; } if (b) { // Finish the last block b->Bytes = p.GetPtr() - Start; b->Chars = Chars; } if (Debug) { // Print the block array for (size_t i=0; iStrWords) { p++; Chars++; } auto &b = Blocks.New(); b.Str = (char*)Start; b.Bytes = p.GetPtr() - Start; b.Chars = Chars; if (PrintCtx) b.Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else b.Hnd = Gtk::pango_layout_new(LFontSystem::Inst()->GetContext()); } /* This could get stuck in an infinite loop. Leaving out for the moment. for (auto &b: Blocks) { if (b.Hnd == NULL && b.Fnt == NULL) { Blocks.Length(0); goto Start; } } */ } void UpdateTabs(int Offset, int Size, bool Debug = false) { if (Ds->Font && Ds->Font->TabSize()) { int Len = 16; LastTabOffset = Offset; Gtk::PangoTabArray *t = Gtk::pango_tab_array_new(Len, true); if (t) { for (int i=0; i bool StringConvert(Out *&out, ssize_t &OutWords, const In *in, ssize_t InLen) { if (!in) { out = 0; OutWords = 0; return false; } auto InSz = sizeof(In); auto OutSz = sizeof(Out); // Work out input size ssize_t InWords; if (InLen >= 0) InWords = InLen; else for (InWords = 0; in[InWords]; InWords++) ; if (InSz == OutSz) { // No conversion optimization out = (Out*)Strdup(in, InWords); OutWords = out ? InWords : 0; return out != 0; } else { // Convert the string to new word size static const char *Cp[] = { NULL, "utf-8", "utf-16", NULL, "utf-32"}; LAssert(OutSz <= 4 && InSz <= 4 && Cp[OutSz] && Cp[InSz]); out = (Out*) LNewConvertCp(Cp[OutSz], in, Cp[InSz], InWords*sizeof(In)); OutWords = Strlen(out); return out != NULL; } return false; } ////////////////////////////////////////////////////////////////////// #define SubtractPtr(a, b) ( ((a)-(b)) / sizeof(*a) ) #define VisibleTabChar 0x2192 #define IsTabChar(c) (c == '\t') // || (c == VisibleTabChar && VisibleTab)) #if USE_CORETEXT #include void LDisplayString::CreateAttrStr() { if (!Wide) return; if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } wchar_t *w = Wide; CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)w, StrlenW(w) * sizeof(*w), kCFStringEncodingUTF32LE, false); if (string) { CFDictionaryRef attributes = Font->GetAttributes(); if (attributes) AttrStr = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); // else LAssert(0); CFRelease(string); } } #endif LDisplayString::LDisplayString(LFont *f, const char *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str) { LAssert(StrWords >= 0); if (StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); } #elif defined MAC && !defined(LGI_SDL) Hnd = 0; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && StrWords > 0) { #if USE_CORETEXT CreateAttrStr(); #else ATSUCreateTextLayout(&Hnd); #endif } #endif } LDisplayString::LDisplayString(LFont *f, const char16 *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str && StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); #elif defined MAC && !defined(LGI_SDL) Hnd = NULL; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && StrWords > 0) { #if USE_CORETEXT CreateAttrStr(); #else OSStatus e = ATSUCreateTextLayout(&Hnd); if (e) printf("%s:%i - ATSUCreateTextLayout failed with %i.\n", _FL, (int)e); #endif } #endif } #ifdef _MSC_VER LDisplayString::LDisplayString(LFont *f, const uint32_t *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str && StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); #endif } #endif LDisplayString::~LDisplayString() { #if defined(LGI_SDL) Img.Reset(); #elif defined __GTK_H__ DeleteObj(d); #elif defined MAC #if USE_CORETEXT if (Hnd) { CFRelease(Hnd); Hnd = NULL; } if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } #else if (Hnd) ATSUDisposeTextLayout(Hnd); #endif #endif DeleteArray(Str); #if LGI_DSP_STR_CACHE DeleteArray(Wide); #endif } void LDisplayString::DrawWhiteSpace(LSurface *pDC, char Ch, LRect &r) { if (Ch == '\t') { r.Inset(3, 3); if (r.Y()/2 == 0) r.y2++; int Cy = (r.Y() >> 1); pDC->Line(r.x1, r.y1+Cy, r.x2, r.y1+Cy); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y1); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y2); } else // Space { int x = r.x1 + (r.X()>>1) - 1; int Cy = r.y1 + (int)ceil(Font->Ascent()) - 2; pDC->Rectangle(x, Cy, x+1, Cy+1); } } void LDisplayString::Layout(bool Debug) { if (LaidOut || !Font) return; LaidOut = 1; #if defined(LGI_SDL) FT_Face Fnt = Font->Handle(); FT_Error error; if (!Fnt || !Str) return; // Create an array of glyph indexes LArray Glyphs; for (OsChar *s = Str; *s; s++) { FT_UInt index = FT_Get_Char_Index(Fnt, *s); if (index) Glyphs.Add(index); } // Measure the string... LPoint Sz; int FontHt = Font->GetHeight(); int AscentF = (int) (Font->Ascent() * FScale); int LoadMode = FT_LOAD_FORCE_AUTOHINT; for (unsigned i=0; iglyph->metrics.horiBearingY; Sz.x += Fnt->glyph->metrics.horiAdvance; Sz.y = MAX(Sz.y, PyF + Fnt->glyph->metrics.height); } } // Create the memory context to draw into xf = Sz.x; x = ((Sz.x + FScale - 1) >> FShift) + 1; yf = FontHt << FShift; y = FontHt; // ((Sz.y + FScale - 1) >> FShift) + 1; if (Img.Reset(new LMemDC(x, y, CsIndex8))) { // Clear the context to black Img->Colour(0); Img->Rectangle(); bool IsItalic = Font->Italic(); int CurX = 0; int FBaseline = Fnt->size->metrics.ascender; for (unsigned i=0; iglyph); error = FT_Render_Glyph(Fnt->glyph, FT_RENDER_MODE_NORMAL); if (error == 0) { FT_Bitmap &bmp = Fnt->glyph->bitmap; if (bmp.buffer) { // Copy rendered glyph into our image memory int Px = (CurX + (FScale >> 1)) >> FShift; int PyF = AscentF - Fnt->glyph->metrics.horiBearingY; int Py = PyF >> FShift; int Skip = 0; if (Fnt->glyph->format == FT_GLYPH_FORMAT_BITMAP) { Px += Fnt->glyph->bitmap_left; Skip = Fnt->glyph->bitmap_left < 0 ? -Fnt->glyph->bitmap_left : 0; Py = (AscentF >> FShift) - Fnt->glyph->bitmap_top; } LAssert(Px + bmp.width <= Img->X()); for (int y=0; y= 0); out += Px+Skip; memcpy(out, in+Skip, bmp.width-Skip); } /* else { LAssert(!"No scanline?"); break; } */ } } if (i < Glyphs.Length() - 1) { FT_Vector kerning; FT_Get_Kerning(Fnt, Glyphs[i], Glyphs[i+1], FT_KERNING_DEFAULT, &kerning); CurX += Fnt->glyph->metrics.horiAdvance + kerning.x; } else { CurX += Fnt->glyph->metrics.horiAdvance; } } } } } else LgiTrace("::Layout Create MemDC failed\n"); #elif defined(__GTK_H__) y = Font->GetHeight(); yf = y * PANGO_SCALE; if (!d->Blocks.Length() || !Font->Handle()) return; LUtf8Ptr Utf(Str); int32 Wide; while (*Utf.GetPtr()) { Wide = Utf; if (!Wide) { LgiTrace("%s:%i - Not utf8\n", _FL); return; } Utf++; } LFontSystem *FSys = LFontSystem::Inst(); Gtk::pango_context_set_font_description(FSys->GetContext(), Font->Handle()); int TabSizePx = Font->TabSize(); int TabSizeF = TabSizePx * FScale; int TabOffsetF = DrawOffsetF % TabSizeF; int OffsetF = TabOffsetF ? TabSizeF - TabOffsetF : 0; d->UpdateTabs(OffsetF / FScale, Font->TabSize()); if (Font->Underline()) { Gtk::PangoAttrList *attrs = Gtk::pango_attr_list_new(); Gtk::PangoAttribute *attr = Gtk::pango_attr_underline_new(Gtk::PANGO_UNDERLINE_SINGLE); Gtk::pango_attr_list_insert(attrs, attr); for (auto &b: d->Blocks) Gtk::pango_layout_set_attributes(b.Hnd, attrs); Gtk::pango_attr_list_unref(attrs); } int Fx = 0; for (auto &b: d->Blocks) { int bx = 0, by = 0; if (b.Hnd) { if (!LIsUtf8(b.Str, b.Bytes)) { LgiTrace("Invalid UTF8: '%.*S'\n", (int)b.Bytes, b.Str); } else { Gtk::pango_layout_set_text(b.Hnd, b.Str, b.Bytes); } Gtk::pango_layout_get_size(b.Hnd, &bx, &by); } else if (b.Fnt) { b.Fnt->_Measure(bx, by, b.Str, b.Bytes); bx <<= FShift; by <<= FShift; } b.ZOff(bx-1, by-1); b.Offset(Fx, 0); xf += bx; yf = MAX(yf, by); } x = (xf + PANGO_SCALE - 1) / PANGO_SCALE; #if 1 y = Font->GetHeight(); #else y = (yf + PANGO_SCALE - 1) / PANGO_SCALE; #endif if (y > Font->GetHeight()) { printf("%s:%i - Height error: %i > %i\n", _FL, y, Font->GetHeight()); } #elif defined MAC && !defined(LGI_SDL) #if USE_CORETEXT int height = Font->GetHeight(); y = height; if (AttrStr) { LAssert(!Hnd); Hnd = CTLineCreateWithAttributedString(AttrStr); if (Hnd) { CGFloat ascent = 0.0; CGFloat descent = 0.0; CGFloat leading = 0.0; double width = CTLineGetTypographicBounds(Hnd, &ascent, &descent, &leading); x = ceil(width); xf = width * FScale; yf = height * FScale; } } #else if (!Hnd || !Str) return; OSStatus e = ATSUSetTextPointerLocation(Hnd, Str, 0, len, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetTextPointerLocation failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } e = ATSUSetRunStyle(Hnd, Font->Handle(), 0, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetRunStyle failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } ATSUTextMeasurement fTextBefore; ATSUTextMeasurement fTextAfter; if (pDC) { OsPainter dc = pDC->Handle(); ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } ATSUTab Tabs[32]; for (int i=0; iTabSize()) << 16; Tabs[i].tabType = kATSULeftTab; } e = ATSUSetTabArray(Hnd, Tabs, CountOf(Tabs)); if (e) printf("%s:%i - ATSUSetTabArray failed (e=%i)\n", _FL, (int)e); e = ATSUGetUnjustifiedBounds( Hnd, kATSUFromTextBeginning, kATSUToTextEnd, &fTextBefore, &fTextAfter, &fAscent, &fDescent); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUGetUnjustifiedBounds failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } xf = fTextAfter - fTextBefore; yf = fAscent + fDescent; x = (xf + 0xffff) >> 16; y = (yf + 0xffff) >> 16; ATSUSetTransientFontMatching(Hnd, true); #endif #elif defined(HAIKU) if (!Font) { LgiTrace("%s:%i - Missing pointer: %p\n", _FL, Font); return; } BFont *fnt = Font->Handle(); if (!fnt) { LgiTrace("%s:%i - Missing handle. %p/%p\n", _FL, fnt); return; } int tabSize = Font->TabSize() ? Font->TabSize() : 32; font_height height = {0}; fnt->GetHeight(&height); yf = y = height.ascent + height.descent + height.leading; if (!Str) return; LUtf8Ptr start(Str); int isTab = -1; for (LUtf8Ptr p(Str); true; p++) { int32_t ch = p; if (isTab < 0) { isTab = IsTabChar(ch); } else if (!ch || IsTabChar(ch) ^ isTab) { auto &l = Info.New(); l.Str = start.GetPtr(); l.Len = p.GetPtr() - start.GetPtr(); const char *strArr[] = { l.Str }; const int32 strLen[] = { l.Len }; float width[1] = { 0 }; fnt->GetStringWidths(strArr, strLen, 1, width); if (isTab) { // Handle tab(s) for (int t=0; tHandle()) Font->Create(); y = Font->GetHeight(); LFontSystem *Sys = LFontSystem::Inst(); if (Sys && Str) { LFont *f = Font; bool GlyphSub = Font->SubGlyphs(); Info[i].Str = Str; int TabSize = Font->TabSize() ? Font->TabSize() : 32; bool WasTab = IsTabChar(*Str); f = GlyphSub ? Sys->GetGlyph(*Str, Font) : Font; if (f && f != Font) { f->Size(Font->Size()); f->SetWeight(Font->GetWeight()); if (!f->Handle()) f->Create(); } bool Debug = WasTab; uint32_t u32; for (LUnicodeString u(Str, StrWords); true; u++) { u32 = *u; LFont *n = GlyphSub ? Sys->GetGlyph(u32, Font) : Font; bool Change = n != f || // The font changed (IsTabChar(u32) ^ WasTab) || // Entering/leaving a run of tabs !u32 || // Hit a NULL character (u.Get() - Info[i].Str) >= 1000; // This is to stop very long segments not rendering if (Change) { // End last segment if (n && n != Font) { n->Size(Font->Size()); n->SetWeight(Font->GetWeight()); if (!n->Handle()) n->Create(); } Info[i].Len = (int) (u.Get() - Info[i].Str); if (Info[i].Len) { if (WasTab) { // Handle tab(s) for (int t=0; tGetHeight() > Font->GetHeight())) { Info[i].SizeDelta = -1; f->PointSize(Font->PointSize() + Info[i].SizeDelta); f->Create(); } #endif if (!f) { // no font, so ? out the chars... as they aren't available anyway // printf("Font Cache Miss, Len=%i\n\t", Info[i].Len); m = Font; for (int n=0; n_Measure(sx, sy, Info[i].Str, Info[i].Len); x += Info[i].X = sx > 0xffff ? 0xffff : sx; } auto Ch = Info[i].First(); Info[i].FontId = !f || Font == f ? 0 : Sys->Lut[Ch]; i++; } f = n; // Start next segment WasTab = IsTabChar(u32); Info[i].Str = u.Get(); } if (!u32) break; } if (Info.Length() > 0 && Info.Last().Len == 0) { Info.Length(Info.Length()-1); } } xf = x; yf = y; #endif } int LDisplayString::GetDrawOffset() { return DrawOffsetF >> FShift; } void LDisplayString::SetDrawOffset(int Px) { if (LaidOut) LAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Px << FShift; } bool LDisplayString::ShowVisibleTab() { return VisibleTab; } void LDisplayString::ShowVisibleTab(bool i) { VisibleTab = i; } bool LDisplayString::IsTruncated() { return AppendDots; } void LDisplayString::TruncateWithDots(int Width) { Layout(); #if defined __GTK_H__ int Fx = 0; int Fwid = Width << FShift; for (auto &b: d->Blocks) { if (Fwid < Fx + b.X()) { if (b.Hnd) { Gtk::pango_layout_set_ellipsize(b.Hnd, Gtk::PANGO_ELLIPSIZE_END); Gtk::pango_layout_set_width(b.Hnd, Fwid - Fx); Gtk::pango_layout_set_single_paragraph_mode(b.Hnd, true); } else if (b.Fnt) { } break; } Fx += b.X(); } #elif WINNATIVE if (Width < X() + 8) { ssize_t c = CharAt(Width); if (c >= 0 && c < StrWords) { if (c > 0) c--; // fudge room for dots if (c > 0) c--; AppendDots = 1; if (Info.Length()) { int Width = 0; int Pos = 0; for (int i=0; i= Pos && c < Pos + Info[i].Len) { Info[i].Len = (int) (c - Pos); Info[i].Str[Info[i].Len] = 0; LFont *f = Font; if (Info[i].FontId) { LFontSystem *Sys = LFontSystem::Inst(); f = Sys->Font[Info[i].FontId]; f->PointSize(Font->PointSize() + Info[i].SizeDelta); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { int Sx, Sy; f->_Measure(Sx, Sy, Info[i].Str, Info[i].Len); Info[i].X = Sx; Width += Info[i].X; } Info.Length(i + 1); break; } Pos += Info[i].Len; Width += Info[i].X; } int DotsX, DotsY; Font->_Measure(DotsX, DotsY, GDisplayStringDots, 3); x = Width + DotsX; } } } #elif defined(LGI_SDL) #elif defined(MAC) #if USE_CORETEXT if (Hnd) { /* CFAttributedStringRef truncationString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), Font->GetAttributes()); if (truncationString) { CTLineRef truncationToken = CTLineCreateWithAttributedString(truncationString); CFRelease(truncationString); if (truncationToken) { CTLineRef TruncatedLine = CTLineCreateTruncatedLine(Hnd, Width, kCTLineTruncationEnd, truncationToken); CFRelease(truncationToken); if (TruncatedLine) { CFRelease(Hnd); Hnd = TruncatedLine; } } } */ } #endif #endif } ssize_t LDisplayString::CharAt(int Px, LPxToIndexType Type) { Layout(); if (Px < 0) { return 0; } else if (Px >= (int)x) { #if defined __GTK_H__ if (Str) { LUtf8Str u(Str); return u.GetChars(); } return 0; #else #if LGI_DSP_STR_CACHE return WideWords; #else return StrWords; #endif #endif } int Status = -1; #if defined(__GTK_H__) int Fx = 0; int Fpos = Px << FShift; Status = 0; for (auto &b: d->Blocks) { int Index = 0, Trailing = 0; int Foffset = Fpos - Fx; if (b.Hnd && Gtk::pango_layout_xy_to_index(b.Hnd, Foffset, 0, &Index, &Trailing)) { if (d->Debug) printf("CharAt(%g) x=%g Status=%i Foffset=%g index=%i trailing=%i\n", (double)Fpos/FScale, (double)b.X()/FScale, Status, (double)Foffset/FScale, Index, Trailing); LUtf8Str u(Str); while ((OsChar*)u.GetPtr() < Str + Index + Trailing) { u++; Status++; } return Status; } else { if (d->Debug) printf("CharAt(%g) x=%g Status=%i Chars=%i\n", (double)Fpos/FScale, (double)b.X()/FScale, Status, b.Chars); Status += b.Chars; } Fx += b.X(); } #elif defined(LGI_SDL) LAssert(!"Impl me"); #elif defined(MAC) if (Hnd && Str) { #if USE_CORETEXT CGPoint pos = { (CGFloat)Px, 1.0f }; CFIndex utf16 = CTLineGetStringIndexForPosition(Hnd, pos); // 'utf16' is in UTF-16, and the API needs to return a UTF-32 index... // So convert between the 2 here... LAssert(Str != NULL); int utf32 = 0; for (int i=0; Str[i] && iTabSize() ? Font->TabSize() : 32; int Cx = 0; int Char = 0; #if DEBUG_CHAR_AT printf("CharAt(%i) Str='%s'\n", Px, Str); #endif for (int i=0; i= Cx && Px < Cx + Info[i].X) { // The position is in this block of characters if (IsTabChar(Info[i].Str[0])) { // Search through tab block for (int t=0; t= Cx && Px < Cx + TabX) { Status = Char; #if DEBUG_CHAR_AT printf("\tIn tab block %i\n", i); #endif break; } Cx += TabX; Char++; } } else { // Find the pos in this block LFont *f = Font; #if defined(WIN32) if (Info[i].FontId) { f = Sys->Font[Info[i].FontId]; f->Colour(Font->Fore(), Font->Back()); f->Size(Font->Size()); if (!f->Handle()) { f->Create(); } } #endif int Fit = f->_CharAt(Px - Cx, Info[i].Str, Info[i].Len, Type); #if DEBUG_CHAR_AT printf("\tNon tab block %i, Fit=%i, Px-Cx=%i-%i=%i, Str='%.5s'\n", i, Fit, Px, Cx, Px-Cx, Info[i].Str); #endif if (Fit >= 0) Status = Char + Fit; else Status = -1; break; } } else { // Not in this block, skip the whole lot Cx += Info[i].X; Char += Info[i].Len; } } if (Status < 0) { Status = Char; } } #endif return Status; } ssize_t LDisplayString::Length() { return StrWords; } int LDisplayString::X() { Layout(); return x; } int LDisplayString::Y() { Layout(); return y; } LPoint LDisplayString::Size() { Layout(); return LPoint(x, y); } #if defined LGI_SDL template bool CompositeText8Alpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8_t *Div255 = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = Div255[a * fore_px.r]; map[a].g = Div255[a * fore_px.g]; map[a].b = Div255[a * fore_px.b]; map[a].a = a; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = (oma * back_px.r) + (a * fore_px.r) / 255; map[a].g = (oma * back_px.g) + (a * fore_px.g) / 255; map[a].b = (oma * back_px.b) + (a * fore_px.b) / 255; map[a].a = 255; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *d = ((OutPx*) (*Out)[py + y]) + Clip.DstClip.x1; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LAssert((uint8_t*)d >= StartOfBuffer); if (Font->Transparent()) { uint8_t a, o; OutPx *s; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *d = map[a]; break; default: // Blend o = 255 - a; s = map + a; #define NonPreMulOver32NoAlpha(c) d->c = ((s->c * a) + (Div255[d->c * 255] * o)) / 255 NonPreMulOver32NoAlpha(r); NonPreMulOver32NoAlpha(g); NonPreMulOver32NoAlpha(b); break; } d++; } } else { while (i < e) { // Copy rop *d++ = map[*i++]; } } LAssert((uint8_t*)d <= EndOfBuffer); } return true; } template bool CompositeText8NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { LRgba32 map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8_t *DivLut = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = DivLut[a * fore_px.r]; map[a].g = DivLut[a * fore_px.g]; map[a].b = DivLut[a * fore_px.b]; map[a].a = a; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = DivLut[(oma * back_px.r) + (a * fore_px.r)]; map[a].g = DivLut[(oma * back_px.g) + (a * fore_px.g)]; map[a].b = DivLut[(oma * back_px.b) + (a * fore_px.b)]; map[a].a = 255; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (int y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = (OutPx*) (*Out)[py + y]; if (!dst) continue; dst += Clip.DstClip.x1; if ((uint8_t*)dst < StartOfBuffer) continue; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LRgba32 *src; LAssert((uint8_t*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8_t a, oma; while (i < e) { // Alpha blend map and output pixel a = *i++; src = map + a; switch (a) { case 0: break; case 255: // Copy dst->r = src->r; dst->g = src->g; dst->b = src->b; break; default: // Blend OverPm32toPm24(src, dst); break; } dst++; } } else { while (i < e) { // Copy rop src = map + *i++; dst->r = src->r; dst->g = src->g; dst->b = src->b; dst++; } } LAssert((uint8_t*)dst <= EndOfBuffer); } return true; } template bool CompositeText5NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... #define MASK_5BIT 0x1f #define MASK_6BIT 0x3f // Create colour map of the foreground/background colours uint8_t *Div255 = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = (int)Div255[a * fore_px.r] >> 3; map[a].g = (int)Div255[a * fore_px.g] >> 2; map[a].b = (int)Div255[a * fore_px.b] >> 3; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = Div255[(oma * back_px.r) + (a * fore_px.r)] >> 3; map[a].g = Div255[(oma * back_px.g) + (a * fore_px.g)] >> 2; map[a].b = Div255[(oma * back_px.b) + (a * fore_px.b)] >> 3; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = ((OutPx*) (*Out)[py + y]); if (!dst) continue; dst += Clip.DstClip.x1; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LAssert((uint8_t*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8_t a; OutPx *src; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *dst = map[a]; break; default: { // Blend #if 0 uint8_t oma = 255 - a; src = map + a; LRgb24 d = { G5bitTo8bit(dst->r), G6bitTo8bit(dst->g), G5bitTo8bit(dst->b)}; LRgb24 s = { G5bitTo8bit(src->r), G6bitTo8bit(src->g), G5bitTo8bit(src->b)}; dst->r = Div255[(oma * d.r) + (a * s.r)] >> 3; dst->g = Div255[(oma * d.g) + (a * s.g)] >> 2; dst->b = Div255[(oma * d.b) + (a * s.b)] >> 3; #else uint8_t a5 = a >> 3; uint8_t a6 = a >> 2; uint8_t oma5 = MASK_5BIT - a5; uint8_t oma6 = MASK_6BIT - a6; src = map + a; dst->r = ((oma5 * (uint8_t)dst->r) + (a5 * (uint8_t)src->r)) / MASK_5BIT; dst->g = ((oma6 * (uint8_t)dst->g) + (a6 * (uint8_t)src->g)) / MASK_6BIT; dst->b = ((oma5 * (uint8_t)dst->b) + (a5 * (uint8_t)src->b)) / MASK_5BIT; #endif break; } } dst++; } } else { while (i < e) { // Copy rop *dst++ = map[*i++]; } } LAssert((uint8_t*)dst <= EndOfBuffer); } return true; } #endif void LDisplayString::Draw(LSurface *pDC, int px, int py, LRect *r, bool Debug) { Layout(); #if DISPLAY_STRING_FRACTIONAL_NATIVE // GTK / Mac use fractional pixels, so call the fractional version: LRect rc; if (r) { rc = *r; rc.x1 <<= FShift; rc.y1 <<= FShift; #if defined(MAC) && !defined(__GTK_H__) rc.x2 <<= FShift; rc.y2 <<= FShift; #else rc.x2 = (rc.x2 + 1) << FShift; rc.y2 = (rc.y2 + 1) << FShift; #endif } FDraw(pDC, px << FShift, py << FShift, r ? &rc : NULL, Debug); #elif defined(HAIKU) if (!Font || !pDC) { LgiTrace("%s:%i - No ptr: %p/%p.\n", _FL, Font, pDC); return; } BFont *fnt = Font->Handle(); BView *view = pDC->Handle(); if (!fnt || !view) { LgiTrace("%s:%i - No handle: %p/%p(%s).\n", _FL, fnt, view, pDC->GetClass()); return; } - if (!Info.Length()) - { - LgiTrace("%s:%i - No layout for '%s'.\n", _FL, Str); - return; - } - font_height height = {0}; fnt->GetHeight(&height); if (!Font->Transparent()) { view->SetHighColor(Font->Back()); if (r) view->FillRect(*r); else view->FillRect(BRect(px, py, px+x, py+y)); } view->SetFont(fnt); view->SetHighColor(Font->Fore()); int cx = px; for (auto &i: Info) { view->DrawString(i.Str, i.Len, BPoint(cx, py + height.ascent)); cx += i.X; } #elif defined(LGI_SDL) if (Img && pDC && pDC->Y() > 0 && (*pDC)[0]) { int Ox = 0, Oy = 0; pDC->GetOrigin(Ox, Oy); LBlitRegions Clip(pDC, px-Ox, py-Oy, Img, r); LColourSpace DstCs = pDC->GetColourSpace(); switch (DstCs) { #define DspStrCase(px_fmt, comp) \ case Cs##px_fmt: \ CompositeText##comp(pDC, Img, Font, px-Ox, py-Oy, Clip); \ break; DspStrCase(Rgb16, 5NoAlpha) DspStrCase(Bgr16, 5NoAlpha) DspStrCase(Rgb24, 8NoAlpha) DspStrCase(Bgr24, 8NoAlpha) DspStrCase(Rgbx32, 8NoAlpha) DspStrCase(Bgrx32, 8NoAlpha) DspStrCase(Xrgb32, 8NoAlpha) DspStrCase(Xbgr32, 8NoAlpha) DspStrCase(Rgba32, 8Alpha) DspStrCase(Bgra32, 8Alpha) DspStrCase(Argb32, 8Alpha) DspStrCase(Abgr32, 8Alpha) default: LgiTrace("%s:%i - LDisplayString::Draw Unsupported colour space.\n", _FL); // LAssert(!"Unsupported colour space."); break; #undef DspStrCase } } else LgiTrace("::Draw argument error.\n"); #elif defined WINNATIVE if (Info.Length() && pDC && Font) { LFontSystem *Sys = LFontSystem::Inst(); COLOUR Old = pDC->Colour(); int TabSize = Font->TabSize() ? Font->TabSize() : 32; int Ox = px; LColour cFore = Font->Fore(); LColour cBack = Font->Back(); LColour cWhitespace; if (VisibleTab) { cWhitespace = Font->WhitespaceColour(); LAssert(cWhitespace.IsValid()); } for (int i=0; iFont[Info[i].FontId]; f->Colour(cFore, cBack); auto Sz = Font->Size(); Sz.Value += Info[i].SizeDelta; f->Size(Sz); f->Transparent(Font->Transparent()); f->Underline(Font->Underline()); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { LRect b; if (r) { b.x1 = i ? px : r->x1; b.y1 = r->y1; b.x2 = i < Info.Length() - 1 ? px + Info[i].X - 1 : r->x2; b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Info[i].X - 1; b.y2 = py + Y() - 1; } if (b.Valid()) { if (IsTabChar(*Info[i].Str)) { // Invisible tab... draw blank space if (!Font->Transparent()) { pDC->Colour(cBack); pDC->Rectangle(&b); } if (VisibleTab) { int X = px; for (int n=0; nColour(cWhitespace); DrawWhiteSpace(pDC, '\t', r); X += Dx; } } } else { // Draw the character(s) LColour Fg = f->Fore(); LAssert(Fg.IsValid()); f->_Draw(pDC, px, py, Info[i].Str, Info[i].Len, &b, Fg); if (VisibleTab) { OsChar *start = Info[i].Str; OsChar *s = start; OsChar *e = s + Info[i].Len; int Sp = -1; while (s < e) { if (*s == ' ') { int Sx, Sy; if (Sp < 0) f->_Measure(Sp, Sy, s, 1); f->_Measure(Sx, Sy, start, (int)(s - start)); LRect r(0, 0, Sp-1, Sy-1); r.Offset(px + Sx, py); pDC->Colour(cWhitespace); DrawWhiteSpace(pDC, ' ', r); } s++; } } } } } // Inc my position px += Info[i].X; } if (AppendDots) { int Sx, Sy; Font->_Measure(Sx, Sy, GDisplayStringDots, 3); LRect b; if (r) { b.x1 = px; b.y1 = r->y1; b.x2 = min(px + Sx - 1, r->x2); b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Sx - 1; b.y2 = py + Y() - 1; } LColour Fg = Font->Fore(); Font->_Draw(pDC, px, py, GDisplayStringDots, 3, &b, Fg); } pDC->Colour(Old); } else if (r && Font && !Font->Transparent()) { pDC->Colour(Font->Back()); pDC->Rectangle(r); } #endif } int LDisplayString::GetDrawOffsetF() { return DrawOffsetF; } void LDisplayString::SetDrawOffsetF(int Fpx) { if (LaidOut) LAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Fpx; } int LDisplayString::FX() { Layout(); return xf; } int LDisplayString::FY() { Layout(); return yf; } LPoint LDisplayString::FSize() { Layout(); return LPoint(xf, yf); } void LDisplayString::FDraw(LSurface *pDC, int fx, int fy, LRect *frc, bool Debug) { Layout(Debug); #if !DISPLAY_STRING_FRACTIONAL_NATIVE // Windows doesn't use fractional pixels, so call the integer version: LRect rc; if (frc) { rc = *frc; rc.x1 >>= FShift; rc.y1 >>= FShift; rc.x2 >>= FShift; rc.y2 >>= FShift; } Draw(pDC, fx >> FShift, fy >> FShift, frc ? &rc : NULL, Debug); #elif defined __GTK_H__ Gtk::cairo_t *cr = pDC->Handle(); if (!cr) { LAssert(!"Can't get cairo."); return; } pango_context_set_font_description(LFontSystem::Inst()->GetContext(), Font->Handle()); cairo_save(cr); LColour b = Font->Back(); double Dx = ((double)fx / FScale); double Dy = ((double)fy / FScale); double rx, ry, rw, rh; if (!Font->Transparent()) { // Background fill cairo_set_source_rgb(cr, (double)b.r()/255.0, (double)b.g()/255.0, (double)b.b()/255.0); cairo_new_path(cr); if (frc) { rx = ((double)frc->x1 / FScale); ry = ((double)frc->y1 / FScale); rw = (double)frc->X() / FScale; rh = (double)frc->Y() / FScale; } else { rx = Dx; ry = Dy; rw = x; rh = y; } cairo_rectangle(cr, rx, ry, rw, rh); cairo_fill(cr); if (frc) { cairo_rectangle(cr, rx, ry, rw, rh); cairo_clip(cr); } } else if (frc) { rx = ((double)frc->x1 / FScale); ry = ((double)frc->y1 / FScale); rw = (double)frc->X() / FScale; rh = (double)frc->Y() / FScale; cairo_rectangle(cr, rx, ry, rw, rh); cairo_clip(cr); } cairo_translate(cr, Dx, Dy); LColour f = Font->Fore(); for (auto &b: d->Blocks) { double Bx = ((double)b.X()) / FScale; #if DEBUG_BOUNDS double By = Font->GetHeight(); cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); cairo_rectangle(cr, 0, 0, Bx, By); cairo_rectangle(cr, 1, 1, Bx - 2.0, By - 2.0); cairo_set_fill_rule(cr, Gtk::CAIRO_FILL_RULE_EVEN_ODD); cairo_fill(cr); #endif if (b.Hnd) { cairo_set_source_rgb( cr, (double)f.r()/255.0, (double)f.g()/255.0, (double)f.b()/255.0); pango_cairo_show_layout(cr, b.Hnd); if (VisibleTab && Str) { LUtf8Str Ptr(Str); auto Ws = Font->WhitespaceColour(); pDC->Colour(Ws); for (int32 u, Idx = 0 ; (u = Ptr); Idx++) { if (IsTabChar(u) || u == ' ') { Gtk::PangoRectangle pos; Gtk::pango_layout_index_to_pos(b.Hnd, Idx, &pos); LRect r(0, 0, pos.width / FScale, pos.height / FScale); r.Offset(pos.x / FScale, pos.y / FScale); DrawWhiteSpace(pDC, u, r); } Ptr++; } } } else if (b.Fnt) { b.Fnt->Transparent(Font->Transparent()); b.Fnt->Back(Font->Back()); b.Fnt->_Draw(pDC, 0, 0, b.Str, b.Bytes, NULL, f); } else LAssert(0); cairo_translate(cr, Bx, 0); } cairo_restore(cr); #elif defined MAC && !defined(LGI_SDL) int Ox = 0, Oy = 0; int px = fx >> FShift; int py = fy >> FShift; LRect rc; if (frc) rc.Set( frc->x1 >> FShift, frc->y1 >> FShift, frc->x2 >> FShift, frc->y2 >> FShift); if (pDC && !pDC->IsScreen()) pDC->GetOrigin(Ox, Oy); if (pDC && !Font->Transparent()) { LColour Old = pDC->Colour(Font->Back()); if (frc) { pDC->Rectangle(&rc); } else { LRect a(px, py, px + x - 1, py + y - 1); pDC->Rectangle(&a); } pDC->Colour(Old); } if (Hnd && pDC && StrWords > 0) { OsPainter dc = pDC->Handle(); #if USE_CORETEXT int y = (pDC->Y() - py + Oy); CGContextSaveGState(dc); pDC->Colour(Font->Fore()); if (pDC->IsScreen()) { if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } } CGFloat Tx = (CGFloat)fx / FScale - Ox; CGFloat Ty = (CGFloat)y - Font->Ascent(); CGContextSetTextPosition(dc, Tx, Ty); CTLineDraw(Hnd, dc); CGContextRestoreGState(dc); #else ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) { printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } else { // Set style attr ATSURGBAlphaColor c; LColour Fore = Font->Fore(); c.red = (double) Fore.r() / 255.0; c.green = (double) Fore.g() / 255.0; c.blue = (double) Fore.b() / 255.0; c.alpha = 1.0; ATSUAttributeTag Tags[] = {kATSURGBAlphaColorTag}; ATSUAttributeValuePtr Values[] = {&c}; ByteCount Lengths[] = {sizeof(c)}; e = ATSUSetAttributes( Font->Handle(), CountOf(Tags), Tags, Lengths, Values); if (e) { printf("%s:%i - Error setting font attr (e=%i)\n", _FL, (int)e); } else { int y = (pDC->Y() - py + Oy); if (pDC->IsScreen()) { CGContextSaveGState(dc); if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, fx - Long2Fix(Ox), Long2Fix(y) - fAscent); CGContextRestoreGState(dc); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, Long2Fix(px - Ox), Long2Fix(y) - fAscent); if (frc) CGContextRestoreGState(dc); } if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUDrawText failed with %i, len=%i, str=%.20s\n", _FL, (int)e, len, a); DeleteArray(a); } } } #endif } #endif } diff --git a/src/common/Lgi/LgiCommon.cpp b/src/common/Lgi/LgiCommon.cpp --- a/src/common/Lgi/LgiCommon.cpp +++ b/src/common/Lgi/LgiCommon.cpp @@ -1,2816 +1,2841 @@ // // Cross platform LGI functions // #if LGI_COCOA #import #endif #define _WIN32_WINNT 0x501 #include #include #include #include #ifdef WINDOWS #include #include "lgi/common/RegKey.h" #include #include #else #include #define _getcwd getcwd #endif #include "lgi/common/Lgi.h" #include "lgi/common/Capabilities.h" #if defined(LINUX) && !defined(LGI_SDL) #include "LgiWinManGlue.h" #elif defined(WINDOWS) #include "lgi/common/RegKey.h" #endif #if defined POSIX #include #include #include #include #include "lgi/common/SubProcess.h" #endif #ifdef HAIKU #include #include #else #include "SymLookup.h" #endif #include "lgi/common/Library.h" #include "lgi/common/Net.h" #if defined(__GTK_H__) namespace Gtk { #include "LgiWidget.h" } #endif ////////////////////////////////////////////////////////////////////////// // Misc stuff #if LGI_COCOA || defined(__GTK_H__) LString LgiArgsAppPath; #endif #if defined MAC #import #if defined LGI_CARBON bool _get_path_FSRef(FSRef &fs, LStringPipe &a) { HFSUniStr255 Name; ZeroObj(Name); FSRef Parent; FSCatalogInfo Cat; ZeroObj(Cat); OSErr e = FSGetCatalogInfo(&fs, kFSCatInfoVolume|kFSCatInfoNodeID, &Cat, &Name, NULL, &Parent); if (!e) { if (_get_path_FSRef(Parent, a)) { LAutoString u((char*)LNewConvertCp("utf-8", Name.unicode, "utf-16", Name.length * sizeof(Name.unicode[0]) )); // printf("CatInfo = '%s' %x %x\n", u.Get(), Cat.nodeID, Cat.volume); if (u && Cat.nodeID > 2) { a.Print("%s%s", DIR_STR, u.Get()); } } return true; } return false; } LAutoString FSRefPath(FSRef &fs) { LStringPipe a; if (_get_path_FSRef(fs, a)) { return LAutoString(a.NewStr()); } return LAutoString(); } #endif #endif bool LPostEvent(OsView Wnd, int Event, LMessage::Param a, LMessage::Param b) { #if LGI_SDL SDL_Event e; e.type = SDL_USEREVENT; e.user.code = Event; e.user.data1 = Wnd; e.user.data2 = a || b ? new LMessage::EventParams(a, b) : NULL; /* printf("LPostEvent Wnd=%p, Event=%i, a/b: %i/%i\n", Wnd, Event, (int)a, (int)b); */ return SDL_PushEvent(&e) == 0; #elif WINNATIVE return PostMessage(Wnd, Event, a, b) != 0; #elif defined(__GTK_H__) LAssert(Wnd); LViewI *View = (LViewI*) g_object_get_data(GtkCast(Wnd, g_object, GObject), "LViewI"); if (View) { LMessage m(0); m.Set(Event, a, b); return m.Send(View); } else printf("%s:%i - Error: LPostEvent can't cast OsView to LViewI\n", _FL); #elif defined(MAC) && !LGI_COCOA #if 0 int64 Now = LCurrentTime(); static int64 Last = 0; static int Count = 0; Count++; if (Now > Last + 1000) { printf("Sent %i events in the last %ims\n", Count, (int)(Now-Last)); Last = Now; Count = 0; } #endif EventRef Ev; OSStatus e = CreateEvent(NULL, kEventClassUser, kEventUser, 0, // EventTime kEventAttributeNone, &Ev); if (e) { printf("%s:%i - CreateEvent failed with %i\n", _FL, (int)e); } else { EventTargetRef t = GetControlEventTarget(Wnd); e = SetEventParameter(Ev, kEventParamLgiEvent, typeUInt32, sizeof(Event), &Event); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = SetEventParameter(Ev, kEventParamLgiA, typeUInt32, sizeof(a), &a); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = SetEventParameter(Ev, kEventParamLgiB, typeUInt32, sizeof(b), &b); if (e) printf("%s:%i - error %i\n", _FL, (int)e); bool Status = false; EventQueueRef q = GetMainEventQueue(); e = SetEventParameter(Ev, kEventParamPostTarget, typeEventTargetRef, sizeof(t), &t); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = PostEventToQueue(q, Ev, kEventPriorityStandard); if (e) printf("%s:%i - error %i\n", _FL, (int)e); else Status = true; // printf("PostEventToQueue %i,%i,%i -> %p\n", Event, a, b, q); ReleaseEvent(Ev); return Status; } #else LAssert(!"Not impl."); #endif return false; } void LExitApp() { exit(0); } ////////////////////////////////////////////////////////////////////////// #ifdef WIN32 bool RegisterActiveXControl(char *Dll) { LLibrary Lib(Dll); if (Lib.IsLoaded()) { #ifdef _MSC_VER typedef HRESULT (STDAPICALLTYPE *p_DllRegisterServer)(void); p_DllRegisterServer DllRegisterServer = (p_DllRegisterServer)Lib.GetAddress("DllRegisterServer"); if (DllRegisterServer) { return DllRegisterServer() == S_OK; } #else LAssert(!"Not impl."); #endif } return false; } #endif ////////////////////////////////////////////////////////////////////////// #ifdef WINDOWS #include #pragma comment(lib, "netapi32.lib") #endif /// \brief Returns the operating system that Lgi is running on. /// \sa Returns one of the defines starting with LGI_OS_UNKNOWN in LgiDefs.h int LGetOs ( /// Returns the version of the OS or NULL if you don't care LArray *Ver ) { #if defined(WIN32) || defined(WIN64) static int Os = LGI_OS_UNKNOWN; static int Version = 0, Revision = 0; if (Os == LGI_OS_UNKNOWN) { #if defined(WIN64) BOOL IsWow64 = TRUE; #elif defined(WIN32) BOOL IsWow64 = FALSE; IsWow64Process(GetCurrentProcess(), &IsWow64); #endif SERVER_INFO_101 *v = NULL; auto r = NetServerGetInfo(NULL, 101, (LPBYTE*)&v); if (r == NERR_Success) { Version = v->sv101_version_major; Revision = v->sv101_version_minor; Os = (v->sv101_version_major >= 6) ? #ifdef WIN32 (IsWow64 ? LGI_OS_WIN64 : LGI_OS_WIN32) #else LGI_OS_WIN64 #endif : LGI_OS_WIN9X; NetApiBufferFree(v); } else LAssert(0); } if (Ver) { Ver->Add(Version); Ver->Add(Revision); } return Os; #elif defined LINUX if (Ver) { utsname Buf; if (!uname(&Buf)) { auto t = LString(Buf.release).SplitDelimit("."); for (int i=0; iAdd(atoi(t[i])); } } } return LGI_OS_LINUX; #elif defined MAC #if !defined(__GTK_H__) if (Ver) { NSOperatingSystemVersion v = [[NSProcessInfo processInfo] operatingSystemVersion]; Ver->Add((int)v.majorVersion); Ver->Add((int)v.minorVersion); Ver->Add((int)v.patchVersion); } #endif return LGI_OS_MAC_OS_X; #elif defined HAIKU return LGI_OS_HAIKU; #else #error "Impl Me" return LGI_OS_UNKNOWN; #endif } const char *LGetOsName() { const char *Str[LGI_OS_MAX] = { "Unknown", "Win9x", "Win32", "Win64", "Haiku", "Linux", "MacOSX", }; auto Os = LGetOs(); if (Os > 0 && Os < CountOf(Str)) return Str[Os]; LAssert(!"Invalid OS index."); return "error"; } #ifdef WIN32 #define RecursiveFileSearch_Wildcard "*.*" #else // unix'ish OS #define RecursiveFileSearch_Wildcard "*" #endif bool LRecursiveFileSearch(const char *Root, LArray *Ext, LArray *Files, uint64 *Size, uint64 *Count, std::function Callback, LCancel *Cancel) { // validate args if (!Root) return false; // get directory enumerator LDirectory Dir; bool Status = true; // enumerate the directory contents for (auto Found = Dir.First(Root); Found && (!Cancel || !Cancel->IsCancelled()); Found = Dir.Next()) { char Name[300]; if (!Dir.Path(Name, sizeof(Name))) continue; if (Callback && !Callback(Name, &Dir)) continue; if (Dir.IsDir()) { // dir LRecursiveFileSearch( Name, Ext, Files, Size, Count, Callback, Cancel); } else { // process file bool Match = true; // if no Ext's then default to match if (Ext) { for (int i=0; iLength(); i++) { const char *e = (*Ext)[i]; char *RawFile = strrchr(Name, DIR_CHAR); if (RawFile && (Match = MatchStr(e, RawFile+1))) { break; } } } if (Match) { // file matched... process: if (Files) Files->Add(NewStr(Name)); if (Size) *Size += Dir.GetSize(); if (Count) (*Count)++; Status = true; } } } return Status; } #define LGI_TRACE_TO_FILE // #include #ifndef WIN32 #define _vsnprintf vsnprintf #endif static LStreamI *_LgiTraceStream = NULL; void LgiTraceSetStream(LStreamI *stream) { _LgiTraceStream = stream; } bool LgiTraceGetFilePath(char *LogPath, int BufLen) { if (!LogPath) return false; auto Exe = LGetExeFile(); if (Exe) { #ifdef MAC char *Dir = strrchr(Exe, DIR_CHAR); if (Dir) { char Part[256]; strcpy_s(Part, sizeof(Part), Dir+1); LMakePath(LogPath, BufLen, "~/Library/Logs", Dir+1); strcat_s(LogPath, BufLen, ".txt"); } else #endif { char *Dot = strrchr(Exe, '.'); if (Dot && !strchr(Dot, DIR_CHAR)) sprintf_s(LogPath, BufLen, "%.*s.txt", (int)(Dot - Exe.Get()), Exe.Get()); else sprintf_s(LogPath, BufLen, "%s.txt", Exe.Get()); } LFile f; if (f.Open(LogPath, O_WRITE)) { f.Close(); } else { char Leaf[64]; char *Dir = strrchr(LogPath, DIR_CHAR); if (Dir) { strcpy_s(Leaf, sizeof(Leaf), Dir + 1); LGetSystemPath(LSP_APP_ROOT, LogPath, BufLen); if (!LDirExists(LogPath)) FileDev->CreateFolder(LogPath); LMakePath(LogPath, BufLen, LogPath, Leaf); } else goto OnError; } #if 0 LFile tmp; if (tmp.Open("c:\\temp\\log.txt", O_WRITE)) { tmp.SetSize(0); tmp.Write(LogPath, strlen(LogPath)); } #endif } else { // Well what to do now? I give up OnError: strcpy_s(LogPath, BufLen, "trace.txt"); return false; } return true; } void LgiTrace(const char *Msg, ...) { #if defined _INC_MALLOC && WINNATIVE if (_heapchk() != _HEAPOK) return; #endif if (!Msg) return; #ifdef WIN32 static LMutex Sem("LgiTrace"); Sem.Lock(_FL, true); #endif char Buffer[2049] = ""; #ifdef LGI_TRACE_TO_FILE static LFile f; static char LogPath[MAX_PATH_LEN] = ""; if (!_LgiTraceStream && LogPath[0] == 0) LgiTraceGetFilePath(LogPath, sizeof(LogPath)); #endif va_list Arg; va_start(Arg, Msg); #ifdef LGI_TRACE_TO_FILE int Ch = #endif vsnprintf(Buffer, sizeof(Buffer)-1, Msg, Arg); va_end(Arg); #ifdef LGI_TRACE_TO_FILE LStreamI *Output = NULL; if (_LgiTraceStream) Output = _LgiTraceStream; else { if (!f.IsOpen() && f.Open(LogPath, O_WRITE)) f.Seek(0, SEEK_END); Output = &f; } if (Output && Ch > 0) { Output->ChangeThread(); Output->Write(Buffer, Ch); } if (!_LgiTraceStream) { #ifdef WINDOWS // Windows can take AGES to close a file when there is anti-virus on, like 100ms. // We can't afford to wait here so just keep the file open but flush the // buffers if we can. FlushFileBuffers(f.Handle()); #else f.Close(); #endif } #endif #if defined WIN32 OutputDebugStringA(Buffer); Sem.Unlock(); #else printf("%s", Buffer); #endif } #ifndef LGI_STATIC #define STACK_SIZE 12 void LStackTrace(const char *Msg, ...) { #ifndef HAIKU LSymLookup::Addr Stack[STACK_SIZE]; ZeroObj(Stack); LSymLookup *Lu = LAppInst ? LAppInst->GetSymLookup() : NULL; if (!Lu) { printf("%s:%i - Failed to get sym lookup object.\n", _FL); return; } int Frames = Lu ? Lu->BackTrace(0, 0, Stack, STACK_SIZE) : 0; if (Msg) { #ifdef LGI_TRACE_TO_FILE static LFile f; static char LogPath[MAX_PATH_LEN] = ""; if (!_LgiTraceStream) { if (LogPath[0] == 0) LgiTraceGetFilePath(LogPath, sizeof(LogPath)); f.Open(LogPath, O_WRITE); } #endif va_list Arg; va_start(Arg, Msg); char Buffer[2049] = ""; int Len = vsnprintf(Buffer, sizeof(Buffer)-1, Msg, Arg); va_end(Arg); Lu->Lookup(Buffer+Len, sizeof(Buffer)-Len-1, Stack, Frames); #ifdef LGI_TRACE_TO_FILE if (f.IsOpen()) { f.Seek(0, SEEK_END); f.Write(Buffer, (int)strlen(Buffer)); f.Close(); } #endif #if defined WIN32 OutputDebugStringA(Buffer); #else printf("Trace: %s", Buffer); #endif } #endif } #endif bool LTrimDir(char *Path) { if (!Path) return false; char *p = strrchr(Path, DIR_CHAR); if (!p) return false; if (p[1] == 0) // Trailing DIR_CHAR doesn't count... do it again. { *p = 0; p = strrchr(Path, DIR_CHAR); if (!p) return false; } *p = 0; return true; } LString LMakeRelativePath(const char *Base, const char *Path) { LStringPipe Status; if (Base && Path) { #ifdef WIN32 bool SameNs = strnicmp(Base, Path, 3) == 0; #else bool SameNs = true; #endif if (SameNs) { auto b = LString(Base + 1).SplitDelimit(":\\/"); auto p = LString(Path + 1).SplitDelimit(":\\/"); int Same = 0; while (Same < b.Length() && Same < p.Length() && stricmp(b[Same], p[Same]) == 0) { Same++; } for (int i = Same; i= b.Length()) { Status.Print(".%s", DIR_STR); } for (int n = Same; n Same) { Status.Push(DIR_STR); } Status.Push(p[n]); } } } return Status.NewGStr(); } bool LIsRelativePath(const char *Path) { if (!Path) return false; if (*Path == '.') return true; #ifdef WIN32 if (IsAlpha(Path[0]) && Path[1] == ':') // Drive letter path return false; #endif if (*Path == DIR_CHAR) return false; if (strstr(Path, "://")) // Protocol def return false; return true; // Correct or not??? } bool LMakePath(char *Str, int StrSize, const char *Path, const char *File) { if (!Str || StrSize <= 0 || !Path || !File) { printf("%s:%i - Invalid LMakePath(%p,%i,%s,%s) param\n", _FL, Str, StrSize, Path, File); return false; } if (StrSize <= 4) { printf("%s:%i - LgiMakeFile buf size=%i?\n", _FL, StrSize); } if (Str && Path && File) { char Dir[] = { '/', '\\', 0 }; if (Path[0] == '~') { auto Parts = LString(Path).SplitDelimit(Dir, 2); char *s = Str, *e = Str + StrSize; for (auto p: Parts) { if (p.Equals("~")) { LGetSystemPath(LSP_HOME, s, e - s); s += strlen(s); } else s += sprintf_s(s, e - s, "%s%s", DIR_STR, p.Get()); } } else if (Str != Path) { strcpy_s(Str, StrSize, Path); } #define EndStr() Str[strlen(Str)-1] #define EndDir() if (!strchr(Dir, EndStr())) strcat(Str, DIR_STR); size_t Len = strlen(Str); char *End = Str + (Len ? Len - 1 : 0); if (strchr(Dir, *End) && End > Str) { *End = 0; } auto T = LString(File).SplitDelimit(Dir); for (int i=0; i= StrSize - 1) return false; Str[Len++] = DIR_CHAR; Str[Len] = 0; } size_t SegLen = strlen(T[i]); if (Len + SegLen + 1 > StrSize) { return false; } strcpy_s(Str + Len, StrSize - Len, T[i]); } } } return true; } bool LgiGetTempPath(char *Dst, int DstSize) { return LGetSystemPath(LSP_TEMP, Dst, DstSize); } bool LGetSystemPath(LSystemPath Which, char *Dst, ssize_t DstSize) { if (!Dst || DstSize <= 0) return false; LFile::Path p; LString s = p.GetSystem(Which, 0); if (!s) return false; strcpy_s(Dst, DstSize, s); return true; } LString LGetSystemPath(LSystemPath Which, int WordSize) { LFile::Path p; return p.GetSystem(Which, WordSize); } LFile::Path::State LFile::Path::Exists() { if (Length() == 0) return TypeNone; #ifdef WINDOWS struct _stat64 s; int r = _stat64(GetFull(), &s); if (r) return TypeNone; if (s.st_mode & _S_IFDIR) return TypeFolder; if (s.st_mode & _S_IFREG) return TypeFile; #else struct stat s; int r = stat(GetFull(), &s); if (r) return TypeNone; if (S_ISDIR(s.st_mode)) return TypeFolder; if (S_ISREG(s.st_mode)) return TypeFile; #endif return TypeNone; } LString LFile::Path::PrintAll() { LStringPipe p; #define _(name) \ p.Print(#name ": '%s'\n", GetSystem(name).Get()); _(LSP_OS) _(LSP_OS_LIB) _(LSP_TEMP) _(LSP_COMMON_APP_DATA) _(LSP_USER_APP_DATA) _(LSP_LOCAL_APP_DATA) _(LSP_DESKTOP) _(LSP_HOME) _(LSP_USER_APPS) _(LSP_EXE) _(LSP_TRASH) _(LSP_APP_INSTALL) _(LSP_APP_ROOT) _(LSP_USER_DOCUMENTS) _(LSP_USER_MUSIC) _(LSP_USER_VIDEO) _(LSP_USER_DOWNLOADS) _(LSP_USER_LINKS) _(LSP_USER_PICTURES) #undef _ #if LGI_COCOA int Domains[] = {NSUserDomainMask, NSSystemDomainMask}; const char *DomainName[] = {"User", "System"}; for (int i=0; i 0) \ { \ LString s = [paths objectAtIndex:0]; \ p.Print("%s." #name ": '%s'\n", DomainName[i], s.Get()); \ } \ else p.Print("%s." #name ": null\n", DomainName[i]); \ } \ } _(NSApplicationDirectory) _(NSDemoApplicationDirectory) _(NSDeveloperApplicationDirectory) _(NSAdminApplicationDirectory) _(NSLibraryDirectory) _(NSDeveloperDirectory) _(NSUserDirectory) _(NSDocumentationDirectory) _(NSDocumentDirectory) _(NSCoreServiceDirectory) _(NSAutosavedInformationDirectory) _(NSDesktopDirectory) _(NSCachesDirectory) _(NSApplicationSupportDirectory) _(NSDownloadsDirectory) _(NSInputMethodsDirectory) _(NSMoviesDirectory) _(NSMusicDirectory) _(NSPicturesDirectory) _(NSPrinterDescriptionDirectory) _(NSSharedPublicDirectory) _(NSPreferencePanesDirectory) _(NSApplicationScriptsDirectory) _(NSItemReplacementDirectory) _(NSAllApplicationsDirectory) _(NSAllLibrariesDirectory) _(NSTrashDirectory) #undef _ } #endif return p.NewGStr(); } LString LFile::Path::GetSystem(LSystemPath Which, int WordSize) { LString Path; #if defined(WIN32) #ifndef CSIDL_PROFILE #define CSIDL_PROFILE 0x0028 #endif #if !defined(CSIDL_MYDOCUMENTS) #define CSIDL_MYDOCUMENTS 0x0005 #endif #if !defined(CSIDL_MYMUSIC) #define CSIDL_MYMUSIC 0x000d #endif #if !defined(CSIDL_MYVIDEO) #define CSIDL_MYVIDEO 0x000e #endif #if !defined(CSIDL_LOCAL_APPDATA) #define CSIDL_LOCAL_APPDATA 0x001c #endif #if !defined(CSIDL_COMMON_APPDATA) #define CSIDL_COMMON_APPDATA 0x0023 #endif #if !defined(CSIDL_APPDATA) #define CSIDL_APPDATA 0x001a #endif #endif /* #if defined(LINUX) && !defined(LGI_SDL) // Ask our window manager add-on if it knows the path LLibrary *WmLib = LAppInst ? LAppInst->GetWindowManagerLib() : NULL; if (WmLib) { Proc_LgiWmGetPath WmGetPath = (Proc_LgiWmGetPath) WmLib->GetAddress("LgiWmGetPath"); char Buf[MAX_PATH_LEN]; if (WmGetPath && WmGetPath(Which, Buf, sizeof(Buf))) { Path = Buf; return Path; } } #endif */ switch (Which) { default: break; case LSP_USER_DOWNLOADS: { #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOWNLOAD); Path = p; #elif defined(WIN32) && defined(_MSC_VER) // OMG!!!! Really? #ifndef REFKNOWNFOLDERID typedef GUID KNOWNFOLDERID; #define REFKNOWNFOLDERID const KNOWNFOLDERID & GUID FOLDERID_Downloads = {0x374DE290,0x123F,0x4565,{0x91,0x64,0x39,0xC4,0x92,0x5E,0x46,0x7B}}; #endif LLibrary Shell("Shell32.dll"); typedef HRESULT (STDAPICALLTYPE *pSHGetKnownFolderPath)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); pSHGetKnownFolderPath SHGetKnownFolderPath = (pSHGetKnownFolderPath)Shell.GetAddress("SHGetKnownFolderPath"); if (SHGetKnownFolderPath) { PWSTR ptr = NULL; HRESULT r = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &ptr); if (SUCCEEDED(r)) { LAutoString u8(WideToUtf8(ptr)); if (u8) Path = u8; CoTaskMemFree(ptr); } } if (!Path.Get()) { LRegKey k(false, "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"); char *p = k.GetStr("{374DE290-123F-4565-9164-39C4925E467B}"); if (LDirExists(p)) Path = p; } if (!Path.Get()) { LString MyDoc = WinGetSpecialFolderPath(CSIDL_MYDOCUMENTS); if (MyDoc) { char Buf[MAX_PATH_LEN]; LMakePath(Buf, sizeof(Buf), MyDoc, "Downloads"); if (LDirExists(Buf)) Path = Buf; } } if (!Path.Get()) { LString Profile = WinGetSpecialFolderPath(CSIDL_PROFILE); if (Profile) { char Buf[MAX_PATH_LEN]; LMakePath(Buf, sizeof(Buf), Profile, "Downloads"); if (LDirExists(Buf)) Path = Buf; } } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDownloadsDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #elif defined(HAIKU) #else LAssert(!"Not implemented"); #endif break; } case LSP_USER_LINKS: { LString Home = LGetSystemPath(LSP_HOME); #if defined(WIN32) char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Home, "Links")) Path = p; #elif defined(LINUX) char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Home, ".config/gtk-3.0")) Path = p; #endif break; } case LSP_USER_PICTURES: { #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOCUMENTS); Path = p; #elif defined(WIN32) Path = WinGetSpecialFolderPath(CSIDL_MYPICTURES); if (Path) return Path; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSPicturesDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #endif // Default to ~/Pictures char hm[MAX_PATH_LEN]; LString Home = LGetSystemPath(LSP_HOME); if (LMakePath(hm, sizeof(hm), Home, "Pictures")) Path = hm; break; } case LSP_USER_DOCUMENTS: { + char path[MAX_PATH_LEN]; + #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOCUMENTS); - Path = p; + if (p) + Path = p; #elif defined(WIN32) && defined(_MSC_VER) Path = WinGetSpecialFolderPath(CSIDL_MYDOCUMENTS); - if (Path) - return Path; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } + + #elif defined(HAIKU) + + if( find_directory + ( + B_SYSTEM_DOCUMENTATION_DIRECTORY, + dev_for_path("/boot"), + true, + path, sizeof(path) + ) == B_OK) + Path = path; #endif - // Default to ~/Documents - char hm[MAX_PATH_LEN]; - LString Home = LGetSystemPath(LSP_HOME); - if (LMakePath(hm, sizeof(hm), Home, "Documents")) - Path = hm; + if (!Path) + { + // Default to ~/Documents + if (LMakePath(path, sizeof(path), LGetSystemPath(LSP_HOME), "Documents")) + Path = path; + } break; } case LSP_USER_MUSIC: { + char path[MAX_PATH_LEN]; + #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_MYMUSIC); #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_MUSIC); - Path = p; + if (p) + Path = p; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kMusicDocumentsFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString a = FSRefPath(Ref); if (a) Path = a.Get(); } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSMusicDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } + #elif defined(HAIKU) + + if( find_directory + ( + B_USER_SOUNDS_DIRECTORY, + dev_for_path("/boot"), + true, + path, sizeof(path) + ) == B_OK) + Path = path; + #endif if (!Path) { // Default to ~/Music - char p[MAX_PATH_LEN]; - LString Home = LGetSystemPath(LSP_HOME); - if (LMakePath(p, sizeof(p), Home, "Music")) - Path = p; + if (LMakePath(path, sizeof(path), LGetSystemPath(LSP_HOME), "Music")) + Path = path; } break; } case LSP_USER_VIDEO: { + char path[MAX_PATH_LEN]; + #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_MYVIDEO); #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_VIDEOS); - Path = p; + if (p) + Path = p; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kMovieDocumentsFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString a = FSRefPath(Ref); if (a) Path = a.Get(); } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSMoviesDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #endif if (!Path) { // Default to ~/Video - char p[MAX_PATH_LEN]; - LString Home = LGetSystemPath(LSP_HOME); - if (LMakePath(p, sizeof(p), Home, "Video")) - Path = p; + if (LMakePath(path, sizeof(path), LGetSystemPath(LSP_HOME), "Video")) + Path = path; } break; } case LSP_USER_APPS: { #if defined WIN32 int Id = #ifdef WIN64 CSIDL_PROGRAM_FILES #else CSIDL_PROGRAM_FILESX86 #endif ; if (WordSize == 32) Id = CSIDL_PROGRAM_FILESX86; else if (WordSize == 64) Id = CSIDL_PROGRAM_FILES; Path = WinGetSpecialFolderPath(Id); #elif defined(HAIKU) - dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; - if (find_directory(B_SYSTEM_APPS_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) + if (find_directory(B_USER_APPS_DIRECTORY, dev_for_path("/boot"), true, path, sizeof(path)) == B_OK) Path = path; #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSApplicationDirectory, NSSystemDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #elif defined MAC Path = "/Applications"; #elif defined LINUX Path = "/usr/bin"; #else LAssert(!"Impl me."); #endif break; } case LSP_APP_INSTALL: { Path = LGetSystemPath(LSP_EXE); if (Path) { size_t Last = Path.RFind(DIR_STR); if (Last > 0) { LString s = Path(Last, -1); if ( stristr(s, #ifdef _DEBUG "Debug" #else "Release" #endif ) ) Path = Path(0, Last); } } break; } case LSP_APP_ROOT: { #ifndef LGI_STATIC const char *Name = NULL; // Try and get the configured app name: if (LAppInst) Name = LAppInst->LBase::Name(); if (!Name) { // Use the exe name? LString Exe = LGetExeFile(); char *l = LGetLeaf(Exe); if (l) { #ifdef WIN32 char *d = strrchr(l, '.'); *d = NULL; #endif Name = l; // printf("%s:%i - name '%s'\n", _FL, Name); } } if (!Name) { LAssert(0); break; } #if defined MAC #if LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); if (paths) Path = [[paths objectAtIndex:0] UTF8String]; #elif LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) { printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); LAssert(0); } else { LAutoString Base = FSRefPath(Ref); Path = Base.Get(); } #else struct passwd *pw = getpwuid(getuid()); if (!pw) return false; Path.Printf("%s/Library", pw->pw_dir); #endif #elif defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_APPDATA); #elif defined LINUX char Dot[128]; snprintf(Dot, sizeof(Dot), ".%s", Name); Name = Dot; struct passwd *pw = getpwuid(getuid()); if (pw) Path = pw->pw_dir; else LAssert(0); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_DIRECTORY , volume, true, path, sizeof(path)) == B_OK) Path = path; #else LAssert(0); #endif if (Path) { Path += DIR_STR; Path += Name; } #endif break; } case LSP_OS: { #if defined WIN32 char16 p[MAX_PATH_LEN]; if (GetWindowsDirectoryW(p, CountOf(p)) > 0) Path = p; #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_SYSTEM_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnAppropriateDisk, kSystemFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSSystemDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; LTrimDir(Path); } #elif defined LINUX Path = "/boot"; // it'll do for now... #endif break; } case LSP_OS_LIB: { #if defined WIN32 char16 p[MAX_PATH_LEN]; if (GetSystemDirectoryW(p, CountOf(p)) > 0) Path = p; #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_SYSTEM_LIB_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined MAC Path = "/Library"; #elif defined LINUX Path = "/lib"; // it'll do for now... #endif break; } case LSP_TEMP: { #if defined WIN32 char16 t[MAX_PATH_LEN]; if (GetTempPathW(CountOf(t), t) > 0) { LAutoString utf(WideToUtf8(t)); if (utf) Path = utf; } #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kTemporaryFolderType, kCreateFolder, &Ref); if (e) LgiTrace("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString u = FSRefPath(Ref); if (u) { Path = u.Get(); } else LgiTrace("%s:%i - FSRefPath failed.\n", _FL); } #elif defined LGI_COCOA NSString *tempDir = NSTemporaryDirectory(); if (tempDir) Path = tempDir; else LAssert(!"No tmp folder?"); #elif defined LINUX Path = "/tmp"; // it'll do for now... #else LAssert(!"Impl me."); #endif break; } case LSP_COMMON_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_COMMON_APPDATA); #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnSystemDisk, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { auto u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSSystemDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LINUX Path = "/usr"; #else LAssert(!"Impl me."); #endif break; } case LSP_USER_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_APPDATA); #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { auto u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LINUX Path = "/usr"; #elif defined HAIKU dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_SETTINGS_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #else LAssert(!"Impl me."); #endif break; } case LSP_LOCAL_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_LOCAL_APPDATA); #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #else LAssert(!"Impl me."); #endif break; } case LSP_DESKTOP: { #if defined(WINDOWS) && defined(_MSC_VER) Path = WinGetSpecialFolderPath(CSIDL_DESKTOPDIRECTORY); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_DESKTOP_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DESKTOP); Path = p; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDesktopDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnAppropriateDisk, kDesktopFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined POSIX struct passwd *pw = getpwuid(getuid()); if (pw) { #ifdef LINUX WindowManager wm = LGetWindowManager(); if (wm == WM_Gnome) { Path.Printf("%s/.gnome-desktop", pw->pw_dir); } #endif if (!LDirExists(Path)) { Path.Printf("%s/Desktop", pw->pw_dir); } } #else #error "Impl me." #endif break; } case LSP_HOME: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_PROFILE); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_COCOA NSString *home = NSHomeDirectory(); if (home) Path = home; else LAssert(!"No home path?"); #elif defined POSIX struct passwd *pw = getpwuid(getuid()); if (pw) Path = pw->pw_dir; #else #error "Impl me." #endif break; } case LSP_EXE: { Path = LGetExeFile(); if (!Path) break; auto p = Path.RFind(DIR_STR); if (p > 0) Path.Length(p); break; } case LSP_TRASH: { #if defined LINUX switch (LGetWindowManager()) { case WM_Kde: { static char KdeTrash[256] = ""; if (!ValidStr(KdeTrash)) { // Ask KDE where the current trash is... LStringPipe o; LSubProcess p("kde-config", "--userpath trash"); if (p.Start()) { p.Communicate(&o); char *s = o.NewStr(); if (s) { // Store it.. strcpy_s(KdeTrash, sizeof(KdeTrash), s); DeleteArray(s); // Clear out any crap at the end... char *e = KdeTrash + strlen(KdeTrash) - 1; while (e > KdeTrash && strchr(" \r\n\t/", *e)) { *e-- = 0; } } else { printf("%s:%i - No output from 'kde-config'.\n", _FL); } } else { printf("%s:%i - Run 'kde-config' failed.\n", _FL); } } if (ValidStr(KdeTrash)) Path = KdeTrash; break; } default: { LString Home = LGetSystemPath(LSP_HOME); if (!Home) { LgiTrace("%s:%i - Can't get LSP_HOME.\n", _FL); break; } char p[MAX_PATH_LEN]; if (!LMakePath(p, sizeof(p), Home, ".local/share/Trash/files") || !LDirExists(p)) { LgiTrace("%s:%i - '%s' doesn't exist.\n", _FL, p); break; } Path = p; break; } } #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_TRASH_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kTrashFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSTrashDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined(WIN32) LAssert(0); #endif break; } } return Path; } LString LGetExeFile() { #if defined WIN32 char16 Exe[MAX_PATH_LEN]; if (GetModuleFileNameW(NULL, Exe, CountOf(Exe)) > 0) { LString e = Exe; if (e) { return e; } else { LgiMsg(0, "LgiFromNativeCp returned 0, ANSI CodePage=%i (%s)", "LgiGetExeFile Error", MB_OK, GetACP(), LAnsiToLgiCp()); return LString(); } } LString m; m.Printf("GetModuleFileName failed err: %08.8X", GetLastError()); MessageBoxA(0, m, "LgiGetExeFile Error", MB_OK); LExitApp(); #elif defined HAIKU // Copy the string so as to not allow callers to change it return LgiArgsAppPath.Get(); #elif defined LINUX static char ExePathCache[MAX_PATH_LEN] = ""; bool Status = false; // this is _REALLY_ lame way to do it... but hey there aren't any // other better ways to get the full path of the running executable if (!ExePathCache[0]) { // First try the self method int Len = readlink("/proc/self/exe", ExePathCache, sizeof(ExePathCache)); Status = LFileExists(ExePathCache); // printf("readlink=%i Status=%i Exe='%s'\n", Len, Status, ExePathCache); if (!Status) { ExePathCache[0] = 0; // Next try the map file method char ProcFile[256]; sprintf_s(ProcFile, sizeof(ProcFile), "/proc/%i/maps", getpid()); int fd = open(ProcFile, O_RDONLY); if (fd >= 0) { int Len = 16 << 10; // is this enough? // no better way of determining the length of proc info? char *Buf = new char[Len+1]; if (Buf) { int r = read(fd, Buf, Len); Buf[r] = 0; char *s = strchr(Buf, '/'); if (s) { char *e = strchr(s, '\n'); if (e) { *e = 0; strcpy_s(ExePathCache, sizeof(ExePathCache), s); Status = true; } } DeleteArray(Buf); } close(fd); } else { // Non proc system (like cygwin for example) // char Cmd[256]; // sprintf_s(Cmd, sizeof(Cmd), "ps | grep \"%i\"", getpid()); LStringPipe Ps; LSubProcess p("ps"); if (p.Start()) { p.Communicate(&Ps); char *PsOutput = Ps.NewStr(); if (PsOutput) { auto n = LString(PsOutput).SplitDelimit("\r\n"); for (int i=0; !Status && i 7) { int LinePid = 0; for (int i=0; iReset(NewStr(Path)); return; } #ifdef WIN32 if (PathLen < sizeof(Path) - 4) { strcat(Path, ".lnk"); if (LResolveShortcut(Path, Path, sizeof(Path))) { if (GStr) *GStr = Path; else if (AStr) AStr->Reset(NewStr(Path)); return; } } #endif } // General search fall back... LArray Ext; LArray Files; Ext.Add((char*)Name); if (LRecursiveFileSearch(Exe, &Ext, &Files) && Files.Length()) { if (GStr) *GStr = Files[0]; else { AStr->Reset(Files[0]); Files.DeleteAt(0); } } Files.DeleteArrays(); } LString LFindFile(const char *Name) { LString s; _LFindFile(Name, &s, NULL); return s; } #if defined WIN32 static LARGE_INTEGER Freq = {0}; static bool CurTimeInit = false; #endif uint64_t LCurrentTime() { #if defined WIN32 if (!CurTimeInit) { CurTimeInit = true; if (!QueryPerformanceFrequency(&Freq)) Freq.QuadPart = 0; } if (Freq.QuadPart) { // Return performance counter in ms LARGE_INTEGER i; if (QueryPerformanceCounter(&i)) { return i.QuadPart * 1000 / Freq.QuadPart; } // Now what?? Give up and go back to tick count I guess. Freq.QuadPart = 0; } // Fall back for systems without a performance counter. return GetTickCount(); #elif defined LGI_CARBON UnsignedWide t; Microseconds(&t); uint64 i = ((uint64)t.hi << 32) | t.lo; return i / 1000; #else timeval tv; gettimeofday(&tv, 0); return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); #endif } uint64_t LgiMicroTime() { #if defined WIN32 if (!CurTimeInit) { CurTimeInit = true; if (!QueryPerformanceFrequency(&Freq)) Freq.QuadPart = 0; } if (Freq.QuadPart) { // Return performance counter in ms LARGE_INTEGER i; if (QueryPerformanceCounter(&i)) { return i.QuadPart * 1000000 / Freq.QuadPart; } } return 0; #elif defined LGI_CARBON UnsignedWide t; Microseconds(&t); return ((uint64)t.hi << 32) | t.lo; #else timeval tv; gettimeofday(&tv, 0); return (tv.tv_sec * 1000000) + tv.tv_usec; #endif } int LIsReleaseBuild() { #ifdef _DEBUG return 0; #else return 1; #endif } bool LIsVolumeRoot(const char *Path) { if (!Path) return false; #ifdef WIN32 if ( IsAlpha(Path[0]) && Path[1] == ':' && ( (Path[2] == 0) || (Path[2] == '\\' && Path[3] == 0) ) ) { return true; } #else auto t = LString(Path).SplitDelimit(DIR_STR); if (t.Length() == 0) return true; #ifdef MAC if (!stricmp(t[0], "Volumes") && t.Length() == 2) return true; #else if (!stricmp(t[0], "mnt") && t.Length() == 2) return true; #endif #endif return false; } LString LFormatSize(int64_t Size) { char Buf[64]; LFormatSize(Buf, sizeof(Buf), Size); return Buf; } void LFormatSize(char *Str, int SLen, int64_t Size) { int64_t K = 1024; int64_t M = K * K; int64_t G = K * M; int64_t T = K * G; if (Size == 1) { strcpy_s(Str, SLen, "1 byte"); } else if (Size < K) { sprintf_s(Str, SLen, "%u bytes", (int)Size); } else if (Size < 10 * K) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f KiB", d / K); } else if (Size < M) { sprintf_s(Str, SLen, "%u KiB", (int) (Size / K)); } else if (Size < G) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f MiB", d / M); } else if (Size < T) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f GiB", d / G); } else { double d = (double)Size; sprintf_s(Str, SLen, "%.2f TiB", d / T); } } char *LTokStr(const char *&s) { char *Status = 0; if (s && *s) { // Skip whitespace static char Delim[] = ", \t\r\n"; while (*s && strchr(Delim, *s)) s++; if (*s) { if (strchr("\'\"", *s)) { char Delim = *s++; const char *e = strchr(s, Delim); if (!e) { // error, no end delimiter e = s; while (*e) e++; } Status = NewStr(s, e - s); s = *e ? e + 1 : e; } else { const char *e = s; while (*e && !strchr(Delim, *e)) e++; Status = NewStr(s, e - s); s = e; } } } return Status; } LString LGetEnv(const char *Var) { #ifdef _MSC_VER char *s = NULL; size_t sz; errno_t err = _dupenv_s(&s, &sz, Var); if (err) return LString(); LString ret(s); free(s); return ret; #else return getenv("PATH"); #endif } LString::Array LGetPath() { LString::Array Paths; #ifdef MAC // OMG, WHY?! Seriously? // // The GUI application path is NOT the same as what is configured for the terminal. // At least in 10.12. And I don't know how to make them the same. This works around // that for the time being. #if 1 LFile EctPaths("/etc/paths", O_READ); Paths = EctPaths.Read().Split("\n"); #else LFile::Path Home(LSP_HOME); Home += ".profile"; if (!Home.Exists()) { Home--; Home += ".zprofile"; } auto Profile = LFile(Home, O_READ).Read().Split("\n"); for (auto Ln : Profile) { auto p = Ln.SplitDelimit(" =", 2); if (p.Length() == 3 && p[0].Equals("export") && p[1].Equals("PATH")) { Paths = p[2].Strip("\"").SplitDelimit(LGI_PATH_SEPARATOR); Paths.SetFixedLength(false); for (auto &p : Paths) { if (p.Equals("$PATH")) { auto SysPath = LGetEnv("PATH").SplitDelimit(LGI_PATH_SEPARATOR); for (unsigned i=0; i 0) { Period = p; } } bool DoEvery::DoNow() { int64 Now = LCurrentTime(); if (LastTime + Period < Now) { LastTime = Now; return true; } return false; } ////////////////////////////////////////////////////////////////////// bool LCapabilityClient::NeedsCapability(const char *Name, const char *Param) { for (int i=0; iNeedsCapability(Name, Param); return Targets.Length() > 0; } LCapabilityClient::~LCapabilityClient() { for (int i=0; iClients.Delete(this); } void LCapabilityClient::Register(LCapabilityTarget *t) { if (t && !Targets.HasItem(t)) { Targets.Add(t); t->Clients.Add(this); } } LCapabilityTarget::~LCapabilityTarget() { for (int i=0; iTargets.Delete(this); } ///////////////////////////////////////////////////////////////////// #define BUF_SIZE (4 << 10) #define PROFILE_MICRO 1 LProfile::LProfile(const char *Name, int HideMs) { MinMs = HideMs; Used = 0; Buf = NULL; Add(Name); } LProfile::~LProfile() { Add("End"); uint64 TotalMs = s.Last().Time - s[0].Time; if (MinMs > 0) { if (TotalMs < MinMs #if PROFILE_MICRO * 1000 #endif ) { return; } } uint64 accum = 0; for (int i=0; i BUF_SIZE - 64) { LAssert(0); return; } char *Name = Buf + Used; Used += sprintf_s(Name, BUF_SIZE - Used, "%s:%i", File, Line) + 1; s.Add(Sample( #if PROFILE_MICRO LgiMicroTime(), #else LCurrentTime(), #endif Name)); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// bool LIsValidEmail(LString Email) { // Local part char buf[321]; char *o = buf; char *e = Email; if (!Email || *e == '.') return false; #define OutputChar() \ if (o - buf >= sizeof(buf) - 1) \ return false; \ *o++ = *e++ // Local part while (*e) { if (strchr("!#$%&\'*+-/=?^_`.{|}~", *e) || IsAlpha((uchar)*e) || IsDigit((uchar)*e)) { OutputChar(); } else if (*e == '\"') { // Quoted string OutputChar(); bool quote = false; while (*e && !quote) { quote = *e == '\"'; OutputChar(); } } else if (*e == '\\') { // Quoted character e++; if (*e < ' ' || *e >= 0x7f) return false; OutputChar(); } else if (*e == '@') { break; } else { // Illegal character return false; } } // Process the '@' if (*e != '@' || o - buf > 64) return false; OutputChar(); // Domain part... if (*e == '[') { // IP addr OutputChar(); // Initial char must by a number if (!IsDigit(*e)) return false; // Check the rest... char *Start = e; while (*e) { if (IsDigit(*e) || *e == '.') { OutputChar(); } else { return false; } } // Not a valid IP if (e - Start > 15) return false; if (*e != ']') return false; OutputChar(); } else { // Hostname, check initial char if (!IsAlpha(*e) && !IsDigit(*e)) return false; // Check the rest. while (*e) { if (IsAlpha(*e) || IsDigit(*e) || strchr(".-", *e)) { OutputChar(); } else { return false; } } } // Remove any trailing dot/dash while (strchr(".-", o[-1])) o--; // Output *o = 0; LAssert(o - buf <= sizeof(buf)); if (strcmp(Email, buf)) Email.Set(buf, o - buf); return true; } ////////////////////////////////////////////////////////////////////////// LString LGetAppForProtocol(const char *Protocol) { LString App; if (!Protocol) return App; #ifdef WINDOWS LRegKey k(false, "HKEY_CLASSES_ROOT\\%s\\shell\\open\\command", Protocol); if (k.IsOk()) { const char *p = k.GetStr(); if (p) { LAutoString a(LTokStr(p)); App = a.Get(); } } #elif defined(LINUX) const char *p = NULL; if (stricmp(Protocol, "mailto")) p = "xdg-email"; else p = "xdg-open"; LString Path = LGetEnv("PATH"); LString::Array a = Path.SplitDelimit(LGI_PATH_SEPARATOR); for (auto i : a) { LFile::Path t(i); t += p; if (t.Exists()) { App = t.GetFull(); break; } } #elif defined(__GTK_H__) LAssert(!"What to do?"); #elif defined(MAC) // Get the handler type LString s; s.Printf("%s://domain/path", Protocol); auto str = s.NsStr(); auto type = [NSURL URLWithString:str]; [str release]; auto handlerUrl = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:type]; [type release]; if (handlerUrl) { // Convert to app path s = [handlerUrl absoluteString]; LUri uri(s); if (uri.sProtocol.Equals("file")) App = uri.sPath.RStrip("/"); else LgiTrace("%s:%i - Error: unknown protocol '%s'\n", _FL, uri.sProtocol.Get()); } [handlerUrl release]; #else #warning "Impl me." #endif return App; } diff --git a/src/common/Widgets/ItemContainer.cpp b/src/common/Widgets/ItemContainer.cpp --- a/src/common/Widgets/ItemContainer.cpp +++ b/src/common/Widgets/ItemContainer.cpp @@ -1,1312 +1,1312 @@ #include "lgi/common/Lgi.h" #include "lgi/common/ItemContainer.h" #include "lgi/common/DisplayString.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Edit.h" #include "lgi/common/CssTools.h" // Colours #if defined(__GTK_H__) #define DOUBLE_BUFFER_COLUMN_DRAWING 1 #else #define DOUBLE_BUFFER_COLUMN_DRAWING 0 #endif #if defined(WIN32) #if !defined(WS_EX_LAYERED) #define WS_EX_LAYERED 0x80000 #endif #if !defined(LWA_ALPHA) #define LWA_ALPHA 2 #endif typedef BOOL (__stdcall *_SetLayeredWindowAttributes)(HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags); #endif class LItemColumnPrivate { public: LRect Pos; bool Down; bool Drag; LItemContainer *Parent; char *cName; LDisplayString *Txt; int cWidth; int cType; LSurface *cIcon; int cImage; int cMark; bool OwnIcon; bool CanResize; LItemColumnPrivate(LItemContainer *parent) { Parent = parent; Txt = 0; cName = 0; cWidth = 0; cIcon = 0; cType = GIC_ASK_TEXT; cImage = -1; cMark = GLI_MARK_NONE; Down = false; OwnIcon = false; CanResize = true; Drag = false; } ~LItemColumnPrivate() { DeleteArray(cName); if (OwnIcon) { DeleteObj(cIcon); } DeleteObj(Txt); } }; static LColour cActiveCol(0x86, 0xba, 0xe9); static void FillStops(LArray &Stops, LRect &r, bool Active) { if (Active) { Stops[0].Set(0.0f, LColour(0xd0, 0xe2, 0xf5)); Stops[1].Set(2.0f / (r.Y() - 2), LColour(0x98, 0xc1, 0xe9)); Stops[2].Set(0.5f, LColour(0x86, 0xba, 0xe9)); Stops[3].Set(0.51f, LColour(0x68, 0xaf, 0xea)); Stops[4].Set(1.0f, LColour(0xbb, 0xfc, 0xff)); } else { LColour cMed(L_MED), cWs(L_WORKSPACE); if (cWs.GetGray() < 96) { cMed = cMed.Mix(LColour::White, 0.25f); cWs = cWs.Mix(LColour::White, 0.25f); } Stops[0].Set(0.0f, cWs); Stops[1].Set(0.5f, cMed.Mix(cWs)); Stops[2].Set(0.51f, cMed); Stops[3].Set(1.0f, cWs); } } ////////////////////////////////////////////////////////////////////////////////////// LItemContainer::LItemContainer() { Flags = 0; DragMode = 0; DragCol = NULL; ColClick = -1; ColumnHeaders = true; ColumnHeader.ZOff(-1, -1); Columns.SetFixedLength(true); ItemEdit = NULL; } LItemContainer::~LItemContainer() { DeleteObj(ItemEdit); DeleteObj(DragCol); Columns.DeleteObjects(); } void LItemContainer::PaintColumnHeadings(LSurface *pDC) { // Draw column headings if (!ColumnHeaders || !ColumnHeader.Valid()) return; LSurface *ColDC = pDC; LRect cr; #if DOUBLE_BUFFER_COLUMN_DRAWING LMemDC Bmp; if (!pDC->SupportsAlphaCompositing() && Bmp.Create(ColumnHeader.X(), ColumnHeader.Y(), System32BitColourSpace)) { ColDC = &Bmp; Bmp.Op(GDC_ALPHA); cr = ColumnHeader.ZeroTranslate(); } else #endif { cr = ColumnHeader; pDC->ClipRgn(&cr); } // Draw columns int cx = cr.x1; if (IconCol) { cr.x1 = cx; cr.x2 = cr.x1 + IconCol->Width() - 1; IconCol->SetPos(cr); IconCol->OnPaint(ColDC, cr); cx += IconCol->Width(); } // Draw other columns for (int i=0; iWidth() - 1; c->SetPos(cr); c->OnPaint(ColDC, cr); cx += c->Width(); } else LAssert(0); } // Draw ending piece cr.x1 = cx; cr.x2 = ColumnHeader.x2 + 2; if (cr.Valid()) { // Draw end section where there are no columns #ifdef MAC LArray Stops; LRect j(cr.x1, cr.y1, cr.x2-1, cr.y2-1); FillStops(Stops, j, false); LFillGradient(ColDC, j, true, Stops); ColDC->Colour(L_LOW); ColDC->Line(cr.x1, cr.y2, cr.x2, cr.y2); #else if (LApp::SkinEngine) { LSkinState State; State.pScreen = ColDC; State.Rect = cr; State.Enabled = Enabled(); State.View = this; LApp::SkinEngine->OnPaint_ListColumn(0, 0, &State); } else { LWideBorder(ColDC, cr, DefaultRaisedEdge); ColDC->Colour(LColour(L_MED)); ColDC->Rectangle(&cr); } #endif } #if DOUBLE_BUFFER_COLUMN_DRAWING if (!pDC->SupportsAlphaCompositing()) pDC->Blt(ColumnHeader.x1, ColumnHeader.y1, &Bmp); else #endif pDC->ClipRgn(0); } LItemColumn *LItemContainer::AddColumn(const char *Name, int Width, int Where) { LItemColumn *c = 0; if (Lock(_FL)) { c = new LItemColumn(this, Name, Width); if (c) { Columns.SetFixedLength(false); Columns.AddAt(Where, c); Columns.SetFixedLength(true); UpdateAllItems(); SendNotify(LNotifyItemColumnsChanged); } Unlock(); } return c; } bool LItemContainer::AddColumn(LItemColumn *Col, int Where) { bool Status = false; if (Col && Lock(_FL)) { Columns.SetFixedLength(false); Status = Columns.AddAt(Where, Col); Columns.SetFixedLength(true); if (Status) { UpdateAllItems(); SendNotify(LNotifyItemColumnsChanged); } Unlock(); } return Status; } void LItemContainer::DragColumn(int Index) { DeleteObj(DragCol); if (Index >= 0) { DragCol = new LDragColumn(this, Index); if (DragCol) { Capture(true); DragMode = DRAG_COLUMN; } } } int LItemContainer::ColumnAtX(int x, LItemColumn **Col, int *Offset) { LItemColumn *Column = 0; if (!Col) Col = &Column; int Cx = GetImageList() ? 16 : 0; int c; for (c=0; c= Cx && x < Cx + (*Col)->Width()) { if (Offset) *Offset = Cx; return c; } Cx += (*Col)->Width(); } return -1; } void LItemContainer::EmptyColumns() { Columns.DeleteObjects(); Invalidate(&ColumnHeader); SendNotify(LNotifyItemColumnsChanged); } int LItemContainer::HitColumn(int x, int y, LItemColumn *&Resize, LItemColumn *&Over) { int Index = -1; Resize = 0; Over = 0; if (ColumnHeaders && ColumnHeader.Overlap(x, y)) { // Clicked on a column heading int cx = ColumnHeader.x1 + ((IconCol) ? IconCol->Width() : 0); for (int n = 0; n < Columns.Length(); n++) { LItemColumn *c = Columns[n]; cx += c->Width(); if (abs(x-cx) < 5) { if (c->Resizable()) { Resize = c; Index = n; break; } } else if (c->d->Pos.Overlap(x, y)) { Over = c; Index = n; break; } } } return Index; } void LItemContainer::OnColumnClick(int Col, LMouse &m) { ColClick = Col; ColMouse = m; LNotification n(LNotifyItemColumnClicked); n.Int[0] = Col; SendNotify(n); } bool LItemContainer::GetColumnClickInfo(int &Col, LMouse &m) { if (ColClick >= 0) { Col = ColClick; m = ColMouse; return true; } return false; } void LItemContainer::GetColumnSizes(ColSizes &cs) { // Read in the current sizes cs.FixedPx = 0; cs.ResizePx = 0; for (int i=0; iResizable()) { ColInfo &Inf = cs.Info.New(); Inf.Col = c; Inf.Idx = i; Inf.ContentPx = c->GetContentSize(); Inf.WidthPx = c->Width(); cs.ResizePx += Inf.ContentPx; } else { cs.FixedPx += c->Width(); } } } LMessage::Result LItemContainer::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_RESIZE_TO_CONTENT: { ResizeColumnsToContent((int)Msg->A()); break; } default: break; } return LLayout::OnEvent(Msg); } void LItemContainer::ResizeColumnsToContent(int Border) { if (!InThread()) { PostEvent(M_RESIZE_TO_CONTENT, Border); return; } if (Lock(_FL)) { // Read in the current sizes ColSizes Sizes; GetColumnSizes(Sizes); // Allocate space int AvailablePx = GetClient().X() - 5; if (VScroll) AvailablePx -= VScroll->X(); int ExpandPx = AvailablePx - Sizes.FixedPx; Sizes.Info.Sort([](auto a, auto b) { int AGrowPx = a->GrowPx(); int BGrowPx = b->GrowPx(); return AGrowPx - BGrowPx; }); for (int i=0; iResizable()) { if (ExpandPx > Sizes.ResizePx) { // Everything fits... Inf.Col->Width(Inf.ContentPx + Border); } else { int Cx = GetClient().X(); double Ratio = Cx ? (double)Inf.ContentPx / Cx : 1.0; if (Ratio < 0.25) { Inf.Col->Width(Inf.ContentPx + Border); } else { // Need to scale to fit... int Px = Inf.ContentPx * ExpandPx / Sizes.ResizePx; Inf.Col->Width(Px + Border); } } ClearDs(Inf.Idx); } } Unlock(); } Invalidate(); } ////////////////////////////////////////////////////////////////////////////// LDragColumn::LDragColumn(LItemContainer *list, int col) { List = list; Index = col; Offset = 0; #ifdef LINUX Back = 0; #endif Col = List->ColumnAt(Index); if (Col) { Col->d->Down = false; Col->d->Drag = true; LRect r = Col->d->Pos; r.y1 = 0; r.y2 = List->Y()-1; List->Invalidate(&r, true); #if WINNATIVE LArray Ver; bool Layered = ( LGetOs(&Ver) == LGI_OS_WIN32 || LGetOs(&Ver) == LGI_OS_WIN64 ) && Ver[0] >= 5; SetStyle(WS_POPUP); SetExStyle(GetExStyle() | WS_EX_TOOLWINDOW); if (Layered) { SetExStyle(GetExStyle() | WS_EX_LAYERED | WS_EX_TRANSPARENT); } #endif Attach(0); #if WINNATIVE if (Layered) { SetWindowLong(Handle(), GWL_EXSTYLE, GetWindowLong(Handle(), GWL_EXSTYLE) | WS_EX_LAYERED); LLibrary User32("User32"); _SetLayeredWindowAttributes SetLayeredWindowAttributes = (_SetLayeredWindowAttributes)User32.GetAddress("SetLayeredWindowAttributes"); if (SetLayeredWindowAttributes) { if (!SetLayeredWindowAttributes(Handle(), 0, DRAG_COL_ALPHA, LWA_ALPHA)) { DWORD Err = GetLastError(); } } } #elif defined(__GTK_H__) Gtk::GtkWindow *w = WindowHandle(); if (w) { gtk_window_set_decorated(w, FALSE); gtk_widget_set_opacity(GtkCast(w, gtk_widget, GtkWidget), DRAG_COL_ALPHA / 255.0); } #endif LMouse m; List->GetMouse(m); Offset = m.x - r.x1; List->PointToScreen(ListScrPos); r.Offset(ListScrPos.x, ListScrPos.y); SetPos(r); Visible(true); } } LDragColumn::~LDragColumn() { Visible(false); if (Col) { Col->d->Drag = false; } List->Invalidate(); } #if LINUX_TRANS_COL void LDragColumn::OnPosChange() { Invalidate(); } #endif void LDragColumn::OnPaint(LSurface *pScreen) { #if LINUX_TRANS_COL LSurface *Buf = new LMemDC(X(), Y(), GdcD->GetBits()); LSurface *pDC = new LMemDC(X(), Y(), GdcD->GetBits()); #else LSurface *pDC = pScreen; #endif pDC->SetOrigin(Col->d->Pos.x1, 0); if (Col) Col->d->Drag = false; List->OnPaint(pDC); if (Col) Col->d->Drag = true; pDC->SetOrigin(0, 0); #if LINUX_TRANS_COL if (Buf && pDC) { LRect p = GetPos(); // Fill the buffer with the background Buf->Blt(ListScrPos.x - p.x1, 0, Back); // Draw painted column over the back with alpha Buf->Op(GDC_ALPHA); LApplicator *App = Buf->Applicator(); if (App) { App->SetVar(GAPP_ALPHA_A, DRAG_COL_ALPHA); } Buf->Blt(0, 0, pDC); // Put result on the screen pScreen->Blt(0, 0, Buf); } DeleteObj(Buf); DeleteObj(pDC); #endif } /////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////// // List column LItemColumn::LItemColumn(LItemContainer *parent, const char *name, int width) : ResObject(Res_Column) { d = new LItemColumnPrivate(parent); d->cWidth = width; if (name) Name(name); } LItemColumn::~LItemColumn() { if (d->Drag) { d->Parent->DragColumn(-1); } DeleteObj(d); } LItemContainer *LItemColumn::GetList() { return d->Parent; } void LItemColumn::Image(int i) { d->cImage = i; } int LItemColumn::Image() { return d->cImage; } bool LItemColumn::Resizable() { return d->CanResize; } void LItemColumn::Resizable(bool i) { d->CanResize = i; } bool LItemColumn::InDrag() { return d->Drag; } LRect LItemColumn::GetPos() { return d->Pos; } void LItemColumn::SetPos(LRect &r) { d->Pos = r; } void LItemColumn::Name(const char *n) { DeleteArray(d->cName); DeleteObj(d->Txt); d->cName = NewStr(n); LFont *f = d->Parent && d->Parent->GetFont() && d->Parent->GetFont()->Handle() ? d->Parent->GetFont() : LSysFont; d->Txt = new LDisplayString(f, (char*)n); if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } char *LItemColumn::Name() { return d->cName; } int LItemColumn::GetIndex() { if (d->Parent) { return (int)d->Parent->Columns.IndexOf(this); } return -1; } int LItemColumn::GetContentSize() { return d->Parent->GetContentSize(GetIndex()); } void LItemColumn::Width(int i) { if (d->cWidth != i) { d->cWidth = i; // If we are attached to a list... if (d->Parent) { /* FIXME int MyIndex = GetIndex(); // Clear all the cached strings for this column for (List::I it=d->Parent->Items.Start(); it.In(); it++) { DeleteObj((*it)->d->Display[MyIndex]); } if (d->Parent->IsAttached()) { // Update the screen from this column across LRect Up = d->Parent->GetClient(); Up.x1 = d->Pos.x1; d->Parent->Invalidate(&Up); } */ } // Notify listener auto p = d->Parent; if (p) p->SendNotify(LNotifyItemColumnsResized); } } int LItemColumn::Width() { return d->cWidth; } void LItemColumn::Mark(int i) { d->cMark = i; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } int LItemColumn::Mark() { return d->cMark; } void LItemColumn::Type(int i) { d->cType = i; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } int LItemColumn::Type() { return d->cType; } void LItemColumn::Icon(LSurface *i, bool Own) { if (d->OwnIcon) { DeleteObj(d->cIcon); } d->cIcon = i; d->OwnIcon = Own; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } LSurface *LItemColumn::Icon() { return d->cIcon; } bool LItemColumn::Value() { return d->Down; } void LItemColumn::Value(bool i) { d->Down = i; } void LItemColumn::OnPaint_Content(LSurface *pDC, LRect &r, bool FillBackground) { if (!d->Drag) { LCssTools Tools(d->Parent); auto Fore = Tools.GetFore(); auto cMed = LColour(L_MED); int Off = d->Down ? 1 : 0; int Mx = r.x1 + 8, My = r.y1 + ((r.Y() - 8) / 2); if (d->cIcon) { if (FillBackground) { pDC->Colour(cMed); pDC->Rectangle(&r); } int x = (r.X()-d->cIcon->X()) / 2; pDC->Blt( r.x1 + x + Off, r.y1 + ((r.Y()-d->cIcon->Y())/2) + Off, d->cIcon); if (d->cMark) { Mx += x + d->cIcon->X() + 4; } } else if (d->cImage >= 0 && d->Parent) { LColour Background = cMed; if (FillBackground) { pDC->Colour(Background); pDC->Rectangle(&r); } if (d->Parent->GetImageList()) { LRect *b = d->Parent->GetImageList()->GetBounds(); int x = r.x1; int y = r.y1; if (b) { b += d->cImage; x = r.x1 + ((r.X()-b->X()) / 2) - b->x1; y = r.y1 + ((r.Y()-b->Y()) / 2) - b->y1; } d->Parent->GetImageList()->Draw(pDC, x + Off, y + Off, d->cImage, Background); } if (d->cMark) { Mx += d->Parent->GetImageList()->TileX() + 4; } } else if (ValidStr(d->cName) && d->Txt) { LFont *f = d->Txt->GetFont(); if (!f) { LAssert(0); return; } LColour cText = Fore; #ifdef MAC // Contrast check if (d->cMark && (cText - cActiveCol) < 64) cText = cText.Invert(); #endif f->Transparent(!FillBackground); f->Colour(cText, cMed); int ty = d->Txt->Y(); int ry = r.Y(); int y = r.y1 + ((ry - ty) >> 1); d->Txt->Draw(pDC, r.x1 + Off + 3, y + Off, &r); if (d->cMark) { Mx += d->Txt->X(); } } else { if (FillBackground) { pDC->Colour(cMed); pDC->Rectangle(&r); } } #define ARROW_SIZE 9 pDC->Colour(Fore); Mx += Off; My += Off - 1; switch (d->cMark) { case GLI_MARK_UP_ARROW: { pDC->Line(Mx + 2, My, Mx + 2, My + ARROW_SIZE - 1); pDC->Line(Mx, My + 2, Mx + 2, My); pDC->Line(Mx + 2, My, Mx + 4, My + 2); break; } case GLI_MARK_DOWN_ARROW: { pDC->Line(Mx + 2, My, Mx + 2, My + ARROW_SIZE - 1); pDC->Line( Mx, My + ARROW_SIZE - 3, Mx + 2, My + ARROW_SIZE - 1); pDC->Line( Mx + 2, My + ARROW_SIZE - 1, Mx + 4, My + ARROW_SIZE - 3); break; } } } } void ColumnPaint(void *UserData, LSurface *pDC, LRect &r, bool FillBackground) { ((LItemColumn*)UserData)->OnPaint_Content(pDC, r, FillBackground); } void LItemColumn::OnPaint(LSurface *pDC, LRect &Rgn) { LRect r = Rgn; if (d->Drag) { pDC->Colour(DragColumnColour); pDC->Rectangle(&r); } else { #ifdef MAC LArray Stops; LRect j(r.x1, r.y1, r.x2-1, r.y2-1); FillStops(Stops, j, d->cMark != 0); LFillGradient(pDC, j, true, Stops); if (d->cMark) pDC->Colour(Rgb24(0x66, 0x93, 0xc0), 24); else pDC->Colour(Rgb24(178, 178, 178), 24); pDC->Line(r.x1, r.y2, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x2, r.y2); LRect n = r; n.Inset(2, 2); OnPaint_Content(pDC, n, false); #else if (LApp::SkinEngine) { LSkinState State; - State.pScreen = pDC; - State.ptrText = &d->Txt; - State.Rect = Rgn; - State.Value = Value(); - State.Enabled = GetList()->Enabled(); - State.View = d->Parent; + State.pScreen = pDC; + State.ptrText = &d->Txt; + State.Rect = Rgn; + State.Value = Value(); + State.Enabled = GetList()->Enabled(); + State.View = d->Parent; LApp::SkinEngine->OnPaint_ListColumn(ColumnPaint, this, &State); } else { if (d->Down) { LThinBorder(pDC, r, DefaultSunkenEdge); LFlatBorder(pDC, r, 1); } else { LWideBorder(pDC, r, DefaultRaisedEdge); } OnPaint_Content(pDC, r, true); } #endif } } /////////////////////////////////////////////////////////////////////////////////////////// LItem::LItem() { SelectionStart = SelectionEnd = -1; } LItem::~LItem() { } LView *LItem::EditLabel(int Col) { LItemContainer *c = GetContainer(); if (!c) return NULL; c->Capture(false); if (!c->ItemEdit) { c->ItemEdit = new LItemEdit(c, this, Col, SelectionStart, SelectionEnd); SelectionStart = SelectionEnd = -1; } return c->ItemEdit; } void LItem::OnEditLabelEnd() { LItemContainer *c = GetContainer(); if (c) c->ItemEdit = NULL; } void LItem::SetEditLabelSelection(int SelStart, int SelEnd) { SelectionStart = SelStart; SelectionEnd = SelEnd; } //////////////////////////////////////////////////////////////////////////////////////////// #define M_END_POPUP (M_USER+0x1500) #define M_LOSING_FOCUS (M_USER+0x1501) class LItemEditBox : public LEdit { LItemEdit *ItemEdit; public: LItemEditBox(LItemEdit *i, int x, int y, const char *s) : LEdit(100, 1, 1, x-3, y-3, s) { ItemEdit = i; Sunken(false); MultiLine(false); #ifndef LINUX SetPos(GetPos()); #endif } const char *GetClass() { return "LItemEditBox"; } void OnCreate() { LEdit::OnCreate(); Focus(true); } void OnFocus(bool f) { if (!f && GetParent()) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEditBox posting M_LOSING_FOCUS\n", _FL); #endif GetParent()->PostEvent(M_LOSING_FOCUS); } LEdit::OnFocus(f); } bool OnKey(LKey &k) { /* This should be handled by LEdit::OnKey now. Which will send a LNotifyEscapeKey or LNotifyReturnKey up to the ItemEdit OnNotify handler. switch (k.vkey) { case LK_RETURN: case LK_ESCAPE: { if (k.Down()) ItemEdit->OnNotify(this, k.c16); return true; } } */ return LEdit::OnKey(k); } bool SetScrollBars(bool x, bool y) { return false; } }; ////////////////////////////////////////////////////////////////////////////////////////// class LItemEditPrivate { public: LItem *Item; LEdit *Edit; int Index; bool Esc; LItemEditPrivate() { Esc = false; Item = 0; Index = 0; } }; LItemEdit::LItemEdit(LView *parent, LItem *item, int index, int SelStart, int SelEnd) : LPopup(parent) { d = new LItemEditPrivate; d->Item = item; d->Index = index; _BorderSize = 0; Sunken(false); Raised(false); #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit(%p/%s, %i, %i, %i)\n", _FL, parent, parent?parent->GetClass():0, index, SelStart, SelEnd); #endif LPoint p; SetParent(parent); GetParent()->PointToScreen(p); LRect r = d->Item->GetPos(d->Index); int MinY = 6 + LSysFont->GetHeight(); if (r.Y() < MinY) r.y2 = r.y1 + MinY - 1; r.Offset(p.x, p.y); SetPos(r); if (Attach(parent)) { d->Edit = new LItemEditBox(this, r.X(), r.Y(), d->Item->GetText(d->Index)); if (d->Edit) { d->Edit->Attach(this); d->Edit->Focus(true); if (SelStart >= 0) { d->Edit->Select(SelStart, SelEnd-SelStart+1); } } Visible(true); } } LItemEdit::~LItemEdit() { if (d->Item) { if (d->Edit && !d->Esc) { auto Str = d->Edit->Name(); #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - ~LItemEdit, updating item(%i) with '%s'\n", _FL, d->Index, Str); #endif LItemContainer *c = d->Item->GetContainer(); if (d->Item->SetText(Str, d->Index)) { d->Item->Update(); } else { // Item is deleting itself... // Make sure there is no dangling ptr on the container.. if (c) c->ItemEdit = NULL; // And we don't touch the no longer existant item.. d->Item = NULL; } } #if DEBUG_EDIT_LABEL else LgiTrace("%s:%i - Edit=%p Esc=%i\n", _FL, d->Edit, d->Esc); #endif if (d->Item) d->Item->OnEditLabelEnd(); } #if DEBUG_EDIT_LABEL else LgiTrace("%s:%i - Error: No item?\n", _FL); #endif DeleteObj(d); } LItem *LItemEdit::GetItem() { return d->Item; } void LItemEdit::OnPaint(LSurface *pDC) { pDC->Colour(L_BLACK); pDC->Rectangle(); } int LItemEdit::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case 100: { if (n.Type == LNotifyEscapeKey) { d->Esc = true; #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit got escape\n", _FL); #endif } if (n.Type == LNotifyEscapeKey || n.Type == LNotifyReturnKey) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit hiding on esc/enter\n", _FL); #endif d->Edit->KeyProcessed(); Visible(false); } break; } } return 0; } void LItemEdit::Visible(bool i) { LPopup::Visible(i); if (!i) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit posting M_END_POPUP\n", _FL); #endif PostEvent(M_END_POPUP); } } bool LItemEdit::OnKey(LKey &k) { if (d->Edit) return d->Edit->OnKey(k); return false; } void LItemEdit::OnFocus(bool f) { if (f && d->Edit) d->Edit->Focus(true); } LMessage::Result LItemEdit::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_LOSING_FOCUS: { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit get M_LOSING_FOCUS\n", _FL); #endif // One of us has to retain focus... don't care which control. if (Focus() || d->Edit->Focus()) break; // else fall thru to end the popup #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit falling thru to M_END_POPUP\n", _FL); #endif } case M_END_POPUP: { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit got M_END_POPUP, quiting\n", _FL); #endif if (d->Item && d->Item->GetContainer()) { d->Item->GetContainer()->Focus(true); } Quit(); return 0; } } return LPopup::OnEvent(Msg); } diff --git a/src/common/Widgets/ToolBar.cpp b/src/common/Widgets/ToolBar.cpp --- a/src/common/Widgets/ToolBar.cpp +++ b/src/common/Widgets/ToolBar.cpp @@ -1,1756 +1,1756 @@ /* ** FILE: GToolbar.cpp ** AUTHOR: Matthew Allen ** DATE: 18/10/2001 ** DESCRIPTION: Toolbar classes ** ** Copyright (C) 2001, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/Notifications.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #include "lgi/common/ToolBar.h" #include "lgi/common/ToolTip.h" #include "lgi/common/Menu.h" #define ToolBarHilightColour LC_HIGH #ifdef WIN32 HPALETTE GetSystemPalette(); bool BltBmpToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop); bool BltBmpToDc(HDC DestDC, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop); bool BltDcToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HDC SrcDC, int xSrc, int ySrc, DWORD dwRop); #define AttachButton(b) AddView(b); #else #define AttachButton(b) b->Attach(this); #endif enum IconCacheType { IconNormal, IconHilight, IconDisabled }; COLOUR Map(LSurface *pDC, COLOUR c); //////////////////////////////////////////////////////////////////////// LImageList *LLoadImageList(const char *File, int x, int y) { if (!File) return NULL; if (x < 0 || y < 0) { // Detect dimensions in the filename. auto leaf = LString(File).Split(DIR_STR).Last(); auto parts = leaf.RSplit(".", 1); auto last = parts[0].Split("-").Last(); auto dim = last.Split("x"); if (dim.Length() == 1) { auto i = dim[0].Strip().Int(); if (i > 0) x = y = (int)i; } else if (dim.Length() == 2) { auto X = dim[0].Strip().Int(), Y = dim[1].Strip().Int(); if (X > 0 && Y > 0) { x = (int)X; y = (int)Y; } } } auto Path = LFileExists(File) ? LString(File) : LFindFile(File); if (!Path) { LgiTrace("%s:%i - Couldn't find '%s'\n", _FL, File); return NULL; } LAutoPtr pDC(GdcD->Load(Path)); if (!pDC) { LgiTrace("%s:%i - Couldn't load '%s'\n", _FL, Path.Get()); return NULL; } return new LImageList(x, y, pDC); } LToolBar *LgiLoadToolbar(LViewI *Parent, const char *File, int x, int y) { LToolBar *Toolbar = new LToolBar; if (!Toolbar) return NULL; LString FileName = LFindFile(File); if (FileName) { bool Success = FileName && Toolbar->SetBitmap(FileName, x, y); if (!Success) { LgiMsg(Parent, "Can't load '%s' for the toolbar.\n" "This is probably because libpng/libjpeg is missing.", "LgiLoadToolbar", MB_OK, File); } } else { LgiMsg(Parent, "Can't find the graphic '%s' for the toolbar.\n" "You can find it in this program's archive.", "LgiLoadToolbar", MB_OK, File); } return Toolbar; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// #define ImgLst_Empty 0x40000000 #define IgmLst_Add 0x80000000 class LImageListPriv { public: LImageList *ImgLst; int Sx, Sy; uint8_t DisabledAlpha; struct CacheDC : public LMemDC { bool Disabled; LColour Back; }; LArray Cache; LArray Bounds; CacheDC *GetCache(LColour Back, bool Disabled) { if (Back.IsTransparent()) return NULL; for (int i=0; iBack == Back && dc->Disabled == Disabled) return dc; } CacheDC *dc = new CacheDC; if (dc) { dc->Disabled = Disabled; dc->Back = Back; bool Status = dc->Create(ImgLst->X(), ImgLst->Y(), GdcD->GetColourSpace()); if (Status) { dc->Colour(dc->Back); dc->Rectangle(); dc->Op(GDC_ALPHA); if (Disabled) { LMemDC tmp(ImgLst->X(), ImgLst->Y(), System32BitColourSpace, LSurface::SurfaceRequireExactCs); tmp.Colour(0, 32); tmp.Rectangle(); tmp.Op(GDC_ALPHA); tmp.Blt(0, 0, ImgLst); tmp.SetConstantAlpha(DisabledAlpha); dc->Blt(0, 0, &tmp); } else { dc->Blt(0, 0, ImgLst); } Cache.Add(dc); } else { delete dc; LAssert(!"Create memdc failed."); } } return dc; } LImageListPriv(LImageList *imglst, int x, int y) { ImgLst = imglst; Sx = x; Sy = y; DisabledAlpha = 40; } ~LImageListPriv() { Cache.DeleteObjects(); } }; static bool HasPad(LColourSpace cs) { if (cs == CsRgbx32 || cs == CsBgrx32 || cs == CsXrgb32 || cs == CsXbgr32) return true; return false; } LImageList::LImageList(int x, int y, LSurface *pDC) { d = new LImageListPriv(this, x, y); uint32_t Transparent = 0; if (pDC && Create(pDC->X(), pDC->Y(), System32BitColourSpace, LSurface::SurfaceRequireExactCs)) { Colour(Transparent, 32); Rectangle(); int Old = Op(GDC_ALPHA); Blt(0, 0, pDC); Op(Old); #if 0 printf("Toolbar input image is %s, has_alpha=%i, has_pad=%i\n", LColourSpaceToString(pDC->GetColourSpace()), pDC->HasAlpha(), HasPad(pDC->GetColourSpace())); #endif - #if 1 + #if 0 static int Idx = 0; char s[256]; sprintf_s(s, sizeof(s), "imglst_%i.bmp", Idx++); GdcD->Save(s, this); // sprintf_s(s, sizeof(s), "src_%i.bmp", Idx++); // GdcD->Save(s, pDC); #endif if (pDC->GetBits() < 32 || HasPad(pDC->GetColourSpace())) { if (!pDC->HasAlpha()) { // No source alpha, do colour keying to create the alpha channel REG uint32_t *p = (uint32_t*)(*this)[0]; if (p) { uint32_t key = *p; for (int y=0; ya == 0) { p->r = 0; p->g = 0; p->b = 0; } p++; } } } } } LImageList::~LImageList() { DeleteObj(d); } void LImageList::Draw(LSurface *pDC, int Dx, int Dy, int Image, LColour Background, bool Disabled) { if (!pDC) return; LRect rSrc; rSrc.ZOff(d->Sx-1, d->Sy-1); rSrc.Offset(Image * d->Sx, 0); LImageListPriv::CacheDC *Cache = d->GetCache(Background, Disabled); if (!Cache && Background.IsValid()) { LRect rDst; rDst.ZOff(d->Sx-1, d->Sy-1); rDst.Offset(Dx, Dy); pDC->Colour(Background); pDC->Rectangle(&rDst); pDC->Colour(LColour(255, 0, 0)); pDC->Line(rDst.x1, rDst.y1, rDst.x2, rDst.y2); pDC->Line(rDst.x2, rDst.y1, rDst.x1, rDst.y2); return; } if (pDC->SupportsAlphaCompositing()) { int Old = pDC->Op(GDC_ALPHA, Disabled ? d->DisabledAlpha : -1); pDC->Blt(Dx, Dy, this, &rSrc); pDC->Op(Old); } else if (Cache) { pDC->Blt(Dx, Dy, Cache, &rSrc); } else LAssert(!"Impl me."); } int LImageList::TileX() { return d->Sx; } int LImageList::TileY() { return d->Sy; } int LImageList::GetItems() { return X() / d->Sx; } void LImageList::Update(int Flags) { } uint8_t LImageList::GetDisabledAlpha() { return d->DisabledAlpha; } void LImageList::SetDisabledAlpha(uint8_t alpha) { d->DisabledAlpha = alpha; } LRect LImageList::GetIconRect(int Idx) { LRect r(0, 0, -1, -1); if (Idx >= 0 && Idx < GetItems()) { r.ZOff(d->Sx-1, d->Sy-1); r.Offset(Idx * d->Sx, 0); } return r; } LRect *LImageList::GetBounds() { if (!d->Bounds.Length() && (*this)[0]) { int Items = GetItems(); if (d->Bounds.Length(Items)) { for (int i=0; iBounds[i].ZOff(d->Sx - 1, d->Sy - 1); d->Bounds[i].Offset(i * d->Sx, 0); LFindBounds(this, &d->Bounds[i]); d->Bounds[i].Offset(-i * d->Sx, 0); } } } return &d->Bounds[0]; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// class LToolBarPrivate { public: int Bx, By; int Sx, Sy; bool Vertical; bool Text; int LastIndex; bool OwnImgList; LImageList *ImgList; LFont *Font; LToolTip *Tip; // Customization menu LDom *CustomDom; const char *CustomProp; // bitmap cache LAutoPtr IconCache; LToolBarPrivate() { Bx = By = 16; Sx = Sy = 10; Vertical = false; Text = false; Font = 0; Tip = 0; CustomProp = 0; CustomDom = 0; } bool ShowTextLabels() { return (Text || !ImgList) && Font; } void FixSeparators(LToolBar *Tb) { // Fix up separators so that no 2 separators are next to each other. I.e. // all the buttons between them are switched off. LToolButton *Last = 0; bool HasVis = false; for (LViewI *v: Tb->IterateViews()) { LToolButton *Btn = dynamic_cast(v); if (Btn) { if (Btn->Separator()) { Btn->Visible(HasVis); if (HasVis) { Last = Btn; } HasVis = false; } else { HasVis |= Btn->Visible(); } } } if (Last) { Last->Visible(HasVis); } } void Customizable(LToolBar *Tb) { LVariant v; if (CustomDom) { CustomDom->GetValue(CustomProp, v); } char *o; if ((o = v.Str())) { auto t = LString(o).SplitDelimit(","); if (t.Length() >= 1) { Text = stricmp(t[0], "text") == 0; // Make all controls not visible. for (auto v: Tb->IterateViews()) { LToolButton *Btn = dynamic_cast(v); if (Btn) v->Visible(false); } // Set sub-set of ctrls visible according to saved ID list for (int i=1; i 0) Tb->SetCtrlVisible(Id, true); } FixSeparators(Tb); } } } }; ///////////////////////////////////////////////////////////////////////////////////////////////////////// struct LToolButtonPriv { LArray Text; }; LToolButton::LToolButton(int Bx, int By) { d = new LToolButtonPriv; Type = TBT_PUSH; SetId(IDM_NONE); Down = false; Clicked = false; Over = false; ImgIndex = -1; NeedsRightClick = false; LRect r(0, 0, Bx+1, By+1); SetPos(r); SetParent(0); TipId = -1; _BorderSize = 0; LResources::StyleElement(this); } LToolButton::~LToolButton() { d->Text.DeleteObjects(); delete d; } bool LToolButton::Name(const char *n) { bool s = LView::Name(n); d->Text.DeleteObjects(); return s; } void LToolButton::Layout() { auto Parent = GetParent(); LToolBar *ToolBar = dynamic_cast(Parent); if (!ToolBar) return; // Text auto s = Name(); if (!ToolBar->d->ShowTextLabels() || !s) return; // Write each word centered on a different line char Buf[256]; strcpy_s(Buf, sizeof(Buf), s); auto t = LString(Buf).SplitDelimit(" "); if (t.Length() < 3) { if (t.Length() > 0) d->Text.Add(new LDisplayString(ToolBar->d->Font, t[0])); if (t.Length() > 1) d->Text.Add(new LDisplayString(ToolBar->d->Font, t[1])); } else if (t.Length() == 3) { sprintf_s(Buf, sizeof(Buf), "%s %s", t[0].Get(), t[1].Get()); LDisplayString *d1 = new LDisplayString(ToolBar->d->Font, Buf); sprintf_s(Buf, sizeof(Buf), "%s %s", t[1].Get(), t[2].Get()); LDisplayString *d2 = new LDisplayString(ToolBar->d->Font, Buf); if (d1 && d2) { if (d1->X() < d2->X()) { DeleteObj(d2); d->Text.Add(d1); d->Text.Add(new LDisplayString(ToolBar->d->Font, t[2])); } else { DeleteObj(d1); d->Text.Add(new LDisplayString(ToolBar->d->Font, t[0])); d->Text.Add(d2); } } } } void LToolButton::OnPaint(LSurface *pDC) { LToolBar *Par = dynamic_cast(GetParent()); bool e = Enabled(); if (Par) { LRect p = GetClient(); #if 0 // def _DEBUG pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif LCssTools Tools(this); LColour cBack = Tools.GetBack(); auto BackImg = Tools.GetBackImage(); bool Hilight = e && Over; if (Hilight) cBack = cBack.Mix(LColour::White); // Draw Background if (GetId() >= 0) { // Draw border LColour Background; if (Down) // Sunken if the button is pressed LThinBorder(pDC, p, DefaultSunkenEdge); if (BackImg) { LDoubleBuffer Buf(pDC); Tools.PaintContent(pDC, p); if (Hilight) { // Draw translucent white over image... pDC->Op(GDC_ALPHA); pDC->Colour(LColour(255, 255, 255, 128)); pDC->Rectangle(&p); } } else { Background = cBack; pDC->Colour(Background); pDC->Box(&p); p.Inset(1, 1); } LRect IconPos; if (Par->d->ImgList) IconPos.Set(0, 0, Par->d->ImgList->TileX()-1, Par->d->ImgList->TileY()-1); else IconPos.ZOff(Par->d->Bx-1, Par->d->By-1); LRegion Unpainted(p); // Center the icon if (IconPos.X() < p.X() - 1) IconPos.Offset((p.X() - IconPos.X()) >> 1, 0); // Offset it if the button is pressed if (Down) IconPos.Offset(2, 2); // Draw any icon. if (ImgIndex >= 0) { if (Par->d->ImgList) { // Draw cached if (BackImg) { Par->d->ImgList->SetDisabledAlpha(0x60); } else if (pDC->SupportsAlphaCompositing()) { pDC->Colour(Background); pDC->Rectangle(&IconPos); } Par->d->ImgList->Draw(pDC, IconPos.x1, IconPos.y1, ImgIndex, Background, !e); Unpainted.Subtract(&IconPos); if (!BackImg) { // Fill in the rest of the area pDC->Colour(Background); for (LRect *r = Unpainted.First(); r; r = Unpainted.Next()) pDC->Rectangle(r); } } else { // Draw a red cross indicating no icons. pDC->Colour(Background); pDC->Rectangle(&p); pDC->Colour(LColour::Red); pDC->Line(IconPos.x1, IconPos.y1, IconPos.x2, IconPos.y2); pDC->Line(IconPos.x2, IconPos.y1, IconPos.x1, IconPos.y2); } } else if (!BackImg) { Tools.PaintContent(pDC, p); } // Text if (Par->d->ShowTextLabels()) { if (Name() && !d->Text.Length()) { Layout(); } if (d->Text.Length()) { // Write each word centered on a different line int Ty = Down + Par->d->By + 2; LColour a = Tools.GetFore(); LColour b = Tools.GetBack(); if (!e) a = b.Mix(a); Par->d->Font->Colour(a, b); for (int i=0; iText.Length(); i++) { LDisplayString *Ds = d->Text[i]; Ds->Draw(pDC, Down + ((X()-Ds->X())/2), Ty); Ty += Ds->Y(); } } } } else { // Separator int Px = X()-1; int Py = Y()-1; if (BackImg) Tools.PaintContent(pDC, p); else { pDC->Colour(cBack); pDC->Rectangle(); } LColour cLow = cBack.Mix(LColour::Black); LColour cHigh = cBack.Mix(LColour::White, 0.8f); if (X() > Y()) { int c = Y()/2-1; pDC->Colour(cLow); pDC->Line(2, c, Px-2, c); pDC->Colour(cHigh); pDC->Line(2, c+1, Px-2, c+1); } else { int c = X()/2-1; pDC->Colour(cLow); pDC->Line(c, 2, c, Py-2); pDC->Colour(cHigh); pDC->Line(c+1, 2, c+1, Py-2); } } } #if 0 // def _DEBUG pDC->Colour(LColour(255, 0, 255)); pDC->Box(); #endif } void LToolButton::Image(int i) { if (ImgIndex != i) { ImgIndex = i; Invalidate(); } } void LToolButton::Value(int64 b) { switch (Type) { case TBT_PUSH: { // do nothing... can't set value break; } case TBT_TOGGLE: { if (Value() != b) { Down = b != 0; Invalidate(); SendNotify(LNotifyValueChanged); } break; } case TBT_RADIO: { if (GetParent() && b) { // Clear any other radio buttons that are down auto it = GetParent()->IterateViews(); ssize_t CurIdx = it.IndexOf(this); if (CurIdx >= 0) { for (ssize_t i=CurIdx-1; i>=0; i--) { LToolButton *But = dynamic_cast(it[i]); if (But->Separator()) break; if (But->Type == TBT_RADIO && But->Down) But->Value(false); } for (size_t i=CurIdx+1; i(it[i]); if (But->Separator()) break; if (But->Type == TBT_RADIO && But->Down) But->Value(false); } } } Down = b != 0; if (GetParent()) { GetParent()->Invalidate(); SendNotify(LNotifyValueChanged); } break; } } } void LToolButton::SendCommand() { LToolBar *t = dynamic_cast(GetParent()); if (t) t->OnButtonClick(this); else printf("%s:%i - Error: parent not toolbar.\n", _FL); } void LToolButton::OnMouseClick(LMouse &m) { LToolBar *ToolBar = dynamic_cast(GetParent()); #if 0 printf("tool button click %i,%i down=%i, left=%i right=%i middle=%i, ctrl=%i alt=%i shift=%i Double=%i\n", m.x, m.y, m.Down(), m.Left(), m.Right(), m.Middle(), m.Ctrl(), m.Alt(), m.Shift(), m.Double()); #endif if (m.IsContextMenu()) { if (!NeedsRightClick && ToolBar && ToolBar->IsCustomizable()) { m.ToScreen(); ToolBar->ContextMenu(m); } else { SendNotify(LNotification(m)); } } else if (m.Left()) { // left click action... if (GetId() >= 0 && Enabled()) { switch (Type) { case TBT_PUSH: { bool Old = Down; Clicked = m.Down(); Capture(m.Down()); if (Old && IsOver(m)) { SendCommand(); SendNotify(LNotifyActivate); } Down = m.Down(); if (Old != Down) { Invalidate(); } break; } case TBT_TOGGLE: { if (m.Down()) { if (m.Left()) { Value(!Down); SendCommand(); } SendNotify(LNotifyActivate); } break; } case TBT_RADIO: { if (m.Down()) { if (!Down && m.Left()) { Value(true); SendCommand(); } SendNotify(LNotifyActivate); } break; } } } } } void LToolButton::OnMouseEnter(LMouse &m) { if (!Separator() && Enabled()) { Over = true; Invalidate(); } if (Clicked) { Value(true); Invalidate(); } else { LToolBar *Bar = dynamic_cast(GetParent()); if (Bar) { Bar->OnMouseEnter(m); if (!Bar->TextLabels() && Bar->d->Tip && TipId < 0) { TipId = Bar->d->Tip->NewTip(Name(), GetPos()); } } if (GetParent()) { LToolBar *ToolBar = dynamic_cast(GetParent()); if (ToolBar) ToolBar->PostDescription(this, Name()); } } } void LToolButton::OnMouseMove(LMouse &m) { } void LToolButton::OnMouseExit(LMouse &m) { if (Over) { Over = false; Invalidate(); } if (Clicked) { Value(false); Invalidate(); } else if (GetParent()) { LToolBar *ToolBar = dynamic_cast(GetParent()); if (ToolBar) ToolBar->PostDescription(this, ""); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////// LToolBar::LToolBar() { d = new LToolBarPrivate; Name("LGI_Toolbar"); _BorderSize = 1; _IsToolBar = 1; // Setup tool button font LFontType SysFontType; if (SysFontType.GetSystemFont("Small")) { d->Font = SysFontType.Create(); if (d->Font) { d->Font->PointSize(MIN(d->Font->PointSize(), LSysFont->PointSize())); d->Font->Colour(L_TEXT); d->Font->Bold(false); d->Font->Transparent(true); } } d->LastIndex = 0; d->OwnImgList = false; d->ImgList = 0; GetCss(true)->BackgroundColor(LColour(L_MED).Mix(LColour::Black, 0.05f)); LResources::StyleElement(this); } LToolBar::~LToolBar() { DeleteObj(d->Tip); if (d->OwnImgList) DeleteObj(d->ImgList); DeleteObj(d->Font); DeleteObj(d); } void LToolBar::OnCreate() { #ifndef WIN32 AttachChildren(); #endif } int LToolBar::GetBx() { return d->Bx; } int LToolBar::GetBy() { return d->By; } void LToolBar::ContextMenu(LMouse &m) { if (IsCustomizable()) { LSubMenu *Sub = new LSubMenu; if (Sub) { int n = 1; for (auto it = Children.begin(); it != Children.end(); it++, n++) { LViewI *v = *it; LToolButton *Btn = dynamic_cast(v); if (Btn && Btn->Separator()) { Sub->AppendSeparator(); } else { auto Item = Sub->AppendItem(v->Name(), n, true); if (Item) { Item->Checked(v->Visible()); } } } Sub->AppendSeparator(); auto Txt = Sub->AppendItem(LLoadString(L_TOOLBAR_SHOW_TEXT, "Show Text Labels"), 1000, true); Txt->Checked(d->Text); bool Save = false; int Pick = Sub->Float(this, m); switch (Pick) { case 1000: { d->Text = !d->Text; Save = true; SendNotify(LNotifyTableLayoutRefresh); break; } default: { LViewI *Ctrl = Children[Pick - 1]; if (Ctrl) { Ctrl->Visible(!Ctrl->Visible()); Save = true; } break; } } DeleteObj(Sub); if (Save) { LStringPipe p(256); p.Push((char*) (d->Text ? "text" : "no")); for (auto v: Children) { if (v->Visible()) { p.Print(",%i", v->GetId()); } } char *o = p.NewStr(); if (o) { if (d->CustomDom) { LVariant v(o); d->CustomDom->SetValue(d->CustomProp, v); } DeleteArray(o); } d->FixSeparators(this); for (auto v: Children) { LToolButton *b = dynamic_cast(v); if (b && b->TipId >= 0) { d->Tip->DeleteTip(b->TipId); b->TipId = -1; } } GetWindow()->PourAll(); } } } } bool LToolBar::IsCustomizable() { return d->CustomDom != 0 && d->CustomProp; } void LToolBar::Customizable(LDom *Store, const char *Option) { d->CustomDom = Store; d->CustomProp = Option; d->Customizable(this); } bool LToolBar::IsVertical() { return d->Vertical; } void LToolBar::IsVertical(bool v) { d->Vertical = v; } bool LToolBar::TextLabels() { return d->Text; } void LToolBar::TextLabels(bool i) { d->Text = i; } LFont *LToolBar::GetFont() { return d->Font; } bool LToolBar::OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Min == 0) { // Calc width LRegion r(0, 0, 10000, 10000); Pour(r); Inf.Width.Min = X(); Inf.Width.Max = X(); } else { // Calc height Inf.Height.Min = Y(); Inf.Height.Max = Y(); } return true; } #define GetBorderSpacing() GetCss() && GetCss()->BorderSpacing().IsValid() ? \ GetCss()->BorderSpacing().ToPx(X(), GetFont()) : \ 1 bool LToolBar::Pour(LRegion &r) { int BorderSpacing = GetBorderSpacing(); int EndX = 0; int EndY = 0; int MaxDim = 0; LCssTools Tools(this); LRect Border = Tools.GetBorder(r); LRect Padding = Tools.GetPadding(r); int PosX = BorderSpacing + Border.x1 + Padding.x1; int PosY = BorderSpacing + Border.y1 + Padding.y1; LRect ButPos; for (auto But: Children) { if (But->Visible()) { int Tx = 0, Ty = 0; LToolButton *Btn = dynamic_cast(But); if (d->ShowTextLabels()) { if (Btn) { if (Btn->d->Text.Length() == 0) { Btn->Layout(); } for (int i=0; id->Text.Length(); i++) { LDisplayString *Ds = Btn->d->Text[i]; Tx = MAX(Ds->X() + 4, Tx); Ty += Ds->Y(); } } } ButPos = But->GetPos(); if (Btn) { if (Btn->Separator()) { // This will be stretched out later by the code that makes // everything the same height. ButPos.ZOff(BORDER_SEPARATOR+1, BORDER_SEPARATOR+1); } else { if (Btn->Image() >= 0) { // Set initial size to the icon size ButPos.ZOff(d->Bx + 2, d->By + 2); } else { // Otherwise default to text size if (d->Vertical) ButPos.ZOff(0, 7); else ButPos.ZOff(7, 0); } Tx += 4; if (ButPos.X() < Tx) { // Make button wider for text label ButPos.x2 = Tx - 1; } ButPos.y2 += Ty; } } if (d->Vertical) MaxDim = MAX(MaxDim, ButPos.X()); else MaxDim = MAX(MaxDim, ButPos.Y()); ButPos.Offset(PosX - ButPos.x1, PosY - ButPos.y1); if (But->GetId() == IDM_BREAK) { ButPos.ZOff(0, 0); if (d->Vertical) { PosX = MaxDim; PosY = BORDER_SHADE + BorderSpacing; } else { PosX = BORDER_SHADE + BorderSpacing; PosY = MaxDim; } } else { if (d->Vertical) PosY = ButPos.y2 + BorderSpacing; else PosX = ButPos.x2 + BorderSpacing; } But->SetPos(ButPos); } else { LRect p(-100, -100, -90, -90); But->SetPos(p); } } for (auto w: Children) { LRect p = w->GetPos(); if (d->Vertical) { if (w->X() < MaxDim) { p.x2 = p.x1 + MaxDim - 1; w->SetPos(p); } } else { if (w->Y() < MaxDim) { p.y2 = p.y1 + MaxDim - 1; w->SetPos(p); } } EndX = MAX(EndX, p.x2); EndY = MAX(EndY, p.y2); } d->Sx = EndX + BorderSpacing; d->Sy = EndY + BorderSpacing; d->Sx += Border.x2 + Padding.x2; d->Sy += Border.y2 + Padding.y2; LRect n; n.ZOff(MAX(7, d->Sx), MAX(7, d->Sy)); LRect *Best = FindLargestEdge(r, GV_EDGE_TOP); if (Best) { n.Offset(Best->x1, Best->y1); n.Bound(Best); SetPos(n, true); // _Dump(); return true; } else LgiTrace("%s:%i - No best pos.\n", _FL); return false; } void LToolBar::OnButtonClick(LToolButton *Btn) { LViewI *v = GetNotify() ? GetNotify() : GetParent(); if (v && Btn) { int Id = Btn->GetId(); if (v->PostEvent(M_COMMAND, (LMessage::Param) Id #if LGI_VIEW_HANDLE , (LMessage::Param) Handle() #endif )) ; //printf("Send M_COMMAND(%i)\n", Id); else printf("%s:%i - Failed to send M_COMMAND.\n", _FL); } else printf("%s:%i - Ptr error: %p %p\n", _FL, v, Btn); } int LToolBar::PostDescription(LView *Ctrl, const char *Text) { if (GetParent()) { return GetParent()->PostEvent(M_DESCRIBE, (LMessage::Param) Ctrl, (LMessage::Param) Text); } return 0; } LMessage::Result LToolBar::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_CHANGE: { if (GetParent()) return GetParent()->OnEvent(Msg); LAutoPtr note((LNotification*)Msg->B()); break; } } return LView::OnEvent(Msg); } void LToolBar::OnPaint(LSurface *pDC) { LRect c = GetClient(); LCssTools Tools(this); Tools.PaintBorder(pDC, c); Tools.PaintPadding(pDC, c); Tools.PaintContent(pDC, c); } void LToolBar::OnMouseClick(LMouse &m) { } void LToolBar::OnMouseEnter(LMouse &m) { if (!d->Tip) { d->Tip = new LToolTip; if (d->Tip) { d->Tip->Attach(this); } } } void LToolBar::OnMouseExit(LMouse &m) { } void LToolBar::OnMouseMove(LMouse &m) { } bool LToolBar::SetBitmap(char *File, int bx, int by) { LAutoPtr pDC(GdcD->Load(File)); return pDC ? SetDC(pDC, bx, by) : false; } bool LToolBar::SetDC(LSurface *pNewDC, int bx, int by) { if (d->OwnImgList) { DeleteObj(d->ImgList); } d->Bx = bx; d->By = by; if (pNewDC) { d->ImgList = new LImageList(bx, by, pNewDC); if (d->ImgList) { d->OwnImgList = true; return true; } } return false; } LImageList *LToolBar::GetImageList() { return d->ImgList; } bool LToolBar::SetImageList(LImageList *l, int bx, int by, bool Own) { if (d->OwnImgList) DeleteObj(d->ImgList); d->OwnImgList = Own; d->Bx = bx; d->By = by; d->ImgList = l; return d->ImgList != 0; } LToolButton *LToolBar::AppendButton(const char *Tip, int Id, int Type, int Enabled, int IconId) { // bool HasIcon = IconId != TOOL_ICO_NONE; LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->Name(Tip); But->SetId(Id); But->Type = Type; But->Enabled(Enabled != 0); if (IconId >= 0) { But->ImgIndex = IconId; } else if (IconId == TOOL_ICO_NEXT) { But->ImgIndex = d->LastIndex++; } else if (IconId == TOOL_ICO_NONE) { But->ImgIndex = -1; } AttachButton(But); } return But; } bool LToolBar::AppendSeparator() { LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->SetId(IDM_SEPARATOR); AttachButton(But); return true; } return false; } bool LToolBar::AppendBreak() { LToolButton *But = new LToolButton(d->Bx, d->By); if (But) { But->SetId(IDM_BREAK); But->SetParent(this); AttachButton(But); return true; } return false; } bool LToolBar::AppendControl(LView *Ctrl) { bool Status = false; if (Ctrl) { Ctrl->SetParent(this); AttachButton(Ctrl); Status = true; } return Status; } void LToolBar::Empty() { for (auto But: Children) { DeleteObj(But); } } #ifdef MAC bool LToolBar::Attach(LViewI *parent) { return LLayout::Attach(parent); } #endif /////////////////////////////////////////////////////////////////////// COLOUR Map(LSurface *pDC, COLOUR c) { if (pDC && pDC->GetBits() <= 8) { if (pDC->IsScreen()) { c = CBit(24, c); } #ifdef WIN32 else { HPALETTE hPal = GetSystemPalette(); if (hPal) { c = GetNearestPaletteIndex(hPal, c); DeleteObject(hPal); } } #endif } return c; } #ifdef WIN32 HPALETTE GetSystemPalette() { HPALETTE hPal = 0; LOGPALETTE *Log = (LOGPALETTE*) new uchar[sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 255)]; if (Log) { Log->palVersion = 0x300; Log->palNumEntries = 256; HDC hDC = CreateCompatibleDC(0); GetSystemPaletteEntries(hDC, 0, 256, Log->palPalEntry); DeleteDC(hDC); hPal = CreatePalette(Log); } return hPal; } bool BltBmpToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC DestDC = CreateCompatibleDC(0); HDC SrcDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hDest = (HBITMAP) SelectObject(DestDC, hDest); hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hDest = (HBITMAP) SelectObject(DestDC, hDest); hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); } if (DestDC) { DeleteDC(DestDC); } if (SrcDC) { DeleteDC(SrcDC); } return Status; } bool BltBmpToDc(HDC DestDC, int xDst, int yDst, int cx, int cy, HBITMAP hSrc, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC SrcDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hSrc = (HBITMAP) SelectObject(SrcDC, hSrc); } if (SrcDC) { DeleteDC(SrcDC); } return Status; } bool BltDcToBmp(HBITMAP hDest, int xDst, int yDst, int cx, int cy, HDC SrcDC, int xSrc, int ySrc, DWORD dwRop) { bool Status = false; HDC DestDC = CreateCompatibleDC(0); if (DestDC && SrcDC) { hDest = (HBITMAP) SelectObject(DestDC, hDest); Status = BitBlt(DestDC, xDst, yDst, cx, cy, SrcDC, xSrc, ySrc, dwRop) != 0; hDest = (HBITMAP) SelectObject(DestDC, hDest); } if (DestDC) { DeleteDC(DestDC); } return Status; } #endif diff --git a/src/common/Widgets/Tree.cpp b/src/common/Widgets/Tree.cpp --- a/src/common/Widgets/Tree.cpp +++ b/src/common/Widgets/Tree.cpp @@ -1,2261 +1,2261 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Tree.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #define TREE_BLOCK 16 #define DRAG_THRESHOLD 4 #define DRAG_SCROLL_EDGE 20 #define DRAG_SCROLL_X 8 #define DRAG_SCROLL_Y 1 #define TreeUpdateNow false -#define TREELOCK LMutex::Auto Lck(d, _FL); #define ForAll(Items) for (auto c : Items) +struct LTreeLocker +{ + LTree *t = NULL; + bool status = false; + LTreeLocker(LTree *tree, const char *file, int line) : t(tree) + { + if (t) + status = t->Lock(file, line); + } + ~LTreeLocker() + { + if (status && t) + t->Unlock(); + } +}; + +#define TREELOCK(ptr) LTreeLocker _lock(ptr, _FL); + ////////////////////////////////////////////////////////////////////////////// // Private class definitions for binary compatibility -class LTreePrivate : public LMutex +class LTreePrivate { public: // Private data int LineFlags[4]; bool LayoutDirty; LPoint Limit; LPoint LastClick; LPoint DragStart; int DragData; LMemDC *IconCache; bool InPour; int64 DropSelectTime; int8 IconTextGap; int LastLayoutPx; LMouse *CurrentClick; LTreeItem *ScrollTo; // Visual style LTree::ThumbStyle Btns; bool JoiningLines; // Pointers into items... be careful to clear when deleting items... LTreeItem *LastHit; List Selection; LTreeItem *DropTarget; - LTreePrivate() : LMutex("LTreePrivate") + LTreePrivate() { CurrentClick = NULL; LastLayoutPx = -1; DropSelectTime = 0; InPour = false; LastHit = 0; DropTarget = 0; IconCache = 0; LayoutDirty = true; IconTextGap = 0; ScrollTo = NULL; Btns = LTree::TreeTriangle; JoiningLines = false; } ~LTreePrivate() { DeleteObj(IconCache); } }; class LTreeItemPrivate { LArray Ds; LArray ColPx; public: LTreeItem *Item; LRect Pos; LRect Thumb; LRect Text; LRect Icon; bool Open; bool Selected; bool Visible; bool Last; int Depth; LTreeItemPrivate(LTreeItem *it) { Item = it; Ds = NULL; Pos.ZOff(-1, -1); Open = false; Selected = false; Visible = false; Last = false; Depth = 0; Text.ZOff(-1, -1); Icon.ZOff(-1, -1); } ~LTreeItemPrivate() { Ds.DeleteObjects(); } LDisplayString *GetDs(int Col, int FixPx) { if (!Ds[Col]) { LFont *f = Item->GetTree() ? Item->GetTree()->GetFont() : LSysFont; auto txt = Item->GetText(Col); if (txt) { Ds[Col] = new LDisplayString(f, Item->GetText(Col)); if (Ds[Col]) { ColPx[Col] = Ds[Col]->X(); if (FixPx > 0) { Ds[Col]->TruncateWithDots(FixPx); } } } } return Ds[Col]; } void ClearDs(int Col = -1) { if (Col >= 0) { delete Ds[Col]; Ds[Col] = NULL; } else { Ds.DeleteObjects(); } } int GetColumnPx(int Col) { int BasePx = 0; GetDs(Col, 0); if (Col == 0) { BasePx = (Depth + 1) * TREE_BLOCK; } return ColPx[Col] + BasePx; } }; ////////////////////////////////////////////////////////////////////////////// LTreeNode::LTreeNode() { Parent = NULL; Tree = NULL; } LTreeNode::~LTreeNode() { } void LTreeNode::SetLayoutDirty() { Tree->d->LayoutDirty = true; } void LTreeNode::_Visible(bool v) { for (LTreeItem *i=GetChild(); i; i=i->GetNext()) { LAssert(i != this); i->OnVisible(v); i->_Visible(v); } } void LTreeNode::_ClearDs(int Col) { List::I it = Items.begin(); for (LTreeItem *c = *it; c; c = *++it) { c->_ClearDs(Col); } } LItemContainer *LTreeItem::GetContainer() { return Tree; } LTreeItem *LTreeNode::Insert(LTreeItem *Obj, ssize_t Idx) { LAssert(Obj != this); if (Obj && Obj->Tree) Obj->Remove(); LTreeItem *NewObj = Obj ? Obj : new LTreeItem; if (NewObj) { NewObj->Parent = Item(); NewObj->_SetTreePtr(Tree); Items.Delete(NewObj); Items.Insert(NewObj, Idx); if (Tree) { Tree->d->LayoutDirty = true; if (Pos() && Pos()->Y() > 0) Tree->_UpdateBelow(Pos()->y1); else Tree->Invalidate(); } } return NewObj; } void LTreeNode::Detach() { if (Parent) { LTreeItem *It = Item(); if (It) { LAssert(Parent->Items.HasItem(It)); Parent->Items.Delete(It); } Parent = 0; } if (Tree) { Tree->d->LayoutDirty = true; Tree->Invalidate(); } if (Item()) Item()->_SetTreePtr(0); } void LTreeNode::Remove() { int y = 0; if (Parent) { LTreeItem *i = Item(); if (i && i->IsRoot()) { LRect *p = Pos(); LTreeItem *Prev = GetPrev(); if (Prev) { y = Prev->d->Pos.y1; } else { y = p->y1; } } else { y = Parent->d->Pos.y1; } } LTree *t = Tree; if (Item()) Item()->_Remove(); if (t) { t->_UpdateBelow(y); } } bool LTreeNode::IsRoot() { return Parent == 0 || (LTreeNode*)Parent == (LTreeNode*)Tree; } size_t LTreeNode::Length() { return Items.Length(); } bool LTreeNode::HasItem(LTreeItem *obj, bool recurse) { if (!obj) return false; if (this == (LTreeNode*)obj) return true; for (auto i: Items) { if (i == obj) return true; if (recurse && i->HasItem(obj, recurse)) return true; } return false; } int LTreeNode::ForEach(std::function Fn) { int Count = 0; for (auto t : Items) { Fn(t); Count += t->ForEach(Fn); } return Count + 1; } ssize_t LTreeNode::IndexOf() { if (Parent) { return Parent->Items.IndexOf(Item()); } else if (Tree) { return Tree->Items.IndexOf(Item()); } return -1; } LTreeItem *LTreeNode::GetChild() { return Items.Length() ? Items[0] : NULL; } LTreeItem *LTreeNode::GetPrev() { List *l = (Parent) ? &Parent->Items : (Tree) ? &Tree->Items : 0; if (l) { ssize_t Index = l->IndexOf(Item()); if (Index >= 0) { return l->ItemAt(Index-1); } } return 0; } LTreeItem *LTreeNode::GetNext() { List *l = (Parent) ? &Parent->Items : (Tree) ? &Tree->Items : 0; if (l) { ssize_t Index = l->IndexOf(Item()); if (Index >= 0) { return l->ItemAt(Index+1); } } return 0; } ////////////////////////////////////////////////////////////////////////////// LTreeItem::LTreeItem() { d = new LTreeItemPrivate(this); } LTreeItem::~LTreeItem() { if (Tree) { if (Tree->d->DropTarget == this) Tree->d->DropTarget = 0; if (Tree->d->LastHit == this) Tree->d->LastHit = 0; if (Tree->IsCapturing()) Tree->Capture(false); } int y = 0; LTree *t = 0; if (Parent && (LTreeNode*)Parent != (LTreeNode*)Tree) { t = Tree; y = Parent->d->Pos.y1; } else if ((LTreeNode*)this != (LTreeNode*)Tree) { t = Tree; LTreeItem *p = GetPrev(); if (p) y = p->d->Pos.y1; else y = d->Pos.y1; } _Remove(); while (Items.Length()) { auto It = Items.begin(); delete *It; } DeleteObj(d); if (t) t->_UpdateBelow(y); } int LTreeItem::GetColumnSize(int Col) { int Px = d->GetColumnPx(Col); if (Expanded()) { ForAll(Items) { int ChildPx = c->GetColumnSize(Col); Px = MAX(ChildPx, Px); } } return Px; } LRect *LTreeItem::Pos() { return &d->Pos; } LPoint LTreeItem::_ScrollPos() { LPoint p; if (Tree) p = Tree->_ScrollPos(); return p; } LRect *LTreeItem::_GetRect(LTreeItemRect Which) { switch (Which) { case TreeItemPos: return &d->Pos; case TreeItemThumb: return &d->Thumb; case TreeItemText: return &d->Text; case TreeItemIcon: return &d->Icon; } return 0; } bool LTreeItem::IsDropTarget() { LTree *t = GetTree(); if (t && t->d && t->d->DropTarget == this) return true; return false; } LRect *LTreeItem::GetPos(int Col) { if (!d->Pos.Valid() && Tree) Tree->_Pour(); static LRect r; r = d->Pos; if (Col >= 0) { LItemColumn *Column = 0; int Cx = Tree->GetImageList() ? 16 : 0; for (int c=0; cColumnAt(c); if (Column) { Cx += Column->Width(); } } Column = Tree->ColumnAt(Col); if (Column) { r.x1 = Cx; r.x2 = Cx + Column->Width() - 1; } } return &r; } void LTreeItem::_RePour() { if (Tree) Tree->_Pour(); } void LTreeItem::ScrollTo() { if (!Tree) return; if (Tree->VScroll) { LRect c = Tree->GetClient(); LRect p = d->Pos; int y = d->Pos.Y() ? d->Pos.Y() : 16; p.Offset(0, (int) (-Tree->VScroll->Value() * y)); if (p.y1 < c.y1) { int Lines = (c.y1 - p.y1 + y - 1) / y; Tree->VScroll->Value(Tree->VScroll->Value() - Lines); } else if (p.y2 > c.y2) { int Lines = (p.y2 - c.y2 + y - 1) / y; Tree->VScroll->Value(Tree->VScroll->Value() + Lines); } } else { Tree->d->ScrollTo = this; if (Tree->IsAttached()) Tree->PostEvent(M_SCROLL_TO); } } void LTreeItem::_SetTreePtr(LTree *t) { if (Tree && !t) { // Clearing tree pointer, must remove all references to this item that // the tree might still have. if (d->Selected) { Tree->d->Selection.Delete(this); d->Selected = false; } if (Tree->d->LastHit == this) { Tree->d->LastHit = 0; } if (Tree->d->DropTarget == this) { Tree->d->DropTarget = 0; } } Tree = t; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) { i->_SetTreePtr(t); } } void LTreeItem::_Remove() { if ((LTreeNode*)this != (LTreeNode*)Tree) { if (Parent) { LAssert(Parent->Items.HasItem(this)); Parent->Items.Delete(this); } else if (Tree) { LAssert(Tree->Items.HasItem(this)); Tree->Items.Delete(this); } if (Tree) { LAssert(Tree->d != NULL); Tree->d->LayoutDirty = true; if (Tree->IsCapturing()) Tree->Capture(false); } } Parent = 0; _SetTreePtr(0); } void LTreeItem::_PourText(LPoint &Size) { LFont *f = Tree ? Tree->GetFont() : LSysFont; auto *Txt = GetText(); #if defined(_WIN64) && defined(_DEBUG) if ((void*)Txt == (void*)0xfeeefeeefeeefeee || (void*)Txt == (void*)0xcdcdcdcdcdcdcdcd) { LAssert(!"Yeah nah..."); } #endif LDisplayString ds(f, Txt); Size.x = ds.X() + 4; Size.y = 0; } void LTreeItem::_PaintText(LItem::ItemPaintCtx &Ctx) { const char *Text = GetText(); if (Text) { LDisplayString *Ds = d->GetDs(0, d->Text.X()); LFont *f = Tree ? Tree->GetFont() : LSysFont; int Tab = f->TabSize(); f->TabSize(0); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.TxtBack); if (Ds) { Ds->Draw(Ctx.pDC, d->Text.x1 + 2, d->Text.y1 + 1, &d->Text); if (Ctx.x2 > d->Text.x2) { LRect r = Ctx; r.x1 = d->Text.x2 + 1; Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&r); } } f->TabSize(Tab); } else { Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&Ctx); } } void LTreeItem::_Pour(LPoint *Limit, int ColumnPx, int Depth, bool Visible) { auto css = GetCss(false); auto display = css ? css->Display() != LCss::DispNone : true; d->Visible = display && Visible; d->Depth = Depth; if (d->Visible) { LPoint TextSize; _PourText(TextSize); LImageList *ImgLst = Tree->GetImageList(); // int IconX = (ImgLst && GetImage() >= 0) ? ImgLst->TileX() + Tree->d->IconTextGap : 0; int IconY = (ImgLst && GetImage() >= 0) ? ImgLst->TileY() : 0; int Height = MAX(TextSize.y, IconY); if (!Height) Height = 16; LDisplayString *Ds = d->GetDs(0, 0); d->Pos.ZOff(ColumnPx - 1, (Ds ? MAX(Height, Ds->Y()) : Height) - 1); d->Pos.Offset(0, Limit->y); if (!d->Pos.Valid()) { printf("Invalid pos: %s, ColumnPx=%i\n", d->Pos.GetStr(), ColumnPx); } Limit->x = MAX(Limit->x, d->Pos.x2 + 1); Limit->y = MAX(Limit->y, d->Pos.y2 + 1); } else { d->Pos.ZOff(-1, -1); } LTreeItem *n; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=n) { n = *++it; i->d->Last = n == 0; i->_Pour(Limit, ColumnPx, Depth+1, d->Open && d->Visible); } } void LTreeItem::_ClearDs(int Col) { d->ClearDs(Col); LTreeNode::_ClearDs(Col); } const char *LTreeItem::GetText(int i) { return Str[i]; } bool LTreeItem::SetText(const char *s, int i) { - LAutoPtr Lck; - - if (Tree) - Lck.Reset(new LMutex::Auto(Tree->d, -1, _FL)); + TREELOCK(Tree); Str[i] = s; - if (Tree) Update(); return true; } int LTreeItem::GetImage(int Flags) { return Sys_Image; } void LTreeItem::SetImage(int i) { Sys_Image = i; } void LTreeItem::Update() { if (Tree) { LRect p = d->Pos; p.x2 = 10000; d->ClearDs(); Tree->_Update(&p, TreeUpdateNow); } } bool LTreeItem::Select() { return d->Selected; } void LTreeItem::Select(bool b) { if (d->Selected != b) { d->Selected = b; if (b) { LTreeItem *p = this; while ((p = p->GetParent())) { p->Expanded(true); } } Update(); if (b && Tree) { Tree->_OnSelect(this); Tree->OnItemSelect(this); } } } bool LTreeItem::Expanded() { return d->Open; } void LTreeItem::Expanded(bool b) { if (d->Open != b) { d->Open = b; if (Items.Length() > 0) { if (Tree) { Tree->d->LayoutDirty = true; Tree->_UpdateBelow(d->Pos.y1); } OnExpand(b); } } } void LTreeItem::OnExpand(bool b) { _Visible(b); } LTreeItem *LTreeItem::_HitTest(int x, int y, bool Debug) { LTreeItem *Status = 0; if (d->Pos.Overlap(x, y) && x > (d->Depth*TREE_BLOCK)) { Status = this; } if (d->Open) { List::I it = Items.begin(); for (LTreeItem *i=*it; i && !Status; i=*++it) { Status = i->_HitTest(x, y, Debug); } } return Status; } void LTreeItem::_MouseClick(LMouse &m) { if (m.Down()) { if ((Items.Length() > 0 && d->Thumb.Overlap(m.x, m.y)) || m.Double()) { Expanded(!Expanded()); } LRect rText = d->Text; if (Tree && Tree->Columns.Length() > 0) rText.x2 = Tree->X(); if (rText.Overlap(m.x, m.y) || d->Icon.Overlap(m.x, m.y)) { Select(true); if (Tree) Tree->OnItemClick(this, m); } } } void LTreeItem::OnPaint(ItemPaintCtx &Ctx) { LAssert(Tree != NULL); if (!d->Visible) return; // background up to text LSurface *&pDC = Ctx.pDC; pDC->Colour(Ctx.Back); pDC->Rectangle(0, d->Pos.y1, (d->Depth*TREE_BLOCK)+TREE_BLOCK, d->Pos.y2); // draw trunk LRect Pos = d->Pos; Pos.x2 = Pos.x1 + Ctx.ColPx[0] - 1; int x = 0; LColour Ws(L_WORKSPACE); LColour Lines = Ws.Invert().Mix(Ws); pDC->Colour(Lines); if (Tree->d->JoiningLines) { for (int i=0; iDepth; i++) { if (Tree->d->LineFlags[0] & (1 << i)) pDC->Line(x + 8, Pos.y1, x + 8, Pos.y2); x += TREE_BLOCK; } } else { x += TREE_BLOCK * d->Depth; } // draw node int cy = Pos.y1 + (Pos.Y() >> 1); if (Items.Length() > 0) { d->Thumb.ZOff(8, 8); d->Thumb.Offset(x + 4, cy - 4); switch (Tree->d->Btns) { case LTree::TreePlus: { // plus/minus symbol pDC->Colour(L_LOW); pDC->Box(&d->Thumb); pDC->Colour(L_WHITE); pDC->Rectangle(d->Thumb.x1+1, d->Thumb.y1+1, d->Thumb.x2-1, d->Thumb.y2-1); pDC->Colour(L_SHADOW); pDC->Line( d->Thumb.x1+2, d->Thumb.y1+4, d->Thumb.x1+6, d->Thumb.y1+4); if (!d->Open) { // not open, so draw the cross bar making the '-' into a '+' pDC->Colour(L_SHADOW); pDC->Line( d->Thumb.x1+4, d->Thumb.y1+2, d->Thumb.x1+4, d->Thumb.y1+6); } break; } case LTree::TreeTriangle: { // Triangle style expander pDC->Colour(Lines); int Off = 2; if (d->Open) { for (int y=0; yThumb.Y(); y++) { int x1 = d->Thumb.x1 + y; int x2 = d->Thumb.x2 - y; if (x2 < x1) break; pDC->HLine(x1, x2, d->Thumb.y1 + y + Off); } } else { for (int x=0; xThumb.X(); x++) { int y1 = d->Thumb.y1 + x; int y2 = d->Thumb.y2 - x; if (y2 < y1) break; pDC->VLine(d->Thumb.x1 + x + Off, y1, y2); } } break; } } pDC->Colour(Lines); if (Tree->d->JoiningLines) { if (Parent || IndexOf() > 0) // draw line to item above pDC->Line(x + 8, Pos.y1, x + 8, d->Thumb.y1-1); // draw line to leaf beside pDC->Line(d->Thumb.x2+1, cy, x + (TREE_BLOCK-1), cy); if (!d->Last) // draw line to item below pDC->Line(x + 8, d->Thumb.y2+1, x + 8, Pos.y2); } } else if (Tree->d->JoiningLines) { // leaf node pDC->Colour(L_MED); if (d->Last) pDC->Rectangle(x + 8, Pos.y1, x + 8, cy); else pDC->Rectangle(x + 8, Pos.y1, x + 8, Pos.y2); pDC->Rectangle(x + 8, cy, x + (TREE_BLOCK-1), cy); } x += TREE_BLOCK; // draw icon int Image = GetImage(Select()); LImageList *Lst = Tree->GetImageList(); if (Image >= 0 && Lst) { d->Icon.ZOff(Lst->TileX() + Tree->d->IconTextGap - 1, Pos.Y() - 1); d->Icon.Offset(x, Pos.y1); pDC->Colour(Ctx.Back); if (Tree->d->IconCache) { // no flicker LRect From; From.ZOff(Lst->TileX()-1, Tree->d->IconCache->Y()-1); From.Offset(Lst->TileX()*Image, 0); pDC->Blt(d->Icon.x1, d->Icon.y1, Tree->d->IconCache, &From); pDC->Rectangle(d->Icon.x1 + Lst->TileX(), d->Icon.y1, d->Icon.x2, d->Icon.y2); } else { // flickers... int Px = d->Icon.y1 + ((Lst->TileY()-Pos.Y()) >> 1); pDC->Rectangle(&d->Icon); Tree->GetImageList()->Draw(pDC, d->Icon.x1, Px, Image, Ctx.Back); } x += d->Icon.X(); } LColour SelFore(Tree->Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Tree->Focus() ? L_FOCUS_SEL_BACK : L_NON_FOCUS_SEL_BACK); bool IsSelected = (Tree->d->DropTarget == this) || (Tree->d->DropTarget == NULL && Select()); LColour Fore = Ctx.Fore; LColour TxtBack = Ctx.TxtBack; auto Css = GetCss(); LCss::ColorDef f, b; if (Css) { f = Css->Color(); b = Css->BackgroundColor(); } // text: first column Ctx.Fore = f.Type == LCss::ColorRgb ? (LColour)f : (IsSelected ? SelFore : Fore); Ctx.TxtBack = b.Type == LCss::ColorRgb ? (LColour)b : (IsSelected ? SelBack : Ctx.Back); auto ColourDiff = abs(Ctx.Fore.GetGray() - Ctx.TxtBack.GetGray()); if (ColourDiff < 32) // Check if the colours are too similar and then disambiguate... { // LgiTrace("%s %s are too similar %i\n", Ctx.Fore.GetStr(), Ctx.TxtBack.GetStr(), (int)ColourDiff); Ctx.TxtBack = Ctx.TxtBack.Mix(L_WORKSPACE); } LPoint TextSize; _PourText(TextSize); d->Text.ZOff(TextSize.x-1, Pos.Y()-1); d->Text.Offset(x, Pos.y1); (LRect&)Ctx = d->Text; Ctx.x2 = Ctx.ColPx[0] - 1; _PaintText(Ctx); x = Pos.x2 + 1; // text: other columns Ctx.Fore = f.Type == LCss::ColorRgb ? (LColour)f : Fore; Ctx.TxtBack = b.Type == LCss::ColorRgb ? (LColour)b : Ctx.Back; for (int i=1; iColumns[i]); x = Ctx.x2 + 1; } Ctx.Fore = Fore; Ctx.TxtBack = TxtBack; // background after text pDC->Colour(Ctx.Back); pDC->Rectangle(x, Pos.y1, MAX(Tree->X(), Tree->d->Limit.x), Pos.y2); // children if (d->Open) { if (!d->Last) Tree->d->LineFlags[0] |= 1 << d->Depth; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->OnPaint(Ctx); Tree->d->LineFlags[0] &= ~(1 << d->Depth); } } void LTreeItem::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LDisplayString *ds = d->GetDs(i, Ctx.ColPx[i]); if (ds) { // Draw the text in the context area: LFont *f = ds->GetFont(); f->Colour(Ctx.Fore, Ctx.TxtBack); ds->Draw(Ctx.pDC, Ctx.x1 + 2, Ctx.y1 + 1, &Ctx); } else { // No string, fill the space with background Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&Ctx); } } ////////////////////////////////////////////////////////////////////////////// LTree::LTree(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_TreeView) { d = new LTreePrivate; SetId(id); LRect e(x, y, x+cx, y+cy); SetPos(e); if (name) Name(name); else Name("LGI.LTree"); Sunken(true); Tree = this; Lines = true; Buttons = true; LinesAtRoot = true; EditLabels = false; ColumnHeaders = false; rItems.ZOff(-1, -1); #if WINNATIVE SetStyle(GetStyle() | WS_CHILD | WS_VISIBLE | WS_TABSTOP); #endif SetTabStop(true); LResources::StyleElement(this); } LTree::~LTree() { Empty(); DeleteObj(d); } -bool LTree::Lock(const char *file, int line, int TimeOut) -{ - if (TimeOut > 0) - return d->LockWithTimeout(TimeOut, file, line); - - return d->Lock(file, line); -} - -void LTree::Unlock() -{ - return d->Unlock(); -} - // Internal tree methods List *LTree::GetSelLst() { return &d->Selection; } void LTree::_Update(LRect *r, bool Now) { - TREELOCK + TREELOCK(this) if (r) { LRect u = *r; LPoint s = _ScrollPos(); LRect c = GetClient(); u.Offset(c.x1-s.x, c.y1-s.y); Invalidate(&u, Now && !d->InPour); } else { Invalidate((LRect*)0, Now && !d->InPour); } } void LTree::_UpdateBelow(int y, bool Now) { - TREELOCK + TREELOCK(this) LPoint s = _ScrollPos(); LRect c = GetClient(); LRect u(c.x1, y - s.y + c.y1, X()-1, Y()-1); Invalidate(&u, Now); } void LTree::ClearDs(int Col) { - TREELOCK + TREELOCK(this) List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_ClearDs(Col); } LPoint LTree::_ScrollPos() { - TREELOCK + TREELOCK(this) LPoint Status; Status.x = (HScroll) ? (int)HScroll->Value() : 0; Status.y = (VScroll) ? (int)VScroll->Value() * TREE_BLOCK : 0; return Status; } void LTree::_UpdateScrollBars() { static bool Processing = false; if (!Processing) { Processing = true; { - TREELOCK + TREELOCK(this) LPoint Old = _ScrollPos(); LRect Client = GetClient(); bool x = d->Limit.x > Client.X(); bool y = d->Limit.y > Client.Y(); SetScrollBars(x, y); Client = GetClient(); // x scroll... in pixels if (HScroll) { HScroll->SetRange(d->Limit.x); HScroll->SetPage(Client.X()); int Max = d->Limit.x - Client.X(); if (HScroll->Value() > Max) { HScroll->Value(Max+1); } } // y scroll... in items if (VScroll) { int All = (d->Limit.y + TREE_BLOCK - 1) / TREE_BLOCK; int Visible = Client.Y() / TREE_BLOCK; VScroll->SetRange(All); VScroll->SetPage(Visible); /* Why is this commented out? -fret Dec2018 int Max = All - Visible + 1; if (VScroll->Value() > Max) VScroll->Value(Max); */ } LPoint New = _ScrollPos(); if (Old.x != New.x || Old.y != New.y) { Invalidate(); } } Processing = false; } } void LTree::_OnSelect(LTreeItem *Item) { - TREELOCK + TREELOCK(this) if ( !MultiSelect() || !d->CurrentClick || ( d->CurrentClick && !d->CurrentClick->Ctrl() ) ) { for (auto i: d->Selection) { if (i != Item) i->Select(false); } d->Selection.Empty(); } else { d->Selection.Delete(Item); } d->Selection.Insert(Item); } void LTree::_Pour() { - TREELOCK + TREELOCK(this) d->InPour = true; d->Limit.x = rItems.x1; d->Limit.y = rItems.y1; int ColumnPx = 0; if (Columns.Length()) { for (int i=0; iWidth(); } } else { ColumnPx = d->LastLayoutPx = GetClient().X(); if (ColumnPx < 16) ColumnPx = 16; } LTreeItem *n; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=n) { n = *++it; i->d->Last = n == 0; i->_Pour(&d->Limit, ColumnPx, 0, true); } _UpdateScrollBars(); d->LayoutDirty = false; d->InPour = false; } // External methods and events void LTree::OnItemSelect(LTreeItem *Item) { if (!Item) return; - TREELOCK + TREELOCK(this) Item->OnSelect(); SendNotify(LNotifyItemSelect); } void LTree::OnItemExpand(LTreeItem *Item, bool Expand) { - TREELOCK + TREELOCK(this) if (Item) Item->OnExpand(Expand); } LTreeItem *LTree::GetAdjacent(LTreeItem *i, bool Down) { - TREELOCK + TREELOCK(this) LTreeItem *Ret = NULL; if (i) { if (Down) { LTreeItem *n = i->GetChild(); if (!n || !n->d->Visible) { for (n = i; n; ) { LTreeItem *p = n->GetParent(); if (p) { ssize_t Index = n->IndexOf(); if (Index < (ssize_t)p->Items.Length()-1) { n = n->GetNext(); break; } else { n = p; } } else { n = n->GetNext(); break; } } } Ret = n; } else { LTreeItem *p = i->GetParent() ? i->GetParent() : 0; ssize_t Index = i->IndexOf(); if (p) { LTreeItem *n = p; if (Index > 0) { n = i->GetPrev(); while ( n->GetChild() && n->GetChild()->d->Visible) { n = n->Items.ItemAt(n->Items.Length()-1); } } Ret = n; } else if (Index > 0) { p = i->GetTree()->ItemAt(Index - 1); while (p->GetChild() && p->GetChild()->d->Visible) { if (p->Items.Length()) { p = p->Items.ItemAt(p->Items.Length()-1); } else break; } Ret = p; } } } return Ret; } bool LTree::OnKey(LKey &k) { if (!Lock(_FL)) return false; bool Status = false; LTreeItem *i = d->Selection[0]; if (!i) { i = Items[0]; if (i) i->Select(); } if (k.Down()) { switch (k.vkey) { case LK_PAGEUP: case LK_PAGEDOWN: { if (i && i->d->Pos.Y() > 0) { int Page = GetClient().Y() / i->d->Pos.Y(); for (int j=0; jSelect(true); i->ScrollTo(); } } Status = true; break; } case LK_HOME: { LTreeItem *i; if ((i = Items[0])) { i->Select(true); i->ScrollTo(); } Status = true; break; } case LK_END: { LTreeItem *n = i, *p = 0; while ((n = GetAdjacent(n, true))) { p = n; } if (p) { p->Select(true); p->ScrollTo(); } Status = true; break; } case LK_LEFT: { if (i) { if (i->Items.Length() && i->Expanded()) { i->Expanded(false); break; } else { LTreeItem *p = i->GetParent(); if (p) { p->Select(true); p->Expanded(false); _Pour(); break; } } } // fall thru } case LK_UP: { LTreeItem *n = GetAdjacent(i, false); if (n) { n->Select(true); n->ScrollTo(); } Status = true; break; } case LK_RIGHT: { if (i) { i->Expanded(true); if (d->LayoutDirty) { _Pour(); break; } } // fall thru } case LK_DOWN: { LTreeItem *n = GetAdjacent(i, true); if (n) { n->Select(true); n->ScrollTo(); } Status = true; break; } case LK_DELETE: { if (k.Down()) { Unlock(); // before potentially being deleted...? SendNotify(LNotification(k)); // This might delete the item... so just return here. return true; } break; } #ifdef VK_APPS case VK_APPS: { LTreeItem *s = Selection(); if (s) { LRect *r = &s->d->Text; if (r) { LMouse m; m.x = r->x1 + (r->X() >> 1); m.y = r->y1 + (r->Y() >> 1); m.Target = this; m.ViewCoords = true; m.Down(true); m.Right(true); s->OnMouseClick(m); } } break; } #endif default: { switch (k.c16) { case 'F': case 'f': { if (k.Ctrl()) SendNotify(LNotifyContainerFind); break; } } break; } } } if (i && i != (LTreeItem*)this) { i->OnKey(k); } Unlock(); return Status; } LTreeItem *LTree::ItemAtPoint(int x, int y, bool Debug) { - TREELOCK + TREELOCK(this) LPoint s = _ScrollPos(); List::I it = Items.begin(); LTreeItem *Hit = NULL; for (LTreeItem *i = *it; i; i=*++it) { Hit = i->_HitTest(s.x + x, s.y + y, Debug); if (Hit) break; } return Hit; } bool LTree::OnMouseWheel(double Lines) { - TREELOCK + TREELOCK(this) if (VScroll) VScroll->Value(VScroll->Value() + (int)Lines); return true; } void LTree::OnMouseClick(LMouse &m) { - TREELOCK + TREELOCK(this) d->CurrentClick = &m; if (m.Down()) { DragMode = DRAG_NONE; if (ColumnHeaders && ColumnHeader.Overlap(m.x, m.y)) { d->DragStart.x = m.x; d->DragStart.y = m.y; // Clicked on a column heading LItemColumn *Resize; LItemColumn *Over = NULL; HitColumn(m.x, m.y, Resize, Over); if (Resize) { if (m.Double()) { Resize->Width(Resize->GetContentSize() + DEFAULT_COLUMN_SPACING); Invalidate(); } else { DragMode = RESIZE_COLUMN; d->DragData = (int)Columns.IndexOf(Resize); Capture(true); } } /* else { DragMode = CLICK_COLUMN; d->DragData = Columns.IndexOf(Over); if (Over) { Over->Value(true); LRect r = Over->GetPos(); Invalidate(&r); Capture(true); } } */ } else if (rItems.Overlap(m.x, m.y)) { Focus(true); Capture(true); d->LastClick.x = m.x; d->LastClick.y = m.y; d->LastHit = ItemAtPoint(m.x, m.y, true); if (d->LastHit) { LPoint c = _ScrollPos(); m.x += c.x; m.y += c.y; d->LastHit->_MouseClick(m); } else { SendNotify(LNotification(m, LNotifyContainerClick)); } } } else if (IsCapturing()) { Capture(false); if (rItems.Overlap(m.x, m.y)) { d->LastClick.x = m.x; d->LastClick.y = m.y; d->LastHit = ItemAtPoint(m.x, m.y); if (d->LastHit) { LPoint c = _ScrollPos(); m.x += c.x; m.y += c.y; d->LastHit->_MouseClick(m); } } } d->CurrentClick = NULL; } void LTree::OnMouseMove(LMouse &m) { if (!IsCapturing()) return; - TREELOCK + TREELOCK(this) switch (DragMode) { /* case DRAG_COLUMN: { if (DragCol) { LPoint p; PointToScreen(p); LRect r = DragCol->GetPos(); r.Offset(-p.x, -p.y); // to view co-ord r.Offset(m.x - DragCol->GetOffset() - r.x1, 0); if (r.x1 < 0) r.Offset(-r.x1, 0); if (r.x2 > X()-1) r.Offset((X()-1)-r.x2, 0); r.Offset(p.x, p.y); // back to screen co-ord DragCol->SetPos(r, true); r = DragCol->GetPos(); } break; } */ case RESIZE_COLUMN: { LItemColumn *c = Columns[d->DragData]; if (c) { // int OldWidth = c->Width(); int NewWidth = m.x - c->GetPos().x1; c->Width(MAX(NewWidth, 4)); _ClearDs(d->DragData); Invalidate(); } break; } default: { if (rItems.Overlap(m.x, m.y)) { if (abs(d->LastClick.x - m.x) > DRAG_THRESHOLD || abs(d->LastClick.y - m.y) > DRAG_THRESHOLD) { OnItemBeginDrag(d->LastHit, m); Capture(false); } } break; } } } void LTree::OnPosChange() { - TREELOCK + TREELOCK(this) if (Columns.Length() == 0 && d->LastLayoutPx != GetClient().X()) d->LayoutDirty = true; LLayout::OnPosChange(); _UpdateScrollBars(); } void LTree::OnPaint(LSurface *pDC) { - TREELOCK + TREELOCK(this) LCssTools Tools(this); #if 0 // coverage testing... pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif rItems = GetClient(); LFont *f = GetFont(); if (ShowColumnHeader()) { ColumnHeader.ZOff(rItems.X()-1, f->GetHeight() + 4); PaintColumnHeadings(pDC); rItems.y1 = ColumnHeader.y2 + 1; } else { ColumnHeader.ZOff(-1, -1); } d->IconTextGap = GetFont()->GetHeight() / 6; auto cText = LColour(L_TEXT); auto cWs = LColour(L_WORKSPACE); LColour Fore = Tools.GetFore(&cText); LColour Background = Tools.GetBack(&cWs, 0); // icon cache if (GetImageList() && !d->IconCache) { int CacheHeight = MAX(LSysFont->GetHeight(), GetImageList()->Y()); d->IconCache = new LMemDC; if (d->IconCache && d->IconCache->Create(GetImageList()->X(), CacheHeight, GdcD->GetColourSpace())) { if (d->IconCache->GetColourSpace() == CsIndex8) { d->IconCache->Palette(new LPalette(GdcD->GetGlobalColour()->GetPalette())); } d->IconCache->Colour(Background); d->IconCache->Rectangle(); d->IconCache->Op(GDC_ALPHA); GetImageList()->Lock(); int DrawY = (CacheHeight - GetImageList()->TileY()) >> 1; LAssert(DrawY >= 0); for (int i=0; iGetItems(); i++) { GetImageList()->Draw(d->IconCache, i * GetImageList()->TileX(), DrawY, i, Background); } GetImageList()->Unlock(); d->IconCache->Unlock(); } } // scroll LPoint s = _ScrollPos(); int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox + s.x, Oy + s.y); // selection colour LArray ColPx; LItem::ItemPaintCtx Ctx; Ctx.pDC = pDC; if (Columns.Length() > 0) { Ctx.Columns = (int)Columns.Length(); for (int i=0; iWidth(); } else { Ctx.Columns = 1; ColPx[0] = rItems.X(); } Ctx.ColPx = &ColPx[0]; Ctx.Fore = Fore; Ctx.Back = Background; Ctx.TxtBack = Background; LColour SelFore(Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Focus() ? L_FOCUS_SEL_BACK : L_NON_FOCUS_SEL_BACK); // layout items if (d->LayoutDirty) { _Pour(); } // paint items ZeroObj(d->LineFlags); List::I it = Items.begin(); for (LTreeItem *i = *it; i; i=*++it) i->OnPaint(Ctx); pDC->SetOrigin(Ox, Oy); if (d->Limit.y-s.y < rItems.Y()) { // paint after items pDC->Colour(Background); pDC->Rectangle(rItems.x1, d->Limit.y - s.y, rItems.x2, rItems.y2); } } int LTree::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_HSCROLL: case IDC_VSCROLL: { - TREELOCK + TREELOCK(this) if (n.Type == LNotifyScrollBarCreate) { _UpdateScrollBars(); if (VScroll) { if (HasItem(d->ScrollTo)) d->ScrollTo->ScrollTo(); d->ScrollTo = NULL; } } Invalidate(); break; } } return LLayout::OnNotify(Ctrl, n); } LMessage::Result LTree::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_SCROLL_TO: { LTreeItem *Item = (LTreeItem*)Msg->A(); if (!HasItem(Item)) break; if (VScroll) Item->ScrollTo(); break; } } return LItemContainer::OnEvent(Msg); } LTreeItem *LTree::Insert(LTreeItem *Obj, ssize_t Pos) { - TREELOCK + TREELOCK(this) LTreeItem *NewObj = LTreeNode::Insert(Obj, Pos); if (NewObj) NewObj->_SetTreePtr(this); return NewObj; } bool LTree::HasItem(LTreeItem *Obj, bool Recurse) { - TREELOCK + TREELOCK(this) if (!Obj) return false; return LTreeNode::HasItem(Obj, Recurse); } bool LTree::Remove(LTreeItem *Obj) { - TREELOCK + TREELOCK(this) bool Status = false; if (Obj && Obj->Tree == this) { Obj->Remove(); Status = true; } return Status; } void LTree::RemoveAll() { - TREELOCK + TREELOCK(this) List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_Remove(); Invalidate(); } void LTree::Empty() { - TREELOCK + TREELOCK(this) LTreeItem *i; while ((i = Items[0])) Delete(i); } bool LTree::Delete(LTreeItem *Obj) { bool Status = false; - TREELOCK + TREELOCK(this) if (Obj) { LTreeItem *i; while ((i = Obj->Items[0])) { Delete(i); } Obj->Remove(); DeleteObj(Obj); Status = true; } return Status; } void LTree::OnPulse() { - TREELOCK + TREELOCK(this) if (d->DropTarget) { int64 p = LCurrentTime() - d->DropSelectTime; if (p >= 1000) { SetPulse(); if (!d->DropTarget->Expanded() && d->DropTarget->GetChild()) { d->DropTarget->Expanded(true); } } } if (InsideDragOp()) { LMouse m; if (GetMouse(m)) { if (!m.Left() && !m.Right() && !m.Middle()) { // Be robust against missing drag exit events (Mac specific?) InsideDragOp(false); } else { LRect c = GetClient(); if (VScroll) { if (m.y < DRAG_SCROLL_EDGE) { // Scroll up... VScroll->Value(VScroll->Value() - DRAG_SCROLL_Y); } else if (m.y > c.Y() - DRAG_SCROLL_EDGE) { // Scroll down... VScroll->Value(VScroll->Value() + DRAG_SCROLL_Y); } } if (HScroll) { if (m.x < DRAG_SCROLL_EDGE) { // Scroll left... HScroll->Value(HScroll->Value() - DRAG_SCROLL_X); } else if (m.x > c.X() - DRAG_SCROLL_EDGE) { // Scroll right... HScroll->Value(HScroll->Value() + DRAG_SCROLL_X); } } } } } } int LTree::GetContentSize(int ColumnIdx) { - TREELOCK + TREELOCK(this) int MaxPx = 0; List::I it = Items.begin(); for (LTreeItem *i = *it; i; i=*++it) { int ItemPx = i->GetColumnSize(ColumnIdx); MaxPx = MAX(ItemPx, MaxPx); } return MaxPx; } LCursor LTree::GetCursor(int x, int y) { - TREELOCK + TREELOCK(this) LItemColumn *Resize = NULL, *Over = NULL; HitColumn(x, y, Resize, Over); return (Resize) ? LCUR_SizeHor : LCUR_Normal; } void LTree::OnDragEnter() { - TREELOCK + TREELOCK(this) InsideDragOp(true); SetPulse(120); } void LTree::OnDragExit() { - TREELOCK + TREELOCK(this) InsideDragOp(false); SetPulse(); SelectDropTarget(0); } void LTree::SelectDropTarget(LTreeItem *Item) { - TREELOCK + TREELOCK(this) if (Item != d->DropTarget) { bool Update = (d->DropTarget != 0) ^ (Item != 0); LTreeItem *Old = d->DropTarget; d->DropTarget = Item; if (Old) { Old->Update(); } if (d->DropTarget) { d->DropTarget->Update(); d->DropSelectTime = LCurrentTime(); } if (Update) { OnFocus(true); } } } bool LTree::Select(LTreeItem *Obj) { - TREELOCK + TREELOCK(this) bool Status = false; if (Obj && IsAttached()) { Obj->Select(true); Status = true; } else if (d->Selection.Length()) { d->Selection.Empty(); OnItemSelect(0); Status = true; } return Status; } LTreeItem *LTree::Selection() { - TREELOCK + TREELOCK(this) return d->Selection[0]; } bool LTree::ForAllItems(std::function Callback) { - TREELOCK + TREELOCK(this) return ForEach(Callback) > 0; } void LTree::OnItemClick(LTreeItem *Item, LMouse &m) { if (!Item) return; - TREELOCK + TREELOCK(this) Item->OnMouseClick(m); if (!m.Ctrl() && !m.Shift()) SendNotify(LNotification(m)); } void LTree::OnItemBeginDrag(LTreeItem *Item, LMouse &m) { if (!Item) return; - TREELOCK + TREELOCK(this) Item->OnBeginDrag(m); } void LTree::OnFocus(bool b) { - TREELOCK + TREELOCK(this) // errors during deletion of the control can cause // this to be called after the destructor if (d) { List::I it = d->Selection.begin(); for (LTreeItem *i=*it; i; i=*++it) i->Update(); } } static void LTreeItemUpdateAll(LTreeNode *n) { for (LTreeItem *i=n->GetChild(); i; i=i->GetNext()) { i->Update(); LTreeItemUpdateAll(i); } } void LTree::UpdateAllItems() { - TREELOCK + TREELOCK(this) d->LayoutDirty = true; LTreeItemUpdateAll(this); } void LTree::SetVisualStyle(ThumbStyle Btns, bool JoiningLines) { - TREELOCK + TREELOCK(this) d->Btns = Btns; d->JoiningLines = JoiningLines; Invalidate(); } diff --git a/src/haiku/App.cpp b/src/haiku/App.cpp --- a/src/haiku/App.cpp +++ b/src/haiku/App.cpp @@ -1,1057 +1,1062 @@ #include #include #include #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/Array.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/FontCache.h" #include "AppPriv.h" #include "MimeType.h" #define DEBUG_MSG_TYPES 0 #define DEBUG_HND_WARNINGS 0 #define IDLE_ALWAYS 0 LString LgiArgsAppPath; //////////////////////////////////////////////////////////////// struct OsAppArgumentsPriv { ::LArray Ptr; ~OsAppArgumentsPriv() { Ptr.DeleteArrays(); } }; OsAppArguments::OsAppArguments(int args, const char **arg) { d = new OsAppArgumentsPriv; for (int i=0; iPtr.Add(NewStr(arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments::~OsAppArguments() { DeleteObj(d); } bool OsAppArguments::Get(const char *Name, const char **Val) { if (!Name) return false; for (int i=0; iPtr.DeleteArrays(); if (!CmdLine) return; for (char *s = CmdLine; *s; ) { while (*s && strchr(WhiteSpace, *s)) s++; if (*s == '\'' || *s == '\"') { char delim = *s++; char *e = strchr(s, delim); if (e) d->Ptr.Add(NewStr(s, e - s)); else break; s = e + 1; } else { char *e = s; while (*e && !strchr(WhiteSpace, *e)) e++; d->Ptr.Add(NewStr(s, e-s)); s = e; } } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; } OsAppArguments &OsAppArguments::operator =(OsAppArguments &a) { d->Ptr.DeleteArrays(); for (int i=0; iPtr.Add(NewStr(a.Arg[i])); } Args = d->Ptr.Length(); Arg = &d->Ptr[0]; return *this; } //////////////////////////////////////////////////////////////// #if HAS_SHARED_MIME #include "GFilterUtils.h" #include "mime-types.h" class LSharedMime : public LLibrary { public: LSharedMime() : #ifdef _DEBUG LLibrary("libsharedmime1d") #else LLibrary("libsharedmime1") #endif { } DynFunc0(int, mimetypes_init); DynFunc1(const char*, mimetypes_set_default_type, const char *, default_type); DynFunc2(const char*, mimetypes_get_file_type, const char*, pathname, mimetypes_flags, flags); DynFunc2(const char*, mimetypes_get_data_type, const void*, data, int, length); DynFunc3(bool, mimetypes_decode, const char *, type, char **, media_type, char **, sub_type); DynFunc2(char *, mimetypes_convert_filename, const char *, pathname, const char *, mime_type); DynFunc3(bool, mimetypes_add_mime_dir, const char *, path, bool, high_priority, bool, rescan); DynFunc2(const char *, mimetypes_get_type_info, const char *, mime_type, const char *, lang); }; #endif #if 1 ///////////////////////////////////////////////////////////////////////////// // // Attempts to cleanup and call drkonqi to process the crash // void LgiCrashHandler(int Sig) { // Don't get into an infinite loop signal(SIGSEGV, SIG_DFL); #ifndef _MSC_VER // Our pid int MyPid = getpid(); printf("LgiCrashHandler trigger MyPid=%i\n", MyPid); #endif exit(-1); } #endif ///////////////////////////////////////////////////////////////////////////// #ifndef XK_Num_Lock #define XK_Num_Lock 0xff7f #endif #ifndef XK_Shift_Lock #define XK_Shift_Lock 0xffe6 #endif #ifndef XK_Caps_Lock #define XK_Caps_Lock 0xffe5 #endif struct Msg { LViewI *v; int m; LMessage::Param a, b; void Set(LViewI *V, int M, LMessage::Param A, LMessage::Param B) { v = V; m = M; a = A; b = B; } }; // Out of thread messages... must lock before access. class LMessageQue : public LMutex { public: typedef ::LArray MsgArray; LMessageQue() : LMutex("LMessageQue") { } MsgArray *Lock(const char *file, int line) { if (!LMutex::Lock(file, line)) return NULL; return &q; } operator bool() { return q.Length() > 0; } private: MsgArray q; } MsgQue; ///////////////////////////////////////////////////////////////////////////// LApp *TheApp = NULL; LSkinEngine *LApp::SkinEngine; LMouseHook *LApp::MouseHook; LApp::LApp(OsAppArguments &AppArgs, const char *name, LAppArguments *Args) : OsApplication(AppArgs.Args, AppArgs.Arg) { TheApp = this; LgiArgsAppPath = AppArgs.Arg[0]; Name(name); d = new LAppPrivate(this); if (LIsRelativePath(LgiArgsAppPath)) { char Cwd[MAX_PATH_LEN]; getcwd(Cwd, sizeof(Cwd)); LMakePath(Cwd, sizeof(Cwd), Cwd, LgiArgsAppPath); LgiArgsAppPath = Cwd; } char AppPathLnk[MAX_PATH_LEN]; if (LResolveShortcut(LgiArgsAppPath, AppPathLnk, sizeof(AppPathLnk))) LgiArgsAppPath = AppPathLnk; int WCharSz = sizeof(wchar_t); #if defined(_MSC_VER) LAssert(WCharSz == 2); ::LFile::Path Dlls(LgiArgsAppPath); Dlls--; SetDllDirectoryA(Dlls); #else LAssert(WCharSz == 4); #endif #ifdef _MSC_VER SetEnvironmentVariable(_T("GTK_CSD"), _T("0")); #else setenv("GTK_CSD", "0", true); #endif // We want our printf's NOW! setvbuf(stdout,(char *)NULL,_IONBF,0); // print mesgs immediately. // Setup the file and graphics sub-systems d->FileSystem = new LFileSystem; d->GdcSystem = new GdcDevice; SetAppArgs(AppArgs); srand(LCurrentTime()); LColour::OnChange(); MouseHook = new LMouseHook; d->GetConfig(); // System font setup LFontType SysFontType; if (SysFontType.GetSystemFont("System")) { SystemNormal = SysFontType.Create(); if (SystemNormal) SystemNormal->Transparent(true); SystemBold = SysFontType.Create(); if (SystemBold) { SystemBold->Bold(true); SystemBold->Transparent(true); SystemBold->Create(); } } else { printf("%s:%i - Couldn't get system font setting.\n", __FILE__, __LINE__); } if (!SystemNormal) { LgiMsg(0, "Error: Couldn't create system font.", "Lgi Error: LApp::LApp", MB_OK); LExitApp(); } if (!GetOption("noskin")) { extern LSkinEngine *CreateSkinEngine(LApp *App); SkinEngine = CreateSkinEngine(this); } } LApp::~LApp() { DeleteObj(AppWnd); DeleteObj(SystemNormal); DeleteObj(SystemBold); DeleteObj(SkinEngine); DeleteObj(MouseHook); DeleteObj(d->FileSystem); DeleteObj(d->GdcSystem); DeleteObj(LFontSystem::Me); DeleteObj(d); TheApp = 0; } LApp *LApp::ObjInstance() { return TheApp; } bool LApp::IsOk() { bool Status = #ifndef __clang__ (this != 0) && #endif (d != 0); LAssert(Status); return Status; } LMouseHook *LApp::GetMouseHook() { return MouseHook; } int LApp::GetMetric(LSystemMetric Metric) { switch (Metric) { case LGI_MET_DECOR_X: return 8; case LGI_MET_DECOR_Y: return 8 + 19; case LGI_MET_DECOR_CAPTION: return 19; default: break; } return 0; } LViewI *LApp::GetFocus() { // GtkWidget *w = gtk_window_get_focus(GtkWindow *window); return NULL; } OsThread LApp::_GetGuiThread() { return d->GuiThread; } OsThreadId LApp::GetGuiThreadId() { return d->GuiThreadId; } OsProcessId LApp::GetProcessId() { #ifdef WIN32 return GetCurrentProcessId(); #else return getpid(); #endif } OsAppArguments *LApp::GetAppArgs() { return IsOk() ? &d->Args : 0; } void LApp::SetAppArgs(OsAppArguments &AppArgs) { if (IsOk()) { d->Args = AppArgs; } } bool LApp::InThread() { OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = GetGuiThreadId(); // printf("Me=%i Gui=%i\n", Me, Gui); return Gui == Me; } bool LApp::Run(OnIdleProc IdleCallback, void *IdleParam) { if (!InThread()) { printf("%s:%i - Error: Out of thread.\n", _FL); return false; } printf("Running main loop...\n"); d->Run(); printf("Main loop finished.\n"); return true; } bool LApp::Yield() { return false; } void LApp::Exit(int Code) { if (Code) { // hard exit ::exit(Code); } else { // soft exit printf("Quitting main loop...\n"); if (d->Lock()) { d->Quit(); d->Unlock(); } } } void LApp::OnUrl(const char *Url) { if (AppWnd) AppWnd->OnUrl(Url); } void LApp::OnReceiveFiles(::LArray &Files) { if (AppWnd) AppWnd->OnReceiveFiles(Files); else LAssert(!"You probably want to set 'AppWnd' before calling LApp::Run... maybe."); } const char *LApp::GetArgumentAt(int n) { return n >= 0 && n < d->Args.Args ? NewStr(d->Args.Arg[n]) : 0; } bool LApp::GetOption(const char *Option, char *Dest, int DestLen) { ::LString Buf; if (GetOption(Option, Buf)) { if (Dest) { if (DestLen > 0) { strcpy_s(Dest, DestLen, Buf); } else return false; } return true; } return false; } bool LApp::GetOption(const char *Option, ::LString &Buf) { if (IsOk() && Option) { int OptLen = strlen(Option); for (int i=1; iArgs.Args; i++) { auto *a = d->Args.Arg[i]; if (!a) continue; if (strchr("-/\\", a[0])) { if (strnicmp(a+1, Option, OptLen) == 0) { const char *Arg = NULL; if (strlen(a+1+OptLen) > 0) { Arg = a + 1 + OptLen; } else if (i < d->Args.Args - 1) { Arg = d->Args.Arg[i + 1]; } if (Arg) { if (strchr("\'\"", *Arg)) { char Delim = *Arg++; char *End = strchr(Arg, Delim); if (End) { auto Len = End-Arg; if (Len > 0) Buf.Set(Arg, Len); else return false; } else return false; } else { Buf = Arg; } } return true; } } } } return false; } void LApp::OnCommandLine() { ::LArray Files; for (int i=1; iArgs; i++) { auto a = GetAppArgs()->Arg[i]; if (LFileExists(a)) { Files.Add(NewStr(a)); } } // call app if (Files.Length() > 0) { OnReceiveFiles(Files); } // clear up Files.DeleteArrays(); } LString LApp::GetFileMimeType(const char *File) { BMimeType mt; auto r = BMimeType::GuessMimeType(File, &mt); if (r != B_OK) { LgiTrace("%s:%i - GuessMimeType(%s) failed: %i\n", _FL, File, r); return LString(); } return mt.Type(); } bool LApp::GetAppsForMimeType(const char *Mime, LArray &Apps) { // Find alternative version of the MIME type (e.g. x-type and type). char AltMime[256]; strcpy(AltMime, Mime); char *s = strchr(AltMime, '/'); if (s) { s++; int Len = strlen(s) + 1; if (strnicmp(s, "x-", 2) == 0) { memmove(s, s+2, Len - 2); } else { memmove(s+2, s, Len); s[0] = 'x'; s[1] = '-'; } } LGetAppsForMimeType(Mime, Apps); LGetAppsForMimeType(AltMime, Apps); return Apps.Length() > 0; } #if defined(LINUX) LLibrary *LApp::GetWindowManagerLib() { if (this != NULL && !d->WmLib) { char Lib[32]; WindowManager Wm = LGetWindowManager(); switch (Wm) { case WM_Kde: strcpy(Lib, "liblgikde3"); break; case WM_Gnome: strcpy(Lib, "liblgignome2"); break; default: strcpy(Lib, "liblgiother"); break; } #ifdef _DEBUG strcat(Lib, "d"); #endif d->WmLib = new LLibrary(Lib, true); if (d->WmLib) { if (d->WmLib->IsLoaded()) { Proc_LgiWmInit WmInit = (Proc_LgiWmInit) d->WmLib->GetAddress("LgiWmInit"); if (WmInit) { WmInitParams Params; // Params.Dsp = XObject::XDisplay(); Params.Args = d->Args.Args; Params.Arg = d->Args.Arg; WmInit(&Params); } // else printf("%s:%i - Failed to find method 'LgiWmInit' in WmLib.\n", __FILE__, __LINE__); } // else printf("%s:%i - couldn't load '%s.so'\n", __FILE__, __LINE__, Lib); } // else printf("%s:%i - alloc error\n", __FILE__, __LINE__); } return d->WmLib && d->WmLib->IsLoaded() ? d->WmLib : 0; } void LApp::DeleteMeLater(LViewI *v) { d->DeleteLater.Add(v); } void LApp::SetClipBoardContent(OsView Hnd, ::LVariant &v) { // Store the clipboard data we will serve d->ClipData = v; } bool LApp::GetClipBoardContent(OsView Hnd, ::LVariant &v, ::LArray &Types) { return false; } #endif LSymLookup *LApp::GetSymLookup() { return NULL; } bool LApp::IsElevated() { #ifdef WIN32 LAssert(!"What API works here?"); return false; #else return geteuid() == 0; #endif } int LApp::GetCpuCount() { return 1; } LFontCache *LApp::GetFontCache() { if (!d->FontCache) d->FontCache.Reset(new LFontCache(SystemNormal)); return d->FontCache; } #ifdef LINUX LApp::DesktopInfo::DesktopInfo(const char *file) { File = file; Dirty = false; if (File) Serialize(false); } bool LApp::DesktopInfo::Serialize(bool Write) { ::LFile f; if (Write) { ::LFile::Path p(File); p--; if (!p.Exists()) return false; } else if (!LFileExists(File)) return false; if (!f.Open(File, Write?O_WRITE:O_READ)) { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, File.Get()); return false; } if (Write) { f.SetSize(0); for (unsigned i=0; i= 0) { int e = l.Find("]", ++s); if (e >= 0) { Cur = &Data.New(); Cur->Name = l(s, e - s + 1); } } else if ((s = l.Find("=")) >= 0) { if (!Cur) Cur = &Data.New(); KeyPair &kp = Cur->Values.New(); kp.Key = l(0, s).Strip(); kp.Value = l(++s, -1).Strip(); // printf("Read '%s': '%s'='%s'\n", Cur->Name.Get(), kp.Key.Get(), kp.Value.Get()); } } } return true; } LApp::DesktopInfo::Section *LApp::DesktopInfo::GetSection(const char *Name, bool Create) { for (unsigned i=0; iGet(Field, false, Dirty); if (kp) { return kp->Value; } } } return ::LString(); } bool LApp::DesktopInfo::Set(const char *Field, const char *Value, const char *Sect) { if (!Field) return false; Section *s = GetSection(Sect ? Sect : DefaultSection, true); if (!s) return false; KeyPair *kp = s->Get(Field, true, Dirty); if (!kp) return false; if (kp->Value != Value) { kp->Value = Value; Dirty = true; } return true; } LApp::DesktopInfo *LApp::GetDesktopInfo() { auto sExe = LGetExeFile(); ::LFile::Path Exe(sExe); ::LFile::Path Desktop(LSP_HOME); ::LString Leaf; Leaf.Printf("%s.desktop", Exe.Last().Get()); Desktop += ".local/share/applications"; Desktop += Leaf; const char *Ex = Exe; const char *Fn = Desktop; if (d->DesktopInfo.Reset(new DesktopInfo(Desktop))) { // Do a sanity check... ::LString s = d->DesktopInfo->Get("Name"); if (!s && Name()) d->DesktopInfo->Set("Name", Name()); s = d->DesktopInfo->Get("Exec"); if (!s || s != (const char*)sExe) d->DesktopInfo->Set("Exec", sExe); s = d->DesktopInfo->Get("Type"); if (!s) d->DesktopInfo->Set("Type", "Application"); s = d->DesktopInfo->Get("Categories"); if (!s) d->DesktopInfo->Set("Categories", "Application;"); s = d->DesktopInfo->Get("Terminal"); if (!s) d->DesktopInfo->Set("Terminal", "false"); d->DesktopInfo->Update(); } return d->DesktopInfo; } bool LApp::SetApplicationIcon(const char *FileName) { DesktopInfo *di = GetDesktopInfo(); if (!di) return false; ::LString IcoPath = di->Get("Icon"); if (IcoPath == FileName) return true; di->Set("Icon", FileName); return di->Update(); } #endif //////////////////////////////////////////////////////////////// OsApplication *OsApplication::Inst = 0; class OsApplicationPriv { public: OsApplicationPriv() { } }; OsApplication::OsApplication(int Args, const char **Arg) { Inst = this; d = new OsApplicationPriv; } OsApplication::~OsApplication() { DeleteObj(d); Inst = 0; } //////////////////////////////////////////////////////////////// int LMessage::Msg() { return what; } LMessage::Param LMessage::A() { int64 a = 0; if (FindInt64(PropA, &a) != B_OK) { int32 c = CountNames(B_ANY_TYPE); printf("%s:%i - Failed to find PropA (%i names)\n", _FL, c); for (int32 i=0; iPostEvent(what, A(), B()); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////// LLocker::LLocker(BHandler *h, const char *File, int Line) { hnd = h; file = File; line = Line; } LLocker::~LLocker() { Unlock(); } bool LLocker::Lock() { if (locked) { printf("%s:%i - Locker already locked.\n", file, line); LAssert(!"Locker already locked."); return false; } if (!hnd) { // printf("%s:%i - Locker hnd is NULL.\n", file, line); return false; } while (!locked) { status_t result = hnd->LockLooperWithTimeout(1000 * 1000); if (result == B_OK) { locked = true; break; } else if (result == B_TIMED_OUT) { // Warn about failure to lock... - thread_id Thread = hnd->Looper()->LockingThread(); - printf("%s:%i - Warning: can't lock. Myself=%i\n", _FL, LGetCurrentThread(), Thread); + auto cur = GetCurrentThreadId(); + auto locking = hnd->Looper()->LockingThread(); + + printf("%s:%i - Warning: can't lock. cur=%i locking=%i\n", + _FL, + cur, + locking); } else if (result == B_BAD_VALUE) { break; } else { // Warn about error printf("%s:%i - Error from LockLooperWithTimeout = 0x%x\n", _FL, result); } } return locked; } status_t LLocker::LockWithTimeout(int64 time) { LAssert(!locked); LAssert(hnd != NULL); status_t result = hnd->LockLooperWithTimeout(time); if (result == B_OK) locked = true; return result; } void LLocker::Unlock() { if (locked) { hnd->UnlockLooper(); locked = false; } } diff --git a/src/haiku/File.cpp b/src/haiku/File.cpp --- a/src/haiku/File.cpp +++ b/src/haiku/File.cpp @@ -1,1513 +1,1452 @@ /*hdr ** FILE: File.cpp ** AUTHOR: Matthew Allen ** DATE: 8/10/2000 -** DESCRIPTION: The new file subsystem +** DESCRIPTION: The file subsystem ** ** Copyright (C) 2000, Matthew Allen ** fret@memecode.com */ /****************************** Includes **********************************/ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include +#include #include "Path.h" +#include "VolumeRoster.h" +#include "Directory.h" #include "lgi/common/LgiDefs.h" #include "lgi/common/File.h" #include "lgi/common/Containers.h" -#include "lgi/common/Token.h" #include "lgi/common/Gdc2.h" #include "lgi/common/LgiCommon.h" -#include "lgi/common/LgiString.h" #include "lgi/common/DateTime.h" -/****************************** Globals ***********************************/ -LString LFile::Path::Sep(DIR_STR); +/****************************** Helper Functions **************************/ +LString BGetFullPath(BEntry &entry) +{ + BPath path; + auto r = entry.GetPath(&path); + if (r != B_OK) + return LString(); + return path.Path(); +} -/****************************** Helper Functions **************************/ +LString BGetFullPath(BDirectory &dir) +{ + BEntry entry; + auto r = dir.GetEntry(&entry); + if (r != B_OK) + return LString(); + return BGetFullPath(entry); +} + +LString BGetFullPath(BVolume &volume) +{ + BDirectory directory; + auto r = volume.GetRootDirectory(&directory); + if (r != B_OK) + return LString(); + return BGetFullPath(directory); +} + char *LReadTextFile(const char *File) { char *s = 0; LFile f; if (File && f.Open(File, O_READ)) { int Len = f.GetSize(); s = new char[Len+1]; if (s) { int Read = f.Read(s, Len); s[Read] = 0; } } return s; } int64 LFileSize(const char *FileName) { - struct stat s; - if (FileName && - stat(FileName, &s) == 0) - { - return s.st_size; - } - - return 0; + BEntry e(FileName); + if (e.InitCheck() != B_OK) + return 0; + + off_t size = 0; + if (e.GetSize(&size) != B_OK) + return 0; + + return size; } bool LDirExists(const char *FileName, char *CorrectCase) { - bool Status = false; - - if (FileName) + if (!FileName) + return false; + + struct stat s; + auto r = lstat(FileName, &s); + if (r == 0) { - struct stat s; - - // Check for exact match... - int r = lstat(FileName, &s); + return S_ISDIR(s.st_mode) || + S_ISLNK(s.st_mode); + } + else + { + r = stat(FileName, &s); if (r == 0) { - Status = S_ISDIR(s.st_mode) || - S_ISLNK(s.st_mode); - // printf("DirStatus(%s) lstat = %i, %i\n", FileName, Status, s.st_mode); - } - else - { - r = stat(FileName, &s); - if (r == 0) - { - Status = S_ISDIR(s.st_mode) || - S_ISLNK(s.st_mode); - // printf("DirStatus(%s) stat ok = %i, %i\n", FileName, Status, s.st_mode); - } - else - { - // printf("DirStatus(%s) lstat and stat failed, r=%i, errno=%i\n", FileName, r, errno); - } + return S_ISDIR(s.st_mode) || + S_ISLNK(s.st_mode); } } - return Status; + return false; } bool LFileExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... if (stat(FileName, &s) == 0) { Status = !S_ISDIR(s.st_mode); } else if (CorrectCase) { // Look for altenate case by enumerating the directory char d[256]; strcpy(d, FileName); char *e = strrchr(d, DIR_CHAR); if (e) { *e++ = 0; DIR *Dir = opendir(d); if (Dir) { dirent *De; while ((De = readdir(Dir))) { if (stricmp(De->d_name, e) == 0) { try { // Tell the calling program the actual case of the file... e = (char*) strrchr(FileName, DIR_CHAR); // If this crashes because the argument is read only then we get caught by the try catch strcpy(e+1, De->d_name); // It worked! Status = true; } catch (...) { // It didn't work :( #ifdef _DEBUG printf("%s,%i - LFileExists(%s) found an alternate case version but couldn't return it to the caller.\n", __FILE__, __LINE__, FileName); #endif } break; } } closedir(Dir); } } } } return Status; } bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len) { if (!LinkFile || !Path || Len < 1) return false; BEntry e(LinkFile); auto r = e.InitCheck(); if (r != B_OK) { printf("%s:%i - LResolveShortcut: %i\n", _FL, r); return false; } if (!e.IsSymLink()) { return false; } r = e.SetTo(LinkFile, true); if (r != B_OK) { printf("%s:%i - LResolveShortcut: %i\n", _FL, r); return false; } - BPath p; - r = e.GetPath(&p); - if (r != B_OK) - { - printf("%s:%i - LResolveShortcut: %i\n", _FL, r); - return false; - } - - strcpy_s(Path, Len, p.Path()); + auto p = BGetFullPath(e); + strcpy_s(Path, Len, p); return true; } void WriteStr(LFile &f, const char *s) { uint32_t Len = (s) ? strlen(s) : 0; f << Len; if (Len > 0) { f.Write(s, Len); } } char *ReadStr(LFile &f DeclDebugArgs) { char *s = 0; // read the strings length... uint32_t Len; f >> Len; if (Len > 0) { // 16mb sanity check.... anything over this // is _probably_ an error if (Len >= (16 << 20)) { // LAssert(0); return 0; } // allocate the memory buffer #if defined(_DEBUG) && defined MEMORY_DEBUG s = new(_file, _line) char[Len+1]; #else s = new char[Len+1]; #endif if (s) { // read the bytes from disk f.Read(s, Len); s[Len] = 0; } else { // memory allocation error, skip the data // on disk so the caller is where they think // they are in the file. f.Seek(Len, SEEK_CUR); } } return s; } ssize_t SizeofStr(const char *s) { return sizeof(ulong) + ((s) ? strlen(s) : 0); } bool LGetDriveInfo ( char *Path, uint64 *Free, uint64 *Size, uint64 *Available ) { bool Status = false; if (Path) { struct stat s; if (lstat(Path, &s) == 0) { // printf("LGetDriveInfo dev=%i\n", s.st_dev); } } return Status; } ///////////////////////////////////////////////////////////////////////// -#include -#include - struct LVolumePriv { - int64 _Size, _Free; - int _Type, _Flags; - LSystemPath SysPath; - LString _Name, _Path; - List _Sub; - List::I _It; + LVolume *Owner = NULL; + int64 Size = 0, Free = 0; + int Type = VT_NONE, Flags = 0; + LSystemPath SysPath = LSP_ROOT; + LString Name, Path; + LVolume *NextVol = NULL, *ChildVol = NULL; - void Init() + LVolumePriv(LVolume *owner, const char *path) : Owner(owner) { - SysPath = LSP_ROOT; - _Type = VT_NONE; - _Flags = 0; - _Size = 0; - _Free = 0; + Path = path; + Name = LGetLeaf(path); + Type = VT_FOLDER; } - LVolumePriv(const char *path) : _It(_Sub.end()) + LVolumePriv(LVolume *owner, LSystemPath sys, const char *name) : Owner(owner) { - Init(); - - _Path = path; - _Name = LGetLeaf(path); - _Type = VT_FOLDER; - } - - LVolumePriv(LSystemPath sys, const char *name) : _It(_Sub.end()) - { - Init(); SysPath = sys; + Name = name; if (SysPath == LSP_ROOT) - _Path = "/"; + Path = "/"; else - _Path = LGetSystemPath(SysPath); - if (_Path) - { - _Name = name; - _Type = sys == LSP_DESKTOP ? VT_DESKTOP : VT_FOLDER; - } + Path = LGetSystemPath(SysPath); + + if (Path) + Type = sys == LSP_DESKTOP ? VT_DESKTOP : VT_FOLDER; } ~LVolumePriv() { - _Sub.DeleteObjects(); + DeleteObj(NextVol); + DeleteObj(ChildVol); } + void Insert(LVolume *vol, LVolume *newVol) + { + if (!vol || !newVol) + return; + + if (vol->d->ChildVol) + { + for (auto v = vol->d->ChildVol; v; v = v->d->NextVol) + { + if (!v->d->NextVol) + { + LAssert(newVol != v->d->Owner); + v->d->NextVol = newVol; + // printf("Insert %p:%s into %p:%s\n", newVol, newVol->Name(), vol, vol->Name()); + break; + } + } + } + else + { + LAssert(newVol != vol->d->Owner); + vol->d->ChildVol = newVol; + // printf("Insert %p:%s into %p:%s\n", newVol, newVol->Name(), vol, vol->Name()); + } + } + LVolume *First() { - if (SysPath == LSP_DESKTOP && !_Sub.Length()) + if (SysPath == LSP_DESKTOP && !ChildVol) { // Get various shortcuts to points of interest - LVolume *v = new LVolume(LSP_ROOT, "Root"); - if (v) - _Sub.Insert(v); - + Insert(Owner, new LVolume(LSP_ROOT, "Root")); + struct passwd *pw = getpwuid(getuid()); if (pw) - { - v = new LVolume(LSP_HOME, "Home"); - if (v) - _Sub.Insert(v); - } - - // Get mount list - // this is just a hack at this stage to establish some base - // functionality. I would appreciate someone telling me how - // to do this properly. Till then... - LFile f; - auto fstab = "/etc/fstab"; - if (LFileExists(fstab) && f.Open(fstab, O_READ)) - { - auto Buf= f.Read(); - f.Close(); + Insert(Owner, new LVolume(LSP_HOME, "Home")); - auto Lines = Buf.SplitDelimit("\r\n"); - for (auto ln : Lines) - { - auto M = ln.Strip().SplitDelimit(" \t"); - if (M[0](0) != '#' && M.Length() > 2) - { - auto &Device = M[0]; - auto &Mount = M[1]; - auto &FileSys = M[2]; - - if - ( - (Device.Find("/dev/") == 0 || Mount.Find("/mnt/") == 0) - && - Device.Lower().Find("/by-uuid/") < 0 - && - Mount.Length() > 1 - && - !FileSys.Equals("swap") - ) - { - v = new LVolume(0); - if (v) - { - char *MountName = strrchr(Mount, '/'); - v->d->_Name = (MountName ? MountName + 1 : Mount.Get()); - v->d->_Path = Mount; - v->d->_Type = VT_HARDDISK; - - char *Device = M[0]; - // char *FileSys = M[2]; - if (stristr(Device, "fd")) - { - v->d->_Type = VT_FLOPPY; - } - else if (stristr(Device, "cdrom")) - { - v->d->_Type = VT_CDROM; - } - - _Sub.Insert(v); - } - } - } - } - } - - LSystemPath p[] = {LSP_USER_DOCUMENTS, + LSystemPath p[] = { LSP_USER_DOCUMENTS, LSP_USER_MUSIC, LSP_USER_VIDEO, LSP_USER_DOWNLOADS, - LSP_USER_PICTURES}; + LSP_USER_PICTURES }; for (int i=0; id->_Path = Path; - v->d->_Name = *Parts.rbegin(); - v->d->_Type = VT_FOLDER; - _Sub.Insert(v); + auto v = new LVolume(0); + if (Path && v) + { + auto Parts = Path.Split("/"); + v->d->Path = Path; + v->d->Name = *Parts.rbegin(); + v->d->Type = VT_FOLDER; + Insert(Owner, v); + } } } } - _It = _Sub.begin(); - return *_It; + return ChildVol; } LVolume *Next() { - return *(++_It); + if (SysPath == LSP_DESKTOP && !NextVol) + { + NextVol = new LVolume(LSP_MOUNT_POINT, "Mounts"); + + LHashTbl, bool> Map; + + BVolumeRoster roster; + BVolume volume; + for (auto r = roster.GetBootVolume(&volume); + r == B_OK; + r = roster.GetNextVolume(&volume)) + { + auto Path = BGetFullPath(volume); + if (!Path) + continue; + + if (Stricmp(Path.Get(), "/") == 0 || + Stricmp(Path.Get(), "/dev") == 0 || + Stricmp(Path.Get(), "/boot/system/var/shared_memory") == 0) + continue; + + if (volume.Capacity() == (4 << 10)) + continue; + + if (!volume.IsPersistent()) + continue; + + char Name[B_FILE_NAME_LENGTH]; + if (volume.GetName(Name) != B_OK) + continue; + + auto Done = Map.Find(Name); + if (Done) + continue; + Map.Add(Name, true); + + auto v = new LVolume(0); + if (v) + { + v->d->Name = Name; + v->d->Path = Path; + v->d->Type = VT_HARDDISK; + v->d->Size = volume.Capacity(); + v->d->Free = volume.FreeBytes(); + + // printf("%s, %s\n", Name, v->d->Path.Get()); + + Insert(NextVol, v); + } + } + } + + return NextVol; } }; -LVolume::LVolume(const char *Path) +LVolume::LVolume(const char *Path = NULL) { - d = new LVolumePriv(Path); + d = new LVolumePriv(this, Path); } LVolume::LVolume(LSystemPath SysPath, const char *Name) { - d = new LVolumePriv(SysPath, Name); + d = new LVolumePriv(this, SysPath, Name); } LVolume::~LVolume() { DeleteObj(d); } const char *LVolume::Name() const { - return d->_Name; + return d->Name; } const char *LVolume::Path() const { - return d->_Path; + return d->Path; } int LVolume::Type() const { - return d->_Type; + return d->Type; } int LVolume::Flags() const { - return d->_Flags; + return d->Flags; } uint64 LVolume::Size() const { - return d->_Size; + return d->Size; } uint64 LVolume::Free() const { - return d->_Free; + return d->Free; } LSurface *LVolume::Icon() const { return NULL; } bool LVolume::IsMounted() const { return true; } bool LVolume::SetMounted(bool Mount) { return Mount; } LVolume *LVolume::First() { return d->First(); } LVolume *LVolume::Next() { return d->Next(); } void LVolume::Insert(LAutoPtr v) { - d->_Sub.Insert(v.Release()); + d->Insert(this, v.Release()); } LDirectory *LVolume::GetContents() { LDirectory *Dir = 0; - if (d->_Path) + if (d->Path) { Dir = new LDirectory; - if (Dir && !Dir->First(d->_Path)) + if (Dir && !Dir->First(d->Path)) DeleteObj(Dir); } return Dir; } /////////////////////////////////////////////////////////////////////////////// LFileSystem *LFileSystem::Instance = 0; LFileSystem::LFileSystem() { Instance = this; Root = 0; } LFileSystem::~LFileSystem() { DeleteObj(Root); } void LFileSystem::OnDeviceChange(char *Reserved) { } LVolume *LFileSystem::GetRootVolume() { if (!Root) Root = new LVolume(LSP_DESKTOP, "Desktop"); return Root; } bool LFileSystem::Copy(const char *From, const char *To, LError *Status, CopyFileCallback Callback, void *Token) { LArray Buf; if (Status) *Status = 0; if (Buf.Length(2 << 20)) { LFile In, Out; if (!In.Open(From, O_READ)) { if (Status) *Status = In.GetError(); return false; } if (!Out.Open(To, O_WRITE)) { if (Status) *Status = Out.GetError(); return false; } int64 Size = In.GetSize(), Done = 0; for (int64 i=0; i 0) { int w = Out.Write(&Buf[0], r); if (w <= 0) break; r -= w; Done += w; if (Callback) Callback(Token, Done, Size); } if (r > 0) break; } return Done == Size; } return false; } bool LFileSystem::Delete(LArray &Files, LArray *Status, bool ToTrash) { bool Error = false; if (ToTrash) { char p[MAX_PATH_LEN]; if (LGetSystemPath(LSP_TRASH, p, sizeof(p))) { for (int i=0; i f; f.Add(FileName); return Delete(f, 0, ToTrash); } return false; } bool LFileSystem::CreateFolder(const char *PathName, bool CreateParentTree, LError *ErrorCode) { int r = mkdir(PathName, S_IRWXU | S_IXGRP | S_IXOTH); if (r) { if (ErrorCode) *ErrorCode = errno; if (CreateParentTree) { LFile::Path p(PathName); LString dir = DIR_STR; for (size_t i=1; i<=p.Length(); i++) { LFile::Path Par(dir + dir.Join(p.Slice(0, i))); if (!Par.Exists()) { const char *ParDir = Par; r = mkdir(ParDir, S_IRWXU | S_IXGRP | S_IXOTH); if (r) { if (ErrorCode) *ErrorCode = errno; break; } LgiTrace("%s:%i - Created '%s'\n", _FL, Par.GetFull().Get()); } } if (p.Exists()) return true; } LgiTrace("%s:%i - mkdir('%s') failed with %i, errno=%i\n", _FL, PathName, r, errno); } return r == 0; } bool LFileSystem::RemoveFolder(const char *PathName, bool Recurse) { if (Recurse) { - LDirectory *Dir = new LDirectory; - if (Dir && Dir->First(PathName)) + LDirectory Dir; + if (Dir.First(PathName)) { do { - char Str[256]; - Dir->Path(Str, sizeof(Str)); + char Str[MAX_PATH_LEN]; + Dir.Path(Str, sizeof(Str)); - if (Dir->IsDir()) - { + if (Dir.IsDir()) RemoveFolder(Str, Recurse); - } else - { Delete(Str, false); - } } - while (Dir->Next()); + while (Dir.Next()); } - DeleteObj(Dir); } return rmdir(PathName) == 0; } bool LFileSystem::Move(const char *OldName, const char *NewName, LError *Err) { if (rename(OldName, NewName)) { printf("%s:%i - rename failed, error: %s(%i)\n", _FL, LErrorCodeToString(errno), errno); return false; } return true; } - -/* -bool Match(char *Name, char *Mask) -{ - strupr(Name); - strupr(Mask); - - while (*Name && *Mask) - { - if (*Mask == '*') - { - if (*Name == *(Mask+1)) - { - Mask++; - } - else - { - Name++; - } - } - else if (*Mask == '?' || *Mask == *Name) - { - Mask++; - Name++; - } - else - { - return false; - } - } - - while (*Mask && ((*Mask == '*') || (*Mask == '.'))) Mask++; - - return (*Name == 0 && *Mask == 0); -} -*/ - -short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -int LeapYear(int year) -{ - if (year & 3) - { - return 0; - } - if ((year % 100 == 0) && !(year % 400 == 0)) - { - return 0; - } - - return 1; -} - -///////////////////////////////////////////////////////////////////////////////// -bool LDirectory::ConvertToTime(char *Str, int SLen, uint64 Time) const -{ - time_t k = Time; - struct tm *t = localtime(&k); - if (t) - { - strftime(Str, SLen, "%I:%M:%S", t); - return true; - } - return false; -} - -bool LDirectory::ConvertToDate(char *Str, int SLen, uint64 Time) const -{ - time_t k = Time; - struct tm *t = localtime(&k); - if (t) - { - strftime(Str, SLen, "%d/%m/%y", t); - return true; - } - return false; -} - -///////////////////////////////////////////////////////////////////////////////// -//////////////////////////// Directory ////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// struct LDirectoryPriv { char BasePath[MAX_PATH_LEN]; char *BaseEnd = NULL; DIR *Dir = NULL; struct dirent *De = NULL; struct stat Stat; LString Pattern; LDirectoryPriv() { BasePath[0] = 0; } bool Ignore() { return De && ( strcmp(De->d_name, ".") == 0 || strcmp(De->d_name, "..") == 0 || ( Pattern && !MatchStr(Pattern, De->d_name) ) ); } }; LDirectory::LDirectory() { d = new LDirectoryPriv; } LDirectory::~LDirectory() { Close(); DeleteObj(d); } LDirectory *LDirectory::Clone() { return new LDirectory; } int LDirectory::First(const char *Name, const char *Pattern) { Close(); if (!Name) return 0; strcpy_s(d->BasePath, sizeof(d->BasePath), Name); + d->BaseEnd = NULL; + if (!Pattern || stricmp(Pattern, LGI_ALL_FILES) == 0) { struct stat S; if (lstat(Name, &S) == 0) { if (S_ISREG(S.st_mode)) { char *Dir = strrchr(d->BasePath, DIR_CHAR); if (Dir) { *Dir++ = 0; d->Pattern = Dir; } } } } else { d->Pattern = Pattern; } d->Dir = opendir(d->BasePath); if (d->Dir) { d->De = readdir(d->Dir); if (d->De) { char s[MaxPathLen]; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (d->Ignore() && !Next()) return false; } } return d->Dir != NULL && d->De != NULL; } int LDirectory::Next() { int Status = false; while (d->Dir && d->De) { if ((d->De = readdir(d->Dir))) { char s[MaxPathLen]; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (!d->Ignore()) { Status = true; break; } } } return Status; } int LDirectory::Close() { if (d->Dir) { closedir(d->Dir); d->Dir = NULL; } d->De = NULL; d->BaseEnd = NULL; d->BasePath[0] = 0; return true; } const char *LDirectory::FullPath() { auto nm = GetName(); if (!nm) return NULL; if (!d->BaseEnd) { d->BaseEnd = d->BasePath + strlen(d->BasePath); if (d->BaseEnd > d->BasePath && d->BaseEnd[-1] != DIR_CHAR) { *d->BaseEnd++ = DIR_CHAR; } } auto used = d->BaseEnd - d->BasePath; auto remaining = sizeof(d->BasePath) - used; strcpy_s(d->BaseEnd, remaining, nm); return d->BasePath; } LString LDirectory::FileName() const { return GetName(); } bool LDirectory::Path(char *s, int BufLen) const { if (!s) { return false; } return LMakePath(s, BufLen, d->BasePath, GetName()); } int LDirectory::GetType() const { return IsDir() ? VT_FOLDER : VT_FILE; } int LDirectory::GetUser(bool Group) const { if (Group) { return d->Stat.st_gid; } else { return d->Stat.st_uid; } } bool LDirectory::IsReadOnly() const { if (getuid() == d->Stat.st_uid) { // Check user perms return !TestFlag(GetAttributes(), S_IWUSR); } else if (getgid() == d->Stat.st_gid) { // Check group perms return !TestFlag(GetAttributes(), S_IWGRP); } // Check global perms return !TestFlag(GetAttributes(), S_IWOTH); } bool LDirectory::IsHidden() const { return GetName() && GetName()[0] == '.'; } bool LDirectory::IsDir() const { int a = GetAttributes(); return !S_ISLNK(a) && S_ISDIR(a); } bool LDirectory::IsSymLink() const { int a = GetAttributes(); return S_ISLNK(a); } long LDirectory::GetAttributes() const { return d->Stat.st_mode; } char *LDirectory::GetName() const { return (d->De) ? d->De->d_name : NULL; } uint64 LDirectory::GetCreationTime() const { return (uint64) d->Stat.st_ctime * LDateTime::Second64Bit; } uint64 LDirectory::GetLastAccessTime() const { return (uint64) d->Stat.st_atime * LDateTime::Second64Bit; } uint64 LDirectory::GetLastWriteTime() const { return (uint64) d->Stat.st_mtime * LDateTime::Second64Bit; } uint64 LDirectory::GetSize() const { return d->Stat.st_size; } int64 LDirectory::GetSizeOnDisk() { return d->Stat.st_size; } +bool LDirectory::ConvertToTime(char *Str, int SLen, uint64 Time) const +{ + time_t k = Time; + struct tm *t = localtime(&k); + if (t) + { + strftime(Str, SLen, "%I:%M:%S", t); + return true; + } + return false; +} + +bool LDirectory::ConvertToDate(char *Str, int SLen, uint64 Time) const +{ + time_t k = Time; + struct tm *t = localtime(&k); + if (t) + { + strftime(Str, SLen, "%d/%m/%y", t); + return true; + } + return false; +} + ///////////////////////////////////////////////////////////////////////////////// class LFilePrivate { public: int hFile; char *Name; bool Swap; int Status; int Attributes; int ErrorCode; LFilePrivate() { hFile = INVALID_HANDLE; Name = 0; Swap = false; Status = true; Attributes = 0; ErrorCode = 0; } ~LFilePrivate() { DeleteArray(Name); } }; LFile::LFile(const char *Path, int Mode) { d = new LFilePrivate; if (Path) Open(Path, Mode); } LFile::~LFile() { if (d && ValidHandle(d->hFile)) { Close(); } DeleteObj(d); } OsFile LFile::Handle() { return d->hFile; } int LFile::GetError() { return d->ErrorCode; } bool LFile::IsOpen() { return ValidHandle(d->hFile); } #define DEBUG_OPEN_FILES 0 #if DEBUG_OPEN_FILES LMutex Lck; LArray OpenFiles; #endif int LFile::Open(const char *File, int Mode) { int Status = false; if (File) { if (TestFlag(Mode, O_WRITE) || TestFlag(Mode, O_READWRITE)) { Mode |= O_CREAT; } Close(); d->hFile = open(File, Mode #ifdef O_LARGEFILE | O_LARGEFILE #endif , S_IRUSR | S_IWUSR); if (ValidHandle(d->hFile)) { d->Attributes = Mode; d->Name = new char[strlen(File)+1]; if (d->Name) { strcpy(d->Name, File); } Status = true; d->Status = true; #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { if (!OpenFiles.HasItem(this)) OpenFiles.Add(this); Lck.Unlock(); } #endif } else { d->ErrorCode = errno; #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { for (unsigned i=0; iGetName()); Lck.Unlock(); } #endif printf("LFile::Open failed\n\topen(%s,%08.8x) = %i\n\terrno=%s\n", File, Mode, d->hFile, LErrorCodeToString(d->ErrorCode)); } } return Status; } int LFile::Close() { if (ValidHandle(d->hFile)) { close(d->hFile); d->hFile = INVALID_HANDLE; DeleteArray(d->Name); #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { OpenFiles.Delete(this); Lck.Unlock(); } #endif } return true; } uint64_t LFile::GetModifiedTime() { struct stat s; stat(d->Name, &s); return s.st_mtime; } bool LFile::SetModifiedTime(uint64_t dt) { struct stat s; stat(d->Name, &s); struct timeval times[2] = {}; times[0].tv_sec = s.st_atime; times[1].tv_sec = dt; int r = utimes(d->Name, times); return r == 0; } void LFile::ChangeThread() { } #define CHUNK 0xFFF0 ssize_t LFile::Read(void *Buffer, ssize_t Size, int Flags) { int Red = 0; if (Buffer && Size > 0) { Red = read(d->hFile, Buffer, Size); } d->Status = Red == Size; return MAX(Red, 0); } ssize_t LFile::Write(const void *Buffer, ssize_t Size, int Flags) { int Written = 0; if (Buffer && Size > 0) { Written = write(d->hFile, Buffer, Size); } d->Status = Written == Size; return MAX(Written, 0); } #ifdef LINUX #define LINUX64 1 #endif int64 LFile::Seek(int64 To, int Whence) { #if LINUX64 return lseek64(d->hFile, To, Whence); // If this doesn't compile, switch off LINUX64 #else return lseek(d->hFile, To, Whence); #endif } int64 LFile::SetPos(int64 Pos) { #if LINUX64 int64 p = lseek64(d->hFile, Pos, SEEK_SET); if (p < 0) { int e = errno; printf("%s:%i - lseek64(%Lx) failed (error %i: %s).\n", __FILE__, __LINE__, Pos, e, GetErrorName(e)); } #else return lseek(d->hFile, Pos, SEEK_SET); #endif } int64 LFile::GetPos() { #if LINUX64 int64 p = lseek64(d->hFile, 0, SEEK_CUR); if (p < 0) { int e = errno; printf("%s:%i - lseek64 failed (error %i: %s).\n", __FILE__, __LINE__, e, GetErrorName(e)); } return p; #else return lseek(d->hFile, 0, SEEK_CUR); #endif } int64 LFile::GetSize() { int64 Here = GetPos(); #if LINUX64 int64 Ret = lseek64(d->hFile, 0, SEEK_END); #else int64 Ret = lseek(d->hFile, 0, SEEK_END); #endif SetPos(Here); return Ret; } int64 LFile::SetSize(int64 Size) { if (ValidHandle(d->hFile)) { int64 Pos = GetPos(); #if LINUX64 ftruncate64(d->hFile, Size); #else ftruncate(d->hFile, Size); #endif if (d->hFile) { SetPos(Pos); } } return GetSize(); } bool LFile::Eof() { return GetPos() >= GetSize(); } ssize_t LFile::SwapRead(uchar *Buf, ssize_t Size) { ssize_t i = 0; ssize_t r = 0; Buf = &Buf[Size-1]; while (Size--) { r = read(d->hFile, Buf--, 1); i += r; } return i; } ssize_t LFile::SwapWrite(uchar *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w = 0; Buf = &Buf[Size-1]; while (Size--) { w = write(d->hFile, Buf--, 1); i += w; } return i; } ssize_t LFile::ReadStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t r = 0; if (Buf && Size > 0) { char c; Size--; do { r = read(d->hFile, &c, 1); if (Eof()) { break; } *Buf++ = c; i++; } while (i < Size - 1 && c != '\n'); *Buf = 0; } return i; } ssize_t LFile::WriteStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w; while (i <= Size) { w = write(d->hFile, Buf, 1); Buf++; i++; if (*Buf == '\n') break; } return i; } void LFile::SetStatus(bool s) { d->Status = s; } bool LFile::GetStatus() { return d->Status; } void LFile::SetSwap(bool s) { d->Swap = s; } bool LFile::GetSwap() { return d->Swap; } int LFile::GetOpenMode() { return d->Attributes; } const char *LFile::GetName() { return d->Name; } #define GFileOp(type) LFile &LFile::operator >> (type &i) { d->Status |= ((d->Swap) ? SwapRead((uchar*) &i, sizeof(i)) : Read(&i, sizeof(i))) != sizeof(i); return *this; } GFileOps(); #undef GFileOp #define GFileOp(type) LFile &LFile::operator << (type i) { d->Status |= ((d->Swap) ? SwapWrite((uchar*) &i, sizeof(i)) : Write(&i, sizeof(i))) != sizeof(i); return *this; } GFileOps(); #undef GFileOp ////////////////////////////////////////////////////////////////////////////// +LString LFile::Path::Sep(DIR_STR); + bool LFile::Path::FixCase() { LString Prev; bool Changed = false; // printf("FixCase '%s'\n", GetFull().Get()); for (size_t i=0; i %s\n", part.Get(), name); Part = Name; Next = (Prev ? Prev : "") + Sep + Part; Changed = true; } } } Prev = Next; } return Changed; } diff --git a/src/haiku/ScreenDC.cpp b/src/haiku/ScreenDC.cpp --- a/src/haiku/ScreenDC.cpp +++ b/src/haiku/ScreenDC.cpp @@ -1,479 +1,481 @@ /*hdr ** FILE: LScreenDC.cpp ** AUTHOR: Matthew Allen ** DATE: 29/11/2021 ** DESCRIPTION: Haiku screen DC ** ** Copyright (C) 2021, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include #include #define LOGGING 0 #define VIEW_CHECK(...) if (!d->v) return __VA_ARGS__; class LScreenPrivate { public: int x = 0, y = 0, Bits = 32; bool Own = false; LColour Col; LRect Client; LView *View = NULL; OsView v = NULL; LScreenPrivate() { Client.ZOff(-1, -1); } ~LScreenPrivate() { } }; // Translates are cumulative... so we undo the last one before resetting it. ///////////////////////////////////////////////////////////////////////////////////////////////////// LScreenDC::LScreenDC() { d = new LScreenPrivate; d->x = GdcD->X(); d->y = GdcD->Y(); d->Bits = GdcD->GetBits(); } LScreenDC::LScreenDC(LView *view, void *param) { d = new LScreenPrivate; if (d->View = view) d->v = view->Handle(); d->Bits = GdcD->GetBits(); /* if (d->v) d->Client = d->v->Frame(); else LgiTrace("%s:%i - LScreenDC::LScreenDC - No view?\n", _FL); */ } LScreenDC::~LScreenDC() { DeleteObj(d); } OsPainter LScreenDC::Handle() { return d->v; } ::LString LScreenDC::Dump() { ::LString s; s.Printf("LScreenDC size=%i,%i\n", d->x, d->y); return s; } bool LScreenDC::SupportsAlphaCompositing() { return true; } LPoint LScreenDC::GetDpi() { return LScreenDpi(); } bool LScreenDC::GetClient(LRect *c) { if (!c) return false; *c = d->Client; return true; } LString GetClip(BView *v) { BRegion r; v->GetClippingRegion(&r); LRect lr = r.Frame(); return lr.GetStr(); } void LScreenDC::GetOrigin(int &x, int &y) { x = OriginX; y = OriginY; #if LOGGING printf("%p.GetOrigin=%i+%i=%i, %i+%i=%i\n", this, OriginX, d->Client.x1, x, OriginY, d->Client.y1, y); #endif } void LScreenDC::SetOrigin(int x, int y) { VIEW_CHECK() if (d->Client.Valid()) { // The clipping region is relative to the offset. // Remove it here and reinstate it after setting the origin. d->v->ConstrainClippingRegion(NULL); } OriginX = x; OriginY = y; #if LOGGING printf("%p.SetOrigin=%i,%i (%i,%i) = %i,%i\n", this, x, y, d->Client.x1, d->Client.y1, d->Client.x1 - OriginX, d->Client.y1 - OriginY); #endif d->v->SetOrigin( d->Client.x1 - OriginX, d->Client.y1 - OriginY); if (d->Client.Valid()) { // Reset the clipping region related to the origin. auto clp = d->Client.ZeroTranslate(); clp.Offset(OriginX, OriginY); d->v->ClipToRect(clp); } } void LScreenDC::SetClient(LRect *c) { VIEW_CHECK() if (c) { d->Client = *c; + OriginX += d->Client.x1; + OriginY += d->Client.y1; #if LOGGING printf("SetClient(%s)\n", d->Client.GetStr()); #endif d->v->SetOrigin(OriginX, OriginY); auto clp = d->Client.ZeroTranslate(); - clp.Offset(OriginX, OriginY); + // clp.Offset(OriginX, OriginY); d->v->ClipToRect(clp); /* BRegion r; d->v->GetClippingRegion(&r); LRect lr = r.Frame(); printf("SetClient %s = %s (%i,%i)\n", c->GetStr(), lr.GetStr(), OriginX, OriginY); */ } else { d->v->ConstrainClippingRegion(NULL); d->v->SetOrigin(OriginX = 0, OriginX = 0); d->Client.ZOff(-1, -1); #if LOGGING printf("SetClient()\n"); #endif } } LRect *LScreenDC::GetClient() { return &d->Client; } uint LScreenDC::LineStyle() { return LSurface::LineStyle(); } uint LScreenDC::LineStyle(uint32_t Bits, uint32_t Reset) { return LSurface::LineStyle(Bits); } int LScreenDC::GetFlags() { return 0; } LRect LScreenDC::ClipRgn() { return Clip; } LRect LScreenDC::ClipRgn(LRect *c) { LRect Prev = Clip; if (c) { Clip = *c; d->v->ClipToRect(Clip); } else { Clip.ZOff(-1, -1); d->v->ConstrainClippingRegion(NULL); } return Prev; } COLOUR LScreenDC::Colour() { return d->Col.Get(GetBits()); } COLOUR LScreenDC::Colour(COLOUR c, int Bits) { auto b = Bits ? Bits : GetBits(); d->Col.Set(c, b); return Colour(d->Col).Get(b); } LColour LScreenDC::Colour(LColour c) { LColour Prev = d->Col; d->Col = c; if (d->v) { d->v->SetLowColor(c); d->v->SetHighColor(c); } else LgiTrace("%s:%i - No view.\n", _FL); return Prev; } int LScreenDC::Op(int ROP, NativeInt Param) { return GDC_SET; // not supported.. } int LScreenDC::Op() { return GDC_SET; // not supported.. } int LScreenDC::X() { return d->Client.Valid() ? d->Client.X() : d->x; } int LScreenDC::Y() { return d->Client.Valid() ? d->Client.Y() : d->y; } int LScreenDC::GetBits() { return d->Bits; } LPalette *LScreenDC::Palette() { return NULL; // not supported. } void LScreenDC::Palette(LPalette *pPal, bool bOwnIt) { } void LScreenDC::Set(int x, int y) { VIEW_CHECK() d->v->StrokeLine(BPoint(x, y), BPoint(x, y)); } COLOUR LScreenDC::Get(int x, int y) { return 0; } void LScreenDC::HLine(int x1, int x2, int y) { VIEW_CHECK() d->v->StrokeLine(BPoint(x1, y), BPoint(x2, y)); } void LScreenDC::VLine(int x, int y1, int y2) { VIEW_CHECK() d->v->StrokeLine(BPoint(x, y1), BPoint(x, y2)); } void LScreenDC::Line(int x1, int y1, int x2, int y2) { VIEW_CHECK() d->v->StrokeLine(BPoint(x1, y1), BPoint(x2, y2)); } void LScreenDC::Circle(double cx, double cy, double radius) { VIEW_CHECK() d->v->StrokeArc(BPoint(cx, cy), radius, radius, 0, 360); } void LScreenDC::FilledCircle(double cx, double cy, double radius) { VIEW_CHECK() d->v->FillArc(BPoint(cx, cy), radius, radius, 0, 360); } void LScreenDC::Arc(double cx, double cy, double radius, double start, double end) { VIEW_CHECK() d->v->StrokeArc(BPoint(cx, cy), radius, radius, start, end); } void LScreenDC::FilledArc(double cx, double cy, double radius, double start, double end) { VIEW_CHECK() d->v->FillArc(BPoint(cx, cy), radius, radius, start, end); } void LScreenDC::Ellipse(double cx, double cy, double x, double y) { VIEW_CHECK() d->v->StrokeArc(BPoint(cx, cy), x, y, 0, 360); } void LScreenDC::FilledEllipse(double cx, double cy, double x, double y) { VIEW_CHECK() d->v->FillArc(BPoint(cx, cy), x, y, 0, 360); } void LScreenDC::Box(int x1, int y1, int x2, int y2) { VIEW_CHECK() d->v->StrokeRect(LRect(x1, y1, x2, y2)); } void LScreenDC::Box(LRect *a) { VIEW_CHECK() if (a) d->v->StrokeRect(*a); else Box(0, 0, X()-1, Y()-1); } void LScreenDC::Rectangle(int x1, int y1, int x2, int y2) { VIEW_CHECK() if (x2 >= x1 && y2 >= y1) { // auto o = d->v->Origin(); // printf("Rect %i,%i,%i,%i %g,%g\n", x1, y1, x2, y2, o.x, o.y); d->v->FillRect(LRect(x1, y1, x2, y2)); } } void LScreenDC::Rectangle(LRect *a) { VIEW_CHECK() LRect r = a ? *a : Bounds(); BRect br(r.x1, r.y1, r.x2, r.y2); d->v->FillRect(br); } void LScreenDC::Polygon(int Points, LPoint *Data) { VIEW_CHECK() if (!Data || Points <= 0) return; } void LScreenDC::Blt(int x, int y, LSurface *Src, LRect *a) { VIEW_CHECK() if (!Src) { LgiTrace("%s:%i - No source.\n", _FL); return; } if (Src->IsScreen()) { LgiTrace("%s:%i - Can't do screen->screen blt.\n", _FL); return; } BBitmap *bmp = Src->GetBitmap(); if (!bmp) { LAssert(!"no bmp."); LgiTrace("%s:%i - No bitmap.\n", _FL); return; } d->v->SetDrawingMode(B_OP_ALPHA); d->v->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); if (a) { BRect bitmapRect = *a; BRect viewRect(x, y, x + a->X() - 1, y + a->Y() - 1); d->v->DrawBitmap(bmp, bitmapRect, viewRect); #if 0 printf("ScreenDC::Blt %g,%g/%gx%g -> %g,%g/%gx%g %i,%i\n", bitmapRect.left, bitmapRect.top, bitmapRect.Width(), bitmapRect.Height(), viewRect.left, viewRect.top, viewRect.Width(), viewRect.Height(), a->X(), a->Y()); #endif } else { BRect bitmapRect = bmp->Bounds(); BRect viewRect(x, y, x + Src->X() - 1, y + Src->Y() - 1); d->v->DrawBitmap(bmp, bitmapRect, viewRect); #if 0 printf("ScreenDC::Blt %g,%g/%gx%g -> %g,%g/%gx%g %i,%i\n", bitmapRect.left, bitmapRect.top, bitmapRect.Width(), bitmapRect.Height(), viewRect.left, viewRect.top, viewRect.Width(), viewRect.Height(), x, y); #endif } d->v->SetDrawingMode(B_OP_COPY); } void LScreenDC::StretchBlt(LRect *Dest, LSurface *Src, LRect *s) { VIEW_CHECK() LAssert(0); } void LScreenDC::Bezier(int Threshold, LPoint *Pt) { VIEW_CHECK() LAssert(0); } void LScreenDC::FloodFill(int x, int y, int Mode, COLOUR Border, LRect *Bounds) { VIEW_CHECK() LAssert(0); } diff --git a/src/haiku/View.cpp b/src/haiku/View.cpp --- a/src/haiku/View.cpp +++ b/src/haiku/View.cpp @@ -1,1129 +1,1130 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 29/11/2021 ** DESCRIPTION: Haiku LView Implementation ** ** Copyright (C) 2021, 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 "ViewPriv.h" #include #define DEBUG_MOUSE_EVENTS 0 #if 0 #define DEBUG_INVALIDATE(...) printf(__VA_ARGS__) #else #define DEBUG_INVALIDATE(...) #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 }; //////////////////////////////////////////////////////////////////////////// void _lgi_yield() { LAppInst->Yield(); } void *IsAttached(BView *v) { auto pview = v->Parent(); auto pwnd = v->Window(); return pwnd ? (void*)pwnd : (void*)pview; } bool LgiIsKeyDown(int Key) { LAssert(0); return false; } LKey::LKey(int Vkey, uint32_t flags) { vkey = Vkey; Flags = flags; IsChar = false; } //////////////////////////////////////////////////////////////////////////////////////////////////// template struct LBView : public Parent { LViewPrivate *d = NULL; static uint32 MouseButtons; LBView(LViewPrivate *priv) : d(priv), Parent ( "", B_FULL_UPDATE_ON_RESIZE | B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS ) { Parent::SetName(d->View->GetClass()); } ~LBView() { if (d) d->Hnd = NULL; } void AttachedToWindow() { if (d) d->View->OnCreate(); } LKey ConvertKey(const char *bytes, int32 numBytes) { LKey k; uint8_t *utf = (uint8_t*)bytes; ssize_t len = numBytes; auto w = LgiUtf8To32(utf, len); key_info KeyInfo; if (get_key_info(&KeyInfo) == B_OK) { k.Ctrl(TestFlag(KeyInfo.modifiers, B_CONTROL_KEY)); k.Alt(TestFlag(KeyInfo.modifiers, B_MENU_KEY)); k.Shift(TestFlag(KeyInfo.modifiers, B_SHIFT_KEY)); k.System(TestFlag(KeyInfo.modifiers, B_COMMAND_KEY)); } #if 0 LString::Array a; for (int i=0; iCurrentMessage(); if (bmsg) { int32 key = 0; if (bmsg->FindInt32("key", &key) == B_OK) { // Translate the function keys into the LGI address space... switch (key) { case B_F1_KEY: w = LK_F1; break; case B_F2_KEY: w = LK_F2; break; case B_F3_KEY: w = LK_F3; break; case B_F4_KEY: w = LK_F4; break; case B_F5_KEY: w = LK_F5; break; case B_F6_KEY: w = LK_F6; break; case B_F7_KEY: w = LK_F7; break; case B_F8_KEY: w = LK_F8; break; case B_F9_KEY: w = LK_F9; break; case B_F10_KEY: w = LK_F10; break; case B_F11_KEY: w = LK_F11; break; case B_F12_KEY: w = LK_F12; break; default: printf("%s:%i - Upsupported key %i.\n", _FL, key); break; } } else printf("%s:%i - No 'key' in BMessage.\n", _FL); } else printf("%s:%i - No BMessage.\n", _FL); } k.c16 = k.vkey = w; } k.IsChar = !( k.System() || k.Alt() ) && ( (k.c16 >= ' ' && k.c16 < LK_DELETE) || k.c16 == LK_BACKSPACE || k.c16 == LK_TAB || k.c16 == LK_RETURN ); return k; } void KeyDown(const char *bytes, int32 numBytes) { if (!d) return; auto k = ConvertKey(bytes, numBytes); k.Down(true); auto wnd = d->View->GetWindow(); if (wnd) wnd->HandleViewKey(d->View, k); else d->View->OnKey(k); } void KeyUp(const char *bytes, int32 numBytes) { if (!d) return; auto k = ConvertKey(bytes, numBytes); auto wnd = d->View->GetWindow(); if (wnd) wnd->HandleViewKey(d->View, k); else d->View->OnKey(k); } void FrameMoved(BPoint newPosition) { if (!d) return; d->View->Pos = Parent::Frame(); d->View->OnPosChange(); } void FrameResized(float newWidth, float newHeight) { if (!d) return; d->View->Pos = Parent::Frame(); d->View->OnPosChange(); } void MessageReceived(BMessage *message) { if (!d) return; void *v = NULL; if (message->FindPointer(LMessage::PropView, &v) == B_OK) { // Proxy'd event for child view... ((LView*)v)->OnEvent((LMessage*)message); return; } else d->View->OnEvent((LMessage*)message); if (message->what == B_MOUSE_WHEEL_CHANGED) { float x = 0.0f, y = 0.0f; message->FindFloat("be:wheel_delta_x", &x); message->FindFloat("be:wheel_delta_y", &y); d->View->OnMouseWheel(y * 3.0f); } else if (message->what == M_SET_SCROLL) { return; } Parent::MessageReceived(message); } void Draw(BRect updateRect) { if (!d) return; LScreenDC dc(d->View); LPoint off(0,0); d->View->_Paint(&dc, &off, NULL); } LMouse ConvertMouse(BPoint where, bool down = false) { LMouse m; BPoint loc; uint32 buttons = 0; m.Target = d->View; m.x = where.x; m.y = where.y; if (down) { m.Down(true); Parent::GetMouse(&loc, &buttons, false); MouseButtons = buttons; // save for up click } else { buttons = MouseButtons; } if (buttons & B_PRIMARY_MOUSE_BUTTON) m.Left(true); if (buttons & B_TERTIARY_MOUSE_BUTTON) m.Middle(true); if (buttons & B_SECONDARY_MOUSE_BUTTON) m.Right(true); uint32 mod = modifiers(); if (mod & B_SHIFT_KEY) m.Shift(true); if (mod & B_OPTION_KEY) m.Alt(true); if (mod & B_CONTROL_KEY) m.Ctrl(true); if (mod & B_COMMAND_KEY) m.System(true); return m; } void MouseDown(BPoint where) { if (!d) return; static uint64_t lastClick = 0; bigtime_t interval = 0; status_t r = get_click_speed(&interval); auto now = LCurrentTime(); bool doubleClick = now-lastClick < (interval/1000); lastClick = now; LMouse m = ConvertMouse(where, true); m.Double(doubleClick); d->View->_Mouse(m, false); } void MouseUp(BPoint where) { if (!d) return; LMouse m = ConvertMouse(where); m.Down(false); d->View->_Mouse(m, false); } void MouseMoved(BPoint where, uint32 code, const BMessage *dragMessage) { if (!d) return; LMouse m = ConvertMouse(where); m.Down( m.Left() || m.Middle() || m.Right()); m.IsMove(true); d->View->_Mouse(m, true); } void MakeFocus(bool focus=true) { if (!d) return; Parent::MakeFocus(focus); d->View->OnFocus(focus); } }; template uint32 LBView::MouseButtons = 0; //////////////////////////////////////////////////////////////////////////////////////////////////// LViewPrivate::LViewPrivate(LView *view) : View(view), Hnd(new LBView(this)) { } LViewPrivate::~LViewPrivate() { View->d = NULL; MsgQue.DeleteObjects(); if (Font && FontOwnType == GV_FontOwned) DeleteObj(Font); if (Hnd) { auto *bv = dynamic_cast*>(Hnd); // printf("%p::~LViewPrivate View=%p bv=%p th=%u\n", this, View, bv, GetCurrentThreadId()); if (bv) bv->d = NULL; auto Wnd = Hnd->Window(); + bool locked = false; if (Wnd) - Wnd->LockLooper(); + locked = Wnd->LockLooper(); if (Hnd->Parent()) Hnd->RemoveSelf(); DeleteObj(Hnd); - if (Wnd) + if (Wnd && locked) Wnd->UnlockLooper(); } } void LView::_Focus(bool f) { ThreadCheck(); if (f) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); LLocker lck(d->Hnd, _FL); if (lck.Lock()) { d->Hnd->MakeFocus(f); lck.Unlock(); } // OnFocus will be called by the LBview handler... Invalidate(); } void LView::_Delete() { SetPulse(); // Remove static references to myself if (_Over == this) _Over = 0; if (_Capturing == this) _Capturing = 0; auto *Wnd = GetWindow(); if (Wnd && Wnd->GetFocus() == static_cast(this)) Wnd->SetFocus(this, LWindow::ViewDelete); if (LAppInst && LAppInst->AppWnd == this) { LAppInst->AppWnd = 0; } // 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; } BCursorID LgiToHaiku(LCursor c) { switch (c) { #define _(l,h) case l: return h; _(LCUR_Blank, B_CURSOR_ID_NO_CURSOR) _(LCUR_Normal, B_CURSOR_ID_SYSTEM_DEFAULT) _(LCUR_UpArrow, B_CURSOR_ID_RESIZE_NORTH) _(LCUR_DownArrow, B_CURSOR_ID_RESIZE_SOUTH) _(LCUR_LeftArrow, B_CURSOR_ID_RESIZE_WEST) _(LCUR_RightArrow, B_CURSOR_ID_RESIZE_EAST) _(LCUR_Cross, B_CURSOR_ID_CROSS_HAIR) _(LCUR_Wait, B_CURSOR_ID_PROGRESS) _(LCUR_Ibeam, B_CURSOR_ID_I_BEAM) _(LCUR_SizeVer, B_CURSOR_ID_RESIZE_NORTH_SOUTH) _(LCUR_SizeHor, B_CURSOR_ID_RESIZE_EAST_WEST) _(LCUR_SizeBDiag, B_CURSOR_ID_RESIZE_NORTH_WEST_SOUTH_EAST) _(LCUR_SizeFDiag, B_CURSOR_ID_RESIZE_NORTH_EAST_SOUTH_WEST) _(LCUR_PointingHand, B_CURSOR_ID_GRAB) _(LCUR_Forbidden, B_CURSOR_ID_NOT_ALLOWED) _(LCUR_DropCopy, B_CURSOR_ID_COPY) _(LCUR_DropMove, B_CURSOR_ID_MOVE) // _(LCUR_SizeAll, // _(LCUR_SplitV, // _(LCUR_SplitH, #undef _ } return B_CURSOR_ID_SYSTEM_DEFAULT; } bool LView::_Mouse(LMouse &m, bool Move) { ThreadCheck(); #if DEBUG_MOUSE_EVENTS if (!Move) LgiTrace("%s:%i - %s\n", _FL, m.ToString().Get()); #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 0 //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)); } int cursor = GetCursor(m.x, m.y); if (cursor >= 0) { BCursorID haikuId = LgiToHaiku((LCursor)cursor); static BCursorID curId = B_CURSOR_ID_SYSTEM_DEFAULT; if (curId != haikuId) { curId = haikuId; LLocker lck(Handle(), _FL); if (lck.Lock()) { Handle()->SetViewCursor(new BCursor(curId)); lck.Unlock(); } } } } 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) { if (Move) Target->OnMouseMove(m); else 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; } 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; } LViewI *LView::FindControl(OsView hCtrl) { if (d->Hnd == hCtrl) { return this; } for (auto i : Children) { LViewI *Ctrl = i->FindControl(hCtrl); if (Ctrl) { return Ctrl; } } return 0; } 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; LLocker lck(d->Hnd, _FL); if (lck.Lock()) { d->Hnd->ResizeTo(Pos.X(), Pos.Y()); d->Hnd->MoveTo(Pos.x1, Pos.y1); lck.Unlock(); } OnPosChange(); } return true; } bool LView::Invalidate(LRect *rc, bool Repaint, bool Frame) { 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, 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; if (d->Hnd) { LLocker lck(d->Hnd, _FL); if (lck.Lock()) d->Hnd->Invalidate(); } Repainting = false; } else { DEBUG_INVALIDATE(" error: repainting\n"); } return true; } void LView::SetPulse(int Length) { DeleteObj(d->PulseThread); if (Length > 0) d->PulseThread = new LPulseThread(this, Length); } LMessage::Param LView::OnEvent(LMessage *Msg) { ThreadCheck(); int Id; switch (Id = Msg->Msg()) { case M_HANDLE_IN_THREAD: { LMessage::InThreadCb *Cb = NULL; if (Msg->FindPointer(LMessage::PropCallback, (void**)&Cb) == B_OK) { // printf("M_HANDLE_IN_THREAD before call..\n"); (*Cb)(); // printf("M_HANDLE_IN_THREAD after call..\n"); delete Cb; // printf("M_HANDLE_IN_THREAD after delete..\n"); } else printf("%s:%i - No Callback.\n", _FL); break; } case M_INVALIDATE: { if ((LView*)this == (LView*)Msg->B()) { LAutoPtr r((LRect*)Msg->A()); Invalidate(r); } break; } case M_PULSE: { OnPulse(); break; } case M_CHANGE: { LViewI *Ctrl = NULL; if (GetViewById(Msg->A(), Ctrl)) { LNotification n((LNotifyType)Msg->B()); return OnNotify(Ctrl, n); } break; } case M_COMMAND: { // printf("M_COMMAND %i\n", (int)Msg->A()); return OnCommand(Msg->A(), 0, 0); } case M_THREAD_COMPLETED: { auto Th = (LThread*)Msg->A(); if (!Th) break; Th->OnComplete(); if (Th->GetDeleteOnExit()) delete Th; return true; } } return 0; } OsView LView::Handle() const { if (!d) { printf("%s:%i - No priv?\n", _FL); return NULL; } return d->Hnd; } bool LView::PointToScreen(LPoint &p) { if (!Handle()) { LgiTrace("%s:%i - No handle.\n", _FL); return false; } LPoint Offset; WindowVirtualOffset(&Offset); // printf("p=%i,%i offset=%i,%i\n", p.x, p.y, Offset.x, Offset.y); p += Offset; // printf("p=%i,%i\n", p.x, p.y); LLocker lck(Handle(), _FL); if (!lck.Lock()) { LgiTrace("%s:%i - Can't lock.\n", _FL); return false; } BPoint pt = Handle()->ConvertToScreen(BPoint(p.x, p.y)); // printf("pt=%g,%g\n", pt.x, pt.y); p.x = pt.x; p.y = pt.y; // printf("p=%i,%i\n\n", p.x, p.y); return true; } bool LView::PointToView(LPoint &p) { if (!Handle()) { LgiTrace("%s:%i - No handle.\n", _FL); return false; } LPoint Offset; WindowVirtualOffset(&Offset); p -= Offset; LLocker lck(Handle(), _FL); if (!lck.Lock()) { LgiTrace("%s:%i - Can't lock.\n", _FL); return false; } BPoint pt = Handle()->ConvertFromScreen(BPoint(Offset.x, Offset.y)); Offset.x = pt.x; Offset.y = pt.y; return true; } bool LView::GetMouse(LMouse &m, bool ScreenCoords) { LLocker Locker(d->Hnd, _FL); if (!Locker.Lock()) return false; // get mouse state BPoint Cursor; uint32 Buttons; d->Hnd->GetMouse(&Cursor, &Buttons); if (ScreenCoords) d->Hnd->ConvertToScreen(&Cursor); // position m.x = Cursor.x; m.y = Cursor.y; // buttons m.Left(TestFlag(Buttons, B_PRIMARY_MOUSE_BUTTON)); m.Middle(TestFlag(Buttons, B_TERTIARY_MOUSE_BUTTON)); m.Right(TestFlag(Buttons, B_SECONDARY_MOUSE_BUTTON)); // key states key_info KeyInfo; if (get_key_info(&KeyInfo) == B_OK) { m.Ctrl(TestFlag(KeyInfo.modifiers, B_CONTROL_KEY)); m.Alt(TestFlag(KeyInfo.modifiers, B_MENU_KEY)); m.Shift(TestFlag(KeyInfo.modifiers, B_SHIFT_KEY)); } return true; } bool LView::IsAttached() { bool attached = false; LLocker lck(d->Hnd, _FL); if (lck.Lock()) { auto pview = d->Hnd->Parent(); auto pwnd = d->Hnd->Window(); attached = pview != NULL || pwnd != NULL; } return attached; } const char *LView::GetClass() { return "LView"; } bool LView::Attach(LViewI *parent) { bool Status = false; bool Debug = false; // !Stricmp(GetClass(), "LScrollBar"); 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) { LgiTrace("%s:%i - No parent window.\n", _FL); } else { auto w = GetWindow(); if (w && TestFlag(WndFlags, GWF_FOCUS)) w->SetFocus(this, LWindow::GainFocus); auto bview = parent->Handle(); if (bview) { LLocker lck(bview, _FL); if (lck.Lock()) { if (Debug) LgiTrace("%s:%i - Attaching %s to view %s\n", _FL, GetClass(), parent->GetClass()); d->Hnd->SetName(GetClass()); if (::IsAttached(d->Hnd)) d->Hnd->RemoveSelf(); bview->AddChild(d->Hnd); d->Hnd->ResizeTo(Pos.X(), Pos.Y()); d->Hnd->MoveTo(Pos.x1, Pos.y1); bool ShowView = TestFlag(GViewFlags, GWF_VISIBLE) && d->Hnd->IsHidden(); if (Debug) LgiTrace("%s:%i - IsHidden=%i ShowView=%i\n", _FL, d->Hnd->IsHidden(), ShowView); if (ShowView) d->Hnd->Show(); Status = true; if (d->MsgQue.Length()) { printf("%s:%i - %s.Attach msgQue=%i\n", _FL, GetClass(), (int)d->MsgQue.Length()); for (auto bmsg: d->MsgQue) d->Hnd->Window()->PostMessage(bmsg); d->MsgQue.DeleteObjects(); } if (Debug) LgiTrace("%s:%i - Attached %s/%p to view %s/%p, success\n", _FL, GetClass(), d->Hnd, parent->GetClass(), bview); } else { if (Debug) LgiTrace("%s:%i - Error attaching %s to view %s, can't lock.\n", _FL, GetClass(), parent->GetClass()); } } if (!Parent->HasView(this)) { OnAttach(); if (!d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); } } 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; } /////////////////////////////////////////////////////////////////// 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/haiku/Window.cpp b/src/haiku/Window.cpp --- a/src/haiku/Window.cpp +++ b/src/haiku/Window.cpp @@ -1,1010 +1,1017 @@ #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" #include "MenuBar.h" #define DEBUG_SETFOCUS 0 #define DEBUG_HANDLEVIEWKEY 0 +#define DEBUG_WAIT_THREAD 1 + +#if DEBUG_WAIT_THREAD + #define WAIT_LOG(...) LgiTrace(__VA_ARGS__) +#else + #define WAIT_LOG(...) +#endif LString ToString(BRect &r) { LString s; s.Printf("%g,%g,%g,%g", r.left, r.top, r.right, r.bottom); return s; } /////////////////////////////////////////////////////////////////////// class HookInfo { public: LWindowHookType Flags; LView *Target; }; enum LAttachState { LUnattached, LAttaching, LAttached, LDetaching, }; class LWindowPrivate : public BWindow { public: LWindow *Wnd; bool SnapToEdge = false; LArray Hooks; LWindowPrivate(LWindow *wnd) : Wnd(wnd), BWindow(BRect(100,100,400,400), "...loading...", B_DOCUMENT_WINDOW_LOOK, B_NORMAL_WINDOW_FEEL, 0) { } ~LWindowPrivate() { DeleteObj(Wnd->Menu); if (IsMinimized()) Wnd->_PrevZoom = LZoomMin; Wnd->d = NULL; } int GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = LNoEvents; return Hooks.Length() - 1; } } return -1; } void FrameMoved(BPoint newPosition) { auto Pos = Frame(); if (Pos != Wnd->Pos) { Wnd->Pos = Pos; Wnd->OnPosChange(); } BWindow::FrameMoved(newPosition); } void FrameResized(float newWidth, float newHeight) { auto Pos = Frame(); if (Pos != Wnd->Pos) { Wnd->Pos.SetSize(newWidth, newHeight); // printf("Wnd->Pos=%s %i,%i %g,%g\n", Wnd->Pos.GetStr(), Wnd->Pos.X(), Wnd->Pos.Y(), newWidth, newHeight); Wnd->OnPosChange(); } BWindow::FrameResized(newWidth, newHeight); } bool QuitRequested() { // printf("%s::QuitRequested() starting..\n", Wnd->GetClass()); auto r = Wnd->OnRequestClose(false); // printf("%s::QuitRequested()=%i\n", Wnd->GetClass(), r); return r; } void MessageReceived(BMessage *message) { if (message->what == M_LWINDOW_DELETE) { // printf("Processing M_LWINDOW_DELETE th=%u\n", GetCurrentThreadId()); Wnd->Handle()->RemoveSelf(); Quit(); // printf("Processed M_LWINDOW_DELETE\n"); } else { BWindow::MessageReceived(message); LView *view = NULL; auto r = message->FindPointer(LMessage::PropView, &view); if (r == B_OK) view->OnEvent((LMessage*)message); else Wnd->OnEvent((LMessage*)message); } } }; /////////////////////////////////////////////////////////////////////// LWindow::LWindow() : LView(0) { d = new LWindowPrivate(this); _QuitOnClose = false; Menu = NULL; _Default = 0; _Window = this; ClearFlag(WndFlags, GWF_VISIBLE); } LWindow::~LWindow() { if (LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; LAssert(!Menu); WaitThread(); } int LWindow::WaitThread() { if (!d) return -1; thread_id id = d->Thread(); bool thisThread = id == GetCurrentThreadId(); - // printf("%s::~LWindow thread=%u lock=%u\n", Name(), GetCurrentThreadId(), d->LockingThread()); + WAIT_LOG("%s::~LWindow thread=%u lock=%u\n", Name(), GetCurrentThreadId(), d->LockingThread()); if (thisThread) { // We are in thread... can delete easily. if (d->Lock()) { // printf("%s::~LWindow Quiting\n", Name()); Handle()->RemoveSelf(); d->Quit(); // printf("%s::~LWindow Quit finished\n", Name()); } d = NULL; return 0; // If we're in thread... no need to wait. } // Post event to the window's thread to delete itself... - // printf("%s::~LWindow posting M_LWINDOW_DELETE from th=%u\n", Name(), GetCurrentThreadId()); + WAIT_LOG("%s::~LWindow posting M_LWINDOW_DELETE from th=%u\n", Name(), GetCurrentThreadId()); d->PostMessage(new BMessage(M_LWINDOW_DELETE)); status_t value = 0; - // printf("wait_for_thread(%u) start..\n", id); + WAIT_LOG("wait_for_thread(%u) start..\n", id); wait_for_thread(id, &value); - // printf("wait_for_thread(%u) end=%i\n", id, value); + WAIT_LOG("wait_for_thread(%u) end=%i\n", id, value); d = NULL; return value; } OsWindow LWindow::WindowHandle() { return d; } bool LWindow::SetIcon(const char *FileName) { LString a; if (!LFileExists(FileName)) { if (a = LFindFile(FileName)) FileName = a; } return false; } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool s) { d->SnapToEdge = s; } bool LWindow::IsActive() { LLocker lck(d, _FL); if (!lck.Lock()) return false; return d->IsActive(); } bool LWindow::SetActive() { LLocker lck(d, _FL); if (!lck.Lock()) return false; d->Activate(); return true; } bool LWindow::Visible() { LLocker lck(d, _FL); if (!lck.Lock()) return false; return !d->IsHidden(); } void LWindow::Visible(bool i) { LLocker lck(d, _FL); if (!lck.Lock()) { printf("%s:%i - Can't lock.\n", _FL); return; } if (i) { if (d->IsHidden()) d->Show(); } else { if (!d->IsHidden()) d->Hide(); } } bool LWindow::Obscured() { return false; } 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; } bool LWindow::Attach(LViewI *p) { LLocker lck(d, _FL); if (!lck.Lock()) return false; auto rootView = Handle(); auto wnd = WindowHandle(); // printf("%s:%i attach %p to %p\n", _FL, Handle(), WindowHandle()); if (rootView && wnd) { wnd->AddChild(rootView); if (rootView->IsHidden()) rootView->Show(); auto menu = wnd->KeyMenuBar(); BRect menuPos = menu ? menu->Frame() : BRect(0, 0, 0, 0); auto f = wnd->Frame(); rootView->ResizeTo(f.Width(), f.Height() - menuPos.Height()); if (menu) rootView->MoveTo(0, menuPos.Height()); rootView->SetResizingMode(B_FOLLOW_ALL_SIDES); } // Setup default button... if (!_Default) _Default = FindControl(IDOK); // Do a rough layout of child windows PourAll(); return true; } 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\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":"")); } #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: return Status; } void LWindow::Raise() { } LWindowZoom LWindow::GetZoom() { if (!d) return _PrevZoom; LLocker lck(d, _FL); if (!IsAttached() || lck.Lock()) { if (d->IsMinimized()) return LZoomMin; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { LLocker lck(d, _FL); if (lck.Lock()) { d->Minimize(i == LZoomMin); } } 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::Name(const char *n) { LLocker lck(d, _FL); if (lck.Lock()) d->SetTitle(n); return LBase::Name(n); } const char *LWindow::Name() { return LBase::Name(); } LPoint LWindow::GetDpi() { return LPoint(96, 96); } 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; r = Pos.ZeroTranslate(); LLocker lck(WindowHandle(), _FL); if (lck.Lock()) { auto br = Handle()->Bounds(); r = br; auto frm = d->Frame(); frm.OffsetBy(-frm.left, -frm.top); // printf("Frame=%s Bounds=%s r=%s\n", ToString(frm).Get(), ToString(br).Get(), r.GetStr()); lck.Unlock(); } 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; iParent(); p; p = p->Parent()) printf(" par=%s %i\n", p->Name(), p->IsHidden()); } } if (menuPos.Width() < 1) // Again... WHHHHY? FFS { float x = 0.0f, y = 0.0f; menu->GetPreferredSize(&x, &y); // printf("Pref=%g,%g\n", x, y); if (y > 0.0f) menu->ResizeTo(frame.Width(), y); } } #if 0 printf("frame=%s menu=%p,%i,%s rootpos=%s\n", ToString(frame).Get(), menu, menu?menu->IsHidden():0, ToString(menuPos).Get(), ToString(rootPos).Get()); #endif int rootTop = menu ? menuPos.bottom + 1 : 0; if (rootPos.top != rootTop) { Handle()->MoveTo(0, rootTop); Handle()->ResizeTo(rootPos.Width(), frame.Height() - menuPos.Height()); } lck.Unlock(); } LView::OnPosChange(); PourAll(); } #define IsTool(v) \ ( \ dynamic_cast(v) \ && \ dynamic_cast(v)->_IsToolBar \ ) void LWindow::PourAll() { LRect c = GetClient(); LRegion Client(c); LViewI *MenuView = 0; LRegion Update(Client); bool HasTools = false; LViewI *v; List::I Lst = Children.begin(); { LRegion Tools; for (v = *Lst; v; v = *++Lst) { bool IsMenu = MenuView == v; if (!IsMenu && IsTool(v)) { LRect OldPos = v->GetPos(); if (OldPos.Valid()) Update.Union(&OldPos); if (HasTools) { // 2nd and later toolbars if (v->Pour(Tools)) { if (!v->Visible()) { v->Visible(true); } auto vpos = v->GetPos(); if (OldPos != vpos) { // position has changed update... v->Invalidate(); } // Has it increased the size of the toolbar area? auto b = Tools.Bound(); if (vpos.y2 >= b.y2) { LRect Bar = Client; Bar.y2 = vpos.y2; Client.Subtract(&Bar); // LgiTrace("IncreaseToolbar=%s\n", Bar.GetStr()); } Tools.Subtract(&vpos); Update.Subtract(&vpos); // LgiTrace("vpos=%s\n", vpos.GetStr()); } } else { // First toolbar if (v->Pour(Client)) { HasTools = true; if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { v->Invalidate(); } LRect Bar(v->GetPos()); // LgiTrace("%s = %s\n", v->GetClass(), Bar.GetStr()); Bar.x2 = GetClient().x2; Tools = Bar; Tools.Subtract(&v->GetPos()); Client.Subtract(&Bar); Update.Subtract(&Bar); } } } } } Lst = Children.begin(); for (LViewI *v = *Lst; v; v = *++Lst) { bool IsMenu = MenuView == v; if (!IsMenu && !IsTool(v)) { LRect OldPos = v->GetPos(); if (OldPos.Valid()) Update.Union(&OldPos); if (v->Pour(Client)) { // LRect p = v->GetPos(); // LgiTrace("%s = %s\n", v->GetClass(), p.GetStr()); if (!v->Visible()) v->Visible(true); v->Invalidate(); Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // non-pourable } } } for (int i=0; iMsg()) { case M_CLOSE: { if (OnRequestClose(false)) { Quit(); return 0; } break; } } return LView::OnEvent(m); } bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority) { bool Status = false; if (Target && EventType) { int i = d->GetHookIndex(Target, true); if (i >= 0) { d->Hooks[i].Flags = EventType; Status = true; } } return Status; } bool LWindow::UnregisterHook(LView *Target) { int i = d->GetHookIndex(Target); if (i >= 0) { d->Hooks.DeleteAt(i); return true; } return false; } void LWindow::OnFrontSwitch(bool b) { } LViewI *LWindow::GetFocus() { return NULL; } void LWindow::SetFocus(LViewI *ctrl, FocusType type) { } void LWindow::SetDragHandlers(bool On) { } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { int Result = RClick.Float(this, m.x, m.y); OnTrayMenuResult(Result); } } } void LWindow::SetAlwaysOnTop(bool b) { }