diff --git a/include/lgi/common/Array.h b/include/lgi/common/Array.h --- a/include/lgi/common/Array.h +++ b/include/lgi/common/Array.h @@ -1,935 +1,936 @@ /// \file /// \author Matthew Allen /// \brief Growable, type-safe array. #pragma once #include #include #include #include #include #define GARRAY_MIN_SIZE 16 #if defined(LGI_CHECK_MALLOC) && !defined(LGI_MEM_DEBUG) #error "Include GMem.h first" #endif #if defined(PLATFORM_MINGW) #ifdef __cplusplus extern "C" { #endif #ifndef _QSORT_S_DEFINED #define _QSORT_S_DEFINED _CRTIMP void __cdecl qsort_s(void *_Base, size_t _NumOfElements, size_t _SizeOfElements, int (__cdecl *_PtFuncCompare)(void *,const void *,const void *), void *_Context); #endif #ifdef __cplusplus } #endif #endif #include "lgi/common/Range.h" /// \brief Growable type-safe array. /// \ingroup Base /// /// You can store simple objects inline in this array, but all their contents are initialized /// to the octet 0x00. Which limits use to objects that don't have a virtual table and /// don't need construction (constructors are not called). /// /// The objects are copied around during use so you can't assume their pointer /// will remain the same over time either. However when objects are deleted from the /// array their destructors WILL be called. This allows you to have simple objects that /// have dynamically allocated pointers that are freed properly. A good example of this /// type of object is the LVariant or LAutoString class. /// /// If you want to store objects with a virtual table, or that need their constructor /// to be called then you should create the LArray with pointers to the objects instead /// of inline objects. And to clean up the memory you can call LArray::DeleteObjects or /// LArray::DeleteArrays. template class LArray { Type *p = NULL; size_t len = 0; size_t alloc = 0; #ifdef _DEBUG public: size_t GetAlloc() { return alloc; } #endif protected: uint8_t fixed : 1; uint8_t warnResize : 1; public: typedef Type ItemType; /// Constructor LArray(size_t PreAlloc = 0) { alloc = len = PreAlloc; fixed = false; warnResize = true; if (alloc) { size_t Bytes = sizeof(Type) * alloc; p = (Type*) malloc(Bytes); if (p) { memset(p, 0, Bytes); } else { alloc = len = 0; } } } LArray(std::initializer_list il) { fixed = false; warnResize = true; if (Length(il.size())) { size_t n = 0; for (auto i: il) p[n++] = i; } } LArray(const LArray &c) { fixed = false; warnResize = true; *this = c; } /// Destructor ~LArray() { Length(0); } /// This frees the memory without calling the destructor of the elements. /// Useful if you're storing large amounts of plain data types, like char or int. void Free() { if (p) { free(p); p = NULL; } len = alloc = 0; } /// Does a range check on a pointer... /// \returns true if the pointer is pointing to a valid object /// in this array. bool PtrCheck(void *Ptr) { return p != NULL && Ptr >= p && Ptr < &p[len]; } /// Does a range check on an index... bool IdxCheck(ssize_t i) const { return i >= 0 && i < (ssize_t)len; } /// Returns the number of used entries size_t Length() const { return len; } /// Makes the length fixed.. void SetFixedLength(bool fix = true, bool warn = true) { fixed = fix; warnResize = warn; } /// Emtpies the array of all objects. bool Empty() { return Length(0); } /// Sets the length of available entries bool Length(size_t i) { if (i > 0) { if (i > len && fixed) { assert(!"Attempt to enlarged fixed array."); return false; } size_t nalloc = alloc; if (i < len) { // Shrinking } else { // Expanding nalloc = 0x10; while (nalloc < i) nalloc <<= 1; assert(nalloc >= i); } if (nalloc != alloc) { Type *np = (Type*)malloc(sizeof(Type) * nalloc); if (!np) { return false; } memset(np + len, 0, (nalloc - len) * sizeof(Type)); if (p) { // copy across common elements memcpy(np, p, MIN(len, i) * sizeof(Type)); free(p); } p = np; alloc = nalloc; } if (i > len) { // zero new elements memset(p + len, 0, sizeof(Type) * (nalloc - len)); } else if (i < len) { for (size_t n=i; n &a) const { if (Length() != a.Length()) return false; for (size_t i=0; i &a) const { return !(*this == a); } LArray &operator =(const LArray &a) { + fixed = false; Length(a.Length()); fixed = a.fixed; warnResize = a.warnResize; if (p && a.p) { for (size_t i=0; i= 0 && (uint32_t)i < len) return p[i]; static Type t; return t; } // Returns the address of an item or NULL if index is out of range Type *AddressOf(size_t i = 0) { return i < len ? p + i : NULL; } /// \brief Returns a reference a given entry. /// /// If the entry is off the end of the array and "fixed" is false, /// it will grow to make it valid. Type &operator [](size_t i) { static Type t; if ( (fixed && (uint32_t)i >= len) ) { if (warnResize) assert(!"Attempt to enlarged fixed array."); return t; } if (i >= (int)alloc) { // increase array length size_t nalloc = MAX(alloc, GARRAY_MIN_SIZE); while (nalloc <= (uint32_t)i) { nalloc <<= 1; } #if 0 if (nalloc > 1<<30) { #if defined(_DEBUG) && defined(_MSC_VER) LAssert(0); #endif ZeroObj(t); return t; } #endif // alloc new array Type *np = (Type*) malloc(sizeof(Type) * nalloc); if (np) { // clear new cells memset(np + len, 0, (nalloc - len) * sizeof(Type)); if (p) { // copy across old cells memcpy(np, p, len * sizeof(Type)); // clear old array free(p); } // new values p = np; alloc = nalloc; } else { static Type *t = 0; return *t; } } // adjust length of the the array if ((uint32_t)i + 1 > len) { len = i + 1; } return p[i]; } /// Delete all the entries as if they are pointers to objects void DeleteObjects() { if (len > 0) { size_t InitialLen = len; delete p[0]; if (InitialLen == len) { // Non self deleting for (uint i=1; i= 0; } /// Removes last element bool PopLast() { if (len <= 0) return false; return Length(len - 1); } /// Deletes an entry bool DeleteAt ( /// The index of the entry to delete size_t Index, /// true if the order of the array matters, otherwise false. bool Ordered = false ) { if (p && Index < len) { // Delete the object p[Index].~Type(); // Move the memory up if (Index < len - 1) { if (Ordered) { memmove(p + Index, p + Index + 1, (len - Index - 1) * sizeof(Type) ); } else { p[Index] = p[len-1]; } } // Adjust length len--; // Kill the element at the end... otherwise New() returns non-zero data. memset(p + len, 0, sizeof(Type)); return true; } return false; } /// Deletes a range. /// This operation always maintains order. /// \returns the number of removed elements ssize_t DeleteRange(LRange r) { // Truncate range to the size of this array if (r.Start < 0) r.Start = 0; if (r.End() >= (ssize_t)len) r.Len = len - r.Start; // Delete all the elements we are removing for (ssize_t i=r.Start; i 0) memmove(&p[r.Start], &p[r.End()], sizeof(Type)*Remain); len = r.Start + Remain; return r.Len; } /// Deletes the entry 'n' bool Delete ( /// The value of the entry to delete Type n, /// true if the order of the array matters, otherwise false. bool Ordered = false ) { ssize_t i = IndexOf(n); if (p && i >= 0) { return DeleteAt(i, Ordered); } return false; } /// Appends an element void Add ( /// Item to insert const Type &n ) { (*this)[len] = n; } /// Appends multiple elements bool Add ( /// Items to insert const Type *s, /// Length of array ssize_t count ) { if (!s || count < 1) return false; ssize_t i = len; if (!Length(len + count)) return false; Type *d = p + i; while (count--) { *d++ = *s++; } return true; } /// Appends an array of elements bool Add ( /// Array to insert const LArray &a ) { ssize_t old = len; if (Length(len + a.Length())) { for (unsigned i=0; i operator +(const LArray &b) { LArray a = *this; a.Add(b); return a; } LArray &operator +=(const LArray &b) { Add(b); return *this; } /// Inserts an element into the array bool AddAt ( /// Item to insert before size_t Index, /// Item to insert Type n ) { // Make room if (!Length(len + 1)) return false; if (Index < len - 1) // Shift elements after insert point up one memmove(p + Index + 1, p + Index, (len - Index - 1) * sizeof(Type) ); else // Add at the end, not after the end... Index = len - 1; // Insert item memset(p + Index, 0, sizeof(*p)); p[Index] = n; return true; } /// Inserts an array into the array at a position bool AddAt ( /// Item to insert before size_t Index, /// Array to insert const LArray &a ) { // Make room if (!Length(len + a.Length())) return false; if (Index < len - 1) // Shift elements after insert point up one memmove(p + Index + a.Length(), p + Index, (len - Index - a.Length()) * sizeof(Type) ); else // Add at the end, not after the end... Index = len - 1; // Insert items memset(p + Index, 0, a.Length() * sizeof(*p)); for (size_t i=0; i 0) return 1; return r < 0 ? -1 : 0; }); } /// Sorts the array via a callback. void Sort(std::function Compare) { #if !defined(WINDOWS) && !defined(HAIKU) && !defined(LINUX) #define USER_DATA_FIRST 1 #else #define USER_DATA_FIRST 0 #endif #if defined(WINDOWS) /* _ACRTIMP void __cdecl qsort_s(void* _Base, rsize_t _NumOfElements, rsize_t _SizeOfElements, int (__cdecl* _PtFuncCompare)(void*, void const*, void const*), void* _Context); */ qsort_s #else qsort_r #endif ( p, len, sizeof(Type), #if USER_DATA_FIRST &Compare, #endif #if defined(HAIKU) || defined(LINUX) // typedef int (*_compare_function_qsort_r)(const void*, const void*, void*); // extern void qsort_r(void* base, size_t numElements, size_t sizeOfElement, _compare_function_qsort_r, void* cookie); [](const void *a, const void *b, void *ud) -> int #else [](void *ud, const void *a, const void *b) -> int #endif { return (*( (std::function*)ud ))((Type*)a, (Type*)b); } #if !USER_DATA_FIRST , &Compare #endif ); } /// \returns a reference to a new object on the end of the array /// /// Never assign this to an existing variable. e.g: /// LArray a; /// MyObject &o = a.New(); /// o.Type = something; /// o = a.New(); /// o.Type = something else; /// /// This causes the first object to be overwritten with a blank copy. Type &New() { return (*this)[len]; } /// Returns the memory held by the array and sets itself to empty Type *Release() { Type *Ptr = p; p = 0; len = alloc = 0; return Ptr; } void Swap(LArray &other) { LSwap(p, other.p); LSwap(len, other.len); LSwap(alloc, other.alloc); } /// Swaps a range of elements between this array and 'b' bool SwapRange ( // The range of 'this' to swap out LRange aRange, // The other array to swap with LArray &b, // The range of 'b' to swap with this array LRange bRange ) { LArray Tmp; // Store entries in this that will be swapped Tmp.Add(AddressOf(aRange.Start), aRange.Len); // Copy b's range into this ssize_t Common = MIN(bRange.Len, aRange.Len); ssize_t aIdx = aRange.Start; ssize_t bIdx = bRange.Start; for (int i=0; i bRange.Len) { // Shrink the range in this to fit 'b' ssize_t Del = aRange.Len - bRange.Len; for (ssize_t i=0; i bRange.Len) { // Grow range to fit this ssize_t Add = aRange.Len - bRange.Len; for (ssize_t i=0; i class Iter { friend class ::LArray; ssize_t i; char each_dir; LArray *a; public: Iter(LArray *arr) // 'End' constructor { i = -1; a = arr; each_dir = 0; } Iter(LArray *arr, size_t pos) { i = pos; a = arr; each_dir = 0; } bool operator ==(const Iter &it) const { int x = (int)In() + (int)it.In(); if (x == 2) return (a == it.a) && (i == it.i); return x == 0; } bool operator !=(const Iter &it) const { return !(*this == it); } operator bool() const { return In(); } bool In() const { return i >= 0 && i < (ssize_t)a->Length(); } bool End() const { return i < 0 || i >= a->Length(); } T &operator *() { return (*a)[i]; } Iter &operator ++() { i++; return *this; } Iter &operator --() { i--; return *this; } Iter &operator ++(int) { i++; return *this; } Iter &operator --(int) { i--; return *this; } }; typedef Iter I; I begin(size_t start = 0) { return I(this, start); } I rbegin() { return I(this, len-1); } I end() { return I(this); } bool Delete(I &It) { return DeleteAt(It.i, true); } LArray Slice(ssize_t Start, ssize_t End = -1) { LArray a; // Range limit the inputs... if (Start < 0) Start = len + Start; if (Start > (ssize_t)len) Start = len; if (End < 0) End = len + End + 1; if (End > (ssize_t)len) End = len; if (End > Start) { a.Length(End - Start); for (size_t i=0; i Reverse() { LArray r; r.Length(len); for (size_t i=0, k=len-1; i Compare) { static Type Empty; if (!Compare) return Empty; for (size_t s = 0, e = len - 1; s <= e; ) { if (e - s < 2) { if (Compare(p[s]) == 0) return p[s]; if (e > s && Compare(p[e]) == 0) return p[e]; break; // Not found } size_t mid = s + ((e - s) >> 1); int result = Compare(p[mid]); if (result == 0) return p[mid]; if (result < 0) s = mid + 1; // search after the mid point else e = mid - 1; // search before } return Empty; } }; diff --git a/include/lgi/common/Browser.h b/include/lgi/common/Browser.h --- a/include/lgi/common/Browser.h +++ b/include/lgi/common/Browser.h @@ -1,37 +1,40 @@ /** \file \author Matthew Allen \brief Simple single window wrapper around the LHtml2 control */ #ifndef _GBROWSER_H_ #define _GBROWSER_H_ class LBrowser : public LWindow { class LBrowserPriv *d; bool OnViewKey(LView *v, LKey &k); public: class LBrowserEvents { public: virtual ~LBrowserEvents() {} virtual bool OnSearch(LBrowser *br, const char *txt) { return false; } }; LBrowser(LViewI *owner, const char *Title, char *Uri = 0); ~LBrowser(); void SetEvents(LBrowserEvents *Events); - bool SetUri(const char *Uri = 0); - bool SetHtml(char *Html); + bool SetUri(const char *Uri = NULL); + bool SetHtml(const char *Html); + + /// Adds optional local file system paths to search for resources. + void AddPath(const char *Path); void OnPosChange(); int OnNotify(LViewI *c, LNotification n); LMessage::Result OnEvent(LMessage *m); }; #endif \ No newline at end of file diff --git a/include/lgi/common/StringClass.h b/include/lgi/common/StringClass.h --- a/include/lgi/common/StringClass.h +++ b/include/lgi/common/StringClass.h @@ -1,1254 +1,1255 @@ /* * A mis-guided attempt to make a string class look and feel like a python string. * * Author: Matthew Allen * Email: fret@memecode.com * Created: 16 Sept 2014 */ #pragma once #include #include #include #if defined(_MSC_VER) || defined(__GTK_H__) // This fixes compile errors in VS2008/Gtk #undef _SIGN_DEFINED #undef abs #endif #include #if defined(_MSC_VER) && _MSC_VER < 1800/*_MSC_VER_VS2013*/ #include #define PRId64 "I64i" #else #define __STDC_FORMAT_MACROS 1 #include #include #ifndef PRId64 #warning "PRId64 not defined." #define PRId64 "Ld" #endif #endif #include "LgiOsDefs.h" #include "lgi/common/Unicode.h" #include "lgi/common/Array.h" #ifndef IsDigit #define IsDigit(ch) ((ch) >= '0' && (ch) <= '9') #endif LgiExtern int LPrintf(class LString &Str, const char *Format, va_list &Arg); /// A pythonic string class. class LString { protected: /// This structure holds the string's data itself and is shared /// between one or more LString instances. struct RefStr { /// A reference count int32 Refs; /// The bytes in 'Str' not including the NULL terminator size_t Len; /// The first byte of the string. Further bytes are allocated /// off the end of the structure using malloc. This must always /// be the last element in the struct. char Str[1]; } *Str; inline void _strip(LString &ret, const char *set, bool left, bool right) { if (!Str) return; char *s = Str->Str; char *e = s + Str->Len; if (!set) set = " \t\r\n"; if (left) { while (s < e && strchr(set, *s)) s++; } if (right) { while (e > s && strchr(set, e[-1])) e--; } if (e > s) ret.Set(s, e - s); } public: #ifdef LGI_UNIT_TESTS static int32 RefStrCount; #endif /// A copyable array of strings class Array : public LArray { public: Array(size_t PreAlloc = 0) : LArray(PreAlloc) { // This allows the parent array to return an empty // string without asserting if the caller requests an // out of range index warnResize = false; } Array(const Array &a) { *this = (Array&)a; } Array &operator =(const Array &a) { *((LArray*)this) = a; - SetFixedLength(true, false); + fixed = a.fixed; + warnResize = a.warnResize; return *this; } Array &operator +=(const Array &a) { SetFixedLength(false); Add(a); SetFixedLength(true); return *this; } Array &operator +=(const LArray &a) { SetFixedLength(false); Add(a); SetFixedLength(true); return *this; } }; /// Empty constructor LString() { Str = NULL; } // This odd looking constructor allows the object to be used as the value type // in a GHashTable, where the initialiser is '0', an integer. LString(int i) { Str = NULL; } #ifndef _MSC_VER // This odd looking constructor allows the object to be used as the value type // in a GHashTable, where the initialiser is '0', an integer. LString(long int i) { Str = NULL; } #endif /// String constructor LString(const char *str, ptrdiff_t bytes) { Str = NULL; Set(str, bytes); } /// const char* constructor LString(const char *str) { Str = NULL; Set(str); } /// const char16* constructor LString(const wchar_t *str, ptrdiff_t wchars = -1) { Str = NULL; SetW(str, wchars); } #if defined(_WIN32) || defined(MAC) /// const uint32* constructor LString(const uint32_t *str, ptrdiff_t chars = -1) { Str = NULL; if (chars < 0) chars = Strlen(str); ptrdiff_t utf_len = 0; const uint32_t *end = str + chars; const uint32_t *c = str; while (c < end) { uint8_t utf[6], *u = utf; ssize_t len = sizeof(utf); if (!LgiUtf32To8(*c++, u, len)) break; utf_len += u - utf; } if (Length((uint32_t)utf_len)) { c = str; uint8_t *u = (uint8_t*)Str->Str; ssize_t len = Str->Len; while (c < end) { if (!LgiUtf32To8(*c++, u, len)) break; } *u++ = 0; } } #endif /// LString constructor LString(const LString &s) { Str = s.Str; if (Str) Str->Refs++; } ~LString() { Empty(); } /// Removes a reference to the string and deletes if needed void Empty() { if (!Str) return; Str->Refs--; if (Str->Refs < 0) { assert(!"Invalid refs"); } if (Str->Refs == 0) { free(Str); #ifdef LGI_UNIT_TESTS RefStrCount--; #endif } Str = NULL; } /// Returns the pointer to the string data char *Get() const { return Str ? Str->Str : NULL; } /// Sets the string to a new value bool Set ( /// Can be a pointer to string data or NULL to create an empty buffer (requires valid length) const char *str, /// Byte length of input string or -1 to copy till the NULL terminator. ptrdiff_t bytes = -1 ) { Empty(); if (bytes < 0) { if (str) bytes = strlen(str); else return false; } Str = (RefStr*)malloc(sizeof(RefStr) + bytes); if (!Str) return false; Str->Refs = 1; Str->Len = (uint32_t)bytes; #ifdef LGI_UNIT_TESTS RefStrCount++; #endif if (str) memcpy(Str->Str, str, bytes); Str->Str[bytes] = 0; return true; } /// Sets the string to a new value bool SetW ( /// Can be a pointer to string data or NULL to create an empty buffer (requires valid length) const wchar_t *str, /// Number of 'char16' values in the input string or -1 to copy till the NULL terminator. ptrdiff_t wchars = -1 ) { size_t Sz = WideToUtf8Len(str, wchars); if (Length(Sz)) { #ifdef _MSC_VER const uint16 *i = (const uint16*) str; ssize_t InLen = wchars >= 0 ? wchars << 1 : 0x7fffffff; assert(sizeof(*i) == sizeof(*str)); uint8_t *o = (uint8_t*)Str->Str; ssize_t OutLen = Str->Len; for (uint32_t ch; ch = LgiUtf16To32(i, InLen); ) { if (!LgiUtf32To8(ch, o, OutLen)) { *o = 0; break; } } #else uint8_t *o = (uint8_t*)Str->Str; ssize_t OutLen = Str->Len; if (wchars >= 0) { const wchar_t *end = str + wchars; for (const wchar_t *ch = str; ch < end; ch++) { if (!LgiUtf32To8(*ch, o, OutLen)) { *o = 0; break; } } } else { for (const wchar_t *ch = str; *ch; ch++) { if (!LgiUtf32To8(*ch, o, OutLen)) { *o = 0; break; } } } #endif *o = 0; } return true; } /// Equality operator (case sensitive) bool operator ==(const LString &s) { const char *a = Get(); const char *b = s.Get(); if (!a && !b) return true; if (!a || !b) return false; return !strcmp(a, b); } bool operator !=(const LString &s) { return !(*this == s); } // Equality function (default: case insensitive, as the operator== is case sensitive) bool Equals(const char *b, bool CaseInsensitive = true) const { const char *a = Get(); if (!a && !b) return true; if (!a || !b) return false; return !(CaseInsensitive ? _stricmp(a, b) : strcmp(a, b)); } // Equality function (default: case insensitive, as the operator== is case sensitive) bool Equals(const LString &s, bool CaseInsensitive = true) const { const char *a = Get(); const char *b = s.Get(); if (!a && !b) return true; if (!a || !b) return false; return !(CaseInsensitive ? _stricmp(a, b) : strcmp(a, b)); } /// Assignment operator to copy one string to another LString &operator =(const LString &s) { if (this != &s) { Empty(); Str = s.Str; if (Str) Str->Refs++; } return *this; } /// Equality with a C string (case sensitive) bool operator ==(const char *b) { const char *a = Get(); if (!a && !b) return true; if (!a || !b) return false; return !strcmp(a, b); } bool operator !=(const char *b) { return !(*this == b); } /// Assignment operators LString &operator =(const char *s) { if (Str == NULL || s < Str->Str || s > Str->Str + Str->Len) { Empty(); Set(s); } else if (s != Str->Str) { // Special case for setting it to part of itself // If you try and set a string to the start, it's a NOP ptrdiff_t Off = s - Str->Str; memmove(Str->Str, s, Str->Len - Off + 1); Str->Len -= (uint32_t)Off; } return *this; } LString &operator =(const wchar_t *s) { SetW(s); return *this; } LString &operator =(int val) { char n[32]; sprintf_s(n, sizeof(n), "%i", val); Set(n); return *this; } LString &operator =(int64 val) { char n[32]; sprintf_s(n, sizeof(n), "%" PRId64, (int64_t)val); Set(n); return *this; } /// Cast to C string operator operator char *() const { return Str && Str->Len > 0 ? Str->Str : NULL; } int operator -(const LString &s) const { return Stricmp(Get(), s.Get()); } /// Concatenation operator LString operator +(const LString &s) { LString Ret; size_t Len = Length() + s.Length(); if (Ret.Set(NULL, Len)) { char *p = Ret.Get(); if (p) { if (Str) { memcpy(p, Str->Str, Str->Len); p += Str->Len; } if (s.Str) { memcpy(p, s.Str->Str, s.Str->Len); p += s.Str->Len; } *p++ = 0; } } return Ret; } /// Concatenation / assignment operator LString &operator +=(const LString &s) { ssize_t Len = Length() + s.Length(); ssize_t Alloc = sizeof(RefStr) + Len; RefStr *rs = (RefStr*)malloc(Alloc); if (rs) { rs->Refs = 1; rs->Len = Len; #ifdef LGI_UNIT_TESTS RefStrCount++; #endif char *p = rs->Str; if (Str) { memcpy(p, Str->Str, Str->Len); p += Str->Len; } if (s.Str) { memcpy(p, s.Str->Str, s.Str->Len); p += s.Str->Len; } *p++ = 0; assert(p - (char*)rs <= Alloc); Empty(); Str = rs; } return *this; } LString operator *(ssize_t mul) { LString s; if (Str) { s.Length(Str->Len * mul); char *out = s.Get(); for (ssize_t i=0; iLen) memcpy(out, Str->Str, Str->Len); *out = 0; } return s; } /// Gets the length in bytes size_t Length() const { return Str ? Str->Len : 0; } size_t Length(ssize_t NewLen) { if (NewLen < 0) { LAssert(!"No negative string len."); Empty(); } else if (Str) { if (NewLen <= (ssize_t)Str->Len) { Str->Len = NewLen; Str->Str[NewLen] = 0; } else { RefStr *n = (RefStr*)malloc(sizeof(RefStr) + NewLen); if (n) { n->Len = NewLen; n->Refs = 1; memcpy(n->Str, Str->Str, Str->Len); n->Str[Str->Len] = 0; // NULL terminate... Empty(); // Deref the old string... Str = n; } else return 0; } } else { Str = (RefStr*)malloc(sizeof(RefStr) + NewLen); if (Str) { Str->Len = NewLen; Str->Refs = 1; Str->Str[0] = 0; // NULL terminate... } else return 0; } return Str ? Str->Len : 0; } /// Splits the string into parts using a separator Array Split(const char *Sep, int Count = -1, bool CaseSen = false) { Array a; if (Str && Sep) { const char *s = Str->Str, *Prev = s; const char *end = s + Str->Len; size_t SepLen = strlen(Sep); if (s[Str->Len] == 0) { while ((s = CaseSen ? strstr(s, Sep) : Stristr(s, Sep))) { if (s > Prev) a.New().Set(Prev, s - Prev); s += SepLen; Prev = s; if (Count > 0 && a.Length() >= (uint32_t)Count) break; } if (Prev < end) a.New().Set(Prev, end - Prev); a.SetFixedLength(); } else assert(!"String not NULL terminated."); } return a; } /// Splits the string into parts using a separator Array RSplit(const char *Sep, int Count = -1, bool CaseSen = false) { Array a; if (Str && Sep) { const char *s = Get(); size_t SepLen = strlen(Sep); LArray seps; while ((s = CaseSen ? strstr(s, Sep) : Stristr(s, Sep))) { seps.Add(s); s += SepLen; } ssize_t i, Last = seps.Length() - 1; LString p; for (i=Last; i>=0; i--) { const char *part = seps[i] + SepLen; if (i == Last) p.Set(part); else p.Set(part, seps[i+1]-part); a.AddAt(0, p); if (Count > 0 && a.Length() >= (uint32_t)Count) break; } const char *End = seps[i > 0 ? i : 0]; p.Set(Get(), End - Get()); a.AddAt(0, p); } a.SetFixedLength(true, false); return a; } /// Splits the string into parts using delimiter chars Array SplitDelimit(const char *Delimiters = NULL, int Count = -1, bool GroupDelimiters = true) const { Array a; if (Str) { const char *delim = Delimiters ? Delimiters : " \t\r\n"; const char *s = Get(), *end = s + Length(); while (s < end) { // Skip over non-delimiters const char *e = s; while (e < end && !strchr(delim, *e)) e++; if (e > s || !GroupDelimiters) a.New().Set(s, e - s); s = e; if (*s) s++; if (GroupDelimiters) { // Skip any delimiters while (s < end && strchr(delim, *s)) s++; } // Create the string if (Count > 0 && a.Length() >= (uint32_t)Count) break; } if ( s < end || ( !GroupDelimiters && s > Get() && strchr(delim, s[-1]) ) ) a.New().Set(s); } a.SetFixedLength(true, false); return a; } /// Joins an array of strings using a separator LString Join(const LArray &a) { LString ret; if (a.Length() == 0) return ret; char *Sep = Get(); size_t SepLen = Sep ? strlen(Sep) : 0; size_t Bytes = SepLen * (a.Length() - 1); LArray ALen; for (unsigned i=0; iStr; LArray Matches; while ((Match = (CaseSen ? strstr(Match, Old) : Stristr(Match, Old)))) { Matches.Add(Match); if (Count >= 0 && (int)Matches.Length() >= Count) break; Match += OldLen; } size_t NewSize = Str->Len + (Matches.Length() * (NewLen - OldLen)); s.Length((uint32_t)NewSize); char *Out = s.Get(); char *In = Str->Str; // For each match... for (unsigned i=0; iStr + Str->Len; if (In < End) { ptrdiff_t Bytes = End - In; memcpy(Out, In, Bytes); Out += Bytes; } assert(Out - s.Get() == NewSize); // Check we got the size right... *Out = 0; // Null terminate } else { s = *this; } return s; } /// Convert string to double double Float() { return Str ? atof(Str->Str) : NAN; } /// Convert to integer int64 Int(int Base = 10) { if (!Str) return -1; if ( Str->Len > 2 && Str->Str[0] == '0' && ( Str->Str[1] == 'x' || Str->Str[1] == 'X' ) ) { return Atoi(Str->Str+2, 16); } return Atoi(Str->Str, Base); } /// Checks if the string is a number bool IsNumeric() { if (!Str) return false; for (char *s = Str->Str; *s; s++) { if (!IsDigit(*s) && !strchr("e-+.", *s)) return false; } return true; } /// Check for non whitespace bool IsEmpty() { if (!Str) return true; for (char *s = Str->Str; *s; s++) { if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') return false; } return true; } /// Reverses all the characters in the string LString Reverse() { LString s; if (Length() > 0) { s = Str->Str; for (auto *a = s.Get(), *b = s.Get() + s.Length() - 1; a < b; a++, b--) { char t = *a; *a = *b; *b = t; } } return s; } /// Find a sub-string ssize_t Find(const char *needle, ssize_t start = 0, ssize_t end = -1) { if (!needle) return -1; char *c = Get(); if (!c) return -1; char *pos = c + start; while (c < pos) { if (!*c) return -1; c++; } char *found = (end > 0) ? Strnstr(c, needle, end - start) : strstr(c, needle); return (found) ? found - Get() : -1; } /// Reverse find a string (starting from the end) ssize_t RFind(const char *needle, int start = 0, ssize_t end = -1) { if (!needle) return -1; char *c = Get(); if (!c) return -1; char *pos = c + start; while (c < pos) { if (!*c) return -1; c++; } char *found, *prev = NULL; size_t str_len = strlen(needle); while (( found = ( (end > 0) ? Strnstr(c, needle, end - start) : strstr(c, needle) ) )) { prev = found; c = found + str_len; } return (prev) ? prev - Get() : -1; } /// Returns a copy of the string with all the characters converted to lower case LString Lower() { LString s; if (Str && s.Set(Str->Str, Str->Len)) Strlwr(s.Get()); return s; } /// Returns a copy of the string with all the characters converted to upper case LString Upper() { LString s; if (Str && s.Set(Str->Str, Str->Len)) Strupr(s.Get()); return s; } void Swap(LString &s) { LSwap(Str, s.Str); } /// Gets the character at 'index' int operator() (ssize_t index) const { if (!Str) return 0; char *c = Str->Str; if (index < 0) { size_t idx = Str->Len + index; return c[idx]; } else if (index < (int)Str->Len) { return c[index]; } return 0; } /// Gets the string between at 'start' and 'end' (not including the end'th character) LString operator() (ptrdiff_t start, ptrdiff_t end) { LString s; if (Str) { ptrdiff_t start_idx = start < 0 ? Str->Len + start + 1 : start; if (start_idx >= 0 && (uint32_t)start_idx < Str->Len) { ptrdiff_t end_idx = end < 0 ? Str->Len + end + 1 : end; if (end_idx >= start_idx && (uint32_t)end_idx <= Str->Len) s.Set(Str->Str + start_idx, end_idx - start_idx); } } return s; } /// Strip off any leading and trailing characters from 'set' (or whitespace if NULL) LString Strip(const char *set = NULL) { LString ret; _strip(ret, set, true, true); return ret; } /// Strip off any leading characters from 'set' (or whitespace if NULL) LString LStrip(const char *set = NULL) { LString ret; _strip(ret, set, true, false); return ret; } /// Strip off any trailing characters from 'set' (or whitespace if NULL) LString RStrip(const char *set = NULL) { LString ret; _strip(ret, set, false, true); return ret; } /// Prints a formatted string to this object int Printf(const char *Fmt, ...) { Empty(); va_list Arg; va_start(Arg, Fmt); int Bytes = Printf(Arg, Fmt); va_end(Arg); return Bytes; } /// Prints a varargs string int Printf(va_list &Arg, const char *Fmt) { Empty(); return LPrintf(*this, Fmt, Arg); } static LString Escape(const char *In, ssize_t Len = -1, const char *Chars = "\r\n\b\\\'\"") { LString s; if (In && Chars) { char Buf[256]; int Ch = 0; if (Len < 0) Len = strlen(In); while (Len-- > 0) { if (Ch > sizeof(Buf)-4) { // Buffer full, add substring to 's' Buf[Ch] = 0; s += Buf; Ch = 0; } if (strchr(Chars, *In)) { Buf[Ch++] = '\\'; switch (*In) { #undef EscChar #define EscChar(from, to) \ case from: Buf[Ch++] = to; break EscChar('\n', 'n'); EscChar('\r', 'r'); EscChar('\\', '\\'); EscChar('\b', 'b'); EscChar('\a', 'a'); EscChar('\t', 't'); EscChar('\v', 'v'); EscChar('\'', '\''); EscChar('\"', '\"'); EscChar('&', '&'); EscChar('?', '?'); #undef EscChar default: Ch += sprintf_s(Buf+Ch, sizeof(Buf)-Ch, "x%02x", *In); break; } } else Buf[Ch++] = *In; In++; } if (Ch > 0) { Buf[Ch] = 0; s += Buf; } } return s; } template static LString UnEscape(const T *In, ssize_t Len = -1) { if (!In) return LString(); LString s; if (Len < 0) // As memory allocation/copying around data is far slower then // just scanning the string for size... don't try and chunk the // processing. Len = Strlen(In); if (!s.Length(Len)) return LString(); auto *Out = s.Get(); auto *End = In + Len; while (In < End) { if (*In == '\\') { In++; switch (*In) { case 'n': case 'N': *Out++ = '\n'; break; case 'r': case 'R': *Out++ = '\r'; break; case 'b': case 'B': *Out++ = '\b'; break; case 't': case 'T': *Out++ = '\t'; break; default: *Out++ = *In; break; case 0: break; } if (*In) In++; else break; } else *Out++ = *In++; } // Trim excess size off string s.Length(Out - s.Get()); return s; } static LString UnEscape(LString s) { return UnEscape(s.Get(), s.Length()); } #if defined(__GTK_H__) #elif defined(MAC) // && __COREFOUNDATION_CFBASE__ LString(const CFStringRef r) { Str = NULL; *this = r; } LString &operator =(CFStringRef r) { if (r) { CFIndex length = CFStringGetLength(r); CFRange range = CFRangeMake(0, length); CFIndex usedBufLen = 0; CFIndex slen = CFStringGetBytes(r, range, kCFStringEncodingUTF8, '?', false, NULL, 0, &usedBufLen); if (Set(NULL, usedBufLen)) { slen = CFStringGetBytes( r, range, kCFStringEncodingUTF8, '?', false, (UInt8*)Str->Str, Str->Len, &usedBufLen); Str->Str[usedBufLen] = 0; // NULL terminate } } return *this; } CFStringRef CreateStringRef() { char *s = Get(); if (!s) return NULL; return CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingUTF8); } #ifdef __OBJC__ NSString *NsStr() { if (Str) return [[NSString alloc] initWithBytes:Str->Str length:Str->Len encoding:NSUTF8StringEncoding]; return nil; } bool operator=(NSString *const s) { *this = [s UTF8String]; return !IsEmpty(); } LString(NSString *const s) { Str = NULL; *this = [s UTF8String]; } #endif #endif }; diff --git a/src/common/Lgi/Browser.cpp b/src/common/Lgi/Browser.cpp --- a/src/common/Lgi/Browser.cpp +++ b/src/common/Lgi/Browser.cpp @@ -1,629 +1,635 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Browser.h" #include "lgi/common/Html.h" #include "lgi/common/Net.h" #include "lgi/common/Http.h" #include "lgi/common/Edit.h" #include "lgi/common/Button.h" #define M_LOADED (M_USER+3000) #define M_BUSY (M_USER+3001) enum { IDC_HTML = 100, IDC_URI, IDC_BACK, IDC_FORWARD, IDC_REFRESH_STOP, IDC_SEARCH_TXT, IDC_SEARCH, }; class LBrowserPriv; class LBrowserThread : public LThread, public LMutex { LBrowserPriv *d; LArray Work; bool Loop; public: LBrowserThread(LBrowserPriv *priv); ~LBrowserThread(); bool Add(char *Uri); void Stop(); int Main(); }; class LBrowserPriv : public LDocumentEnv { public: typedef LHashTbl,LStream*> Collection; struct FileLock : public LMutex::Auto { Collection *Files; FileLock(LMutex *s, const char *file, int line) : LMutex::Auto(s, file, line) { Files = 0; } }; typedef LAutoPtr FilePtr; private: Collection Files; // requires locking to access public: LBrowser *Wnd = NULL; Html1::LHtml *Html = NULL; LAutoPtr Thread; LEdit *UriEdit = NULL; LEdit *SearchEdit = NULL; LButton *Back = NULL; LButton *Forward = NULL; LButton *Stop = NULL; LButton *Search = NULL; LArray History; ssize_t CurHistory = 0; bool Loading = false; LBrowser::LBrowserEvents *Events = NULL; LHttp Http; + LString::Array ResourcePaths; LBrowserPriv(LBrowser *wnd) { Wnd = wnd; + ResourcePaths.SetFixedLength(false); } FilePtr Lock() { FilePtr p(new FileLock(this, _FL)); p->Files = &Files; return p; } void LoadStream(LStream *s) { int len = (int) s->GetSize(); LArray txt; txt.Length(len); s->Read(&txt[0], len); txt.Add(0); Html->Name(&txt[0]); } bool LoadCurrent() { char *Uri = History[CurHistory]; if (!Uri) return false; LUri u(Uri); #ifdef WIN32 if (u.sPath) u.sPath = u.sPath.Replace("/", DIR_STR); #endif bool IsFile = LFileExists(u.sPath); bool IsHttp = false; if (IsFile) u.sProtocol = "file"; else if (u.Set(Uri)) { if (u.sProtocol) { if (!_stricmp(u.sProtocol, "file")) { if (!u.sPath) return false; IsFile = true; } else if (!_stricmp(u.sProtocol, "http")) { IsHttp = true; } else return false; // Protocol not support } else { // Probably...? IsHttp = true; u.sProtocol = "http"; History[CurHistory] = u.ToString(); Uri = History[CurHistory]; } } if (IsFile) { LAutoString txt(LReadTextFile(u.sPath)); if (txt) { Html->Name(txt); } else return false; } else if (IsHttp) { if (!Thread) Thread.Reset(new LBrowserThread(this)); Thread->Add(Uri); } if (u.sAnchor) { Html->GotoAnchor(u.sAnchor); } LString a = u.ToString(); UriEdit->Name(a); Wnd->SetCtrlEnabled(IDC_BACK, CurHistory > 0); Wnd->SetCtrlEnabled(IDC_FORWARD, CurHistory < (ssize_t)History.Length() - 1); return true; } bool OnNavigate(LDocView *Parent, const char *Uri) { if (!Uri) return false; LUri u(Uri); char Sep, Buf[MAX_PATH_LEN]; if (!u.sProtocol) { // Relative link? char *Cur = History[CurHistory]; if (strchr(Cur, '\\')) Sep = '\\'; else Sep = '/'; // Trim off filename... char *Last = strrchr(Cur, Sep); auto t = LString(Uri).SplitDelimit("\\/"); if (*Uri != '#' && Last) sprintf_s(Buf, sizeof(Buf), "%.*s", (int) (Last - Cur), Cur); else strcpy_s(Buf, sizeof(Buf), Cur); char *End = Buf + strlen(Buf) - 1; if (*End == Sep) *End = 0; for (unsigned i=0; iSetUri(Uri); } LoadType GetContent(LoadJob *&j) { if (!j || !j->Uri) return LoadError; auto Uri = History[CurHistory]; LUri BaseUri(Uri); LUri u(j->Uri); - char *LoadFileName = 0; + const char *LoadFileName = NULL; if (u.sProtocol) { } else LoadFileName = j->Uri; if (LoadFileName) { - char p[MAX_PATH_LEN]; - LMakePath(p, sizeof(p), !BaseUri.sPath.IsEmpty() ? BaseUri.sPath : Uri, ".."); - LMakePath(p, sizeof(p), p, LoadFileName); - if (LFileExists(p)) + auto SearchPaths = ResourcePaths; + if (!BaseUri.sPath.IsEmpty()) { - char *Ext = LGetExtension(p); - if - ( - Ext && - ( - !_stricmp(Ext, "jpg") || - !_stricmp(Ext, "jpeg") || - !_stricmp(Ext, "gif") || - !_stricmp(Ext, "png") || - !_stricmp(Ext, "tif") || - !_stricmp(Ext, "tiff") || - !_stricmp(Ext, "bmp") || - !_stricmp(Ext, "ico") - ) - ) + LFile::Path p(BaseUri.sPath); + p--; + SearchPaths.AddAt(0, p.GetFull()); + } + + LFile::Path p; + for (auto Path: SearchPaths) + { + p = Path; + p += LoadFileName; + if (p.Exists()) { - j->pDC.Reset(GdcD->Load(p)); - return LoadImmediate; - } - else - { - LFile *f; - if (j->Stream.Reset(f = new LFile)) + auto mt = LGetFileMimeType(p).SplitDelimit("/"); + if (mt[0].Equals("image")) { - if (!f->Open(p, O_READ)) - { - j->Stream.Reset(); - j->Error.Printf("Can't open file '%s' for reading", p); - return LoadError; - } - + j->pDC.Reset(GdcD->Load(p)); return LoadImmediate; } + else + { + LFile *f; + if (j->Stream.Reset(f = new LFile)) + { + if (!f->Open(p, O_READ)) + { + j->Stream.Reset(); + j->Error.Printf("Can't open file '%s' for reading", p); + return LoadError; + } + + return LoadImmediate; + } + } + break; } } } return LoadError; } }; LBrowserThread::LBrowserThread(LBrowserPriv *priv) : LThread("LBrowserThread.Thread"), LMutex("LBrowserThread.Mutex") { Loop = true; d = priv; Run(); } LBrowserThread::~LBrowserThread() { Loop = false; while (!IsExited()) LSleep(10); } void LBrowserThread::Stop() { d->Http.Close(); } bool LBrowserThread::Add(char *Uri) { if (Lock(_FL)) { Work.New().Reset(NewStr(Uri)); Unlock(); return true; } return false; } int LBrowserThread::Main() { bool IsBusy = false; while (Loop) { LAutoString Uri; if (Lock(_FL)) { if (Work.Length()) { Uri = Work[0]; Work.DeleteAt(0, true); if (!IsBusy) d->Wnd->PostEvent(M_BUSY, IsBusy = true); } else if (IsBusy) d->Wnd->PostEvent(M_BUSY, IsBusy = false); Unlock(); } if (Uri) { LUri u(Uri); if (!u.Port) u.Port = HTTP_PORT; int Status = 0; LAutoPtr p(new LStringPipe); if (p) { LProxyUri Proxy; if (Proxy.sHost) d->Http.SetProxy(Proxy.sHost, Proxy.Port); LAutoPtr Sock(new LSocket); if (d->Http.Open(Sock, u.sHost, u.Port)) { LHttp::ContentEncoding Enc; bool b = d->Http.Get(Uri, "Cache-Control:no-cache", &Status, p, &Enc); LBrowserPriv::FilePtr f = d->Lock(); if (!b) { p->Print("

HTTP Error

\nCode: %i
", Status); } f->Files->Add(Uri, p.Release()); d->Wnd->PostEvent(M_LOADED); } } } else LSleep(50); } return false; } LBrowser::LBrowser(LViewI *owner, const char *Title, char *Uri) { d = new LBrowserPriv(this); Name(Title?Title:(char*)"Browser"); LRect r(0, 0, 1000, 800); SetPos(r); MoveSameScreen(owner); if (!Attach(0)) return; #ifdef MAC #define BTN_X 50 #else #define BTN_X 30 #endif AddView(d->Back = new LButton(IDC_BACK, 0, 0, BTN_X, 20, "<-")); AddView(d->Forward = new LButton(IDC_FORWARD, 0, 0, BTN_X, 20, "->")); AddView(d->Stop = new LButton(IDC_REFRESH_STOP, 0, 0, -1, 20, "Refresh")); AddView(d->UriEdit = new LEdit(IDC_URI, 0, 0, 100, 20, 0)); AddView(d->SearchEdit = new LEdit(IDC_SEARCH_TXT, 0, 0, 100, 20, "")); AddView(d->Search = new LButton(IDC_SEARCH, 0, 0, -1, 20, "Search")); AddView(d->Html = new Html1::LHtml(IDC_HTML, 0, 0, 100, 100)); AttachChildren(); OnPosChange(); Visible(true); d->Html->SetEnv(d); d->Html->SetLinkDoubleClick(false); d->Back->Enabled(false); d->Forward->Enabled(false); if (Uri) SetUri(Uri); RegisterHook(this, LKeyEvents); } LBrowser::~LBrowser() { DeleteObj(d); } bool LBrowser::OnViewKey(LView *v, LKey &k) { if (k.Ctrl() && ToUpper(k.c16) == 'W') { Quit(); return true; } return false; } bool LBrowser::SetUri(const char *Uri) { if (Uri) { char s[MAX_PATH_LEN]; if (LDirExists(Uri)) { sprintf_s(s, sizeof(s), "%s%cindex.html", Uri, DIR_CHAR); Uri = s; } // Delete history past the current point while ((ssize_t)d->History.Length() > d->CurHistory + 1) { d->History.DeleteAt(d->CurHistory + 1); } // Add a new URI to the history d->History.New() = Uri; d->CurHistory = d->History.Length() - 1; // Enabled the controls accordingly d->Back->Enabled(d->CurHistory > 0); d->Forward->Enabled(d->CurHistory < (ssize_t)d->History.Length() - 1); // Show the content d->LoadCurrent(); } else { d->Html->Name(""); } return true; } void LBrowser::OnPosChange() { LRect c = GetClient(); LRect e = c; e.y2 = e.y1 + LSysFont->GetHeight() + 8; LRect back = e; LRect forward = e; LRect stop = e; LRect search_txt = e; LRect search_btn = e; LRect uri = e; back.x2 = back.x1 + (d->Back ? d->Back->X() - 1 : 0); forward.x1 = back.x2 + 1; forward.x2 = forward.x1 + (d->Forward ? d->Forward->X() - 1 : 0); stop.x1 = forward.x2 + 1; stop.x2 = stop.x1 + (d->Stop ? d->Stop->X() - 1 : 0); uri.x1 = stop.x2 + 1; search_btn.x1 = search_btn.x2 - (d->Search ? d->Search->X() - 1 : 0); search_txt.x2 = search_btn.x1 - 1; search_txt.x1 = search_txt.x2 - 99; uri.x2 = search_txt.x1 - 1; LRect html = c; html.y1 = e.y2 + 1; if (d->Back) d->Back->SetPos(back); if (d->Forward) d->Forward->SetPos(forward); if (d->Stop) d->Stop->SetPos(stop); if (d->UriEdit) d->UriEdit->SetPos(uri); if (d->SearchEdit) d->SearchEdit->SetPos(search_txt); if (d->Search) d->Search->SetPos(search_btn); if (d->Html) d->Html->SetPos(html); } void LBrowser::SetEvents(LBrowserEvents *Events) { d->Events = Events; } -bool LBrowser::SetHtml(char *Html) +void LBrowser::AddPath(const char *Path) +{ + d->ResourcePaths.Add(Path); +} + +bool LBrowser::SetHtml(const char *Html) { d->History.Length(++d->CurHistory); d->Html->Name(Html); d->UriEdit->Name(0); d->Back->Enabled(d->CurHistory > 0); d->Forward->Enabled(d->CurHistory < (ssize_t)d->History.Length() - 1); return true; } int LBrowser::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_SEARCH_TXT: { if (n.Type != LNotifyReturnKey) break; // else fall through } case IDC_SEARCH: { if (d->Events && d->SearchEdit) { const char *Search = d->SearchEdit->Name(); if (Search) d->Events->OnSearch(this, Search); } break; } case IDC_URI: { if (n.Type == LNotifyReturnKey) { const char *u = c->Name(); SetUri(u); break; } break; } case IDC_BACK: { if (d->CurHistory > 0) { d->CurHistory--; d->LoadCurrent(); } break; } case IDC_FORWARD: { if (d->CurHistory < (ssize_t)d->History.Length() - 1) { d->CurHistory++; d->LoadCurrent(); } break; } case IDC_REFRESH_STOP: { if (d->Loading) { d->Thread->Stop(); } else // refresh { d->LoadCurrent(); } break; } } return 0; } LMessage::Result LBrowser::OnEvent(LMessage *m) { switch (m->Msg()) { case M_LOADED: { LBrowserPriv::FilePtr f = d->Lock(); char *Uri = d->History[d->CurHistory]; LStream *s = f->Files->Find(Uri); if (s) { f->Files->Delete(Uri); d->LoadStream(s); DeleteObj(s); } break; } case M_BUSY: { d->Loading = m->A() != 0; if (d->Loading) SetCtrlName(IDC_REFRESH_STOP, "Stop"); else SetCtrlName(IDC_REFRESH_STOP, "Refresh"); break; } } return LWindow::OnEvent(m); }