diff --git a/include/lgi/common/ClipBoard.h b/include/lgi/common/ClipBoard.h --- a/include/lgi/common/ClipBoard.h +++ b/include/lgi/common/ClipBoard.h @@ -1,98 +1,98 @@ /// \file #ifndef _GCLIPBOARD_H #define _GCLIPBOARD_H #include /// Clipboard API class LgiClass LClipBoard { class LClipBoardPriv *d; LView *Owner; bool Open; LAutoPtr pDC; #if LGI_COCOA LString Txt; #else LAutoString Txt; #endif LAutoWString wTxt; public: /// On windows, this equates to a CF_TEXT, CF_BITMAP, CF_DIB type #define typedef uint32_t FormatType; - typedef std::function, LString)> BitmapCb; + typedef std::function Img, LString Err)> BitmapCb; + typedef std::function BinaryCb; + typedef std::function FilesCb; static LString FmtToStr(FormatType Fmt); static FormatType StrToFmt(LString Fmt); /// Creates the clipboard access object. LClipBoard(LView *o); ~LClipBoard(); bool IsOpen() { return Open; } LClipBoard &operator =(LClipBoard &c); /// Empties the clipboard of it's current content. bool Empty(); bool EnumFormats(LArray &Formats); // Text bool Text(const char *Str, bool AutoEmpty = true); char *Text(); // ptr returned is still owned by this object bool TextW(const char16 *Str, bool AutoEmpty = true); char16 *TextW(); // ptr returned is still owned by this object // HTML bool Html(const char *doc, bool AutoEmpty = true); LString Html(); // Bitmap bool Bitmap(LSurface *pDC, bool AutoEmpty = true); - bool Bitmap(BitmapCb Callback); + void Bitmap(BitmapCb Callback); // Files - LString::Array Files(); + void Files(FilesCb Callback); bool Files(LString::Array &Paths, bool AutoEmpty = true); // Binary bool Binary(FormatType Format, uint8_t *Ptr, ssize_t Len, bool AutoEmpty); // Set - bool Binary(FormatType Format, LAutoPtr &Ptr, ssize_t *Len); // Get - + void Binary(FormatType Format, BinaryCb Callback); // Get - [[deprecated]] LAutoPtr Bitmap(); #if WINNATIVE - LAutoPtr ConvertFromPtr(void *Ptr); + LAutoPtr ConvertFromPtr(void *Ptr); #elif defined(LINUX) - void FreeImage(unsigned char *pixels); + void FreeImage(unsigned char *pixels); #endif }; #ifdef __OBJC__ extern NSString *const LBinaryDataPBoardType; @interface LBinaryData : NSObject { } @property(assign) NSData *data; - (id)init:(LString)format ptr:(uchar*)ptr len:(ssize_t)Len; - (id)init:(NSData*)data; - (bool)getData:(LString*)format data:(LAutoPtr*)Ptr len:(ssize_t*)Len var:(LVariant*)var; // Writer - (nullable id)pasteboardPropertyListForType:(NSString *)type; - (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard; // Reader + (NSPasteboardReadingOptions)readingOptionsForType:(NSString *)type pasteboard:(NSPasteboard *)pasteboard; + (NSArray *)readableTypesForPasteboard:(NSPasteboard *)pasteboard; @end #endif #endif diff --git a/include/lgi/common/LgiDefs.h b/include/lgi/common/LgiDefs.h --- a/include/lgi/common/LgiDefs.h +++ b/include/lgi/common/LgiDefs.h @@ -1,589 +1,590 @@ /** \file \author Matthew Allen \date 24/9/1999 \brief Defines and types Copyright (C) 1999-2004, Matthew Allen */ #ifndef _LGIDEFS_H_ #define _LGIDEFS_H_ #ifdef HAIKU #include #endif #include "LgiInc.h" #if defined(WIN32) && defined(__GNUC__) #define PLATFORM_MINGW #endif #include // Unsafe typedefs, for backward compatibility typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; typedef unsigned long ulong; // Length safe typedesf, use these in new code #ifdef _MSC_VER /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed __int64 int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned __int64 uint64; #ifndef _WCHAR_T_DEFINED #include #endif #pragma warning(error:4263) #ifdef _WIN64 typedef signed __int64 ssize_t; #else typedef signed int ssize_t; #endif #elif defined(LINUX) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef int64_t int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef uint64_t uint64; #elif !defined(HAIKU) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed long long int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned long long uint64; #endif #ifndef __cplusplus #include #if defined(_MSC_VER) && _MSC_VER<1800 typedef unsigned char bool; #define true 1 #define false 0 #else #include #endif #endif /// \brief Wide unicode char /// /// This is 16 bits on Win32 and Mac, but 32 bits on unix platforms. There are a number /// of wide character string function available for manipulating wide char strings. /// /// Firstly to convert to and from utf-8 there is: ///
    ///
  • Utf8ToWide() ///
  • WideToUtf8() ///
/// /// Wide versions of standard library functions are available: ///
    ///
  • StrchrW() ///
  • StrrchrW() ///
  • StrnchrW() ///
  • StrstrW() ///
  • StristrW() ///
  • StrnstrW() ///
  • StrnistrW() ///
  • StrcmpW() ///
  • StricmpW() ///
  • StrncmpW() ///
  • StrnicmpW() ///
  • StrcpyW() ///
  • StrncpyW() ///
  • StrlenW() ///
  • StrcatW() ///
  • HtoiW() ///
  • NewStrW() ///
  • TrimStrW() ///
  • ValidStrW() ///
  • MatchStrW() ///
#include typedef wchar_t char16; #if !WINNATIVE #ifdef UNICODE typedef char16 TCHAR; #ifndef _T #define _T(arg) L##arg #endif #else typedef char TCHAR; #ifndef _T #define _T(arg) arg #endif #endif #endif #if defined(_MSC_VER) #if _MSC_VER >= 1400 #ifdef _WIN64 typedef __int64 NativeInt; typedef unsigned __int64 UNativeInt; #else typedef _W64 int NativeInt; typedef _W64 unsigned int UNativeInt; #endif #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #else #if __LP64__ typedef int64 NativeInt; typedef uint64 UNativeInt; #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #endif /// Generic pointer to any base type. Used when addressing continuous data of /// different types. typedef union { int8_t *s8; uint8_t *u8; int16_t *s16; uint16_t *u16; int32_t *s32; uint32_t *u32; int64 *s64; uint64 *u64; NativeInt *ni; UNativeInt *uni; char *c; char16 *w; float *f; double *d; #ifdef __cplusplus bool *b; #else unsigned char *b; #endif void **vp; int i; } LPointer; // Basic macros #define limit(i,l,u) (((i)<(l)) ? (l) : (((i)>(u)) ? (u) : (i))) // #define makelong(a, b) ((a)<<16 | (b&0xFFFF)) // #define loword(a) (a&0xFFFF) // #define hiword(a) (a>>16) #undef ABS #ifdef __cplusplus template inline T ABS(T v) { if (v < 0) return -v; return v; } #else #define ABS(v) ((v) < 0 ? -(v) : (v)) #endif /// Returns true if 'c' is an ascii character #define IsAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) /// Returns true if 'c' is a digit (number) #define IsDigit(c) ((c) >= '0' && (c) <= '9') /// Returns true if 'c' is a hexadecimal digit #define IsHexDigit(c) ( \ ((c) >= '0' && (c) <= '9') || \ ((c) >= 'a' && (c) <= 'f') || \ ((c) >= 'A' && (c) <= 'F') \ ) // Byte swapping #define LgiSwap16(a) ( (((a) & 0xff00) >> 8) | \ (((a) & 0x00ff) << 8) ) #define LgiSwap32(a) ( (((a) & 0xff000000) >> 24) | \ (((a) & 0x00ff0000) >> 8) | \ (((a) & 0x0000ff00) << 8) | \ (((a) & 0x000000ff) << 24) ) #ifdef __GNUC__ #define LgiSwap64(a) ( (((a) & 0xff00000000000000LLU) >> 56) | \ (((a) & 0x00ff000000000000LLU) >> 40) | \ (((a) & 0x0000ff0000000000LLU) >> 24) | \ (((a) & 0x000000ff00000000LLU) >> 8) | \ (((a) & 0x00000000ff000000LLU) << 8) | \ (((a) & 0x0000000000ff0000LLU) << 24) | \ (((a) & 0x000000000000ff00LLU) << 40) | \ (((a) & 0x00000000000000ffLLU) << 56) ) #else #define LgiSwap64(a) ( (((a) & 0xff00000000000000) >> 56) | \ (((a) & 0x00ff000000000000) >> 40) | \ (((a) & 0x0000ff0000000000) >> 24) | \ (((a) & 0x000000ff00000000) >> 8) | \ (((a) & 0x00000000ff000000) << 8) | \ (((a) & 0x0000000000ff0000) << 24) | \ (((a) & 0x000000000000ff00) << 40) | \ (((a) & 0x00000000000000ff) << 56) ) #endif #ifdef __cplusplus // Define a way of creating a 4 character code string literal as an int. constexpr uint32_t Lgi4CC( char const p[5] ) { return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; } #endif // Asserts +#ifndef LAssert LgiFunc void _lgi_assert(bool b, const char *test, const char *file, int line); #ifdef _DEBUG #define LAssert(b) _lgi_assert(b, #b, __FILE__, __LINE__) #else #define LAssert(b) ((void)0) #endif - +#endif // Good ol NULLy #ifndef NULL #define NULL 0 #endif // Slashes and quotes #define IsSlash(c) (((c)=='/')||((c)=='\\')) #define IsQuote(c) (((c)=='\"')||((c)=='\'')) // Some objectish ones #define ZeroObj(obj) memset(&obj, 0, sizeof(obj)) #ifndef CountOf #define CountOf(array) (sizeof(array)/sizeof(array[0])) #endif #ifdef __cplusplus template void LSwap(T &a, T &b) { T tmp = a; a = b; b = tmp; } #endif #ifndef MEMORY_DEBUG #define DeleteObj(obj) if (obj) { delete obj; obj = 0; } #define DeleteArray(obj) if (obj) { delete [] obj; obj = 0; } #endif // Flags #define SetFlag(i, f) (i) |= (f) #define ClearFlag(i, f) (i) &= ~(f) #define TestFlag(i, f) (((i) & (f)) != 0) // Defines /// Enum of all the operating systems we might be running on. enum LgiOs { /// \brief Unknown OS /// \sa LGetOs LGI_OS_UNKNOWN = 0, /// \brief Windows 95, 98[se] or ME. (Not supported) /// \sa LGetOs LGI_OS_WIN9X, /// \brief Windows 10+ 32bit. (Not supported) /// \sa LGetOs LGI_OS_WIN32, /// \brief Windows 10+ 64bit. (Supported) /// \sa LGetOs LGI_OS_WIN64, /// \brief BeOS/Haiku. (Somewhat supported) /// \sa LGetOs LGI_OS_HAIKU, /// \brief Linux. (Kernels v2.4 and up supported) /// \sa LGetOs LGI_OS_LINUX, /// \brief Mac OS X 10.15 or later. (Supported) /// \sa LGetOs LGI_OS_MAC_OS_X, /// One higher than the maximum OS define LGI_OS_MAX, }; // Edge types enum LEdge { EdgeNone, EdgeXpSunken, EdgeXpRaised, EdgeXpChisel, EdgeXpFlat, EdgeWin7FocusSunken, EdgeWin7Sunken, }; #define DefaultSunkenEdge EdgeWin7Sunken #define DefaultRaisedEdge EdgeXpRaised // Cursors enum LCursor { /// Blank/invisible cursor LCUR_Blank, /// Normal arrow LCUR_Normal, /// Upwards arrow LCUR_UpArrow, /// Downwards arrow LCUR_DownArrow, /// Left arrow LCUR_LeftArrow, /// Right arrow LCUR_RightArrow, /// Crosshair LCUR_Cross, /// Hourglass/watch LCUR_Wait, /// Ibeam/text entry LCUR_Ibeam, /// Vertical resize (|) LCUR_SizeVer, /// Horizontal resize (-) LCUR_SizeHor, /// Diagonal resize (/) LCUR_SizeBDiag, /// Diagonal resize (\) LCUR_SizeFDiag, /// All directions resize LCUR_SizeAll, /// Vertical splitting LCUR_SplitV, /// Horziontal splitting LCUR_SplitH, /// A pointing hand LCUR_PointingHand, /// A slashed circle LCUR_Forbidden, /// Copy Drop LCUR_DropCopy, /// Copy Move LCUR_DropMove, }; // General Event Flags #define LGI_EF_LCTRL (1 << 0) #define LGI_EF_RCTRL (1 << 1) #define LGI_EF_CTRL (LGI_EF_LCTRL | LGI_EF_RCTRL) #define LGI_EF_LALT (1 << 2) #define LGI_EF_RALT (1 << 3) #define LGI_EF_ALT (LGI_EF_LALT | LGI_EF_RALT) #define LGI_EF_LSHIFT (1 << 4) #define LGI_EF_RSHIFT (1 << 5) #define LGI_EF_SHIFT (LGI_EF_LSHIFT | LGI_EF_RSHIFT) #define LGI_EF_DOWN (1 << 6) #define LGI_EF_DOUBLE (1 << 7) #define LGI_EF_CAPS_LOCK (1 << 8) #define LGI_EF_IS_CHAR (1 << 9) #define LGI_EF_IS_NOT_CHAR (1 << 10) #define LGI_EF_SYSTEM (1 << 11) // Windows key/Apple key etc // Mouse Event Flags #define LGI_EF_LEFT (1 << 16) #define LGI_EF_MIDDLE (1 << 17) #define LGI_EF_RIGHT (1 << 18) #define LGI_EF_MOVE (1 << 19) #define LGI_EF_XBTN1 (1 << 20) #define LGI_EF_XBTN2 (1 << 21) // Emit compiler warnings #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning: " // To use just do #pragma message(__LOC__"My warning message") // Simple definition of breakable unicode characters #define LGI_BreakableChar(c) ( \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' || \ ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) // Os metrics enum LSystemMetric { /// Get the standard window horizontal border size /// \sa LApp::GetMetric() LGI_MET_DECOR_X = 1, /// Get the standard window vertical border size including caption bar. /// \sa LApp::GetMetric() LGI_MET_DECOR_Y, /// Get the standard window caption bar height only. /// \sa LApp::GetMetric() LGI_MET_DECOR_CAPTION, /// Get the height of a single line menu bar /// \sa LApp::GetMetric() LGI_MET_MENU, /// This is non-zero if the system is theme aware LGI_MET_THEME_AWARE, /// Size of a window's shadow LGI_MET_WINDOW_SHADOW, }; /// \brief Types of system paths available for querying /// \sa LgiGetSystemPath enum LSystemPath { LSP_ROOT, /// The location of the operating system folder /// [Win32] = e.g. C:\Windows /// [Mac] = /System /// [Linux] /boot LSP_OS, /// The system library folder /// [Win32] = e.g. C:\Windows\System32 /// [Mac] = /Library /// [Linux] = /usr/lib LSP_OS_LIB, /// A folder for storing temporary files. These files are usually /// deleted automatically later by the system. /// [Win32] = ~\Local Settings\Temp /// [Mac] = ~/Library/Caches/TemporaryItems /// [Linux] = /tmp LSP_TEMP, /// System wide application data /// [Win32] = ~\..\All Users\Application Data /// [Mac] = /System/Library /// [Linux] = /usr LSP_COMMON_APP_DATA, /// User specific application data /// [Win32] = ~\Application Data /// [Mac] = ~/Library /// [Linux] = /usr /// [Haiku] = ~/config (???) LSP_USER_APP_DATA, /// Machine + user specific application data (probably should not use) /// [Win32] = ~\Local Settings\Application Data /// [Mac] = ~/Library /// [Linux] = /usr/local LSP_LOCAL_APP_DATA, /// Desktop dir /// i.e. ~/Desktop LSP_DESKTOP, /// Home dir /// i.e. ~ LSP_HOME, /// Application install folder: /// [Win] c:\Program Files /// [Mac] /Applications /// [Linux] /usr/bin LSP_USER_APPS, /// The running application's path. /// [Mac] This doesn't include the "Contents/MacOS" part of the path inside the bundle. LSP_EXE, /// The system trash folder. LSP_TRASH, /// The app's install folder. /// [Win32] = $ExePath (sans '\Release' or '\Debug') /// [Mac/Linux] = $ExePath /// Where $ExePath is the folder the application is running from LSP_APP_INSTALL, /// The app's root folder (Where config and data should be stored) /// LOptionsFile uses LSP_APP_ROOT as the default location. /// [Win32] = ~\Application Data\Roaming\$AppName /// [Mac] = ~/Library/$AppName /// [Linux] = ~/.$AppName /// Where $AppName = LApp::GetName. /// If the given folder doesn't exist it will be created. LSP_APP_ROOT, /// This is the user's documents folder /// [Win32] ~\My Documents /// [Mac] ~\Documents LSP_USER_DOCUMENTS, /// This is the user's music folder /// [Win32] ~\My Music /// [Mac] ~\Music LSP_USER_MUSIC, /// This is the user's video folder /// [Win32] ~\My Videos /// [Mac] ~\Movies LSP_USER_VIDEO, /// This is the user's download folder /// ~\Downloads LSP_USER_DOWNLOADS, /// This is the user's links folder /// [Win32] = ~\Links /// [Mac] = ??? /// [Linux] = ??? LSP_USER_LINKS, /// User's pictures/photos folder LSP_USER_PICTURES, /// [Win32] = C:\Users\%HOME%\Pictures /// [Mac] = ??? /// [Linux] = ~\Pictures LSP_MOUNT_POINT, }; #ifdef _DEBUG #define DeclDebugArgs , const char *_file, int _line #define PassDebugArgs , __FILE__, __LINE__ #else #define DeclDebugArgs #define PassDebugArgs #endif #define _FL __FILE__, __LINE__ #define CALL_MEMBER_FN(obj, memFn) ((obj).*(memFn)) #include "lgi/common/AutoPtr.h" #endif diff --git a/include/lgi/common/StructuredIo.h b/include/lgi/common/StructuredIo.h --- a/include/lgi/common/StructuredIo.h +++ b/include/lgi/common/StructuredIo.h @@ -1,337 +1,339 @@ #ifndef _STRUCTURED_IO_H_ #define _STRUCTURED_IO_H_ #include #include "lgi/common/Variant.h" #include "lgi/common/LMallocArray.h" #define DEBUG_STRUCT_IO 0 /* Generic base data field: (where size_t == EncSize/DecSize field) uint8_t type; // LVariantType size_t name_len; char name[name_len]; size_t data_size; uint8_t data[data_size]; Objects are wrapped with generic fields of the type GV_CUSTOM // start of object... ...array of member fields.... GV_VOID_PTR // end of object */ class LStructuredIo : public LMallocArray { bool Write = true; size_t Pos = 0; protected: bool CheckSpace(size_t bytes) { auto l = Pos + bytes; if (l > Length()) { if (!Length((l + 255) & ~0xff)) return false; LAssert(Length() >= l); } return true; } constexpr static int SevenMask = 0x7f; template void EncSize(LPointer &p, T sz) { if (!sz) *p.u8++ = 0; else while (sz) { uint8_t bits = sz & SevenMask; sz >>= 7; *p.u8++ = bits | (sz ? 0x80 : 0); } } template void DecSize(LPointer &p, T &sz) { sz = 0; uint8_t bits, shift = 0; do { bits = *p.u8++; sz |= ((T)bits & SevenMask) << shift; shift += 7; } while (bits & 0x80); } bool Encode(uint8_t type, const void *obj = NULL, size_t sz = 0, const char *name = NULL) { LPointer p; LAssert(Write); auto name_len = Strlen(name); if (!CheckSpace(1 + 8 + name_len + 8 + sz)) return false; p.u8 = AddressOf(Pos); #if DEBUG_STRUCT_IO auto type_addr = p.u8 - AddressOf(); #endif *p.u8++ = type; EncSize(p, name_len); if (name) { memcpy(p.u8, name, name_len); p.u8 += name_len; } EncSize(p, sz); #if DEBUG_STRUCT_IO auto data_addr = p.u8 - AddressOf(); #endif if (obj) { memcpy(p.u8, obj, sz); p.u8 += sz; } else LAssert(sz == 0); Pos = p.u8 - AddressOf(); #if DEBUG_STRUCT_IO LgiTrace("Encode(%i @ %i,%i sz=%i) after=%i\n", type, (int)type_addr, (int)data_addr, (int)sz, (int)Pos); #endif return true; } public: constexpr static LVariantType StartObject = GV_CUSTOM; constexpr static LVariantType EndObject = GV_VOID_PTR; constexpr static LVariantType EndRow = GV_NULL; LStructuredIo(bool write) : Write(write) { } bool GetWrite() { return Write; } size_t GetPos() { return Pos; } void Bool(bool &b, const char *name = NULL) { Encode(GV_BOOL, &b, sizeof(b), name); } template void Int(T &i, const char *name = NULL) { Encode(GV_INT64, &i, sizeof(i), name); } template void Float(T &f, const char *name = NULL) { Encode(GV_DOUBLE, &f, sizeof(f), name); } template void String(T *str, ssize_t sz = -1, const char *name = NULL) { if (sz < 0) sz = Strlen(str); Encode(sizeof(*str) > 1 ? GV_WSTRING : GV_STRING, str, sz * sizeof(T), name); } void String(LString &str, const char *name = NULL) { Encode(GV_STRING, str.Get(), str.Length(), name); } template void Binary(T *data, size_t elements, const char *name = NULL) { if (!data || elements == 0) return; Encode(GV_BINARY, data, sizeof(*data)*elements, name); } struct ObjRef { LStructuredIo *io; ObjRef(ObjRef &&r) : io(NULL) { LSwap(io, r.io); } ObjRef(LStructuredIo *parent) : io(parent) { } ~ObjRef() { if (io) io->Encode(EndObject); } }; ObjRef StartObj(const char *name) { ObjRef r(this); Encode(StartObject, NULL, 0, name); return r; } bool Decode(std::function callback, Progress *prog = NULL) { if (Length() == 0) return false; LPointer p; auto end = AddressOf()+Length(); p.u8 = AddressOf(Pos); #define CHECK_EOF(sz) if (p.u8 + sz > end) return false CHECK_EOF(1); #if DEBUG_STRUCT_IO auto type_addr = p.u8 - AddressOf(); #endif LVariantType type = (LVariantType)(*p.u8++); if (type >= GV_MAX) return false; if (type == EndRow) { - callback(type, 0, NULL, NULL); + if (callback) + callback(type, 0, NULL, NULL); Pos = p.u8 - AddressOf(); return true; } size_t name_len, data_size; DecSize(p, name_len); CHECK_EOF(name_len); LString name(p.c, name_len); p.s8 += name_len; DecSize(p, data_size); CHECK_EOF(data_size); #if DEBUG_STRUCT_IO auto data_addr = p.u8 - AddressOf(); #endif - callback(type, data_size, p.u8, name); + if (callback) + callback(type, data_size, p.u8, name); p.u8 += data_size; Pos = p.u8 - AddressOf(); #if DEBUG_STRUCT_IO LgiTrace("Decode(%i @ %i,%i sz=%i) after=%i\n", type, (int)type_addr, (int)data_addr, (int)data_size, (int)Pos); #endif return true; } bool Flush(LStream *s) { if (!s || !Write || Length() == 0) return false; (*this)[Pos++] = EndRow; bool Status = s->Write(AddressOf(), Pos) == Pos; Pos = 0; return Status; } }; #define IntIo(type) inline void StructIo(LStructuredIo &io, type i) { io.Int(i); } #define StrIo(type) inline void StructIo(LStructuredIo &io, type i) { io.String(i); } IntIo(char) IntIo(unsigned char) IntIo(short) IntIo(unsigned short) IntIo(int) IntIo(unsigned int) IntIo(int64_t) IntIo(uint64_t) IntIo(size_t) IntIo(ssize_t) StrIo(char*); StrIo(const char*); StrIo(wchar_t*); StrIo(const wchar_t*); inline void StructIo(LStructuredIo &io, LString &s) { if (io.GetWrite()) io.String(s.Get(), s.Length()); else io.Decode([&s](auto type, auto sz, auto ptr, auto name) { if (type == GV_STRING && ptr && sz > 0) s.Set((char*)ptr, sz); }); } inline void StructIo(LStructuredIo &io, LStringPipe &p) { // auto obj = io.StartObj("LStringPipe"); if (io.GetWrite()) { p.Iterate([&io](auto ptr, auto bytes) { io.String(ptr, bytes); return true; }); } else { io.Decode([&p](auto type, auto sz, auto ptr, auto name) { if (type == GV_STRING && ptr && sz > 0) p.Write(ptr, sz); }); } } inline void StructIo(LStructuredIo &io, LRect &r) { auto obj = io.StartObj("LRect"); io.Int(r.x1, "x1"); io.Int(r.y1, "y1"); io.Int(r.x2, "x2"); io.Int(r.y2, "y2"); } inline void StructIo(LStructuredIo &io, LColour &c) { auto obj = io.StartObj("LColour"); LString cs; uint8_t r, g, b, a; if (io.GetWrite()) { cs = LColourSpaceToString(c.GetColourSpace()); r = c.r(); g = c.g(); b = c.b(); a = c.a(); } io.String(cs, "colourSpace"); io.Int(r, "r"); io.Int(g, "g"); io.Int(b, "b"); io.Int(a, "a"); if (!io.GetWrite()) { c.SetColourSpace(LStringToColourSpace(cs)); c.r(r); c.g(g); c.b(b); c.a(a); } } #endif diff --git a/include/lgi/common/StructuredLog.h b/include/lgi/common/StructuredLog.h --- a/include/lgi/common/StructuredLog.h +++ b/include/lgi/common/StructuredLog.h @@ -1,167 +1,201 @@ #pragma once #include #include "lgi/common/File.h" #ifndef _STRUCTURED_IO_H_ #include "lgi/common/StructuredIo.h" #endif class LStructuredLog { LFile f; LStructuredIo io; template void Store(T &t) { StructIo(io, t); } public: LStructuredLog(const char *FileName, bool write = true) : io(write) { LString fn; if (LIsRelativePath(FileName)) { LFile::Path p(LSP_APP_INSTALL); p += FileName; fn = p.GetFull(); } else fn = FileName; if (f.Open(fn, write ? O_WRITE : O_READ) && write) f.SetSize(0); } virtual ~LStructuredLog() { io.Flush(&f); } LStructuredIo &GetIo() { return io; } // Write objects to the log. Custom types will need to have a StructIo implementation template void Log(Args&&... args) { if (!io.GetWrite()) { LAssert(!"Not open for writing."); return; } int dummy[] = { 0, ( (void) Store(std::forward(args)), 0) ... }; io.Flush(&f); } // Read and decode to string objects. bool Read(std::function callback, Progress *prog = NULL) { if (io.GetWrite()) { LAssert(!"Not open for reading."); return false; } if (io.GetPos() >= io.Length()) { if (!io.Length(f.GetSize())) return false; if (prog) prog->SetRange(f.GetSize()); for (int64_t i=0; iValue(i); if (prog->IsCancelled()) return false; } } } if (prog && prog->IsCancelled()) return false; return io.Decode(callback, prog); } + bool Read(int64_t &i) + { + bool typeErr = false; + Read([&](auto type, auto sz, auto ptr, auto name) + { + switch (type) + { + case GV_INT32: + { + LAssert(sz == 4); + i = *(int32_t*)ptr; + break; + } + case GV_INT64: + { + if (sz == 4) + i = *(int32_t*)ptr; + else if (sz == 8) + i = *(int64_t*)ptr; + else + LAssert(!"Unknown size"); + break; + } + default: + { + typeErr = true; + break; + } + } + }); + + return !typeErr; + } + // Read and convert to a string. - void Read(std::function callback) + bool Read(LString &s) { LStringPipe p; while (Read([&p](auto type, auto sz, auto ptr, auto name) { LString prefix; if (type == LStructuredIo::EndRow) return; if (p.GetSize()) prefix = " "; if (name) { prefix += name; prefix += "="; } switch (type) { case GV_STRING: { p.Print("%s%.*s", prefix ? prefix.Get() : "", (int)sz, ptr); break; } case GV_INT64: { if (sz > 4) p.Print("%s" LPrintfInt64, prefix ? prefix.Get() : "", *((int64*)ptr)); else p.Print("%s%i", prefix ? prefix.Get() : "", *((int*)ptr)); break; } case LStructuredIo::StartObject: { p.Print("%s {", name); break; } case LStructuredIo::EndObject: { p.Print(" }"); break; } default: { LAssert(!"Impl me."); break; } } })); - callback(p.NewLStr()); + s = p.NewLStr(); + return s != NULL; } static void UnitTest() { auto fn = "my-test-log.struct"; { LStructuredLog Log(fn); LString asd = LString("1234568,"); int ert = 123; LRect rc(10, 10, 200, 100); Log.Log("asd:", asd, rc, ert); } { LStructuredLog Rd(fn, false); - Rd.Read([](LString s) - { + LString s; + if (Rd.Read(s)) LgiTrace("%s\n", s.Get()); - }); } } }; diff --git a/include/lgi/common/Variant.h b/include/lgi/common/Variant.h --- a/include/lgi/common/Variant.h +++ b/include/lgi/common/Variant.h @@ -1,474 +1,474 @@ /** \file \author Matthew Allen \brief Variant class.\n Copyright (C), Matthew Allen */ #ifndef __LVariant_H__ #define __LVariant_H__ #undef Bool #include "lgi/common/DateTime.h" #include "lgi/common/Containers.h" #include "lgi/common/HashTable.h" #include "lgi/common/LgiString.h" class LCompiledCode; #if !defined(_MSC_VER) && !defined(LINUX) && (defined(LGI_64BIT) || defined(MAC)) && !defined(HAIKU) #define LVARIANT_SIZET 1 #define LVARIANT_SSIZET 1 #endif /// The different types the varient can be. /// \sa LVariant::TypeToString to convert to string. enum LVariantType { // Main types - /// Null type + /// Null type (0) GV_NULL, - /// 32-bit integer + /// 32-bit integer (1) GV_INT32, - /// 64-bit integer + /// 64-bit integer (2) GV_INT64, - /// true or false boolean. + /// true or false boolean. (3) GV_BOOL, - /// C++ double + /// C++ double (4) GV_DOUBLE, - /// Null terminated string value + /// Null terminated string value (5) GV_STRING, - /// Block of binary data + /// Block of binary data (6) GV_BINARY, - /// List of LVariant + /// List of LVariant (7) GV_LIST, - /// Pointer to LDom object + /// Pointer to LDom object (8) GV_DOM, - /// DOM reference, ie. a variable in a DOM object + /// DOM reference, ie. a variable in a DOM object (9) GV_DOMREF, - /// Untyped pointer + /// Untyped pointer (10) GV_VOID_PTR, - /// LDateTime class. + /// LDateTime class. (11) GV_DATETIME, - /// Hash table class, containing pointers to LVariants + /// Hash table class, containing pointers to LVariants (12) GV_HASHTABLE, - // Scripting language operator + // Scripting language operator (13) GV_OPERATOR, - // Custom scripting lang type + // Custom scripting lang type (14) GV_CUSTOM, - // Wide string + // Wide string (15) GV_WSTRING, - // LSurface ptr + // LSurface ptr (16) GV_LSURFACE, - /// Pointer to LView + /// Pointer to LView (17) GV_GVIEW, - /// Pointer to LMouse + /// Pointer to LMouse (18) GV_LMOUSE, - /// Pointer to LKey + /// Pointer to LKey (19) GV_LKEY, - /// Pointer to LStream + /// Pointer to LStream (20) GV_STREAM, - /// The maximum value for the variant type. + /// The maximum value for the variant type. (21) /// (This is used by the scripting engine to refer to a LVariant itself) GV_MAX, }; /// Language operators enum LOperator { OpNull, OpAssign, OpPlus, OpUnaryPlus, OpMinus, OpUnaryMinus, OpMul, OpDiv, OpMod, OpLessThan, OpLessThanEqual, OpGreaterThan, OpGreaterThanEqual, OpEquals, OpNotEquals, OpPlusEquals, OpMinusEquals, OpMulEquals, OpDivEquals, OpPostInc, OpPostDec, OpPreInc, OpPreDec, OpAnd, OpOr, OpNot, }; class LgiClass LCustomType : public LDom { protected: struct CustomField : public LDom { ssize_t Offset; ssize_t Bytes; ssize_t ArrayLen; LVariantType Type; LString Name; LCustomType *Nested; ssize_t Sizeof(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; public: struct Method : public LDom { LString Name; LArray Params; size_t Address; int FrameSize; Method() { Address = -1; FrameSize = -1; } }; protected: // Global vars int Pack; size_t Size; LString Name; // Fields LArray Flds; LHashTbl, int> FldMap; // Methods LArray Methods; LHashTbl, Method*> MethodMap; // Private methods ssize_t PadSize(); public: LCustomType(const char *name, int pack = 1); LCustomType(const char16 *name, int pack = 1); ~LCustomType(); size_t Sizeof(); const char *GetName() { return Name; } ssize_t Members() { return Flds.Length(); } int AddressOf(const char *Field); int IndexOf(const char *Field); bool DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen = 1); bool DefineField(const char *Name, LCustomType *Type, int ArrayLen = 1); Method *DefineMethod(const char *Name, LArray &Params, size_t Address); Method *GetMethod(const char *Name); // Field access. You can't use the LDom interface to get/set member variables because // there is no provision for the 'This' pointer. bool Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex = 0); bool Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex = 0); // Dom access. However the DOM can be used to access information about the type itself. // Which doesn't need a 'This' pointer. bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool CallMethod(const char *MethodName, LScriptArguments &Args); }; /// A class that can be different types class LgiClass LVariant { public: typedef LHashTbl,LVariant*> LHash; /// The type of the variant LVariantType Type; /// The value of the variant union { /// Valid when Type == #GV_INT32 int Int; /// Valid when Type == #GV_BOOL bool Bool; /// Valid when Type == #GV_INT64 int64 Int64; /// Valid when Type == #GV_DOUBLE double Dbl; /// Valid when Type == #GV_STRING char *String; /// Valid when Type == #GV_WSTRING char16 *WString; /// Valid when Type == #GV_DOM LDom *Dom; /// Valid when Type is #GV_VOID_PTR, #GV_GVIEW, #GV_LMOUSE or #GV_LKEY void *Ptr; /// Valid when Type == #GV_BINARY struct _Binary { ssize_t Length; void *Data; } Binary; /// Valid when Type == #GV_LIST List *Lst; /// Valid when Type == #GV_HASHTABLE LHash *Hash; /// Valid when Type == #GV_DATETIME LDateTime *Date; /// Valid when Type == #GV_CUSTOM struct _Custom { LCustomType *Dom; uint8_t *Data; bool operator == (_Custom &c) { return Dom == c.Dom && Data == c.Data; } } Custom; /// Valid when Type == #GV_DOMREF struct _DomRef { /// The pointer to the dom object LDom *Dom; /// The name of the variable to set/get in the dom object char *Name; } DomRef; /// Valid when Type == #GV_OPERATOR LOperator Op; /// Valid when Type == #GV_LSURFACE struct { class LSurface *Ptr; bool Own; LSurface *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Surface; /// Valid when Type == #GV_STREAM struct { class LStreamI *Ptr; bool Own; LStreamI *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Stream; /// Valid when Type == #GV_GVIEW class LView *View; /// Valid when Type == #GV_LMOUSE class LMouse *Mouse; /// Valid when Type == #GV_LKEY class LKey *Key; } Value; /// Constructor to null LVariant(); /// Constructor for integers LVariant(int32_t i); LVariant(uint32_t i); LVariant(int64_t i); LVariant(uint64_t i); #if LVARIANT_SIZET LVariant(size_t i); #endif #if LVARIANT_SSIZET LVariant(ssize_t i); #endif /// Constructor for double LVariant(double i); /// Constructor for string LVariant(const char *s); /// Constructor for wide string LVariant(const char16 *s); /// Constructor for ptr LVariant(void *p); /// Constructor for DOM ptr LVariant(LDom *p); /// Constructor for DOM variable reference LVariant(LDom *p, char *name); /// Constructor for date LVariant(const LDateTime *d); /// Constructor for variant LVariant(LVariant const &v); /// Constructor for operator LVariant(LOperator Op); /// Destructor ~LVariant(); /// Assign bool value LVariant &operator =(bool i); /// Assign an integer value LVariant &operator =(int32_t i); LVariant &operator =(uint32_t i); LVariant &operator =(int64_t i); LVariant &operator =(uint64_t i); #if LVARIANT_SIZET LVariant &operator =(size_t i); #endif #if LVARIANT_SSIZET LVariant &operator =(ssize_t i); #endif /// Assign double value LVariant &operator =(double i); /// Assign string value (makes a copy) LVariant &operator =(const char *s); /// Assign a wide string value (makes a copy) LVariant &operator =(const char16 *s); /// Assign another variant value LVariant &operator =(LVariant const &i); /// Assign value to a void ptr LVariant &operator =(void *p); /// Assign value to DOM ptr LVariant &operator =(LDom *p); /// Assign value to be a date/time LVariant &operator =(const LDateTime *d); LVariant &operator =(class LView *p); LVariant &operator =(class LMouse *p); LVariant &operator =(class LKey *k); LVariant &operator =(class LStream *s); bool operator ==(LVariant &v); bool operator !=(LVariant &v) { return !(*this == v); } /// Sets the value to a DOM variable reference bool SetDomRef(LDom *obj, char *name); /// Sets the value to a copy of block of binary data bool SetBinary(ssize_t Len, void *Data, bool Own = false); /// Sets the value to a copy of the list List *SetList(List *Lst = NULL); /// Sets the value to a hashtable bool SetHashTable(LHash *Table = NULL, bool Copy = true); /// Set the value to a surface bool SetSurface(class LSurface *Ptr, bool Own); /// Set the value to a stream bool SetStream(class LStreamI *Ptr, bool Own); /// Returns the string if valid (will convert a GV_WSTRING to utf) char *Str(); /// Returns the value as an LString LString LStr(); /// Returns a wide string if valid (will convert a GV_STRING to wide) char16 *WStr(); /// Returns the string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char *ReleaseStr(); /// Returns the wide string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char16 *ReleaseWStr(); /// Sets the variant to a heap string and takes ownership of it bool OwnStr(char *s); /// Sets the variant to a wide heap string and takes ownership of it bool OwnStr(char16 *s); /// Sets the variant to NULL void Empty(); /// Returns the byte length of the data int64 Length(); /// True if currently a int bool IsInt(); /// True if currently a bool bool IsBool(); /// True if currently a double bool IsDouble(); /// True if currently a string bool IsString(); /// True if currently a binary block bool IsBinary(); /// True if currently null bool IsNull(); /// Changes the variant's type, maintaining the value where possible. If /// no conversion is available then nothing happens. LVariant &Cast(LVariantType NewType); /// Casts the value to int, from whatever source type. The /// LVariant type does not change after calling this. int32 CastInt32() const; /// Casts the value to a 64 bit int, from whatever source type. The /// LVariant type does not change after calling this. int64 CastInt64() const; /// Casts the value to double, from whatever source type. The /// LVariant type does not change after calling this. double CastDouble() const; /// Cast to a string from whatever source type, the LVariant will /// take the type GV_STRING after calling this. This is because /// returning a static string is not thread safe. char *CastString(); /// Casts to a DOM ptr LDom *CastDom() const; /// Casts to a boolean. You probably DON'T want to use this function. The /// behaviour for strings -> bool is such that if the string is value it /// always evaluates to true, and false if it's not a valid string. Commonly /// what you want is to evaluate whether the string is zero or non-zero in /// which cast you should use "CastInt32() != 0" instead. bool CastBool() const; /// Returns the pointer if available. void *CastVoidPtr() const; /// Returns a LView LView *CastView() const { return Type == GV_GVIEW ? Value.View : NULL; } /// List insert bool Add(LVariant *v, int Where = -1); /// Converts the varient type to a string static const char *TypeToString(LVariantType t); /// Converts an operator to a string static const char *OperatorToString(LOperator op); /// Converts the value to a string description include type. LString ToString(); }; // General collection of arguments and a return value class LgiClass LScriptArguments : public LArray { friend class LScriptEngine; friend class LVirtualMachine; friend class LVirtualMachinePriv; friend struct ExecuteFunctionState; LVirtualMachineI *Vm = NULL; class LStream *Console = NULL; LVariant *LocalReturn = NULL; // Owned by this instance LVariant *Return = NULL; const char *File = NULL; int Line = 0; LString ExceptionMsg; ssize_t Address; public: static LStream NullConsole; LScriptArguments(LVirtualMachineI *vm, LVariant *ret = NULL, LStream *console = NULL, ssize_t address = -1); ~LScriptArguments(); LVirtualMachineI *GetVm() { return Vm; } void SetVm(LVirtualMachineI *vm) { Vm = vm; } LVariant *GetReturn() { return Return; } // Must never be NULL. LStream *GetConsole() { return Console; } bool HasException() { return File != NULL || ExceptionMsg.Get() || Line != 0; } bool Throw(const char *File, int Line, const char *Msg, ...); // Accessor shortcuts const char *StringAt(size_t i); int32_t Int32At(size_t i, int32_t Default = 0); int64_t Int64At(size_t i, int64_t Default = 0); double DoubleAt(size_t i, double Default = 0); }; #endif diff --git a/include/lgi/win/LgiOsDefs.h b/include/lgi/win/LgiOsDefs.h --- a/include/lgi/win/LgiOsDefs.h +++ b/include/lgi/win/LgiOsDefs.h @@ -1,308 +1,309 @@ // // FILE: LgiOsDefs.h (Win32) // AUTHOR: Matthew Allen // DATE: 24/9/99 // DESCRIPTION: Lgi Win32 OS defines // // Copyright (C) 1999, Matthew Allen // fret@memecode.com // #pragma once #pragma warning(disable : 4251 4275) #define WIN32GTK 0 #define WINNATIVE 1 #define LGI_VIEW_HANDLE 1 #define LGI_VIEW_HASH 0 #ifndef WINDOWS #error "Define WINDOWS in your project" #endif #ifdef _WIN64 #define LGI_64BIT 1 #ifndef WIN64 #define WIN64 1 #endif #else #define LGI_32BIT 1 #ifndef WIN32 #define WIN32 1 #endif #endif #include #include #include "lgi/common/LgiInc.h" #include "lgi/common/LgiDefs.h" #define _MSC_VER_VS2022 1930 // MSVC++ 17.0 (really all 193x values) #define _MSC_VER_VS2019 1920 // MSVC++ 16.0 (really all 192x values) #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_VS2019 #define _MSC_VER_STR "16" #define _MSC_YEAR_STR "19" #elif _MSC_VER >= _MSC_VER_VS2017 #define _MSC_VER_STR "15" #define _MSC_YEAR_STR "17" #elif _MSC_VER >= _MSC_VER_VS2015 #define _MSC_VER_STR "14" #define _MSC_YEAR_STR "15" #elif _MSC_VER >= _MSC_VER_VS2013 #define _MSC_VER_STR "12" #define _MSC_YEAR_STR "13" #elif _MSC_VER >= _MSC_VER_VS2012 #define _MSC_VER_STR "11" #define _MSC_YEAR_STR "12" #elif _MSC_VER >= _MSC_VER_VS2010 #define _MSC_VER_STR "10" #define _MSC_YEAR_STR "10" #else #define _MSC_VER_STR "9" #endif ////////////////////////////////////////////////////////////////// // Includes #define WIN32_LEAN_AND_MEAN #include "winsock2.h" #include "windows.h" #include "SHELLAPI.H" #include "COMMDLG.H" #include ////////////////////////////////////////////////////////////////// // Typedefs typedef HWND OsWindow; typedef HWND OsView; typedef HANDLE OsThread; typedef HANDLE OsProcess; typedef char16 OsChar; typedef HBITMAP OsBitmap; typedef HDC OsPainter; typedef HFONT OsFont; #if _MSC_VER <= _MSC_VER_VC6 typedef unsigned long ULONG_PTR, *PULONG_PTR; #define sprintf_s _snprintf #endif typedef BOOL (__stdcall *pSHGetSpecialFolderPathA)(HWND hwndOwner, LPSTR lpszPath, int nFolder, BOOL fCreate); typedef BOOL (__stdcall *pSHGetSpecialFolderPathW)(HWND hwndOwner, LPWSTR lpszPath, int nFolder, BOOL fCreate); typedef int (__stdcall *pSHFileOperationA)(LPSHFILEOPSTRUCTA lpFileOp); typedef int (__stdcall *pSHFileOperationW)(LPSHFILEOPSTRUCTW lpFileOp); typedef int (__stdcall *p_vscprintf)(const char *format, va_list argptr); #include "lgi/common/Message.h" class LgiClass OsAppArguments { LAutoWString CmdLine; void _Default(); public: HINSTANCE hInstance; DWORD Pid; char16 *lpCmdLine; int nCmdShow; OsAppArguments(); OsAppArguments(int Args, const char **Arg); OsAppArguments &operator =(OsAppArguments &p); void Set(char *Utf); void Set(int Args, const char **Arg); }; ////////////////////////////////////////////////////////////////// // Defines #define IsWin9x (LApp::Win9x) #define DefaultOsView(t) NULL #define GWL_LGI_MAGIC 8 #define GWL_EXTRA_BYTES 12 // Key redefs enum LgiKeys { LK_TAB = VK_TAB, LK_RETURN = VK_RETURN, LK_SPACE = VK_SPACE, LK_DELETE = VK_DELETE, LK_ESCAPE = VK_ESCAPE, LK_PAGEUP = VK_PRIOR, LK_PAGEDOWN = VK_NEXT, LK_BACKSPACE = VK_BACK, LK_F1 = VK_F1, LK_F2 = VK_F2, LK_F3 = VK_F3, LK_F4 = VK_F4, LK_F5 = VK_F5, LK_F6 = VK_F6, LK_F7 = VK_F7, LK_F8 = VK_F8, LK_F9 = VK_F9, LK_F10 = VK_F10, LK_F11 = VK_F11, LK_F12 = VK_F12, LK_LEFT = VK_LEFT, LK_RIGHT = VK_RIGHT, LK_UP = VK_UP, LK_DOWN = VK_DOWN, LK_HOME = VK_HOME, LK_END = VK_END, LK_INSERT = VK_INSERT, LK_SHIFT = VK_SHIFT, LK_ALT = VK_MENU, LK_CTRL = VK_CONTROL, LK_DECIMAL = VK_DECIMAL, LK_COMMA = VK_OEM_COMMA, // , . LK_MINUS = VK_OEM_MINUS, // - _ LK_EQUALS = VK_OEM_PLUS, // = + LK_SEMI_COLON = VK_OEM_1, // ; : LK_SLASH = VK_OEM_2, // / ? LK_TILDE = VK_OEM_3, // ~ LK_OPEN_BRACKET = VK_OEM_4, // [ { LK_BACK_SLASH = VK_OEM_5, // \ | LK_CLOSE_BRACKET = VK_OEM_6, // ] } LK_SINGLE_QUOTE = VK_OEM_7, // ' " }; // Sleep the current thread LgiFunc void LSleep(DWORD i); // Process typedef DWORD OsProcessId; LgiExtern HINSTANCE _lgi_app_instance; #define LProcessInst() _lgi_app_instance extern p_vscprintf lgi_vscprintf; #define LGetCurrentProcess() GetCurrentProcessId() // Threads typedef DWORD OsThreadId; typedef CRITICAL_SECTION OsSemaphore; #define LGetCurrentThread() GetCurrentThread() // #define GetCurrentThreadId() GetCurrentThreadId() // Socket/Network #define ValidSocket(s) ((s) != INVALID_SOCKET) typedef SOCKET OsSocket; #define LGI_GViewMagic 0x14412662 #define LGI_FileDropFormat "CF_HDROP" #define LGI_StreamDropFormat CFSTR_FILEDESCRIPTORW #define LGI_WideCharset "ucs-2" #define LPrintfInt64 "%I64i" +#define LPrintfUInt64 "%I64u" #define LPrintfHex64 "%I64x" #if LGI_64BIT #define LPrintfSizeT "%I64u" #define LPrintfSSizeT "%I64d" #else #define LPrintfSizeT "%u" #define LPrintfSSizeT "%d" #endif #define LGI_IllegalFileNameChars "\t\r\n/\\:*?\"<>|" #define MK_LEFT MK_LBUTTON #define MK_RIGHT MK_RBUTTON #define MK_MIDDLE MK_MBUTTON #define MK_CTRL MK_CONTROL // Stupid mouse wheel defines don't work. hmmm #define WM_MOUSEWHEEL 0x020A #define WHEEL_DELTA 120 #ifndef SPI_GETWHEELSCROLLLINES #define SPI_GETWHEELSCROLLLINES 104 #endif // Window flags #define GWF_VISIBLE WS_VISIBLE #define GWF_DISABLED WS_DISABLED #define GWF_BORDER WS_BORDER #define GWF_TABSTOP WS_TABSTOP #define GWF_FOCUS 0x00000001 #define GWF_OVER 0x00000002 #define GWF_DROP_TARGET 0x00000004 #define GWF_SUNKEN 0x00000008 #define GWF_FLAT 0x00000010 #define GWF_RAISED 0x00000020 #define GWF_DIALOG 0x00000040 #define GWF_DESTRUCTOR 0x00000080 #define GWF_QUIT_WND 0x00000100 #define GWF_SYS_BORDER 0x00000200 // ask the system to draw the border // Widgets #define DialogToPixelX(i) (((i)*Bx)/4) #define DialogToPixelY(i) (((i)*By)/8) #define PixelToDialogX(i) (((i)*4)/Bx) #define PixelToDialogY(i) (((i)*8)/By) #define DIALOG_X 1.56 #define DIALOG_Y 1.85 #define CTRL_X 1.50 #define CTRL_Y 1.64 // Directories #define DIR_CHAR '\\' #define DIR_STR "\\" #define EOL_SEQUENCE "\r\n" #define LGI_PATH_SEPARATOR ";" #define LGI_ALL_FILES "*.*" #define LGI_LIBRARY_EXT "dll" #define LGI_EXECUTABLE_EXT ".exe" ///////////////////////////////////////////////////////////////////////////////////// // Typedefs typedef HWND OsView; ///////////////////////////////////////////////////////////////////////////////////// // Externs LgiFunc class LViewI *LWindowFromHandle(OsView hWnd); LgiFunc int GetMouseWheelLines(); LgiFunc int WinPointToHeight(int Pt, HDC hDC = NULL); LgiFunc int WinHeightToPoint(int Ht, HDC hDC = NULL); LgiExtern class LString WinGetSpecialFolderPath(int Id); /// Convert a string d'n'd format to an OS dependant integer. LgiFunc int FormatToInt(LString s); /// Convert a Os dependant integer d'n'd format to a string. LgiFunc char *FormatToStr(int f); extern bool LgiToWindowsCursor(OsView Hnd, LCursor Cursor); #ifdef _MSC_VER #define snprintf _snprintf //#define vsnprintf _vsnprintf #define vsnwprintf _vsnwprintf #define stricmp _stricmp #define strlwr _strlwr #define strnicmp _strnicmp #define vsnprintf _vsnprintf_s // #define getcwd _getcwd #endif #define atoi64 _atoi64 #ifdef __GNUC__ // #define stricmp strcasecmp // #define strnicmp strncasecmp #define vsnprintf_s vsnprintf #define swprintf_s snwprintf #define vsprintf_s vsnprintf #if !defined(_TRUNCATE) #define _TRUNCATE ((size_t)-1) #endif #endif diff --git a/src/common/Lgi/FindInFiles.cpp b/src/common/Lgi/FindInFiles.cpp --- a/src/common/Lgi/FindInFiles.cpp +++ b/src/common/Lgi/FindInFiles.cpp @@ -1,252 +1,255 @@ #include "lgi/common/Lgi.h" #include "lgi/common/FindInFiles.h" #include "lgi/common/Popup.h" #include "lgi/common/List.h" #include "lgi/common/Button.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/FileSelect.h" enum Ctrls { IDC_STATIC = -1, IDC_TABLE = 100, IDC_SEARCH, IDC_SEARCH_HISTORY, IDC_WHERE, IDC_WHERE_BROWSE, IDC_WHERE_HISTORY, IDC_INC_SUB_FOLDERS, IDC_MATCH_CASE, IDC_FILE_TYPES, IDC_MATCH_WHOLE_WORD, IDC_TYPES_HISTORY }; class LHistory; class LHistoryPopup : public LPopup { LHistory *History; LList *Lst; public: LHistoryPopup(LHistory *h); void OnPaint(LSurface *pDC) { pDC->Colour(LColour(0, 0, 0)); pDC->Rectangle(); } }; class LHistory : public LDropDown, public ResObject { friend class LHistoryPopup; LString All; LString::Array Strs; public: LHistory(int Id) : LDropDown(Id, 0, 0, 80, 20, 0), ResObject(Res_Custom) { SetPopup(new LHistoryPopup(this)); } void Add(const char *s) { if (!s) return; for (int i=0; iGetHeight() * 1.6); } else if (!Inf.Height.Max) { Inf.Height.Min = Inf.Height.Max = LButton::Overhead.y + LSysFont->GetHeight(); } else return false; return true; } const char *Name() { LString Sep("\t"); All = Sep.Join(Strs); return All; } bool Name(const char *s) { All = s; Strs = All.SplitDelimit("\t"); return true; } }; LHistoryPopup::LHistoryPopup(LHistory *h) : LPopup(h) { History = h; LRect r(0, 0, 300, 200); SetPos(r); r.Inset(1, 1); AddView(Lst = new LList(100, r.x1, r.y1, r.X()-1, r.Y()-1)); Lst->Sunken(false); Lst->ShowColumnHeader(false); for (int i=0; iStrs.Length(); i++) { LListItem *it = new LListItem; it->SetText(History->Strs[i]); Lst->Insert(it); } } struct LFindInFilesPriv : public LXmlTreeUi { LTableLayout *Tbl; LAutoString Search; LDom *Store; LHistory *SearchHistory, *WhereHistory, *TypesHistory; LFindInFilesPriv() { Tbl = NULL; Store = NULL; Map("fif_search", IDC_SEARCH); Map("fif_search_history", IDC_SEARCH_HISTORY); Map("fif_where", IDC_WHERE); Map("fif_where_history", IDC_WHERE_HISTORY); Map("fif_inc_sub", IDC_INC_SUB_FOLDERS); Map("fif_match_case", IDC_MATCH_CASE); Map("fif_match_word", IDC_MATCH_WHOLE_WORD); Map("fif_types", IDC_FILE_TYPES); Map("fif_types_history", IDC_TYPES_HISTORY); } ~LFindInFilesPriv() { } }; LFindInFiles::LFindInFiles(LViewI *Parent, LAutoString Search, LDom *Store) { d = new LFindInFilesPriv; d->Search = Search; d->Store = Store; SetParent(Parent); LRect r(7, 7, 450, 343); SetPos(r); AddView(d->Tbl = new LTableLayout(IDC_TABLE)); int y = 0; int cols = 3; LView *v; Name("Find In Files"); auto *c = d->Tbl->GetCell(0, y, true, cols); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Find what:")); c = d->Tbl->GetCell(0, ++y, true, cols-1); c->Add(v = new LEdit(IDC_SEARCH, 0, 0, 60, 20)); v->Focus(true); c = d->Tbl->GetCell(2, y); c->Add(d->SearchHistory = new LHistory(IDC_SEARCH_HISTORY)); c = d->Tbl->GetCell(0, ++y, true, cols); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Look in:")); c = d->Tbl->GetCell(0, ++y); c->Add(new LEdit(IDC_WHERE, 0, 0, 60, 20)); c = d->Tbl->GetCell(1, y); c->Add(new LButton(IDC_WHERE_BROWSE, 0, 0, 20, 20, "...")); c = d->Tbl->GetCell(2, y); c->Add(d->WhereHistory = new LHistory(IDC_WHERE_HISTORY)); c = d->Tbl->GetCell(0, ++y, true, cols); c->PaddingTop(LCss::Len("0.5em")); c->Add(new LCheckBox(IDC_INC_SUB_FOLDERS, 0, 0, -1, -1, "Include sub-folders")); c = d->Tbl->GetCell(0, ++y, true, cols); c->Add(new LCheckBox(IDC_MATCH_CASE, 0, 0, -1, -1, "Match case")); c = d->Tbl->GetCell(0, ++y, true, cols); c->Add(new LCheckBox(IDC_MATCH_WHOLE_WORD, 0, 0, -1, -1, "Match whole word")); c = d->Tbl->GetCell(0, ++y, true, cols); c->PaddingTop(LCss::Len("1em")); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Look in these file types:")); c = d->Tbl->GetCell(0, ++y, true, cols-1); c->Add(new LEdit(IDC_FILE_TYPES, 0, 0, 60, 20)); c = d->Tbl->GetCell(cols-1, y); c->Add(d->TypesHistory = new LHistory(IDC_TYPES_HISTORY)); c = d->Tbl->GetCell(0, ++y, true, cols); c->TextAlign(LCss::AlignRight); c->Add(new LButton(IDOK, 0, 0, 60, 20, "Search")); c->Add(new LButton(IDCANCEL, 0, 0, 60, 20, "Cancel")); d->Convert(d->Store, this, true); MoveToCenter(); } LFindInFiles::~LFindInFiles() { d->Convert(d->Store, this, false); DeleteObj(d); } int LFindInFiles::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_WHERE_BROWSE: { - LFileSelect s; - s.Parent(this); - if (s.Open()) + auto s = new LFileSelect(this); + s->Open([this](auto s, auto ok) { - LFile::Path p = s.Name(); - if (p.IsFile()) - p--; - SetCtrlName(IDC_WHERE, p.GetFull()); - } + if (ok) + { + LFile::Path p = s->Name(); + if (p.IsFile()) + p--; + SetCtrlName(IDC_WHERE, p.GetFull()); + } + delete s; + }); break; } case IDOK: { // Fall thru d->SearchHistory->Add(GetCtrlName(IDC_SEARCH)); d->WhereHistory->Add(GetCtrlName(IDC_WHERE)); d->TypesHistory->Add(GetCtrlName(IDC_FILE_TYPES)); } case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return LDialog::OnNotify(Ctrl, n); } diff --git a/src/common/Text/TextView4.cpp b/src/common/Text/TextView4.cpp --- a/src/common/Text/TextView4.cpp +++ b/src/common/Text/TextView4.cpp @@ -1,5593 +1,5593 @@ #ifdef WIN32 #include #include #endif #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/TextView4.h" #include "lgi/common/Input.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Mail.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/DropFiles.h" #include "ViewPriv.h" #ifdef _DEBUG #define FEATURE_HILIGHT_ALL_MATCHES 1 #else #define FEATURE_HILIGHT_ALL_MATCHES 0 #endif #define DefaultCharset "utf-8" #define SubtractPtr(a, b) ((a) - (b)) #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define PROFILE_PAINT 0 #define DRAW_LINE_BOXES 0 #define WRAP_POUR_TIMEOUT 90 // ms #define PULSE_TIMEOUT 250 // ms #define CURSOR_BLINK 1000 // ms #define IDC_VS 1000 enum Cmds { IDM_COPY_URL = 100, IDM_AUTO_INDENT, IDM_UTF8, IDM_PASTE_NO_CONVERT, IDM_FIXED, IDM_SHOW_WHITE, IDM_HARD_TABS, IDM_INDENT_SIZE, IDM_TAB_SIZE, IDM_DUMP, IDM_RTL, IDM_COPY_ALL, IDM_SELECT_ALL, #ifndef IDM_OPEN IDM_OPEN, #endif #ifndef IDM_NEW IDM_NEW, #endif #ifndef IDM_COPY IDM_COPY, #endif #ifndef IDM_CUT IDM_CUT, #endif #ifndef IDM_PASTE IDM_PASTE, #endif #ifndef IDM_UNDO IDM_UNDO, #endif #ifndef IDM_REDO IDM_REDO, #endif }; #define PAINT_BORDER Back #if DRAW_LINE_BOXES #define PAINT_AFTER_LINE LColour(240, 240, 240) #else #define PAINT_AFTER_LINE Back #endif #define CODEPAGE_BASE 100 #define CONVERT_CODEPAGE_BASE 200 #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif #define THREAD_CHECK() \ if (!InThread()) \ { \ LgiTrace("%s:%i - %s called out of thread.\n", _FL, __FUNCTION__); \ return false; \ } static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #ifndef WINDOWS static LArray Ctrls; #endif ////////////////////////////////////////////////////////////////////// class LDocFindReplaceParams4 : public LDocFindReplaceParams, public LMutex { public: // Find/Replace History LAutoWString LastFind; LAutoWString LastReplace; bool MatchCase = false; bool MatchWord = false; bool SelectionOnly = false; bool SearchUpwards = false; LDocFindReplaceParams4() : LMutex("LDocFindReplaceParams4") { } }; class LTextView4Private : public LCss, public LMutex { public: LTextView4 *View = NULL; LRect rPadding; int PourX = -1; bool LayoutDirty = true; ssize_t DirtyStart = 0, DirtyLen = 0; LColour UrlColour; bool CenterCursor = false; ssize_t WordSelectMode = -1; LString Eol; LString LastError; // If the scroll position is set before we get a scroll bar, store the index // here and set it when the LNotifyScrollBarCreate arrives. ssize_t VScrollCache = -1; // Find/Replace Params bool OwnFindReplaceParams = true; LDocFindReplaceParams4 *FindReplaceParams = NULL; // Map buffer ssize_t MapLen = 0; char16 *MapBuf = NULL; // // Thread safe Name(char*) impl LString SetName; // #ifdef _DEBUG LString PourLog; #endif LTextView4Private(LTextView4 *view) : LMutex("LTextView4Private") { View = view; UrlColour.Rgb(0, 0, 255); LColour::GetConfigColour("colour.L_URL", UrlColour); rPadding.ZOff(0, 0); FindReplaceParams = new LDocFindReplaceParams4; } ~LTextView4Private() { if (OwnFindReplaceParams) { DeleteObj(FindReplaceParams); } DeleteArray(MapBuf); } void SetDirty(ssize_t Start, ssize_t Len = 0) { LayoutDirty = true; DirtyStart = Start; DirtyLen = Len; } void OnChange(PropType Prop) { if (Prop == LCss::PropPadding || Prop == LCss::PropPaddingLeft || Prop == LCss::PropPaddingRight || Prop == LCss::PropPaddingTop || Prop == LCss::PropPaddingBottom) { LCssTools t(this, View->GetFont()); rPadding.ZOff(0, 0); rPadding = t.ApplyPadding(rPadding); } } }; ////////////////////////////////////////////////////////////////////// enum UndoType { UndoDelete, UndoInsert, UndoChange }; struct Change : public LRange { UndoType Type; LArray Txt; }; struct LTextView4Undo : public LUndoEvent { LTextView4 *View; LArray Changes; LTextView4Undo(LTextView4 *view) { View = view; } void AddChange(ssize_t At, ssize_t Len, UndoType Type) { Change &c = Changes.New(); c.Start = At; c.Len = Len; c.Txt.Add(View->Text + At, Len); c.Type = Type; } void OnChange() { for (auto &c : Changes) { size_t Len = c.Len; if (View->Text) { char16 *t = View->Text + c.Start; for (size_t i=0; id->SetDirty(c.Start, c.Len); } } // LUndoEvent void ApplyChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); View->Cursor = c.Start + c.Len; break; } case UndoDelete: { View->Delete(c.Start, c.Len); View->Cursor = c.Start; break; } case UndoChange: { OnChange(); break; } } } View->UndoOn = true; View->Invalidate(); } void RemoveChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Delete(c.Start, c.Len); break; } case UndoDelete: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); break; } case UndoChange: { OnChange(); break; } } View->Cursor = c.Start; } View->UndoOn = true; View->Invalidate(); } }; void LTextView4::LStyle::RefreshLayout(size_t Start, ssize_t Len) { View->PourText(Start, Len); View->PourStyle(Start, Len); } ////////////////////////////////////////////////////////////////////// LTextView4::LTextView4( int Id, int x, int y, int cx, int cy, LFontType *FontType) : ResObject(Res_Custom) { // init vars LView::d->Css.Reset(d = new LTextView4Private(this)); TabSize = TAB_SIZE; IndentSize = TAB_SIZE; // setup window SetId(Id); // default options #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #endif d->Padding(LCss::Len(LCss::LenPx, 2)); // Data Text = new char16[Alloc]; if (Text) *Text = 0; // Display if (FontType) { Font = FontType->Create(); } else { LFontType Type; if (Type.GetSystemFont("Fixed")) Font = Type.Create(); else printf("%s:%i - failed to create font.\n", _FL); } if (Font) { SetTabStop(true); Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); Underline->Create(); } Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } OnFontChange(); } else { LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType); Font = LSysFont; } CursorPos.ZOff(1, LineY-1); CursorPos.Offset(d->rPadding.x1, d->rPadding.y1); LRect r; r.ZOff(cx-1, cy-1); r.Offset(x, y); SetPos(r); LResources::StyleElement(this); } LTextView4::~LTextView4() { #if DEBUG_EDIT_LOG Edits.DeleteObjects(); #endif #ifndef WINDOWS Ctrls.Delete(this); #endif Line.DeleteObjects(); Style.Empty(); DeleteArray(TextCache); DeleteArray(Text); if (Font != LSysFont) DeleteObj(Font); DeleteObj(FixedFont); DeleteObj(Underline); DeleteObj(Bold); // 'd' is owned by the LView::Css auto ptr } char16 *LTextView4::MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace) { if (ObscurePassword /*|| ShowWhiteSpace*/ || RtlTrailingSpace) { if (Len > d->MapLen) { DeleteArray(d->MapBuf); d->MapBuf = new char16[Len + RtlTrailingSpace]; d->MapLen = Len; } if (d->MapBuf) { int n = 0; if (RtlTrailingSpace) { d->MapBuf[n++] = ' '; for (int i=0; iMapBuf[n++] = Str[i]; } } else if (ObscurePassword) { for (int i=0; iMapBuf[n++] = '*'; } } /* else if (ShowWhiteSpace) { for (int i=0; iMapBuf[n++] = 0xb7; } else if (Str[i] == '\t') { d->MapBuf[n++] = 0x2192; } else { d->MapBuf[n++] = Str[i]; } } } */ return d->MapBuf; } } return Str; } void LTextView4::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LFont *f = FixedFont; FixedFont = Font; Font = f; if (!Font) { Font = Type.Create(); if (Font) { Font->PointSize(FixedFont->PointSize()); } } LDocView::SetFixedWidthFont(i); } } else if (FixedFont) { LFont *f = FixedFont; FixedFont = Font; Font = f; LDocView::SetFixedWidthFont(i); } OnFontChange(); Invalidate(); } } void LTextView4::SetReadOnly(bool i) { LDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } void LTextView4::SetCrLf(bool crlf) { CrLf = crlf; } void LTextView4::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void LTextView4::SetWrapType(LDocWrapType i) { LDocView::SetWrapType(i); CanScrollX = i != L_WRAP_REFLOW; OnPosChange(); Invalidate(); } LFont *LTextView4::GetFont() { return Font; } LFont *LTextView4::GetBold() { return Bold; } void LTextView4::SetFont(LFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { if (Font != LSysFont) DeleteObj(Font); Font = f; } else if (!Font || Font == LSysFont) { Font = new LFont(*f); } else { *Font = *f; } if (Font) { if (!Underline) Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); } if (!Bold) Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } } OnFontChange(); } void LTextView4::OnFontChange() { if (Font) { // get line height // int OldLineY = LineY; if (!Font->Handle()) Font->Create(); LineY = Font->GetHeight(); if (LineY < 1) LineY = 1; // get tab size char Spaces[32]; memset(Spaces, 'A', TabSize); Spaces[TabSize] = 0; LDisplayString ds(Font, Spaces); Font->TabSize(ds.X()); // repour doc d->SetDirty(0, Size); // validate blue underline font if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); } #if WINNATIVE // Set the IME font. HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; LOGFONT FontInfo; GetObject(Font->Handle(), sizeof(FontInfo), &FontInfo); ImmSetCompositionFont(hIMC, &FontInfo); ImmReleaseContext(Handle(), hIMC); } #endif } } LString LTextView4::LogLines() { int Idx = 0; LStringPipe p; p.Print("DocSize: %i\n", (int)Size); for (auto i : Line) { p.Print(" [%i] alloc.ln=%i, %i+%i+%i=%i, %s\n", Idx, i->Line, (int)i->Start, (int)i->Len, i->NewLine, (int)(i->Start + i->Len + i->NewLine), i->r.GetStr()); Idx++; } auto r = p.NewLStr(); LgiTrace(r); #ifdef _DEBUG if (d->PourLog) LgiTrace("%s", d->PourLog.Get()); #endif return r; } bool LTextView4::ValidateLines(bool CheckBox) { size_t Pos = 0; char16 *c = Text; size_t Idx = 0; LTextLine *Prev = NULL; for (auto l: Line) { if (l->Start != Pos) { d->LastError.Printf("%s:%i - Incorrect start.", _FL); goto OnError; } char16 *e = c; if (WrapType == L_WRAP_NONE) { while (*e && *e != '\n') e++; } else { char16 *end = Text + l->Start + l->Len; while (*e && *e != '\n' && e < end) e++; } ssize_t Len = e - c; if (l->Len != Len) { d->LastError.Printf("%s:%i - Incorrect length: " LPrintfSSizeT " != " LPrintfSSizeT ", Idx=" LPrintfSizeT, _FL, l->Len, Len, Idx); goto OnError; } if (CheckBox && Prev && Prev->r.y2 != l->r.y1 - 1) { d->LastError.Printf("%s:%i - Lines not joined vertically", _FL); goto OnError; } if (l->NewLine) { if (*e == '\n') { e++; } else { d->LastError.Printf("%s:%i - Expecting new line.", _FL); goto OnError; } } Pos = e - Text; c = e; Idx++; Prev = l; } if (WrapType == L_WRAP_NONE && Pos != Size) { d->LastError.Printf("%s:%i - Last line != end of doc", _FL); goto OnError; } return true; OnError: #if DEBUG_EDIT_LOG SaveLog("C:\\Code\\TextView4.slog"); #endif return false; } int LTextView4::AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle) { int Changes = 0; for (auto &s : Style) { if (s.Start == Start) { if (Diff < 0 || ExtendStyle) s.Len += Diff; else s.Start += Diff; Changes++; } else if (s.Start > Start) { s.Start += Diff; Changes++; } } return Changes; } // break array, break out of loop when we hit these chars #define ExitLoop(c) ( (c) == 0 || \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' \ ) // extra breaking opportunities #define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) /* Prerequisite: The Line list must have either the objects with the correct Start/Len or be missing the lines altogether... */ void LTextView4::PourText(size_t Start, ssize_t Length /* == 0 means it's a delete */) { #if PROFILE_POUR char _txt[256]; sprintf_s(_txt, sizeof(_txt), "%p::PourText Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(_txt); #endif LAssert(InThread()); LRect Client = GetClient(); int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2; int Cy = 0; MaxX = 0; ssize_t Idx = -1; LTextLine *Cur = GetTextLine(Start, &Idx); if (!Cur || !Cur->r.Valid()) { // Find the last line that has a valid position... for (int64_t mid, s = 0, e = Idx >= 0 ? Idx : Line.Length() - 1; s < e; ) { if (e - s <= 1) { if (Line[e]->r.Valid()) mid = e; else mid = s; Cur = Line[mid]; Cy = Cur->r.y1; Idx = mid; break; } else mid = s + ((e - s) >> 1); auto sItem = Line[mid]; if (sItem->r.Valid()) s = mid + 1; // Search Mid->e else e = mid - 1; // Search s->Mid } } if (Cur && !Cur->r.Valid()) Cur = NULL; if (Cur) { Cy = Cur->r.y1; Start = Cur->Start; Length = Size - Start; } else { Idx = 0; Start = 0; Length = Size; } if (!Text || !Font || Mx <= 0) return; // Tracking vars ssize_t e; int WrapCol = GetWrapAtCol(); LDisplayString Sp(Font, " ", 1); int WidthOfSpace = Sp.X(); if (WidthOfSpace < 1) { printf("%s:%i - WidthOfSpace test failed.\n", _FL); return; } // Alright... lets pour! uint64 StartTs = LCurrentTime(); if (WrapType == L_WRAP_NONE) { // Find the dimensions of each line that is missing a rect #if PROFILE_POUR Prof.Add("NoWrap: ExistingLines"); #endif #ifdef _DEGBUG LStringPipe Log(1024); Log.Printf("Pour: " LPrintfSizeT ", " LPrintfSSizeT ", partial=%i\n", Start, Length, PartialPour); #endif ssize_t Pos = 0; for (; Idx < (ssize_t)Line.Length(); Idx++) { LTextLine *l = Line[Idx]; #ifdef _DEGBUG Log.Printf(" [%i] exist: r.val=%i\n", Idx, l->r.Valid()); #endif if (!l->r.Valid()) // If the layout is not valid... { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + ds.X(); MaxX = MAX(MaxX, l->r.X()); } // Adjust the y position anyway... it's free. l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Cy = l->r.y2 + 1; Pos = l->Start + l->Len; if (Text[Pos] == '\n') Pos++; } // Now if we are missing lines as well, create them and lay them out #if PROFILE_POUR Prof.Add("NoWrap: NewLines"); #endif while (Pos < Size) { LTextLine *l = new LTextLine(_FL); l->Start = Pos; char16 *c = Text + Pos; char16 *e = c; while (*e && *e != '\n') e++; l->Len = e - c; l->NewLine = *e == '\n'; #ifdef _DEGBUG Log.Printf(" [%i] new: start=" LPrintfSSizeT ", len=" LPrintfSSizeT "\n", Idx, l->Start, l->Len); #endif l->r.x1 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; if (l->Len) { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x2 = l->r.x1 + ds.X(); } else { l->r.x2 = l->r.x1; } - LAssert(l->Len > 0); + LAssert(l->Len + l->NewLine > 0); Line.Add(l); if (*e == '\n') e++; MaxX = MAX(MaxX, l->r.X()); Cy = l->r.y2 + 1; Pos = e - Text; Idx++; } #ifdef _DEGBUG d->PourLog = Log.NewLStr(); #endif PartialPour = false; } else // Wrap text { int DisplayStart = ScrollYLine(); int DisplayLines = (Client.Y() + LineY - 1) / LineY; int DisplayEnd = DisplayStart + DisplayLines; // Pouring is split into 2 parts... // 1) pouring to the end of the displayed text. // 2) pouring from there to the end of the document. // potentially taking several goes to complete the full pour // This allows the document to display and edit faster.. bool PourToDisplayEnd = Line.Length() < DisplayEnd; #if 0 LgiTrace("Idx=%i, DisplayStart=%i, DisplayLines=%i, DisplayEnd=%i, PourToDisplayEnd=%i\n", Idx, DisplayStart, DisplayLines, DisplayEnd, PourToDisplayEnd); #endif if ((ssize_t)Line.Length() > Idx) { for (size_t i=Idx; i= Size || Text[e] == '\n' || (e-i) >= WrapCol) { break; } e++; } // Seek back some characters if we are mid word size_t OldE = e; if (e < Size && Text[e] != '\n') { while (e > i) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) { break; } e--; } } if (e == i) { // No line break at all, so seek forward instead for (e=OldE; e < Size && Text[e] != '\n'; e++) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) break; } } // Calc the width LDisplayString ds(Font, Text + i, e - i); Width = ds.X(); } else { // Wrap to edge of screen ssize_t PrevExitChar = -1; int PrevX = -1; while (true) { if (e >= Size || ExitLoop(Text[e]) || ExtraBreak(Text[e])) { LDisplayString ds(Font, Text + i, e - i); if (ds.X() + Cx > Mx) { if (PrevExitChar > 0) { e = PrevExitChar; Width = PrevX; } else { Width = ds.X(); } break; } else if (e >= Size || Text[e] == '\n') { Width = ds.X(); break; } PrevExitChar = e; PrevX = ds.X(); } e++; } } // Create layout line LTextLine *l = new LTextLine(_FL); if (l) { l->Start = i; l->Len = e - i; l->NewLine = Text[e] == '\n'; l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + Width - 1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; LAssert(l->Len > 0); Line.Add(l); if (PourToDisplayEnd) { if (Line.Length() > DisplayEnd) { // We have reached the end of the displayed area... so // exit out temporarily to display the layout to the user PartialPour = true; break; } } else { // Otherwise check if we are taking too long... if (Line.Length() % 20 == 0) { uint64 Now = LCurrentTime(); if (Now - StartTs > WRAP_POUR_TIMEOUT) { PartialPour = true; // LgiTrace("Pour timeout...\n"); break; } } } MaxX = MAX(MaxX, l->r.X()); Cy += LineY; if (e < Size) e++; } } if (i >= Size) PartialPour = false; SendNotify(LNotifyCursorChanged); } #ifdef _DEBUG ValidateLines(true); #endif #if PROFILE_POUR Prof.Add("LastLine"); #endif if (!PartialPour) { auto It = Line.rbegin(); LTextLine *Last = It != Line.end() ? *It : NULL; if (!Last || Last->EndNewLine() < Size) { auto LastEnd = Last ? Last->End() : 0; LTextLine *l = new LTextLine(_FL); if (l) { l->Start = LastEnd; l->Len = Size - LastEnd; l->r.x1 = l->r.x2 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Add(l); MaxX = MAX(MaxX, l->r.X()); Cy += LineY; } } } bool ScrollYNeeded = Client.Y() < (Line.Length() * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); d->LayoutDirty = WrapType != L_WRAP_NONE && ScrollChange; #if PROFILE_POUR static LString _s; _s.Printf("ScrollBars dirty=%i", d->LayoutDirty); Prof.Add(_s); #endif if (ScrollChange) { // LgiTrace("%s:%i - SetScrollBars(%i) %i %i\n", _FL, ScrollYNeeded, Client.Y(), (Line.Length() * LineY)); SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); #if 0 // def _DEBUG if (GetWindow()) { static char s[256]; sprintf_s(s, sizeof(s), "Pour: %.2f sec", (double)_PourTime / 1000); GetWindow()->PostEvent(M_TEXTVIEW_DEBUG_TEXT, (LMessage::Param)s); } #endif #if POUR_DEBUG printf("Lines=%i\n", Line.Length()); int Index = 0; for (LTextLine *l=Line.First(); l; l=Line.Next(), Index++) { printf("\t[%i] %i,%i (%s)\n", Index, l->Start, l->Len, l->r.Describe()); } #endif } bool LTextView4::InsertStyle(LAutoPtr s) { if (!s) return false; LAssert(s->Start >= 0); LAssert(s->Len > 0); ssize_t Last = 0; // int n = 0; // LgiTrace("StartStyle=%i,%i(%i) %s\n", (int)s->Start, (int)s->Len, (int)(s->Start+s->Len), s->Fore.GetStr()); if (Style.Length() > 0) { // Optimize for last in the list auto Last = Style.rbegin(); if (s->Start >= (ssize_t)Last->End()) { Style.Insert(*s); return true; } } for (auto i = Style.begin(); i != Style.end(); i++) { if (s->Overlap(*i)) { if (s->Owner > i->Owner) { // Fail the insert return false; } else { // Replace mode... *i = *s; return true; } } if (s->Start >= Last && s->Start < i->Start) { Style.Insert(*s, i); return true; } } Style.Insert(*s); return true; } LTextView4::LStyle *LTextView4::GetNextStyle(StyleIter &s, ssize_t Where) { if (Where >= 0) s = Style.begin(); else s++; while (s != Style.end()) { // determine whether style is relevant.. // styles in the selected region are ignored ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (SelStart >= 0 && s->Start >= Min && s->Start+s->Len < Max) { // style is completely inside selection: ignore s++; } else if (Where >= 0 && s->Start+s->Len < Where) { s++; } else { return &(*s); } } return NULL; } #if 0 CURSOR_CHAR GetCursor() { #ifdef WIN32 LArray Ver; int Os = LGetOs(&Ver); if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && Ver[0] >= 5) { return MAKEINTRESOURCE(32649); // hand } else { return IDC_ARROW; } #endif return 0; } #endif LTextView4::LStyle *LTextView4::HitStyle(ssize_t i) { for (auto &s : Style) { if (i >= s.Start && i < (ssize_t)s.End()) { return &s; } } return NULL; } void LTextView4::PourStyle(size_t Start, ssize_t EditSize) { #ifdef _DEBUG int64 StartTime = LCurrentTime(); #endif LAssert(InThread()); if (!Text || Size < 1) return; ssize_t Length = MAX(EditSize, 0); if ((ssize_t)Start + Length >= Size) Length = Size - Start; // For deletes, this sizes the edit length within bounds. // Expand re-style are to word boundaries before and after the area of change while (Start > 0 && UrlChar(Text[Start-1])) { // Move the start back Start--; Length++; } while ((ssize_t)Start + Length < Size && UrlChar(Text[Start+Length])) { // Move the end back Length++; } // Delete all the styles that we own inside the changed area for (StyleIter s = Style.begin(); s != Style.end();) { if (s->Owner == STYLE_NONE) { if (EditSize > 0) { if (s->Overlap(Start, EditSize < 0 ? -EditSize : EditSize)) { Style.Delete(s); continue; } } else { if (s->Overlap(Start, -EditSize)) { Style.Delete(s); continue; } } } s++; } if (UrlDetect) { LArray Links; LAssert((ssize_t)Start + Length <= Size); if (LDetectLinks(Links, Text + Start, Length)) { for (uint32_t i=0; i Url(new LStyle(STYLE_URL)); if (Url) { Url->View = this; Url->Start = Inf.Start + Start; Url->Len = Inf.Len; Url->Font = Underline; Url->Fore = d->UrlColour; InsertStyle(Url); } } } } #ifdef _DEBUG _StyleTime = LCurrentTime() - StartTime; #endif } bool LTextView4::Insert(size_t At, const char16 *Data, ssize_t Len) { static int count = -1; count++; #if DEBUG_EDIT_LOG { EditLog *e = new EditLog; e->Length = Len; e->Type = EditInsert; e->Str.Add(Data, Len); Edits.Add(e); } #endif #if 0 LProfile Prof("LTextView4::Insert"); Prof.HideResultsIfBelow(1000); #define PROF(s) Prof.Add(s) #else #define PROF(s) #endif LAssert(InThread()); if (!ReadOnly && Len > 0) { if (!Data) return false; // limit input to valid data At = MIN(Size, (ssize_t)At); // make sure we have enough memory size_t NewAlloc = Size + Len + 1; NewAlloc += ALLOC_BLOCK - (NewAlloc % ALLOC_BLOCK); if (NewAlloc != Alloc) { char16 *NewText = new char16[NewAlloc]; if (NewText) { if (Text) { // copy any existing data across memcpy(NewText, Text, (Size + 1) * sizeof(char16)); } DeleteArray(Text); Text = NewText; Alloc = NewAlloc; } else { // memory allocation error return false; } } PROF("MemChk"); if (Text) { // Insert the data // Move the section after the insert to make space... auto After = Size - At; if (After > 0) memmove(Text+(At+Len), Text+At, After * sizeof(char16)); PROF("Cpy"); // Copy new data in... memcpy(Text+At, Data, Len * sizeof(char16)); Size += Len; Text[Size] = 0; // NULL terminate PROF("Undo"); // Add the undo object... if (UndoOn) { LAutoPtr Obj; if (!UndoCur) Obj.Reset(new LTextView4Undo(this)); auto u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoInsert); else LAssert(!"No undo obj?"); if (Obj) UndoQue += Obj.Release(); } // Clear layout info for the new text ssize_t Idx = -1; LTextLine *Cur = NULL; if (Line.Length() == 0) { // Empty doc... set up the first line Line.Add(Cur = new LTextLine(_FL)); Idx = 0; Cur->Start = 0; } else { Cur = GetTextLine(At, &Idx); } if (Cur) { if (WrapType == L_WRAP_NONE) { // Clear layout for current line... Cur->r.ZOff(-1, -1); PROF("NoWrap add lines"); LgiTrace("Insert '%S' at %i, size=%i\n", Data, (int)At, (int)Size); // Add any new lines that we need... char16 *e = Text + At + Len; char16 *c; for (c = Text + At; c < e; c++) { if (*c == '\n') { // Set the size of the current line... size_t Pos = c - Text; Cur->Len = Pos - Cur->Start; Cur->NewLine = true; LgiTrace(" LF at %i, Idx=%i\n", (int)Pos, (int)Idx); if (!*++c) { Cur = NULL; break; } // Create a new line... Cur = new LTextLine(_FL); if (!Cur) return false; Cur->Start = c - Text; Line.AddAt(++Idx, Cur); LgiTrace(" newLine at %i, Idx=%i\n", (int)Cur->Start, Idx); } } PROF("CalcLen"); if (Cur) { // Make sure the last Line's length is set.. Cur->CalcLen(Text); LgiTrace(" CalcLen, size=%i, start=%i, len=%i\n", (int)Size, (int)Cur->Start, (int)Cur->Len); } PROF("UpdatePos"); // Now update all the positions of the following lines... for (size_t i = ++Idx; i < Line.Length(); i++) Line[i]->Start += Len; } else { // Clear all lines to the end of the doc... LgiTrace("ClearLines %i\n", (int)Idx+1); for (size_t i = ++Idx; i < Line.Length(); i++) delete Line[i]; Line.Length(Idx); } } else { // If wrap is on then this can happen when an Insert happens before the // OnPulse event has laid out the new text. Probably not a good thing in // non-wrap mode if (WrapType == L_WRAP_NONE) { LTextLine *l = *Line.rbegin(); printf("%s:%i - Insert error: no cur, At=%i, Size=%i, Lines=%i, WrapType=%i\n", _FL, (int)At, (int)Size, (int)Line.Length(), (int)WrapType); if (l) printf("Last=%i, %i\n", (int)l->Start, (int)l->Len); } } #ifdef _DEBUG PROF("Validate"); ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, Len); Dirty = true; if (PourEnabled) { PROF("PourText"); PourText(At, Len); PROF("PourStyle"); auto Start = LCurrentTime(); PourStyle(At, Len); auto End = LCurrentTime(); if (End - Start > 1000) { PourStyle(At, Len); } } SendNotify(LNotifyDocChanged); return true; } } return false; } bool LTextView4::Delete(size_t At, ssize_t Len) { bool Status = false; LAssert(InThread()); if (!ReadOnly) { // limit input At = MAX(At, 0); At = MIN((ssize_t)At, Size); Len = MIN(Size-(ssize_t)At, Len); if (Len > 0) { int HasNewLine = 0; for (int i=0; i Obj(new LTextView4Undo(this)); LTextView4Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoDelete); if (Obj) UndoQue += Obj.Release(); } memmove(Text+At, Text+(At+Len), (Size-At-Len) * sizeof(char16)); Size -= Len; Text[Size] = 0; if (WrapType == L_WRAP_NONE) { ssize_t Idx = -1; LTextLine *Cur = GetTextLine(At, &Idx); if (Cur) { Cur->r.ZOff(-1, -1); // Delete some lines... for (int i=0; iCalcLen(Text); // Shift all further lines down... for (size_t i = Idx + 1; i < Line.Length(); i++) Line[i]->Start -= Len; } } else { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { for (size_t i = Index; i < Line.Length(); i++) delete Line[i]; Line.Length(Index); } } Dirty = true; Status = true; #ifdef _DEBUG ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, -Len); if (PourEnabled) { PourText(At, -Len); PourStyle(At, -Len); } if (Cursor >= (ssize_t)At && Cursor <= (ssize_t)At + Len) { SetCaret(At, false, HasNewLine != 0); } // Handle repainting in flowed mode, when the line starts change if (WrapType == L_WRAP_REFLOW) { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { LRect r = Cur->r; r.x2 = GetClient().x2; r.y2 = GetClient().y2; Invalidate(&r); } } SendNotify(LNotifyDocChanged); Status = true; } } return Status; } void LTextView4::DeleteSelection(char16 **Cut) { if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Cut) { *Cut = NewStrW(Text + Min, Max - Min); } Delete(Min, Max - Min); SetCaret(Min, false, true); } } LArray::I LTextView4::GetTextLineIt(ssize_t Offset, ssize_t *Index) { if (Line.Length() == 0) { if (Index) *Index = 0; return Line.end(); } if (Offset <= 0) { if (Index) *Index = 0; return Line.begin(); } else if (Line.Length()) { auto l = Line.Last(); if (Offset > l->End()) { if (Index) *Index = Line.Length() - 1; return Line.begin(Line.Length()-1); } } size_t mid = 0, s = 0, e = Line.Length() - 1; while (s < e) { if (e - s <= 1) { if (Line[s]->Overlap(Offset)) mid = s; else if (Line[e]->Overlap(Offset)) mid = e; else goto OnError; } else mid = s + ((e - s) >> 1); auto l = Line[mid]; auto end = l->EndNewLine(); if (Offset < l->Start) e = mid - 1; else if (Offset > end) s = mid + 1; else { if (!Line[mid]->Overlap(Offset)) goto OnError; if (Index) *Index = mid; return Line.begin(mid); } } if (!Line[s]->Overlap(Offset)) goto OnError; if (Index) *Index = s; return Line.begin(s); OnError: return Line.end(); } int64 LTextView4::Value() { auto n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void LTextView4::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } LString LTextView4::operator[](ssize_t LineIdx) { if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines()) return LString(); LTextLine *Ln = Line[LineIdx-1]; if (!Ln) return LString(); LString s(Text + Ln->Start, Ln->Len); return s; } const char *LTextView4::Name() { UndoQue.Empty(); DeleteArray(TextCache); TextCache = WideToUtf8(Text); return TextCache; } bool LTextView4::Name(const char *s) { if (InThread()) { UndoQue.Empty(); DeleteArray(TextCache); DeleteArray(Text); Line.DeleteObjects(); Style.Empty(); LAssert(LIsUtf8(s)); Text = Utf8ToWide(s); if (!Text) { Text = new char16[1]; if (Text) *Text = 0; } Size = Text ? StrlenW(Text) : 0; Alloc = Size + 1; Cursor = MIN(Cursor, Size); if (Text) { // Remove '\r's char16 *o = Text; for (char16 *i=Text; *i; i++) { if (*i != '\r') { *o++ = *i; } else Size--; } *o++ = 0; } // update everything else d->SetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); } else if (d->Lock(_FL)) { if (IsAttached()) { d->SetName = s; PostEvent(M_TEXT_UPDATE_NAME); } else LAssert(!"Can't post event to detached/virtual window."); d->Unlock(); } return true; } const char16 *LTextView4::NameW() { return Text; } bool LTextView4::NameW(const char16 *s) { DeleteArray(Text); Size = s ? StrlenW(s) : 0; Alloc = Size + 1; Text = new char16[Alloc]; Cursor = MIN(Cursor, Size); if (Text) { memcpy(Text, s, Size * sizeof(char16)); // remove LF's int In = 0, Out = 0; CrLf = false; for (; InSetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); return true; } LRange LTextView4::GetSelectionRange() { LRange r; if (HasSelection()) { r.Start = MIN(SelStart, SelEnd); ssize_t End = MAX(SelStart, SelEnd); r.Len = End - r.Start; } return r; } char *LTextView4::GetSelection() { LRange s = GetSelectionRange(); if (s.Len > 0) { return (char*)LNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) ); } return 0; } bool LTextView4::HasSelection() { return (SelStart >= 0) && (SelStart != SelEnd); } void LTextView4::SelectAll() { SelStart = 0; SelEnd = Size; Invalidate(); } void LTextView4::UnSelectAll() { bool Update = HasSelection(); SelStart = -1; SelEnd = -1; if (Update) { Invalidate(); } } size_t LTextView4::GetLines() { return Line.Length(); } void LTextView4::GetTextExtent(int &x, int &y) { PourText(0, Size); x = MaxX + d->rPadding.x1; y = (int)(Line.Length() * LineY); } bool LTextView4::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index) { ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Index < 0 ? Cursor : Index, &FromIndex); if (!From) return false; Pt.x = (int) (Cursor - From->Start); Pt.y = (int) FromIndex; return true; } ssize_t LTextView4::GetCaret(bool Cur) { if (Cur) { return Cursor; } return 0; } ssize_t LTextView4::IndexAt(int x, int y) { LTextLine *l = Line.ItemAt(y); if (l) { return l->Start + MIN(x, l->Len); } return 0; } bool LTextView4::ScrollToOffset(size_t Off) { bool ForceFullUpdate = false; ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Off, &ToIndex); if (To) { LRect Client = GetClient(); int DisplayLines = Client.Y() / LineY; if (VScroll) { if (ToIndex < VScroll->Value()) { // Above the visible region... if (d->CenterCursor) { ssize_t i = ToIndex - (DisplayLines >> 1); VScroll->Value(MAX(0, i)); } else { VScroll->Value(ToIndex); } ForceFullUpdate = true; } if (ToIndex >= VScroll->Value() + DisplayLines) { int YOff = d->CenterCursor ? DisplayLines >> 1 : DisplayLines; ssize_t v = MIN(ToIndex - YOff + 1, (ssize_t)Line.Length() - DisplayLines); if (v != VScroll->Value()) { // Below the visible region VScroll->Value(v); ForceFullUpdate = true; } } } else { d->VScrollCache = ToIndex; } } return ForceFullUpdate; } void LTextView4::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { // int _Start = LCurrentTime(); Blink = true; // Bound the new cursor position to the document if ((ssize_t)i > Size) i = Size; // Store the old selection and cursor ssize_t s = SelStart, e = SelEnd, c = Cursor; // If there is going to be a selected area if (Select && i != SelStart) { // Then set the start if (SelStart < 0) { // We are starting a new selection SelStart = Cursor; } // And end SelEnd = i; } else { // Clear the selection SelStart = SelEnd = -1; } ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Cursor, &FromIndex); Cursor = i; // check the cursor is on the screen ForceFullUpdate |= ScrollToOffset(Cursor); // check whether we need to update the screen ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Cursor, &ToIndex); if (ForceFullUpdate || !To || !From) { // need full update Invalidate(); } else if ( ( SelStart != s || SelEnd != e ) ) { // Update just the selection bounds LRect Client = GetClient(); size_t Start, End; if (SelStart >= 0 && s >= 0) { // Selection has changed, union the before and after regions Start = MIN(Cursor, c); End = MAX(Cursor, c); } else if (SelStart >= 0) { // Selection created... Start = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else if (s >= 0) { // Selection removed... Start = MIN(s, e); End = MAX(s, e); } else return; LTextLine *SLine = GetTextLine(Start); LTextLine *ELine = GetTextLine(End); LRect u; if (SLine && ELine) { if (SLine->r.Valid()) { u = DocToScreen(SLine->r); } else u.Set(0, 0, Client.X()-1, 1); // Start of visible page LRect b(0, Client.Y()-1, Client.X()-1, Client.Y()-1); if (ELine->r.Valid()) { b = DocToScreen(ELine->r); } else { b.Set(0, Client.Y()-1, Client.X()-1, Client.Y()-1); } u.Union(&b); u.x1 = 0; u.x2 = X(); } else { /* printf("%s,%i - Couldn't get SLine and ELine: %i->%p, %i->%p\n", _FL, (int)Start, SLine, (int)End, ELine); */ u = Client; } Invalidate(&u); } else if (Cursor != c) { // just the cursor has moved // update the line the cursor moved to LRect r = To->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); if (To != From) { // update the line the cursor came from, // if it's a different line from the "to" r = From->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); } } if (c != Cursor) { // Send off notify SendNotify(LNotifyCursorChanged); } //int _Time = LCurrentTime() - _Start; //printf("Setcursor=%ims\n", _Time); } void LTextView4::SetBorder(int b) { } bool LTextView4::Cut() { bool Status = false; char16 *Txt16 = 0; DeleteSelection(&Txt16); if (Txt16) { #ifdef WIN32 Txt16 = ConvertToCrLf(Txt16); #endif char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); LClipBoard Clip(this); Clip.Text(Txt8); Status = Clip.TextW(Txt16, false); DeleteArray(Txt8); DeleteArray(Txt16); } return Status; } bool LTextView4::Copy() { bool Status = true; if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); #ifdef WIN32 char16 *Txt16 = NewStrW(Text+Min, Max-Min); Txt16 = ConvertToCrLf(Txt16); char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); #else char *Txt8 = (char*)LNewConvertCp("utf-8", Text+Min, LGI_WideCharset, (Max-Min)*sizeof(*Text)); #endif LClipBoard Clip(this); Clip.Text(Txt8); #ifdef WIN32 Clip.TextW(Txt16, false); DeleteArray(Txt16); #endif DeleteArray(Txt8); } else LgiTrace("%s:%i - No selection.\n", _FL); return Status; } bool LTextView4::Paste() { LClipBoard Clip(this); LAutoWString Mem; char16 *t = Clip.TextW(); if (!t) // ala Win9x { char *s = Clip.Text(); if (s) { Mem.Reset(Utf8ToWide(s)); t = Mem; } } if (!t) return false; if (SelStart >= 0) { DeleteSelection(); } // remove '\r's char16 *s = t, *d = t; for (; *s; s++) { if (*s != '\r') { *d++ = *s; } } *d++ = 0; // insert text ssize_t Len = StrlenW(t); Insert(Cursor, t, Len); SetCaret(Cursor+Len, false, true); // Multiline return true; } void LTextView4::ClearDirty(std::function OnStatus, bool Ask, const char *FileName) { if (Dirty) { int Answer = (Ask) ? LgiMsg(this, LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { auto DoSave = [this, OnStatus](bool ok, const char *FileName) { Save(FileName); if (OnStatus) OnStatus(ok); }; if (!FileName) { LFileSelect *Select = new LFileSelect; Select->Parent(this); Select->Save([FileName=LString(FileName), DoSave](auto Select, auto ok) { if (ok) DoSave(ok, Select->Name()); else DoSave(ok, FileName); delete Select; }); } else DoSave(true, FileName); } else if (Answer == IDCANCEL) { if (OnStatus) OnStatus(false); return; } } if (OnStatus) OnStatus(true); } bool LTextView4::Open(const char *Name, const char *CharSet) { bool Status = false; LFile f; if (f.Open(Name, O_READ|O_SHARE)) { DeleteArray(Text); int64 Bytes = f.GetSize(); if (Bytes < 0 || Bytes & 0xffff000000000000LL) { LgiTrace("%s:%i - Invalid file size: " LPrintfInt64 "\n", _FL, Bytes); return false; } SetCaret(0, false); Line.DeleteObjects(); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } // Convert to unicode first.... if (Bytes == 0) { Text = new char16[1]; if (Text) Text[0] = 0; } else { Text = (char16*)LNewConvertCp(LGI_WideCharset, DataStart, CharSet ? CharSet : DefaultCharset); } if (Text) { // Remove LF's char16 *In = Text, *Out = Text; CrLf = false; Size = 0; while (*In) { if (*In >= ' ' || *In == '\t' || *In == '\n') { *Out++ = *In; Size++; } else if (*In == '\r') { CrLf = true; } In++; } Size = (int) (Out - Text); *Out = 0; Alloc = Size + 1; Dirty = false; if (Text && Text[0] == 0xfeff) // unicode byte order mark { memmove(Text, Text+1, Size * sizeof(*Text)); Size--; } PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(true); Status = true; } } DeleteArray(c8); } else { Alloc = Size = 0; } Invalidate(); } return Status; } template bool WriteToStream(LFile &out, T *in, size_t len, bool CrLf) { if (!in) return false; if (CrLf) { int BufLen = 1 << 20; LAutoPtr Buf(new T[BufLen]); T *b = Buf; T *e = Buf + BufLen; T *c = in; T *end = c + len; while (c < end) { if (b > e - 16) { auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; b = Buf; } if (*c == '\n') { *b++ = '\r'; *b++ = '\n'; } else { *b++ = *c; } c++; } auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; } else { auto Bytes = len * sizeof(T); if (out.Write(in, Bytes) != Bytes) return false; } return true; } bool LTextView4::Save(const char *Name, const char *CharSet) { LFile f; LString TmpName; bool Status = false; d->LastError.Empty(); if (f.Open(Name, O_WRITE)) { if (f.SetSize(0) != 0) { // Can't resize file, fall back to renaming it and // writing a new file... f.Close(); TmpName = Name; TmpName += ".tmp"; if (!FileDev->Move(Name, TmpName)) { LgiTrace("%s:%i - Failed to move '%s'.\n", _FL, Name); return false; } if (!f.Open(Name, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Name); return false; } } if (Text) { auto InSize = Size * sizeof(char16); if (CharSet && !Stricmp(CharSet, "utf-16")) { if (sizeof(*Text) == 2) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 32->16 convert LAutoPtr c16((uint16_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c16) Status = WriteToStream(f, c16.Get(), Strlen(c16.Get()), CrLf); } } else if (CharSet && !Stricmp(CharSet, "utf-32")) { if (sizeof(*Text) == 4) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 16->32 convert LAutoPtr c32((uint32_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c32) Status = WriteToStream(f, c32.Get(), Strlen(c32.Get()), CrLf); } } else { LAutoString c8((char*)LNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, InSize)); if (c8) Status = WriteToStream(f, c8.Get(), strlen(c8), CrLf); } if (Status) Dirty = false; } } else { int Err = f.GetError(); LString sErr = LErrorCodeToString(Err); d->LastError.Printf("Failed to open '%s' for writing: %i - %s\n", Name, Err, sErr.Get()); } if (TmpName) FileDev->Delete(TmpName); return Status; } const char *LTextView4::GetLastError() { return d->LastError; } void LTextView4::UpdateScrollBars(bool Reset) { if (VScroll) { LRect Before = GetClient(); int DisplayLines = Y() / LineY; ssize_t Lines = GetLines(); VScroll->SetRange(Lines); if (VScroll) { VScroll->SetPage(DisplayLines); ssize_t Max = Lines - DisplayLines + 1; bool Inval = false; if (VScroll->Value() > Max) { VScroll->Value(Max); Inval = true; } if (Reset) { VScroll->Value(0); SelStart = SelEnd = -1; } else if (d->VScrollCache >= 0) { VScroll->Value(d->VScrollCache); d->VScrollCache = -1; SelStart = SelEnd = -1; } LRect After = GetClient(); if (Before != After && GetWrapType()) { d->SetDirty(0, Size); Inval = true; } if (Inval) { Invalidate(); } } } } void LTextView4::DoCase(std::function Callback, bool Upper) { if (Text) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Min < Max) { if (UndoOn) { LAutoPtr Obj(new LTextView4Undo(this)); LTextView4Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(Min, Max - Min, UndoChange); if (Obj) UndoQue += Obj.Release(); } for (ssize_t i=Min; i= 'a' && Text[i] <= 'z') Text[i] = Text[i] - 'a' + 'A'; } else { if (Text[i] >= 'A' && Text[i] <= 'Z') Text[i] = Text[i] - 'A' + 'a'; } } Dirty = true; d->SetDirty(Min, 0); Invalidate(); SendNotify(LNotifyDocChanged); } } if (Callback) Callback(Text != NULL); } ssize_t LTextView4::GetLine() { ssize_t Idx = 0; GetTextLine(Cursor, &Idx); return Idx + 1; } void LTextView4::SetLine(int64_t i) { LTextLine *l = Line.ItemAt(i - 1); if (l) { d->CenterCursor = true; SetCaret(l->Start, false); d->CenterCursor = false; } } void LTextView4::DoGoto(std::function Callback) { LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); Dlg->DoModal([this, Dlg, Callback](auto d, auto code) { auto ok = code == IDOK && Dlg->GetStr(); if (ok) SetLine(Dlg->GetStr().Int()); if (Callback) Callback(ok); delete Dlg; }); } LDocFindReplaceParams *LTextView4::CreateFindReplaceParams() { return new LDocFindReplaceParams4; } void LTextView4::SetFindReplaceParams(LDocFindReplaceParams *Params) { if (Params) { if (d->OwnFindReplaceParams) { DeleteObj(d->FindReplaceParams); } d->OwnFindReplaceParams = false; d->FindReplaceParams = (LDocFindReplaceParams4*) Params; } } void LTextView4::DoFindNext(std::function Callback) { bool Status = false; if (InThread()) { if (d->FindReplaceParams->Lock(_FL)) { if (d->FindReplaceParams->LastFind) Status = OnFind(d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); d->FindReplaceParams->Unlock(); } } else if (IsAttached()) { Status = PostEvent(M_TEXTVIEW_FIND); } if (Callback) Callback(Status); } void LTextView4::DoFind(std::function Callback) { LString u; if (HasSelection()) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); u = LString(Text + Min, Max - Min); } else { u = d->FindReplaceParams->LastFind.Get(); } auto Dlg = new LFindDlg(this, [this, Params=d->FindReplaceParams, Callback](auto Dlg, auto Action) { if (Params && Params->Lock(_FL)) { Params->MatchWord = Dlg->MatchWord; Params->MatchCase = Dlg->MatchCase; Params->SelectionOnly = Dlg->SelectionOnly; Params->SearchUpwards = Dlg->SearchUpwards; Params->LastFind.Reset(Utf8ToWide(Dlg->Find)); Params->Unlock(); } DoFindNext([this, Callback](bool ok) { Focus(true); if (Callback) Callback(ok); }); }, u); Dlg->DoModal(NULL); } void LTextView4::DoReplace(std::function Callback) { bool SingleLineSelection = false; SingleLineSelection = HasSelection(); if (SingleLineSelection) { LRange Sel = GetSelectionRange(); for (ssize_t i = Sel.Start; i < Sel.End(); i++) { if (Text[i] == '\n') { SingleLineSelection = false; break; } } } LAutoString LastFind8(SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind)); LAutoString LastReplace8(WideToUtf8(d->FindReplaceParams->LastReplace)); auto Dlg = new LReplaceDlg(this, [this](auto Dlg, auto Action) { LReplaceDlg *Replace = dynamic_cast(Dlg); LAssert(Replace != NULL); if (Action == IDCANCEL) return; if (d->FindReplaceParams->Lock(_FL)) { d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Replace->Find)); d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Replace->Replace)); d->FindReplaceParams->MatchWord = Replace->MatchWord; d->FindReplaceParams->MatchCase = Replace->MatchCase; d->FindReplaceParams->SelectionOnly = Replace->SelectionOnly; switch (Action) { case IDC_FR_FIND: { OnFind( d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); break; } case IDOK: case IDC_FR_REPLACE: { OnReplace( d->FindReplaceParams->LastFind, d->FindReplaceParams->LastReplace, Action == IDOK, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); break; } } d->FindReplaceParams->Unlock(); } }, LastFind8, LastReplace8); Dlg->MatchWord = d->FindReplaceParams->MatchWord; Dlg->MatchCase = d->FindReplaceParams->MatchCase; Dlg->SelectionOnly = HasSelection(); Dlg->DoModal(NULL); } void LTextView4::SelectWord(size_t From) { for (SelStart = From; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = From; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Invalidate(); } typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n); ptrdiff_t LTextView4::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { if (!ValidStrW(Find)) return -1; ssize_t FindLen = StrlenW(Find); // Setup range to search ssize_t Begin, End; if (SelectionOnly && HasSelection()) { Begin = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else { Begin = 0; End = Size; } // Look through text... ssize_t i; bool Wrap = false; if (Cursor > End - FindLen) { Wrap = true; if (SearchUpwards) i = End - FindLen; else i = Begin; } else { i = Cursor; } if (i < Begin) i = Begin; if (i > End) i = End; StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW; char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]); for (; SearchUpwards ? i >= Begin : i <= End - FindLen; i += SearchUpwards ? -1 : 1) { if ( (MatchCase ? Text[i] : toupper(Text[i])) == FindCh ) { char16 *Possible = Text + i; if (CmpFn(Possible, Find, FindLen) == 0) { if (MatchWord) { // Check boundaries if (Possible > Text) // Check off the start { if (!IsWordBoundry(Possible[-1])) continue; } if (i + FindLen < Size) // Check off the end { if (!IsWordBoundry(Possible[FindLen])) continue; } } LRange r(Possible - Text, FindLen); if (!r.Overlap(Cursor)) return r.Start; } } if (!Wrap && (i + 1 > End - FindLen)) { Wrap = true; i = Begin; End = Cursor; } } return -1; } bool LTextView4::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); // Not sure what this is doing??? if (HasSelection() && SelEnd < SelStart) { Cursor = SelStart; } #if FEATURE_HILIGHT_ALL_MATCHES // Clear existing styles for matches for (StyleIter s = Style.begin(); s != Style.end(); ) { if (s->Owner == STYLE_FIND_MATCHES) Style.Delete(s); else s++; } ssize_t FindLen = StrlenW(Find); ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc; if (FirstLoc >= 0) { SetCaret(FirstLoc, false); SetCaret(FirstLoc + FindLen, true); } ssize_t Old = Cursor; if (!SearchUpwards) Cursor += FindLen; while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc) { LAutoPtr s(new LStyle(STYLE_FIND_MATCHES)); s->Start = Loc; s->Len = FindLen; s->Fore = LColour(L_FOCUS_SEL_FORE); s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE)); InsertStyle(s); Cursor = Loc + FindLen; } Cursor = Old; ScrollToOffset(Cursor); Invalidate(); #else ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (Loc >= 0) { SetCaret(Loc, false); SetCaret(Loc + StrlenW(Find), true); return true; } #endif return false; } bool LTextView4::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); if (ValidStrW(Find)) { // int Max = -1; ssize_t FindLen = StrlenW(Find); ssize_t ReplaceLen = StrlenW(Replace); // size_t OldCursor = Cursor; ptrdiff_t First = -1; while (true) { ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (First < 0) { First = Loc; } else if (Loc == First) { break; } if (Loc >= 0) { ssize_t OldSelStart = SelStart; ssize_t OldSelEnd = SelEnd; Delete(Loc, FindLen); Insert(Loc, Replace, ReplaceLen); SelStart = OldSelStart; SelEnd = OldSelEnd - FindLen + ReplaceLen; Cursor = Loc + ReplaceLen; } if (!All) { return Loc >= 0; } if (Loc < 0) break; } } return false; } ssize_t LTextView4::SeekLine(ssize_t Offset, LTextViewSeek Where) { THREAD_CHECK(); switch (Where) { case PrevLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset--; for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case NextLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; Offset++; break; } case StartLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case EndLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; break; } default: { LAssert(false); break; } } return Offset; } bool LTextView4::OnMultiLineTab(bool In) { bool Status = false; ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd), i; Min = SeekLine(Min, StartLine); int Ls = 0; LArray p; for (i=Min; i=0; i--) { if (In) { // <- ssize_t n = Indexes[i], Space = 0; for (; Space ssize_t Len = Indexes[i]; for (; Text[Len] != '\n' && Len Indexes[i]) { if (HardTabs) { char16 Tab[] = {'\t', 0}; Insert(Indexes[i], Tab, 1); Max++; } else { char16 *Sp = new char16[IndentSize]; if (Sp) { for (int n=0; nChanges.Length()) { UndoQue += UndoCur; UndoCur = NULL; } else { DeleteObj(UndoCur); } SelStart = Min; SelEnd = Cursor = Max; PourEnabled = true; PourText(Min, Max - Min); PourStyle(Min, Max - Min); d->SetDirty(Min, Max-Min); Invalidate(); Status = true; return Status; } void LTextView4::OnSetHidden(int Hidden) { } void LTextView4::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; LLayout::OnPosChange(); LRect c = GetClient(); bool ScrollYNeeded = c.Y() < (Line.Length() * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); if (ScrollChange) { // printf("%s:%i - SetScrollBars(%i)\n", _FL, ScrollYNeeded); SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); if (GetWrapType() && d->PourX != X()) { d->PourX = X(); d->SetDirty(0, Size); } Processing = false; } } int LTextView4::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.Supports("text/uri-list"); Formats.Supports("text/html"); Formats.Supports("UniformResourceLocatorW"); return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int LTextView4::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned i=0; iIsBinary()) { OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length); OsChar *s = (OsChar*) Data->Value.Binary.Data; int len = 0; while (s < e && s[len]) { len++; } LAutoWString w ( (char16*)LNewConvertCp ( LGI_WideCharset, s, ( sizeof(OsChar) == 1 ? "utf-8" : LGI_WideCharset ), len * sizeof(*s) ) ); Insert(Cursor, w, len); Invalidate(); return DROPEFFECT_COPY; } } else if (dd.IsFileDrop()) { // We don't directly handle file drops... pass up to the parent bool FoundTarget = false; for (LViewI *p = GetParent(); p; p = p->GetParent()) { LDragDropTarget *t = p->DropTarget(); if (t) { Status = t->OnDrop(Data, Pt, KeyState); if (Status != DROPEFFECT_NONE) { FoundTarget = true; break; } } } if (!FoundTarget) { auto Wnd = GetWindow(); if (Wnd) { LDropFiles files(dd); Wnd->OnReceiveFiles(files); } } } } return Status; } void LTextView4::OnCreate() { SetWindow(this); DropTarget(true); #ifndef WINDOWS if (Ctrls.Length() == 0) #endif SetPulse(PULSE_TIMEOUT); #ifndef WINDOWS Ctrls.Add(this); #endif } void LTextView4::OnEscape(LKey &K) { } bool LTextView4::OnMouseWheel(double l) { if (VScroll) { int64 NewPos = VScroll->Value() + (int)l; NewPos = limit(NewPos, 0, (ssize_t)GetLines()); VScroll->Value(NewPos); Invalidate(); } return true; } void LTextView4::OnFocus(bool f) { Invalidate(); } ssize_t LTextView4::HitText(int x, int y, bool Nearest) { if (!Text) return 0; bool Down = y >= 0; auto Y = VScroll ? VScroll->Value() : 0; if (Y < (ssize_t)Line.Length()) y += Line[Y]->r.y1; while (Y>=0 && Y<(ssize_t)Line.Length()) { auto l = Line[Y]; if (l->r.Overlap(x, y)) { // Over a line int At = x - l->r.x1; ssize_t Char = 0; LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0); Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate); return l->Start + Char; } else if (y >= l->r.y1 && y <= l->r.y2) { // Click horizontally before of after line if (x < l->r.x1) return l->Start; else if (x > l->r.x2) return l->Start + l->Len; } if (Down) Y++; else Y--; } // outside text area if (Down) { if (Line.Length()) { if (y > Line.Last()->r.y2) { // end of document return Size; } } } return 0; } void LTextView4::Undo() { int Old = UndoQue.GetPos(); UndoQue.Undo(); if (Old && !UndoQue.GetPos()) { Dirty = false; SendNotify(LNotifyDocChanged); } } void LTextView4::Redo() { UndoQue.Redo(); } void LTextView4::DoContextMenu(LMouse &m) { LSubMenu RClick; LAutoString ClipText; { LClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } LStyle *s = HitStyle(HitText(m.x, m.y, true)); if (s) { if (OnStyleMenu(s, &RClick)) { RClick.AppendSeparator(); } } RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem("Copy All", IDM_COPY_ALL, true); RClick.AppendItem("Select All", IDM_SELECT_ALL, true); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo()); RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo()); RClick.AppendSeparator(); auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); if (Environment) Environment->AppendItems(&RClick, NULL); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(LNotifyFixedWidthChanged); break; } case IDM_CUT: { Cut(); break; } case IDM_COPY: { Copy(); break; } case IDM_PASTE: { Paste(); break; } case IDM_COPY_ALL: { SelectAll(); Copy(); break; } case IDM_SELECT_ALL: { SelectAll(); break; } case IDM_UNDO: { Undo(); break; } case IDM_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); LInput *i = new LInput(this, s, "Indent Size:", "Text"); i->DoModal([this, i](auto dlg, auto code) { if (code) IndentSize = atoi(i->GetStr()); delete i; }); break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); LInput *i = new LInput(this, s, "Tab Size:", "Text"); i->DoModal([this, i](auto dlg, auto code) { if (code) SetTabSize((uint8_t)Atoi(i->GetStr().Get())); delete i; }); break; } default: { if (s) { OnStyleMenuClick(s, Id); } if (Environment) { Environment->OnMenu(this, Id, 0); } break; } } } bool LTextView4::OnStyleClick(LStyle *style, LMouse *m) { switch (style->Owner) { case STYLE_URL: { if ( (!m) || (m->Left() && m->Down() && m->Double()) ) { LString s(Text + style->Start, style->Len); if (s) { OnUrl(s); return true; } } break; } default: break; } return false; } bool LTextView4::OnStyleMenu(LStyle *style, LSubMenu *m) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); if (LIsValidEmail(s)) m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true); else m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true); m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true); return true; } default: break; } return false; } void LTextView4::OnStyleMenuClick(LStyle *style, int i) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); switch (i) { case IDM_NEW: case IDM_OPEN: { if (s) OnUrl(s); break; } case IDM_COPY_URL: { if (s) { LClipBoard Clip(this); Clip.Text(s); } break; } } break; } default: break; } } void LTextView4::OnMouseClick(LMouse &m) { bool Processed = false; m.x += ScrollX; if (m.Down()) { if (!m.IsContextMenu()) { Focus(true); ssize_t Hit = HitText(m.x, m.y, true); if (Hit >= 0) { SetCaret(Hit, m.Shift()); LStyle *s = HitStyle(Hit); if (s) Processed = OnStyleClick(s, &m); } if (!Processed && m.Double()) { d->WordSelectMode = Cursor; SelectWord(Cursor); } else { d->WordSelectMode = -1; } } else { DoContextMenu(m); return; } } if (!Processed) { Capture(m.Down()); } } int LTextView4::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return LView::OnHitTest(x, y); } void LTextView4::OnMouseMove(LMouse &m) { m.x += ScrollX; if (!IsCapturing()) return; ssize_t Hit = HitText(m.x, m.y, true); if (d->WordSelectMode < 0) { SetCaret(Hit, m.Left()); } else { ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode; ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode; for (SelStart = Min; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = Max; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Cursor = SelEnd; Invalidate(); } } LCursor LTextView4::GetCursor(int x, int y) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); LStyle *s = NULL; if (c.Overlap(x, y)) { ssize_t Hit = HitText(x, y, true); s = HitStyle(Hit); } return s ? s->Cursor : LCUR_Ibeam; } int LTextView4::GetColumn() { int x = 0; LTextLine *l = GetTextLine(Cursor); if (l) { for (ssize_t i=l->Start; i> 1); m.Target = this; DoContextMenu(m); } else if (k.IsChar) { switch (k.vkey) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.vkey == LK_TAB) && k.c16 != 127 ) ) { if (k.Down()) { // letter/number etc if (SelStart >= 0) { bool MultiLine = false; if (k.vkey == LK_TAB) { size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd); for (size_t i=Min; iLen : 0; if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize)) { int x = GetColumn(); int Add = IndentSize - (x % IndentSize); if (HardTabs && ((x + Add) % TabSize) == 0) { int Rx = x; size_t Remove; for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--); ssize_t Chars = (ssize_t)Cursor - Remove; Delete(Remove, Chars); Insert(Remove, &k.c16, 1); Cursor = Remove + 1; Invalidate(); } else { char16 *Sp = new char16[Add]; if (Sp) { for (int n=0; nLen : 0; SetCaret(Cursor + Add, false, Len != NewLen - 1); } DeleteArray(Sp); } } } else { char16 In = k.GetChar(); if (In == '\t' && k.Shift() && Cursor > 0) { l = GetTextLine(Cursor); if (Cursor > l->Start) { if (Text[Cursor-1] == '\t') { Delete(Cursor - 1, 1); SetCaret(Cursor, false, false); } else if (Text[Cursor-1] == ' ') { ssize_t Start = (ssize_t)Cursor - 1; while (Start >= l->Start && strchr(" \t", Text[Start-1])) Start--; int Depth = SpaceDepth(Text + Start, Text + Cursor); int NewDepth = Depth - (Depth % IndentSize); if (NewDepth == Depth && NewDepth > 0) NewDepth -= IndentSize; int Use = 0; while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth) Use++; Delete(Start + Use, Cursor - Start - Use); SetCaret(Start + Use, false, false); } } } else if (In && Insert(Cursor, &In, 1)) { l = GetTextLine(Cursor); size_t NewLen = (l) ? l->Len : 0; SetCaret(Cursor + 1, false, Len != NewLen - 1); } } } return true; } break; } case LK_RETURN: #if defined MAC case LK_KEYPADENTER: #endif { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); } return true; break; } case LK_BACKSPACE: { if (GetReadOnly()) break; if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { if (SelStart >= 0) { // delete selection DeleteSelection(); } else { char Del = Cursor > 0 ? Text[Cursor-1] : 0; if (Del == ' ' && (!HardTabs || IndentSize != TabSize)) { // Delete soft tab int x = GetColumn(); int Max = x % IndentSize; if (Max == 0) Max = IndentSize; ssize_t i; for (i=Cursor-1; i>=0; i--) { if (Max-- <= 0 || Text[i] != ' ') { i++; break; } } if (i < 0) i = 0; if (i < Cursor - 1) { ssize_t Del = (ssize_t)Cursor - i; Delete(i, Del); // SetCursor(i, false, false); // Invalidate(); break; } } else if (Del == '\t' && HardTabs && IndentSize != TabSize) { int x = GetColumn(); Delete(--Cursor, 1); for (int c=GetColumn(); c 0) { Delete(Cursor - 1, 1); } } } return true; break; } } } else // not a char { switch (k.vkey) { case LK_TAB: return true; case LK_RETURN: { return !GetReadOnly(); } case LK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) { Redo(); } else { Undo(); } } } else if (k.Ctrl()) { if (k.Down()) { ssize_t Start = Cursor; while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; Delete(Cursor, Start - Cursor); Invalidate(); } } return true; } break; } case LK_F3: { if (k.Down()) { DoFindNext(NULL); } return true; break; } case LK_LEFT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MIN(SelStart, SelEnd), false); } else if (Cursor > 0) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_StartOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select bool StartWhiteSpace = IsWhiteSpace(Text[n]); bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]); if (!StartWhiteSpace || Text[n] == '\n') { n--; } // Skip ws for (; n > 0 && strchr(" \t", Text[n]); n--) ; if (Text[n] == '\n') { n--; } else if (!StartWhiteSpace || !LeftWhiteSpace) { if (IsDelimiter(Text[n])) { for (; n > 0 && IsDelimiter(Text[n]); n--); } else { for (; n > 0; n--) { //IsWordBoundry(Text[n]) if (IsWhiteSpace(Text[n]) || IsDelimiter(Text[n])) { break; } } } } if (n > 0) n++; } else { // single char n--; } SetCaret(n, k.Shift()); } } return true; break; } case LK_RIGHT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MAX(SelStart, SelEnd), false); } else if (Cursor < Size) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_EndOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select if (IsWhiteSpace(Text[n])) { for (; nStart, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len); ssize_t CharX = PrevLine.CharAt(ScreenX); SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift()); } } } return true; break; } case LK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto LTextView4_PageDown; #endif auto It = GetTextLineIt(Cursor); if (It != Line.end()) { auto l = *It; It++; if (It != Line.end()) { LTextLine *Next = *It; LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString NextLine(Font, Text + Next->Start, Next->Len); ssize_t CharX = NextLine.CharAt(ScreenX); SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift()); } } } return true; break; } case LK_END: { if (k.Down()) { if (k.Ctrl()) { SetCaret(Size, k.Shift()); } else { #ifdef MAC Jump_EndOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { SetCaret(l->Start + l->Len, k.Shift()); } } } return true; break; } case LK_HOME: { if (k.Down()) { if (k.Ctrl()) { SetCaret(0, k.Shift()); } else { #ifdef MAC Jump_StartOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { char16 *Line = Text + l->Start; char16 *s; char16 SpTab[] = {' ', '\t', 0}; for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++); ssize_t Whitespace = SubtractPtr(s, Line); if (l->Start + Whitespace == Cursor) { SetCaret(l->Start, k.Shift()); } else { SetCaret(l->Start + Whitespace, k.Shift()); } } } } return true; break; } case LK_PAGEUP: { #ifdef MAC LTextView4_PageUp: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_PAGEDOWN: { #ifdef MAC LTextView4_PageDown: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case LK_DELETE: { if (!GetReadOnly()) { if (k.Down()) { if (SelStart >= 0) { if (k.Shift()) { Cut(); } else { DeleteSelection(); } } else if (Cursor < Size && Delete(Cursor, 1)) { Invalidate(); } } return true; } break; } default: { if (k.c16 == 17) break; if (k.CtrlCmd() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } break; } case 0xbb: // Ctrl+'+' { if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } break; } case 'a': case 'A': { if (k.Down()) { // select all SelStart = 0; SelEnd = Size; Invalidate(); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) { DoFind(NULL); } return true; break; } case 'g': case 'G': { if (k.Down()) { DoGoto(NULL); } return true; break; } case 'h': case 'H': { if (k.Down()) { DoReplace(NULL); } return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) { DoCase(NULL, k.Shift()); } return true; } break; } case LK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void LTextView4::OnEnter(LKey &k) { // enter if (SelStart >= 0) { DeleteSelection(); } char16 InsertStr[256] = {'\n', 0}; LTextLine *CurLine = GetTextLine(Cursor); if (CurLine && AutoIndent) { int WsLen = 0; for (; WsLen < CurLine->Len && WsLen < (Cursor - CurLine->Start) && strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++); if (WsLen > 0) { memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16)); InsertStr[WsLen+1] = 0; } } if (Insert(Cursor, InsertStr, StrlenW(InsertStr))) { SetCaret(Cursor + StrlenW(InsertStr), false, true); } } int LTextView4::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin) { int w = x; int Size = f->TabSize(); for (char16 *c = s; SubtractPtr(c, s) < Len; ) { if (*c == 9) { w = ((((w-Origin) + Size) / Size) * Size) + Origin; c++; } else { char16 *e; for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++); LDisplayString ds(f, c, SubtractPtr(e, c)); w += ds.X(); c = e; } } return w - x; } int LTextView4::ScrollYLine() { return (VScroll) ? (int)VScroll->Value() : 0; } int LTextView4::ScrollYPixel() { return ScrollYLine() * LineY; } LRect LTextView4::DocToScreen(LRect r) { r.Offset(0, d->rPadding.y1 - ScrollYPixel()); return r; } void LTextView4::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void LTextView4::OnPaint(LSurface *pDC) { #if LGI_EXCEPTIONS try { #endif #if PROFILE_PAINT char s[256]; sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(s); #endif if (d->LayoutDirty) { #if PROFILE_PAINT Prof.Add("PourText"); #endif PourText(d->DirtyStart, d->DirtyLen); #if PROFILE_PAINT Prof.Add("PourStyle"); #endif PourStyle(d->DirtyStart, d->DirtyLen); d->LayoutDirty = false; } #if PROFILE_PAINT Prof.Add("Setup"); #endif LRect r = GetClient(); r.x2 += ScrollX; int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox+ScrollX, Oy); #if 0 // Coverage testing... pDC->Colour(Rgb24(255, 0, 255), 24); pDC->Rectangle(); #endif LSurface *pOut = pDC; bool DrawSel = false; bool HasFocus = Focus(); // printf("%s:%i - HasFocus = %i\n", _FL, HasFocus); LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE)); LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK)); LCss::ColorDef ForeDef, BkDef; if (GetCss()) { ForeDef = GetCss()->Color(); BkDef = GetCss()->BackgroundColor(); } LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT)); LColour Back ( /*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb ? LColour(BkDef.Rgb32, 32) : Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED) ); // LColour Whitespace = Fore.Mix(Back, 0.85f); if (!Enabled()) { Fore = LColour(L_LOW); Back = LColour(L_MED); } #ifdef DOUBLE_BUFFER_PAINT LMemDC *pMem = new LMemDC; pOut = pMem; #endif if (Text && Font #ifdef DOUBLE_BUFFER_PAINT && pMem && pMem->Create(r.X()-d->rPadding.x1, LineY, GdcD->GetBits()) #endif ) { ssize_t SelMin = MIN(SelStart, SelEnd); ssize_t SelMax = MAX(SelStart, SelEnd); // font properties Font->Colour(Fore, Back); // Font->WhitespaceColour(Whitespace); Font->Transparent(false); // draw margins pDC->Colour(PAINT_BORDER); // top margin pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1); // left margin { LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2); OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER); } // draw lines of text int k = ScrollYLine(); LTextLine *l = NULL; int Dy = 0; if (k < Line.Length()) Dy = -Line[k]->r.y1; ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes if (k < Line.Length() && (l = Line[k]) && SelStart >= 0 && SelStart < l->Start && SelEnd > l->Start) { // start of visible area is in selection // init to selection colour DrawSel = true; Font->Colour(SelectedText, SelectedBack); NextSelection = SelMax; } StyleIter Si = Style.begin(); LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0); DocOffset = (l) ? l->r.y1 : 0; #if PROFILE_PAINT Prof.Add("foreach Line loop"); #endif // loop through all visible lines int y = d->rPadding.y1; while ( k < Line.Length() && (l = Line[k]) && l->r.y1+Dy < r.Y()) { LRect Tr = l->r; #ifdef DOUBLE_BUFFER_PAINT Tr.Offset(-Tr.x1, -Tr.y1); #else Tr.Offset(0, y - Tr.y1); #endif //LRect OldTr = Tr; // deal with selection change on beginning of line if (NextSelection == l->Start) { // selection change DrawSel = !DrawSel; NextSelection = (NextSelection == SelMin) ? SelMax : -1; } if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } // How many chars on this line have we // processed so far: ssize_t Done = 0; bool LineHasSelection = NextSelection >= l->Start && NextSelection < l->Start + l->Len; // Fractional pixels we have moved so far: int MarginF = d->rPadding.x1 << LDisplayString::FShift; int FX = MarginF; int FY = Tr.y1 << LDisplayString::FShift; // loop through all sections of similar text on a line while (Done < l->Len) { // decide how big this block is int RtlTrailingSpace = 0; ssize_t Cur = l->Start + Done; ssize_t Block = l->Len - Done; // check for style change if (NextStyle && (ssize_t)NextStyle->End() <= l->Start) NextStyle = GetNextStyle(Si); if (NextStyle) { // start if (l->Overlap(NextStyle->Start) && NextStyle->Start > Cur && NextStyle->Start - Cur < Block) { Block = NextStyle->Start - Cur; } // end ssize_t StyleEnd = NextStyle->Start + NextStyle->Len; if (l->Overlap(StyleEnd) && StyleEnd > Cur && StyleEnd - Cur < Block) { Block = StyleEnd - Cur; } } // check for next selection change // this may truncate the style if (NextSelection > Cur && NextSelection - Cur < Block) { Block = NextSelection - Cur; } LAssert(Block != 0); // sanity check if (NextStyle && // There is a style (Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block Cur >= NextStyle->Start && // && we're inside the styled area Cur < NextStyle->Start+NextStyle->Len) { LFont *Sf = NextStyle->Font ? NextStyle->Font : Font; if (Sf) { // draw styled text if (NextStyle->Fore.IsValid()) Sf->Fore(NextStyle->Fore); if (NextStyle->Back.IsValid()) Sf->Back(NextStyle->Back); else if (l->Back.IsValid()) Sf->Back(l->Back); else Sf->Back(Back); Sf->Transparent(false); LAssert(l->Start + Done >= 0); LDisplayString Ds( Sf, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); if (NextStyle->Decor == LCss::TextDecorSquiggle) { pOut->Colour(NextStyle->DecorColour); int x = FX >> LDisplayString::FShift; int End = x + Ds.X(); while (x < End) { pOut->Set(x, Tr.y2-(x%2)); x++; } } FX += Ds.FX(); LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Sf->Colour(fore, back); } else LAssert(0); } else { // draw a block of normal text LAssert(l->Start + Done >= 0); LDisplayString Ds( Font, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); FX += Ds.FX(); } if (NextStyle && Cur+Block >= NextStyle->Start+NextStyle->Len) { // end of this styled block NextStyle = GetNextStyle(Si); } if (NextSelection == Cur+Block) { // selection change DrawSel = !DrawSel; if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } NextSelection = (NextSelection == SelMin) ? SelMax : -1; } Done += Block + RtlTrailingSpace; } // end block loop Tr.x1 = FX >> LDisplayString::FShift; // eol processing ssize_t EndOfLine = l->Start+l->Len; if (EndOfLine >= SelMin && EndOfLine < SelMax) { // draw the '\n' at the end of the line as selected // LColour bk = Font->Back(); pOut->Colour(Font->Back()); pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2); Tr.x2 += 7; } else Tr.x2 = Tr.x1; // draw any space after text pOut->Colour(PAINT_AFTER_LINE); pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2); // cursor? if (HasFocus) { // draw the cursor if on this line if (Cursor >= l->Start && Cursor <= l->Start+l->Len) { CursorPos.ZOff(1, LineY-1); ssize_t At = Cursor-l->Start; LDisplayString Ds(Font, MapText(Text+l->Start, At), At); Ds.ShowVisibleTab(ShowWhiteSpace); int CursorX = Ds.X(); CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1); if (CanScrollX) { // Cursor on screen check LRect Scr = GetClient(); Scr.Offset(ScrollX, 0); LRect Cur = CursorPos; if (Cur.x2 > Scr.x2 - 5) // right edge check { ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40; Invalidate(); } else if (Cur.x1 < Scr.x1 && ScrollX > 0) { ScrollX = MAX(0, Cur.x1 - 40); Invalidate(); } } if (Blink) { LRect c = CursorPos; #ifdef DOUBLE_BUFFER_PAINT c.Offset(-d->rPadding.x1, -y); #endif pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192)); pOut->Rectangle(&c); } #if WINNATIVE HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; ImmSetCompositionWindow(hIMC, &Cf); ImmReleaseContext(Handle(), hIMC); } #endif } } #if DRAW_LINE_BOXES { uint Style = pDC->LineStyle(LSurface::LineAlternate); LColour Old = pDC->Colour(LColour::Red); pDC->Box(&OldTr); pDC->Colour(Old); pDC->LineStyle(Style); LString s; s.Printf("%i, %i", Line.IndexOf(l), l->Start); LDisplayString ds(LSysFont, s); LSysFont->Transparent(true); ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1); } #endif #ifdef DOUBLE_BUFFER_PAINT // dump to screen pDC->Blt(d->rPadding.x1, y, pOut); #endif y += LineY; k++; } // end of line loop // draw any space under the lines if (y <= r.y2) { pDC->Colour(Back); // pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2); } #ifdef DOUBLE_BUFFER_PAINT DeleteObj(pMem); #endif } else { // default drawing: nothing pDC->Colour(Back); pDC->Rectangle(&r); } // _PaintTime = LCurrentTime() - StartTime; #ifdef PAINT_DEBUG if (GetNotify()) { char s[256]; sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime); LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s); GetNotify()->OnEvent(&m); } #endif // printf("PaintTime: %ims\n", _PaintTime); #if LGI_EXCEPTIONS } catch (...) { LgiMsg(this, "LTextView4::OnPaint crashed.", "Lgi"); } #endif } LMessage::Result LTextView4::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_TEXT_UPDATE_NAME: { if (d->Lock(_FL)) { Name(d->SetName); d->SetName.Empty(); d->Unlock(); } break; } case M_TEXTVIEW_FIND: { if (InThread()) DoFindNext(NULL); else LgiTrace("%s:%i - Not in thread.\n", _FL); break; } case M_TEXTVIEW_REPLACE: { // DoReplace(); break; } case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return Size; } case WM_GETTEXT: { int Chars = (int)Msg->A(); char *Out = (char*)Msg->B(); if (Out) { char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return LLayout::OnEvent(Msg); } int LTextView4::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { if (n.Type == LNotifyScrollBarCreate) { UpdateScrollBars(); } Invalidate(); } return 0; } void LTextView4::InternalPulse() { if (!ReadOnly) { uint64 Now = LCurrentTime(); if (!BlinkTs) BlinkTs = Now; else if (Now - BlinkTs > CURSOR_BLINK) { Blink = !Blink; LRect p = CursorPos; p.Offset(-ScrollX, 0); Invalidate(&p); BlinkTs = Now; } } if (PartialPour) PourText(Size, 0); } void LTextView4::OnPulse() { #ifdef WINDOWS InternalPulse(); #else for (auto c: Ctrls) c->InternalPulse(); #endif } void LTextView4::OnUrl(char *Url) { if (Environment) Environment->OnNavigate(this, Url); else { LUri u(Url); bool Email = LIsValidEmail(Url); const char *Proto = Email ? "mailto" : u.sProtocol; LString App = LGetAppForProtocol(Proto); if (App) LExecute(App, Url); else LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto); } } bool LTextView4::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } #if DEBUG_EDIT_LOG #include "lgi/common/StructuredIo.h" inline void StructIo(LStructuredIo &io, LTextView4::EditLog &s) { auto obj = io.StartObj("LTextView4::EditLog"); io.Int(s.Type, "type"); io.Int(s.Length, "len"); if (io.GetWrite()) { io.String(s.Str.AddressOf(), s.Str.Length(), "str"); } else { io.Decode([&s](auto type, auto sz, auto ptr, auto name) { if (type == GV_WSTRING && ptr && sz > 0) s.Str.Add((char16*)ptr, sz/sizeof(char16)); }); } } inline void StructIo(LStructuredIo &io, LTextView4::LTextLine &s) { auto obj = io.StartObj("LTextView4::LTextLine"); io.Int(s.Start, "start"); io.Int(s.Len, "len"); io.String(s.File, Strlen(s.File), "file"); io.Int(s.Line, "line"); StructIo(io, s.r); StructIo(io, s.c); StructIo(io, s.Back); io.Int(s.NewLine, "newLine"); } #include "lgi/common/StructuredLog.h" void LTextView4::SaveLog(const char *File) { if (!FirstErrorLog) return; LStructuredLog log(File, true); auto &io = log.GetIo(); auto lines = LogLines(); for (auto e: Edits) log.Log(*e); for (auto ln: Line) log.Log(*ln); log.Log("Size:", Size); io.String(Text, Size, "Text"); io.String(d->LastError, "LastError"); io.String(lines, "Lines"); FirstErrorLog = false; } void LTextView4::LoadLog(const char *File) { LStructuredLog log(File, false); Edits.DeleteObjects(); log.Read([this](auto type, auto size, auto ptr, auto msg) { int asd=0; }); } #endif /////////////////////////////////////////////////////////////////////////////// class LTextView4_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LTextView4") == 0) { return new LTextView4(-1, 0, 0, 2000, 2000); } return 0; } } TextView4_Factory; diff --git a/src/common/Widgets/CheckBox.cpp b/src/common/Widgets/CheckBox.cpp --- a/src/common/Widgets/CheckBox.cpp +++ b/src/common/Widgets/CheckBox.cpp @@ -1,468 +1,464 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/CheckBox.h" #include "lgi/common/DisplayString.h" #include "lgi/common/StringLayout.h" #include "lgi/common/LgiRes.h" static int PadX1Px = 20; static int PadX2Px = 6; #ifdef MAC static int PadYPx = 8; static int TextYOffset = 6; #else static int PadYPx = 0; static int TextYOffset = 0; #endif static int MinYSize = 16; class LCheckBoxPrivate : public LMutex, public LStringLayout { LCheckBox *Ctrl; public: int64 Val; bool Over; bool Three; LRect ValuePos; LCheckBoxPrivate(LCheckBox *ctrl) : LMutex("LCheckBoxPrivate"), LStringLayout(LAppInst->GetFontCache()) { Ctrl = ctrl; Val = 0; Over = false; Three = false; Wrap = true; AmpersandToUnderline = true; ValuePos.ZOff(-1, -1); } bool PreLayout(int32 &Min, int32 &Max) { if (Lock(_FL)) { DoPreLayout(Min, Max); Unlock(); } else return false; return true; } bool Layout(int Px) { if (Lock(_FL)) { DoLayout(Px, MinYSize); Unlock(); } else return false; return true; } }; /////////////////////////////////////////////////////////////////////////////////////////// // Check box LCheckBox::LCheckBox(int id, int x, int y, int cx, int cy, const char *name, int InitState) : ResObject(Res_CheckBox) { d = new LCheckBoxPrivate(this); Name(name); LPoint Max = d->GetMax(); if (cx < 0) cx = Max.x + PadX1Px + PadX2Px; if (cy < 0) cy = MAX(Max.y, MinYSize) + PadYPx; d->Val = InitState; LRect r(x, y, x+cx, y+cy); SetPos(r); SetId(id); SetTabStop(true); } LCheckBox::~LCheckBox() { DeleteObj(d); } #ifdef WINNATIVE int LCheckBox::SysOnNotify(int Msg, int Code) { return 0; } #endif void LCheckBox::OnAttach() { LResources::StyleElement(this); OnStyleChange(); LView::OnAttach(); } void LCheckBox::OnStyleChange() { if (d->Lock(_FL)) { d->Empty(); d->Add(LView::Name(), GetCss()); d->DoLayout(X()); d->Unlock(); Invalidate(); } } int LCheckBox::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl == (LViewI*)this && n.Type == LNotifyActivate) { Value(!Value()); } return 0; } LMessage::Result LCheckBox::OnEvent(LMessage *m) { return LView::OnEvent(m); } bool LCheckBox::ThreeState() { return d->Three; } void LCheckBox::ThreeState(bool t) { d->Three = t; } int64 LCheckBox::Value() { return d->Val; } void LCheckBox::Value(int64 i) { if (d->Val != i) { d->Val = i; Invalidate(&d->ValuePos); SendNotify(LNotifyValueChanged); } } bool LCheckBox::Name(const char *n) { bool Status = false; if (d->Lock(_FL)) { Status = LView::Name(n); d->Empty(); d->Add(n, GetCss()); d->SetBaseFont(GetFont()); auto x = X(); d->DoLayout(x ? x : GdcD->X()); d->Unlock(); } return Status; } bool LCheckBox::NameW(const char16 *n) { bool Status = false; if (d->Lock(_FL)) { Status = LView::NameW(n); d->Empty(); d->Add(LBase::Name(), GetCss()); d->SetBaseFont(GetFont()); auto x = X(); d->DoLayout(x ? x : GdcD->X()); d->Unlock(); } return Status; } void LCheckBox::SetFont(LFont *Fnt, bool OwnIt) { LAssert(Fnt && Fnt->Handle()); if (d->Lock(_FL)) { LView::SetFont(Fnt, OwnIt); d->Unlock(); } d->Layout(X()); Invalidate(); } void LCheckBox::OnMouseClick(LMouse &m) { if (Enabled()) { int Click = IsCapturing(); Capture(d->Over = m.Down()); if (m.Down()) { Focus(true); } LRect r(0, 0, X()-1, Y()-1); if (!m.Down() && r.Overlap(m.x, m.y) && Click) { if (d->Three) { switch (d->Val) { case 0: Value(2); break; case 2: Value(1); break; default: Value(0); break; } } else { Value(!d->Val); } } else { Invalidate(&d->ValuePos); } } } void LCheckBox::OnMouseEnter(LMouse &m) { if (IsCapturing()) { d->Over = true; Invalidate(&d->ValuePos); } } void LCheckBox::OnMouseExit(LMouse &m) { if (IsCapturing()) { d->Over = false; Invalidate(&d->ValuePos); } } bool LCheckBox::OnKey(LKey &k) { switch (k.vkey) { case LK_SPACE: { if (!k.Down()) Value(!Value()); return true; } } return false; } void LCheckBox::OnFocus(bool f) { Invalidate(); } void LCheckBox::OnPosChange() { d->Layout(X()); } bool LCheckBox::OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Min) { auto n = Name(); if (n) { d->PreLayout(Inf.Width.Min, Inf.Width.Max); // FIXME: no wrapping support so.... Inf.Width.Min = Inf.Width.Max; Inf.Width.Min += PadX1Px + PadX2Px; Inf.Width.Max += PadX1Px + PadX2Px; } else { auto Fnt = GetFont(); - Inf.Width.Min = Inf.Width.Max = Fnt->Ascent() + 2; + Inf.Width.Min = Inf.Width.Max = (int32)(Fnt->Ascent() + 2); } } else { d->Layout(Inf.Width.Max); Inf.Height.Min = d->GetMin().y + PadYPx; Inf.Height.Max = d->GetMax().y + PadYPx; } return true; } int LCheckBox::BoxSize() { auto Fnt = GetFont(); int Px = (int) Fnt->Ascent() + 2; - - printf("BoxSize Px=%i Y()=%i\n", Px, Y()); - if (Px > Y()) Px = Y(); - return Px; } void LCheckBox::OnPaint(LSurface *pDC) { #if 0 pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif if (LApp::SkinEngine && TestFlag(LApp::SkinEngine->GetFeatures(), GSKIN_CHECKBOX)) { // auto Fnt = GetFont(); int Px = BoxSize(); LSkinState State; State.pScreen = pDC; State.MouseOver = d->Over; State.aText = d->GetStrs(); d->ValuePos.Set(0, 0, Px-1, Px-1); d->ValuePos.Offset(0, (Y()-d->ValuePos.Y())>>1); State.Rect = d->ValuePos; LApp::SkinEngine->OnPaint_LCheckBox(this, &State); } else { bool en = Enabled(); LRect r = GetClient(); #if defined MAC && !LGI_COCOA d->ValuePos.Set(0, 0, PadX1Px, MinYSize); #else d->ValuePos.Set(0, 0, 12, 12); #endif if (d->ValuePos.y2 < r.y2) { pDC->Colour(L_MED); pDC->Rectangle(0, d->ValuePos.y2+1, d->ValuePos.x2, r.y2); } LRect t = r; t.x1 = d->ValuePos.x2 + 1; // LColour cFore = StyleColour(LCss::PropColor, LC_TEXT); LColour cBack = StyleColour(LCss::PropBackgroundColor, LColour(L_MED)); if (d->Lock(_FL)) { int Ty = MAX(0, r.Y() - d->GetBounds().Y()) >> 1; LPoint pt(t.x1, t.y1 + MIN(Ty, TextYOffset)); d->Paint(pDC, pt, cBack, t, en, false); d->Unlock(); } #if defined LGI_CARBON if (!cBack.IsTransparent()) { pDC->Colour(cBack); pDC->Rectangle(d->ValuePos.x1, d->ValuePos.y1, d->ValuePos.x2, Y()-1); } LRect c = GetClient(); #if 0 pDC->Colour(LColour(255, 0, 0)); pDC->Box(&c); pDC->Line(c.x1, c.y1, c.x2, c.y2); pDC->Line(c.x2, c.y1, c.x1, c.y2); #endif for (LViewI *v = this; v && !v->Handle(); v = v->GetParent()) { LRect p = v->GetPos(); c.Offset(p.x1, p.y1); } HIRect Bounds = c; HIThemeButtonDrawInfo Info; HIRect LabelRect; Info.version = 0; Info.state = d->Val ? kThemeStatePressed : (Enabled() ? kThemeStateActive : kThemeStateInactive); Info.kind = kThemeCheckBox; Info.value = d->Val ? kThemeButtonOn : kThemeButtonOff; Info.adornment = Focus() ? kThemeAdornmentFocus : kThemeAdornmentNone; OSStatus e = HIThemeDrawButton( &Bounds, &Info, pDC->Handle(), kHIThemeOrientationNormal, &LabelRect); if (e) printf("%s:%i - HIThemeDrawButton failed %li\n", _FL, e); #else LWideBorder(pDC, d->ValuePos, DefaultSunkenEdge); pDC->Colour(d->Over || !en ? L_MED : L_WORKSPACE); pDC->Rectangle(&d->ValuePos); pDC->Colour(en ? L_TEXT : L_LOW); if (d->Three && d->Val == 2) { for (int y=d->ValuePos.y1; y<=d->ValuePos.y2; y++) { for (int x=d->ValuePos.x1; x<=d->ValuePos.x2; x++) { if ( (x&1) ^ (y&1) ) { pDC->Set(x, y); } } } } else if (d->Val) { pDC->Line(d->ValuePos.x1+1, d->ValuePos.y1+1, d->ValuePos.x2-1, d->ValuePos.y2-1); pDC->Line(d->ValuePos.x1+1, d->ValuePos.y1+2, d->ValuePos.x2-2, d->ValuePos.y2-1); pDC->Line(d->ValuePos.x1+2, d->ValuePos.y1+1, d->ValuePos.x2-1, d->ValuePos.y2-2); pDC->Line(d->ValuePos.x1+1, d->ValuePos.y2-1, d->ValuePos.x2-1, d->ValuePos.y1+1); pDC->Line(d->ValuePos.x1+1, d->ValuePos.y2-2, d->ValuePos.x2-2, d->ValuePos.y1+1); pDC->Line(d->ValuePos.x1+2, d->ValuePos.y2-1, d->ValuePos.x2-1, d->ValuePos.y1+2); } #endif } } diff --git a/src/common/Widgets/Editor/RichTextEdit.cpp b/src/common/Widgets/Editor/RichTextEdit.cpp --- a/src/common/Widgets/Editor/RichTextEdit.cpp +++ b/src/common/Widgets/Editor/RichTextEdit.cpp @@ -1,3097 +1,3099 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/RichTextEdit.h" #include "lgi/common/Input.h" #include "lgi/common/ScrollBar.h" #ifdef WIN32 #include #endif #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/FontCache.h" #include "lgi/common/Unicode.h" #include "lgi/common/DropFiles.h" #include "lgi/common/HtmlCommon.h" #include "lgi/common/HtmlParser.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/Homoglyphs.h" // If this is not found add $lgi/private/common to your include paths #include "ViewPriv.h" #define DefaultCharset "utf-8" #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define ALLOC_BLOCK 64 #define IDC_VS 1000 #define PAINT_BORDER Back #define PAINT_AFTER_LINE Back #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif // static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #include "RichTextEditPriv.h" ////////////////////////////////////////////////////////////////////// LRichTextEdit::LRichTextEdit( int Id, int x, int y, int cx, int cy, LFontType *FontType) : ResObject(Res_Custom) { // init vars LView::d->Css.Reset(new LRichTextPriv(this, &d)); // setup window SetId(Id); SetTabStop(true); // default options #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #else CrLf = false; #endif d->Padding(LCss::Len(LCss::LenPx, 4)); #if 0 d->BackgroundColor(LCss::ColorDef(LColour::Green)); #else d->BackgroundColor(LColour(L_WORKSPACE)); #endif SetFont(LSysFont); #if 0 // def _DEBUG Name("\n" "\n" " This is some bold text to test with.
\n" " A second line of text for testing.\n" "\n" "\n"); #endif } LRichTextEdit::~LRichTextEdit() { // 'd' is owned by the LView CSS autoptr. } bool LRichTextEdit::SetSpellCheck(LSpellCheck *sp) { if ((d->SpellCheck = sp)) { if (IsAttached()) d->SpellCheck->EnumLanguages(AddDispatch()); // else call that OnCreate } return d->SpellCheck != NULL; } bool LRichTextEdit::IsDirty() { return d->Dirty; } void LRichTextEdit::IsDirty(bool dirty) { if (d->Dirty ^ dirty) { d->Dirty = dirty; } } void LRichTextEdit::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LDocView::SetFixedWidthFont(i); } } OnFontChange(); Invalidate(); } } void LRichTextEdit::SetReadOnly(bool i) { LDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } LRect LRichTextEdit::GetArea(RectType Type) { return Type >= ContentArea && Type <= MaxArea ? d->Areas[Type] : LRect(0, 0, -1, -1); } bool LRichTextEdit::ShowStyleTools() { return d->ShowTools; } void LRichTextEdit::ShowStyleTools(bool b) { if (d->ShowTools ^ b) { d->ShowTools = b; Invalidate(); } } void LRichTextEdit::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void LRichTextEdit::SetWrapType(LDocWrapType i) { LDocView::SetWrapType(i); OnPosChange(); Invalidate(); } LFont *LRichTextEdit::GetFont() { return d->Font; } void LRichTextEdit::SetFont(LFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { d->Font.Reset(f); } else if (d->Font.Reset(new LFont)) { *d->Font = *f; d->Font->Create(NULL, 0, 0); } OnFontChange(); } void LRichTextEdit::OnFontChange() { } void LRichTextEdit::PourText(ssize_t Start, ssize_t Length /* == 0 means it's a delete */) { } void LRichTextEdit::PourStyle(ssize_t Start, ssize_t EditSize) { } bool LRichTextEdit::Insert(int At, char16 *Data, int Len) { return false; } bool LRichTextEdit::Delete(int At, int Len) { return false; } bool LRichTextEdit::DeleteSelection(char16 **Cut) { AutoTrans t(new LRichTextPriv::Transaction); if (!d->DeleteSelection(t, Cut)) return false; return d->AddTrans(t); } int64 LRichTextEdit::Value() { const char *n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void LRichTextEdit::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } bool LRichTextEdit::GetFormattedContent(const char *MimeType, LString &Out, LArray *Media) { if (!MimeType || _stricmp(MimeType, "text/html")) return false; if (!d->ToHtml(Media)) return false; Out = d->UtfNameCache; return true; } const char *LRichTextEdit::Name() { d->ToHtml(); return d->UtfNameCache; } const char *LRichTextEdit::GetCharset() { return d->Charset; } void LRichTextEdit::SetCharset(const char *s) { d->Charset = s; } bool LRichTextEdit::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case HtmlImagesLinkCid: { Value = d->HtmlLinkAsCid; break; } case SpellCheckLanguage: { Value = d->SpellLang.Get(); break; } case SpellCheckDictionary: { Value = d->SpellDict.Get(); break; } default: return false; } return true; } bool LRichTextEdit::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case HtmlImagesLinkCid: { d->HtmlLinkAsCid = Value.CastInt32() != 0; break; } case SpellCheckLanguage: { d->SpellLang = Value.Str(); break; } case SpellCheckDictionary: { d->SpellDict = Value.Str(); break; } default: return false; } return true; } static LHtmlElement *FindElement(LHtmlElement *e, HtmlTag TagId) { if (e->TagId == TagId) return e; for (unsigned i = 0; i < e->Children.Length(); i++) { LHtmlElement *c = FindElement(e->Children[i], TagId); if (c) return c; } return NULL; } void LRichTextEdit::OnAddStyle(const char *MimeType, const char *Styles) { if (d->CreationCtx) { d->CreationCtx->StyleStore.Parse(Styles); } } bool LRichTextEdit::Name(const char *s) { d->Empty(); d->OriginalText = s; LHtmlElement Root(NULL); if (!d->CreationCtx.Reset(new LRichTextPriv::CreateContext(d))) return false; if (!d->LHtmlParser::Parse(&Root, s)) return d->Error(_FL, "Failed to parse HTML."); LHtmlElement *Body = FindElement(&Root, TAG_BODY); if (!Body) Body = &Root; bool Status = d->FromHtml(Body, *d->CreationCtx); // d->DumpBlocks(); if (!d->Blocks.Length()) { d->EmptyDoc(); } else { // Clear out any zero length blocks. for (unsigned i=0; iBlocks.Length(); i++) { LRichTextPriv::Block *b = d->Blocks[i]; if (b->Length() == 0) { d->Blocks.DeleteAt(i--, true); DeleteObj(b); } } } if (Status) SetCursor(0, false); Invalidate(); return Status; } const char16 *LRichTextEdit::NameW() { d->WideNameCache.Reset(Utf8ToWide(Name())); return d->WideNameCache; } bool LRichTextEdit::NameW(const char16 *s) { LAutoString a(WideToUtf8(s)); return Name(a); } char *LRichTextEdit::GetSelection() { if (!HasSelection()) return NULL; LArray Text; if (!d->GetSelection(&Text, NULL)) return NULL; return WideToUtf8(&Text[0]); } bool LRichTextEdit::HasSelection() { return d->Selection.Get() != NULL; } void LRichTextEdit::SelectAll() { AutoCursor Start(new BlkCursor(d->Blocks.First(), 0, 0)); d->SetCursor(Start); LRichTextPriv::Block *Last = d->Blocks.Length() ? d->Blocks.Last() : NULL; if (Last) { AutoCursor End(new BlkCursor(Last, Last->Length(), Last->GetLines()-1)); d->SetCursor(End, true); } else d->Selection.Reset(); Invalidate(); } void LRichTextEdit::UnSelectAll() { bool Update = HasSelection(); if (Update) { d->Selection.Reset(); Invalidate(); } } void LRichTextEdit::SetStylePrefix(LString s) { d->SetPrefix(s); } bool LRichTextEdit::IsBusy(bool Stop) { return d->IsBusy(Stop); } size_t LRichTextEdit::GetLines() { uint32_t Count = 0; for (size_t i=0; iBlocks.Length(); i++) { LRichTextPriv::Block *b = d->Blocks[i]; Count += b->GetLines(); } return Count; } int LRichTextEdit::GetLine() { if (!d->Cursor) return -1; ssize_t Idx = d->Blocks.IndexOf(d->Cursor->Blk); if (Idx < 0) { LAssert(0); return -1; } int Count = 0; // Count lines in blocks before the cursor... for (int i=0; iBlocks[i]; Count += b->GetLines(); } // Add the lines in the cursor's block... if (d->Cursor->LineHint) { Count += d->Cursor->LineHint; } else { LArray BlockLine; if (d->Cursor->Blk->OffsetToLine(d->Cursor->Offset, NULL, &BlockLine)) Count += BlockLine.First(); else { // Hmmm... LAssert(!"Can't find block line."); return -1; } } return Count; } void LRichTextEdit::SetLine(int Line) { int Count = 0; // Count lines in blocks before the cursor... for (int i=0; i<(int)d->Blocks.Length(); i++) { LRichTextPriv::Block *b = d->Blocks[i]; int Lines = b->GetLines(); if (Line >= Count && Line < Count + Lines) { auto BlockLine = Line - Count; auto Offset = b->LineToOffset(BlockLine); if (Offset >= 0) { AutoCursor c(new BlkCursor(b, Offset, BlockLine)); d->SetCursor(c); break; } } Count += Lines; } } void LRichTextEdit::GetTextExtent(int &x, int &y) { x = d->DocumentExtent.x; y = d->DocumentExtent.y; } bool LRichTextEdit::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index) { ssize_t Offset = -1; int BlockLines = -1; LRichTextPriv::Block *b = d->GetBlockByIndex(Index, &Offset, NULL, &BlockLines); if (!b) return false; int Cols; LArray Lines; if (b->OffsetToLine(Offset, &Cols, &Lines)) return false; Pt.x = Cols; Pt.y = BlockLines + Lines.First(); return true; } ssize_t LRichTextEdit::GetCaret(bool Cur) { if (!d->Cursor) return -1; ssize_t CharPos = 0; for (ssize_t i=0; i<(ssize_t)d->Blocks.Length(); i++) { LRichTextPriv::Block *b = d->Blocks[i]; if (d->Cursor->Blk == b) return CharPos + d->Cursor->Offset; CharPos += b->Length(); } LAssert(!"Cursor block not found."); return -1; } bool LRichTextEdit::IndexAt(int x, int y, ssize_t &Off, int &LineHint) { LPoint Doc = d->ScreenToDoc(x, y); Off = d->HitTest(Doc.x, Doc.y, LineHint); return Off >= 0; } ssize_t LRichTextEdit::IndexAt(int x, int y) { ssize_t Idx; int Line; if (!IndexAt(x, y, Idx, Line)) return -1; return Idx; } void LRichTextEdit::SetCursor(int i, bool Select, bool ForceFullUpdate) { ssize_t Offset = -1; LRichTextPriv::Block *Blk = d->GetBlockByIndex(i, &Offset); if (Blk) { AutoCursor c(new BlkCursor(Blk, Offset, -1)); if (c) d->SetCursor(c, Select); } } bool LRichTextEdit::Cut() { if (!HasSelection()) return false; char16 *Txt = NULL; if (!DeleteSelection(&Txt)) return false; bool Status = true; if (Txt) { LClipBoard Cb(this); Status = Cb.TextW(Txt); DeleteArray(Txt); } SendNotify(LNotifyDocChanged); return Status; } bool LRichTextEdit::Copy() { if (!HasSelection()) return false; LArray PlainText; LAutoString Html; if (!d->GetSelection(&PlainText, &Html)) return false; LString PlainUtf8 = PlainText.AddressOf(); if (HasHomoglyphs(PlainUtf8, PlainUtf8.Length())) { if (LgiMsg( this, LLoadString(L_TEXTCTRL_HOMOGLYPH_WARNING, "Text contains homoglyph characters that maybe a phishing attack.\n" "Do you really want to copy it?"), LLoadString(L_TEXTCTRL_WARNING, "Warning"), MB_YESNO) == IDNO) return false; } // Put on the clipboard LClipBoard Cb(this); bool Status = Cb.TextW(PlainText.AddressOf()); Cb.Html(Html, false); return Status; } bool LRichTextEdit::Paste() { LClipBoard Cb(this); - return Cb.Bitmap([this](auto bmp, auto str) + Cb.Bitmap([this](auto bmp, auto str) { LString Html; LAutoWString Text; LAutoPtr Img; Img = bmp; if (!Img) { LClipBoard Cb(this); Html = Cb.Html(); if (!Html) Text.Reset(NewStrW(Cb.TextW())); } if (!Html && !Text && !Img) return false; if (!d->Cursor || !d->Cursor->Blk) { LAssert(0); return false; } AutoTrans Trans(new LRichTextPriv::Transaction); if (HasSelection()) { if (!d->DeleteSelection(Trans, NULL)) return false; } if (Html) { LHtmlElement Root(NULL); if (!d->CreationCtx.Reset(new LRichTextPriv::CreateContext(d))) return false; if (!d->LHtmlParser::Parse(&Root, Html)) return d->Error(_FL, "Failed to parse HTML."); LHtmlElement *Body = FindElement(&Root, TAG_BODY); if (!Body) Body = &Root; if (d->Cursor) { auto *b = d->Cursor->Blk; ssize_t BlkIdx = d->Blocks.IndexOf(b); LRichTextPriv::Block *After = NULL; ssize_t AddIndex = BlkIdx;; // Split 'b' to make room for pasted objects if (d->Cursor->Offset > 0) { After = b->Split(Trans, d->Cursor->Offset); AddIndex = BlkIdx+1; } // else Insert before cursor block auto *PastePoint = new LRichTextPriv::TextBlock(d); if (PastePoint) { d->Blocks.AddAt(AddIndex++, PastePoint); if (After) d->Blocks.AddAt(AddIndex++, After); d->CreationCtx->Tb = PastePoint; d->FromHtml(Body, *d->CreationCtx); } } } else if (Text) { LAutoPtr Utf32((uint32_t*)LNewConvertCp("utf-32", Text, LGI_WideCharset)); ptrdiff_t Len = Strlen(Utf32.Get()); if (!d->Cursor->Blk->AddText(Trans, d->Cursor->Offset, Utf32.Get(), (int)Len)) { LAssert(0); return false; } d->Cursor->Offset += Len; d->Cursor->LineHint = -1; } else if (Img) { LRichTextPriv::Block *b = d->Cursor->Blk; ssize_t BlkIdx = d->Blocks.IndexOf(b); LRichTextPriv::Block *After = NULL; ssize_t AddIndex; LAssert(BlkIdx >= 0); // Split 'b' to make room for the image if (d->Cursor->Offset > 0) { After = b->Split(Trans, d->Cursor->Offset); AddIndex = BlkIdx+1; } else { // Insert before.. AddIndex = BlkIdx; } LRichTextPriv::ImageBlock *ImgBlk = new LRichTextPriv::ImageBlock(d); if (ImgBlk) { d->Blocks.AddAt(AddIndex++, ImgBlk); if (After) d->Blocks.AddAt(AddIndex++, After); Img->MakeOpaque(); ImgBlk->SetImage(Img); AutoCursor c(new BlkCursor(ImgBlk, 1, -1)); d->SetCursor(c); } } Invalidate(); SendNotify(LNotifyDocChanged); return d->AddTrans(Trans); }); + + return true; } bool LRichTextEdit::ClearDirty(bool Ask, const char *FileName) { if (1 /*dirty*/) { int Answer = (Ask) ? LgiMsg(this, LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { if (FileName) Save(FileName); else { LFileSelect *Select = new LFileSelect; Select->Parent(this); Select->Save([this](auto dlg, auto status) { if (status) Save(dlg->Name()); delete dlg; }); } } else if (Answer == IDCANCEL) { return false; } } return true; } bool LRichTextEdit::Open(const char *Name, const char *CharSet) { bool Status = false; LFile f; if (f.Open(Name, O_READ|O_SHARE)) { size_t Bytes = (size_t)f.GetSize(); SetCursor(0, false); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } } DeleteArray(c8); } else { } Invalidate(); } return Status; } bool LRichTextEdit::Save(const char *FileName, const char *CharSet) { LFile f; if (!FileName || !f.Open(FileName, O_WRITE)) return false; f.SetSize(0); const char *Nm = Name(); if (!Nm) return false; size_t Len = strlen(Nm); return f.Write(Nm, (int)Len) == Len; } void LRichTextEdit::UpdateScrollBars(bool Reset) { if (VScroll) { //LRect Before = GetClient(); } } void LRichTextEdit::DoCase(std::function Callback, bool Upper) { if (!HasSelection()) { if (Callback) Callback(false); return; } bool Cf = d->CursorFirst(); LRichTextPriv::BlockCursor *Start = Cf ? d->Cursor : d->Selection; LRichTextPriv::BlockCursor *End = Cf ? d->Selection : d->Cursor; if (Start->Blk == End->Blk) { // In the same block... ssize_t Len = End->Offset - Start->Offset; Start->Blk->DoCase(NoTransaction, Start->Offset, Len, Upper); } else { // Multi-block delete... // 1) Delete all the content to the end of the first block ssize_t StartLen = Start->Blk->Length(); if (Start->Offset < StartLen) Start->Blk->DoCase(NoTransaction, Start->Offset, StartLen - Start->Offset, Upper); // 2) Delete any blocks between 'Start' and 'End' ssize_t i = d->Blocks.IndexOf(Start->Blk); if (i >= 0) { for (++i; d->Blocks[i] != End->Blk && i < (int)d->Blocks.Length(); ) { LRichTextPriv::Block *b = d->Blocks[i]; b->DoCase(NoTransaction, 0, -1, Upper); } } else { LAssert(0); if (Callback) Callback(false); return; } // 3) Delete any text up to the Cursor in the 'End' block End->Blk->DoCase(NoTransaction, 0, End->Offset, Upper); } // Update the screen d->Dirty = true; Invalidate(); if (Callback) Callback(true); } void LRichTextEdit::DoGoto(std::function Callback) { auto input = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); input->DoModal([this, input, Callback](auto dlg, auto ok) { if (ok) { auto i = input->GetStr().Int(); if (i >= 0) { SetLine((int)i); if (Callback) Callback(true); return; } } if (Callback) Callback(false); delete dlg; }); } LDocFindReplaceParams *LRichTextEdit::CreateFindReplaceParams() { return new LDocFindReplaceParams3; } void LRichTextEdit::SetFindReplaceParams(LDocFindReplaceParams *Params) { if (Params) { } } void LRichTextEdit::DoFindNext(std::function Callback) { if (Callback) Callback(false); } ////////////////////////////////////////////////////////////////////////////////// FIND void LRichTextEdit::DoFind(std::function Callback) { LArray Sel; if (HasSelection()) d->GetSelection(&Sel, NULL); LAutoString u(Sel.Length() ? WideToUtf8(&Sel.First()) : NULL); auto Dlg = new LFindDlg(this, [this](auto dlg, auto ctrlId) { return OnFind(dlg); }, u); Dlg->DoModal([this, Dlg, Callback](auto dlg, auto ctrlId) { if (Callback) Callback(ctrlId != IDCANCEL); Focus(true); delete dlg; }); } bool LRichTextEdit::OnFind(LFindReplaceCommon *Params) { if (!Params || !d->Cursor) { LAssert(0); return false; } LAutoPtr w((uint32_t*)LNewConvertCp("utf-32", Params->Find, "utf-8", Params->Find.Length())); ssize_t Idx = d->Blocks.IndexOf(d->Cursor->Blk); if (Idx < 0) { LAssert(0); return false; } for (unsigned n = 0; n < d->Blocks.Length(); n++) { ssize_t i = Idx + n; LRichTextPriv::Block *b = d->Blocks[i % d->Blocks.Length()]; ssize_t At = n ? 0 : d->Cursor->Offset; ssize_t Result = b->FindAt(At, w, Params); if (Result >= At) { ptrdiff_t Len = Strlen(w.Get()); AutoCursor Sel(new BlkCursor(b, Result, -1)); d->SetCursor(Sel, false); AutoCursor Cur(new BlkCursor(b, Result + Len, -1)); return d->SetCursor(Cur, true); } } return false; } ////////////////////////////////////////////////////////////////////////////////// REPLACE void LRichTextEdit::DoReplace(std::function Callback) { if (Callback) Callback(false); } bool LRichTextEdit::OnReplace(LFindReplaceCommon *Params) { return false; } ////////////////////////////////////////////////////////////////////////////////// void LRichTextEdit::SelectWord(size_t From) { int BlockIdx; ssize_t Start, End; LRichTextPriv::Block *b = d->GetBlockByIndex(From, &Start, &BlockIdx); if (!b) return; LArray Txt; if (!b->CopyAt(0, b->Length(), &Txt)) return; End = Start; while (Start > 0 && !IsWordBreakChar(Txt[Start-1])) Start--; while ( End < b->Length() && ( End == Txt.Length() || !IsWordBreakChar(Txt[End]) ) ) End++; AutoCursor c(new BlkCursor(b, Start, -1)); d->SetCursor(c); c.Reset(new BlkCursor(b, End, -1)); d->SetCursor(c, true); } bool LRichTextEdit::OnMultiLineTab(bool In) { return false; } void LRichTextEdit::OnSetHidden(int Hidden) { } void LRichTextEdit::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; LLayout::OnPosChange(); // LRect c = GetClient(); Processing = false; } LLayout::OnPosChange(); } int LRichTextEdit::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.SupportsFileDrops(); #ifdef WINDOWS Formats.Supports("UniformResourceLocatorW"); #endif return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int LRichTextEdit::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Effect = DROPEFFECT_NONE; for (unsigned i=0; iAreas[ContentArea].Overlap(Pt.x, Pt.y)) { int AddIndex = -1; LPoint TestPt( Pt.x - d->Areas[ContentArea].x1, Pt.y - d->Areas[ContentArea].y1); if (VScroll) TestPt.y += (int)(VScroll->Value() * d->ScrollLinePx); LDropFiles Df(dd); for (unsigned n=0; nHitTest(TestPt.x, TestPt.y, LineHint); if (Idx >= 0) { ssize_t BlkOffset; int BlkIdx; LRichTextPriv::Block *b = d->GetBlockByIndex(Idx, &BlkOffset, &BlkIdx); if (b) { LRichTextPriv::Block *After = NULL; // Split 'b' to make room for the image if (BlkOffset > 0) { After = b->Split(NoTransaction, BlkOffset); AddIndex = BlkIdx+1; } else { // Insert before.. AddIndex = BlkIdx; } LRichTextPriv::ImageBlock *ImgBlk = new LRichTextPriv::ImageBlock(d); if (ImgBlk) { d->Blocks.AddAt(AddIndex++, ImgBlk); if (After) d->Blocks.AddAt(AddIndex++, After); ImgBlk->Load(f); Effect = DROPEFFECT_COPY; } } } } } } break; } } if (Effect != DROPEFFECT_NONE) { Invalidate(); SendNotify(LNotifyDocChanged); } return Effect; } void LRichTextEdit::OnCreate() { SetWindow(this); DropTarget(true); if (Focus()) SetPulse(RTE_PULSE_RATE); if (d->SpellCheck) d->SpellCheck->EnumLanguages(AddDispatch()); } void LRichTextEdit::OnEscape(LKey &K) { } bool LRichTextEdit::OnMouseWheel(double l) { if (VScroll) { VScroll->Value(VScroll->Value() + (int64)l); Invalidate(); } return true; } void LRichTextEdit::OnFocus(bool f) { Invalidate(); SetPulse(f ? RTE_PULSE_RATE : -1); } ssize_t LRichTextEdit::HitTest(int x, int y) { int Line = -1; return d->HitTest(x, y, Line); } void LRichTextEdit::Undo() { if (d->UndoPos > 0) d->SetUndoPos(d->UndoPos - 1); } void LRichTextEdit::Redo() { if (d->UndoPos < (int)d->UndoQue.Length()) d->SetUndoPos(d->UndoPos + 1); } #ifdef _DEBUG class NodeView : public LWindow { public: LTree *Tree; NodeView(LViewI *w) { LRect r(0, 0, 500, 600); SetPos(r); MoveSameScreen(w); Attach(0); if ((Tree = new LTree(100, 0, 0, 100, 100))) { Tree->SetPourLargest(true); Tree->Attach(this); } } }; #endif void LRichTextEdit::DoContextMenu(LMouse &m) { LMenuItem *i; LSubMenu RClick; LAutoString ClipText; { LClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } LRichTextPriv::Block *Over = NULL; LRect &Content = d->Areas[ContentArea]; LPoint Doc = d->ScreenToDoc(m.x, m.y); ssize_t Offset = -1, BlkOffset = -1; if (Content.Overlap(m.x, m.y)) { int LineHint; Offset = d->HitTest(Doc.x, Doc.y, LineHint, &Over, &BlkOffset); } if (Over) Over->DoContext(RClick, Doc, BlkOffset, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_RTE_CUT, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_RTE_COPY, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_RTE_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_RTE_UNDO, false /* UndoQue.CanUndo() */); RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_RTE_REDO, false /* UndoQue.CanRedo() */); RClick.AppendSeparator(); #if 0 i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); #endif i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); LSubMenu *Src = RClick.AppendSub("Source"); if (Src) { Src->AppendItem("Copy Original", IDM_COPY_ORIGINAL, d->OriginalText.Get() != NULL); Src->AppendItem("Copy Current", IDM_COPY_CURRENT); #ifdef _DEBUG Src->AppendItem("Dump Nodes", IDM_DUMP_NODES); // Edit->DumpNodes(Tree); #endif } if (Over) { #ifdef _DEBUG // RClick.AppendItem(Over->GetClass(), -1, false); #endif Over->DoContext(RClick, Doc, BlkOffset, false); } if (Environment) Environment->AppendItems(&RClick, NULL); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m.x, m.y)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(LNotifyFixedWidthChanged); break; } case IDM_RTE_CUT: { Cut(); break; } case IDM_RTE_COPY: { Copy(); break; } case IDM_RTE_PASTE: { Paste(); break; } case IDM_RTE_UNDO: { Undo(); break; } case IDM_RTE_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); auto i = new LInput(this, s, "Indent Size:", "Text"); i->DoModal([this, i](auto dlg, auto ctrlId) { if (ctrlId == IDOK) { IndentSize = (uint8_t)i->GetStr().Int(); Invalidate(); } delete dlg; }); break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); auto i = new LInput(this, s, "Tab Size:", "Text"); i->DoModal([this, i](auto dlg, auto ok) { if (ok) SetTabSize((uint8_t)i->GetStr().Int()); delete dlg; }); break; } case IDM_COPY_ORIGINAL: { LClipBoard c(this); c.Text(d->OriginalText); break; } case IDM_COPY_CURRENT: { LClipBoard c(this); c.Text(Name()); break; } case IDM_DUMP_NODES: { #ifdef _DEBUG NodeView *nv = new NodeView(GetWindow()); DumpNodes(nv->Tree); nv->Visible(true); #endif break; } default: { if (Over) { LMessage Cmd(M_COMMAND, Id); if (Over->OnEvent(&Cmd)) break; } if (Environment) Environment->OnMenu(this, Id, 0); break; } } } void LRichTextEdit::OnMouseClick(LMouse &m) { bool Processed = false; RectType Clicked = d->PosToButton(m); if (m.Down()) { Focus(true); if (m.IsContextMenu()) { DoContextMenu(m); return; } else { Focus(true); if (d->Areas[ToolsArea].Overlap(m.x, m.y) // || d->Areas[CapabilityArea].Overlap(m.x, m.y) ) { if (Clicked != MaxArea) { if (d->BtnState[Clicked].IsPress) { d->BtnState[d->ClickedBtn = Clicked].Pressed = true; Invalidate(d->Areas + Clicked); Capture(true); } else { Processed |= d->ClickBtn(m, Clicked); } } } else { d->WordSelectMode = !Processed && m.Double(); AutoCursor c(new BlkCursor(NULL, 0, 0)); LPoint Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx)) { d->ClickedBtn = ContentArea; d->SetCursor(c, m.Shift()); if (d->WordSelectMode) SelectWord(Idx); } } } } else if (IsCapturing()) { Capture(false); if (d->ClickedBtn != MaxArea) { d->BtnState[d->ClickedBtn].Pressed = false; Invalidate(d->Areas + d->ClickedBtn); Processed |= d->ClickBtn(m, Clicked); } d->ClickedBtn = MaxArea; } if (!Processed) { Capture(m.Down()); } } int LRichTextEdit::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return LView::OnHitTest(x, y); } void LRichTextEdit::OnMouseMove(LMouse &m) { LRichTextEdit::RectType OverBtn = d->PosToButton(m); if (d->OverBtn != OverBtn) { if (d->OverBtn < MaxArea) { d->BtnState[d->OverBtn].MouseOver = false; Invalidate(&d->Areas[d->OverBtn]); } d->OverBtn = OverBtn; if (d->OverBtn < MaxArea) { d->BtnState[d->OverBtn].MouseOver = true; Invalidate(&d->Areas[d->OverBtn]); } } if (IsCapturing()) { if (d->ClickedBtn == ContentArea) { AutoCursor c; LPoint Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx) && c) { if (d->WordSelectMode && d->Selection) { // Extend the selection to include the whole word if (!d->CursorFirst()) { // Extend towards the end of the doc... LArray Txt; if (c->Blk->CopyAt(0, c->Blk->Length(), &Txt)) { while ( c->Offset < (int)Txt.Length() && !IsWordBreakChar(Txt[c->Offset]) ) c->Offset++; } } else { // Extend towards the start of the doc... LArray Txt; if (c->Blk->CopyAt(0, c->Blk->Length(), &Txt)) { while ( c->Offset > 0 && !IsWordBreakChar(Txt[c->Offset-1]) ) c->Offset--; } } } d->SetCursor(c, m.Left()); } } } #ifdef WIN32 LRect c = GetClient(); c.Offset(-c.x1, -c.y1); if (c.Overlap(m.x, m.y)) { /* LStyle *s = HitStyle(Hit); TCHAR *c = (s) ? s->GetCursor() : 0; if (!c) c = IDC_IBEAM; ::SetCursor(LoadCursor(0, MAKEINTRESOURCE(c))); */ } #endif } bool LRichTextEdit::OnKey(LKey &k) { if (k.Down() && d->Cursor) d->Cursor->Blink = true; if (k.c16 == 17) return false; #ifdef WINDOWS // Wtf is this? // Weeeelll, windows likes to send a LK_TAB after a Ctrl+I doesn't it? // And this just takes care of that TAB before it can overwrite your // selection. if (ToLower(k.c16) == 'i' && k.Ctrl()) { d->EatVkeys.Add(LK_TAB); } else if (d->EatVkeys.Length()) { auto Idx = d->EatVkeys.IndexOf(k.vkey); if (Idx >= 0) { // Yum yum d->EatVkeys.DeleteAt(Idx); return true; } } #endif // k.Trace("LRichTextEdit::OnKey"); if (k.IsContextMenu()) { LMouse m; DoContextMenu(m); } else if (k.IsChar) { switch (k.vkey) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.vkey == LK_TAB) && k.c16 != 127 ) ) { if (k.Down() && d->Cursor && d->Cursor->Blk) { // letter/number etc LRichTextPriv::Block *b = d->Cursor->Blk; AutoTrans Trans(new LRichTextPriv::Transaction); d->DeleteSelection(Trans, NULL); LNamedStyle *AddStyle = NULL; if (d->StyleDirty.Length() > 0) { LAutoPtr Mod(new LCss); if (Mod) { // Get base styles at the cursor.. LNamedStyle *Base = b->GetStyle(d->Cursor->Offset); if (Base) *Mod = *Base; // Apply dirty toolbar styles... if (d->StyleDirty.HasItem(FontFamilyBtn)) Mod->FontFamily(LCss::StringsDef(d->Values[FontFamilyBtn].Str())); if (d->StyleDirty.HasItem(FontSizeBtn)) Mod->FontSize(LCss::Len(LCss::LenPt, (float) d->Values[FontSizeBtn].CastDouble())); if (d->StyleDirty.HasItem(BoldBtn)) Mod->FontWeight(d->Values[BoldBtn].CastInt32() ? LCss::FontWeightBold : LCss::FontWeightNormal); if (d->StyleDirty.HasItem(ItalicBtn)) Mod->FontStyle(d->Values[ItalicBtn].CastInt32() ? LCss::FontStyleItalic : LCss::FontStyleNormal); if (d->StyleDirty.HasItem(UnderlineBtn)) Mod->TextDecoration(d->Values[UnderlineBtn].CastInt32() ? LCss::TextDecorUnderline : LCss::TextDecorNone); if (d->StyleDirty.HasItem(ForegroundColourBtn)) Mod->Color(LCss::ColorDef(LCss::ColorRgb, (uint32_t)d->Values[ForegroundColourBtn].CastInt64())); if (d->StyleDirty.HasItem(BackgroundColourBtn)) Mod->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, (uint32_t)d->Values[BackgroundColourBtn].CastInt64())); AddStyle = d->AddStyleToCache(Mod); } d->StyleDirty.Length(0); } else if (b->Length() == 0) { // We have no existing style to modify, so create one from scratch. LAutoPtr Mod(new LCss); if (Mod) { // Apply dirty toolbar styles... Mod->FontFamily(LCss::StringsDef(d->Values[FontFamilyBtn].Str())); Mod->FontSize(LCss::Len(LCss::LenPt, (float)d->Values[FontSizeBtn].CastDouble())); Mod->FontWeight(d->Values[BoldBtn].CastInt32() ? LCss::FontWeightBold : LCss::FontWeightNormal); Mod->FontStyle(d->Values[ItalicBtn].CastInt32() ? LCss::FontStyleItalic : LCss::FontStyleNormal); Mod->TextDecoration(d->Values[UnderlineBtn].CastInt32() ? LCss::TextDecorUnderline : LCss::TextDecorNone); Mod->Color(LCss::ColorDef(LCss::ColorRgb, (uint32_t)d->Values[ForegroundColourBtn].CastInt64())); auto Bk = d->Values[BackgroundColourBtn].CastInt64(); if (Bk > 0) Mod->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, (uint32_t)Bk)); AddStyle = d->AddStyleToCache(Mod); } } uint32_t Ch = k.c16; if (b->AddText(Trans, d->Cursor->Offset, &Ch, 1, AddStyle)) { d->Cursor->Set(d->Cursor->Offset + 1); Invalidate(); SendNotify(LNotifyDocChanged); d->AddTrans(Trans); } } return true; } break; } case LK_RETURN: { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); SendNotify(LNotifyDocChanged); } return true; } case LK_BACKSPACE: { if (GetReadOnly()) break; bool Changed = false; AutoTrans Trans(new LRichTextPriv::Transaction); if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { LRichTextPriv::Block *b; if (HasSelection()) { Changed = d->DeleteSelection(Trans, NULL); } else if (d->Cursor && (b = d->Cursor->Blk)) { if (d->Cursor->Offset > 0) { Changed = b->DeleteAt(Trans, d->Cursor->Offset-1, 1) > 0; if (Changed) { // Has block size reached 0? if (b->Length() == 0) { // Then delete it... LRichTextPriv::Block *n = d->Next(b); if (n) { d->Blocks.Delete(b, true); d->Cursor.Reset(new LRichTextPriv::BlockCursor(n, 0, 0)); } else { // No other block to go to, so leave this empty block at the end // of the documnent but set the cursor correctly. d->Cursor->Set(0); } } else { d->Cursor->Set(d->Cursor->Offset - 1); } } } else { // At the start of a block: LRichTextPriv::Block *Prev = d->Prev(d->Cursor->Blk); if (Prev) { // Try and merge the two blocks... ssize_t Len = Prev->Length(); d->Merge(Trans, Prev, d->Cursor->Blk); AutoCursor c(new BlkCursor(Prev, Len, -1)); d->SetCursor(c); } else // at the start of the doc... { // Don't send the doc changed... return true; } } } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(LNotifyDocChanged); } return true; } } } else // not a char { switch (k.vkey) { case LK_TAB: return true; case LK_RETURN: return !GetReadOnly(); case LK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) Redo(); else Undo(); } } else if (k.Ctrl()) { if (k.Down()) { // Implement delete by word LAssert(!"Impl backspace by word"); } } return true; } break; } case LK_F3: { if (k.Down()) DoFindNext(NULL); return true; } case LK_LEFT: { #ifdef MAC if (k.Ctrl()) #else if (k.Alt()) #endif return false; if (k.Down()) { if (HasSelection() && !k.Shift()) { LRect r = d->SelectionRect(); Invalidate(&r); AutoCursor c(new BlkCursor(d->CursorFirst() ? *d->Cursor : *d->Selection)); d->SetCursor(c); } else { #ifdef MAC if (k.System()) goto Jump_StartOfLine; else #endif d->Seek(d->Cursor, #ifdef MAC k.Alt() ? #else k.Ctrl() ? #endif LRichTextPriv::SkLeftWord : LRichTextPriv::SkLeftChar, k.Shift()); } } return true; } case LK_RIGHT: { #ifdef MAC if (k.Ctrl()) #else if (k.Alt()) #endif return false; if (k.Down()) { if (HasSelection() && !k.Shift()) { LRect r = d->SelectionRect(); Invalidate(&r); AutoCursor c(new BlkCursor(d->CursorFirst() ? *d->Selection : *d->Cursor)); d->SetCursor(c); } else { #ifdef MAC if (k.System()) goto Jump_EndOfLine; #endif d->Seek(d->Cursor, #ifdef MAC k.Alt() ? #else k.Ctrl() ? #endif LRichTextPriv::SkRightWord : LRichTextPriv::SkRightChar, k.Shift()); } } return true; } case LK_UP: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto GTextView4_PageUp; #endif d->Seek(d->Cursor, LRichTextPriv::SkUpLine, k.Shift()); } return true; } case LK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto GTextView4_PageDown; #endif d->Seek(d->Cursor, LRichTextPriv::SkDownLine, k.Shift()); } return true; } case LK_END: { if (k.Down()) { #ifdef MAC if (!k.Ctrl()) Jump_EndOfLine: #endif d->Seek(d->Cursor, k.Ctrl() ? LRichTextPriv::SkDocEnd : LRichTextPriv::SkLineEnd, k.Shift()); } return true; } case LK_HOME: { if (k.Down()) { #ifdef MAC if (!k.Ctrl()) Jump_StartOfLine: #endif d->Seek(d->Cursor, k.Ctrl() ? LRichTextPriv::SkDocStart : LRichTextPriv::SkLineStart, k.Shift()); } return true; } case LK_PAGEUP: { #ifdef MAC GTextView4_PageUp: #endif if (k.Down()) { d->Seek(d->Cursor, LRichTextPriv::SkUpPage, k.Shift()); } return true; break; } case LK_PAGEDOWN: { #ifdef MAC GTextView4_PageDown: #endif if (k.Down()) { d->Seek(d->Cursor, LRichTextPriv::SkDownPage, k.Shift()); } return true; break; } case LK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case LK_DELETE: { if (GetReadOnly()) break; if (!k.Down()) return true; bool Changed = false; LRichTextPriv::Block *b; AutoTrans Trans(new LRichTextPriv::Transaction); if (HasSelection()) { if (k.Shift()) Changed |= Cut(); else Changed |= d->DeleteSelection(Trans, NULL); } else if (d->Cursor && (b = d->Cursor->Blk)) { if (d->Cursor->Offset >= b->Length()) { // Cursor is at the end of this block, pull the styles // from the next block into this one. LRichTextPriv::Block *next = d->Next(b); if (!next) { // No next block, therefor nothing to delete break; } // Try and merge the blocks if (d->Merge(Trans, b, next)) Changed = true; else { // If the cursor is on the last empty line of a text block, // we should delete that '\n' first LRichTextPriv::TextBlock *tb = dynamic_cast(b); if (tb && tb->IsEmptyLine(d->Cursor)) Changed = tb->StripLast(Trans); // move the cursor to the next block d->Cursor.Reset(new LRichTextPriv::BlockCursor(b = next, 0, 0)); } } if (!Changed && b->DeleteAt(Trans, d->Cursor->Offset, 1)) { if (b->Length() == 0) { LRichTextPriv::Block *n = d->Next(b); if (n) { d->Blocks.Delete(b, true); d->Cursor.Reset(new LRichTextPriv::BlockCursor(n, 0, 0)); } } Changed = true; } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(LNotifyDocChanged); } return true; } default: { if (k.c16 == 17) break; if (k.c16 == ' ' && k.Ctrl() && k.Alt() && d->Cursor && d->Cursor->Blk) { if (k.Down()) { // letter/number etc LRichTextPriv::Block *b = d->Cursor->Blk; uint32_t Nbsp[] = {0xa0}; if (b->AddText(NoTransaction, d->Cursor->Offset, Nbsp, 1)) { d->Cursor->Set(d->Cursor->Offset + 1); Invalidate(); SendNotify(LNotifyDocChanged); } } break; } if (k.CtrlCmd() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { /* if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } */ break; } case 0xbb: // Ctrl+'+' { /* if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } */ break; } case 'a': case 'A': { if (k.Down()) { // select all SelectAll(); } return true; break; } case 'b': case 'B': { if (k.Down()) { // Bold selection LMouse m; GetMouse(m); d->ClickBtn(m, BoldBtn); } return true; break; } case 'l': case 'L': { if (k.Down()) { // Underline selection LMouse m; GetMouse(m); d->ClickBtn(m, UnderlineBtn); } return true; break; } case 'i': case 'I': { if (k.Down()) { // Italic selection LMouse m; GetMouse(m); d->ClickBtn(m, ItalicBtn); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) DoFind(NULL); return true; } case 'g': case 'G': { if (k.Down()) DoGoto(NULL); return true; break; } case 'h': case 'H': { if (k.Down()) DoReplace(NULL); return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) DoCase(NULL, k.Shift()); return true; } break; } case LK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void LRichTextEdit::OnEnter(LKey &k) { AutoTrans Trans(new LRichTextPriv::Transaction); // Enter key handling bool Changed = false; if (HasSelection()) Changed |= d->DeleteSelection(Trans, NULL); if (d->Cursor && d->Cursor->Blk) { LRichTextPriv::Block *b = d->Cursor->Blk; const uint32_t Nl[] = {'\n'}; if (b->AddText(Trans, d->Cursor->Offset, Nl, 1)) { d->Cursor->Set(d->Cursor->Offset + 1); Changed = true; } else { // Some blocks don't take text. However a new block can be created or // the text added to the start of the next block if (d->Cursor->Offset == 0) { LRichTextPriv::Block *Prev = d->Prev(b); if (Prev) Changed = Prev->AddText(Trans, Prev->Length(), Nl, 1); else // No previous... must by first block... create new block: { LRichTextPriv::TextBlock *tb = new LRichTextPriv::TextBlock(d); if (tb) { Changed = true; // tb->AddText(Trans, 0, Nl, 1); d->Blocks.AddAt(0, tb); } } } else if (d->Cursor->Offset == b->Length()) { LRichTextPriv::Block *Next = d->Next(b); if (Next) { if ((Changed = Next->AddText(Trans, 0, Nl, 1))) d->Cursor->Set(Next, 0, -1); } else // No next block. Create one: { LRichTextPriv::TextBlock *tb = new LRichTextPriv::TextBlock(d); if (tb) { Changed = true; // tb->AddText(Trans, 0, Nl, 1); d->Blocks.Add(tb); } } } } } if (Changed) { Invalidate(); d->AddTrans(Trans); SendNotify(LNotifyDocChanged); } } void LRichTextEdit::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void LRichTextEdit::OnPaint(LSurface *pDC) { LRect r = GetClient(); if (!r.Valid()) return; #if 0 pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif int FontY = GetFont()->GetHeight(); LCssTools ct(d, d->Font); r = ct.PaintBorder(pDC, r); bool HasSpace = r.Y() > (FontY * 3); if (d->ShowTools && HasSpace) { d->Areas[ToolsArea] = r; d->Areas[ToolsArea].y2 = d->Areas[ToolsArea].y1 + (FontY + 8) - 1; r.y1 = d->Areas[ToolsArea].y2 + 1; } else { d->Areas[ToolsArea].ZOff(-1, -1); } d->Areas[ContentArea] = r; if (d->Layout(VScroll)) d->Paint(pDC, VScroll); // else the scroll bars changed, wait for re-paint } LMessage::Result LRichTextEdit::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } case M_BLOCK_MSG: { LRichTextPriv::Block *b = (LRichTextPriv::Block*)Msg->A(); LAutoPtr msg((LMessage*)Msg->B()); if (d->Blocks.HasItem(b) && msg) { b->OnEvent(msg); } else printf("%s:%i - No block to receive M_BLOCK_MSG.\n", _FL); break; } case M_ENUMERATE_LANGUAGES: { LAutoPtr< LArray > Languages((LArray*)Msg->A()); if (!Languages) { LgiTrace("%s:%i - M_ENUMERATE_LANGUAGES no param\n", _FL); break; } // LgiTrace("%s:%i - Got M_ENUMERATE_LANGUAGES %s\n", _FL, d->SpellLang.Get()); bool Match = false; for (auto &s: *Languages) { if (s.LangCode.Equals(d->SpellLang) || s.EnglishName.Equals(d->SpellLang)) { // LgiTrace("%s:%i - EnumDict called %s\n", _FL, s.LangCode.Get()); d->SpellCheck->EnumDictionaries(AddDispatch(), s.LangCode); Match = true; break; } } if (!Match) LgiTrace("%s:%i - EnumDict not called %s\n", _FL, d->SpellLang.Get()); break; } case M_ENUMERATE_DICTIONARIES: { LAutoPtr< LArray > Dictionaries((LArray*)Msg->A()); if (!Dictionaries) break; bool Match = false; for (auto &s: *Dictionaries) { // LgiTrace("%s:%i - M_ENUMERATE_DICTIONARIES: %s, %s\n", _FL, s.Dict.Get(), d->SpellDict.Get()); if (s.Dict.Equals(d->SpellDict)) { d->SpellCheck->SetDictionary(AddDispatch(), s.Lang, s.Dict); Match = true; break; } } if (!Match) d->SpellCheck->SetDictionary(AddDispatch(), d->SpellLang, NULL); break; } case M_SET_DICTIONARY: { d->SpellDictionaryLoaded = Msg->A() != 0; // LgiTrace("%s:%i - M_SET_DICTIONARY=%i\n", _FL, d->SpellDictionaryLoaded); if (d->SpellDictionaryLoaded) { AutoTrans Trans(new LRichTextPriv::Transaction); // Get any loaded text blocks to check their spelling bool Status = false; for (unsigned i=0; iBlocks.Length(); i++) { Status |= d->Blocks[i]->OnDictionary(Trans); } if (Status) d->AddTrans(Trans); } break; } case M_CHECK_TEXT: { LAutoPtr Ct((LSpellCheck::CheckText*)Msg->A()); if (!Ct || Ct->User.Length() > 1) { LAssert(0); break; } LRichTextPriv::Block *b = (LRichTextPriv::Block*)Ct->User[SpellBlockPtr].CastVoidPtr(); if (!d->Blocks.HasItem(b)) break; b->SetSpellingErrors(Ct->Errors, *Ct); Invalidate(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return 0 /*Size*/; } case WM_GETTEXT: { int Chars = (int)Msg->A(); char *Out = (char*)Msg->B(); if (Out) { char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } case M_COMPONENT_INSTALLED: { LAutoPtr Comp((LString*)Msg->A()); if (Comp) d->OnComponentInstall(*Comp); break; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return LLayout::OnEvent(Msg); } int LRichTextEdit::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { Invalidate(d->Areas + ContentArea); } return 0; } void LRichTextEdit::OnPulse() { if (!ReadOnly && d->Cursor) { uint64 n = LCurrentTime(); if (d->BlinkTs - n >= RTE_CURSOR_BLINK_RATE) { d->BlinkTs = n; d->Cursor->Blink = !d->Cursor->Blink; d->InvalidateDoc(&d->Cursor->Pos); } // Do autoscroll while the user has clicked and dragged off the control: if (VScroll && IsCapturing() && d->ClickedBtn == LRichTextEdit::ContentArea) { LMouse m; GetMouse(m); // Is the mouse outside the content window LRect &r = d->Areas[ContentArea]; if (!r.Overlap(m.x, m.y)) { AutoCursor c(new BlkCursor(NULL, 0, 0)); LPoint Doc = d->ScreenToDoc(m.x, m.y); ssize_t Idx = -1; if (d->CursorFromPos(Doc.x, Doc.y, &c, &Idx)) { d->SetCursor(c, true); if (d->WordSelectMode) SelectWord(Idx); } // Update the screen. d->InvalidateDoc(NULL); } } } } void LRichTextEdit::OnUrl(char *Url) { if (Environment) { Environment->OnNavigate(this, Url); } } bool LRichTextEdit::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; // Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } #if _DEBUG void LRichTextEdit::SelectNode(LString Param) { LRichTextPriv::Block *b = (LRichTextPriv::Block*) Param.Int(16); bool Valid = false; for (auto i : d->Blocks) { if (i == b) Valid = true; i->DrawDebug = false; } if (Valid) { b->DrawDebug = true; Invalidate(); } } void LRichTextEdit::DumpNodes(LTree *Root) { d->DumpNodes(Root); } #endif /////////////////////////////////////////////////////////////////////////////// SelectColour::SelectColour(LRichTextPriv *priv, LPoint p, LRichTextEdit::RectType t) : LPopup(priv->View) { d = priv; Type = t; int Px = 16; int PxSp = Px + 2; int x = 6; int y = 6; // Do grey ramp for (int i=0; i<8; i++) { Entry &en = e.New(); int Grey = i * 255 / 7; en.r.ZOff(Px-1, Px-1); en.r.Offset(x + (i * PxSp), y); en.c.Rgb(Grey, Grey, Grey); } // Do colours y += PxSp + 4; int SatRange = 255 - 64; int SatStart = 255 - 32; int HueStep = 360 / 8; for (int sat=0; sat<8; sat++) { for (int hue=0; hue<8; hue++) { LColour c; c.SetHLS(hue * HueStep, SatStart - ((sat * SatRange) / 7), 255); c.ToRGB(); Entry &en = e.New(); en.r.ZOff(Px-1, Px-1); en.r.Offset(x + (hue * PxSp), y); en.c = c; } y += PxSp; } SetParent(d->View); LRect r(0, 0, 12 + (8 * PxSp) - 1, y + 6 - 1); r.Offset(p.x, p.y); SetPos(r); Visible(true); } void SelectColour::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); for (unsigned i=0; iColour(e[i].c); pDC->Rectangle(&e[i].r); } } void SelectColour::OnMouseClick(LMouse &m) { if (m.Down()) { for (unsigned i=0; iValues[Type] = (int64)e[i].c.c32(); d->View->Invalidate(d->Areas + Type); d->OnStyleChange(Type); Visible(false); break; } } } } void SelectColour::Visible(bool i) { LPopup::Visible(i); if (!i) { d->View->Focus(true); delete this; } } /////////////////////////////////////////////////////////////////////////////// #define EMOJI_PAD 2 #include "lgi/common/Emoji.h" int EmojiMenu::Cur = 0; EmojiMenu::EmojiMenu(LRichTextPriv *priv, LPoint p) : LPopup(priv->View) { d = priv; d->GetEmojiImage(); int MaxIdx = 0; LHashTbl, int> Map; for (int b=0; b= 0) { Map.Add(Emoji.Index, u); MaxIdx = MAX(MaxIdx, Emoji.Index); } } } int Sz = EMOJI_CELL_SIZE - 1; int PaneCount = 5; int PaneSz = (int)(Map.Length() / PaneCount); int ImgIdx = 0; int PaneSelectSz = LSysFont->GetHeight() * 2; int Rows = (PaneSz + EMOJI_GROUP_X - 1) / EMOJI_GROUP_X; LRect r(0, 0, (EMOJI_CELL_SIZE + EMOJI_PAD) * EMOJI_GROUP_X + EMOJI_PAD, (EMOJI_CELL_SIZE + EMOJI_PAD) * Rows + EMOJI_PAD + PaneSelectSz); r.Offset(p.x, p.y); SetPos(r); for (int pi = 0; pi < PaneCount; pi++) { Pane &p = Panes[pi]; int Wid = X() - (EMOJI_PAD*2); p.Btn.x1 = EMOJI_PAD + (pi * Wid / PaneCount); p.Btn.y1 = EMOJI_PAD; p.Btn.x2 = EMOJI_PAD + ((pi + 1) * Wid / PaneCount) - 1; p.Btn.y2 = EMOJI_PAD + PaneSelectSz; int Dx = EMOJI_PAD; int Dy = p.Btn.y2 + 1; while ((int)p.e.Length() < PaneSz && ImgIdx <= MaxIdx) { uint32_t u = Map.Find(ImgIdx); if (u) { Emoji &Ch = p.e.New(); Ch.u = u; int Sx = ImgIdx % EMOJI_GROUP_X; int Sy = ImgIdx / EMOJI_GROUP_X; Ch.Src.ZOff(Sz, Sz); Ch.Src.Offset(Sx * EMOJI_CELL_SIZE, Sy * EMOJI_CELL_SIZE); Ch.Dst.ZOff(Sz, Sz); Ch.Dst.Offset(Dx, Dy); Dx += EMOJI_PAD + EMOJI_CELL_SIZE; if (Dx + EMOJI_PAD + EMOJI_CELL_SIZE >= r.X()) { Dx = EMOJI_PAD; Dy += EMOJI_PAD + EMOJI_CELL_SIZE; } } ImgIdx++; } } SetParent(d->View); Visible(true); } void EmojiMenu::OnPaint(LSurface *pDC) { LAutoPtr DblBuf; if (!pDC->SupportsAlphaCompositing()) DblBuf.Reset(new LDoubleBuffer(pDC)); pDC->Colour(L_MED); pDC->Rectangle(); LSurface *EmojiImg = d->GetEmojiImage(); if (EmojiImg) { pDC->Op(GDC_ALPHA); for (unsigned i=0; iColour(L_LIGHT); pDC->Rectangle(&p.Btn); } LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); Ds.Draw(pDC, p.Btn.x1 + ((p.Btn.X()-Ds.X())>>1), p.Btn.y1 + ((p.Btn.Y()-Ds.Y())>>1)); } Pane &p = Panes[Cur]; for (unsigned i=0; iBlt(g.Dst.x1, g.Dst.y1, EmojiImg, &g.Src); } } else { LRect c = GetClient(); LDisplayString Ds(LSysFont, "Loading..."); LSysFont->Colour(L_TEXT, L_MED); LSysFont->Transparent(true); Ds.Draw(pDC, (c.X()-Ds.X())>>1, (c.Y()-Ds.Y())>>1); } } bool EmojiMenu::InsertEmoji(uint32_t Ch) { if (!d->Cursor || !d->Cursor->Blk) return false; AutoTrans Trans(new LRichTextPriv::Transaction); if (!d->Cursor->Blk->AddText(NoTransaction, d->Cursor->Offset, &Ch, 1, NULL)) return false; AutoCursor c(new BlkCursor(*d->Cursor)); c->Offset++; d->SetCursor(c); d->AddTrans(Trans); d->Dirty = true; d->InvalidateDoc(NULL); d->View->SendNotify(LNotifyDocChanged); return true; } void EmojiMenu::OnMouseClick(LMouse &m) { if (m.Down()) { for (unsigned i=0; iView->Focus(true); delete this; } } /////////////////////////////////////////////////////////////////////////////// class LRichTextEdit_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LRichTextEdit") == 0) { return new LRichTextEdit(-1, 0, 0, 2000, 2000); } return 0; } } RichTextEdit_Factory; diff --git a/src/linux/CrashHandler/CrashHandler.cpp b/src/linux/CrashHandler/CrashHandler.cpp --- a/src/linux/CrashHandler/CrashHandler.cpp +++ b/src/linux/CrashHandler/CrashHandler.cpp @@ -1,425 +1,425 @@ #include #include #include #include #include #include #include #define LAssert assert #include "lgi/common/LgiDefs.h" #include "lgi/common/Array.h" #include "lgi/common/StringClass.h" #define _FL __FILE__, __LINE__ enum HandleType { hRead, hWrite, hMax, }; int in[hMax]; // app -> child pipe int out[hMax]; // child -> app pipe bool loop = true; void LSleep(uint32_t i) { struct timespec request = {}, remain = {}; request.tv_sec = i / 1000; request.tv_nsec = (i % 1000) * 1000000; while (nanosleep(&request, &remain) == -1) request = remain; } template T *strnchr(T *str, char ch, size_t len) { if (!str) return NULL; T *end = str + len; while (str < end) { if (*str == ch) return str; str++; } return NULL; } enum AppState { sInit, sFrameSwitch, sLocals, sArgs, sList, sGetThreads, sThreadSwitch, sThreadBt, } state = sInit; LString::Array *output = NULL; int curThread = 1, threads = -1; FILE *crashLog = NULL; void Log(const char *fmt, ...) { va_list args; va_start(args, fmt); char buf[1024]; auto ch = vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); if (ch > 0) { printf("%.*s", (int)ch, buf); if (crashLog) fwrite(buf, ch, 1, crashLog); } } void OpenCrashLog() { if (crashLog) return; time_t now; time(&now); auto cur = localtime(&now); char file[256] = "crash.log"; if (cur) snprintf(file, sizeof(file), - "crash-%i%02.2i%02.2i-%02.2i%02.2i%02.2i.log", + "crash-%i%2.2i%2.2i-%2.2i%2.2i%2.2i.log", cur->tm_year + 1900, cur->tm_mon + 1, cur->tm_mday, cur->tm_hour, cur->tm_min, cur->tm_sec); crashLog = fopen(file, "w"); } void DumpOutput(const char *Label) { Log("%s:\n", Label); for (auto ln: *output) { if (ln.Find("(gdb)") < 0) Log(" %s\n", ln.Get()); } Log("\n"); output->Length(0); } /* Basic state flow: sInit sGetThreads # display all the threads for each thread: sThreadBt # back trace the thread sThreadSwitch # display back trace and check for crashed frame if has crash frame: sFrameSwitch # get to the right frame... sLocals # display the locals sArgs # display any function args sList # list the source code around the crash point */ void AtPrompt() { auto GotoNextThread = [&]() { if (curThread <= threads) { // Check out the next thread... char cmd[256]; auto cmdSz = snprintf(cmd, sizeof(cmd), "thread %i\n", curThread); int wr = write(in[hWrite], cmd, cmdSz); state = sThreadBt; } else loop = false; }; switch (state) { case sInit: { /* First thing is to look through all the threads and find the one that crashed... No point dumping locals and args from the thread that didn't crash. */ auto cmd = "info threads\n"; int wr = write(in[hWrite], cmd, strlen(cmd)); state = sGetThreads; output = new LString::Array; OpenCrashLog(); break; } case sFrameSwitch: { DumpOutput("Frame"); // Then start dumping out the locals and args... auto cmd = "info locals\n"; auto wr = write(in[hWrite], cmd, strlen(cmd)); state = sLocals; break; } case sLocals: { DumpOutput("Locals"); auto cmd = "info args\n"; int wr = write(in[hWrite], cmd, strlen(cmd)); state = sArgs; break; } case sArgs: { DumpOutput("Args"); auto cmd = "list\n"; int wr = write(in[hWrite], cmd, strlen(cmd)); state = sList; break; } case sList: { DumpOutput("List"); // If there are more threads... go back trace them GotoNextThread(); break; } case sGetThreads: { LString last; for (int i=output->Length()-1; i>=0; i--) { last = (*output)[i].Strip(); if (isdigit(last(0))) { threads = (int)last.Int(); break; } } DumpOutput("Threads"); output->Length(0); if (threads > 0) { GotoNextThread(); } else { Log("%s:%i - Error: failed to get thread count.\n", _FL); loop = false; } break; } case sThreadSwitch: { bool hasCrash = false; int crashFrame = -1; if (output->Length() > 0) { char label[64]; snprintf(label, sizeof(label), "Thread %i", curThread++); // Scan through the output and look for some crash indication int curFrame = -1; for (unsigned i=0; iLength(); i++) { auto ln = (*output)[i]; auto s = ln.Strip(); if (s.Length() && s(0) == '#') curFrame = s.SplitDelimit()[0].Strip("#").Int(); if (ln.Find("LgiCrashHandler") >= 0 || ln.Find("signal handler called") >= 0) { hasCrash = true; crashFrame = curFrame + 1; } } DumpOutput(label); } char cmd[256]; if (hasCrash && crashFrame >= 0) { // Switch to the crashing frame here... auto cmdSz = snprintf(cmd, sizeof(cmd), "frame %i\n", crashFrame); int wr = write(in[hWrite], cmd, cmdSz); state = sFrameSwitch; } else { // Check out the next thread... GotoNextThread(); } break; } case sThreadBt: { auto cmd = "bt\n"; int wr = write(in[hWrite], cmd, strlen(cmd)); state = sThreadSwitch; output->Length(0); break; } } } void OnLine(char *str) { // printf("line='%s'\n", str); if (output) output->Add(str); if (!strcasecmp(str, "(gdb) ")) AtPrompt(); } int main(int argCount, char **arg) { int pid = -1; for (int i=1; i 0) { memmove(buf, nl, remaining); used = remaining; // printf("rem=%i used=%i\n", (int)remaining, (int)used); } else { used = 0; // printf("break used=%i\n", (int)used); break; } } if (used > 0) { buf[used] = 0; OnLine(buf); used = 0; } // printf("\n"); } if (crashLog) fclose(crashLog); return 0; } diff --git a/src/linux/CrashHandler/CrashHandlerMakefile.linux b/src/linux/CrashHandler/CrashHandlerMakefile.linux --- a/src/linux/CrashHandler/CrashHandlerMakefile.linux +++ b/src/linux/CrashHandler/CrashHandlerMakefile.linux @@ -1,80 +1,85 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = ./crash-handler ifndef Build Build = Debug endif +MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BuildDir = $(Build) -Flags = -fPIC -w -fno-inline -fpermissive +CFlags = -MMD -MP -g -fPIC -fno-inline +CppFlags = $(CFlags) -fpermissive -std=c++14 ifeq ($(Build),Debug) - Flags += -g -std=c++14 + CFlags += -g + CppFlags += -g Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../../../include/lgi/linux/Gtk \ -I../../../include else - Flags += -s -Os -std=c++14 + CFlags += -s -Os + CppFlags += -s -Os Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../../../include/lgi/linux/Gtk \ -I../../../include endif # Dependencies Source = CrashHandler.cpp -SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Source))) +SourceC := $(filter %.c,$(Source)) +ObjectsC := $(SourceC:.c=.o) +SourceCpp := $(filter %.cpp,$(Source)) +ObjectsCpp := $(SourceCpp:.cpp=.o) +Objects := $(notdir $(ObjectsC) $(ObjectsCpp)) +Objects := $(addprefix $(BuildDir)/,$(Objects)) +Deps := $(patsubst %.o,%.d,$(Objects)) -Objects := $(addprefix $(BuildDir)/,$(SourceLst)) +$(BuildDir)/%.o: %.c + mkdir -p $(@D) + echo $(notdir $<) [$(Build)] + $(CC) $(Inc) $(CFlags) $(Defs) -c $< -o $@ + +$(BuildDir)/%.o: %.cpp + mkdir -p $(@D) + echo $(notdir $<) [$(Build)] + $(CPP) $(Inc) $(CppFlags) $(Defs) -c $< -o $@ # Target # Executable target $(Target) : $(Objects) mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. -.SECONDEXPANSION: -$(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) - mkdir -p $(@D) - @echo $( Img; /////////////////////////////////////////////////////////////////////////////////////////////// LClipBoard::LClipBoard(LView *o) { d = new LClipBoardPriv; Owner = o; Open = false; d->c = gtk_clipboard_get(GDK_NONE); // gdk_atom_intern("CLIPBOARD", false) if (d->c) Open = true; #if DEBUG_CLIPBOARD printf("d->c = %i\n", d->c); #endif } LClipBoard::~LClipBoard() { d->c = 0; DeleteObj(d); } bool LClipBoard::Empty() { if (d->c) { gtk_clipboard_clear(d->c); #if DEBUG_CLIPBOARD printf("gtk_clipboard_clear(%i)\n", d->c); #endif return true; } return false; } bool LClipBoard::EnumFormats(::LArray &Formats) { return false; } bool LClipBoard::Html(const char *doc, bool AutoEmpty) { return false; } ::LString LClipBoard::Html() { return ::LString(); } bool LClipBoard::Text(const char *Str, bool AutoEmpty) { bool Status = false; if (AutoEmpty) { Empty(); } if (Str && d->c) { gtk_clipboard_set_text(d->c, Str, strlen(Str)); #if DEBUG_CLIPBOARD printf("gtk_clipboard_set_text(%i,%s,%i)\n", d->c, Str, strlen(Str)); #endif Status = true; } return Status; } char *LClipBoard::Text() { char *t = 0; if (d->c) { #if DEBUG_CLIPBOARD printf("gtk_clipboard_wait_for_text starting...\n"); #endif gchar *txt = gtk_clipboard_wait_for_text(d->c); #if DEBUG_CLIPBOARD printf("gtk_clipboard_wait_for_text(%i)='%s'\n", d->c, txt); #endif if (txt) { t = NewStr(txt); g_free(txt); } } return t; } bool LClipBoard::TextW(const char16 *Str, bool AutoEmpty) { LAutoString u(WideToUtf8(Str)); return Text(u, AutoEmpty); } char16 *LClipBoard::TextW() { LAutoString u(Text()); return Utf8ToWide(u); } void LClipBoard::FreeImage(unsigned char *pixels) { if (Img) { if (pixels == (*Img)[0]) Img.Reset(); else LgiTrace("%s:%i - LClipBoard::FreeImage wrong ptr.\n", _FL); } } bool LClipBoard::Bitmap(LSurface *pDC, bool AutoEmpty) { if (!pDC || !d->c) { LAssert(!"Param error."); return false; } if (!Img.Reset(new LMemDC)) { LAssert(!"Can't create surface..."); return false; } if (!Img->Create(pDC->X(), pDC->Y(), CsArgb32)) { LAssert(!"Can't create surface copy..."); return false; } // We have to create a copy to get the byte order right: Img->Blt(0, 0, pDC); // Img->SwapRedAndBlue(); // Img->Colour(LColour(0xff, 0, 0)); // Img->Rectangle(); // And also if the caller free's their copy of the image before the pixbuf is done it'll crash... auto pb = gdk_pixbuf_new_from_data( (*Img)[0], GDK_COLORSPACE_RGB, LColourSpaceHasAlpha(Img->GetColourSpace()), 8, Img->X(), Img->Y(), Img->GetRowStep(), [](auto pixels, auto obj) { auto This = (LClipBoard*)obj; This->FreeImage(pixels); }, this); if (!pb) { if (Img) { - LgiTrace("%s:%i - gdk_pixbuf_new_from_data failed for %s, params:\n" + LgiTrace("%s:%i - gdk_pixbuf_new_from_data failed for %s, params:\n" "ptr: %p, alpha: %i, bits: %i, size: %ix%i, row: %i\n", LColourSpaceToString(Img->GetColourSpace()), (*Img)[0], LColourSpaceHasAlpha(Img->GetColourSpace()), Img->GetBits(), Img->X(), Img->Y(), Img->GetRowStep()); } LAssert(!"gdk_pixbuf_new_from_data failed."); return false; } gtk_clipboard_set_image(d->c, pb); return true; // have to assume it worked... } -void ClipboardImageReceived(GtkClipboard *Clipboard, GdkPixbuf *Img, LClipBoard::BitmapCb *Cb) +static void ClipboardImageReceived(GtkClipboard *Clipboard, GdkPixbuf *Img, LClipBoard::BitmapCb *Cb) { auto chan = gdk_pixbuf_get_n_channels(Img); auto alpha = gdk_pixbuf_get_has_alpha(Img); LColourSpace cs = System32BitColourSpace; LAutoPtr Out; if (chan == 4) { cs = System32BitColourSpace; } else if (chan == 3) { if (alpha) cs = System32BitColourSpace; else cs = System24BitColourSpace; } else if (chan == 1) { cs = CsIndex8; } else { LString s; s.Printf("Unexpected colourspace: %i channels.", (int)chan); (*Cb)(Out, s); delete Cb; return; } auto x = gdk_pixbuf_get_width(Img), y = gdk_pixbuf_get_height(Img); LAutoPtr m(new LMemDC(x, y, cs)); if (!m) { (*Cb)(Out, "Alloc failed"); delete Cb; return; } auto px = gdk_pixbuf_get_pixels(Img); auto row = gdk_pixbuf_get_rowstride(Img); for (int yy=0; yyr = in->r; out->g = in->g; out->b = in->b; \ out++; in++; \ } \ break; \ } #define Rop32(out_cs, in_cs) \ case Cs##out_cs: \ { \ auto in = (L##in_cs*)(px + (yy*row)); \ auto out = (L##out_cs*) (*m)[yy]; \ auto end = out + x; \ while (out < end) \ { \ out->r = in->r; out->g = in->g; out->b = in->b; out->a = in->a; \ out++; in++; \ } \ break; \ } switch (m->GetColourSpace()) { Rop24(Bgr24, Rgb24); Rop24(Rgb24, Rgb24); Rop24(Bgrx32, Rgb24); Rop24(Rgbx32, Rgb24); Rop24(Xrgb32, Rgb24); Rop24(Xbgr32, Rgb24); Rop32(Bgra32, Rgba32); Rop32(Rgba32, Rgba32); Rop32(Argb32, Rgba32); Rop32(Abgr32, Rgba32); default: LAssert(!"Unsupported colour space."); yy = y; break; } } Out.Reset(m.Release()); (*Cb)(Out, LString()); delete Cb; } -bool LClipBoard::Bitmap(LClipBoard::BitmapCb Callback) +void LClipBoard::Bitmap(LClipBoard::BitmapCb Callback) { if (!Callback) - return false; + return; gtk_clipboard_request_image(d->c, (GtkClipboardImageReceivedFunc) ClipboardImageReceived, new LClipBoard::BitmapCb(Callback)); - return true; } -LAutoPtr LClipBoard::Bitmap() -{ - auto Ts = LCurrentTime(); - LString Error; - LAutoPtr Img; - - LClipBoard::BitmapCb Callback = [&](auto img, auto err) - { - Img = img; - Error = err; - }; - - gtk_clipboard_request_image(d->c, (GtkClipboardImageReceivedFunc) ClipboardImageReceived, new LClipBoard::BitmapCb(Callback)); - - while (!Error && !Img && (LCurrentTime() - Ts) < LGI_RECEIVE_CLIPBOARD_TIMEOUT) - LYield(); - - return Img; -} - void LgiClipboardGetFunc(GtkClipboard *clipboard, - GtkSelectionData *data, - guint info, - gpointer user_data) + GtkSelectionData *data, + guint info, + gpointer user_data) { if (Data.Lock(_FL)) { ::LVariant *p = (::LVariant*)user_data; #if DEBUG_CLIPBOARD printf("%s:%i - LgiClipboardGetFunc: %p, %i\n", _FL, p, info); #endif switch (info) { case GV_BINARY: { if (p->Type == info) { // data->data = p->Value.Binary.Data; // data->length = p->Value.Binary.Length; gtk_selection_data_set(data, gtk_selection_data_get_target(data), 8, (guchar*)p->Value.Binary.Data, p->Value.Binary.Length); } else LgiTrace("%s:%i - Variant is the wrong type: %i\n", _FL, p->Type); break; } default: { LgiTrace("%s:%i - Undefined data type: %i\n", _FL, info); break; } } Data.Unlock(); } } void LgiClipboardClearFunc(GtkClipboard *clipboard, - gpointer user_data) + gpointer user_data) { if (Data.Lock(_FL)) { ::LVariant *p = (::LVariant*)user_data; #if DEBUG_CLIPBOARD printf("%s:%i - LgiClipboardClearFunc: %i\n", _FL, p->Type); #endif p->Empty(); Data.Unlock(); } } bool LClipBoard::Binary(FormatType Format, uchar *Ptr, ssize_t Len, bool AutoEmpty) { if (!Ptr || Len <= 0) return false; - ::LVariant *p = NULL; + LVariant *p = NULL; if (Data.Lock(_FL)) { for (int i=0; iSetBinary(Len, Ptr); break; } } Data.Unlock(); } if (!p) { #if DEBUG_CLIPBOARD printf("%s:%i - no slots to store data\n", _FL); #endif return false; } GtkTargetEntry te; te.target = (char*)LGI_CLIP_BINARY; te.flags = 0; // GTK_TARGET_SAME_APP? te.info = GV_BINARY; // App defined data type ID Gtk::gboolean r = gtk_clipboard_set_with_data(d->c, - &te, - 1, - LgiClipboardGetFunc, - LgiClipboardClearFunc, - p); + &te, + 1, + LgiClipboardGetFunc, + LgiClipboardClearFunc, + p); #if DEBUG_CLIPBOARD printf("%s:%i - gtk_clipboard_set_with_data = %i\n", _FL, r); #endif return r; } -::LString::Array LClipBoard::Files() +void LClipBoard::Files(FilesCb Callback) { - ::LString::Array a; - return a; + if (!Callback) + return; + + gtk_clipboard_request_uris (d->c, + [](auto clipboard, auto uris, auto data) + { + LAutoPtr cb((FilesCb*)data); + + LString::Array files; + for (int i=0; uris[i]; i++) + files.Add(uris[i]); + + (*cb)(files, LString()); + }, + new FilesCb(Callback)); } -bool LClipBoard::Files(::LString::Array &a, bool AutoEmpty) +enum FilesDataType { - return false; -} - -struct ReceiveData -{ - LAutoPtr *Ptr; - ssize_t *Len; + UriList, + GnomeCopiedFiles, + Utf8String, }; -void LgiClipboardReceivedFunc(GtkClipboard *clipboard, - GtkSelectionData *data, - gpointer user_data) +bool LClipBoard::Files(LString::Array &a, bool AutoEmpty) { - ReceiveData *r = (ReceiveData*) user_data; - if (!data || !r) - { - LgiTrace("%s:%i - Missing ptr: %p %p\n", _FL, data, r); - return; - } - - auto Bytes = gtk_selection_data_get_length(data); - if (Bytes < 0) + GtkTargetEntry targets[3] = { - LgiTrace("%s:%i - No data? (%i)\n", _FL, Bytes); - return; - } + { "text/uri-list", 0, UriList }, + { "x-special/gnome-copied-files", 0, GnomeCopiedFiles }, + { "UTF8_STRING", 0, Utf8String }, + }; - uint8_t *d = new uint8_t[Bytes]; - if (!d) - { - LgiTrace("%s:%i - Alloc failed %i\n", _FL, Bytes); - return; - } - - memcpy(d, gtk_selection_data_get_data(data), Bytes); - if (r->Len) - *r->Len = Bytes; - r->Ptr->Reset(d); - - #if DEBUG_CLIPBOARD - printf("%s:%i - LgiClipboardReceivedFunc\n", _FL); - #endif + return gtk_clipboard_set_with_data( + d->c, + targets, + CountOf(targets), + []( GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer user_data_or_owner) + { + auto type = (FilesDataType)info; + switch (type) + { + case UriList: + { + break; + } + case GnomeCopiedFiles: + { + break; + } + case Utf8String: + { + break; + } + } + }, + []( GtkClipboard *clipboard, + gpointer user_data_or_owner) + { + }, + NULL); } -bool LClipBoard::Binary(FormatType Format, LAutoPtr &Ptr, ssize_t *Len) +void LClipBoard::Binary(FormatType Format, BinaryCb Callback) { - ReceiveData r = {&Ptr, Len}; - - gtk_clipboard_request_contents( d->c, - gdk_atom_intern(LGI_CLIP_BINARY, false), - LgiClipboardReceivedFunc, - &r); - + if (!Callback) + return; + + gtk_clipboard_request_contents( + // The clipboard + d->c, + // The atom to return + gdk_atom_intern(LGI_CLIP_BINARY, false), + // Lambda callback to receive the data + [](auto clipboard, auto data, auto ptr) + { + LAutoPtr cb((BinaryCb*)ptr); + + auto Bytes = gtk_selection_data_get_length(data); + if (Bytes < 0) + { + LgiTrace("%s:%i - No data? (%i)\n", _FL, Bytes); + return; + } - uint64 Start = LCurrentTime(); - do - { - if (r.Ptr->Get()) - break; - LYield(); - LSleep(1); - } - while (LCurrentTime() - Start > LGI_RECEIVE_CLIPBOARD_TIMEOUT); + LString s; + if (!s.Length(Bytes)) + { + LgiTrace("%s:%i - Alloc failed %i\n", _FL, Bytes); + return; + } - #if DEBUG_CLIPBOARD - printf("%s:%i - LClipBoard::Binary %p, %i\n", _FL, r.Ptr->Get(), Len ? *Len : -1); - #endif - - return r.Ptr->Get() != NULL; + memcpy(s.Get(), gtk_selection_data_get_data(data), Bytes); + (*cb)(s, LString()); + }, + // Copy of the user's callback + new BinaryCb(Callback)); } diff --git a/src/mac/cocoa/ClipBoard.mm b/src/mac/cocoa/ClipBoard.mm --- a/src/mac/cocoa/ClipBoard.mm +++ b/src/mac/cocoa/ClipBoard.mm @@ -1,337 +1,329 @@ // MacOSX Clipboard Implementation #include "lgi/common/Lgi.h" #include "lgi/common/ClipBoard.h" #define kClipboardTextType "public.utf16-plain-text" class LClipBoardPriv { public: LClipBoardPriv() { } ~LClipBoardPriv() { } }; /////////////////////////////////////////////////////////////////////////////////////////////// LClipBoard::LClipBoard(LView *o) { d = new LClipBoardPriv; Owner = o; Open = true; } LClipBoard::~LClipBoard() { DeleteObj(d); } bool LClipBoard::Empty() { LAutoPool Ap; bool Status = false; Txt.Empty(); wTxt.Reset(); NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; [pasteboard clearContents]; return Status; } bool LClipBoard::Text(const char *Str, bool AutoEmpty) { LAutoPool Ap; LAssert(AutoEmpty); // If the pasteboard isn't emptied we can't set it's value Empty(); Txt = Str; wTxt.Reset(); auto *pb = [NSPasteboard generalPasteboard]; auto *text = Txt.NsStr(); [pb addTypes:[NSArray arrayWithObject:NSPasteboardTypeString] owner:nil]; auto result = [pb setString:text forType:NSPasteboardTypeString]; LAssert(result); return result != 0; } char *LClipBoard::Text() { LAutoPool Ap; NSArray *classes = [[NSArray alloc] initWithObjects:[NSString class], nil]; NSArray *copiedItems = [[NSPasteboard generalPasteboard] readObjectsForClasses:classes options:[NSDictionary dictionary]]; if (copiedItems != nil) { for (NSString *s in copiedItems) { Txt = [s UTF8String]; break; } } [classes release]; return Txt; } bool LClipBoard::TextW(const char16 *Str, bool AutoEmpty) { bool Status = false; if (AutoEmpty) Empty(); Txt = Str; wTxt.Reset(); NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; NSArray *array = [NSArray arrayWithObject:Txt.NsStr()]; [pasteboard writeObjects:array]; return Status; } char16 *LClipBoard::TextW() { Text(); wTxt.Reset(Utf8ToWide(Txt)); return wTxt; } bool LClipBoard::Html(const char *doc, bool AutoEmpty) { return false; } LString LClipBoard::Html() { return LString(); } bool LClipBoard::Bitmap(LSurface *pDC, bool AutoEmpty) { LAssert(!"Not impl."); return false; } -bool LClipBoard::Bitmap(BitmapCb Callback) +void LClipBoard::Bitmap(BitmapCb Callback) { LAssert(!"Not impl."); - return false; } -LAutoPtr LClipBoard::Bitmap() +void LClipBoard::Files(FilesCb Callback) { LAssert(!"Not impl."); - return LAutoPtr(NULL); -} - -LString::Array LClipBoard::Files() -{ - LAssert(!"Not impl."); - return LString::Array(); } bool LClipBoard::Files(LString::Array &Paths, bool AutoEmpty) { LAssert(!"Not impl."); return false; } // This is a custom type to wrap binary data. NSString *const LBinaryDataPBoardType = @"com.memecode.lgi"; #if 0 static void _dump(const char *verb, uchar *ptr, uint64_t len) { printf("%s " LPrintfInt64 " bytes:\n", verb, len); for (int i=0; i mem; mem.Length(sizeof(LBinaryData_Hdr)+fmt.Length()); LBinaryData_Hdr *h = (LBinaryData_Hdr*)mem.AddressOf(); h->Magic = LBinaryData_Magic; h->FormatLen = (uint32_t)fmt.Length(); h->DataLen = Len; strcpy(h->Format, fmt); NSMutableData *m; self.data = m = [[NSMutableData alloc] initWithBytes:mem.AddressOf() length:mem.Length()]; [m appendBytes:ptr length:Len]; // _dump("Pasting", ptr, h->DataLen); } return self; } - (id)init:(NSData*)d { if ((self = [super init]) != nil) { self.data = d; } return self; } // Any of these parameters can be non-NULL if the caller doesn't care about them -- (bool)getData:(LString*)Format data:(LAutoPtr*)Ptr len:(ssize_t*)Len var:(LVariant*)Var +- (bool)getData:(LString*)Format data:(LString*)Str var:(LVariant*)Var { if (!self.data) { LgiTrace("%s:%i - No data object.\n", _FL); return false; } LArray mem; if (!mem.Length(MIN(self.data.length, 256))) { LgiTrace("%s:%i - Alloc failed.\n", _FL); return false; } [self.data getBytes:mem.AddressOf() length:mem.Length()]; LBinaryData_Hdr *h = (LBinaryData_Hdr*)mem.AddressOf(); if (h->Magic != LBinaryData_Magic) { LgiTrace("%s:%i - Data block missing magic.\n", _FL); return false; } - if (Len) - *Len = h->DataLen; + // h->DataLen; if (Format) *Format = h->Format; - if (Ptr) + if (Str) { - if (!Ptr->Reset(new uint8_t[h->DataLen])) + if (!Str->Length(h->DataLen)) { LgiTrace("%s:%i - Failed to alloc " LPrintfInt64 " bytes.\n", _FL, h->DataLen); return false; } NSRange r; r.location = sizeof(LBinaryData_Hdr) + h->FormatLen; r.length = h->DataLen; - [self.data getBytes:Ptr->Get() range:r]; + [self.data getBytes:Str->Get() range:r]; // _dump("Receiving", Ptr.Get(), h->DataLen); } else if (Var) { Var->Empty(); Var->Type = GV_BINARY; Var->Value.Binary.Length = h->DataLen; if ((Var->Value.Binary.Data = new char[h->DataLen])) { NSRange r; r.location = sizeof(LBinaryData_Hdr) + h->FormatLen; r.length = h->DataLen; [self.data getBytes:Var->Value.Binary.Data range:r]; } else return false; } return true; } - (nullable id)pasteboardPropertyListForType:(NSString *)type { if ([type isEqualToString:LBinaryDataPBoardType]) return self.data; return nil; } - (NSArray *)writableTypesForPasteboard:(NSPasteboard *)pasteboard { return [NSArray arrayWithObjects:LBinaryDataPBoardType, kUTTypeData, nil]; } + (NSPasteboardReadingOptions)readingOptionsForType:(NSString *)type pasteboard:(NSPasteboard *)pasteboard { return NSPasteboardReadingAsData; } + (NSArray *)readableTypesForPasteboard:(NSPasteboard *)pasteboard { return [NSArray arrayWithObjects:LBinaryDataPBoardType, kUTTypeData, nil]; } @end #define LGI_ClipBoardType "clipboard-binary" bool LClipBoard::Binary(FormatType Format, uchar *Ptr, ssize_t Len, bool AutoEmpty) { if (!Ptr || Len <= 0) return false; NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; auto data = [[LBinaryData alloc] init:LGI_ClipBoardType ptr:Ptr len:Len]; NSArray *array = [NSArray arrayWithObject:data]; [pasteboard clearContents]; auto r = [pasteboard writeObjects:array]; return r; } -bool LClipBoard::Binary(FormatType Format, LAutoPtr &Ptr, ssize_t *Len) +void LClipBoard::Binary(FormatType Format, BinaryCb Callback) { NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; auto d = [pasteboard dataForType:LBinaryDataPBoardType]; if (!d) { - LgiTrace("%s:%i - No LBinaryDataPBoardType data.\n", _FL); - return false; + if (Callback) Callback(LString(), "No LBinaryDataPBoardType data."); + return; } auto data = [[LBinaryData alloc] init:d]; if (!data) { - LgiTrace("%s:%i - LBinaryData alloc failed.\n", _FL); - return false; + if (Callback) Callback(LString(), "LBinaryData alloc failed."); + return; } - auto Status = [data getData:NULL data:&Ptr len:Len var:NULL]; + LString str; + [data getData:NULL data:&str var:NULL]; [data release]; - return Status; + if (Callback) Callback(str, LString()); } diff --git a/src/win/Lgi/ClipBoard.cpp b/src/win/Lgi/ClipBoard.cpp --- a/src/win/Lgi/ClipBoard.cpp +++ b/src/win/Lgi/ClipBoard.cpp @@ -1,892 +1,873 @@ #include "lgi/common/Lgi.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Palette.h" #include "lgi/common/Com.h" class LClipBoardPriv { public: LString Utf8; LAutoWString Wide; }; #if 0 class LFileEnum : public LUnknownImpl { int Idx; LArray Types; public: LFileEnum(LClipBoard::FormatType type) { Idx = 0; // Types.Add(type); Types.Add(CF_HDROP); AddInterface(IID_IEnumFORMATETC, (IEnumFORMATETC*)this); } ~LFileEnum() { LgiTrace("%s:%i - ~LFileEnum()\n", _FL); } HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC *fmt, ULONG *pceltFetched) { if (!fmt || Idx >= Types.Length()) return S_FALSE; fmt->cfFormat = Types[Idx]; fmt->dwAspect = DVASPECT_CONTENT; fmt->lindex = -1; fmt->ptd = NULL; fmt->tymed = TYMED_HGLOBAL; if (pceltFetched) *pceltFetched = 1; Idx += celt; LgiTrace("%s:%i - Next(%i) returned '%s'\n", _FL, celt, LClipBoard::FmtToStr(fmt->cfFormat).Get()); return S_OK; } HRESULT STDMETHODCALLTYPE Skip(ULONG celt) { return S_OK; } HRESULT STDMETHODCALLTYPE Reset() { return S_OK; } HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC **ppenum) { return E_NOTIMPL; } }; struct LFileName : public LUnknownImpl { char16 *w; LFileName(STGMEDIUM *Med, const char *u) { Med->tymed = TYMED_FILE; Med->lpszFileName = w = Utf8ToWide(u); Med->pUnkForRelease = this; } ~LFileName() { DeleteArray(w); } }; class LFileData : public LUnknownImpl { int Cur; LClipBoard::FormatType Type, PrefDrop, ShellIdList; public: LString::Array Files; LFileData(LString::Array &files) : Files(files) { // TraceRefs = true; Type = LClipBoard::StrToFmt(CFSTR_FILENAMEA); PrefDrop = LClipBoard::StrToFmt(CFSTR_PREFERREDDROPEFFECT); ShellIdList = LClipBoard::StrToFmt(CFSTR_SHELLIDLIST); Cur = 0; AddInterface(IID_IDataObject, (IDataObject*)this); LgiTrace("%s:%i - LFileData() = %p\n", _FL, this); } ~LFileData() { LgiTrace("%s:%i - ~LFileData()\n", _FL); } HRESULT STDMETHODCALLTYPE GetData(FORMATETC *Fmt, STGMEDIUM *Med) { LString sFmt = LClipBoard::FmtToStr(Fmt->cfFormat); LgiTrace("%s:%i - GetData(%s) starting...\n", _FL, sFmt.Get()); if (!Med) return E_INVALIDARG; if (Fmt->cfFormat == PrefDrop) { Med->tymed = TYMED_HGLOBAL; Med->hGlobal = GlobalAlloc(GHND, sizeof(DWORD)); DWORD* data = (DWORD*)GlobalLock(Med->hGlobal); *data = DROPEFFECT_COPY; GlobalUnlock(Med->hGlobal); Med->pUnkForRelease = NULL; } else if (Fmt->cfFormat == Type) { new LFileName(Med, Files[Cur++]); } else if (Fmt->cfFormat == CF_HDROP) { LDragDropSource Src; LDragData Data; LMouse m; if (!Src.CreateFileDrop(&Data, m, Files)) { LgiTrace("%s:%i - CreateFileDrop failed.\n", _FL); return E_FAIL; } LVariant &d = Data.Data[0]; Med->tymed = TYMED_HGLOBAL; Med->hGlobal = GlobalAlloc(GHND, d.Value.Binary.Length); if (Med->hGlobal == NULL) { LgiTrace("%s:%i - GlobalAlloc failed.\n", _FL); return E_FAIL; } char* data = (char*)GlobalLock(Med->hGlobal); memcpy(data, d.Value.Binary.Data, d.Value.Binary.Length); GlobalUnlock(Med->hGlobal); Med->pUnkForRelease = NULL; } else if (Fmt->cfFormat == ShellIdList) { LgiTrace("%s:%i - GetData ShellIdList not supported.\n", _FL); return E_NOTIMPL; /* LPIDA Data = NULL; ITEMIDLIST *IdList = NULL; size_t Size = sizeof(CIDA) + (Files.Length() * sizeof(UINT)) + (Files.Length() * sizeof(ITEMIDLIST)); Med->hGlobal = GlobalAlloc(GHND, Size); if (Med->hGlobal == NULL) return E_FAIL; Data = (LPIDA) GlobalLock(Med->hGlobal); Data->cidl = Files.Length(); LPITEMIDLIST *Parent = (LPITEMIDLIST) (Data + 1); Data->aoffset[0] = (char*)Parent - (char*)Data; LPointer p; p.vp = Parent + 1; for (unsigned i=0; iaoffset[i+1] } GlobalUnlock(Med->hGlobal); Med->pUnkForRelease = NULL; */ } else { LgiTrace("%s:%i - GetData(%s) not supported.\n", _FL, sFmt.Get()); return DV_E_FORMATETC; } LgiTrace("%s:%i - GetData(%s) OK.\n", _FL, sFmt.Get()); return S_OK; } HRESULT STDMETHODCALLTYPE GetDataHere(FORMATETC *pformatetc, STGMEDIUM *pmedium) { LgiTrace("%s:%i - GetDataHere not impl\n", _FL); return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE QueryGetData(FORMATETC *Fmt) { if (Fmt->cfFormat == Type || Fmt->cfFormat == CF_HDROP) return S_OK; LString sFmt = LClipBoard::FmtToStr(Fmt->cfFormat); LgiTrace("%s:%i - QueryGetData(%s) not supported.\n", _FL, sFmt.Get()); return DV_E_FORMATETC; } HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(FORMATETC *Fmt, FORMATETC *pformatetcOut) { LgiTrace("%s:%i - GetCanonicalFormatEtc not impl\n", _FL); return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE SetData(FORMATETC *Fmt, STGMEDIUM *pmedium, BOOL fRelease) { LString sFmt = LClipBoard::FmtToStr(Fmt->cfFormat); LgiTrace("%s:%i - SetData(%s)\n", _FL, sFmt.Get()); return S_OK; } HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dir, IEnumFORMATETC **enumFmt) { if (dir == DATADIR_GET) { if (*enumFmt = new LFileEnum(Type)) (*enumFmt)->AddRef(); LgiTrace("%s:%i - Returning LFileEnum obj.\n", _FL); return S_OK; } else if (dir == DATADIR_SET) { } LgiTrace("%s:%i - EnumFormatEtc error\n", _FL); return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE DAdvise(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) { LgiTrace("%s:%i - DAdvise not impl\n", _FL); return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) { LgiTrace("%s:%i - DUnadvise not impl\n", _FL); return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA **ppenumAdvise) { LgiTrace("%s:%i - EnumDAdvise not impl\n", _FL); return E_NOTIMPL; } }; #endif -LString::Array LClipBoard::Files() +void LClipBoard::Files(FilesCb Callback) { LString::Array f; + LString errMsg; if (Open) { CloseClipboard(); Open = FALSE; } LPDATAOBJECT pObj = NULL; auto r = OleGetClipboard(&pObj); - if (SUCCEEDED(r)) + if (FAILED(r)) + { + errMsg = "OleGetClipboard failed."; + + LArray Fmts; + if (EnumFormats(Fmts)) + { + for (auto f : Fmts) + { + auto s = FmtToStr(f); + LgiTrace("ClipFmt: %s\n", s.Get()); + } + } + } + else { LArray Fmts; IEnumFORMATETC *pEnum = NULL; r = pObj->EnumFormatEtc(DATADIR_GET, &pEnum); - if (SUCCEEDED(r)) + if (FAILED(r)) + { + errMsg = "EnumFormatEtc failed."; + } + else { FORMATETC Fmt; r = pEnum->Next(1, &Fmt, NULL); while (r == S_OK) { Fmts.Add(Fmt.cfFormat); LgiTrace("Got format: 0x%x = %s\n", Fmt.cfFormat, FmtToStr(Fmt.cfFormat).Get()); if (Fmt.cfFormat == CF_HDROP) { STGMEDIUM Med; auto Res = pObj->GetData(&Fmt, &Med); if (Res == S_OK) { switch (Med.tymed) { case TYMED_HGLOBAL: { auto Sz = GlobalSize(Med.hGlobal); DROPFILES *p = (DROPFILES*)GlobalLock(Med.hGlobal); if (p) { LPointer End; End.c = (char*)p + Sz; if (p->fWide) { wchar_t *w = (wchar_t*) ((char*)p + p->pFiles); while (w < End.w && *w) { f.Add(w); w += Strlen(w) + 1; } } else { char *n = (char*)p + p->pFiles; while (n < End.c && *n) { auto u = LFromNativeCp(n); f.Add(u.Get()); n += Strlen(n) + 1; } } } /* int Count = DragQueryFileW(hDrop, -1, NULL, 0); for (int i=0; i 0) { FileNames.Add(WideToUtf8(FileName)); } } */ GlobalUnlock(Med.hGlobal); break; } } } } r = pEnum->Next(1, &Fmt, NULL); } pEnum->Release(); } pObj->Release(); } - else - { - LArray Fmts; - if (EnumFormats(Fmts)) - { - for (auto f : Fmts) - { - auto s = FmtToStr(f); - LgiTrace("ClipFmt: %s\n", s.Get()); - } - } - } - return f; + if (Callback) + Callback(f, errMsg); } bool LClipBoard::Files(LString::Array &Paths, bool AutoEmpty) { LDragDropSource Src; LDragData Output; LMouse m; if (Owner) Owner->GetMouse(m, true); if (!Src.CreateFileDrop(&Output, m, Paths)) return false; LVariant &v = Output.Data[0]; if (v.Type != GV_BINARY) return false; HGLOBAL hMem = GlobalAlloc(GMEM_ZEROINIT|GMEM_MOVEABLE|GMEM_DDESHARE, v.Value.Binary.Length); auto *p = GlobalLock(hMem); CopyMemory(p, v.Value.Binary.Data, v.Value.Binary.Length); GlobalUnlock(hMem); OpenClipboard(NULL); if (AutoEmpty) EmptyClipboard(); auto r = SetClipboardData(CF_HDROP, hMem); CloseClipboard(); return r != NULL; } /////////////////////////////////////////////////////////////////////////////////////////////// LString LClipBoard::FmtToStr(FormatType Fmt) { TCHAR n[256] = {0}; int r = GetClipboardFormatName(Fmt, n, CountOf(n)); if (!r) { switch (Fmt) { case CF_TEXT: return "CF_TEXT"; case CF_BITMAP: return "CF_BITMAP"; case CF_HDROP: return "CF_HDROP"; case CF_UNICODETEXT: return "CF_UNICODETEXT"; default: LAssert(!"Not impl."); break; } } return n; } LClipBoard::FormatType LClipBoard::StrToFmt(LString Fmt) { return RegisterClipboardFormatA(Fmt); } /////////////////////////////////////////////////////////////////////////////////////////////// LClipBoard::LClipBoard(LView *o) { d = new LClipBoardPriv; Open = false; Owner = o; if (Owner) Open = OpenClipboard(Owner->Handle()) != 0; } LClipBoard::~LClipBoard() { if (Open) { CloseClipboard(); Open = FALSE; } DeleteObj(d); } LClipBoard &LClipBoard::operator =(LClipBoard &c) { LAssert(0); return *this; } bool LClipBoard::EnumFormats(LArray &Formats) { UINT Idx = 0; UINT Fmt; while (Fmt = EnumClipboardFormats(Idx)) { Formats.Add(Fmt); Idx++; } return Formats.Length() > 0; } bool LClipBoard::Empty() { d->Utf8.Empty(); d->Wide.Reset(); return EmptyClipboard() != 0; } // Text bool LClipBoard::Text(const char *Str, bool AutoEmpty) { bool Status = false; if (Str) { auto Native = LToNativeCp(Str); if (Native) { Status = Binary(CF_TEXT, (uchar*)Native.Get(), strlen(Native)+1, AutoEmpty); } else LgiTrace("%s:%i - Conversion to native cs failed.\n", _FL); } else LgiTrace("%s:%i - No text.\n", _FL); return Status; } char *LClipBoard::Text() { - ssize_t Len = 0; - LAutoPtr Str; - if (Binary(CF_TEXT, Str, &Len)) + Binary(CF_TEXT, [this](auto str, auto err) { - d->Utf8 = LFromNativeCp((char*)Str.Get()); - return d->Utf8; - } + d->Utf8 = LFromNativeCp(str); + }); - return NULL; + return d->Utf8; } bool LClipBoard::TextW(const char16 *Str, bool AutoEmpty) { if (Str) { auto Len = StrlenW(Str); return Binary(CF_UNICODETEXT, (uchar*) Str, (Len+1) * sizeof(ushort), AutoEmpty); } return false; } char16 *LClipBoard::TextW() { - ssize_t Len = 0; - LAutoPtr Str; - if (Binary(CF_UNICODETEXT, Str, &Len)) + Binary(CF_UNICODETEXT, [this](auto str, auto err) { - d->Wide.Reset(NewStrW((char16*) Str.Get(), Len / 2)); - return d->Wide; - } + d->Wide.Reset(NewStrW((char16*) str.Get(), str.Length() / 2)); + }); - return NULL; + return d->Wide; } #ifndef CF_HTML #define CF_HTML RegisterClipboardFormatA("HTML Format") #endif bool LClipBoard::Html(const char *Doc, bool AutoEmpty) { if (!Doc) return false; LString s; s.Printf("Version:0.9\n" "StartHTML:000000\n" "EndHTML:000000\n" ""); auto Start = s.Length(); s += Doc; auto End = s.Length(); auto p = s.Split("000000", 2); if (p.Length() != 3) return false; LString n; n.Printf("%06i", (int)Start); s = p[0] + n; n.Printf("%06i", (int)End); s += p[1] + n + p[2]; auto Len = Strlen(Doc); return Binary(CF_HTML, (uchar*) s.Get(), s.Length(), AutoEmpty); } LString LClipBoard::Html() { - LAutoPtr Buf; - ssize_t Len; - if (!Binary(CF_HTML, Buf, &Len)) + LString Txt; + Binary(CF_HTML, [&Txt](auto str, auto err) + { + Txt = str; + }); + + if (!Txt) return NULL; - LString Txt((char*)Buf.Get(), Len); auto Ln = Txt.Split("\n", 20); ssize_t Start = -1, End = -1; for (auto l : Ln) { auto p = l.Strip().Split(":", 1); if (p.Length() == 0) ; else if (p[0].Equals("StartHTML")) Start = (ssize_t)p[1].Int(); else if (p[0].Equals("EndHTML")) End = (ssize_t)p[1].Int(); } if (Start <= 0 || End <= 0) return false; return Txt(Start, End).Strip(); } // Bitmap bool LClipBoard::Bitmap(LSurface *pDC, bool AutoEmpty) { bool Status = FALSE; Empty(); if (pDC) { int Bytes = BMPWIDTH(pDC->X() * pDC->GetBits()); int Colours = (pDC->GetBits()>8) ? ((pDC->GetBits() != 24) ? 3 : 0) : 1 << pDC->GetBits(); int HeaderSize = sizeof(BITMAPINFOHEADER); int PaletteSize = Colours * sizeof(RGBQUAD); int MapSize = Bytes * pDC->Y(); int TotalSize = HeaderSize + PaletteSize + MapSize; HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, TotalSize); if (hMem) { BITMAPINFO *Info = (BITMAPINFO*) GlobalLock(hMem); if (Info) { // Header Info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); Info->bmiHeader.biWidth = pDC->X(); Info->bmiHeader.biHeight = pDC->Y(); Info->bmiHeader.biPlanes = 1; Info->bmiHeader.biBitCount = pDC->GetBits(); if (pDC->GetBits() == 16 || pDC->GetBits() == 32) { Info->bmiHeader.biCompression = BI_BITFIELDS; } else { Info->bmiHeader.biCompression = BI_RGB; } Info->bmiHeader.biSizeImage = MapSize; Info->bmiHeader.biXPelsPerMeter = 0; Info->bmiHeader.biYPelsPerMeter = 0; Info->bmiHeader.biClrUsed = 0; Info->bmiHeader.biClrImportant = 0; if (pDC->GetBits() <= 8) { // Palette LPalette *Pal = pDC->Palette(); RGBQUAD *Rgb = Info->bmiColors; if (Pal) { GdcRGB *p = (*Pal)[0]; if (p) { for (int i=0; irgbRed = p->r; Rgb->rgbGreen = p->g; Rgb->rgbBlue = p->b; Rgb->rgbReserved = p->a; } } } else { memset(Rgb, 0, Colours * sizeof(RGBQUAD)); } } else { int *Primaries = (int*) Info->bmiColors; switch (pDC->GetBits()) { case 16: { Primaries[0] = Rgb16(255, 0, 0); Primaries[1] = Rgb16(0, 255, 0); Primaries[2] = Rgb16(0, 0, 255); break; } case 32: { Primaries[0] = Rgba32(255, 0, 0, 0); Primaries[1] = Rgba32(0, 255, 0, 0); Primaries[2] = Rgba32(0, 0, 255, 0); break; } } } // Bits uchar *Dest = ((uchar*)Info) + HeaderSize + PaletteSize; for (int y=pDC->Y()-1; y>=0; y--) { uchar *s = (*pDC)[y]; if (s) { memcpy(Dest, s, Bytes); } Dest += Bytes; } #if 0 LFile f; if (f.Open("c:\\tmp\\out.bmp", O_WRITE)) { f.SetSize(0); f.Write(Info, TotalSize); f.Close(); } #endif Status = SetClipboardData(CF_DIB, hMem) != 0; } else { GlobalFree(hMem); } } } return Status; } LAutoPtr LClipBoard::ConvertFromPtr(void *Ptr) { LAutoPtr pDC; BITMAPINFO *Info = (BITMAPINFO*) Ptr; if ( Info && pDC.Reset(new LMemDC) && (Info->bmiHeader.biCompression == BI_RGB || Info->bmiHeader.biCompression == BI_BITFIELDS) ) { if ( pDC->Create ( Info->bmiHeader.biWidth, Info->bmiHeader.biHeight, LBitsToColourSpace(max(Info->bmiHeader.biPlanes * Info->bmiHeader.biBitCount, 8)) ) ) { int Colours = 0; char *Source = (char*) Info->bmiColors; // do palette if (pDC->GetBits() <= 8) { if (Info->bmiHeader.biClrUsed > 0) Colours = Info->bmiHeader.biClrUsed; else Colours = 1 << pDC->GetBits(); LPalette *Pal = new LPalette(NULL, Colours); if (Pal) { GdcRGB *d = (*Pal)[0]; RGBQUAD *s = (RGBQUAD*) Source; if (d) { for (int i=0; ir = s->rgbRed; d->g = s->rgbGreen; d->b = s->rgbBlue; d->a = s->rgbReserved; } } Source = (char*) s; pDC->Palette(Pal); } } if (Info->bmiHeader.biCompression == BI_BITFIELDS) Source += sizeof(DWORD) * 3; // do pixels int Bytes = BMPWIDTH(pDC->X() * pDC->GetBits()); for (int y=pDC->Y()-1; y>=0; y--) { uchar *d = (*pDC)[y]; if (d) memcpy(d, Source, Bytes); Source += Bytes; } } } return pDC; } -bool LClipBoard::Bitmap(BitmapCb Callback) +void LClipBoard::Bitmap(BitmapCb Callback) { if (!Callback) - return false; + return; HGLOBAL hMem = GetClipboardData(CF_DIB); LAutoPtr pDC; if (void *Ptr = GlobalLock(hMem)) { pDC = ConvertFromPtr(Ptr); GlobalUnlock(hMem); } Callback(pDC, pDC ? NULL : "No bitmap on clipboard."); - return true; -} - -LAutoPtr LClipBoard::Bitmap() -{ - HGLOBAL hMem = GetClipboardData(CF_DIB); - LAutoPtr pDC; - if (void *Ptr = GlobalLock(hMem)) - { - #if 0 - SIZE_T TotalSize = GlobalSize(hMem); - LFile f; - if (f.Open("c:\\tmp\\in.bmp", O_WRITE)) - { - f.SetSize(0); - f.Write(Ptr, TotalSize); - f.Close(); - } - #endif - - pDC = ConvertFromPtr(Ptr); - GlobalUnlock(hMem); - } - - return pDC; } bool LClipBoard::Binary(FormatType Format, uchar *Ptr, ssize_t Len, bool AutoEmpty) { bool Status = FALSE; if (AutoEmpty) { Empty(); } if (Ptr && Len > 0) { HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, Len); if (hMem) { char *Data = (char*) GlobalLock(hMem); if (Data) { memcpy(Data, Ptr, Len); GlobalUnlock(hMem); Status = SetClipboardData(Format, hMem) != 0; if (!Status) LgiTrace("%s:%i - SetClipboardData failed.\n", _FL); } else { GlobalFree(hMem); } } else LgiTrace("%s:%i - Alloc failed.\n", _FL); } else LgiTrace("%s:%i - No data to set.\n", _FL); return Status; } -bool LClipBoard::Binary(FormatType Format, LAutoPtr &Ptr, ssize_t *Length) +void LClipBoard::Binary(FormatType Format, BinaryCb Callback) { - bool Status = false; + if (!Callback) + return; HGLOBAL hMem = GetClipboardData(Format); - if (hMem) + if (!hMem) { - auto Len = GlobalSize(hMem); - if (Length) - *Length = Len; - - uchar *Data = (uchar*) GlobalLock(hMem); - if (Data) - { - if (Ptr.Reset(new uchar[Len+sizeof(char16)])) - { - memcpy(Ptr, Data, Len); - - // String termination - memset(Ptr + Len, 0, sizeof(char16)); - - Status = true; - } - } - - GlobalUnlock(hMem); + Callback(LString(), "GetClipboardData failed."); + return; } - return Status; + auto Len = GlobalSize(hMem); + uchar *Data = (uchar*) GlobalLock(hMem); + if (Data) + { + LString Ptr; + if (Ptr.Length(Len + sizeof(char16))) + { + memcpy(Ptr.Get(), Data, Len); + memset(Ptr.Get() + Len, 0, sizeof(char16)); // terminate with zeros + Ptr.Length(Len); + + Callback(Ptr, LString()); + } + else Callback(LString(), "Alloc failed."); + } + else Callback(LString(), "GlobalLock failed."); + + GlobalUnlock(hMem); } diff --git a/test/HtmlEditor/linux/Makefile.linux b/test/HtmlEditor/linux/Makefile.linux --- a/test/HtmlEditor/linux/Makefile.linux +++ b/test/HtmlEditor/linux/Makefile.linux @@ -1,406 +1,407 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = ./htmleditor ifndef Build Build = Debug endif MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BuildDir = $(Build) -Flags = -fPIC -w -fno-inline -fpermissive +CFlags = -MMD -MP -g -fPIC -fno-inline +CppFlags = $(CFlags) -fpermissive -std=c++14 ifeq ($(Build),Debug) - Flags += -MMD -MP -g -std=c++14 + CFlags += -g + CppFlags += -g Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = \ -lpthread \ `pkg-config --libs gtk+-3.0` \ -llgi-gtk3$(Tag) \ -L../../$(BuildDir) Inc = \ -I./resources \ `pkg-config --cflags gtk+-3.0` \ -I../../../../../../../usr/include/libappindicator3-0.1 \ -I../../../../../../../usr/include/gstreamer-1.0 \ -I../../private/common \ -I../../include/lgi/linux/Gtk \ -I../../include/lgi/linux \ -I../../include \ -I../../../../../codelib/openssl/include else - Flags += -MMD -MP -s -Os -std=c++14 + CFlags += -s -Os + CppFlags += -s -Os Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = \ -lpthread \ `pkg-config --libs gtk+-3.0` \ -llgi-gtk3$(Tag) \ -L../../$(BuildDir) Inc = \ -I./resources \ `pkg-config --cflags gtk+-3.0` \ -I../../../../../../../usr/include/libappindicator3-0.1 \ -I../../../../../../../usr/include/gstreamer-1.0 \ -I../../private/common \ -I../../include/lgi/linux/Gtk \ -I../../include/lgi/linux \ -I../../include \ -I../../../../../codelib/openssl/include endif # Dependencies Source = src/Rich.cpp \ ../../src/common/Widgets/Editor/TextBlock.cpp \ ../../src/common/Widgets/Editor/RichTextEditPriv.cpp \ ../../src/common/Widgets/Editor/RichTextEdit.cpp \ ../../src/common/Widgets/Editor/ImageBlock.cpp \ ../../src/common/Widgets/Editor/HorzRuleBlock.cpp \ ../../src/common/Widgets/Editor/BlockCursor.cpp \ ../../src/common/Text/HtmlParser.cpp \ ../../src/common/Text/HtmlCommon.cpp \ ../../src/common/Text/Html.cpp \ ../../src/common/Text/Homoglyphs/HomoglyphsTable.cpp \ ../../src/common/Text/Homoglyphs/Homoglyphs.cpp \ ../../src/common/Text/Emoji/EmojiTools.cpp \ ../../src/common/Text/Emoji/EmojiMap.cpp \ ../../src/common/Net/OpenSSLSocket.cpp \ ../../src/common/Net/Http.cpp \ ../../src/common/Lgi/LgiMain.cpp \ ../../src/common/Gdc2/Tools/GdcTools.cpp \ ../../src/common/Gdc2/Filters/Png.cpp \ ../../src/common/Gdc2/Filters/Lzw.cpp \ ../../src/common/Gdc2/Filters/Jpeg.cpp \ ../../src/common/Gdc2/Filters/Gif.cpp SourceC := $(filter %.c,$(Source)) ObjectsC := $(SourceC:.c=.o) SourceCpp := $(filter %.cpp,$(Source)) ObjectsCpp := $(SourceCpp:.cpp=.o) Objects := $(notdir $(ObjectsC) $(ObjectsCpp)) Objects := $(addprefix $(BuildDir)/,$(Objects)) Deps := $(patsubst %.o,%.d,$(Objects)) $(BuildDir)/%.o: %.c mkdir -p $(@D) echo $(notdir $<) [$(Build)] - $(CC) $(Inc) $(Flags) $(Defs) -c $< -o $@ + $(CC) $(Inc) $(CFlags) $(Defs) -c $< -o $@ $(BuildDir)/%.o: %.cpp mkdir -p $(@D) echo $(notdir $<) [$(Build)] - $(CPP) $(Inc) $(Flags) $(Defs) -c $< -o $@ + $(CPP) $(Inc) $(CppFlags) $(Defs) -c $< -o $@ # Target # Executable target $(Target) : ../../$(BuildDir)/liblgi-gtk3$(Tag).so $(Objects) mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. ../../$(BuildDir)/liblgi-gtk3$(Tag).so : ../../include/lgi/common/App.h \ ../../include/lgi/common/Array.h \ ../../include/lgi/common/AutoPtr.h \ ../../include/lgi/common/Base64.h \ ../../include/lgi/common/Bitmap.h \ ../../include/lgi/common/Box.h \ ../../include/lgi/common/Button.h \ ../../include/lgi/common/CairoSurface.h \ ../../include/lgi/common/Cancel.h \ ../../include/lgi/common/Capabilities.h \ ../../include/lgi/common/Charset.h \ ../../include/lgi/common/CheckBox.h \ ../../include/lgi/common/ClipBoard.h \ ../../include/lgi/common/Colour.h \ ../../include/lgi/common/ColourSpace.h \ ../../include/lgi/common/Com.h \ ../../include/lgi/common/Combo.h \ ../../include/lgi/common/Containers.h \ ../../include/lgi/common/Core.h \ ../../include/lgi/common/Css.h \ ../../include/lgi/common/CssTools.h \ ../../include/lgi/common/CurrentTime.h \ ../../include/lgi/common/DataDlg.h \ ../../include/lgi/common/DateTime.h \ ../../include/lgi/common/Dialog.h \ ../../include/lgi/common/DisplayString.h \ ../../include/lgi/common/DocView.h \ ../../include/lgi/common/Dom.h \ ../../include/lgi/common/DomFields.h \ ../../include/lgi/common/DragAndDrop.h \ ../../include/lgi/common/DropFiles.h \ ../../include/lgi/common/Edit.h \ ../../include/lgi/common/Emoji.h \ ../../include/lgi/common/Error.h \ ../../include/lgi/common/EventTargetThread.h \ ../../include/lgi/common/File.h \ ../../include/lgi/common/FileSelect.h \ ../../include/lgi/common/Filter.h \ ../../include/lgi/common/FindReplaceDlg.h \ ../../include/lgi/common/Font.h \ ../../include/lgi/common/FontCache.h \ ../../include/lgi/common/FontSelect.h \ ../../include/lgi/common/Gdc2.h \ ../../include/lgi/common/GdcTools.h \ ../../include/lgi/common/GdiLeak.h \ ../../include/lgi/common/HashTable.h \ ../../include/lgi/common/ImageList.h \ ../../include/lgi/common/Input.h \ ../../include/lgi/common/ItemContainer.h \ ../../include/lgi/common/Json.h \ ../../include/lgi/common/Layout.h \ ../../include/lgi/common/Lgi.h \ ../../include/lgi/common/LgiClasses.h \ ../../include/lgi/common/LgiCommon.h \ ../../include/lgi/common/LgiDefs.h \ ../../include/lgi/common/LgiInc.h \ ../../include/lgi/common/LgiInterfaces.h \ ../../include/lgi/common/LgiMsgs.h \ - ../../include/lgi/common/LgiNetInc.h \ ../../include/lgi/common/LgiRes.h \ ../../include/lgi/common/LgiString.h \ ../../include/lgi/common/LgiUiBase.h \ ../../include/lgi/common/Library.h \ ../../include/lgi/common/LibraryUtils.h \ ../../include/lgi/common/List.h \ ../../include/lgi/common/ListItemCheckBox.h \ ../../include/lgi/common/ListItemRadioBtn.h \ ../../include/lgi/common/LMallocArray.h \ ../../include/lgi/common/Mail.h \ ../../include/lgi/common/Matrix.h \ ../../include/lgi/common/Mem.h \ ../../include/lgi/common/Menu.h \ ../../include/lgi/common/Message.h \ ../../include/lgi/common/Mime.h \ ../../include/lgi/common/Mru.h \ ../../include/lgi/common/Mutex.h \ ../../include/lgi/common/Net.h \ - ../../include/lgi/common/NetTools.h \ ../../include/lgi/common/Notifications.h \ ../../include/lgi/common/OAuth2.h \ ../../include/lgi/common/OptionsFile.h \ ../../include/lgi/common/Palette.h \ ../../include/lgi/common/Panel.h \ ../../include/lgi/common/Password.h \ ../../include/lgi/common/Path.h \ ../../include/lgi/common/PixelRops.h \ ../../include/lgi/common/Point.h \ ../../include/lgi/common/Popup.h \ ../../include/lgi/common/PopupList.h \ ../../include/lgi/common/PopupNotification.h \ ../../include/lgi/common/Printer.h \ ../../include/lgi/common/Profile.h \ ../../include/lgi/common/Progress.h \ ../../include/lgi/common/ProgressDlg.h \ ../../include/lgi/common/ProgressView.h \ ../../include/lgi/common/Properties.h \ ../../include/lgi/common/RadioGroup.h \ ../../include/lgi/common/Range.h \ ../../include/lgi/common/Rect.h \ ../../include/lgi/common/RectF.h \ ../../include/lgi/common/RefCount.h \ ../../include/lgi/common/RegKey.h \ ../../include/lgi/common/Res.h \ ../../include/lgi/common/Rops.h \ ../../include/lgi/common/ScrollBar.h \ ../../include/lgi/common/SkinEngine.h \ ../../include/lgi/common/Slider.h \ ../../include/lgi/common/Splitter.h \ ../../include/lgi/common/StatusBar.h \ ../../include/lgi/common/Store3Defs.h \ ../../include/lgi/common/Stream.h \ ../../include/lgi/common/StringClass.h \ ../../include/lgi/common/StringLayout.h \ ../../include/lgi/common/StructuredIo.h \ ../../include/lgi/common/StructuredLog.h \ ../../include/lgi/common/SubProcess.h \ ../../include/lgi/common/TableLayout.h \ ../../include/lgi/common/TabView.h \ ../../include/lgi/common/TextFile.h \ ../../include/lgi/common/TextLabel.h \ ../../include/lgi/common/TextLog.h \ ../../include/lgi/common/TextView3.h \ ../../include/lgi/common/Thread.h \ ../../include/lgi/common/ThreadEvent.h \ ../../include/lgi/common/Token.h \ ../../include/lgi/common/ToolBar.h \ ../../include/lgi/common/ToolTip.h \ ../../include/lgi/common/TrayIcon.h \ ../../include/lgi/common/Tree.h \ ../../include/lgi/common/Undo.h \ ../../include/lgi/common/Unicode.h \ ../../include/lgi/common/UnicodeString.h \ ../../include/lgi/common/UnrolledList.h \ + ../../include/lgi/common/Uri.h \ ../../include/lgi/common/Variant.h \ ../../include/lgi/common/View.h \ ../../include/lgi/common/Widgets.h \ ../../include/lgi/common/Window.h \ ../../include/lgi/common/XmlTree.h \ ../../include/lgi/linux/Gtk/LgiOsClasses.h \ ../../include/lgi/linux/Gtk/LgiOsDefs.h \ ../../include/lgi/linux/Gtk/LgiWidget.h \ ../../include/lgi/linux/Gtk/LgiWinManGlue.h \ ../../include/lgi/linux/SymLookup.h \ ../../include/lgi/mac/cocoa/LCocoaView.h \ ../../include/lgi/mac/cocoa/LgiMac.h \ ../../include/lgi/mac/cocoa/LgiOs.h \ ../../include/lgi/mac/cocoa/LgiOsClasses.h \ ../../include/lgi/mac/cocoa/LgiOsDefs.h \ ../../include/lgi/mac/cocoa/ObjCWrapper.h \ ../../include/lgi/mac/cocoa/SymLookup.h \ ../../private/common/FontPriv.h \ ../../private/common/ViewPriv.h \ ../../private/linux/AppPriv.h \ ../../src/common/Gdc2/15Bit.cpp \ ../../src/common/Gdc2/16Bit.cpp \ ../../src/common/Gdc2/24Bit.cpp \ ../../src/common/Gdc2/32Bit.cpp \ ../../src/common/Gdc2/8Bit.cpp \ ../../src/common/Gdc2/Alpha.cpp \ ../../src/common/Gdc2/Colour.cpp \ ../../src/common/Gdc2/Filters/Filter.cpp \ ../../src/common/Gdc2/Font/Charset.cpp \ ../../src/common/Gdc2/Font/DisplayString.cpp \ ../../src/common/Gdc2/Font/Font.cpp \ ../../src/common/Gdc2/Font/FontSystem.cpp \ ../../src/common/Gdc2/Font/FontType.cpp \ ../../src/common/Gdc2/Font/StringLayout.cpp \ ../../src/common/Gdc2/Font/TypeFace.cpp \ ../../src/common/Gdc2/GdcCommon.cpp \ ../../src/common/Gdc2/Path/Path.cpp \ ../../src/common/Gdc2/Rect.cpp \ ../../src/common/Gdc2/RopsCases.cpp \ ../../src/common/Gdc2/Surface.cpp \ ../../src/common/Gdc2/Tools/ColourReduce.cpp \ ../../src/common/Gdc2/Tools/GdcTools.cpp \ ../../src/common/General/Containers.cpp \ ../../src/common/General/DateTime.cpp \ ../../src/common/General/ExeCheck.cpp \ ../../src/common/General/FileCommon.cpp \ ../../src/common/General/Password.cpp \ ../../src/common/General/Properties.cpp \ ../../src/common/Hash/md5/md5.c \ ../../src/common/Hash/md5/md5.h \ ../../src/common/Hash/sha1/sha1.c \ ../../src/common/Hash/sha1/sha1.h \ ../../src/common/Lgi/Alert.cpp \ ../../src/common/Lgi/AppCommon.cpp \ ../../src/common/Lgi/Css.cpp \ ../../src/common/Lgi/CssTools.cpp \ ../../src/common/Lgi/DataDlg.cpp \ ../../src/common/Lgi/DragAndDropCommon.cpp \ ../../src/common/Lgi/FileSelect.cpp \ ../../src/common/Lgi/FindReplace.cpp \ ../../src/common/Lgi/FontSelect.cpp \ ../../src/common/Lgi/GuiUtils.cpp \ ../../src/common/Lgi/Input.cpp \ ../../src/common/Lgi/LgiCommon.cpp \ ../../src/common/Lgi/Library.cpp \ ../../src/common/Lgi/LMsg.cpp \ ../../src/common/Lgi/MemStream.cpp \ ../../src/common/Lgi/MenuCommon.cpp \ ../../src/common/Lgi/Mru.cpp \ ../../src/common/Lgi/Mutex.cpp \ ../../src/common/Lgi/Object.cpp \ ../../src/common/Lgi/OptionsFile.cpp \ ../../src/common/Lgi/Rand.cpp \ ../../src/common/Lgi/Stream.cpp \ ../../src/common/Lgi/SubProcess.cpp \ ../../src/common/Lgi/ThreadCommon.cpp \ ../../src/common/Lgi/ThreadEvent.cpp \ ../../src/common/Lgi/ToolTip.cpp \ ../../src/common/Lgi/TrayIcon.cpp \ ../../src/common/Lgi/Variant.cpp \ ../../src/common/Lgi/ViewCommon.cpp \ ../../src/common/Lgi/WindowCommon.cpp \ ../../src/common/Net/Base64.cpp \ ../../src/common/Net/MDStringToDigest.cpp \ ../../src/common/Net/Net.cpp \ - ../../src/common/Net/NetTools.cpp \ ../../src/common/Net/Uri.cpp \ ../../src/common/Resource/LgiRes.cpp \ ../../src/common/Resource/Res.cpp \ ../../src/common/Skins/Gel/Gel.cpp \ ../../src/common/Text/DocView.cpp \ ../../src/common/Text/String.cpp \ ../../src/common/Text/TextView3.cpp \ ../../src/common/Text/Token.cpp \ ../../src/common/Text/Unicode.cpp \ ../../src/common/Text/Utf8.cpp \ ../../src/common/Text/XmlTree.cpp \ ../../src/common/Widgets/Bitmap.cpp \ ../../src/common/Widgets/Box.cpp \ ../../src/common/Widgets/Button.cpp \ ../../src/common/Widgets/CheckBox.cpp \ ../../src/common/Widgets/Combo.cpp \ ../../src/common/Widgets/Edit.cpp \ ../../src/common/Widgets/ItemContainer.cpp \ ../../src/common/Widgets/List.cpp \ ../../src/common/Widgets/Panel.cpp \ ../../src/common/Widgets/Popup.cpp \ ../../src/common/Widgets/Progress.cpp \ ../../src/common/Widgets/ProgressDlg.cpp \ ../../src/common/Widgets/RadioGroup.cpp \ ../../src/common/Widgets/ScrollBar.cpp \ ../../src/common/Widgets/Slider.cpp \ ../../src/common/Widgets/Splitter.cpp \ ../../src/common/Widgets/StatusBar.cpp \ ../../src/common/Widgets/TableLayout.cpp \ ../../src/common/Widgets/TabView.cpp \ ../../src/common/Widgets/TextLabel.cpp \ ../../src/common/Widgets/ToolBar.cpp \ ../../src/common/Widgets/Tree.cpp \ ../../src/linux/General/File.cpp \ ../../src/linux/General/Mem.cpp \ ../../src/linux/General/ShowFileProp_Linux.cpp \ ../../src/linux/Gtk/Gdc2.cpp \ ../../src/linux/Gtk/LgiWidget.cpp \ ../../src/linux/Gtk/MemDC.cpp \ ../../src/linux/Gtk/PrintDC.cpp \ ../../src/linux/Gtk/ScreenDC.cpp \ ../../src/linux/Lgi/App.cpp \ ../../src/linux/Lgi/ClipBoard.cpp \ ../../src/linux/Lgi/DragAndDrop.cpp \ ../../src/linux/Lgi/General.cpp \ ../../src/linux/Lgi/Layout.cpp \ ../../src/linux/Lgi/Menu.cpp \ ../../src/linux/Lgi/Printer.cpp \ ../../src/linux/Lgi/Thread.cpp \ ../../src/linux/Lgi/View.cpp \ ../../src/linux/Lgi/Widgets.cpp \ ../../src/linux/Lgi/Window.cpp export Build=$(Build); \ - $(MAKE) -C ../../ -f Makefile.linux + $(MAKE) -C ../../ -f ./linux/Makefile.linux -include $(Objects:.o=.d) # Clean just this target clean : rm -rf $(BuildDir) $(Target) @echo Cleaned $(BuildDir) $(Target) # Clean all targets cleanall : rm -rf $(BuildDir) $(Target) @echo Cleaned $(BuildDir) $(Target) - +make -C "../../" -f "Makefile.linux" clean + +make -C "../../" -f ./linux/Makefile.linux clean VPATH=$(BuildDir) \ ./src \ ../../src/common/Widgets/Editor \ ../../src/common/Text/Homoglyphs \ ../../src/common/Text/Emoji \ ../../src/common/Text \ ../../src/common/Net \ ../../src/common/Lgi \ ../../src/common/Gdc2/Tools \ ../../src/common/Gdc2/Filters diff --git a/test/HtmlViewer/linux/Makefile.linux b/test/HtmlViewer/linux/Makefile.linux --- a/test/HtmlViewer/linux/Makefile.linux +++ b/test/HtmlViewer/linux/Makefile.linux @@ -1,401 +1,402 @@ #!/usr/bin/make # # This makefile generated by LgiIde # http://www.memecode.com/lgi.php # .SILENT : CC = gcc CPP = g++ Target = ./HtmlTestSuite ifndef Build Build = Debug endif MakeDir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BuildDir = $(Build) -Flags = -fPIC -w -fno-inline -fpermissive +CFlags = -MMD -MP -g -fPIC -fno-inline +CppFlags = $(CFlags) -fpermissive -std=c++14 ifeq ($(Build),Debug) - Flags += -MMD -MP -g -std=c++14 + CFlags += -g + CppFlags += -g Tag = d Defs = -D_DEBUG -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = \ -static-libgcc \ `pkg-config --libs gtk+-3.0` \ -llgi-gtk3$(Tag) \ -L../../$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../../../../../../../usr/include/gstreamer-1.0 \ -I../../private/common \ -I../../include/lgi/linux/Gtk \ -I../../include/lgi/linux \ -I../../include \ -I../../../../../codelib/openssl/include else - Flags += -MMD -MP -s -Os -std=c++14 + CFlags += -s -Os + CppFlags += -s -Os Defs = -DLINUX -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DPOSIX Libs = \ -static-libgcc \ `pkg-config --libs gtk+-3.0` \ -llgi-gtk3$(Tag) \ -L../../$(BuildDir) Inc = \ `pkg-config --cflags gtk+-3.0` \ -I../../../../../../../usr/include/gstreamer-1.0 \ -I../../private/common \ -I../../include/lgi/linux/Gtk \ -I../../include/lgi/linux \ -I../../include \ -I../../../../../codelib/openssl/include endif # Dependencies Source = TestSuite.cpp \ ../../src/common/Widgets/ZoomView.cpp \ ../../src/common/Text/HtmlParser.cpp \ ../../src/common/Text/HtmlCommon.cpp \ ../../src/common/Text/Html.cpp \ ../../src/common/Text/Homoglyphs/HomoglyphsTable.cpp \ ../../src/common/Text/Homoglyphs/Homoglyphs.cpp \ ../../src/common/Text/Emoji/EmojiTools.cpp \ ../../src/common/Text/Emoji/EmojiMap.cpp \ ../../src/common/Net/OpenSSLSocket.cpp \ ../../src/common/Net/Http.cpp \ ../../src/common/Lgi/LgiMain.cpp \ ../../src/common/Images/Compare/ImageComparison.cpp \ ../../src/common/Gdc2/Font/EmojiFont.cpp \ ../../src/common/Gdc2/Filters/Png.cpp \ ../../src/common/Coding/ScriptVM.cpp \ ../../src/common/Coding/ScriptLibrary.cpp \ ../../src/common/Coding/ScriptCompiler.cpp \ ../../src/common/Coding/LexCpp.cpp SourceC := $(filter %.c,$(Source)) ObjectsC := $(SourceC:.c=.o) SourceCpp := $(filter %.cpp,$(Source)) ObjectsCpp := $(SourceCpp:.cpp=.o) Objects := $(notdir $(ObjectsC) $(ObjectsCpp)) Objects := $(addprefix $(BuildDir)/,$(Objects)) Deps := $(patsubst %.o,%.d,$(Objects)) $(BuildDir)/%.o: %.c mkdir -p $(@D) echo $(notdir $<) [$(Build)] - $(CC) $(Inc) $(Flags) $(Defs) -c $< -o $@ + $(CC) $(Inc) $(CFlags) $(Defs) -c $< -o $@ $(BuildDir)/%.o: %.cpp mkdir -p $(@D) echo $(notdir $<) [$(Build)] - $(CPP) $(Inc) $(Flags) $(Defs) -c $< -o $@ + $(CPP) $(Inc) $(CppFlags) $(Defs) -c $< -o $@ # Target # Executable target $(Target) : ../../$(BuildDir)/liblgi-gtk3$(Tag).so $(Objects) mkdir -p $(BuildDir) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. ../../$(BuildDir)/liblgi-gtk3$(Tag).so : ../../include/lgi/common/App.h \ ../../include/lgi/common/Array.h \ ../../include/lgi/common/AutoPtr.h \ ../../include/lgi/common/Base64.h \ ../../include/lgi/common/Bitmap.h \ ../../include/lgi/common/Box.h \ ../../include/lgi/common/Button.h \ ../../include/lgi/common/CairoSurface.h \ ../../include/lgi/common/Cancel.h \ ../../include/lgi/common/Capabilities.h \ ../../include/lgi/common/Charset.h \ ../../include/lgi/common/CheckBox.h \ ../../include/lgi/common/ClipBoard.h \ ../../include/lgi/common/Colour.h \ ../../include/lgi/common/ColourSpace.h \ ../../include/lgi/common/Com.h \ ../../include/lgi/common/Combo.h \ ../../include/lgi/common/Containers.h \ ../../include/lgi/common/Core.h \ ../../include/lgi/common/Css.h \ ../../include/lgi/common/CssTools.h \ ../../include/lgi/common/CurrentTime.h \ ../../include/lgi/common/DataDlg.h \ ../../include/lgi/common/DateTime.h \ ../../include/lgi/common/Dialog.h \ ../../include/lgi/common/DisplayString.h \ ../../include/lgi/common/DocView.h \ ../../include/lgi/common/Dom.h \ ../../include/lgi/common/DomFields.h \ ../../include/lgi/common/DragAndDrop.h \ ../../include/lgi/common/DropFiles.h \ ../../include/lgi/common/Edit.h \ ../../include/lgi/common/Emoji.h \ ../../include/lgi/common/Error.h \ ../../include/lgi/common/EventTargetThread.h \ ../../include/lgi/common/File.h \ ../../include/lgi/common/FileSelect.h \ ../../include/lgi/common/Filter.h \ ../../include/lgi/common/FindReplaceDlg.h \ ../../include/lgi/common/Font.h \ ../../include/lgi/common/FontCache.h \ ../../include/lgi/common/FontSelect.h \ ../../include/lgi/common/Gdc2.h \ ../../include/lgi/common/GdcTools.h \ ../../include/lgi/common/GdiLeak.h \ ../../include/lgi/common/HashTable.h \ ../../include/lgi/common/ImageList.h \ ../../include/lgi/common/Input.h \ ../../include/lgi/common/ItemContainer.h \ ../../include/lgi/common/Json.h \ ../../include/lgi/common/Layout.h \ ../../include/lgi/common/Lgi.h \ ../../include/lgi/common/LgiClasses.h \ ../../include/lgi/common/LgiCommon.h \ ../../include/lgi/common/LgiDefs.h \ ../../include/lgi/common/LgiInc.h \ ../../include/lgi/common/LgiInterfaces.h \ ../../include/lgi/common/LgiMsgs.h \ - ../../include/lgi/common/LgiNetInc.h \ ../../include/lgi/common/LgiRes.h \ ../../include/lgi/common/LgiString.h \ ../../include/lgi/common/LgiUiBase.h \ ../../include/lgi/common/Library.h \ ../../include/lgi/common/LibraryUtils.h \ ../../include/lgi/common/List.h \ ../../include/lgi/common/ListItemCheckBox.h \ ../../include/lgi/common/ListItemRadioBtn.h \ ../../include/lgi/common/LMallocArray.h \ ../../include/lgi/common/Mail.h \ ../../include/lgi/common/Matrix.h \ ../../include/lgi/common/Mem.h \ ../../include/lgi/common/Menu.h \ ../../include/lgi/common/Message.h \ ../../include/lgi/common/Mime.h \ ../../include/lgi/common/Mru.h \ ../../include/lgi/common/Mutex.h \ ../../include/lgi/common/Net.h \ - ../../include/lgi/common/NetTools.h \ ../../include/lgi/common/Notifications.h \ ../../include/lgi/common/OAuth2.h \ ../../include/lgi/common/OptionsFile.h \ ../../include/lgi/common/Palette.h \ ../../include/lgi/common/Panel.h \ ../../include/lgi/common/Password.h \ ../../include/lgi/common/Path.h \ ../../include/lgi/common/PixelRops.h \ ../../include/lgi/common/Point.h \ ../../include/lgi/common/Popup.h \ ../../include/lgi/common/PopupList.h \ ../../include/lgi/common/PopupNotification.h \ ../../include/lgi/common/Printer.h \ ../../include/lgi/common/Profile.h \ ../../include/lgi/common/Progress.h \ ../../include/lgi/common/ProgressDlg.h \ ../../include/lgi/common/ProgressView.h \ ../../include/lgi/common/Properties.h \ ../../include/lgi/common/RadioGroup.h \ ../../include/lgi/common/Range.h \ ../../include/lgi/common/Rect.h \ ../../include/lgi/common/RectF.h \ ../../include/lgi/common/RefCount.h \ ../../include/lgi/common/RegKey.h \ ../../include/lgi/common/Res.h \ ../../include/lgi/common/Rops.h \ ../../include/lgi/common/ScrollBar.h \ ../../include/lgi/common/SkinEngine.h \ ../../include/lgi/common/Slider.h \ ../../include/lgi/common/Splitter.h \ ../../include/lgi/common/StatusBar.h \ ../../include/lgi/common/Store3Defs.h \ ../../include/lgi/common/Stream.h \ ../../include/lgi/common/StringClass.h \ ../../include/lgi/common/StringLayout.h \ ../../include/lgi/common/StructuredIo.h \ ../../include/lgi/common/StructuredLog.h \ ../../include/lgi/common/SubProcess.h \ ../../include/lgi/common/TableLayout.h \ ../../include/lgi/common/TabView.h \ ../../include/lgi/common/TextFile.h \ ../../include/lgi/common/TextLabel.h \ ../../include/lgi/common/TextLog.h \ ../../include/lgi/common/TextView3.h \ ../../include/lgi/common/Thread.h \ ../../include/lgi/common/ThreadEvent.h \ ../../include/lgi/common/Token.h \ ../../include/lgi/common/ToolBar.h \ ../../include/lgi/common/ToolTip.h \ ../../include/lgi/common/TrayIcon.h \ ../../include/lgi/common/Tree.h \ ../../include/lgi/common/Undo.h \ ../../include/lgi/common/Unicode.h \ ../../include/lgi/common/UnicodeString.h \ ../../include/lgi/common/UnrolledList.h \ + ../../include/lgi/common/Uri.h \ ../../include/lgi/common/Variant.h \ ../../include/lgi/common/View.h \ ../../include/lgi/common/Widgets.h \ ../../include/lgi/common/Window.h \ ../../include/lgi/common/XmlTree.h \ ../../include/lgi/linux/Gtk/LgiOsClasses.h \ ../../include/lgi/linux/Gtk/LgiOsDefs.h \ ../../include/lgi/linux/Gtk/LgiWidget.h \ ../../include/lgi/linux/Gtk/LgiWinManGlue.h \ ../../include/lgi/linux/SymLookup.h \ ../../include/lgi/mac/cocoa/LCocoaView.h \ ../../include/lgi/mac/cocoa/LgiMac.h \ ../../include/lgi/mac/cocoa/LgiOs.h \ ../../include/lgi/mac/cocoa/LgiOsClasses.h \ ../../include/lgi/mac/cocoa/LgiOsDefs.h \ ../../include/lgi/mac/cocoa/ObjCWrapper.h \ ../../include/lgi/mac/cocoa/SymLookup.h \ ../../private/common/FontPriv.h \ ../../private/common/ViewPriv.h \ ../../private/linux/AppPriv.h \ ../../src/common/Gdc2/15Bit.cpp \ ../../src/common/Gdc2/16Bit.cpp \ ../../src/common/Gdc2/24Bit.cpp \ ../../src/common/Gdc2/32Bit.cpp \ ../../src/common/Gdc2/8Bit.cpp \ ../../src/common/Gdc2/Alpha.cpp \ ../../src/common/Gdc2/Colour.cpp \ ../../src/common/Gdc2/Filters/Filter.cpp \ ../../src/common/Gdc2/Font/Charset.cpp \ ../../src/common/Gdc2/Font/DisplayString.cpp \ ../../src/common/Gdc2/Font/Font.cpp \ ../../src/common/Gdc2/Font/FontSystem.cpp \ ../../src/common/Gdc2/Font/FontType.cpp \ ../../src/common/Gdc2/Font/StringLayout.cpp \ ../../src/common/Gdc2/Font/TypeFace.cpp \ ../../src/common/Gdc2/GdcCommon.cpp \ ../../src/common/Gdc2/Path/Path.cpp \ ../../src/common/Gdc2/Rect.cpp \ ../../src/common/Gdc2/RopsCases.cpp \ ../../src/common/Gdc2/Surface.cpp \ ../../src/common/Gdc2/Tools/ColourReduce.cpp \ ../../src/common/Gdc2/Tools/GdcTools.cpp \ ../../src/common/General/Containers.cpp \ ../../src/common/General/DateTime.cpp \ ../../src/common/General/ExeCheck.cpp \ ../../src/common/General/FileCommon.cpp \ ../../src/common/General/Password.cpp \ ../../src/common/General/Properties.cpp \ ../../src/common/Hash/md5/md5.c \ ../../src/common/Hash/md5/md5.h \ ../../src/common/Hash/sha1/sha1.c \ ../../src/common/Hash/sha1/sha1.h \ ../../src/common/Lgi/Alert.cpp \ ../../src/common/Lgi/AppCommon.cpp \ ../../src/common/Lgi/Css.cpp \ ../../src/common/Lgi/CssTools.cpp \ ../../src/common/Lgi/DataDlg.cpp \ ../../src/common/Lgi/DragAndDropCommon.cpp \ ../../src/common/Lgi/FileSelect.cpp \ ../../src/common/Lgi/FindReplace.cpp \ ../../src/common/Lgi/FontSelect.cpp \ ../../src/common/Lgi/GuiUtils.cpp \ ../../src/common/Lgi/Input.cpp \ ../../src/common/Lgi/LgiCommon.cpp \ ../../src/common/Lgi/Library.cpp \ ../../src/common/Lgi/LMsg.cpp \ ../../src/common/Lgi/MemStream.cpp \ ../../src/common/Lgi/MenuCommon.cpp \ ../../src/common/Lgi/Mru.cpp \ ../../src/common/Lgi/Mutex.cpp \ ../../src/common/Lgi/Object.cpp \ ../../src/common/Lgi/OptionsFile.cpp \ ../../src/common/Lgi/Rand.cpp \ ../../src/common/Lgi/Stream.cpp \ ../../src/common/Lgi/SubProcess.cpp \ ../../src/common/Lgi/ThreadCommon.cpp \ ../../src/common/Lgi/ThreadEvent.cpp \ ../../src/common/Lgi/ToolTip.cpp \ ../../src/common/Lgi/TrayIcon.cpp \ ../../src/common/Lgi/Variant.cpp \ ../../src/common/Lgi/ViewCommon.cpp \ ../../src/common/Lgi/WindowCommon.cpp \ ../../src/common/Net/Base64.cpp \ ../../src/common/Net/MDStringToDigest.cpp \ ../../src/common/Net/Net.cpp \ - ../../src/common/Net/NetTools.cpp \ ../../src/common/Net/Uri.cpp \ ../../src/common/Resource/LgiRes.cpp \ ../../src/common/Resource/Res.cpp \ ../../src/common/Skins/Gel/Gel.cpp \ ../../src/common/Text/DocView.cpp \ ../../src/common/Text/String.cpp \ ../../src/common/Text/TextView3.cpp \ ../../src/common/Text/Token.cpp \ ../../src/common/Text/Unicode.cpp \ ../../src/common/Text/Utf8.cpp \ ../../src/common/Text/XmlTree.cpp \ ../../src/common/Widgets/Bitmap.cpp \ ../../src/common/Widgets/Box.cpp \ ../../src/common/Widgets/Button.cpp \ ../../src/common/Widgets/CheckBox.cpp \ ../../src/common/Widgets/Combo.cpp \ ../../src/common/Widgets/Edit.cpp \ ../../src/common/Widgets/ItemContainer.cpp \ ../../src/common/Widgets/List.cpp \ ../../src/common/Widgets/Panel.cpp \ ../../src/common/Widgets/Popup.cpp \ ../../src/common/Widgets/Progress.cpp \ ../../src/common/Widgets/ProgressDlg.cpp \ ../../src/common/Widgets/RadioGroup.cpp \ ../../src/common/Widgets/ScrollBar.cpp \ ../../src/common/Widgets/Slider.cpp \ ../../src/common/Widgets/Splitter.cpp \ ../../src/common/Widgets/StatusBar.cpp \ ../../src/common/Widgets/TableLayout.cpp \ ../../src/common/Widgets/TabView.cpp \ ../../src/common/Widgets/TextLabel.cpp \ ../../src/common/Widgets/ToolBar.cpp \ ../../src/common/Widgets/Tree.cpp \ ../../src/linux/General/File.cpp \ ../../src/linux/General/Mem.cpp \ ../../src/linux/General/ShowFileProp_Linux.cpp \ ../../src/linux/Gtk/Gdc2.cpp \ ../../src/linux/Gtk/LgiWidget.cpp \ ../../src/linux/Gtk/MemDC.cpp \ ../../src/linux/Gtk/PrintDC.cpp \ ../../src/linux/Gtk/ScreenDC.cpp \ ../../src/linux/Lgi/App.cpp \ ../../src/linux/Lgi/ClipBoard.cpp \ ../../src/linux/Lgi/DragAndDrop.cpp \ ../../src/linux/Lgi/General.cpp \ ../../src/linux/Lgi/Layout.cpp \ ../../src/linux/Lgi/Menu.cpp \ ../../src/linux/Lgi/Printer.cpp \ ../../src/linux/Lgi/Thread.cpp \ ../../src/linux/Lgi/View.cpp \ ../../src/linux/Lgi/Widgets.cpp \ ../../src/linux/Lgi/Window.cpp export Build=$(Build); \ - $(MAKE) -C ../../ -f Makefile.linux + $(MAKE) -C ../../ -f ./linux/Makefile.linux -include $(Objects:.o=.d) # Clean just this target clean : rm -rf $(BuildDir) $(Target) @echo Cleaned $(BuildDir) $(Target) # Clean all targets cleanall : rm -rf $(BuildDir) $(Target) @echo Cleaned $(BuildDir) $(Target) - +make -C "../../" -f "Makefile.linux" clean + +make -C "../../" -f ./linux/Makefile.linux clean VPATH=$(BuildDir) \ ./ \ ../../src/common/Widgets \ ../../src/common/Text/Homoglyphs \ ../../src/common/Text/Emoji \ ../../src/common/Text \ ../../src/common/Net \ ../../src/common/Lgi \ ../../src/common/Images/Compare \ ../../src/common/Gdc2/Font \ ../../src/common/Gdc2/Filters \ ../../src/common/Coding diff --git a/win/Lgi_vs2019.vcxproj b/win/Lgi_vs2019.vcxproj --- a/win/Lgi_vs2019.vcxproj +++ b/win/Lgi_vs2019.vcxproj @@ -1,625 +1,624 @@  Debug x64 ReleaseNoOptimize x64 Release x64 Lgi {95DF9CA4-6D37-4A85-A648-80C2712E0DA1} 10.0 DynamicLibrary v142 false Unicode DynamicLibrary v142 false Unicode DynamicLibrary v142 false Unicode <_ProjectFileVersion>12.0.30501.0 ..\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include Lgi19x64 ..\Lib\ $(Platform)$(Configuration)19\ true $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include Lgi19x64d ..\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include Lgi19x64nop NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/Lgi.tlb MinSpace OnlyExplicitInline ..\include;..\include\lgi\win;..\private\common;..\private\win;..\..\..\..\CodeLib\libiconv\include;..\..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level2 true ProgramDatabase Default true NDEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows true false $(OutDir)$(TargetName).lib MachineX64 _DEBUG;%(PreprocessorDefinitions) true true X64 .\Debug/Lgi.tlb Disabled ..\include;..\include\lgi\win;..\private\common;..\private\win;..\..\..\..\CodeLib\libiconv\include;..\..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;LGI_LIBRARY;_DEBUG;WINDOWS;LGI_RES;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level3 true ProgramDatabase Default false true _DEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) NotSet $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows false $(OutDir)$(TargetName).lib MachineX64 NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/Lgi.tlb MinSpace OnlyExplicitInline ..\include;..\include\lgi\win;..\private\common;..\private\win;..\..\..\..\CodeLib\libiconv\include;..\..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level2 true ProgramDatabase Default false true NDEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows true false $(OutDir)$(TargetName).lib MachineX64 false true true false false false true true true true true true false false false true true true true true true - - + \ No newline at end of file diff --git a/win/Lgi_vs2019.vcxproj.filters b/win/Lgi_vs2019.vcxproj.filters --- a/win/Lgi_vs2019.vcxproj.filters +++ b/win/Lgi_vs2019.vcxproj.filters @@ -1,1229 +1,1223 @@  {afe8cb77-9ad1-4536-bbdd-3c127e7ed08c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {66a64573-871b-4499-ae26-c19e9e2a514a} {3fc23ef0-f144-4f1f-a9b4-18d3392bb63d} {c6cd6d73-d33c-4413-ade1-9dad78e2dc9c} {c8684fc7-2e3c-4f15-8284-9d44b044f6c6} {87b1c801-b9ce-4f6c-97ab-a8f89aee9594} {c06c25f2-2c07-4900-a517-4a6a324069e9} {2c01a737-36cf-4197-bfa1-20395060263f} {1e4cd802-8b94-4328-930e-37bfbfbedff5} {01075698-dde2-4ed0-808f-7dd54414b597} {4a6845a8-e6ec-47d5-8f6c-aa0cfdbc68df} {f567a76b-edd5-434d-b0d9-d51481f34845} {3dee0237-b01c-44e8-a730-08dc661df82f} {bbaaace6-0ac7-4e76-90ae-9e5d5a0cb899} {71e7b970-a070-40a7-a99f-88c215e14e44} {6e115ea1-09fb-492b-82b6-428afe17fed9} {719ef36f-419f-46f9-aef9-2f8158e4d106} {fb221556-3700-4dd8-ba9a-10b5d66bfe54} {8e9f0321-56ae-4485-a338-e87d714c7f50} {c6050f41-574b-4a92-9ce5-860be2719ecf} {a07cd969-801e-4ec9-9394-e34912a3271d} {e067fae0-ef98-4e7a-8434-6f36590ba0e6} {0a3e2151-b13a-407a-8cd9-ccb20f15cacf} {c72248a4-c2f0-43b9-950d-89d09bfddbb3} {dad60202-c763-4f32-9dfb-fe7def4637ee} {532dfa4a-58d3-4133-9ed6-a9fbf7f1556e} {5409aca4-2a55-4b2f-a719-d3db4e3cd7e4} {e3a3aadd-47ef-4723-9bcc-7db1f75a18b0} {c4327acf-78c3-4ef1-b8bc-3aac9ea52b41} {b3c906b8-595e-4641-8eec-8ad03ab13f6f} {baf0a65b-4a9c-4b11-850d-a957c19a22bf} {6e349f5b-36a8-4821-9574-4040a3784204} {ddfdebae-8bcf-4224-8938-2716aba03822} {a126c55a-edee-489f-a852-25cbd1b433b2} {ab5fd4a0-3862-42fd-b4ff-d5d8b0443f53} {048d5e0a-220f-4911-999d-96965eb53691} {258aef64-8cd0-4838-8131-147196656a99} - - {814a5d81-3fd5-461b-a4a3-cda593ea404b} - {5fe450b8-5fa9-440e-9fb0-03860b3548d0} h;hpp;hxx;hm;inl Source Files\Core Source Files\Core Source Files\Dialogs Source Files\General Source Files\Widgets - - Source Files\Widgets - Source Files\Widgets Source Files\Widgets\Native Windows Source Files\Widgets\Native Windows Source Files\Widgets - - Source Files\Graphics - - - Source Files\Widgets - Source Files\Text Source Files\General\Clipboard Source Files\Graphics Source Files\Widgets\Native Windows Source Files\Core\Memory Subsystem Source Files\Widgets\Css Source Files\Widgets\Css Source Files\Core\DateTime Source Files\Widgets\Native Windows Source Files\Graphics\Font Source Files\Core\Drag and Drop Source Files\Core\Drag and Drop Source Files\Core Source Files\Core\Drag and Drop - - Source Files\Widgets - Source Files\Widgets\Native Windows Source Files\General Source Files\Core\File Source Files\Core\File Source Files\Dialogs Source Files\Graphics\Filters Source Files\Dialogs Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics Source Files\Graphics Source Files\Graphics\Gdi Leak Source Files\Core\Skin Source Files\General Source Files\General\Growl Source Files\Graphics Source Files\Dialogs - - Source Files\Core - Source Files\Core Source Files\General Source Files\General Source Files\Core\Resources Source Files\Core\Libraries - - Source Files\Widgets - Source Files\Dialogs Source Files\General\Hash Source Files\General\Hash Source Files\Core\Memory Subsystem Source Files\Graphics\Surfaces Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Core\Memory Subsystem Source Files\Core\Menus Source Files\Core\Menus Source Files\Network - - Source Files\Core - - - Source Files\Core\Threads - Source Files\Network Source Files\General Source Files\General Source Files\Widgets Source Files\General Source Files\Graphics Source Files\Widgets Source Files\Graphics\Surfaces Source Files\Core Source Files\Widgets\Native Windows Source Files\Widgets Source Files\Widgets Source Files\Widgets\Native Windows Source Files\General - - Source Files\Graphics - Source Files\Core\Resources Source Files\Graphics\Surfaces Source Files\Widgets Source Files\General\Hash Source Files\Widgets\Native Windows - - Source Files\Widgets - Source Files\Widgets\Native Windows - - Source Files\Widgets - Source Files\Widgets - - Source Files\Core\Memory Subsystem - Source Files\Text Source Files\Graphics\Font Source Files\Core\Process Source Files\Graphics\Surfaces - - Source Files\Widgets - - - Source Files\Widgets - Source Files\Widgets Source Files\Widgets Source Files\Widgets Source Files\Core\Threads Source Files\Core\Threads Source Files\Core\Threads Source Files\Text Source Files\Widgets Source Files\Widgets Source Files\Widgets - - Source Files\Widgets - Source Files\Graphics\Font Source Files\Text Source Files\Text Source Files\Network Source Files\Text - - Source Files\General - Source Files\Core Source Files\Core Source Files\Core Source Files\Core Source Files\Text + + Source Files\Widgets\Container + + + Source Files\Widgets\Container + + + Source Files\Widgets\Layout + + + Source Files\Widgets\Layout + + + Source Files\Widgets\Layout + + + Source Files\Widgets\Xp + + + Source Files\Widgets\Xp + + + Source Files\Widgets\Xp + + + Source Files\Widgets\Layout + + + Source Files\Widgets\Container + + + Source Files\Core\Mutex + + + Source Files\Core\Stream + + + Source Files\Core\Variant + + + Source Files\General\Mru + + + Source Files\Graphics\Colour Reduction + + + Source Files\Graphics\Rect/Region + - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files Header Files - - Header Files - - - Header Files - Header Files Header Files - - Header Files - - - Header Files - Header Files - - Header Files - Header Files - - Header Files - Header Files Header Files Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files - - Header Files - Header Files - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files Header Files - - Header Files - Header Files Header Files - - Header Files - Header Files - - Header Files - - - Header Files - Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files - - Header Files - Header Files - - Header Files - Header Files Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files - - Header Files - Header Files - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - - - Header Files - Header Files Header Files - - Header Files - Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files - - Header Files - - - Header Files - Header Files Header Files Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files Header Files Header Files Header Files - - Header Files - Header Files - - Header Files - Header Files Header Files - - Header Files - - - Header Files - Header Files Header Files Header Files - - Header Files - - - Header Files - - - Header Files - Header Files Header Files Header Files - - Header Files - Header Files Header Files Header Files - - Header Files - Header Files Header Files - - Header Files - - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files Header Files Header Files - - Header Files - - - Header Files - - - Header Files - Header Files Header Files Header Files - - Header Files - Header Files Header Files - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files Header Files - - Header Files - Header Files Header Files - - Header Files - Header Files - - Header Files - - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files - - Header Files - - - Header Files - Header Files - - Header Files - - - Header Files - - - Header Files - Header Files - - Header Files - - - Header Files - Header Files - - Header Files - Header Files - - Header Files - Header Files Header Files Header Files - - Header Files - Header Files - - Header Files - - - Header Files - Header Files Header Files Header Files Header Files Header Files + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets + + + Source Files\Widgets\Container + + + Source Files\Widgets\Container + + + Source Files\Widgets\Container + + + Source Files\Widgets\Container + + + Source Files\Widgets\Layout + + + Source Files\Widgets\Layout + + + Source Files\Widgets\Layout + + + Source Files\Widgets\Xp + + + Source Files\Widgets\Xp + + + Source Files\Widgets\Xp + + + Source Files\Widgets\Layout + + + Source Files\Text + + + Source Files\Text + + + Source Files\Text + + + Source Files\Text + + + Source Files\Text + + + Source Files\Text + + + Source Files\Text + + + Source Files\Network + + + Source Files\Network + + + Source Files\Network + + + Source Files\Core + + + Source Files\Core + + + Source Files\Widgets\Container + + + Source Files\Core + + + Source Files\Core + + + Source Files\Core + + + Source Files\Core + + + Source Files\Core\DateTime + + + Source Files\Core\Drag and Drop + + + Source Files\Core\Drag and Drop + + + Source Files\Core\File + + + Source Files\Core\Libraries + + + Source Files\Core\Libraries + + + Source Files\Core\Memory Subsystem + + + Source Files\Core\Memory Subsystem + + + Source Files\Core\Menus + + + Source Files\Core\Menus + + + Source Files\Core\Mutex + + + Source Files\Core\Process + + + Source Files\Core\Resources + + + Source Files\Core\Resources + + + Source Files\Core\Skin + + + Source Files\Core\Skin + + + Source Files\Core\Stream + + + Source Files\Core\Threads + + + Source Files\Core\Threads + + + Source Files\Core\Variant + + + Source Files\Core + + + Source Files\Dialogs + + + Source Files\Dialogs + + + Source Files\Dialogs + + + Source Files\General\Clipboard + + + Source Files\General\Growl + - Header Files + Source Files\General\Hash - Header Files + Source Files\General\Hash + + + Source Files\General\Mru + + + Source Files\General + + + Source Files\General + + + Source Files\General + + + Source Files\Graphics\Filters + + + Source Files\Graphics\Font + + + Source Files\Graphics\Font + + + Source Files\Graphics\Font + + + Source Files\Graphics\Font - - Header Files + + Source Files\Graphics\Font + + + Source Files\Graphics\Rect/Region + + + Source Files\Graphics\Rect/Region + + + Source Files\Graphics + + + Source Files\Graphics + + + Source Files\Graphics + + + Source Files\Graphics + + + Source Files\Graphics\Gdi Leak + + + Source Files\Graphics + + + Source Files\Core\Memory Subsystem \ No newline at end of file