diff --git a/doc/WhatsNew.rst b/doc/WhatsNew.rst index 314d37e17..5a138bcd3 100644 --- a/doc/WhatsNew.rst +++ b/doc/WhatsNew.rst @@ -46,6 +46,9 @@ Ver 5.0.0 (unreleased) - Added PluginConfig plugin; allows configuration of all Ginga plugins graphically; can enable/disable, change menu categories, etc. - Removed --profile and --debug command-line options +- Fix a number of issues with the help system to make it simpler and + more robust; removed WBrowser plugin, handle URLs with Python webbrowser + module Ver 4.1.0 (2022-06-30) ====================== diff --git a/doc/manual/plugins.rst b/doc/manual/plugins.rst index d514db948..28f50e05d 100644 --- a/doc/manual/plugins.rst +++ b/doc/manual/plugins.rst @@ -40,7 +40,6 @@ Global plugins plugins_global/colorbar plugins_global/cursor plugins_global/operations - plugins_global/wbrowser plugins_global/fbrowser plugins_global/colormappicker plugins_global/errors diff --git a/doc/manual/plugins_global/wbrowser.rst b/doc/manual/plugins_global/wbrowser.rst deleted file mode 100644 index 9bd6b8267..000000000 --- a/doc/manual/plugins_global/wbrowser.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _plugins-wbrowser: - -WBrowser -======== - -.. automodapi:: ginga.rv.plugins.WBrowser - :no-heading: - :skip: WBrowser diff --git a/ginga/GingaPlugin.py b/ginga/GingaPlugin.py index c90fc41d0..9e77253e1 100644 --- a/ginga/GingaPlugin.py +++ b/ginga/GingaPlugin.py @@ -44,7 +44,7 @@ def stop(self): """This method is called to stop the plugin.""" pass - def _help_docstring(self): + def _get_docstring(self): import inspect # Insert section title at the beginning @@ -52,22 +52,15 @@ def _help_docstring(self): plg_mod = inspect.getmodule(self) plg_doc = ('{}\n{}\n'.format(plg_name, '=' * len(plg_name)) + plg_mod.__doc__) + return plg_name, plg_doc + def _help_docstring(self): + plg_name, plg_doc = self._get_docstring() self.fv.help_text(plg_name, plg_doc, text_kind='rst', trim_pfx=4) - def help(self): + def help(self, text_kind='rst'): """Display help for the plugin.""" - if not self.fv.gpmon.has_plugin('WBrowser'): - self._help_docstring() - return - - self.fv.start_global_plugin('WBrowser') - - # need to let GUI finish processing, it seems - self.fv.update_pending() - - obj = self.fv.gpmon.get_plugin('WBrowser') - obj.show_help(plugin=self, no_url_callback=self._help_docstring) + self.fv.help_plugin(self, text_kind=text_kind) class GlobalPlugin(BasePlugin): diff --git a/ginga/doc/download_doc.py b/ginga/doc/download_doc.py index 35ae82ebf..bd818569f 100644 --- a/ginga/doc/download_doc.py +++ b/ginga/doc/download_doc.py @@ -1,180 +1,45 @@ -"""Download rendered HTML doc from RTD.""" -import os -import shutil -import zipfile -import urllib +"""Tools for accessing HTML doc from RTD.""" -from astropy.utils import minversion -from astropy.utils.data import get_pkg_data_path +import re -from ginga import toolkit +from ginga.GingaPlugin import GlobalPlugin, LocalPlugin +import ginga -__all__ = ['get_doc'] +__all__ = ['get_online_docs_url'] +# base of our online documentation +rtd_base_url = "https://ginga.readthedocs.io/en/" -def _find_rtd_version(): - """Find closest RTD doc version.""" - vstr = 'latest' - try: - import ginga - from bs4 import BeautifulSoup - except ImportError: - return vstr - # No active doc build before this release, just use latest. - if not minversion(ginga, '2.6.0'): - return vstr - - # Get RTD download listing. - url = 'https://readthedocs.org/projects/ginga/downloads/' - with urllib.request.urlopen(url) as r: # nosec - soup = BeautifulSoup(r, 'html.parser') - - # Compile a list of available HTML doc versions for download. - all_rtd_vernums = [] - for link in soup.find_all('a'): - href = link.get('href') - if 'htmlzip' not in href: - continue - s = href.split('/')[-2] - if s.startswith('v'): # Ignore latest and stable - all_rtd_vernums.append(s) - all_rtd_vernums.sort(reverse=True) - - # Find closest match. - ginga_ver = ginga.__version__ - for rtd_ver in all_rtd_vernums: - if ginga_ver > rtd_ver[1:]: # Ignore "v" in comparison - break - else: - vstr = rtd_ver - - return vstr - - -def _download_rtd_zip(rtd_version=None, **kwargs): +def get_online_docs_url(plugin=None): """ - Download and extract HTML ZIP from RTD to installed doc data path. - Download is skipped if content already exists. + Return URL to online documentation closest to this Ginga version. Parameters ---------- - rtd_version : str or `None` - RTD version to download; e.g., "latest", "stable", or "v2.6.0". - If not given, download closest match to software version. - - kwargs : dict - Keywords for ``urlretrieve()``. - - Returns - ------- - index_html : str - Path to local "index.html". - - """ - # https://github.com/ejeschke/ginga/pull/451#issuecomment-298403134 - if not toolkit.family.startswith('qt'): - raise ValueError('Downloaded documentation not compatible with {} ' - 'UI toolkit browser'.format(toolkit.family)) - - if rtd_version is None: - rtd_version = _find_rtd_version() - - data_path = os.path.dirname( - get_pkg_data_path('help.html', package='ginga.doc')) - index_html = os.path.join(data_path, 'index.html') - - # There is a previous download of documentation; Do nothing. - # There is no check if downloaded version is outdated; The idea is that - # this folder would be empty again when installing new version. - if os.path.isfile(index_html): - return index_html - - url = ('https://readthedocs.org/projects/ginga/downloads/htmlzip/' - '{}/'.format(rtd_version)) - local_path = urllib.request.urlretrieve(url, **kwargs)[0] # nosec - - with zipfile.ZipFile(local_path, 'r') as zf: - zf.extractall(data_path) - - # RTD makes an undesirable sub-directory, so move everything there - # up one level and delete it. - subdir = os.path.join(data_path, 'ginga-{}'.format(rtd_version)) - for s in os.listdir(subdir): - src = os.path.join(subdir, s) - if os.path.isfile(src): - shutil.copy(src, data_path) - else: # directory - shutil.copytree(src, os.path.join(data_path, s)) - shutil.rmtree(subdir) - - if not os.path.isfile(index_html): - raise OSError( - '{} is missing; Ginga doc download failed'.format(index_html)) - - return index_html - - -def get_doc(logger=None, plugin=None, reporthook=None): - """ - Return URL to documentation. Attempt download if does not exist. - - Parameters - ---------- - logger : obj or `None` - Ginga logger. - plugin : obj or `None` Plugin object. If given, URL points to plugin doc directly. If this function is called from within plugin class, pass ``self`` here. - reporthook : callable or `None` - Report hook for ``urlretrieve()``. - Returns ------- - url : str or `None` - URL to local documentation, if available. + url : str + URL to online documentation (top-level, if plugin == None). """ - from ginga.GingaPlugin import GlobalPlugin, LocalPlugin - - if isinstance(plugin, GlobalPlugin): - plugin_page = 'plugins_global' - plugin_name = str(plugin) - elif isinstance(plugin, LocalPlugin): - plugin_page = 'plugins_local' - plugin_name = str(plugin) - else: - plugin_page = None - plugin_name = None - - try: - index_html = _download_rtd_zip(reporthook=reporthook) - - # Download failed, use online resource - except Exception as e: - url = 'https://ginga.readthedocs.io/en/latest/' - - if plugin_name is not None: - if toolkit.family.startswith('qt'): - # This displays plugin docstring. - url = None - else: - # This redirects to online doc. - url += 'manual/{}/{}.html'.format(plugin_page, plugin_name) - - if logger is not None: - logger.error(str(e)) - - # Use local resource + ginga_ver = ginga.__version__ + if re.match(r'^v\d+\.\d+\.\d+$', ginga_ver): + rtd_version = ginga_ver else: - pfx = 'file:' - url = '{}{}'.format(pfx, index_html) - - # https://github.com/rtfd/readthedocs.org/issues/2803 - if plugin_name is not None: - url += '#{}'.format(plugin_name) + # default to latest + rtd_version = 'latest' + url = f"{rtd_base_url}{rtd_version}" + if plugin is not None: + plugin_name = str(plugin) + if isinstance(plugin, GlobalPlugin): + url += f'/manual/plugins_global/{plugin_name}.html' + elif isinstance(plugin, LocalPlugin): + url += f'/manual/plugins_local/{plugin_name}.html' return url diff --git a/ginga/examples/configs/plugin_WBrowser.cfg b/ginga/examples/configs/plugin_WBrowser.cfg deleted file mode 100644 index 1a233d1cf..000000000 --- a/ginga/examples/configs/plugin_WBrowser.cfg +++ /dev/null @@ -1,7 +0,0 @@ -# -# WBrowser plugin preferences file -# -# Place this in file under ~/.ginga with the name "plugin_WBrowser.cfg" - -# Set to True to force offline doc browsing, otherwise download from RTD -offline_doc_only = False diff --git a/ginga/gtk3w/Widgets.py b/ginga/gtk3w/Widgets.py index 4c0f0dd33..016d9ae2a 100644 --- a/ginga/gtk3w/Widgets.py +++ b/ginga/gtk3w/Widgets.py @@ -18,33 +18,16 @@ from gi.repository import GObject from gi.repository import GdkPixbuf -import gi -has_webkit = False - -try: - # this is necessary to prevent a warning message on import - gi.require_version('WebKit2', '4.0') - - from gi.repository import WebKit2 as WebKit # noqa - has_webkit = True -except Exception: - try: - gi.require_version('WebKit', '3.0') - from gi.repository import WebKit # noqa - except Exception: - pass - __all__ = ['WidgetError', 'WidgetBase', 'TextEntry', 'TextEntrySet', 'TextArea', 'Label', 'Button', 'ComboBox', 'SpinBox', 'Slider', 'Dial', 'ScrollBar', 'CheckBox', 'ToggleButton', 'RadioButton', 'Image', 'ProgressBar', 'StatusBar', 'TreeView', - 'WebView', 'ContainerBase', 'Box', 'HBox', 'VBox', 'Frame', + 'ContainerBase', 'Box', 'HBox', 'VBox', 'Frame', 'Expander', 'TabWidget', 'StackWidget', 'MDIWidget', 'ScrollArea', 'Splitter', 'GridBox', 'Toolbar', 'MenuAction', 'Menu', 'Menubar', 'TopLevelMixin', 'TopLevel', 'Application', 'Dialog', 'SaveDialog', 'DragPackage', 'WidgetMoveEvent', - 'name_mangle', 'make_widget', 'hadjust', 'build_info', 'wrap', - 'has_webkit'] + 'name_mangle', 'make_widget', 'hadjust', 'build_info', 'wrap'] # path to our icons icondir = os.path.split(ginga.icons.__file__)[0] @@ -1161,33 +1144,6 @@ def _start_drag(self, treeview, context, selection, drag_pkg.start_drag() -class WebView(WidgetBase): - def __init__(self): - if not has_webkit: - raise NotImplementedError("Missing webkit") - - super(WebView, self).__init__() - self.widget = WebKit.WebView() - - def load_url(self, url): - self.widget.open(url) - - def load_html_string(self, html_string): - self.widget.load_string(html_string, 'text/html', 'utf-8', 'file://') - - def go_back(self): - self.widget.go_back() - - def go_forward(self): - self.widget.go_forward() - - def reload_page(self): - self.widget.reload() - - def stop_loading(self): - self.widget.stop_loading() - - # CONTAINERS class ContainerBase(WidgetBase): @@ -2457,18 +2413,28 @@ class Dialog(TopLevelMixin, WidgetBase): def __init__(self, title='', flags=0, buttons=[], parent=None, modal=False): WidgetBase.__init__(self) + self.buttons = [] if parent is not None: self.parent = parent.get_widget() else: self.parent = None - button_list = [] + self.widget = Gtk.Dialog(title=title, flags=flags) + btn_box = Gtk.ButtonBox() + btn_box.set_border_width(5) + btn_box.set_spacing(4) + for name, val in buttons: - button_list.extend([name, val]) + btn = Button(name) + self.buttons.append(btn) + + def cb(val): + return lambda w: self._cb_redirect(val) + + btn.add_callback('activated', cb(val)) + btn_box.pack_start(btn.get_widget(), True, True, 0) - self.widget = Gtk.Dialog(title=title, flags=flags, - buttons=tuple(button_list)) self.widget.set_modal(modal) TopLevelMixin.__init__(self, title=title) @@ -2477,12 +2443,13 @@ def __init__(self, title='', flags=0, buttons=[], self.content.set_border_width(0) content = self.widget.get_content_area() content.pack_start(self.content.get_widget(), True, True, 0) + content.pack_end(btn_box, True, True, 0) - self.widget.connect("response", self._cb_redirect) + #self.widget.connect("response", self._cb_redirect) self.enable_callback('activated') - def _cb_redirect(self, w, val): + def _cb_redirect(self, val): self.make_callback('activated', val) def get_content_area(self): diff --git a/ginga/qtw/QtHelp.py b/ginga/qtw/QtHelp.py index c8f6044a7..17052daed 100644 --- a/ginga/qtw/QtHelp.py +++ b/ginga/qtw/QtHelp.py @@ -49,10 +49,6 @@ from qtpy.QtCore import QItemSelectionModel # noqa from qtpy.QtWidgets import QApplication # noqa from qtpy import QtSvg # noqa - try: - from qtpy.QtWebEngineWidgets import QWebEngineView as QWebView # noqa - except ImportError as e: - pass # Let's see what qtpy configured for us... from qtpy import PYQT5, PYQT6, PYSIDE2, PYSIDE6 @@ -524,7 +520,7 @@ def set_default_opengl_context(): fmt.setVersion(req.major, req.minor) fmt.setProfile(QSurfaceFormat.CoreProfile) fmt.setDefaultFormat(fmt) - QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts) + QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts, True) def delete_widget(w): diff --git a/ginga/qtw/Widgets.py b/ginga/qtw/Widgets.py index 21a9f9c90..361b9395d 100644 --- a/ginga/qtw/Widgets.py +++ b/ginga/qtw/Widgets.py @@ -15,24 +15,16 @@ from ginga.misc import Callback, Bunch, Settings, LineHistory from ginga.util.paths import icondir -has_webkit = False -try: - from ginga.qtw.QtHelp import QWebView # noqa - has_webkit = True -except ImportError: - pass - __all__ = ['WidgetError', 'WidgetBase', 'TextEntry', 'TextEntrySet', 'GrowingTextEdit', 'TextArea', 'Label', 'Button', 'ComboBox', 'SpinBox', 'Slider', 'Dial', 'ScrollBar', 'CheckBox', 'ToggleButton', 'RadioButton', 'Image', 'ProgressBar', 'StatusBar', 'TreeView', - 'WebView', 'ContainerBase', 'Box', 'HBox', 'VBox', 'Frame', + 'ContainerBase', 'Box', 'HBox', 'VBox', 'Frame', 'Expander', 'TabWidget', 'StackWidget', 'MDIWidget', 'ScrollArea', 'Splitter', 'GridBox', 'ToolbarAction', 'Toolbar', 'MenuAction', 'Menu', 'Menubar', 'TopLevelMixin', 'TopLevel', 'Application', 'Dialog', 'SaveDialog', 'DragPackage', - 'name_mangle', 'make_widget', 'hadjust', 'build_info', 'wrap', - 'has_webkit'] + 'name_mangle', 'make_widget', 'hadjust', 'build_info', 'wrap'] class WidgetError(Exception): @@ -1033,33 +1025,6 @@ def _start_drag(self, event): drag_pkg.start_drag() -class WebView(WidgetBase): - def __init__(self): - if not has_webkit: - raise NotImplementedError("Missing webkit") - - super(WebView, self).__init__() - self.widget = QWebView() - - def load_url(self, url): - self.widget.load(QtCore.QUrl(url)) - - def load_html_string(self, html_string): - self.widget.setHtml(html_string) - - def go_back(self): - self.widget.back() - - def go_forward(self): - self.widget.forward() - - def reload_page(self): - self.widget.reload() - - def stop_loading(self): - self.widget.stop() - - # CONTAINERS class ContainerBase(WidgetBase): @@ -2197,6 +2162,7 @@ def __init__(self, title='', flags=None, buttons=[], parent = parent.get_widget() self.widget = QtGui.QDialog(parent) self.widget.setModal(modal) + self.buttons = [] vbox = QtGui.QVBoxLayout() vbox.setContentsMargins(0, 0, 0, 0) @@ -2210,16 +2176,19 @@ def __init__(self, title='', flags=None, buttons=[], if len(buttons) > 0: hbox_w = QtGui.QWidget() hbox = QtGui.QHBoxLayout() + hbox.setContentsMargins(5, 5, 5, 5) + hbox.setSpacing(4) hbox_w.setLayout(hbox) for name, val in buttons: - btn = QtGui.QPushButton(name) + btn = Button(name) + self.buttons.append(btn) def cb(val): - return lambda: self._cb_redirect(val) + return lambda w: self._cb_redirect(val) - btn.clicked.connect(cb(val)) - hbox.addWidget(btn, stretch=0) + btn.add_callback('activated', cb(val)) + hbox.addWidget(btn.get_widget(), stretch=1) vbox.addWidget(hbox_w, stretch=0) # self.widget.closeEvent = lambda event: self.delete() diff --git a/ginga/rv/Control.py b/ginga/rv/Control.py index c7277ef69..2a74baade 100644 --- a/ginga/rv/Control.py +++ b/ginga/rv/Control.py @@ -17,6 +17,7 @@ import inspect import warnings from collections import OrderedDict +import webbrowser # Local application imports from ginga import cmap, imap @@ -25,6 +26,7 @@ from ginga.util import viewer as gviewer from ginga.canvas.CanvasObject import drawCatalog from ginga.modes import modeinfo +from ginga.doc import download_doc # GUI imports from ginga.gw import GwHelp, GwMain, PluginManager @@ -39,13 +41,6 @@ from ginga.rv.Channel import Channel from ginga.rv.rvmode import RVMode -have_docutils = False -try: - from docutils.core import publish_string - have_docutils = True -except ImportError: - pass - pluginconfpfx = None @@ -180,6 +175,7 @@ def __init__(self, logger, thread_pool, module_manager, preferences, # fullscreen viewer and top-level widget self.fs_viewer = None self.w.fscreen = None + self._help = Bunch.Bunch(remember_choice=False, choice=0) # enables reference-viewer specific bindings RVMode.set_shell_ref(self) @@ -430,10 +426,8 @@ def help_text(self, name, text, text_kind='plain', trim_pfx=0): """ Provide help text for the user. - This method will convert the text as necessary with docutils and - display it in the WBrowser plugin, if available. If the plugin is - not available and the text is type 'rst' then the text will be - displayed in a plain text widget. + This method will trim the text as necessary and display it in + the text widget. Parameters ---------- @@ -441,69 +435,100 @@ def help_text(self, name, text, text_kind='plain', trim_pfx=0): Category of help to show. text : str - The text to show. Should be plain, HTML or RST text + The text to show. text_kind : str (optional) - One of 'plain', 'html', 'rst'. Default is 'plain'. + One of 'plain' or 'rst'. Default is 'plain'. trim_pfx : int (optional) Number of spaces to trim off the beginning of each line of text. """ - if trim_pfx > 0: # caller wants to trim some space off the front # of each line text = toolbox.trim_prefix(text, trim_pfx) - if text_kind == 'rst': - # try to convert RST to HTML using docutils - try: - overrides = {'input_encoding': 'ascii', - 'output_encoding': 'utf-8'} - text_html = publish_string(text, writer_name='html', - settings_overrides=overrides) - # docutils produces 'bytes' output, but webkit needs - # a utf-8 string - text = text_html.decode('utf-8') - text_kind = 'html' - - except Exception as e: - self.logger.error("Error converting help text to HTML: %s" % ( - str(e))) - # revert to showing RST as plain text + if text_kind in ['rst', 'plain']: + self.show_help_text(name, text) else: raise ValueError( "I don't know how to display text of kind '%s'" % (text_kind)) - if text_kind == 'html': - self.help(text=text, text_kind='html') - - else: - self.show_help_text(name, text) - def help(self, text=None, text_kind='url'): + if text_kind == 'url': + if text is None: + # get top URL of external Ginga RTD docs + text = download_doc.get_online_docs_url(plugin=None) + self.show_help_url(text) + else: + if isinstance(text, str): + self.show_help_text('HELP', text) - if not self.gpmon.has_plugin('WBrowser'): - return self.show_error("help() requires 'WBrowser' plugin") - - self.start_global_plugin('WBrowser') - - # need to let GUI finish processing, it seems - self.update_pending() + def show_help_url(self, url): + """ + Open a URL in an external browser using Python's webbrowser module. + """ + self.logger.info(f"opening '{url}' in external browser...") + webbrowser.open(url) - obj = self.gpmon.get_plugin('WBrowser') + def help_plugin(self, plugin_obj, text_kind='rst', url=None): + """ + Called from a plugin's default help() method. Offers to show the + user plugin docstring in a text widget or view the RTD doc in an + external web browser. + """ - if text is not None: - if text_kind == 'url': - obj.browse(text) - else: - obj.browse(text, url_is_content=True) - else: - obj.show_help() + def _do_help(val, url=None): + if val == 1: + # show plain text in a text widget + if plugin_obj is not None: + name, doc = plugin_obj._get_docstring() + self.show_help_text(name, doc) + elif val == 2: + # show web page in external browser + if url is None: + url = download_doc.get_online_docs_url(plugin=plugin_obj) + self.show_help_url(url) + + if self._help.choice > 0: + # User made a decision to keep getting plugin help the same way + return _do_help(self._help.choice, url=url) + + # Create troubleshooting dialog if downloading cannot be done + dialog = Widgets.Dialog(title="Show documentation", + parent=self.w.root, + modal=False, + buttons=[("Cancel", 0), + ("Show RST text", 1), + ("Use external browser", 2), + ]) + dialog.buttons[0].set_tooltip("Skip help") + dialog.buttons[1].set_tooltip("Show local docstring for plugin help") + dialog.buttons[2].set_tooltip("Show online web documentation in external browser") + vbox = dialog.get_content_area() + dialog_text = Widgets.TextArea(wrap=True, editable=False) + dialog_text.set_text("How would you like to see help?") + vbox.add_widget(dialog_text, stretch=1) + cb = Widgets.CheckBox("Remember my choice for session") + cb.set_state(False) + vbox.add_widget(cb, stretch=0) + + def _remember_choice_cb(w, tf): + self._help.remember_choice = tf + + def _do_help_cb(dialog, val, url=None): + if self._help.remember_choice: + self._help.choice = val + self.ds.remove_dialog(dialog) + _do_help(val, url=url) + + cb.add_callback('activated', _remember_choice_cb) + dialog.add_callback('activated', _do_help_cb, url=url) + self.ds.show_dialog(dialog) - def show_help_text(self, name, help_txt, wsname='right'): + def show_help_text(self, name, help_txt, wsname='channels'): """ Show help text in a closeable tab window. The title of the window is set from ``name`` prefixed with 'HELP:' diff --git a/ginga/rv/plugins/WBrowser.py b/ginga/rv/plugins/WBrowser.py deleted file mode 100644 index a35fc4290..000000000 --- a/ginga/rv/plugins/WBrowser.py +++ /dev/null @@ -1,192 +0,0 @@ -# This is open-source software licensed under a BSD license. -# Please see the file LICENSE.txt for details. -""" -Web browser plugin for Ginga. - -**Plugin Type: Global** - -``WBrowser`` is a global plugin. Only one instance can be opened. - -**Usage** - -This global plugin is used to browse help pages for Ginga. - -When a "Help" button is pressed from a plugin (e.g., ``Pick``), -Ginga will attempt to download an existing documentation build -from *ReadTheDocs* for the matching version. If successful, -plugin documentation from that download is displayed. -If not successful or deliberately disabled in "plugin_WBrowser.cfg", -Ginga will render the plugin's docstring locally. - -""" - -import os - -# GINGA -from ginga.GingaPlugin import GlobalPlugin -from ginga.gw import Widgets - -__all__ = ['WBrowser'] - -WAIT_HTML = """Downloading documentation - -

Downloading and unpacking Ginga documentation from ReadTheDocs. -This may take several seconds (or longer, depending on your connection).

-

Please wait...

- - -""" - - -class WBrowser(GlobalPlugin): - - def __init__(self, fv): - # superclass defines some variables for us, like logger - super(WBrowser, self).__init__(fv) - - prefs = self.fv.get_preferences() - self.settings = prefs.create_category('plugin_WBrowser') - self.settings.add_defaults(offline_doc_only=False) - self.settings.load(onError='silent') - - def build_gui(self, container): - if not Widgets.has_webkit: - self.browser = Widgets.Label( - "Please install the python-webkit package to enable " - "this plugin") - else: - self.browser = Widgets.WebView() - - sw = Widgets.ScrollArea() - sw.set_widget(self.browser) - - container.add_widget(sw, stretch=1) - sw.show() - - self.entry = Widgets.TextEntrySet() - container.add_widget(self.entry, stretch=0) - self.entry.add_callback('activated', lambda w: self.browse_cb()) - - tbar = Widgets.Toolbar(orientation='horizontal') - for tt, cb, ico in ( - ('Go back', lambda w: self.back_cb(), 'prev_48.png'), - ('Go forward', lambda w: self.forward_cb(), 'next_48.png'), - ('Reload page', lambda w: self.reload_cb(), 'rotate_48.png'), - ('Stop loading', lambda w: self.stop_cb(), 'stop_48.png'), - ('Go to top of documentation', lambda w: self.show_help(), - 'fits.png')): - btn = tbar.add_action( - None, iconpath=os.path.join(self.fv.iconpath, ico)) - btn.add_callback('activated', cb) - btn.set_tooltip(tt) - container.add_widget(tbar, stretch=0) - - btns = Widgets.HBox() - btns.set_border_width(4) - btns.set_spacing(3) - btn = Widgets.Button('Close') - btn.add_callback('activated', lambda w: self.close()) - btns.add_widget(btn, stretch=0) - btn = Widgets.Button('Help') - btn.add_callback('activated', lambda w: self.help()) - btns.add_widget(btn, stretch=0) - btns.add_widget(Widgets.Label(''), stretch=1) - container.add_widget(btns, stretch=0) - - self.gui_up = True - - def _download_doc(self, plugin=None, no_url_callback=None): - from ginga.doc.download_doc import get_doc - self.fv.assert_nongui_thread() - - self.fv.gui_do(self._load_doc, WAIT_HTML, url_is_content=True) - - def _dl_indicator(count, blksize, totalsize): - pct = (count * blksize) / totalsize - msg = 'Downloading: {:.1%} complete'.format(pct) - self.fv.gui_do(self.entry.set_text, msg) - - # This can block as long as it takes without blocking the UI. - if self.settings.get('offline_doc_only', False): - url = None # DEBUG: Use this to force offline mode. - else: - url = get_doc(logger=self.logger, plugin=plugin, - reporthook=_dl_indicator) - - self.fv.gui_do(self._load_doc, url, no_url_callback=no_url_callback) - - def _load_doc(self, url, no_url_callback=None, url_is_content=False): - self.fv.assert_gui_thread() - if url is None: - self.entry.set_text('') - if no_url_callback is None: - self.fv.show_error("Couldn't load web page") - else: - no_url_callback() - else: - self.browse(url, url_is_content=url_is_content) - - def show_help(self, plugin=None, no_url_callback=None): - """See `~ginga.GingaPlugin` for usage of optional keywords.""" - if not Widgets.has_webkit: - return - - self.fv.nongui_do(self._download_doc, plugin=plugin, - no_url_callback=no_url_callback) - - def browse(self, url, url_is_content=False): - if not Widgets.has_webkit: - return - - try: - if url_is_content: # This was load_html() - self.browser.load_html_string(url) - self.entry.set_text('') - else: - self.logger.debug("Browsing '{}'".format(url)) - self.browser.load_url(url) - self.entry.set_text(url) - except Exception as e: - self.fv.show_error("Couldn't load web page: {}".format(str(e))) - else: - self.browser.show() - - def browse_cb(self): - url = str(self.entry.get_text()).strip() - if len(url) > 0: - self.browse(url) - - def back_cb(self): - if not Widgets.has_webkit: - return - self.browser.go_back() - - def forward_cb(self): - if not Widgets.has_webkit: - return - self.browser.go_forward() - - def reload_cb(self): - if not Widgets.has_webkit: - return - self.browser.reload_page() - - def stop_cb(self): - if not Widgets.has_webkit: - return - self.browser.stop_loading() - - def close(self): - self.fv.stop_global_plugin(str(self)) - return True - - def __str__(self): - return 'wbrowser' - - -# Append module docstring with config doc for auto insert by Sphinx. -from ginga.util.toolbox import generate_cfg_example # noqa -if __doc__ is not None: - __doc__ += generate_cfg_example('plugin_WBrowser', package='ginga') - -# END diff --git a/ginga/web/pgw/Widgets.py b/ginga/web/pgw/Widgets.py index 4efd8e2b1..c20c6b9eb 100644 --- a/ginga/web/pgw/Widgets.py +++ b/ginga/web/pgw/Widgets.py @@ -14,9 +14,6 @@ from ginga.misc import Callback, Bunch, Settings, LineHistory from ginga.web.pgw import PgHelp -# For future support of WebView widget -has_webkit = False - __all__ = ['WidgetError', 'WidgetBase', 'TextEntry', 'TextEntrySet', 'TextArea', 'Dial', 'Label', 'Button', 'ComboBox', 'SpinBox', 'Slider', 'ScrollBar', 'CheckBox', 'ToggleButton', @@ -25,8 +22,7 @@ 'Expander', 'TabWidget', 'StackWidget', 'MDIWidget', 'ScrollArea', 'Splitter', 'GridBox', 'ToolbarAction', 'Toolbar', 'MenuAction', 'Menu', 'Menubar', 'Page', 'TopLevel', 'Application', 'Dialog', - 'name_mangle', 'make_widget', 'hadjust', 'build_info', 'wrap', - 'has_webkit'] + 'name_mangle', 'make_widget', 'hadjust', 'build_info', 'wrap'] class WidgetError(Exception): @@ -3589,9 +3585,9 @@ def __init__(self, title='', flags=None, buttons=[], self.title = title self.parent = parent - self.buttons = buttons self.value = None self.modal = modal + self.buttons = [] self.body = VBox() for name in ('activated', 'open', 'close', 'resize'): self.enable_callback(name) @@ -3607,6 +3603,7 @@ def __init__(self, title='', flags=None, buttons=[], hbox.set_spacing(4) for name, val in buttons: btn = Button(name) + self.buttons.append(btn) btn.add_callback('activated', self._btn_choice, name, val) hbox.add_widget(btn) self.body.add_widget(hbox, stretch=0) diff --git a/setup.cfg b/setup.cfg index 5c8ca8cae..7cf887e55 100644 --- a/setup.cfg +++ b/setup.cfg @@ -63,10 +63,8 @@ recommended = matplotlib>=3.4 opencv-python>=4.5.4.58 exifread>=2.3.2 - beautifulsoup4>=4.3.2 astroquery>=0.3.5 python-dateutil>=2.8.2 - docutils photutils test = attrs>=19.2.0