Example #1
0
class View(common.View):
    def __init__(self, scene):
        common.View.__init__(self, scene)
        self.setMouseTracking(True) # fix for not updating position for simulated events
        self.scene.text_changed.connect(self.viewport().update) # ensure a full redraw
        self.progress_loaded_timer = QTimer()
        self.progress_loaded_timer.setInterval(1500)
        self.progress_loaded_timer.setSingleShot(True)
        self.progress_loaded_timer.timeout.connect(self.viewport().update)
        
    def resizeEvent(self, e):
        common.View.resizeEvent(self, e)
        if not self.scene.playtest:
            self.fit()

    def fit(self):
        rect = self.scene.itemsBoundingRect().adjusted(-0.3, -0.3, 0.3, 0.3)
        self.setSceneRect(rect)
        self.fitInView(rect, qt.KeepAspectRatio)
        zoom = self.transform().mapRect(QRectF(0, 0, 1, 1)).width()
        if zoom > 100:
            self.resetTransform()
            self.scale(100, 100)
    
    def paintEvent(self, e):
        common.View.paintEvent(self, e)
        g = QPainter(self.viewport())
        g.setRenderHints(self.renderHints())
        area = self.viewport().rect().adjusted(5, 2, -5, -2)
        
        if self.progress_loaded_timer.isActive():
            g.setPen(QPen(Color.dark_text))
            g.drawText(area, qt.AlignTop | qt.AlignLeft, "Progress loaded")
        
        try:
            self._info_font
        except AttributeError:
            self._info_font = g.font()
            multiply_font_size(self._info_font, 3)
        
        try:
            txt = ('{r} ({m})' if self.scene.mistakes else '{r}').format(r=self.scene.remaining, m=self.scene.mistakes)
            g.setFont(self._info_font)
            g.setPen(QPen(Color.dark_text))
            g.drawText(area, qt.AlignTop | qt.AlignRight, txt)
        except AttributeError: pass

    def wheelEvent(self, e):
        pass
Example #2
0
class Splitter(QSplitter):

    state_changed = pyqtSignal(object)
    reapply_sizes = pyqtSignal(object)

    def __init__(self,
                 name,
                 label,
                 icon,
                 initial_show=True,
                 initial_side_size=120,
                 connect_button=True,
                 orientation=Qt.Orientation.Horizontal,
                 side_index=0,
                 parent=None,
                 shortcut=None,
                 hide_handle_on_single_panel=True):
        QSplitter.__init__(self, parent)
        self.reapply_sizes.connect(self.setSizes,
                                   type=Qt.ConnectionType.QueuedConnection)
        self.hide_handle_on_single_panel = hide_handle_on_single_panel
        if hide_handle_on_single_panel:
            self.state_changed.connect(self.update_handle_width)
        self.original_handle_width = self.handleWidth()
        self.resize_timer = QTimer(self)
        self.resize_timer.setSingleShot(True)
        self.desired_side_size = initial_side_size
        self.desired_show = initial_show
        self.resize_timer.setInterval(5)
        self.resize_timer.timeout.connect(self.do_resize)
        self.setOrientation(orientation)
        self.side_index = side_index
        self._name = name
        self.label = label
        self.initial_side_size = initial_side_size
        self.initial_show = initial_show
        self.splitterMoved.connect(self.splitter_moved,
                                   type=Qt.ConnectionType.QueuedConnection)
        self.button = LayoutButton(icon, label, self, shortcut=shortcut)
        if connect_button:
            self.button.clicked.connect(self.double_clicked)

        if shortcut is not None:
            self.action_toggle = QAction(QIcon(icon),
                                         _('Toggle') + ' ' + label, self)
            self.action_toggle.changed.connect(self.update_shortcut)
            self.action_toggle.triggered.connect(self.toggle_triggered)
            if parent is not None:
                parent.addAction(self.action_toggle)
                if hasattr(parent, 'keyboard'):
                    parent.keyboard.register_shortcut(
                        'splitter %s %s' % (name, label),
                        str(self.action_toggle.text()),
                        default_keys=(shortcut, ),
                        action=self.action_toggle)
                else:
                    self.action_toggle.setShortcut(shortcut)
            else:
                self.action_toggle.setShortcut(shortcut)

    def update_shortcut(self):
        self.button.update_shortcut(self.action_toggle)

    def toggle_triggered(self, *args):
        self.toggle_side_pane()

    def createHandle(self):
        return SplitterHandle(self.orientation(), self)

    def initialize(self):
        for i in range(self.count()):
            h = self.handle(i)
            if h is not None:
                h.splitter_moved()
        self.state_changed.emit(not self.is_side_index_hidden)

    def splitter_moved(self, *args):
        self.desired_side_size = self.side_index_size
        self.state_changed.emit(not self.is_side_index_hidden)

    def update_handle_width(self, not_one_panel):
        self.setHandleWidth(self.original_handle_width if not_one_panel else 0)

    @property
    def is_side_index_hidden(self):
        sizes = list(self.sizes())
        try:
            return sizes[self.side_index] == 0
        except IndexError:
            return True

    @property
    def save_name(self):
        ori = 'horizontal' if self.orientation() == Qt.Orientation.Horizontal \
                else 'vertical'
        return self._name + '_' + ori

    def print_sizes(self):
        if self.count() > 1:
            print(self.save_name,
                  'side:',
                  self.side_index_size,
                  'other:',
                  end=' ')
            print(list(self.sizes())[self.other_index])

    @property
    def side_index_size(self):
        if self.count() < 2:
            return 0
        return self.sizes()[self.side_index]

    @side_index_size.setter
    def side_index_size(self, val):
        if self.count() < 2:
            return
        side_index_hidden = self.is_side_index_hidden
        if val == 0 and not side_index_hidden:
            self.save_state()
        sizes = list(self.sizes())
        for i in range(len(sizes)):
            sizes[i] = val if i == self.side_index else 10
        self.setSizes(sizes)
        sizes = list(self.sizes())
        total = sum(sizes)
        total_needs_adjustment = self.hide_handle_on_single_panel and side_index_hidden
        if total_needs_adjustment:
            total -= self.original_handle_width
        for i in range(len(sizes)):
            sizes[i] = val if i == self.side_index else total - val
        self.setSizes(sizes)
        self.initialize()
        if total_needs_adjustment:
            # the handle visibility and therefore size distribution will change
            # when the event loop ticks
            self.reapply_sizes.emit(sizes)

    def do_resize(self, *args):
        orig = self.desired_side_size
        QSplitter.resizeEvent(self, self._resize_ev)
        if orig > 20 and self.desired_show:
            c = 0
            while abs(self.side_index_size - orig) > 10 and c < 5:
                self.apply_state(self.get_state(), save_desired=False)
                c += 1

    def resizeEvent(self, ev):
        if self.resize_timer.isActive():
            self.resize_timer.stop()
        self._resize_ev = ev
        self.resize_timer.start()

    def get_state(self):
        if self.count() < 2:
            return (False, 200)
        return (self.desired_show, self.desired_side_size)

    def apply_state(self, state, save_desired=True):
        if state[0]:
            self.side_index_size = state[1]
            if save_desired:
                self.desired_side_size = self.side_index_size
        else:
            self.side_index_size = 0
        self.desired_show = state[0]

    def default_state(self):
        return (self.initial_show, self.initial_side_size)

    # Public API {{{

    def update_desired_state(self):
        self.desired_show = not self.is_side_index_hidden

    def save_state(self):
        if self.count() > 1:
            gprefs[self.save_name + '_state'] = self.get_state()

    @property
    def other_index(self):
        return (self.side_index + 1) % 2

    def restore_state(self):
        if self.count() > 1:
            state = gprefs.get(self.save_name + '_state', self.default_state())
            self.apply_state(state, save_desired=False)
            self.desired_side_size = state[1]

    def toggle_side_pane(self, hide=None):
        if hide is None:
            action = 'show' if self.is_side_index_hidden else 'hide'
        else:
            action = 'hide' if hide else 'show'
        getattr(self, action + '_side_pane')()

    def show_side_pane(self):
        if self.count() < 2 or not self.is_side_index_hidden:
            return
        if self.desired_side_size == 0:
            self.desired_side_size = self.initial_side_size
        self.apply_state((True, self.desired_side_size))

    def hide_side_pane(self):
        if self.count() < 2 or self.is_side_index_hidden:
            return
        self.apply_state((False, self.desired_side_size))

    def double_clicked(self, *args):
        self.toggle_side_pane()
Example #3
0
class Preview(QWidget):

    sync_requested = pyqtSignal(object, object)
    split_requested = pyqtSignal(object, object, object)
    split_start_requested = pyqtSignal()
    link_clicked = pyqtSignal(object, object)
    refresh_starting = pyqtSignal()
    refreshed = pyqtSignal()
    live_css_data = pyqtSignal(object)
    render_process_restarted = pyqtSignal()
    open_file_with = pyqtSignal(object, object, object)
    edit_file = pyqtSignal(object)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout()
        self.setLayout(l)
        l.setContentsMargins(0, 0, 0, 0)
        self.stack = QStackedLayout(l)
        self.stack.setStackingMode(QStackedLayout.StackingMode.StackAll)
        self.current_sync_retry_count = 0
        self.view = WebView(self)
        self.view._page.bridge.request_sync.connect(self.request_sync)
        self.view._page.bridge.request_split.connect(self.request_split)
        self.view._page.bridge.live_css_data.connect(self.live_css_data)
        self.view._page.bridge.bridge_ready.connect(self.on_bridge_ready)
        self.view._page.loadFinished.connect(self.load_finished)
        self.view._page.loadStarted.connect(self.load_started)
        self.view.render_process_restarted.connect(self.render_process_restarted)
        self.pending_go_to_anchor = None
        self.inspector = self.view.inspector
        self.stack.addWidget(self.view)
        self.cover = c = QLabel(_('Loading preview, please wait...'))
        c.setWordWrap(True)
        c.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
        c.setStyleSheet('QLabel { background-color: palette(window); }')
        c.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.stack.addWidget(self.cover)
        self.stack.setCurrentIndex(self.stack.indexOf(self.cover))
        self.bar = QToolBar(self)
        l.addWidget(self.bar)

        ac = actions['auto-reload-preview']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.auto_reload_toggled)
        self.auto_reload_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['sync-preview-to-editor']
        ac.setCheckable(True)
        ac.setChecked(True)
        ac.toggled.connect(self.sync_toggled)
        self.sync_toggled(ac.isChecked())
        self.bar.addAction(ac)

        self.bar.addSeparator()

        ac = actions['split-in-preview']
        ac.setCheckable(True)
        ac.setChecked(False)
        ac.toggled.connect(self.split_toggled)
        self.split_toggled(ac.isChecked())
        self.bar.addAction(ac)

        ac = actions['reload-preview']
        ac.triggered.connect(self.refresh)
        self.bar.addAction(ac)

        actions['preview-dock'].toggled.connect(self.visibility_changed)

        self.current_name = None
        self.last_sync_request = None
        self.refresh_timer = QTimer(self)
        self.refresh_timer.timeout.connect(self.refresh)
        parse_worker.start()
        self.current_sync_request = None

        self.search = HistoryLineEdit2(self)
        self.search.setClearButtonEnabled(True)
        ac = self.search.findChild(QAction, QT_HIDDEN_CLEAR_ACTION)
        if ac is not None:
            ac.triggered.connect(self.clear_clicked)
        self.search.initialize('tweak_book_preview_search')
        self.search.setPlaceholderText(_('Search in preview'))
        self.search.returnPressed.connect(self.find_next)
        self.bar.addSeparator()
        self.bar.addWidget(self.search)
        for d in ('next', 'prev'):
            ac = actions['find-%s-preview' % d]
            ac.triggered.connect(getattr(self, 'find_' + d))
            self.bar.addAction(ac)

    def clear_clicked(self):
        self.view._page.findText('')

    def find(self, direction):
        text = str(self.search.text())
        self.view._page.findText(text, (
            QWebEnginePage.FindFlag.FindBackward if direction == 'prev' else QWebEnginePage.FindFlags(0)))

    def find_next(self):
        self.find('next')

    def find_prev(self):
        self.find('prev')

    def go_to_anchor(self, anchor):
        self.view._page.go_to_anchor(anchor)

    def request_sync(self, tagname, href, lnum):
        if self.current_name:
            c = current_container()
            if tagname == 'a' and href:
                if href and href.startswith('#'):
                    name = self.current_name
                else:
                    name = c.href_to_name(href, self.current_name) if href else None
                if name == self.current_name:
                    return self.go_to_anchor(urlparse(href).fragment)
                if name and c.exists(name) and c.mime_map[name] in OEB_DOCS:
                    return self.link_clicked.emit(name, urlparse(href).fragment or TOP)
            self.sync_requested.emit(self.current_name, lnum)

    def request_split(self, loc, totals):
        actions['split-in-preview'].setChecked(False)
        if not loc or not totals:
            return error_dialog(self, _('Invalid location'),
                                _('Cannot split on the body tag'), show=True)
        if self.current_name:
            self.split_requested.emit(self.current_name, loc, totals)

    @property
    def bridge_ready(self):
        return self.view._page.bridge.ready

    def sync_to_editor(self, name, sourceline_address):
        self.current_sync_request = (name, sourceline_address)
        self.current_sync_retry_count = 0
        QTimer.singleShot(100, self._sync_to_editor)

    def _sync_to_editor(self):
        if not actions['sync-preview-to-editor'].isChecked() or self.current_sync_retry_count >= 3000 or self.current_sync_request is None:
            return
        if self.refresh_timer.isActive() or not self.bridge_ready or self.current_sync_request[0] != self.current_name:
            self.current_sync_retry_count += 1
            return QTimer.singleShot(100, self._sync_to_editor)
        sourceline_address = self.current_sync_request[1]
        self.current_sync_request = None
        self.current_sync_retry_count = 0
        self.view._page.go_to_sourceline_address(sourceline_address)

    def report_worker_launch_error(self):
        if parse_worker.launch_error is not None:
            tb, parse_worker.launch_error = parse_worker.launch_error, None
            error_dialog(self, _('Failed to launch worker'), _(
                'Failed to launch the worker process used for rendering the preview'), det_msg=tb, show=True)

    def name_to_qurl(self, name=None):
        name = name or self.current_name
        qurl = QUrl()
        qurl.setScheme(FAKE_PROTOCOL), qurl.setAuthority(FAKE_HOST), qurl.setPath('/' + name)
        return qurl

    def show(self, name):
        if name != self.current_name:
            self.refresh_timer.stop()
            self.current_name = name
            self.report_worker_launch_error()
            parse_worker.add_request(name)
            self.view.set_url(self.name_to_qurl())
            return True

    def refresh(self):
        if self.current_name:
            self.refresh_timer.stop()
            # This will check if the current html has changed in its editor,
            # and re-parse it if so
            self.report_worker_launch_error()
            parse_worker.add_request(self.current_name)
            # Tell webkit to reload all html and associated resources
            current_url = self.name_to_qurl()
            self.refresh_starting.emit()
            if current_url != self.view.url():
                # The container was changed
                self.view.set_url(current_url)
            else:
                self.view.refresh()
            self.refreshed.emit()

    def clear(self):
        self.view.clear()
        self.current_name = None

    @property
    def is_visible(self):
        return actions['preview-dock'].isChecked()

    @property
    def live_css_is_visible(self):
        try:
            return actions['live-css-dock'].isChecked()
        except KeyError:
            return False

    def start_refresh_timer(self):
        if self.live_css_is_visible or (self.is_visible and actions['auto-reload-preview'].isChecked()):
            self.refresh_timer.start(tprefs['preview_refresh_time'] * 1000)

    def stop_refresh_timer(self):
        self.refresh_timer.stop()

    def auto_reload_toggled(self, checked):
        if self.live_css_is_visible and not actions['auto-reload-preview'].isChecked():
            actions['auto-reload-preview'].setChecked(True)
            error_dialog(self, _('Cannot disable'), _(
                'Auto reloading of the preview panel cannot be disabled while the'
                ' Live CSS panel is open.'), show=True)
        actions['auto-reload-preview'].setToolTip(_(
            'Auto reload preview when text changes in editor') if not checked else _(
                'Disable auto reload of preview'))

    def sync_toggled(self, checked):
        actions['sync-preview-to-editor'].setToolTip(_(
            'Disable syncing of preview position to editor position') if checked else _(
                'Enable syncing of preview position to editor position'))

    def visibility_changed(self, is_visible):
        if is_visible:
            self.refresh()

    def split_toggled(self, checked):
        actions['split-in-preview'].setToolTip('<p>' + (_(
            'Abort file split') if checked else _(
                'Split this file at a specified location.<p>After clicking this button, click'
                ' inside the preview panel above at the location you want the file to be split.')))
        if checked:
            self.split_start_requested.emit()
        else:
            self.view._page.split_mode(False)

    def do_start_split(self):
        self.view._page.split_mode(True)

    def stop_split(self):
        actions['split-in-preview'].setChecked(False)

    def load_started(self):
        self.stack.setCurrentIndex(self.stack.indexOf(self.cover))

    def on_bridge_ready(self):
        self.stack.setCurrentIndex(self.stack.indexOf(self.view))

    def load_finished(self, ok):
        self.stack.setCurrentIndex(self.stack.indexOf(self.view))
        if self.pending_go_to_anchor:
            self.view._page.go_to_anchor(self.pending_go_to_anchor)
            self.pending_go_to_anchor = None
        if actions['split-in-preview'].isChecked():
            if ok:
                self.do_start_split()
            else:
                self.stop_split()

    def request_live_css_data(self, editor_name, sourceline, tags):
        if self.view._page.bridge.ready:
            self.view._page.bridge.live_css(editor_name, sourceline, tags)

    def apply_settings(self):
        s = self.view.settings()
        s.setFontSize(QWebEngineSettings.FontSize.DefaultFontSize, tprefs['preview_base_font_size'])
        s.setFontSize(QWebEngineSettings.FontSize.DefaultFixedFontSize, tprefs['preview_mono_font_size'])
        s.setFontSize(QWebEngineSettings.FontSize.MinimumLogicalFontSize, tprefs['preview_minimum_font_size'])
        s.setFontSize(QWebEngineSettings.FontSize.MinimumFontSize, tprefs['preview_minimum_font_size'])
        sf, ssf, mf = tprefs['engine_preview_serif_family'], tprefs['engine_preview_sans_family'], tprefs['engine_preview_mono_family']
        if sf:
            s.setFontFamily(QWebEngineSettings.FontFamily.SerifFont, sf)
        if ssf:
            s.setFontFamily(QWebEngineSettings.FontFamily.SansSerifFont, ssf)
        if mf:
            s.setFontFamily(QWebEngineSettings.FontFamily.FixedFont, mf)
        stdfnt = tprefs['preview_standard_font_family'] or 'serif'
        stdfnt = {
            'serif': QWebEngineSettings.FontFamily.SerifFont,
            'sans': QWebEngineSettings.FontFamily.SansSerifFont,
            'mono': QWebEngineSettings.FontFamily.FixedFont
        }[stdfnt]
        s.setFontFamily(QWebEngineSettings.FontFamily.StandardFont, s.fontFamily(stdfnt))
Example #4
0
class LiveCSS(QWidget):

    goto_declaration = pyqtSignal(object)

    def __init__(self, preview, parent=None):
        QWidget.__init__(self, parent)
        self.preview = preview
        preview.live_css_data.connect(self.got_live_css_data)
        self.preview_is_refreshing = False
        self.refresh_needed = False
        preview.refresh_starting.connect(self.preview_refresh_starting)
        preview.refreshed.connect(self.preview_refreshed)
        self.apply_theme()
        self.setAutoFillBackground(True)
        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self.update_data)
        self.update_timer.setSingleShot(True)
        self.update_timer.setInterval(500)
        self.now_showing = (None, None, None)

        self.stack = s = QStackedLayout(self)
        self.setLayout(s)

        self.clear_label = la = QLabel(
            '<h3>' + _('No style information found') + '</h3><p>' +
            _('Move the cursor inside a HTML tag to see what styles'
              ' apply to that tag.'))
        la.setWordWrap(True)
        la.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
        s.addWidget(la)

        self.box = box = Box(self)
        box.hyperlink_activated.connect(
            self.goto_declaration, type=Qt.ConnectionType.QueuedConnection)
        self.scroll = sc = QScrollArea(self)
        sc.setWidget(box)
        sc.setWidgetResizable(True)
        s.addWidget(sc)

    def preview_refresh_starting(self):
        self.preview_is_refreshing = True

    def preview_refreshed(self):
        self.preview_is_refreshing = False
        self.refresh_needed = True
        self.start_update_timer()

    def apply_theme(self):
        f = self.font()
        f.setFamily(tprefs['editor_font_family'] or default_font_family())
        f.setPointSizeF(tprefs['editor_font_size'])
        self.setFont(f)
        theme = get_theme(tprefs['editor_theme'])
        pal = self.palette()
        pal.setColor(QPalette.ColorRole.Window,
                     theme_color(theme, 'Normal', 'bg'))
        pal.setColor(QPalette.ColorRole.WindowText,
                     theme_color(theme, 'Normal', 'fg'))
        pal.setColor(QPalette.ColorRole.AlternateBase,
                     theme_color(theme, 'HighlightRegion', 'bg'))
        pal.setColor(QPalette.ColorRole.Link, theme_color(theme, 'Link', 'fg'))
        pal.setColor(QPalette.ColorRole.LinkVisited,
                     theme_color(theme, 'Keyword', 'fg'))
        self.setPalette(pal)
        if hasattr(self, 'box'):
            self.box.relayout()
        self.update()

    def clear(self):
        self.stack.setCurrentIndex(0)

    def show_data(self, editor_name, sourceline, tags):
        if self.preview_is_refreshing:
            return
        if sourceline is None:
            self.clear()
        else:
            self.preview.request_live_css_data(editor_name, sourceline, tags)

    def got_live_css_data(self, result):
        maximum_specificities = {}
        for node in result['nodes']:
            for rule in node['css']:
                self.process_rule(rule, node['ancestor_specificity'],
                                  maximum_specificities)
        for node in result['nodes']:
            for rule in node['css']:
                for prop in rule['properties']:
                    if prop.specificity < maximum_specificities[prop.name]:
                        prop.is_overriden = True
        self.display_received_live_css_data(result)

    def display_received_live_css_data(self, data):
        editor_name = data['editor_name']
        sourceline = data['sourceline']
        tags = data['tags']
        if data is None or len(data['computed_css']) < 1:
            if editor_name == self.current_name and (editor_name, sourceline,
                                                     tags) == self.now_showing:
                # Try again in a little while in case there was a transient
                # error in the web view
                self.start_update_timer()
                return
            self.clear()
            return
        self.now_showing = (editor_name, sourceline, tags)
        data['html_name'] = editor_name
        self.box.show_data(data)
        self.refresh_needed = False
        self.stack.setCurrentIndex(1)

    def process_rule(self, rule, ancestor_specificity, maximum_specificities):
        selector = rule['selector']
        sheet_index = rule['sheet_index']
        rule_address = rule['rule_address'] or ()
        if selector is not None:
            try:
                specificity = [0] + list(parse(selector)[0].specificity())
            except (AttributeError, TypeError, SelectorError):
                specificity = [0, 0, 0, 0]
        else:  # style attribute
            specificity = [1, 0, 0, 0]
        specificity.extend((sheet_index, tuple(rule_address)))
        properties = []
        for prop in rule['properties']:
            important = 1 if prop[-1] == 'important' else 0
            p = Property(prop,
                         [ancestor_specificity] + [important] + specificity)
            properties.append(p)
            if p.specificity > maximum_specificities.get(
                    p.name, lowest_specificity):
                maximum_specificities[p.name] = p.specificity
        rule['properties'] = properties

        href = rule['href']
        if hasattr(href, 'startswith') and href.startswith(
                f'{FAKE_PROTOCOL}://{FAKE_HOST}'):
            qurl = QUrl(href)
            name = qurl.path()[1:]
            if name:
                rule['href'] = name

    @property
    def current_name(self):
        return self.preview.current_name

    @property
    def is_visible(self):
        return self.isVisible()

    def showEvent(self, ev):
        self.update_timer.start()
        actions['auto-reload-preview'].setEnabled(True)
        return QWidget.showEvent(self, ev)

    def sync_to_editor(self):
        self.update_data()

    def update_data(self):
        if not self.is_visible or self.preview_is_refreshing:
            return
        editor_name = self.current_name
        ed = editors.get(editor_name, None)
        if self.update_timer.isActive() or (ed is None
                                            and editor_name is not None):
            return QTimer.singleShot(100, self.update_data)
        if ed is not None:
            sourceline, tags = ed.current_tag(for_position_sync=False)
            if self.refresh_needed or self.now_showing != (editor_name,
                                                           sourceline, tags):
                self.show_data(editor_name, sourceline, tags)

    def start_update_timer(self):
        if self.is_visible:
            self.update_timer.start()

    def stop_update_timer(self):
        self.update_timer.stop()

    def navigate_to_declaration(self, data, editor):
        if data['type'] == 'inline':
            sourceline, tags = data['sourceline_address']
            editor.goto_sourceline(sourceline, tags, attribute='style')
        elif data['type'] == 'sheet':
            editor.goto_css_rule(data['rule_address'])
        elif data['type'] == 'elem':
            editor.goto_css_rule(data['rule_address'],
                                 sourceline_address=data['sourceline_address'])