diff --git a/include/lgi/common/DateTime.h b/include/lgi/common/DateTime.h --- a/include/lgi/common/DateTime.h +++ b/include/lgi/common/DateTime.h @@ -1,464 +1,469 @@ /// \file /// \author Matthew Allen /** * \defgroup Time Time and date handling * \ingroup Lgi */ #ifndef __DATE_TIME_H #define __DATE_TIME_H #include #include "lgi/common/StringClass.h" #define GDTF_DEFAULT 0 /// Format the date as DD/MM/YYYY /// \ingroup Time #define GDTF_DAY_MONTH_YEAR 0x001 /// Format the date as MM/DD/YYYY /// \ingroup Time #define GDTF_MONTH_DAY_YEAR 0x002 /// Format the date as YYYY/MM/DD /// \ingroup Time #define GDTF_YEAR_MONTH_DAY 0x004 /// The bit mask for the date /// \ingroup Time #define GDTF_DATE_MASK 0x00f /// Format the time as HH:MM and an am/pm marker /// \ingroup Time #define GDTF_12HOUR 0x010 /// Format the time as 24 hour time /// \ingroup Time #define GDTF_24HOUR 0x020 /// The bit mask for the time /// \ingroup Time #define GDTF_TIME_MASK 0x0f0 /// Format the date with a leading zero /// \ingroup Time #define GDTF_DAY_LEADINGZ 0x100 /// Format the month with a leading zero /// \ingroup Time #define GDTF_MONTH_LEADINGZ 0x200 class LDateTime; class LgiClass LTimeStamp { uint64_t ts = 0; public: constexpr static uint64_t WINDOWS_TICK = 10000000; constexpr static uint64_t SEC_TO_UNIX_EPOCH = 11644473600LL; + LTimeStamp(uint64_t sysTime) + { + ts = sysTime; + } + LTimeStamp(time_t unixTime = 0) { if (unixTime) *this = unixTime; } uint64_t Get() const { return ts; } uint64_t &Ref() { return ts; } // Convert from unit time to LGI time stamp LTimeStamp &operator =(const time_t unixTime); // Convert from LDateTime LTimeStamp &operator =(const LDateTime &dt); // Convert from LGI time stamp to unix time time_t Unix() const; // operator bool() const { return ts != 0; } bool Valid() const { return ts != 0; } LTimeStamp operator +(uint64_t offset) { LTimeStamp t; t.Ref() = ts + offset; return t; } bool operator <(const LTimeStamp &b) const { return ts < b.ts; } bool operator <=(const LTimeStamp &b) const { return ts <= b.ts; } bool operator >(const LTimeStamp &b) const { return ts > b.ts; } bool operator >=(const LTimeStamp &b) const { return ts >= b.ts; } int64_t operator -(const LTimeStamp &b) const { return (int64_t)ts - (int64_t)b.ts; } LTimeStamp &operator +=(int64_t i) { ts += i; return *this; } LTimeStamp &operator -=(int64_t i) { ts -= i; return *this; } }; /// A date/time class /// /// This class interacts with system times represented as 64bit ints. The various OS support different /// formats for that 64bit int values. /// - On windows the system times are in 100-nanosecond intervals since /// January 1, 1601 (UTC), as per the FILETIME structure. /// - On Posix systems (Linux/Mac) the 64bit values are in milliseconds since January 1, 1800 UTC. /// This is just (unixTime + Offset1800) * 1000. /// If you are serializing these 64bit values you should take that into account, they are NOT cross platform. /// The LDirectory class uses the same 64bit values as accepted here for the file's last modified timestamp /// etc. To convert the 64bit values to seconds, divide by LDateTime::Second64Bit, useful for calculating /// the time in seconds between 2 LDateTime objects. /// /// \ingroup Time class LgiClass LDateTime // This class can't have a virtual table, because it's used in // LArray's which initialize with all zero bytes. { /// 1 - DaysInMonth int16 _Day; /// #### int16 _Year; /// Milliseconds: 0-999 int16 _Thousands; /// 1-12 int16 _Month; /// 0-59 int16 _Seconds; /// 0-59 int16 _Minutes; /// 0-23 (24hr) int16 _Hours; /// The current timezone of this object, defaults to the system timezone int16 _Tz; // in minutes (+10 == 600 etc) /// Combination of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 _Format; /// The default formatting of datetimes static uint16 DefaultFormat; /// The default date separator character static char DefaultSeparator; public: LDateTime(const char *Init = NULL); LDateTime(time_t unixTime); LDateTime(const LTimeStamp &Ts); LDateTime(const LDateTime &dt) { *this = dt; } ~LDateTime(); /// Resolution of a second when using 64 bit int's /// \sa LDateTime::Get(int64), LDateTime::Set(int64) #ifdef WIN32 static constexpr int64_t Second64Bit = 10000000; #else static constexpr int64_t Second64Bit = 1000; #endif static constexpr int64_t MinuteLength = 60; // seconds static constexpr int64_t HourLength = MinuteLength * 60; // seconds static constexpr int64_t DayLength = HourLength * 24; // seconds static constexpr const char *WeekdaysShort[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static constexpr const char *WeekdaysLong[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; static constexpr const char *MonthsShort[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; static constexpr const char *MonthsLong[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; // On Posix systems this allows representation of times before 1/1/1970. // The Lgi epoch is considered to be 1/1/1800 instead and this offset converts // from one to the other. static constexpr int64_t Offset1800 = 5364662400; // Seconds from 1/1/1800 to 1/1/1970 /// Returns true if all the components are in a valid range bool IsValid() const; /// Sets the date to an NULL state void Empty(); /// Returns the day int Day() const { return _Day; } /// Sets the day void Day(int d) { _Day = d; } /// Returns the month int Month() const { return _Month; } /// Sets the month void Month(int m) { _Month = m; } /// Sets the month by it's name void Month(char *m); /// Returns the year int Year() const { return _Year; } /// Sets the year void Year(int y) { _Year = y; } /// Returns the millisecond part of the time int Thousands() const { return _Thousands; } /// Sets the millisecond part of the time void Thousands(int t) { _Thousands = t; } /// Returns the seconds part of the time int Seconds() const { return _Seconds; } /// Sets the seconds part of the time void Seconds(int s) { _Seconds = s; } /// Returns the minutes part of the time int Minutes() const { return _Minutes; } /// Sets the minutes part of the time void Minutes(int m) { _Minutes = m; } /// Returns the hours part of the time int Hours() const { return _Hours; } /// Sets the hours part of the time void Hours(int h) { _Hours = h; } /// Returns the timezone of this current date time object in minutes (+10 = 600) int GetTimeZone() const { return _Tz; } /// Returns the timezone in hours double GetTimeZoneHours() const { return (double)_Tz / 60.0; } /// Sets the timezone of this current object.in minutes (+10 = 600) void SetTimeZone ( /// The new timezone (in minutes, 600 = +10:00) int Tz, /// True if you want to convert the date and time to the new zone, /// False if you want to leave the date/time as it is. bool ConvertTime ); /// Set this object to UTC timezone, changing the other members as /// needed LDateTime &ToUtc(bool AssumeLocal = false) { if (AssumeLocal) _Tz = SystemTimeZone(); SetTimeZone(0, true); return *this; } /// \returns the UTC version of this object. LDateTime Utc() const { LDateTime dt = *this; return dt.ToUtc(); } /// Changes the timezone to the local zone, changing other members /// as needed. LDateTime &ToLocal(bool AssumeUtc = false) { if (AssumeUtc) _Tz = 0; SetTimeZone(SystemTimeZone(), true); return *this; } /// \returns the local time version of this object. LDateTime Local() const { LDateTime dt = *this; return dt.ToLocal(); } /// Gets the current formatting of the date, the format only effects /// the representation of the date when converted to/from a string. /// \returns a bit mask of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 GetFormat() const { return _Format; } /// Sets the current formatting of the date, the format only effects /// the representation of the date when converted to/from a string void SetFormat ( /// a bit mask of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 f ) { _Format = f; } /// \returns zero based index of weekday, or -1 if not found. static int IsWeekDay(const char *s); /// \returns zero based index of month, or -1 if not found. static int IsMonth(const char *s); /// The default format for the date when formatted as a string static uint16 GetDefaultFormat(); /// Sets the default format for the date when formatted as a string static void SetDefaultFormat(uint16 f) { DefaultFormat = f; } /// Gets the data and time as a LString LString Get() const; /// Gets the date and time as a string /// \sa LDateTime::GetFormat() void Get(char *Str, size_t SLen) const; /// Gets the data and time as a 64 bit int (os specific) bool Get(LTimeStamp &s) const; /// Gets just the date as a string /// \sa LDateTime::GetFormat() /// \returns The number of characters written to 'Str' int GetDate(char *Str, size_t SLen) const; /// Gets just the date as a LString /// \sa LDateTime::GetFormat() LString GetDate() const; /// Gets just the time as a string /// \sa LDateTime::GetFormat() /// \returns The number of characters written to 'Str' int GetTime(char *Str, size_t SLen) const; /// Gets just the time as a LString /// \sa LDateTime::GetFormat() LString GetTime() const; // Unix epoch support uint64_t GetUnix(); bool SetUnix(uint64_t s); /// Returns the 64bit LTimeStamp. LTimeStamp Ts() const; /// Get the timestamp in a format compatible with the current operating system APIs. /// \returns the timestamp or zero on error. uint64_t OsTime() const; bool OsTime(uint64_t ts); /// Get the current time... static LDateTime Now(); /// Sets the date and time to the system clock LDateTime &SetNow(); /// Parses a date time from a string /// \sa LDateTime::GetFormat() bool Set(const char *Str); /// Sets the date and time from a 64 bit int (os specific) bool Set(const LTimeStamp &s); /// Sets the time from a unit time_t bool Set(time_t tt); /// Parses the date from a string /// \sa LDateTime::GetFormat() bool SetDate(const char *Str); /// Parses the time from a string /// \sa LDateTime::GetFormat() bool SetTime(const char *Str); /// Parses the date time from a free form string bool Parse(LString s); /// Set to a tm struct bool Set(struct tm *t, bool inferTimezone); /// Describes the perios between this and 'to' in the form: /// ##d ##h ##m ##s /// Order of the dates isn't important. LString DescribePeriod(LDateTime to); static LString DescribePeriod(double seconds); /// \returns true if 'd' is on the same day as this object bool IsSameDay(LDateTime &d) const; /// \returns true if 'd' is on the same month as this object bool IsSameMonth(LDateTime &d) const; /// \returns true if 'd' is on the same year as this object bool IsSameYear(LDateTime &d) const; /// \returns the start of day, same date but time: 00:00:00 LDateTime StartOfDay() const; /// \returns the end of day, same date but time: 23:59:59 LDateTime EndOfDay() const; /// \returns whether a year is a leap year or not bool IsLeapYear ( /// Pass a specific year here, or ignore to return if the current Date/Time is in a leap year. int Year = -1 ) const; /// Returns the day of the week as an index, 0=sun, 1=mon, 2=teus etc int DayOfWeek() const; /// \returns the number of days in the current month int DaysInMonth() const; /// Adds a number of seconds to the current date/time void AddSeconds(int64 Seconds); /// Adds a number of minutes to the current date/time void AddMinutes(int64 Minutes); /// Adds a number of hours to the current date/time void AddHours(int64 Hours); /// Adds a number of days to the current date/time bool AddDays(int64 Days); /// Adds a number of months to the current date/time void AddMonths(int64 Months); /// Adds years void AddYears(int yr) { _Year += yr; } /// The system timezone including daylight savings offset in minutes, +10 would return 600 static int SystemTimeZone(bool ForceUpdate = false); /// Any daylight savings offset applied to TimeZone(), in minutes. e.g. to retreive the /// timezone uneffected by DST use TimeZone() - TimeZoneOffset(). static int SystemTimeZoneOffset(); /// Daylight savings info record struct LgiClass LDstInfo { /// Timestamp where the DST timezone changes to 'Offset' LTimeStamp Utc; /// The new offset in minutes (e.g. 600 = +10 hours) int Offset; LDateTime GetLocal(); }; /// Retreives daylight savings start and end events for a given period. One event will be emitted /// for the current DST/TZ setting at the datetime specified by 'Start', followed by any changes that occur /// between that and the 'End' datetime. To retreive just the DST info for start, use NULL for end. static bool GetDaylightSavingsInfo ( /// [Out] The array to receive DST info. At minimum one record will be returned /// matching the TZ in place for the start datetime. LArray &Out, /// [In] The start date that you want DST info for. LDateTime &Start, /// [Optional In] The end of the period you want DST info for. LDateTime *End = 0 ); /// Using the DST info this will convert 'dt' from UTC to local static bool DstToLocal(LArray &Dst, LDateTime &dt); /// Decodes an email date into the current instance bool Decode(const char *In); /// Returns a month index from a month name static int MonthFromName(const char *Name); // File int Sizeof(); bool Serialize(class LFile &f, bool Write); bool Serialize(class LDom *Props, char *Name, bool Write); // operators bool operator <(const LDateTime &dt) const; bool operator <=(const LDateTime &dt) const; bool operator >(const LDateTime &dt) const; bool operator >=(const LDateTime &dt) const; bool operator ==(const LDateTime &dt) const; bool operator !=(const LDateTime &dt) const; int Compare(const LDateTime *d) const; LDateTime operator -(const LDateTime &dt); LDateTime operator +(const LDateTime &dt); int DiffMonths(const LDateTime &dt); LDateTime &operator =(struct tm *t); LDateTime &operator =(const LDateTime &t); LDateTime &operator =(LDateTime const *t) { if (t) *this = *t; return *this; } /// LDom interface. /// /// Even though we don't inherit from a LDom class this class supports the same /// interface for ease of use. Currently there are cases where LDateTime is used /// in LArray's which don't implement calling a constructor (they init with all /// zeros). bool GetVariant(const char *Name, class LVariant &Value, char *Array = NULL); bool SetVariant(const char *Name, class LVariant &Value, char *Array = NULL); bool CallMethod(const char *Name, class LVariant *ReturnValue, LArray &Args); }; /// Time zone information struct LTimeZone { public: /// The offset from UTC float Offset; /// The name of the zone const char *Text; }; /// A list of all known timezones. extern LTimeZone LTimeZones[]; #ifdef _DEBUG LgiFunc bool LDateTime_Test(); #endif #endif diff --git a/include/lgi/common/Uri.h b/include/lgi/common/Uri.h --- a/include/lgi/common/Uri.h +++ b/include/lgi/common/Uri.h @@ -1,67 +1,80 @@ #pragma once #include "lgi/common/StringClass.h" /// Uri parser class LgiClass LUri { public: LString sProtocol; LString sUser; LString sPass; LString sHost; - int Port; + int Port = 0; LString sPath; LString sAnchor; /// Parser for URI's. LUri ( /// Optional URI to start parsing - const char *uri = 0 + const char *uri = NULL + ); + LUri + ( + const char *proto, + const char *user, + const char *pass, + const char *host, + int port, + const char *path, + const char *anchor = NULL ); ~LUri(); bool IsProtocol(const char *p) { return sProtocol.Equals(p); } bool IsHttp() { return sProtocol.Equals("http") || sProtocol.Equals("https"); } bool IsFile() { return sProtocol.Equals("file"); } void SetFile(LString Path) { Empty(); sProtocol = "file"; sPath = Path; } const char *LocalPath(); operator bool(); /// Parse a URI into it's sub fields... bool Set(const char *uri); /// Re-constructs the URI LString ToString(); /// Empty this object... void Empty(); /// URL encode LString EncodeStr ( /// The string to encode const char *s, /// [Optional] Any extra characters you want encoded const char *ExtraCharsToEncode = 0 ); /// URL decode LString DecodeStr(const char *s); /// Separate args into map typedef LHashTbl,LString> StrMap; StrMap Params(); LUri &operator =(const LUri &u); LUri &operator =(const char *s) { Set(s); return *this; } LUri &operator +=(const char *s); + + // Unit testing... + static bool UnitTests(); }; /// Proxy settings lookup class LgiClass LProxyUri : public LUri { public: LProxyUri(); }; diff --git a/src/common/LiteHtml/LiteHtmlView.cpp b/src/common/LiteHtml/LiteHtmlView.cpp --- a/src/common/LiteHtml/LiteHtmlView.cpp +++ b/src/common/LiteHtml/LiteHtmlView.cpp @@ -1,566 +1,609 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/Edit.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Layout.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Menu.h" #include "lgi/common/LiteHtmlView.h" #include "lgi/common/Thread.h" #include "lgi/common/Http.h" +#include "lgi/common/PopupNotification.h" #undef min #undef max #include "litehtml/html.h" #define NOT_IMPL \ - LgiTrace("%s:%i - %s not impl.\n", _FL, __func__); \ - LAssert(!"not impl"); + LgiTrace("%s:%i - %s not impl.\n", _FL, __func__); struct LiteHtmlViewPriv : public litehtml::document_container, public LCancel { struct NetworkThread : public LThread, public LCancel { using TCallback = std::function; LView *view = NULL; LString url; TCallback callback; NetworkThread(LView *View, const char *Url, TCallback cb) : LThread("LiteHtmlViewPriv.NetworkThread") { view = View; url = Url; callback = std::move(cb); Run(); } ~NetworkThread() { Cancel(); WaitForExit(); } int Main() { LStringPipe p; LString err; auto result = LgiGetUri(this, &p, &err, url); // LgiTrace("net(%s) = %i\n", url.Get(), result); view->RunCallback([result, data=p.NewLStr(), this]() { callback(result, data); }); return 0; } }; struct Image { int status = -1; LString uri; LAutoPtr img; }; LArray threads; LiteHtmlView *view = NULL; LWindow *wnd = NULL; litehtml::document::ptr doc; LRect client; LString cursorName; LString currentUrl; LHashTbl, LFont*> fontMap; LHashTbl, Image*> imageCache; LiteHtmlViewPriv(LiteHtmlView *v) : view(v) { } ~LiteHtmlViewPriv() { Cancel(); threads.DeleteObjects(); // Do this before releasing other owned objects, like the fontMap. doc.reset(); // Clean up caches imageCache.DeleteObjects(); fontMap.DeleteObjects(); } LColour Convert(const litehtml::web_color &c) { return LColour(c.red, c.green, c.blue, c.alpha); } LRect Convert(const litehtml::position &p) { LRect r(p.x, p.y, p.x + p.width - 1, p.y + p.height - 1); return r; } void UpdateScreen(litehtml::position::vector &redraw) { if (redraw.size() > 0) { // FIXME: should invalidate just the dirty regions... view->Invalidate(); } } litehtml::uint_ptr create_font( const char* faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics *fm) { litehtml::uint_ptr hnd; do { hnd = LRand(10000); } while (fontMap.Find(hnd) != NULL); LFont *fnt = new LFont; bool status = fnt->Create(faceName, LCss::Len(LCss::LenPt, size) ); if (!status) LgiTrace("%s:%i - failed to create font(%s,%i)\n", _FL, faceName, size); fontMap.Add(hnd, fnt); if (fm) { LDisplayString ds(fnt, "x"); fm->height = fnt->GetHeight(); fm->ascent = fnt->Ascent(); fm->descent = fnt->Descent(); fm->x_height = ds.Y(); fm->draw_spaces = false; } return hnd; } void delete_font(litehtml::uint_ptr hFont) { auto fnt = fontMap.Find(hFont); if (fnt) { delete fnt; fontMap.Delete(hFont); } } int text_width(const char* text, litehtml::uint_ptr hFont) { auto fnt = fontMap.Find(hFont); if (!fnt) return 0; LDisplayString ds(fnt, text); return ds.X(); } void draw_text(litehtml::uint_ptr hdc, const char* text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position& pos) { auto pDC = (LSurface*)hdc; auto Fnt = fontMap.Find(hFont); if (!pDC || !Fnt) return; LDisplayString ds(Fnt, text); Fnt->Fore(Convert(color)); Fnt->Transparent(true); ds.Draw(pDC, pos.x, pos.y); } int pt_to_px(int pt) const { auto dpi = wnd->GetDpi(); int px = pt * dpi.x / 72; return px; } int get_default_font_size() const { - return 13; // LSysFont->PointSize(); + return 12; // LSysFont->PointSize(); } const char *get_default_font_name() const { return LSysFont->Face(); } void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) { auto pDC = (LSurface*)hdc; auto Fnt = fontMap.Find(marker.font); if (!pDC) return; pDC->Colour(Convert(marker.color)); switch (marker.marker_type) { + case litehtml::list_style_type_none: + break; + case litehtml::list_style_type_circle: + pDC->Circle(marker.pos.x, marker.pos.y, 3); + break; case litehtml::list_style_type_disc: - { pDC->FilledCircle(marker.pos.x, marker.pos.y, 3); break; - } + case litehtml::list_style_type_square: + pDC->Box(marker.pos.x-2, marker.pos.y-2, marker.pos.x+2, marker.pos.y+2); + break; default: - { LgiTrace("%s:%i - draw_list_marker %i not impl\n", marker.marker_type); break; - } } } + LString FullUri(const char *src, const char *baseurl) + { + LUri s(src); + if (s.sProtocol) + return src; + + if (s.sPath) + { + LUri c(currentUrl); + c += s.sPath; + return c.ToString(); + } + + return LString(); + } + void load_image(const char *src, const char *baseurl, bool redraw_on_ready) { - if (!imageCache.Find(src)) + auto absUri = FullUri(src, baseurl); + if (!absUri) + return; + + if (!imageCache.Find(absUri)) { - LgiTrace("load_image(%s,%s)\n", src, baseurl); + LgiTrace("load_image(%s) %s + %s\n", absUri.Get(), currentUrl.Get(), src); if (auto i = new Image) { - i->uri = src; - imageCache.Add(src, i); - threads.Add(new NetworkThread(view, src, [i, this](auto status, auto data) + i->uri = absUri; + imageCache.Add(absUri, i); + threads.Add(new NetworkThread(view, absUri, [i, this, redraw_on_ready](auto status, auto data) { auto leaf = i->uri.SplitDelimit("/").Last(); if (status) { LMemStream s(data.Get(), data.Length(), false); i->status = i->img.Reset(GdcD->Load(&s, leaf)); LgiTrace("load_image(%s) load status=%i\n", i->uri.Get(), i->status); + if (i->status && redraw_on_ready) + view->Invalidate(); } else { i->status = false; LgiTrace("load_image(%s) network status=%i\n", i->uri.Get(), i->status); } - - view->Invalidate(); })); } } } void get_image_size(const char *src, const char *baseurl, litehtml::size &sz) { - if (auto i = imageCache.Find(src)) + auto absUri = FullUri(src, baseurl); + if (!absUri) + return; + + if (auto i = imageCache.Find(absUri)) { if (i->img) { sz.width = i->img->X(); sz.height = i->img->Y(); } } } void draw_background(litehtml::uint_ptr hdc, const std::vector &background) { auto pDC = (LSurface*)hdc; for (auto b: background) { auto rc = Convert(b.border_box); if (!b.image.empty()) { - if (auto i = imageCache.Find(b.image.c_str() )) + auto absUri = FullUri(b.image.c_str(), b.baseurl.c_str()); + if (auto i = imageCache.Find(absUri)) { if (i->img) { + auto op = pDC->Op(GDC_ALPHA); pDC->Blt(b.position_x, b.position_y, i->img); + pDC->Op(op); } else LgiTrace("%s:%i - draw_background(img=%s) img no surface\n", _FL, b.image.c_str()); } else LgiTrace("%s:%i - draw_background(img=%s) img not found\n", _FL, b.image.c_str()); } else { pDC->Colour(Convert(b.color)); pDC->Rectangle(&rc); } } } void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) { auto pDC = (LSurface*)hdc; auto drawEdge = [&](const litehtml::border &b, int x, int y, int dx, int dy, int ix, int iy) { pDC->Colour(Convert(b.color)); for (int i=0; iLine(x, y, x+dx, y+dy); x += ix; y += iy; } }; int x2 = draw_pos.width - 1; int y2 = draw_pos.height - 1; drawEdge(borders.left, draw_pos.x, draw_pos.y, 0, y2, 1, 0); drawEdge(borders.top, draw_pos.x, draw_pos.y, x2, 0, 0, 1); drawEdge(borders.right, draw_pos.x+x2, draw_pos.y, 0, y2, -1, 0); drawEdge(borders.bottom, draw_pos.x, draw_pos.y+y2, x2, 0, 0, -1); } void set_caption(const char* caption) { wnd->Name(caption); } void set_base_url(const char* base_url) { - NOT_IMPL + currentUrl = base_url; } void link(const std::shared_ptr& doc, const litehtml::element::ptr& el) { NOT_IMPL } void on_anchor_click(const char* url, const litehtml::element::ptr& el) { if (!url) return; threads.Add(new NetworkThread(view, url, [this, url=LString(url)](auto status, auto data) { - currentUrl = url; - doc = litehtml::document::createFromString(data, this); - view->OnNavigate(url); - view->Invalidate(); + if (data) + { + currentUrl = url; + doc = litehtml::document::createFromString(data, this); + view->OnNavigate(url); + view->Invalidate(); + } + else LPopupNotification::Message(wnd, LString::Fmt("No data for '%s'", url.Get())); + })); } void set_cursor(const char* cursor) { cursorName = cursor; } void transform_text(litehtml::string& text, litehtml::text_transform tt) { NOT_IMPL } void import_css(litehtml::string& text, const litehtml::string& url, litehtml::string& baseurl) { LUri cur(currentUrl); LUri u(url.c_str()); LString newUri; if (u.sProtocol && u.sHost) { // Absolute URI newUri = url.c_str(); } else if (char *s = u.sPath.Get()) { // Relative URI if (*s == '/') cur.sPath.Empty(); cur.sPath = u.sPath; newUri = cur.ToString(); } if (newUri) { LStringPipe out; LString err; if (LgiGetUri(this, &out, &err, newUri)) { text = out.NewLStr().Get(); } else LgiTrace("%s:%i - error: LgiGetUri(%s)=%s (currentUrl=%s)\n", _FL, newUri.Get(), err.Get(), currentUrl.Get()); } else LgiTrace("%s:%i - error: no uri for loading css.\n", _FL); } void set_clip(const litehtml::position& pos, const litehtml::border_radiuses& bdr_radius) { // NOT_IMPL } void del_clip() { // NOT_IMPL } void get_client_rect(litehtml::position &out) const { out = litehtml::position(client.x1, client.y1, client.X(), client.Y()); } litehtml::element::ptr create_element( const char* tag_name, const litehtml::string_map& attributes, const std::shared_ptr& doc) { return NULL; } void get_media_features(litehtml::media_features& media) const { // NOT_IMPL } void get_language(litehtml::string& language, litehtml::string& culture) const { NOT_IMPL } /* litehtml::string resolve_color(const litehtml::string &color) const { NOT_IMPL return litehtml::string(); } */ }; ///////////////////////////////////////////////////////////////// LiteHtmlView::LiteHtmlView(int id) { d = new LiteHtmlViewPriv(this); SetId(id); } LiteHtmlView::~LiteHtmlView() { delete d; } void LiteHtmlView::OnAttach() { d->wnd = GetWindow(); } LCursor LiteHtmlView::GetCursor(int x, int y) { if (d->cursorName == "pointer") return LCUR_PointingHand; return LCUR_Normal; } void LiteHtmlView::OnNavigate(LString url) { d->currentUrl = url; Invalidate(); } bool LiteHtmlView::SetUrl(LString url) { - if (LFileExists(url)) + LUri u(url); + if (u.IsProtocol("file") || + (!u.sProtocol && LFileExists(url))) { auto html_text = LReadFile(url); if (html_text) { d->currentUrl = url; d->doc = litehtml::document::createFromString(html_text, d); OnNavigate(url); return d->doc != NULL; } } + else + { + litehtml::element::ptr elem; + d->on_anchor_click(url, elem); + } return false; } void LiteHtmlView::OnPaint(LSurface *pDC) { #ifdef WINDOWS LDoubleBuffer buf(pDC); #endif d->client = GetClient(); if (d->doc) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(); auto width = pDC->X(); int r = d->doc->render(width); if (r) { auto width = d->doc->content_width(); auto height = d->doc->content_height(); if (height > Y()) { SetScrollBars(false, true); if (VScroll) { VScroll->SetRange(height); VScroll->SetPage(Y()); } } litehtml::position clip(0, 0, pDC->X(), pDC->Y()); d->doc->draw((litehtml::uint_ptr)pDC, 0, VScroll?-VScroll->Value():0, &clip); } } else { LLayout::OnPaint(pDC); } } int LiteHtmlView::OnNotify(LViewI *c, LNotification n) { // LgiTrace("OnNotify %i=%i, %i=%i\n", c->GetId(), IDC_VSCROLL, n.Type, LNotifyValueChanged); if (c->GetId() == IDC_VSCROLL && n.Type == LNotifyValueChanged) { // LgiTrace("Inval\n"); Invalidate(); } return LLayout::OnNotify(c, n); } void LiteHtmlView::OnMouseClick(LMouse &m) { if (!d->doc) return; int64_t sx, sy; GetScrollPos(sx, sy); litehtml::position::vector redraw_boxes; if (m.IsContextMenu()) { LSubMenu sub; sub.AppendItem("notImpl: submenu", -1, false); sub.Float(this, m); } else if (m.Left()) { if (m.Down()) d->doc->on_lbutton_down(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); else d->doc->on_lbutton_up(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); } d->UpdateScreen(redraw_boxes); } void LiteHtmlView::OnMouseMove(LMouse &m) { if (!d->doc) return; int64_t sx, sy; GetScrollPos(sx, sy); litehtml::position::vector redraw_boxes; d->doc->on_mouse_over(m.x+sx, m.y+sy, m.x, m.y, redraw_boxes); d->UpdateScreen(redraw_boxes); } diff --git a/src/common/Net/Uri.cpp b/src/common/Net/Uri.cpp --- a/src/common/Net/Uri.cpp +++ b/src/common/Net/Uri.cpp @@ -1,406 +1,439 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Net.h" #include "lgi/common/RegKey.h" #include "lgi/common/Uri.h" ///////////////////////////////////////////////////////////////////////////////// static const char *Ws = " \t\r\n"; #define SkipWs(s) while (*s && strchr(Ws, *s)) s++; LUri::LUri(const char *uri) { - Port = 0; if (uri) Set(uri); } +LUri::LUri +( + const char *proto, + const char *user, + const char *pass, + const char *host, + int port, + const char *path, + const char *anchor +) +{ + sProtocol = proto; + sUser = user; + sPass = pass; + sHost = host; + Port = port; + sPath = path; + sAnchor = anchor; +} + LUri::~LUri() { Empty(); } LUri &LUri::operator +=(const char *s) { // Add segment to path - if (!sPath.Length() || sPath(-1) != '/') - sPath += IsFile() ? DIR_STR : "/"; + if (!s) + return *this; - auto len = sPath.Length(); - sPath += s; + if (*s == '/') + sPath.Empty(); // reset - if (IsFile()) + auto parts = sPath.SplitDelimit("/\\"); + parts.SetFixedLength(false); + + for (auto p: LString(s).SplitDelimit("/\\")) { - char *c = sPath.Get(); - #if DIR_CHAR == '/' - char from = '\\'; - #else - char from = '/'; - #endif - for (size_t i=len; c && i 1 && - s[1] == '/' && - s[2] == '/') + const char *hasProto = NULL; + const char *hasAt = NULL; + const char *hasPath = NULL; + const char *hasColon = NULL; + + for (auto c = s; *c; c++) { - sProtocol.Set(p, s - p); - s += 3; - } - else - { - // No protocol, so assume it's a host name or path - s = p; + if (c[0] == ':' && + c[1] == '/' && + c[2] == '/') + { + if (!hasProto) + { + hasProto = c; + c += 2; + } + } + else if (c[0] == '@' && !hasAt) + { + hasAt = c; // keep the first '@' + } + else if (c[0] == ':') + { + hasColon = c; + } + else if ((c[0] == '/' || c[0] == '\\') && !hasPath) + { + hasPath = c; + break; // anything after this is path... + } } - // Check for path - bool HasPath = false; - if - ( - (s[0] && s[1] == ':') - || - (s[0] == '/') - || - (s[0] == '\\') - ) + if (hasProto) + { + sProtocol.Set(s, hasProto - s); + s = hasProto + 3; + } + + if (hasAt) { - HasPath = true; + if (hasAt >= s) + { + auto p = LString(s, hasAt - s).SplitDelimit(":", 1); + if (p.Length() == 2) + { + sUser = DecodeStr(p[0]); + sPass = DecodeStr(p[1]); + } + else if (p.Length() == 1) + { + sUser = DecodeStr(p[0]); + } + + s = hasAt + 1; + } + else LAssert(!"hasAt should be > s"); + } + + bool hasHost = hasProto || hasAt || hasColon || !hasPath; + if (hasHost) + { + auto p = LString(s, hasPath ? hasPath - s : -1).SplitDelimit(":", 1); + if (p.Length() == 2) + { + sHost = p[0]; + Port = p[1].Int(); + } + else if (p.Length() == 1) + { + sHost = p[0]; + } } else { - // Scan over the host name - p = s; - while ( *s && - *s > ' ' && - *s < 127 && - *s != '/' && - *s != '\\') - { - s++; - } + hasPath = s; + } - sHost.Set(p, s - p); - if (sHost) - { - char *At = strchr(sHost, '@'); - if (At) - { - *At++ = 0; - char *Col = strchr(sHost, ':'); - if (Col) - { - *Col++ = 0; - sPass = DecodeStr(Col); - } - - sUser = DecodeStr(sHost); - sHost = At; - } - - char *Col = strchr(sHost, ':'); - if (Col) - { - Port = atoi(Col+1); - sHost.Length(Col-sHost.Get()); - } - } - - HasPath = *s == '/'; + if (hasPath) + { + sPath = hasPath; } - if (HasPath) + if (sPath) { - const char *Start = s; - while (*s && *s != '#') - s++; - - if (*s) + auto anchor = sPath.Find("#"); + if (anchor >= 0) { - sPath.Set(Start, s - Start); - sAnchor = s + 1; + sAnchor = sPath(anchor, -1); + sPath.Length(anchor); } - else - { - sPath = Start; - } + } - #if 0 - // This decodes the path from %## encoding to raw characters. - // However sometimes we need the encoded form. So instead of - // doing the conversion here the caller has to do it now. - char *i = Path, *o = Path; - while (*i) - { - if (*i == '%' && i[1] && i[2]) - { - char h[3] = {i[1], i[2], 0}; - *o++ = htoi(h); - i+=2; - } - else - { - *o++ = *i; - } - i++; - } - *o = 0; - #endif - } - return sHost || sPath; } LString LUri::EncodeStr(const char *s, const char *ExtraCharsToEncode) { LStringPipe p(256); if (s) { while (*s) { if (*s == ' ' || (ExtraCharsToEncode && strchr(ExtraCharsToEncode, *s))) { char h[4]; sprintf_s(h, sizeof(h), "%%%2.2X", (uint32_t)(uchar)*s++); p.Write(h, 3); } else { p.Write(s++, 1); } } } return p.NewLStr(); } LUri::StrMap LUri::Params() { StrMap m; if (sPath) { const char *q = strchr(sPath, '?'); if (q++) { auto Parts = LString(q).SplitDelimit("&"); for (auto p : Parts) { auto Var = p.Split("=", 1); if (Var.Length() == 2) m.Add(Var[0], Var[1]); } } } return m; } LString LUri::DecodeStr(const char *s) { LStringPipe p(256); if (s) { while (*s) { if (s[0] == '%' && s[1] && s[2]) { char h[3] = { s[1], s[2], 0 }; char c = htoi(h); p.Write(&c, 1); s += 3; } else { p.Write(s++, 1); } } } return p.NewLStr(); } +struct UriUnitCase +{ + const char *str; + LUri uri; +}; + +bool LUri::UnitTests() +{ + UriUnitCase Parse[] = { + {"http://user:pass@host:1234/somePath/seg/file.png", LUri("http", "user", "pass", "host", 1234, "somePath/seg/file.png")}, + {"user:pass@host:1234/somePath/seg/file.png", LUri(NULL, "user", "pass", "host", 1234, "somePath/seg/file.png") }, + {"user@host:1234/somePath/seg/file.png", LUri(NULL, "user", NULL, "host", 1234, "somePath/seg/file.png") }, + {"user@host/somePath/seg/file.png", LUri(NULL, "user", NULL, "host", 0, "somePath/seg/file.png") }, + {"user@host", LUri(NULL, "user", NULL, "host", 0, NULL) }, + {"host", LUri(NULL, NULL, NULL, "host", 0, NULL) }, + {"host:1234", LUri(NULL, NULL, NULL, "host", 1234, NULL) }, + {"somePath/seg/file.png", LUri(NULL, NULL, NULL, NULL, 0, "somePath/seg/file.png") }, + }; + + for (auto &test: Parse) + { + LUri u(test.str); + if (u != test.uri) + { + LAssert(!"test failed"); + return false; + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////// #if defined LGI_CARBON int CFNumberRefToInt(CFNumberRef r, int Default = 0) { int i = Default; if (r && CFGetTypeID(r) == CFNumberGetTypeID()) { CFNumberGetValue(r, kCFNumberIntType, &r); } return i; } #endif LProxyUri::LProxyUri() { #if defined(WIN32) LRegKey k(false, "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"); if (k.IsOk()) { uint32_t Enabled = 0; if (k.GetInt("ProxyEnable", Enabled) && Enabled) { char *p = k.GetStr("ProxyServer"); if (p) { Set(p); } } } #elif defined LINUX char *HttpProxy = getenv("http_proxy"); if (HttpProxy) { Set(HttpProxy); } #elif defined MAC // CFDictionaryRef Proxies = SCDynamicStoreCopyProxies(0); // if (!Proxies) // LgiTrace("%s:%i - SCDynamicStoreCopyProxies failed.\n", _FL); // else // { // int enable = CFNumberRefToInt((CFNumberRef) CFDictionaryGetValue(Proxies, kSCPropNetProxiesHTTPEnable)); // if (enable) // { // #ifdef LGI_COCOA // LAssert(!"Fixme"); // #else // Host = CFStringToUtf8((CFStringRef) CFDictionaryGetValue(Proxies, kSCPropNetProxiesHTTPProxy)); // #endif // Port = CFNumberRefToInt((CFNumberRef) CFDictionaryGetValue(Proxies, kSCPropNetProxiesHTTPPort)); // } // // CFRelease(Proxies); // } #elif defined(HAIKU) // There doesn't seem to be a system wide proxy setting, so for the time being // lets just put a setting in the Lgi config and use that: if (!LAppInst) { LgiTrace("%s:%i - No LApp instance yet?\n", _FL); } else { auto p = LAppInst->GetConfig(LApp::CfgNetworkHttpProxy); if (p) { Set(p); } else { static bool First = true; if (First) { First = false; LgiTrace("%s:%i No HTTP Proxy configured in '%s'.\n", _FL, LAppInst->GetConfigPath().Get()); } } } #else #warning "Impl getting OS proxy here." #endif } diff --git a/test/LiteHtml/CMakeLists.txt b/test/LiteHtml/CMakeLists.txt --- a/test/LiteHtml/CMakeLists.txt +++ b/test/LiteHtml/CMakeLists.txt @@ -1,23 +1,35 @@ cmake_minimum_required(VERSION 3.18) project(LgiLiteHtml) set(LGI_LIB_ONLY TRUE) add_subdirectory(../.. lgi) add_subdirectory(../../../../../codelib/litehtml litehtml) set(SRC src/main.cpp ../../src/common/LiteHtml/LiteHtmlView.cpp ../../src/common/Net/Http.cpp ../../src/common/Net/OpenSSLSocket.cpp - ../../src/common/Lgi/LgiMain.cpp) + ../../src/common/Lgi/LgiMain.cpp + ../../src/common/Gdc2/Filters/Png.cpp + ../../src/common/Gdc2/Filters/Jpeg.cpp) if(WIN32) set(APP_TYPE WIN32) else() set(APP_TYPE) endif() add_executable(LgiLiteHtml ${APP_TYPE} ${SRC}) -target_link_libraries(LgiLiteHtml PUBLIC lgi litehtml) \ No newline at end of file +target_link_libraries(LgiLiteHtml PUBLIC lgi litehtml) +if(WIN32) + target_include_directories(LgiLiteHtml + PRIVATE + "c:/Program Files/OpenSSL-Win64/include" + ${CODELIB}/zlib + ${CODELIB}/zlib/build64 + ${CODELIB}/libpng + ${CODELIB}/libpng/build64 + ${CODELIB}/libjpeg-9a) +endif() \ No newline at end of file diff --git a/test/LiteHtml/src/main.cpp b/test/LiteHtml/src/main.cpp --- a/test/LiteHtml/src/main.cpp +++ b/test/LiteHtml/src/main.cpp @@ -1,84 +1,95 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/Edit.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Layout.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Menu.h" #include "lgi/common/LiteHtmlView.h" +#include "lgi/common/Uri.h" const char *AppName = "LgiLiteHtml"; enum Ctrls { IDC_BOX = 100, IDC_LOCATION, IDC_BROWSER, }; class AppLiteHtmlView : public LiteHtmlView { public: AppLiteHtmlView(int id) : LiteHtmlView(id) { } void OnNavigate(LString url) override { GetWindow()->SetCtrlName(IDC_LOCATION, url); LiteHtmlView::OnNavigate(url); } }; class App : public LWindow { LBox *box = NULL; LEdit *location = NULL; LiteHtmlView *browser = NULL; public: App() { LRect r(200, 200, 1400, 1000); SetPos(r); Name(AppName); MoveToCenter(); SetQuitOnClose(true); if (Attach(0)) { AddView(box = new LBox(IDC_BOX, true)); box->AddView(location = new LEdit(IDC_LOCATION, 0, 0, 100, 20)); box->AddView(browser = new AppLiteHtmlView(IDC_BROWSER)); box->Value(LSysFont->GetHeight() + 8); AttachChildren(); location->Focus(true); Visible(true); } } void SetUrl(LString s) { if (location) location->Name(s); if (browser) browser->SetUrl(s); } void OnReceiveFiles(LArray &Files) { SetUrl(Files[0]); } }; int LgiMain(OsAppArguments &AppArgs) { LApp app(AppArgs, "application/x-lgi-litehtml"); if (app.IsOk()) { - app.AppWnd = new App; + auto result = LUri::UnitTests(); + + App *a = new App; + app.AppWnd = a; + + LString cmdLine(AppArgs.lpCmdLine); + LUri u(cmdLine); + if (u.IsProtocol("http") || + u.IsProtocol("https")) + a->SetUrl(cmdLine); + app.Run(); } return 0; }