/* XOWA: the XOWA Offline Wiki Application Copyright (C) 2012 gnosygnu@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package gplx.gfui; import gplx.core.envs.Env_; import gplx.core.primitives.*; import gplx.core.threads.Thread_adp_; import java.security.acl.Owner; import gplx.*; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.widgets.*; import java.security.acl.Owner; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.widgets.*; class Swt_html implements Gxw_html, Swt_control, FocusListener { private Swt_html_lnr_location lnr_location; private Swt_html_lnr_status lnr_status; public Swt_html(Swt_kit kit, Swt_control owner_control, KeyValHash ctorArgs) { this.kit = kit; lnr_location = new Swt_html_lnr_location(this); lnr_status = new Swt_html_lnr_status(this); Object browser_tid_obj = ctorArgs.FetchValOr(Swt_kit.Cfg_Html_BrowserType, null); this.browser_tid = browser_tid_obj == null ? Browser_tid_none : Int_.cast(browser_tid_obj); browser = new Browser(owner_control.Under_composite(), browser_tid); core = new Swt_core_cmds_html(this, browser); browser.addKeyListener(new Swt_lnr_key(this)); browser.addMouseListener(new Swt_html_lnr_mouse(this, browser, kit)); browser.addLocationListener(lnr_location); browser.addProgressListener(new Swt_html_lnr_progress(this)); browser.addStatusTextListener(lnr_status); browser.addFocusListener(this); browser.addTitleListener(new Swt_html_lnr_title(this)); // browser.addTraverseListener(new Swt_html_lnr_Traverse(this)); } public Swt_kit Kit() {return kit;} private Swt_kit kit; @Override public Control Under_control() {return browser;} private Browser browser; @Override public Composite Under_composite() {return null;} @Override public Control Under_menu_control() {return browser;} public int Browser_tid() {return browser_tid;} private final int browser_tid; public String Load_by_url_path() {return load_by_url_path;} private String load_by_url_path; public void Html_doc_html_load_by_mem(String html) { this.html_doc_html_load_tid = Gxw_html_load_tid_.Tid_mem; this.load_by_url_path = null; browser.setText(html); // DBG: Io_mgr.I.SaveFilStr(Io_url_.new_fil_("C:\\temp.txt"), s) } public void Html_doc_html_load_by_url(Io_url path, String html) { this.html_doc_html_load_tid = Gxw_html_load_tid_.Tid_url; this.load_by_url_path = path.To_http_file_str(); Io_mgr.Instance.SaveFilStr(path, html); browser.setUrl(path.Xto_api()); } public byte Html_doc_html_load_tid() {return html_doc_html_load_tid;} private byte html_doc_html_load_tid; public void Html_doc_html_load_tid_(byte v) {html_doc_html_load_tid = v;} public void Html_js_enabled_(boolean v) {browser.setJavascriptEnabled(v);} public void Html_js_cbks_add(String func_name, GfoInvkAble invk) {new Swt_html_func(browser, func_name, invk);} public String Html_js_eval_script(String script) {return Eval_script_as_str(script);} public boolean Html_js_eval_proc_as_bool(String proc, Object... args) {return Bool_.cast(Html_js_eval_proc_as_obj(proc, args));} public String Html_js_eval_proc_as_str(String proc, Object... args) {return Object_.Xto_str_strict_or_null(Html_js_eval_proc_as_obj(proc, args));} private Object Html_js_eval_proc_as_obj(String proc, Object... args) { Bry_bfr bfr = Bry_bfr.new_(); bfr.Add_str_a7("return ").Add_str_u8(proc).Add_byte(Byte_ascii.Paren_bgn); int args_len = args.length; for (int i = 0; i < args_len; ++i) { Object arg = args[i]; if (i != 0) bfr.Add_byte(Byte_ascii.Comma); boolean quote_val = true; if ( Type_adp_.Eq_typeSafe(arg, Bool_.Cls_ref_type) || Type_adp_.Eq_typeSafe(arg, Int_.Cls_ref_type) || Type_adp_.Eq_typeSafe(arg, Long_.Cls_ref_type) ) { quote_val = false; } if (quote_val) bfr.Add_byte(Byte_ascii.Apos); if (quote_val) bfr.Add_str_u8(Escape_quote(Object_.Xto_str_strict_or_null_mark(arg))); else bfr.Add_obj_strict(arg); if (quote_val) bfr.Add_byte(Byte_ascii.Apos); } bfr.Add_byte(Byte_ascii.Paren_end).Add_byte(Byte_ascii.Semic); return Eval_script(bfr.To_str_and_clear()); } private static String Escape_quote(String v) { String rv = v; rv = String_.Replace(rv, "'", "\\'"); rv = String_.Replace(rv, "\"", "\\\""); rv = String_.Replace(rv, "\n", "\\n"); return rv; } public void Html_invk_src_(GfoEvObj invk) {lnr_location.Host_set(invk); lnr_status.Host_set(invk);} public void Html_dispose() { browser.dispose(); delete_owner.SubElems().DelOrFail(delete_cur); // NOTE: must delete cur from owner, else new tab will fail after closing one; DATE:2014-07-09 Env_.GarbageCollect(); } private GfuiElem delete_owner, delete_cur; public void Delete_elems_(GfuiElem delete_owner, GfuiElem delete_cur) {this.delete_owner = delete_owner; this.delete_cur = delete_cur;} // HACK: set owner / cur so delete can work; @Override public GxwCore_base Core() {return core;} private GxwCore_base core; @Override public GxwCbkHost Host() {return host;} @Override public void Host_set(GxwCbkHost host) {this.host = host;} GxwCbkHost host; @Override public String TextVal() {return browser.getText();} @Override public void TextVal_set(String v) {browser.setText(v);} @Override public void EnableDoubleBuffering() {} @Override public Object Invk(GfsCtx ctx, int ikey, String k, GfoMsg m) {return GfoInvkAble_.Rv_unhandled;} private String Eval_script_as_str(String script) {return (String)Eval_script(script);} public Object Eval_script(String script) { eval_rslt.Clear(); try { eval_rslt.Result_set(browser.evaluate(script)); return eval_rslt.Result(); } catch (Exception e) {eval_rslt.Error_set(e.getMessage()); return eval_rslt.Error();} } private Swt_html_eval_rslt eval_rslt = new Swt_html_eval_rslt(); @Override public void focusGained(FocusEvent arg0) {} @Override public void focusLost(FocusEvent arg0) {} public static final int Browser_tid_none = SWT.NONE , Browser_tid_mozilla = SWT.MOZILLA , Browser_tid_webkit = SWT.WEBKIT ; } class Swt_core_cmds_html extends Swt_core_cmds { public Swt_core_cmds_html(Swt_html html_box, Control control) {super(control);} @Override public void Focus() { if (Focus_able()) control.forceFocus(); } @Override public void Select_exec() { this.Focus(); } } class Swt_html_lnr_traverse implements TraverseListener { public Swt_html_lnr_traverse(Swt_html html_box) {} @Override public void keyTraversed(TraverseEvent arg0) {} } class Swt_html_lnr_title implements TitleListener { private Swt_html html_box; public Swt_html_lnr_title(Swt_html html_box) {this.html_box = html_box;} @Override public void changed(TitleEvent ev) { try {UsrDlg_.Instance.Note(ev.title);} catch (Exception e) {html_box.Kit().Ask_ok("xowa.swt.html_box", "title.fail", Err_.Message_gplx_full(e));} // NOTE: must catch error or will cause app to lock; currently called inside displaySync } } class Swt_html_func extends BrowserFunction { private GfoInvkAble invk; public Swt_html_func(Browser browser, String name, GfoInvkAble invk) { super (browser, name); this.invk = invk; } public Object function (Object[] args) { try { return gplx.gfui.Gfui_html.Js_args_exec(invk, args); } catch (Exception e) { return Err_.Message_gplx_full(e); } } } class Swt_html_lnr_status implements StatusTextListener { public Swt_html_lnr_status(Swt_html html_box) {this.html_box = html_box;} private Swt_html html_box; public void Host_set(GfoEvObj host) {this.host = host;} GfoEvObj host; @Override public void changed(StatusTextEvent ev) { if (html_box.Kit().Kit_mode__term()) return; // shutting down raises status changed events; ignore, else SWT exception thrown; DATE:2014-05-29 String ev_text = ev.text; String load_by_url_path = html_box.Load_by_url_path(); if (load_by_url_path != null) ev_text = String_.Replace(ev_text, load_by_url_path, ""); // remove "C:/xowa/tab_1.html" // if (String_.Has(ev_text, "Loading [MathJax]")) return; // suppress MathJax messages; // NOTE: disabled for 2.1 (which no longer outputs messages to status); DATE:2013-05-03 try {if (host != null) GfoEvMgr_.PubObj(host, Gfui_html.Evt_link_hover, "v", ev_text);} catch (Exception e) {html_box.Kit().Ask_ok("xowa.gui.html_box", "status.fail", Err_.Message_gplx_full(e));} // NOTE: must catch error or will cause app to lock; currently called inside displaySync } } class Swt_html_lnr_progress implements ProgressListener { public Swt_html_lnr_progress(Swt_html html_box) {} @Override public void changed(ProgressEvent arg0) {} @Override public void completed(ProgressEvent arg0) { // UsrDlg_._.Note("done"); } } class Swt_html_lnr_location implements LocationListener { public Swt_html_lnr_location(Swt_html html_box) {this.html_box = html_box;} private Swt_html html_box; public void Host_set(GfoEvObj host) {this.host = host;} private GfoEvObj host; @Override public void changed(LocationEvent arg) {Pub_evt(arg, Gfui_html.Evt_location_changed);} @Override public void changing(LocationEvent arg) {Pub_evt(arg, Gfui_html.Evt_location_changing);} private void Pub_evt(LocationEvent arg, String evt) { String location = arg.location; if (String_.Eq(location, "about:blank")) return; // location changing event fires once when page is loaded; ignore if ( html_box.Browser_tid() == Swt_html.Browser_tid_webkit // webkit prefixes "about:blank" to anchors; causes TOC to fail when clicking on links; EX:about:blank#TOC1; DATE:2015-06-09 && String_.Has_at_bgn(location, "about:blank")) { location = String_.Mid(location, 11); // 11 = "about:blank".length } if ( html_box.Html_doc_html_load_tid() == Gxw_html_load_tid_.Tid_url // navigating to file://page.html will fire location event; ignore if url mode && String_.Has_at_bgn(location, "file:") && String_.Has_at_end(location, ".html") ) return; try { GfoEvMgr_.PubObj(host, evt, "v", location); arg.doit = false; // cancel navigation event, else there will be an error when trying to go to invalid location } catch (Exception e) {html_box.Kit().Ask_ok("xowa.gui.html_box", evt, Err_.Message_gplx_full(e));} // NOTE: must catch error or will cause app to lock; currently called inside displaySync } } class Swt_html_lnr_mouse implements MouseListener { private GxwElem elem; private Browser browser; private Swt_kit kit; public Swt_html_lnr_mouse(GxwElem elem, Browser browser, Swt_kit kit) {this.elem = elem; this.browser = browser; this.kit = kit;} @Override public void mouseDown(MouseEvent ev) { if (Is_at_scrollbar_area()) return; elem.Host().MouseDownCbk(XtoMouseData(ev)); } @Override public void mouseUp(MouseEvent ev) { if (Is_at_scrollbar_area()) return; elem.Host().MouseUpCbk(XtoMouseData(ev)); } private boolean Is_at_scrollbar_area() { // WORKAROUND.SWT: SEE:NOTE_1:browser scrollbar and click Point browser_size = browser.getSize(); Point click_pos = kit.Swt_display().getCursorLocation(); return click_pos.x >= browser_size.x - 12; } @Override public void mouseDoubleClick(MouseEvent ev) {} IptEvtDataMouse XtoMouseData(MouseEvent ev) { IptMouseBtn btn = null; switch (ev.button) { case 1: btn = IptMouseBtn_.Left; break; case 2: btn = IptMouseBtn_.Middle; break; case 3: btn = IptMouseBtn_.Right; break; case 4: btn = IptMouseBtn_.X1; break; case 5: btn = IptMouseBtn_.X2; break; } return IptEvtDataMouse.new_(btn, IptMouseWheel_.None, ev.x, ev.y); } } /* NOTE_1:browser scrollbar and click a click in the scrollbar area will raise a mouse-down/mouse-up event in content-editable mode . a click should be consumed by the scrollbar and not have any effect elsewhere on the window . instead, a click event is raised, and counted twice 1) for the scroll bar this will scroll the area. 2) for the window. if keyboard-focus is set on a link, then it will activate the link. swt does not expose any scrollbar information (visible, width), b/c the scrollbar is controlled by the underlying browser so, assume: . scrollbar is always present . scrollbar has arbitrary width (currently 12) . and discard if click is in this scrollbar area two issues still occur with the workaround 1) even if the scrollbar is not present, any click on the right-hand edge of the screen will be ignored 2) click -> hold -> move mouse over to left -> release; the mouse up should be absorbed, but it is not due to position of release */