diff --git a/include/lgi/common/LiteHtmlView.h b/include/lgi/common/LiteHtmlView.h --- a/include/lgi/common/LiteHtmlView.h +++ b/include/lgi/common/LiteHtmlView.h @@ -1,36 +1,38 @@ #pragma once class LiteHtmlView : public LLayout { protected: struct LiteHtmlViewPriv *d; bool LoadCurrent(); public: LiteHtmlView(int id); ~LiteHtmlView(); /// Set a new URL bool SetUrl(LString url); void HistoryBack(); void HistoryForward(); bool Refresh(); // Events: // The current url has been set and the document created, but not fully loaded virtual void OnNavigate(LString url); // The history position has changed and the validity of going forward/back needs to be updated virtual void OnHistory(bool hasBack, bool hasForward) {} + // Set the page name in the title bar of the window. + virtual void SetCaption(LString name) {} // LLayout impl void OnAttach() override; LCursor GetCursor(int x, int y) override; void OnPaint(LSurface *pDC) override; int OnNotify(LViewI *c, LNotification n) override; bool OnMouseWheel(double Lines); void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; LMessage::Result OnEvent(LMessage *Msg) override; }; 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,921 +1,969 @@ #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" #include "lgi/common/Path.h" #include "lgi/common/ClipBoard.h" #undef min #undef max #include "litehtml/html.h" #define NOT_IMPL \ LgiTrace("%s:%i - %s not impl.\n", _FL, __func__); #define NOT_SUPPORT_COLOUR LColour(255, 0, 255) enum Messages { M_LOAD_URI = M_USER + 100, }; enum Ids { ID_COPY_URL = 100, }; 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; LRect clip; bool clipSet = false; // Need to update the clipping region on the DC LString cursorName; LHashTbl, LFont*> fontMap; LHashTbl, Image*> imageCache; // Url history state LString::Array history; int historyPos = 0; LiteHtmlViewPriv(LiteHtmlView *v) : view(v) { } ~LiteHtmlViewPriv() { Empty(); } void Empty() { // Clean up threads... Cancel(true); threads.DeleteObjects(); // Do this before releasing other owned objects, like the fontMap. doc.reset(); // Clean up caches imageCache.DeleteObjects(); fontMap.DeleteObjects(); // Reset to go state... Cancel(false); } LString CurrentUrl() { return history[historyPos]; } LString AbsoluteUrl(LString part) { LString abs; LUri p(part); // Absolute? if (p.sProtocol) return part; // Relative? LUri u(CurrentUrl()); u += part; return u.ToString(); } LColour Convert(const litehtml::web_color &c) const { return LColour(c.red, c.green, c.blue, c.alpha); } LRect Convert(const litehtml::position &p) const { return LRect(p.x, p.y, p.x + p.width - 1, p.y + p.height - 1); } LSurface *Convert(litehtml::uint_ptr hdc) { auto pdc = (LSurface*)hdc; if (clipSet) { if (clip.Valid()) pdc->ClipRgn(&clip); else pdc->ClipRgn(NULL); clipSet = false; } return pdc; } LPointF Convert(const litehtml::pointF &p) const { return LPointF(p.x, p.y); } void UpdateScreen(litehtml::position::vector &redraw) { for (auto &r: redraw) { auto rect = Convert(r); view->Invalidate(&rect); } } 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); auto faceNames = LString(faceName).SplitDelimit(","); printf("create_font('%s', %i, %i, %i, %i)\n", faceName, size, weight, italic, decoration); LFont *fnt = new LFont; fnt->Bold(weight > 400); if (italic == litehtml::font_style_italic) fnt->Italic(true); for (auto face: faceNames) { bool status = fnt->Create(face, LCss::Len(LCss::LenPt, size) ); if (status) break; 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 = ceil(fnt->Ascent()); fm->descent = ceil(fnt->Descent()); fm->x_height = ds.Y(); fm->draw_spaces = false; printf("\tht=%i as=%i de=%i x=%i\n", fm->height, fm->ascent, fm->descent, fm->x_height); } 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) { bool debug = Stricmp(text, "Open") == 0; if (debug) { int asd=0; } auto pDC = Convert(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 LSysFont->PointSize() * 1.3; } 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 = Convert(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; - auto cur = CurrentUrl(); - LUri c(cur); - - LFile::Path p(c.LocalPath()); - p = p / ".." / src; - c.sPath = p.GetFull().Replace(DIR_STR, "/"); + LUri c(CurrentUrl()); + c += src; return c.ToString(); } void load_image(const char *src, const char *baseurl, bool redraw_on_ready) { auto absUri = FullUri(src, baseurl); if (!absUri) return; if (!imageCache.Find(absUri)) { LgiTrace("load_image(%s) %s + %s\n", absUri.Get(), baseurl, src); if (auto i = new Image) { i->uri = absUri; imageCache.Add(absUri, i); LUri u(absUri); if (u.IsFile()) { auto path = u.LocalPath(); LFile in(path, O_READ); i->status = i->img.Reset(GdcD->Load(&in, LGetLeaf(path))); if (i->status && redraw_on_ready) view->Invalidate(); } else { 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); } })); } } } } void get_image_size(const char *src, const char *baseurl, litehtml::size &sz) { 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_image(litehtml::uint_ptr hdc, const litehtml::background_layer& layer, const std::string& url, const std::string& base_url) { auto pDC = Convert(hdc); auto pos = Convert(layer.border_box); auto absUri = FullUri(url.c_str(), base_url.c_str()); if (auto i = imageCache.Find(absUri)) { if (i->img) { auto op = pDC->Op(GDC_ALPHA); pDC->Blt(pos.x1, pos.y1, i->img); pDC->Op(op); return; } } // Draw missing image pDC->Colour(L_HIGH); pDC->Rectangle(&pos); pDC->Colour(LColour::Red); pDC->Line(pos.x1, pos.y1, pos.x2, pos.y2); pDC->Line(pos.x2, pos.y1, pos.x1, pos.y2); } void draw_solid_fill(litehtml::uint_ptr hdc, const litehtml::background_layer& layer, const litehtml::web_color& color) { auto pDC = Convert(hdc); auto pos = Convert(layer.border_box); - pDC->Colour(Convert(color)); - pDC->Rectangle(&pos); + if (hasRadius(layer.border_radius)) + { + LPath path; + LMemDC mem(pos.X(), pos.Y(), System32BitColourSpace); + mem.Colour(0, 32); + mem.Rectangle(); + draw_radius(path, pos.ZeroTranslate(), layer.border_radius); + LSolidBrush brush(Convert(color)); + path.Fill(&mem, brush); + pDC->Op(GDC_ALPHA); + pDC->Blt(pos.x1, pos.y1, &mem); + } + else + { + pDC->Colour(Convert(color)); + pDC->Rectangle(&pos); + } } void draw_linear_gradient(litehtml::uint_ptr hdc, const litehtml::background_layer& layer, const litehtml::background_layer::linear_gradient& gradient) { auto pDC = Convert(hdc); auto pos = Convert(layer.border_box); auto origin = pos.TopLeft(); LMemDC mem(pos.X(), pos.Y(), System32BitColourSpace); LArray stops; for (auto &in: gradient.color_points) { auto &out = stops.New(); out.Pos = in.offset; out.c32 = Convert(in.color).c32(); } LLinearBlendBrush brush(Convert(gradient.start) - origin, Convert(gradient.end) - origin, (int)stops.Length(), stops.AddressOf()); LPath path; - path.Rectangle(0.0, 0.0, mem.X(), mem.Y()); + if (hasRadius(layer.border_radius)) + { + mem.Colour(0, 32); + mem.Rectangle(); + draw_radius(path, pos.ZeroTranslate(), layer.border_radius); + } + else + { + path.Rectangle(0.0, 0.0, mem.X(), mem.Y()); + } + path.Fill(&mem, brush); + pDC->Op(GDC_ALPHA); pDC->Blt(pos.x1, pos.y1, &mem); } void draw_radial_gradient(litehtml::uint_ptr hdc, const litehtml::background_layer& layer, const litehtml::background_layer::radial_gradient& gradient) { auto pDC = Convert(hdc); auto pos = Convert(layer.border_box); pDC->Colour(NOT_SUPPORT_COLOUR); pDC->Rectangle(&pos); } void draw_conic_gradient(litehtml::uint_ptr hdc, const litehtml::background_layer& layer, const litehtml::background_layer::conic_gradient& gradient) { auto pDC = Convert(hdc); auto pos = Convert(layer.border_box); pDC->Colour(NOT_SUPPORT_COLOUR); pDC->Rectangle(&pos); } - /* - void draw_background(litehtml::uint_ptr hdc, const std::vector &background) + bool hasRadius(const litehtml::border_radiuses &r) + { + return r.top_left_x != 0 || + r.top_left_y != 0 || + r.top_right_x != 0 || + r.top_right_y != 0 || + r.bottom_right_x != 0 || + r.bottom_right_y != 0 || + r.bottom_left_x != 0 || + r.bottom_left_y != 0; + } + + void draw_radius(LPath &path, LRect &b, const litehtml::border_radiuses &rad) { - auto pDC = Convert(hdc); - for (auto b: background) - { - auto rc = Convert(b.border_box); - if (!b.image.empty()) - { - 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 if (!b.gradient.is_empty()) - { - if (b.gradient.m_type == litehtml::web_gradient::linear_gradient && - b.gradient.m_colors.size() == 2) - { - LMemDC mem(rc.X(), rc.Y(), System32BitColourSpace); - LBlendStop stops[2] = { - {0.0, Convert(b.gradient.m_colors[0]).c32()}, - {1.0, Convert(b.gradient.m_colors[1]).c32()} - }; - LLinearBlendBrush brush(LPointF(0.0, 0.0), LPointF(0.0, rc.Y()), 2, stops); - LPath path; - path.Rectangle(0.0, 0.0, mem.X(), mem.Y()); - path.Fill(&mem, brush); - pDC->Blt(rc.x1, rc.y1, &mem); - } - else LgiTrace("%s:%i - Invalid gradient.\n", _FL); - } - else - { - pDC->Colour(Convert(b.color)); - pDC->Rectangle(&rc); - } - } + #define K(rad) (0.5522847498 * (rad)) + + path.MoveTo(b.x1 + rad.top_left_x, b.y1); + + LPointF c(b.x2 - rad.top_right_x, b.y1 + rad.top_right_y); + path.LineTo(c.x, b.y1); + path.CubicBezierTo( c.x + K(rad.top_right_x), b.y1, + b.x2, c.y - K(rad.top_right_y), + b.x2, c.y); + + c.Set(b.x2 - rad.bottom_right_x, b.y2 - rad.bottom_right_y); + path.LineTo(b.x2, c.y); + + path.CubicBezierTo( b.x2, c.y + K(rad.bottom_right_y), + c.x + K(rad.bottom_right_x), b.y2, + c.x, b.y2); + + c.Set(b.x1 + rad.bottom_left_x, b.y2 - rad.bottom_left_y); + path.LineTo(c.x, b.y2); + + path.CubicBezierTo( c.x - K(rad.bottom_left_x), b.y2, + b.x1, c.y + K(rad.bottom_left_y), + b.x1, c.y); + + c.Set(b.x1 + rad.top_left_x, b.y1 + rad.top_left_y); + path.LineTo(b.x1, c.y); + + path.CubicBezierTo( b.x1, c.y - K(rad.top_left_y), + c.x - K(rad.top_left_x), b.y1, + c.x, b.y1); } - */ void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders& borders, const litehtml::position& draw_pos, bool root) { auto pDC = Convert(hdc); - auto drawEdge = [&](const litehtml::border &b, int x, int y, int dx, int dy, int ix, int iy) + if (hasRadius(borders.radius)) { - pDC->Colour(Convert(b.color)); - for (int i=0; iOp(GDC_ALPHA); + pDC->Blt(draw_pos.x, draw_pos.y, &mem); + } + else + { + auto drawEdge = [&](const litehtml::border &b, int x, int y, int dx, int dy, int ix, int iy) { - pDC->Line(x, y, x+dx, y+dy); - x += ix; - y += 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); + 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); + view->SetCaption(caption); } void set_base_url(const char* base_url) { history[historyPos] = 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; auto full = FullUri(url, NULL); view->PostEvent(M_LOAD_URI, new LString(full)); } 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) { auto cssUrl = FullUri(url.c_str(), baseurl.c_str()); if (cssUrl) { LUri u(cssUrl); if (u.IsFile()) { auto path = u.LocalPath(); LFile in(path, O_READ); if (in) { text = in.Read().Get(); } else LgiTrace("%s:%i - error: failed to open '%s' for reading.\n", _FL, path.Get()); } else { LStringPipe out; LString err; if (LgiGetUri(this, &out, &err, cssUrl)) { text = out.NewLStr().Get(); } else LgiTrace("%s:%i - error: LgiGetUri(%s)=%s (currentUrl=%s)\n", _FL, cssUrl.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) { clip = Convert(pos); clipSet = true; } void del_clip() { clip.ZOff(-1, -1); clipSet = true; } 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 { media.type = litehtml::media_type_screen; media.width = client.X(); media.height = client.Y(); media.device_width = GdcD->X(); media.device_height = GdcD->Y(); media.color = GdcD->GetBits(); media.color_index = 0; media.monochrome = false; media.resolution = LScreenDpi().x; } 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) { Invalidate(); } void LiteHtmlView::HistoryBack() { if (d->historyPos > 0) { d->historyPos--; LoadCurrent(); } } void LiteHtmlView::HistoryForward() { if (d->historyPos < d->history.Length() - 1) { d->historyPos++; LoadCurrent(); } } bool LiteHtmlView::Refresh() { d->Empty(); Invalidate(); return LoadCurrent(); } bool LiteHtmlView::LoadCurrent() { OnHistory(d->historyPos > 0, d->historyPos < d->history.Length() - 1); // Create the document... auto url = d->CurrentUrl(); LUri u(url); if (!u.sProtocol && LFileExists(url)) { // Rewrite to 'file' url u.sProtocol = "file"; u.sHost.Empty(); u.sPath = url.Replace(DIR_STR, "/"); d->history[d->historyPos] = url = u.ToString(); } if (u.IsProtocol("file")) { auto html_text = LReadFile(u.LocalPath()); if (!html_text) return false; d->client = GetClient(); d->doc = litehtml::document::createFromString(html_text.Get(), d); if (!d->doc) return false; OnNavigate(url); } else { d->threads.Add(new LiteHtmlViewPriv::NetworkThread(this, url, [this, url](auto status, auto data) { if (data) { d->doc = litehtml::document::createFromString(data.Get(), d); OnNavigate(url); Invalidate(); } else LPopupNotification::Message(GetWindow(), LString::Fmt("No data for '%s'", url.Get())); })); } return true; } bool LiteHtmlView::SetUrl(LString url) { d->Empty(); // Update history and current doc if (d->history.Length()) d->historyPos++; d->history.Length(d->historyPos); d->history[d->historyPos] = url; return LoadCurrent(); } 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); } bool LiteHtmlView::OnMouseWheel(double Lines) { if (!VScroll) return false; VScroll->Value(VScroll->Value() + (Lines * LSysFont->GetHeight())); return true; } void LiteHtmlView::OnMouseClick(LMouse &m) { if (!d->doc) return; int64_t sx, sy; GetScrollPos(sx, sy); litehtml::position::vector redraw_boxes; LString lnk; if (d->doc) { if (auto e = d->doc->get_over_element()) { auto tag = e->get_tagName(); if (!Stricmp(tag, "a")) lnk = e->get_attr("href"); } } if (m.IsContextMenu()) { LSubMenu sub; sub.AppendItem("Copy link", ID_COPY_URL, !lnk.IsEmpty()); switch (sub.Float(this, m)) { case ID_COPY_URL: { LClipBoard c(this); auto abs = d->AbsoluteUrl(lnk); c.Text(abs); break; } } } 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); } else if (m.Button1()) { if (m.Down()) HistoryBack(); } else if (m.Button2()) { if (m.Down()) HistoryForward(); } 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); } LMessage::Result LiteHtmlView::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_LOAD_URI: { auto url = Msg->AutoA(); if (url) SetUrl(*url); break; } } return LLayout::OnEvent(Msg); } 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,442 +1,441 @@ #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) { 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 (!s) return *this; if (*s == '/') sPath.Empty(); // reset - bool isFolder = sPath ? sPath(-1) : false; - auto parts = sPath.SplitDelimit("/\\"); + auto lastSep = sPath.RFind("/"); + auto path = lastSep >= 0 ? sPath(0, lastSep) : sPath; + auto parts = path.SplitDelimit("/\\"); parts.SetFixedLength(false); - if (parts.Length() > 0 && !isFolder) - parts.PopLast(); for (auto p: LString(s).SplitDelimit("/\\")) { if (p.Equals("..")) parts.PopLast(); else if (p.Equals(".")) ; else parts.Add(p); } sPath = LString(IsFile() ? DIR_STR : "/").Join(parts); return *this; } LUri &LUri::operator =(const LUri &u) { Empty(); sProtocol = u.sProtocol; sUser = u.sUser; sPass = u.sPass; sHost = u.sHost; sPath = u.sPath; sAnchor = u.sAnchor; Port = u.Port; return *this; } void LUri::Empty() { Port = 0; sProtocol.Empty(); sUser.Empty(); sPass.Empty(); sHost.Empty(); sPath.Empty(); sAnchor.Empty(); } LUri::operator bool() { return IsFile() ? !sPath.IsEmpty() : !sHost.IsEmpty(); } LString LUri::LocalPath() { if (!IsFile()) return LString(); #ifdef WINDOWS if (sPath.Length() > 0 && sPath(0) == '/') return sPath(1, -1).Replace("/", DIR_STR); #endif return sPath.Replace("/", DIR_STR); } LString LUri::ToString() { LStringPipe p; if (sProtocol) p.Print("%s://", sProtocol.Get()); if (sUser || sPass) { auto UserEnc = EncodeStr(sUser, "@:"); auto PassEnc = EncodeStr(sPass, "@:"); p.Print("%s:%s@", UserEnc?UserEnc.Get():"", PassEnc?PassEnc.Get():""); } if (sHost) p.Write(sHost); if (Port) p.Print(":%i", Port); if (sPath) { auto e = EncodeStr(sPath); char *s = e ? e : sPath; p.Print("%s%s", *s == '/' ? "" : "/", s); } if (sAnchor) p.Print("#%s", sAnchor.Get()); return p.NewLStr(); } bool LUri::Set(const char *uri) { if (!uri) return false; Empty(); const char *s = uri; SkipWs(s); // Scan ahead and check for protocol... const char *hasProto = NULL; const char *hasAt = NULL; const char *hasPath = NULL; const char *hasColon = NULL; for (auto c = s; *c; c++) { 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... } } if (hasProto) { sProtocol.Set(s, hasProto - s); s = hasProto + 3; } if (hasAt) { 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 = (int)p[1].Int(); } else if (p.Length() == 1) { sHost = p[0]; } } else { hasPath = s; } if (hasPath) { sPath = hasPath; } if (sPath) { auto anchor = sPath.Find("#"); if (anchor >= 0) { sAnchor = sPath(anchor, -1); sPath.Length(anchor); } } 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/src/main.cpp b/test/LiteHtml/src/main.cpp --- a/test/LiteHtml/src/main.cpp +++ b/test/LiteHtml/src/main.cpp @@ -1,148 +1,153 @@ #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" #include "lgi/common/Button.h" const char *AppName = "LgiLiteHtml"; enum Ctrls { IDC_BOX = 100, IDC_CTRLS, IDC_LOCATION, IDC_BROWSER, IDC_BACK, IDC_FORWARD, IDC_REFRESH, }; class AppLiteHtmlView : public LiteHtmlView { public: AppLiteHtmlView(int id) : LiteHtmlView(id) { } + void SetCaption(LString name) override + { + GetWindow()->Name(LString::Fmt("%s - %s", AppName, name.Get())); + } + void OnNavigate(LString url) override { GetWindow()->SetCtrlName(IDC_LOCATION, url); LiteHtmlView::OnNavigate(url); } void OnHistory(bool hasBack, bool hasForward) { auto wnd = GetWindow(); wnd->SetCtrlEnabled(IDC_BACK, hasBack); wnd->SetCtrlEnabled(IDC_FORWARD, hasForward); } }; class App : public LWindow { LBox *box = NULL; LBox *ctrls = NULL; LEdit *location = NULL; LiteHtmlView *browser = NULL; LButton *back = NULL; LButton *forward = NULL; LButton *refresh = 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(ctrls = new LBox(IDC_CTRLS, false)); ctrls->AddView(back = new LButton(IDC_BACK, 0, 0, -1, -1, "<")); back->GetCss(true)->Width("2em"); back->Enabled(false); ctrls->AddView(forward = new LButton(IDC_FORWARD, 0, 0, -1, -1, ">")); forward->GetCss(true)->Width("2em"); forward->Enabled(false); ctrls->AddView(refresh = new LButton(IDC_REFRESH, 0, 0, -1, -1, "\xE2\x9F\xB3")); refresh->GetCss(true)->Width("2em"); ctrls->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 (browser) browser->SetUrl(s); } void OnReceiveFiles(LArray &Files) { SetUrl(Files[0]); } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BACK: browser->HistoryBack(); break; case IDC_FORWARD: browser->HistoryForward(); break; case IDC_REFRESH: browser->Refresh(); break; case IDC_LOCATION: if (n.Type == LNotifyReturnKey) browser->SetUrl(Ctrl->Name()); break; } return LWindow::OnNotify(Ctrl, n); } }; int LgiMain(OsAppArguments &AppArgs) { LApp app(AppArgs, "application/x-lgi-litehtml"); if (app.IsOk()) { auto result = LUri::UnitTests(); App *a = new App; app.AppWnd = a; #ifdef WINDOWS LString cmdLine(AppArgs.lpCmdLine); #else LString cmdLine; if (AppArgs.Args > 1) cmdLine = AppArgs.Arg[1]; #endif LUri u(cmdLine); if (u.IsProtocol("http") || u.IsProtocol("https")) a->SetUrl(cmdLine.Strip()); app.Run(); } return 0; }