def get(self, qurl, html=None, num_retries=1, delay=10, timeout=10): t1 = time() loop = QEventLoop() timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(loop.quit) self.loadFinished.connect(loop.quit) if qurl: if html: self.setHtml(html, qurl) else: self.mainFrame().load(QUrl(qurl)) timer.start(timeout * 1000) loop.exec_() # delay here until download finished or timeout if timer.isActive(): # downloaded successfully timer.stop() self._wait(delay - (time() - t1)) parsed_html = self.mainFrame().toHtml() else: # did not download in time if num_retries > 0: logging.debug('Timeout - retrying') parsed_html = self.get(qurl, num_retries=num_retries - 1, timerout=timeout, delay=delay) else: logging.debug('Timed out') parsed_html = '' self.mainFrame().setHtml(None) return parsed_html
def get(self, qurl, html=None, num_retries=1, delay = 10, timeout = 10): t1 = time() loop = QEventLoop() timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(loop.quit) self.loadFinished.connect(loop.quit) if qurl: if html: self.setHtml(html, qurl) else: self.mainFrame().load(QUrl(qurl)) timer.start(timeout * 1000) loop.exec_() # delay here until download finished or timeout if timer.isActive(): # downloaded successfully timer.stop() self._wait(delay - (time() - t1)) parsed_html = self.mainFrame().toHtml() else: # did not download in time if num_retries > 0: logging.debug('Timeout - retrying') parsed_html = self.get(qurl, num_retries=num_retries-1, timerout=timeout, delay=delay) else: logging.debug('Timed out') parsed_html = '' self.mainFrame().setHtml(None) return parsed_html
class CoverDelegate(QStyledItemDelegate): # {{{ ICON_SIZE = 150, 200 needs_redraw = pyqtSignal() def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) self.angle = 0 self.timer = QTimer(self) self.timer.timeout.connect(self.frame_changed) self.dark_color = parent.palette().color(QPalette.ColorRole.WindowText) self.light_color = parent.palette().color(QPalette.ColorRole.Window) self.spinner_width = 64 def frame_changed(self, *args): self.angle = (self.angle - 2) % 360 self.needs_redraw.emit() def start_animation(self): self.angle = 0 self.timer.start(10) def stop_animation(self): self.timer.stop() def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) style = QApplication.style() waiting = self.timer.isActive() and bool( index.data(Qt.ItemDataRole.UserRole)) if waiting: rect = QRect(0, 0, self.spinner_width, self.spinner_width) rect.moveCenter(option.rect.center()) draw_snake_spinner(painter, rect, self.angle, self.light_color, self.dark_color) else: # Ensure the cover is rendered over any selection rect style.drawItemPixmap( painter, option.rect, Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter, QPixmap(index.data(Qt.ItemDataRole.DecorationRole)))
def reconnect_to_backend(self, app_backend, error): # pragma: no cover """ Set AlignakApp in reconnect mode and try to login to Backend :param app_backend: AppBackend object :type app_backend: AppBackend :param error: string error to display in banner :type error: str """ self.reconnect_mode = True logger.warning('Application reconnecting MODE: %s', self.reconnecting) send_banner('ERROR', 'Alignak Backend seems unreachable ! %s' % error, duration=5000) timer = QTimer(self) def connect_to_backend(): """Try to log in to Backend""" try: connect = app_backend.login() assert connect # If connect, reconnecting is disable timer.stop() logger.info('Connection restored : %s', connect) send_banner( 'OK', 'Connection with the Backend has been restored ! You are logged in again', duration=5000 ) self.reconnect_mode = False except AssertionError: send_banner( 'ERROR', 'Backend is still unreachable... Alignak-app try to reconnect', duration=5000 ) logger.error('Backend is still unreachable...') if timer.isActive(): pass else: timer.start(10000) timer.timeout.connect(connect_to_backend)
class CoverDelegate(QStyledItemDelegate): # {{{ ICON_SIZE = 150, 200 needs_redraw = pyqtSignal() def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) self.angle = 0 self.timer = QTimer(self) self.timer.timeout.connect(self.frame_changed) self.dark_color = parent.palette().color(QPalette.WindowText) self.light_color = parent.palette().color(QPalette.Window) self.spinner_width = 64 def frame_changed(self, *args): self.angle = (self.angle-2)%360 self.needs_redraw.emit() def start_animation(self): self.angle = 0 self.timer.start(10) def stop_animation(self): self.timer.stop() def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) style = QApplication.style() waiting = self.timer.isActive() and bool(index.data(Qt.UserRole)) if waiting: rect = QRect(0, 0, self.spinner_width, self.spinner_width) rect.moveCenter(option.rect.center()) draw_snake_spinner(painter, rect, self.angle, self.light_color, self.dark_color) else: # Ensure the cover is rendered over any selection rect style.drawItemPixmap(painter, option.rect, Qt.AlignTop|Qt.AlignHCenter, QPixmap(index.data(Qt.DecorationRole)))
class External(): def __init__(self): self.watchTimer = QTimer() self.watchTimer.setInterval(800) self.watchTimer.timerEvent = self.onWatch self.files_changed = queue.Queue() pass def onWatch(self,e): try: file_type, filename, action = self.files_changed.get_nowait() print(file_type, filename, action) except queue.Empty: pass pass def onFileChanged(self, path=None): print('file changed', path) pass def load(self, path): print('load', path) Watcher(path, self.files_changed) if not self.watchTimer.isActive(): self.watchTimer.start() pass pass def update(self, path): pass def create(self, tmppath=None): if not tmppath: tmppath = 'tmp' if not os.path.exists(tmppath): os.mkdir(tmppath) pass pass
class LiveCSS(QWidget): goto_declaration = pyqtSignal(object) def __init__(self, preview, parent=None): QWidget.__init__(self, parent) self.preview = preview 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.AlignTop | Qt.AlignLeft) s.addWidget(la) self.box = box = Box(self) box.hyperlink_activated.connect(self.goto_declaration, type=Qt.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 # We must let the event loop run otherwise the webview will return # stale data in read_data() 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.setPointSize(tprefs['editor_font_size']) self.setFont(f) theme = get_theme(tprefs['editor_theme']) pal = self.palette() pal.setColor(pal.Window, theme_color(theme, 'Normal', 'bg')) pal.setColor(pal.WindowText, theme_color(theme, 'Normal', 'fg')) pal.setColor(pal.AlternateBase, theme_color(theme, 'HighlightRegion', 'bg')) pal.setColor(pal.Link, theme_color(theme, 'Link', 'fg')) pal.setColor(pal.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: data = self.read_data(sourceline, 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 if self.now_showing == (None, None, None) or self.now_showing[0] != self.current_name: self.clear() return # Try to refresh the data for the currently shown tag instead # of clearing editor_name, sourceline, tags = self.now_showing data = self.read_data(sourceline, tags) if data is None or len(data['computed_css']) < 1: 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 read_data(self, sourceline, tags): mf = self.preview.view.page().mainFrame() tags = [x.lower() for x in tags] result = unicode_type(mf.evaluateJavaScript( 'window.calibre_preview_integration.live_css(%s, %s)' % ( json.dumps(sourceline), json.dumps(tags))) or '') try: result = json.loads(result) except ValueError: result = None if result is not None: maximum_specificities = {} for node in result['nodes']: is_ancestor = node['is_ancestor'] for rule in node['css']: self.process_rule(rule, is_ancestor, 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 return result def process_rule(self, rule, is_ancestor, 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))) ancestor_specificity = 0 if is_ancestor else 1 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, (0,0,0,0,0,0)): maximum_specificities[p.name] = p.specificity rule['properties'] = properties href = rule['href'] if hasattr(href, 'startswith') and href.startswith('%s://%s' % (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'])
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.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.loadFinished.connect(self.load_finished) self.view.render_process_restarted.connect( self.render_process_restarted) self.pending_go_to_anchor = None self.inspector = self.view.inspector l.addWidget(self.view) 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.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 find(self, direction): text = unicode_type(self.search.text()) self.view._page.findText(text, (QWebEnginePage.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) def sync_to_editor(self, name, sourceline_address): self.current_sync_request = (name, sourceline_address) QTimer.singleShot(100, self._sync_to_editor) def _sync_to_editor(self): if not actions['sync-preview-to-editor'].isChecked(): return try: if self.refresh_timer.isActive( ) or self.current_sync_request[0] != self.current_name: return QTimer.singleShot(100, self._sync_to_editor) except TypeError: return # Happens if current_sync_request is None sourceline_address = self.current_sync_request[1] self.current_sync_request = None 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.setUrl(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.setUrl(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( textwrap.fill( _('Abort file split') if checked else _('Split this file at a specified location.\n\nAfter 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_finished(self, ok): 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(s.DefaultFontSize, tprefs['preview_base_font_size']) s.setFontSize(s.DefaultFixedFontSize, tprefs['preview_mono_font_size']) s.setFontSize(s.MinimumLogicalFontSize, tprefs['preview_minimum_font_size']) s.setFontSize(s.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(s.SerifFont, sf) if ssf: s.setFontFamily(s.SansSerifFont, ssf) if mf: s.setFontFamily(s.FixedFont, mf) stdfnt = tprefs['preview_standard_font_family'] or 'serif' stdfnt = getattr(s, { 'serif': 'SerifFont', 'sans': 'SansSerifFont', 'mono': 'FixedFont' }[stdfnt]) s.setFontFamily(s.StandardFont, s.fontFamily(stdfnt))
class ScudCloud(QtWidgets.QMainWindow): forceClose = False messages = 0 speller = Speller() title = 'ScudCloud' def __init__(self, debug=False, minimized=None, urgent_hint=None, settings_path='', cache_path=''): super(ScudCloud, self).__init__(None) self.debug = debug self.minimized = minimized self.urgent_hint = urgent_hint self.setWindowTitle(self.title) self.settings_path = settings_path self.cache_path = cache_path self.notifier = Notifier(Resources.APP_NAME, Resources.get_path('scudcloud.png')) self.settings = QSettings(self.settings_path + '/scudcloud_qt5.cfg', QSettings.IniFormat) self.notifier.enabled = self.settings.value('Notifications', defaultValue=True, type=bool) self.identifier = self.settings.value("Domain") if Unity is not None: self.launcher = Unity.LauncherEntry.get_for_desktop_id( "scudcloud.desktop") else: self.launcher = DummyLauncher(self) self.webSettings() self.snippetsSettings() self.leftPane = LeftPane(self) self.stackedWidget = QtWidgets.QStackedWidget() centralWidget = QtWidgets.QWidget(self) layout = QtWidgets.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.leftPane) layout.addWidget(self.stackedWidget) centralWidget.setLayout(layout) self.setCentralWidget(centralWidget) self.startURL = Resources.SIGNIN_URL if self.identifier is not None: if isinstance(self.identifier, str): self.domains = self.identifier.split(",") else: self.domains = self.identifier self.startURL = self.normalize(self.domains[0]) else: self.domains = [] self.addWrapper(self.startURL) self.addMenu() self.tray = Systray(self) self.systray(self.minimized) self.installEventFilter(self) self.statusBar().showMessage('Loading Slack...') self.tickler = QTimer(self) self.tickler.setInterval(1800000) # Watch for ScreenLock events if DBusQtMainLoop is not None: DBusQtMainLoop(set_as_default=True) sessionBus = dbus.SessionBus() # Ubuntu 12.04 and other distros sessionBus.add_match_string( "type='signal',interface='org.gnome.ScreenSaver'") # Ubuntu 14.04 sessionBus.add_match_string( "type='signal',interface='com.ubuntu.Upstart0_6'") # Ubuntu 16.04 and KDE sessionBus.add_match_string( "type='signal',interface='org.freedesktop.ScreenSaver'") # Cinnamon sessionBus.add_match_string( "type='signal',interface='org.cinnamon.ScreenSaver'") sessionBus.add_message_filter(self.screenListener) self.tickler.timeout.connect(self.sendTickle) # If dbus is not present, tickler timer will act like a blocker to not send tickle too often else: self.tickler.setSingleShot(True) self.tickler.start() def screenListener(self, bus, message): event = message.get_member() # "ActiveChanged" for Ubuntu 12.04 and other distros. "EventEmitted" for Ubuntu 14.04 and above if event == "ActiveChanged" or event == "EventEmitted": arg = message.get_args_list()[0] # True for Ubuntu 12.04 and other distros. "desktop-lock" for Ubuntu 14.04 and above if (arg == True or arg == "desktop-lock") and self.tickler.isActive(): self.tickler.stop() elif (arg == False or arg == "desktop-unlock") and not self.tickler.isActive(): self.sendTickle() self.tickler.start() def sendTickle(self): for i in range(0, self.stackedWidget.count()): self.stackedWidget.widget(i).sendTickle() def addWrapper(self, url): webView = Wrapper(self) webView.load(QtCore.QUrl(url)) webView.show() webView.setZoomFactor(self.zoom) self.stackedWidget.addWidget(webView) self.stackedWidget.setCurrentWidget(webView) self.clearMemory() def webSettings(self): self.cookiesjar = PersistentCookieJar(self) self.zoom = self.readZoom() # We don't want Flash (it causes a lot of trouble in some distros) QWebSettings.globalSettings().setAttribute(QWebSettings.PluginsEnabled, False) # We don't need Java QWebSettings.globalSettings().setAttribute(QWebSettings.JavaEnabled, False) # Enabling Local Storage (now required by Slack) QWebSettings.globalSettings().setAttribute( QWebSettings.LocalStorageEnabled, True) # We need browsing history (required to not limit LocalStorage) QWebSettings.globalSettings().setAttribute( QWebSettings.PrivateBrowsingEnabled, False) # Enabling Cache self.diskCache = QNetworkDiskCache(self) self.diskCache.setCacheDirectory(self.cache_path) # Required for copy and paste clipboard integration QWebSettings.globalSettings().setAttribute( QWebSettings.JavascriptCanAccessClipboard, True) # Enabling Inspeclet only when --debug=True (requires more CPU usage) QWebSettings.globalSettings().setAttribute( QWebSettings.DeveloperExtrasEnabled, self.debug) # Sharing the same networkAccessManager self.networkAccessManager = QNetworkAccessManager(self) self.networkAccessManager.setCookieJar(self.cookiesjar) self.networkAccessManager.setCache(self.diskCache) def snippetsSettings(self): self.disable_snippets = self.settings.value("Snippets") if self.disable_snippets is not None: self.disable_snippets = self.disable_snippets == "False" else: self.disable_snippets = False if self.disable_snippets: disable_snippets_css = '' with open(Resources.get_path('disable_snippets.css'), 'r') as f: disable_snippets_css = f.read() with open(os.path.join(self.cache_path, 'resources.css'), 'a') as f: f.write(disable_snippets_css) def toggleFullScreen(self): if self.isFullScreen(): self.showMaximized() else: self.showFullScreen() def toggleMenuBar(self): menu = self.menuBar() state = menu.isHidden() menu.setVisible(state) if state: self.settings.setValue("Menu", "True") else: self.settings.setValue("Menu", "False") def restore(self): geometry = self.settings.value("geometry") if geometry is not None: self.restoreGeometry(geometry) windowState = self.settings.value("windowState") if windowState is not None: self.restoreState(windowState) else: self.setWindowState(QtCore.Qt.WindowMaximized) def systray(self, show=None): if show is None: show = self.settings.value("Systray") == "True" if show: self.tray.show() self.menus["file"]["close"].setEnabled(True) self.settings.setValue("Systray", "True") else: self.tray.setVisible(False) self.menus["file"]["close"].setEnabled(False) self.settings.setValue("Systray", "False") def readZoom(self): default = 1 if self.settings.value("Zoom") is not None: default = float(self.settings.value("Zoom")) return default def setZoom(self, factor=1): if factor > 0: for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) widget.setZoomFactor(factor) self.settings.setValue("Zoom", factor) def zoomIn(self): self.setZoom(self.current().zoomFactor() + 0.1) def zoomOut(self): self.setZoom(self.current().zoomFactor() - 0.1) def zoomReset(self): self.setZoom() def addTeam(self): self.switchTo(Resources.SIGNIN_URL) def addMenu(self): # We'll register the webpage shorcuts with the window too (Fixes #338) undo = self.current().pageAction(QWebPage.Undo) redo = self.current().pageAction(QWebPage.Redo) cut = self.current().pageAction(QWebPage.Cut) copy = self.current().pageAction(QWebPage.Copy) paste = self.current().pageAction(QWebPage.Paste) back = self.current().pageAction(QWebPage.Back) forward = self.current().pageAction(QWebPage.Forward) reload = self.current().pageAction(QWebPage.Reload) self.menus = { "file": { "preferences": self.createAction("Preferences", lambda: self.current().preferences()), "systray": self.createAction("Close to Tray", self.systray, None, True), "addTeam": self.createAction("Sign in to Another Team", lambda: self.addTeam()), "signout": self.createAction("Signout", lambda: self.current().logout()), "close": self.createAction("Close", self.close, QKeySequence.Close), "exit": self.createAction("Quit", self.exit, QKeySequence.Quit) }, "edit": { "undo": self.createAction( undo.text(), lambda: self.current().page().triggerAction(QWebPage.Undo), undo.shortcut()), "redo": self.createAction( redo.text(), lambda: self.current().page().triggerAction(QWebPage.Redo), redo.shortcut()), "cut": self.createAction( cut.text(), lambda: self.current().page().triggerAction(QWebPage.Cut), cut.shortcut()), "copy": self.createAction( copy.text(), lambda: self.current().page().triggerAction(QWebPage.Copy), copy.shortcut()), "paste": self.createAction( paste.text(), lambda: self.current().page().triggerAction( QWebPage.Paste), paste.shortcut()), "back": self.createAction( back.text(), lambda: self.current().page().triggerAction(QWebPage.Back), back.shortcut()), "forward": self.createAction( forward.text(), lambda: self.current().page(). triggerAction(QWebPage.Forward), forward.shortcut()), "reload": self.createAction( reload.text(), lambda: self.current().page().triggerAction( QWebPage.Reload), reload.shortcut()), }, "view": { "zoomin": self.createAction("Zoom In", self.zoomIn, QKeySequence.ZoomIn), "zoomout": self.createAction("Zoom Out", self.zoomOut, QKeySequence.ZoomOut), "reset": self.createAction("Reset", self.zoomReset, QtCore.Qt.CTRL + QtCore.Qt.Key_0), "fullscreen": self.createAction("Toggle Full Screen", self.toggleFullScreen, QtCore.Qt.Key_F11), "hidemenu": self.createAction("Toggle Menubar", self.toggleMenuBar, QtCore.Qt.Key_F12) }, "help": { "help": self.createAction("Help and Feedback", lambda: self.current().help(), QKeySequence.HelpContents), "center": self.createAction("Slack Help Center", lambda: self.current().helpCenter()), "about": self.createAction("About", lambda: self.current().about()) } } menu = self.menuBar() fileMenu = menu.addMenu("&File") fileMenu.addAction(self.menus["file"]["preferences"]) fileMenu.addAction(self.menus["file"]["systray"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["addTeam"]) fileMenu.addAction(self.menus["file"]["signout"]) fileMenu.addSeparator() fileMenu.addAction(self.menus["file"]["close"]) fileMenu.addAction(self.menus["file"]["exit"]) editMenu = menu.addMenu("&Edit") editMenu.addAction(self.menus["edit"]["undo"]) editMenu.addAction(self.menus["edit"]["redo"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["cut"]) editMenu.addAction(self.menus["edit"]["copy"]) editMenu.addAction(self.menus["edit"]["paste"]) editMenu.addSeparator() editMenu.addAction(self.menus["edit"]["back"]) editMenu.addAction(self.menus["edit"]["forward"]) editMenu.addAction(self.menus["edit"]["reload"]) viewMenu = menu.addMenu("&View") viewMenu.addAction(self.menus["view"]["zoomin"]) viewMenu.addAction(self.menus["view"]["zoomout"]) viewMenu.addAction(self.menus["view"]["reset"]) viewMenu.addSeparator() viewMenu.addAction(self.menus["view"]["fullscreen"]) viewMenu.addAction(self.menus["view"]["hidemenu"]) helpMenu = menu.addMenu("&Help") helpMenu.addAction(self.menus["help"]["help"]) helpMenu.addAction(self.menus["help"]["center"]) helpMenu.addSeparator() helpMenu.addAction(self.menus["help"]["about"]) self.enableMenus(False) showSystray = self.settings.value("Systray") == "True" self.menus["file"]["systray"].setChecked(showSystray) self.menus["file"]["close"].setEnabled(showSystray) # Restore menu visibility visible = self.settings.value("Menu") if visible is not None and visible == "False": menu.setVisible(False) def enableMenus(self, enabled): self.menus["file"]["preferences"].setEnabled(enabled == True) self.menus["file"]["addTeam"].setEnabled(enabled == True) self.menus["file"]["signout"].setEnabled(enabled == True) self.menus["help"]["help"].setEnabled(enabled == True) def createAction(self, text, slot, shortcut=None, checkable=False): action = QtWidgets.QAction(text, self) action.triggered.connect(slot) if shortcut is not None: action.setShortcut(shortcut) self.addAction(action) if checkable: action.setCheckable(True) return action def normalize(self, url): if url.endswith(".slack.com"): url += "/" elif not url.endswith(".slack.com/"): url = "https://" + url + ".slack.com/" return url def current(self): return self.stackedWidget.currentWidget() def teams(self, teams): if len(self.domains) == 0: self.domains.append(teams[0]['team_url']) team_list = [t['team_url'] for t in teams] for t in teams: for i in range(0, len(self.domains)): self.domains[i] = self.normalize(self.domains[i]) # When team_icon is missing, the team already exists (Fixes #381, #391) if 'team_icon' in t: if self.domains[i] in team_list: add = next(item for item in teams if item['team_url'] == self.domains[i]) if 'team_icon' in add: self.leftPane.addTeam(add['id'], add['team_name'], add['team_url'], add['team_icon']['image_44'], add == teams[0]) # Adding new teams and saving loading positions if t['team_url'] not in self.domains: self.leftPane.addTeam( t['id'], t['team_name'], t['team_url'], t['team_icon']['image_44'], t == teams[0]) self.domains.append(t['team_url']) self.settings.setValue("Domain", self.domains) if len(teams) > 1: self.leftPane.show() def switchTo(self, url): exists = False for i in range(0, self.stackedWidget.count()): if self.stackedWidget.widget(i).url().toString().startswith(url): self.stackedWidget.setCurrentIndex(i) self.quicklist(self.current().listChannels()) self.current().setFocus() self.leftPane.click(i) self.clearMemory() exists = True break if not exists: self.addWrapper(url) def eventFilter(self, obj, event): if event.type( ) == QtCore.QEvent.ActivationChange and self.isActiveWindow(): self.focusInEvent(event) if event.type() == QtCore.QEvent.KeyPress: # Ctrl + <n> modifiers = QtWidgets.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ControlModifier: if event.key() == QtCore.Qt.Key_1: self.leftPane.click(0) elif event.key() == QtCore.Qt.Key_2: self.leftPane.click(1) elif event.key() == QtCore.Qt.Key_3: self.leftPane.click(2) elif event.key() == QtCore.Qt.Key_4: self.leftPane.click(3) elif event.key() == QtCore.Qt.Key_5: self.leftPane.click(4) elif event.key() == QtCore.Qt.Key_6: self.leftPane.click(5) elif event.key() == QtCore.Qt.Key_7: self.leftPane.click(6) elif event.key() == QtCore.Qt.Key_8: self.leftPane.click(7) elif event.key() == QtCore.Qt.Key_9: self.leftPane.click(8) # Ctrl + Tab elif event.key() == QtCore.Qt.Key_Tab: self.leftPane.clickNext(1) # Ctrl + BackTab if (modifiers & QtCore.Qt.ControlModifier) and ( modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_Backtab: self.leftPane.clickNext(-1) # Ctrl + Shift + <key> if (modifiers & QtCore.Qt.ShiftModifier) and ( modifiers & QtCore.Qt.ShiftModifier): if event.key() == QtCore.Qt.Key_V: self.current().createSnippet() return QtWidgets.QMainWindow.eventFilter(self, obj, event) def focusInEvent(self, event): self.launcher.set_property("urgent", False) self.tray.stopAlert() # Let's tickle all teams on window focus, but only if tickle was not fired in last 30 minutes if DBusQtMainLoop is None and not self.tickler.isActive(): self.sendTickle() self.tickler.start() def titleChanged(self): self.setWindowTitle(self.current().title()) def setForceClose(self): self.forceClose = True def closeEvent(self, event): if not self.forceClose and self.settings.value("Systray") == "True": self.hide() event.ignore() else: self.cookiesjar.save() self.settings.setValue("Domain", self.domains) self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) self.settings.setValue("Domain", self.domains) self.forceClose = False def show(self): self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) self.activateWindow() self.setVisible(True) def exit(self): # Make sure tray is not visible (Fixes #513) self.tray.setVisible(False) self.setForceClose() self.close() def quicklist(self, channels): if Dbusmenu is not None: if channels is not None: ql = Dbusmenu.Menuitem.new() self.launcher.set_property("quicklist", ql) for c in channels: if type(c) is dict and hasattr( c, '__getitem__') and c['is_member']: item = Dbusmenu.Menuitem.new() item.property_set(Dbusmenu.MENUITEM_PROP_LABEL, "#" + c['name']) item.property_set("id", c['name']) item.property_set_bool(Dbusmenu.MENUITEM_PROP_VISIBLE, True) item.connect(Dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, self.current().openChannel) ql.child_append(item) self.launcher.set_property("quicklist", ql) def notify(self, title, message, icon): if self.debug: print("Notification: title [{}] message [{}] icon [{}]".format( title, message, icon)) self.notifier.notify(title, message, icon) self.alert() def alert(self): if not self.isActiveWindow(): self.launcher.set_property("urgent", True) self.tray.alert() if self.urgent_hint is True: QApplication.alert(self) def count(self): total = 0 unreads = 0 for i in range(0, self.stackedWidget.count()): widget = self.stackedWidget.widget(i) highlights = widget.highlights unreads += widget.unreads total += highlights if total > self.messages: self.alert() if 0 == total: self.launcher.set_property("count_visible", False) self.tray.setCounter(0) if unreads > 0: self.setWindowTitle("*{}".format(self.title)) else: self.setWindowTitle(self.title) else: self.tray.setCounter(total) self.launcher.set_property("count", total) self.launcher.set_property("count_visible", True) self.setWindowTitle("[{}]{}".format(str(total), self.title)) self.messages = total def clearMemory(self): QWebSettings.globalSettings().clearMemoryCaches() QWebSettings.globalSettings().clearIconDatabase()
class LiveCSS(QWidget): goto_declaration = pyqtSignal(object) def __init__(self, preview, parent=None): QWidget.__init__(self, parent) self.preview = preview 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.AlignTop | Qt.AlignLeft) s.addWidget(la) self.box = box = Box(self) box.hyperlink_activated.connect(self.goto_declaration, type=Qt.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 # We must let the event loop run otherwise the webview will return # stale data in read_data() 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.setPointSize(tprefs['editor_font_size']) self.setFont(f) theme = get_theme(tprefs['editor_theme']) pal = self.palette() pal.setColor(pal.Window, theme_color(theme, 'Normal', 'bg')) pal.setColor(pal.WindowText, theme_color(theme, 'Normal', 'fg')) pal.setColor(pal.AlternateBase, theme_color(theme, 'HighlightRegion', 'bg')) pal.setColor(pal.Link, theme_color(theme, 'Link', 'fg')) pal.setColor(pal.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: data = self.read_data(sourceline, 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 if self.now_showing == ( None, None, None) or self.now_showing[0] != self.current_name: self.clear() return # Try to refresh the data for the currently shown tag instead # of clearing editor_name, sourceline, tags = self.now_showing data = self.read_data(sourceline, tags) if data is None or len(data['computed_css']) < 1: 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 read_data(self, sourceline, tags): mf = self.preview.view.page().mainFrame() tags = [x.lower() for x in tags] result = str( mf.evaluateJavaScript( 'window.calibre_preview_integration.live_css(%s, %s)' % (json.dumps(sourceline), json.dumps(tags))) or '') try: result = json.loads(result) except ValueError: result = None if result is not None: maximum_specificities = {} for node in result['nodes']: is_ancestor = node['is_ancestor'] for rule in node['css']: self.process_rule(rule, is_ancestor, 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 return result def process_rule(self, rule, is_ancestor, 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))) ancestor_specificity = 0 if is_ancestor else 1 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, (0, 0, 0, 0, 0, 0)): maximum_specificities[p.name] = p.specificity rule['properties'] = properties href = rule['href'] if hasattr(href, 'startswith') and href.startswith( '%s://%s' % (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'])
class Splitter(QSplitter): state_changed = pyqtSignal(object) def __init__(self, name, label, icon, initial_show=True, initial_side_size=120, connect_button=True, orientation=Qt.Horizontal, side_index=0, parent=None, shortcut=None, hide_handle_on_single_panel=True): QSplitter.__init__(self, parent) 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.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), unicode_type(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.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 if val == 0 and not self.is_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) total = sum(self.sizes()) sizes = list(self.sizes()) for i in range(len(sizes)): sizes[i] = val if i == self.side_index else total - val self.setSizes(sizes) self.initialize() 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()
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() def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) l.setContentsMargins(0, 0, 0, 0) self.view = WebView(self) self.view.page().sync_requested.connect(self.request_sync) self.view.page().split_requested.connect(self.request_split) self.view.page().loadFinished.connect(self.load_finished) self.inspector = self.view.inspector self.inspector.setPage(self.view.page()) l.addWidget(self.view) 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.initialize('tweak_book_preview_search') self.search.setPlaceholderText(_('Search in preview')) self.search.returnPressed.connect(partial(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(partial(self.find, d)) self.bar.addAction(ac) def find(self, direction): text = unicode(self.search.text()) self.view.findText(text, QWebPage.FindWrapsAroundDocument | ( QWebPage.FindBackward if direction == 'prev' else QWebPage.FindFlags(0))) 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.view.page().go_to_anchor(urlparse(href).fragment, lnum) 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): if self.current_name: self.split_requested.emit(self.current_name, loc, totals) def sync_to_editor(self, name, sourceline_address): self.current_sync_request = (name, sourceline_address) QTimer.singleShot(100, self._sync_to_editor) def _sync_to_editor(self): if not actions['sync-preview-to-editor'].isChecked(): return try: if self.refresh_timer.isActive() or self.current_sync_request[0] != self.current_name: return QTimer.singleShot(100, self._sync_to_editor) except TypeError: return # Happens if current_sync_request is None sourceline_address = self.current_sync_request[1] self.current_sync_request = None 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 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.setUrl(QUrl.fromLocalFile(current_container().name_to_abspath(name))) 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 = QUrl.fromLocalFile(current_container().name_to_abspath(self.current_name)) self.refresh_starting.emit() if current_url != self.view.url(): # The container was changed self.view.setUrl(current_url) else: self.view.refresh() self.refreshed.emit() def clear(self): self.view.clear() self.current_name = None @property def current_root(self): return self.view.page().current_root @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(textwrap.fill(_( 'Abort file split') if checked else _( 'Split this file at a specified location.\n\nAfter 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_finished(self, ok): if actions['split-in-preview'].isChecked(): if ok: self.do_start_split() else: self.stop_split() def apply_settings(self): s = self.view.page().settings() s.setFontSize(s.DefaultFontSize, tprefs['preview_base_font_size']) s.setFontSize(s.DefaultFixedFontSize, tprefs['preview_mono_font_size']) s.setFontSize(s.MinimumLogicalFontSize, tprefs['preview_minimum_font_size']) s.setFontSize(s.MinimumFontSize, tprefs['preview_minimum_font_size']) sf, ssf, mf = tprefs['preview_serif_family'], tprefs['preview_sans_family'], tprefs['preview_mono_family'] s.setFontFamily(s.StandardFont, {'serif':sf, 'sans':ssf, 'mono':mf, None:sf}[tprefs['preview_standard_font_family']]) s.setFontFamily(s.SerifFont, sf) s.setFontFamily(s.SansSerifFont, ssf) s.setFontFamily(s.FixedFont, mf)
class BrowserWindow(QMainWindow): class SavedWindow(object): def __init__(self, window=None): ''' @param: window BrowserWindow ''' self.windowState = QByteArray() self.windowGeometry = QByteArray() self.windowUiState = {} # QString -> QVariant self.virtualDesktop = -1 self.currentTab = -1 self.tabs = [] # WebTab.SavedTab if window: self.init(window) def init(self, window): if window.isFullScreen(): self.windowState = QByteArray() else: self.windowState = window.saveState() self.windowGeometry = window.saveGeometry() self.windowUiState = window._saveUiState() tabsCount = window.tabCount() for idx in range(tabsCount): # TabbedWebView webView = window.weView(idx) if not webView: continue webTab = webView.webTab() if not webTab: continue tab = WebTab.SavedTab(webTab) if not tab.isValid(): continue if webTab.isCurrentTab(): self.currentTab = len(self.tabs) self.tabs.append(tab) def isValid(self): for tab in self.tabs: if not tab.isValid(): return False return self.currentTab > -1 def clear(self): self.windowState.clear() self.windowGeometry.clear() self.virtualDesktop = -1 self.currentTab = -1 self.tabs.clear() def __init__(self, type_, startUrl=QUrl()): super().__init__(None) self._startUrl = startUrl self._homepage = QUrl() self._windowType = type_ self._startTab = None # WebTab self._startPage = None # WebPage self._mainLayout = None # QVBoxLayout self._mainSplitter = None # QSplitter self._tabWidget = None # TabWidget self._sideBar = None # QPointer<SideBar> self._sideBarManager = SideBarManager(self) self._statusBar = None self._navigationContainer = None # NavigationContainer self._navigationToolbar = None # NavigationBar self._bookmarksToolbar = None # BookmarksToolbar self._progressBar = None # ProgressBar self._ipLabel = None # QLabel self._superMenu = None # QMenu self._mainMenu = None # MainMenu self._tabModel = None # TabModel self._tabMruModel = None # TabMruModel self._sideBarWidth = 0 self._webViewWidth = 0 # Shortcuts self._useTabNumberShortcuts = False self._useSpeedDialNumberShortcuts = False self._useSingleKeyShortcuts = False # Remember visibility of menubar and statusbar after entering Fullscreen self._menuBarVisible = False self._statusBarVisible = False self._htmlFullScreenView = None # TabbedWebView self._hideNavigationTimer = None # QTimer self._deleteOnCloseWidgets = [] # QList<QPointer<QWidget>> self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_DontCreateNativeAncestors) self.setObjectName('mainwindow') self.setWindowTitle(const.APPNAME) self.setProperty('private', gVar.app.isPrivate()) self._setupUi() self._setupMenu() self._hideNavigationTimer = QTimer(self) self._hideNavigationTimer.setInterval(1000) self._hideNavigationTimer.setSingleShot(True) self._hideNavigationTimer.timeout.connect(self._hideNavigationSlot) gVar.app.settingsReloaded.connect(self._loadSettings) QTimer.singleShot(0, self._postLaunch) if gVar.app.isPrivate(): gVar.appTools.setWmClass( '%s Browser (Private Window)' % const.APPNAME, self) else: gVar.appTools.setWmClass('%s Browser' % const.APPNAME, self) def __del__(self): gVar.app.plugins().emitMainWindowDeleted(self) for widget in self._deleteOnCloseWidgets: widget.deleteLater() def setStartTab(self, tab): ''' @param: tab WebTab ''' self._startTab = tab def setStartPage(self, page): ''' @param: page WebPage ''' self._startPage = page def restoreWindow(self, window): ''' @param: window SavedWindow ''' self.restoreState(window.windowState) self.restoreGeometry(window.windowGeometry) self._restoreUiState(window.windowUiState) if not gVar.app.isTestModeEnabled(): self.show( ) # Window has to be visible before adding QWebEngineView's self._tabWidget.restoreState(window.tabs, window.currentTab) self._updateStartupFocus() def fullScreenNavigationVisible(self): return self._navigationContainer.isVisible() def showNavigationWithFullScreen(self): if self._htmlFullScreenView: return if self._hideNavigationTimer.isActive(): self._hideNavigationTimer.stop() self._navigationContainer.show() def hideNavigationWithFullScreen(self): if self._tabWidget.isCurrentTabFresh(): return if not self._hideNavigationTimer.isActive(): self._hideNavigationTimer.start() def currentTabChanged(self): view = self.weView() self._navigationToolbar.setCurrentView(view) if not view: return title = view.webTab().title(True) # allowEmpty if not title: self.setWindowTitle(const.APPNAME) else: self.setWindowTitle('%s - %s' % (title, const.APPNAME)) self._ipLabel.setText(view.getIp()) view.setFocus() self.updateLoadingActions() # Settings correct tab order (LocationBar -> WebSearchBar -> WebView) self.setTabOrder(self.locationBar(), self._navigationToolbar.webSearchBar()) self.setTabOrder(self._navigationToolbar.webSearchBar(), view) def updateLoadingActions(self): view = self.weView() if not view: return isLoading = view.isLoading() self._ipLabel.setVisible(not isLoading) self._progressBar.setVisible(isLoading) self.action('View/Stop').setEnabled(isLoading) self.action('View/Reload').setEnabled(not isLoading) if isLoading: self._progressBar.setValue(view.loadingProgress()) self._navigationToolbar.showStopButton() else: self._navigationToolbar.showReloadButton() def addBookmark(self, url, title): BookmarksTools.addBookmarkDilaog(self, url, title) def addDeleteOnCloseWidget(self, widget): ''' @param: widget QWidget ''' if widget not in self._deleteOnCloseWidgets: self._deleteOnCloseWidgets.append(widget) def createToolbarsMenu(self, menu): self.removeActions(menu.actions()) menu.clear() action = menu.addAction('&Menu Bar', self.toggleShowMenubar) action.setCheckable(True) action.setChecked(self.menuBar().isVisible()) action = menu.addAction('&Navigation Toolbar', self.toggleShowNavigationToolbar) action.setCheckable(True) action.setChecked(self._navigationToolbar.isVisible()) action = menu.addAction('&Bookmarks Toolbar', self.toggleShowBookmarksToolbar) action.setCheckable(True) action.setChecked(Settings().value( 'Browser-View-Settings/showBookmarksToolbar', True)) menu.addSeparator() action = menu.addAction('&Tabs on Top', self.toggleTabsOnTop) action.setCheckable(True) action.setChecked(gVar.appSettings.tabsOnTop) self.addActions(menu.actions()) def createSidebarsMenu(self, menu): self._sideBarManager.createMenu(menu) def createEncodingMenu(self, menu): isoCodecs = [] utfCodecs = [] windowsCodecs = [] isciiCodecs = [] ibmCodecs = [] otherCodecs = [] allCodecs = [] for mib in QTextCodec.availableMibs(): codecName = QTextCodec.codecForMib(mib).name() codecName = codecName.data().decode('utf8') if codecName in allCodecs: continue allCodecs.append(codecName) if codecName.startswith('ISO'): isoCodecs.append(codecName) elif codecName.startswith('UTF'): utfCodecs.append(codecName) elif codecName.startswith('windows'): windowsCodecs.append(codecName) elif codecName.startswith('Iscii'): isciiCodecs.append(codecName) elif codecName.startswith('IBM'): ibmCodecs.append(codecName) else: otherCodecs.append(codecName) if not menu.isEmpty(): menu.addSeperator() self._createEncodingSubMenu('ISO', isoCodecs, menu) self._createEncodingSubMenu('UTF', utfCodecs, menu) self._createEncodingSubMenu('Windows', windowsCodecs, menu) self._createEncodingSubMenu('Iscii', isciiCodecs, menu) self._createEncodingSubMenu('IBM', ibmCodecs, menu) self._createEncodingSubMenu('Other', otherCodecs, menu) def removeActions(self, actions): ''' @param: actions QList<QAction*> ''' for action in actions: self.removeAction(action) def addSideBar(self): ''' @return SideBar* ''' if self._sideBar: return self._sideBar self._sideBar = SideBar(self._sideBarManager, self) self._mainSplitter.insertWidget(0, self._sideBar) self._mainSplitter.setCollapsible(0, False) self._mainSplitter.setSizes([self._sideBarWidth, self._webViewWidth]) return self._sideBar def setSideBarNone(self): ''' @note: when sideBar is notified destroyed, we should clean python ref in window ''' self._sideBar = None def saveSideBarSettings(self): if self._sideBar: # That +1 is important here, without it, the sidebar width would # decrease by 1 pixel every close self._sideBarWidth = self._mainSplitter.sizes()[0] + 1 self._webViewWidth = self.width() - self._sideBarWidth Settings().setValue('Browser-View-Settings/SideBar', self._sideBarManager.activeSideBar()) def tabCount(self): return self._tabWidget.count() def weView(self, index=None): ''' @return TabbedWebView ''' if index is None: index = self._tabWidget.currentIndex() webTab = self._tabWidget.widget(index) if not webTab: return None return webTab.webView() def windowType(self): ''' @return const.BrowserWindowType ''' return self._windowType def locationBar(self): ''' @return LocationBar ''' return self._tabWidget.locationBars().currentWidget() def tabWidget(self): return self._tabWidget def bookmarksToolbar(self): ''' @return BookmarksToolbar ''' return self._bookmarksToolbar def statusBar(self): ''' @return StatusBar ''' return self._statusBar def navigationBar(self): ''' @return NavigationBar ''' return self._navigationToolbar def sideBarManager(self): ''' @return SideBarManager ''' return self._sideBarManager def ipLabel(self): ''' @return QLabel ''' return self._ipLabel def superMenu(self): ''' @return QMenu ''' return self._superMenu def homepageUrl(self): return self._homepage def action(self, name): ''' @return QAction ''' return self._mainMenu.action(name) def tabModel(self): ''' @return TabModel ''' return self._tabModel def tabMruModel(self): ''' @return TabMruModel ''' return self._tabMruModel # public Q_SIGNALS: startingCompleted = pyqtSignal() aboutToClose = pyqtSignal() # public Q_SLOTS: def addTab(self): self._tabWidget.addViewByUrl(QUrl(), const.NT_SelectedNewEmptyTab, True) self._tabWidget.setCurrentTabFresh(True) if self.isFullScreen(): self.showNavigationWithFullScreen() def goHome(self): self.loadAddress(self._homepage) def goHomeInNewTab(self): self._tabWidget.addViewByUrl(self._homepage, const.NT_SelectedTab) def goBack(self): self.weView().back() def goForward(self): self.weView().forward() def reload(self): self.weView().reload() def reloadBypassCache(self): self.weView().reloadBypassCache() def setWindowTitle(self, title): if gVar.app.isPrivate(): title = '%s (Private Browsing)' % title super().setWindowTitle(title) def showWebInspector(self): webView = self.weView() if webView and webView.webTab(): webView.webTab().showWebInspector() def toggleWebInspector(self): webView = self.weView() if webView and webView.webTab(): webView.webTab().toggleWebInspector() def showHistoryManager(self): gVar.app.browsingLibrary().showHistory(self) def toggleShowMenubar(self): self.setUpdatesEnabled(False) menuBar = self.menuBar() menuBar.setVisible(not menuBar.isVisible()) self._navigationToolbar.setSuperMenuVisible(not menuBar.isVisible()) self.setUpdatesEnabled(True) Settings().setValue('Browser-View-Settings/showMenuBar', menuBar.isVisible()) # Make sure we show Navigation Toolbar when Menu Bar is hidden if not self._navigationToolbar.isVisible() and not menuBar.isVisible(): self.toggleShowNavigationToolbar() def toggleShowStatusBar(self): self.setUpdatesEnabled(False) self._statusBar.setVisible(not self._statusBar.isVisible()) self.setUpdatesEnabled(True) Settings().setValue('Browser-View-Settings/showStatusBar', self._statusBar.isVisible()) def toggleShowBookmarksToolbar(self): self.setUpdatesEnabled(False) self._bookmarksToolbar.setVisible( not self._bookmarksToolbar.isVisible()) self.setUpdatesEnabled(True) Settings().setValue('Browser-View-Settings/showBookmarksToolbar', self._bookmarksToolbar.isVisible()) Settings().setValue('Browser-View-Settings/instantBookmarksToolbar', False) def toggleShowNavigationToolbar(self): self.setUpdatesEnabled(False) self._navigationToolbar.setVisible( not self._navigationToolbar.isVisible()) self.setUpdatesEnabled(True) Settings().setValue('Browser-View-Settings/showNavigationToolbar', self._navigationToolbar.isVisible()) # Make sure we show Navigation Toolbar when Menu Bar is hidden if not self._navigationToolbar.isVisible() and not self.menuBar( ).isVisible(): self.toggleShowMenubar() def toggleTabsOnTop(self, enable): gVar.appSettings.tabsOnTop = enable self._navigationContainer.toggleTabsOnTop(enable) def toggleFullScreen(self): if self._htmlFullScreenView: self.weView().triggerPageAction(QWebEnginePage.ExitFullScreen) return if self.isFullScreen(): self.setWindowState(self.windowState() & ~Qt.WindowFullScreen) else: self.setWindowState(self.windowState() | Qt.WindowFullScreen) def requestHtmlFullScreen(self, view, enable): ''' @param: view TabbedWebView @param: enable bool ''' if enable: self.setWindowState(self.windowState() | Qt.WindowFullScreen) else: self.setWindowState(self.windowState() & ~Qt.WindowFullScreen) if self._sideBar: self._sideBar.setHidden(enable) self._htmlFullScreenView = enable and view or None def loadActionUrl(self, obj=None): if not obj: obj = self.sender() action = obj if isinstance(action, QAction): self.loadAddress(action.data().toUrl()) def loadActionUrlInNewTab(self, obj=None): if not obj: obj = self.sender() action = obj if isinstance(action, QAction): self.loadAddress(action.data().toUrl(), const.NT_SelectedTabAtTheEnd) def bookmarkPage(self): view = self.weView() BookmarksTools.addBookmarkDialog(self, view.url(), view.title()) def bookmarkAllTabs(self): BookmarksTools.bookmarkAllTabsDialog(self, self._tabWidget) def loadAddress(self, url): webView = self.weView() if webView.webTab().isPinned(): index = self._tabWidget.addViewByUrl( url, gVar.appSettings.newTabPosition) self.weView(index).setFocus() else: webView.setFocus() webView.loadByReq(LoadRequest(url)) def showSource(self, view=None): if not view: view = self.weView() if not view: return view.showSource() # private Q_SLOTS: def _openLocation(self): if self.isFullScreen(): self.showNavigationWithFullScreen() self.locationBar().setFocus() self.locationBar().selectAll() def _openFile(self): fileTypes = ("%s(*.html *.htm *.shtml *.shtm *.xhtml);;" "%s(*.png *.jpg *.jpeg *.bmp *.gif *.svg *.tiff);;" "%s(*.txt);;" "%s(*.*)") fileTypes %= ("HTML files", "Image files", "Text files", "All files") filePath = gVar.appTools.getOpenFileName("MainWindow-openFile", self, "Open file...", QDir.homePath(), fileTypes) if filePath: self.loadAddress(QUrl.fromLocalFile(filePath)) def _closeWindow(self): if const.OS_MAC: self.close() return if gVar.app.windowCount() > 1: self.close() def _closeTab(self): # Don't close pinned tabs with keyboard shortcuts (Ctrl+w, Ctrl+F4) webView = self.weView() if webView and not webView.webTab().isPinned(): self._tabWidget.requestCloseTab() def _loadSettings(self): settings = Settings() # Url settings settings.beginGroup('Web-URL-Settings') self._homepage = settings.value('homepage', 'app:start', type=QUrl) settings.endGroup() # Browser Window settings settings.beginGroup('Browser-View-Settings') showStatusBar = settings.value('showStatusBar', False) showBookmarksToolbar = settings.value('showBookmarksToolbar', False) showNavigationToolbar = settings.value('showNavigationToolbar', True) showMenuBar = settings.value('showMenuBar', False) # Make sure both menubar and navigationbar are not hidden if not showNavigationToolbar: showMenuBar = True settings.setValue('showMenubar', True) settings.endGroup() settings.beginGroup('Shortcuts') self._useTabNumberShortcuts = settings.value('useTabNumberShortcuts', True) self._useSpeedDialNumberShortcuts = settings.value( 'useSpeedDialNumberShortcuts', True) self._useSingleKeyShortcuts = settings.value('useSingleKeyShortcuts', False) settings.endGroup() settings.beginGroup('Web-Browser-Settings') quitAction = self._mainMenu.action('Standard/Quit') if settings.value('closeAppWithCtrlQ', True): quitAction.setShortcut( gVar.appTools.actionShortcut(QKeySequence.Quit, QKeySequence('Ctrl+Q'))) else: quitAction.setShortcut(QKeySequence()) settings.endGroup() self._statusBarVisible = showStatusBar self.statusBar().setVisible(not self.isFullScreen() and showStatusBar) self._bookmarksToolbar.setVisible(showBookmarksToolbar) self._navigationToolbar.setVisible(showNavigationToolbar) if not const.OS_MACOS: self._menuBarVisible = showMenuBar self.menuBar().setVisible(not self.isFullScreen() and showMenuBar) showSuperMenu = self.isFullScreen() or not showMenuBar # TODO: debug code showSuperMenu = True self._navigationToolbar.setSuperMenuVisible(showSuperMenu) def _postLaunch(self): # noqa C901 self._loadSettings() addTab = True startUrl = QUrl() from .MainApplication import MainApplication afterLaunch = gVar.app.afterLaunch() if afterLaunch == MainApplication.OpenBlankPage: startUrl = QUrl() elif afterLaunch == MainApplication.OpenSpeedDial: startUrl = QUrl('app:speeddial') elif afterLaunch in (MainApplication.OpenHomePage, MainApplication.RestoreSession, MainApplication.SelectSession): startUrl = QUrl(self._homepage) if not gVar.app.isTestModeEnabled(): self.show() if self._windowType == const.BW_FirstAppWindow: if gVar.app.isStartingAfterCrash(): addTab = False startUrl.clear() self._tabWidget.addViewByUrl(QUrl('app:restore'), const.NT_CleanSelectedTabAtTheEnd) elif afterLaunch in (MainApplication.SelectSession, MainApplication.RestoreSession): addTab = self._tabWidget.count() <= 0 elif self._windowType in (const.BW_NewWindow, const.BW_MacFirstWindow): addTab = True elif self._windowType == const.BW_OtherRestoredWindow: addTab = False if not self._startUrl.isEmpty(): startUrl = QUrl(self._startUrl) addTab = True if self._startTab: addTab = False self._tabWidget.addViewByTab(self._startTab, const.NT_SelectedTab) if self._startPage: addTab = False self._tabWidget.addViewByUrl(QUrl()) self.weView().setPage(self._startPage) if addTab: self._tabWidget.addViewByUrl(startUrl, const.NT_CleanSelectedTabAtTheEnd) if not startUrl or startUrl == 'app:speeddial': self.locationBar().setFocus() # Someting went really wrong .. add one tab if self._tabWidget.count() <= 0: self._tabWidget.addViewByUrl(self._homepage, const.NT_SelectedTabAtTheEnd) gVar.app.plugins().emitMainWindowCreated(self) self.startingCompleted.emit() self.raise_() self.activateWindow() self._updateStartupFocus() def _webSearch(self): self._navigationToolbar.webSearchBar().setFocus() self._navigationToolbar.webSearchBar().selectAll() def _searchOnPage(self): webView = self.weView() if webView and webView.webTab(): searchText = webView.page().selectedText() if '\n' not in searchText: webView.webTab().showSearchToolBar(searchText) else: webView.webTab().showSearchToolBar() def _changeEncoding(self): action = self.sender() if action: encoding = action.data() gVar.app.webSettings().setDefaultTextEncoding(encoding) Settings().setValue('Web-Browser-Settings/DefaultEncoding', encoding) self.weView().reload() def _printPage(self): self.weView().printPage() def _saveSettings(self): if gVar.app.isPrivate(): return settings = Settings() settings.beginGroup('Browser-View-Settings') settings.setValue('WindowGeometry', self.saveGeometry()) state = self._saveUiState() for key, val in state.items(): settings.setValue(key, val) settings.endGroup() def _hideNavigationSlot(self): view = self.weView() mouseInView = view and view.underMouse() if self.isFullScreen() and mouseInView: self._navigationContainer.hide() # private # override def event(self, event): ''' @param: event QEvent ''' if event.type() == QEvent.WindowStateChange: e = event assert (isinstance(e, QWindowStateChangeEvent)) if not (e.oldState() & Qt.WindowFullScreen) and ( self.windowState() & Qt.WindowFullScreen): # Enter fullscreen self._statusBarVisible = self._statusBar.isVisible() if not const.OS_MACOS: self._menuBarVisible = self.menuBar().isVisible() self.menuBar().hide() self._statusBar.hide() self._navigationContainer.hide() self._navigationToolbar.enterFullScreen() # Show main menu button since menubar is hidden self._navigationToolbar.setSuperMenuVisible(True) elif (e.oldState() & Qt.WindowFullScreen ) and not (self.windowState() & Qt.WindowFullScreen): # Leave fullscreen self._statusBar.setVisible(self._statusBarVisible) if not const.OS_MACOS: self.menuBar().setVisible(self._menuBarVisible) self._navigationContainer.show() self._navigationToolbar.setSuperMenuVisible( not self._menuBarVisible) self._navigationToolbar.leaveFullScreen() self._htmlFullScreenView = None if self._hideNavigationTimer: self._hideNavigationTimer.stop() return super().event(event) # override def resizeEvent(self, event): ''' @param: event QResizeEvent ''' self._bookmarksToolbar.setMaximumWidth(self.width()) super().resizeEvent(event) # override def keyPressEvent(self, event): # noqa C901 ''' @param: event QKeyEvent ''' if gVar.app.plugins().processKeyPress(const.ON_BrowserWindow, self, event): return number = -1 # TabbedWebView view = self.weView() evtKey = event.key() if evtKey == Qt.Key_Back: if view: view.back() event.accept() elif evtKey == Qt.Key_Forward: if view: view.forward() event.accept() elif evtKey == Qt.Key_Stop: if view: view.stop() event.accept() elif evtKey in (Qt.Key_Reload, Qt.Key_Refresh): if view: view.reload() event.accept() elif evtKey == Qt.Key_HomePage: self.goHome() event.accept() elif evtKey == Qt.Key_Favorites: gVar.app.browsingLibrary().showBookmarks(self) event.accept() elif evtKey == Qt.Key_Search: self.searchOnPage() event.accept() elif evtKey in (Qt.Key_F6, Qt.Key_OpenUrl): self.openLocation() event.accept() elif evtKey == Qt.Key_History: self.showHistoryManager() event.accept() elif evtKey == Qt.Key_AddFavorite: self.bookmarkPage() event.accept() elif evtKey == Qt.Key_News: self.action("Tools/RssReader").trigger() event.accept() elif evtKey == Qt.Key_Tools: self.action("Standard/Preferences").trigger() event.accept() elif evtKey == Qt.Key_Tab: if event.modifiers() == Qt.ControlModifier: self._tabWidget.event(event) elif evtKey == Qt.Key_Backtab: if event.modifiers() == Qt.ControlModifier + Qt.ShiftModifier: self._tabWidget.event(event) elif evtKey == Qt.Key_PageDown: if event.modifiers() == Qt.ControlModifier: self._tabWidget.nextTab() event.accept() elif evtKey == Qt.Key_PageUp: if event.modifiers() == Qt.ControlModifier: self._tabWidget.previousTab() event.accept() elif evtKey == Qt.Key_Equal: if view and event.modifiers() == Qt.ControlModifier: view.zoomIn() event.accept() elif evtKey == Qt.Key_I: if event.modifiers() == Qt.ControlModifier: self.action("Tools/SiteInfo").trigger() event.accept() elif evtKey == Qt.Key_U: if event.modifiers() == Qt.ControlModifier: self.action("View/PageSource").trigger() event.accept() elif evtKey == Qt.Key_F: if event.modifiers() == Qt.ControlModifier: self.action("Edit/Find").trigger() event.accept() elif evtKey == Qt.Key_Slash: if self._useSingleKeyShortcuts: self.action("Edit/Find").trigger() event.accept() elif evtKey == Qt.Key_1: number = 1 elif evtKey == Qt.Key_2: number = 2 elif evtKey == Qt.Key_3: number = 3 elif evtKey == Qt.Key_4: number = 4 elif evtKey == Qt.Key_5: number = 5 elif evtKey == Qt.Key_6: number = 6 elif evtKey == Qt.Key_7: number = 7 elif evtKey == Qt.Key_8: number = 8 elif evtKey == Qt.Key_9: number = 9 if number != -1: modifiers = event.modifiers() if modifiers & Qt.AltModifier and self._useTabNumberShortcuts: if number == 9: number = self._tabWidget.count() self._tabWidget.setCurrentIndex(number - 1) event.accept() return if modifiers & Qt.ControlModifier and self._useSpeedDialNumberShortcuts: # QUrl url = gVar.app.plugins().speedDial().urlForShortcut(number - 1) if url.isValid(): self.loadAddress(url) event.accept() return if modifiers == Qt.NoModifier and self._useSingleKeyShortcuts: if number == 1: self._tabWidget.previousTab() if number == 2: self._tabWidget.nextTab() super().keyPressEvent(event) # override def keyReleaseEvent(self, event): ''' @param: event QKeyEvent ''' if gVar.app.plugins().processKeyRelease(const.ON_BrowserWindow, self, event): return evtKey = event.key() if evtKey == Qt.Key_F: if event.modifiers() == Qt.ControlModifier: self.action('Edit/Find').trigger() event.accept() super().keyReleaseEvent(event) # override def closeEvent(self, event): ''' @param: event QCloseEvent ''' if gVar.app.isClosing(): self._saveSettings() return settings = Settings() askOnClose = settings.value('Browser-Tabs-Settings/AskOnClosing', True) from .MainApplication import MainApplication if gVar.app.afterLaunch() in (MainApplication.SelectSession, MainApplication.RestoreSession ) and gVar.app.windowCount() == 1: askOnClose = False if askOnClose and self._tabWidget.normalTabsCount() > 1: dialog = CheckBoxDialog(QMessageBox.Yes | QMessageBox.No, self) dialog.setDefaultButton(QMessageBox.No) dialog.setText( "There are still %s open tabs and your session won't be stored.\n" % self._tabWidget.count() + "Are you sure you want to close this window?") dialog.setCheckBoxText("Don't ask again") dialog.setWindowTitle('There are still open tabs') dialog.setIcon(QMessageBox.Warning) if dialog.exec_() != QMessageBox.Yes: event.ignore() return if dialog.isChecked(): settings.setValue('Browser-Tabs-Settings/AskOnClosing', False) self.aboutToClose.emit() self._saveSettings() gVar.app.closedWindowsManager().saveWindow(self) if gVar.app.windowCount() == 1: gVar.app.quitApplication() event.accept() # == private == def _setupUi(self): settings = Settings() settings.beginGroup('Browser-View-Settings') windowGeometry = settings.value('WindowGeometry', b'') keys = [ ('LocationBarWidth', int), ('WebSearchBarWidth', int), ('SideBarWidth', int), ('WebViewWidth', int), ('SideBar', str), ] uiState = {} for key, typ in keys: if settings.contains(key): uiState[key] = typ(settings.value(key)) settings.endGroup() widget = QWidget(self) widget.setCursor(Qt.ArrowCursor) self.setCentralWidget(widget) self._mainLayout = QVBoxLayout(widget) self._mainLayout.setContentsMargins(0, 0, 0, 0) self._mainLayout.setSpacing(0) self._mainSplitter = QSplitter(self) self._mainSplitter.setObjectName('sidebar-splitter') self._tabWidget = TabWidget(self) self._superMenu = QMenu(self) self._navigationToolbar = NavigationBar(self) self._bookmarksToolbar = BookmarksToolbar(self) self._tabModel = TabModel(self, self) self._tabMruModel = TabMruModel(self, self) self._tabMruModel.setSourceModel(self._tabModel) self._navigationContainer = NavigationContainer(self) self._navigationContainer.addWidget(self._navigationToolbar) self._navigationContainer.addWidget(self._bookmarksToolbar) self._navigationContainer.setTabBar(self._tabWidget.tabBar()) self._mainSplitter.addWidget(self._tabWidget) self._mainSplitter.setCollapsible(0, False) self._mainLayout.addWidget(self._navigationContainer) self._mainLayout.addWidget(self._mainSplitter) self._statusBar = StatusBar(self) self._statusBar.setObjectName('mainwindow-statusbar') self._statusBar.setCursor(Qt.ArrowCursor) self.setStatusBar(self._statusBar) self._progressBar = ProgressBar(self._statusBar) self._ipLabel = QLabel(self) self._ipLabel.setObjectName('statusbar-ip-label') self._ipLabel.setToolTip('IP Address of current page') self._statusBar.addPermanentWidget(self._progressBar) self._statusBar.addPermanentWidget(self.ipLabel()) downloadsButton = DownloadsButton(self) self._statusBar.addButton(downloadsButton) self._navigationToolbar.addToolButton(downloadsButton) desktop = gVar.app.desktop() windowWidth = desktop.availableGeometry().width() / 1.3 windowHeight = desktop.availableGeometry().height() / 1.3 # Let the WM decides where to put new browser window if self._windowType not in (const.BW_FirstAppWindow, const.BW_MacFirstWindow) and \ gVar.app.getWindow(): if const.OS_WIN: # Windows WM places every new window in the middle of screen .. for some reason p = gVar.app.getWindow().geometry().topLeft() p.setX(p.x() + 30) p.setY(p.y() + 30) if not desktop.availableGeometry( gVar.app.getWindow()).contains(p): p.setX( desktop.availableGeometry(gVar.app.getWindow()).x() + 30) p.setY( desktop.availableGeometry(gVar.app.getWindow()).y() + 30) self.setGeometry(QRect(p, gVar.app.getWindow().size())) else: self.resize(gVar.app.getWindow().size()) elif not self.restoreGeometry(windowGeometry): if const.OS_WIN: self.setGeometry( QRect( desktop.availableGeometry(gVar.app.getWindow()).x() + 30, desktop.availableGeometry(gVar.app.getWindow()).y() + 30, windowWidth, windowHeight)) else: self.resize(windowWidth, windowHeight) self._restoreUiState(uiState) # Set some sane minimum width self.setMinimumWidth(300) def _setupMenu(self): if const.OS_MACOS: macMainMenu = None if not macMainMenu: macMainMenu = MainMenu(self, 0) macMainMenu.initMenuBar(QMenuBar(0)) gVar.app.activeWindowChanged.connect(macMainMenu.setWindow) else: macMainMenu.setWindow(self) else: self.setMenuBar(MenuBar(self)) self._mainMenu = MainMenu(self, self) self._mainMenu.initMenuBar(self.menuBar()) self._mainMenu.initSuperMenu(self._superMenu) # Setup other shortcuts reloadBypassCacheAction = QShortcut(QKeySequence('Ctrl+F5'), self) reloadBypassCacheAction2 = QShortcut(QKeySequence('Ctrl+Shift+R'), self) reloadBypassCacheAction.activated.connect(self.reloadBypassCache) reloadBypassCacheAction2.activated.connect(self.reloadBypassCache) closeTabAction = QShortcut(QKeySequence('Ctrl+W'), self) closeTabAction2 = QShortcut(QKeySequence('Ctrl+F4'), self) closeTabAction.activated.connect(self._closeTab) closeTabAction2.activated.connect(self._closeTab) reloadAction = QShortcut(QKeySequence('Ctrl+R'), self) reloadAction.activated.connect(self.reload) openLocationAction = QShortcut(QKeySequence('Alt+D'), self) openLocationAction.activated.connect(self._openLocation) inspectorAction = QShortcut(QKeySequence('F12'), self) inspectorAction.activated.connect(self.toggleWebInspector) restoreClosedWindow = QShortcut(QKeySequence('Ctrl+Shift+N'), self) restoreClosedWindow.activated.connect( gVar.app.closedWindowsManager().restoreClosedWindow) def _updateStartupFocus(self): def _updateStartupFocusCb(): # Scroll to current tab self.tabWidget().tabBar().ensureVisible() # Update focus page = self.weView().page() url = page.requestedUrl() if not self._startPage and not LocationBar.convertUrlToText(url): self.locationBar().setFocus() else: self.weView().setFocus() QTimer.singleShot(500, _updateStartupFocusCb) def _createEncodingAction(self, codecName, activeCodecName, menu): ''' @param: codecName QString @param: activeCodecName QString @param: menu QMenu ''' action = QAction(codecName, menu) action.setData(codecName) action.setCheckable(True) action.triggered.connect(self._changeEncoding) if activeCodecName.lower() == codecName.lower(): action.setChecked(True) return action def _createEncodingSubMenu(self, name, codecNames, menu): ''' @param: name QString @param: codecName QStringList @param: menu QMenu ''' if not codecNames: return codecNames.sort() subMenu = QMenu(name, menu) activeCodecName = gVar.app.webSettings().defaultTextEncoding() group = QActionGroup(subMenu) for codecName in codecNames: act = self._createEncodingAction(codecName, activeCodecName, subMenu) group.addAction(act) subMenu.addAction(act) menu.addMenu(subMenu) def _saveUiState(self): ''' @return: QHash<QStirng, QVariant> ''' self.saveSideBarSettings() state = {} state['LocationBarWidth'] = self._navigationToolbar.splitter().sizes( )[0] state['WebSearchBarWidth'] = self._navigationToolbar.splitter().sizes( )[1] state['SideBarWidth'] = self._sideBarWidth state['WebViewWidth'] = self._webViewWidth state['SideBar'] = self._sideBarManager.activeSideBar() return state def _restoreUiState(self, state): ''' @param: state QHash<QString, QVariant> ''' locationBarWidth = state.get('LocationBarWidth', 480) webSearchBarWidth = state.get('WebSearchBarWidth', 140) self._navigationToolbar.setSplitterSizes(locationBarWidth, webSearchBarWidth) self._sideBarWidth = state.get('SideBarWidth', 250) self._webViewWidth = state.get('WebViewWidth', 2000) if self._sideBar: self._mainSplitter.setSizes( [self._sideBarWidth, self._webViewWidth]) activeSideBar = state.get('SideBar') if not activeSideBar and self._sideBar: self._sideBar.close() else: self._sideBarManager.showSideBar(activeSideBar, False)
class Splitter(QSplitter): state_changed = pyqtSignal(object) def __init__(self, name, label, icon, initial_show=True, initial_side_size=120, connect_button=True, orientation=Qt.Horizontal, side_index=0, parent=None, shortcut=None, hide_handle_on_single_panel=True): QSplitter.__init__(self, parent) 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.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), unicode_type(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.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]) @dynamic_property def side_index_size(self): def fget(self): if self.count() < 2: return 0 return self.sizes()[self.side_index] def fset(self, val): if self.count() < 2: return if val == 0 and not self.is_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) total = sum(self.sizes()) sizes = list(self.sizes()) for i in range(len(sizes)): sizes[i] = val if i == self.side_index else total-val self.setSizes(sizes) self.initialize() return property(fget=fget, fset=fset) 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()
class CoverDelegate(QStyledItemDelegate): # {{{ needs_redraw = pyqtSignal() def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) self.angle = 0 self.timer = QTimer(self) self.timer.timeout.connect(self.frame_changed) self.color = parent.palette().color(QPalette.WindowText) self.spinner_width = 64 def frame_changed(self, *args): self.angle = (self.angle + 30) % 360 self.needs_redraw.emit() def start_animation(self): self.angle = 0 self.timer.start(200) def stop_animation(self): self.timer.stop() def draw_spinner(self, painter, rect): width = rect.width() outer_radius = (width - 1) * 0.5 inner_radius = (width - 1) * 0.5 * 0.38 capsule_height = outer_radius - inner_radius capsule_width = int(capsule_height * (0.23 if width > 32 else 0.35)) capsule_radius = capsule_width // 2 painter.save() painter.setRenderHint(painter.Antialiasing) for i in xrange(12): color = QColor(self.color) color.setAlphaF(1.0 - (i / 12.0)) painter.setPen(Qt.NoPen) painter.setBrush(color) painter.save() painter.translate(rect.center()) painter.rotate(self.angle - i * 30.0) painter.drawRoundedRect( -capsule_width * 0.5, -(inner_radius + capsule_height), capsule_width, capsule_height, capsule_radius, capsule_radius, ) painter.restore() painter.restore() def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) style = QApplication.style() waiting = self.timer.isActive() and bool(index.data(Qt.UserRole)) if waiting: rect = QRect(0, 0, self.spinner_width, self.spinner_width) rect.moveCenter(option.rect.center()) self.draw_spinner(painter, rect) else: # Ensure the cover is rendered over any selection rect style.drawItemPixmap( painter, option.rect, Qt.AlignTop | Qt.AlignHCenter, QPixmap(index.data(Qt.DecorationRole)) )
class LiveCSS(QWidget): goto_declaration = pyqtSignal(object) def __init__(self, preview, parent=None): QWidget.__init__(self, parent) self.preview = preview 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.AlignTop | Qt.AlignLeft) s.addWidget(la) self.box = box = Box(self) box.hyperlink_activated.connect(self.goto_declaration, type=Qt.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 # We must let the event loop run otherwise the webview will return # stale data in read_data() 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.setPointSize(tprefs["editor_font_size"]) self.setFont(f) theme = get_theme(tprefs["editor_theme"]) pal = self.palette() pal.setColor(pal.Window, theme_color(theme, "Normal", "bg")) pal.setColor(pal.WindowText, theme_color(theme, "Normal", "fg")) pal.setColor(pal.AlternateBase, theme_color(theme, "HighlightRegion", "bg")) pal.setColor(pal.Link, theme_color(theme, "Link", "fg")) pal.setColor(pal.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: data = self.read_data(sourceline, 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 if self.now_showing == (None, None, None) or self.now_showing[0] != self.current_name: self.clear() return # Try to refresh the data for the currently shown tag instead # of clearing editor_name, sourceline, tags = self.now_showing data = self.read_data(sourceline, tags) if data is None or len(data["computed_css"]) < 1: 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 read_data(self, sourceline, tags): mf = self.preview.view.page().mainFrame() tags = [x.lower() for x in tags] result = unicode( mf.evaluateJavaScript( "window.calibre_preview_integration.live_css(%s, %s)" % (json.dumps(sourceline), json.dumps(tags)) ) or "" ) try: result = json.loads(result) except ValueError: result = None if result is not None: maximum_specificities = {} for node in result["nodes"]: is_ancestor = node["is_ancestor"] for rule in node["css"]: self.process_rule(rule, is_ancestor, 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 return result def process_rule(self, rule, is_ancestor, 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): specificity = [0, 0, 0, 0] else: # style attribute specificity = [1, 0, 0, 0] specificity.extend((sheet_index, tuple(rule_address))) ancestor_specificity = 0 if is_ancestor else 1 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, (0, 0, 0, 0, 0, 0)): maximum_specificities[p.name] = p.specificity rule["properties"] = properties href = rule["href"] if hasattr(href, "startswith") and href.startswith("file://"): href = href[len("file://") :] if iswindows and href.startswith("/"): href = href[1:] if href: rule["href"] = current_container().abspath_to_name(href, root=self.preview.current_root) @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"])
class SequencePlayback(): def __init__(self): self.imageSequence = [] self.framerate = 0 self.currentFrame = -1 self.currentFrameIdx = -1 self.endFrameIdx = 1 self.state = '' self.timer = QTimer() self.timer.timerEvent = self.onTick Event.add(AudioPlaybackEvent.TICK, self.onTick) Event.add(PlaybackEvent.STATE, self.onState) self.setFramerate(24) pass def onState(self, state): self.state = state if state == PlayStateType.PLAY: self.play() elif state == PlayStateType.PAUSE: self.pause() pass pass def onTick(self, time): self.render() pass # def load(self, imagesPath=None): # if imagesPath: # for root, dirs, files in os.walk(imagesPath): # for filespath in files: # filename = os.path.join(root, filespath).replace('\\', '/') # # todo support image ext # if filename.find('.png') < 0: # continue # simage = SImage(filename) # self.imageSequence.append(simage) # simage.frameIdx = len(self.imageSequence) # self.endFrameIdx = simage.frameIdx # print('[load img]: ', filename) # Event.dis(ActionEvent.LOAD_SEQ, self.imageSequence) # pass def play(self): if not self.timer.isActive(): self.timer.start() pass pass def pause(self): if self.timer.isActive(): self.timer.stop() pass pass def render(self): self.currentFrameIdx = (self.currentFrameIdx + 1) % self.endFrameIdx event = SequencePlaybackEvent() event.type = SequencePlaybackEvent.RENDER_FRAME event.frameIdx = self.currentFrameIdx Event.dis(SequencePlaybackEvent.RENDER_FRAME, event) def setFramerate(self, framerate): self.framerate = framerate self.timer.setInterval(1000 / self.framerate) pass
class Widget(QWidget, ScreenWidget): name = "timeSetup" def __init__(self): QWidget.__init__(self) self.ui = Ui_DateTimeWidget() self.ui.setupUi(self) self.timer = QTimer(self) self.from_time_updater = True self.is_date_changed = False self.current_zone = "" self.tz_dict = {} self.continents = [] self.countries = [] for country, data in yali.localedata.locales.items(): if country == ctx.consts.lang: if data.has_key("timezone"): ctx.installData.timezone = data["timezone"] # Append continents and countries the time zone dictionary self.createTZDictionary() # Sort continent list self.sortContinents() # Append sorted continents to combobox self.loadContinents() # Load current continents country list self.getCountries(self.current_zone["continent"]) # Highlight the current zone self.index = self.ui.continentList.findText( self.current_zone["continent"]) self.ui.continentList.setCurrentIndex(self.index) self.index = self.ui.countryList.findText(self.current_zone["country"]) self.ui.countryList.setCurrentIndex(self.index) # Initialize widget signal and slots self.__initSignals__() self.ui.calendarWidget.setDate(QDate.currentDate()) self.pthread = None self.pds_messagebox = PMessageBox(self) self.pds_messagebox.enableOverlay() self.timer.start(1000) def __initSignals__(self): self.ui.timeEdit.timeChanged[QTime].connect(self.timerStop) self.ui.calendarWidget.dateChanged.connect(self.dateChanged) self.timer.timeout.connect(self.updateClock) self.ui.continentList.activated[str].connect(self.getCountries) def createTZDictionary(self): tz = TimeZoneList() zones = [x.timeZone for x in tz.getEntries()] zones.sort() for zone in zones: split = zone.split("/") # Human readable continent names continent_pretty_name = split[0].replace("_", " ") continent_pretty_name = continent_pretty_name # Some country names can be like Argentina/Catamarca so this fixes the splitting problem # caused by zone.split("/") # # Remove continent info and take the rest as the country name split.pop(0) country_pretty_name = " / ".join(split) # Human readable country names country_pretty_name = country_pretty_name.replace("_", " ") # Get current zone if zone == ctx.installData.timezone: self.current_zone = { "continent": continent_pretty_name, "country": country_pretty_name } # Append to dictionary if self.tz_dict.has_key(continent_pretty_name): self.tz_dict[continent_pretty_name].append( [country_pretty_name, zone]) else: self.tz_dict[continent_pretty_name] = [[ country_pretty_name, zone ]] def sortContinents(self): for continent in self.tz_dict.keys(): self.continents.append(continent) self.continents.sort() def loadContinents(self): for continent in self.continents: self.ui.continentList.addItem(continent) def getCountries(self, continent): # Countries of the selected continent countries = self.tz_dict[str(continent)] self.ui.countryList.clear() for country, zone in countries: self.ui.countryList.addItem(country, zone) self.countries.append(country) def dateChanged(self): self.is_date_changed = True def timerStop(self): if self.from_time_updater: return # Human action detected; stop the timer. self.timer.stop() def updateClock(self): # What time is it ? cur = QTime.currentTime() self.from_time_updater = True self.ui.timeEdit.setTime(cur) self.from_time_updater = False def shown(self): self.timer.start(1000) if ctx.flags.install_type == ctx.STEP_BASE: self.pthread = PThread(self, self.startInit, self.dummy) def dummy(self): pass def setTime(self): ctx.interface.informationWindow.update(_("Adjusting time settings")) date = self.ui.calendarWidget.date() time = self.ui.timeEdit.time() args = "%02d%02d%02d%02d%04d.%02d" % (date.month(), date.day(), time.hour(), time.minute(), date.year(), time.second()) # Set current date and time ctx.logger.debug("Date/Time setting to %s" % args) yali.util.run_batch("date", [args]) # Sync date time with hardware ctx.logger.debug("YALI's time is syncing with the system.") yali.util.run_batch("hwclock", ["--systohc"]) ctx.interface.informationWindow.hide() def execute(self): if not self.timer.isActive() or self.is_date_changed: QTimer.singleShot(500, self.setTime) self.timer.stop() index = self.ui.countryList.currentIndex() ctx.installData.timezone = self.ui.countryList.itemData(index) ctx.logger.debug("Time zone selected as %s " % ctx.installData.timezone) if ctx.flags.install_type == ctx.STEP_BASE: #FIXME:Refactor hacky code ctx.installData.rootPassword = ctx.consts.default_password ctx.installData.hostName = yali.util.product_release() if ctx.storageInitialized: disks = filter(lambda d: not d.format.hidden, ctx.storage.disks) if len(disks) == 1: ctx.storage.clearPartDisks = [disks[0].name] ctx.mainScreen.step_increment = 2 else: ctx.mainScreen.step_increment = 1 return True else: self.pds_messagebox.setMessage( _("Storage Devices initialising...")) self.pds_messagebox.animate(start=MIDCENTER, stop=MIDCENTER) ctx.mainScreen.step_increment = 0 self.pthread.start() QTimer.singleShot(2, self.startStorageInitialize) return False return True def startInit(self): self.pds_messagebox.animate(start=MIDCENTER, stop=MIDCENTER) def startStorageInitialize(self): ctx.storageInitialized = yali.storage.initialize( ctx.storage, ctx.interface) self.initFinished() def initFinished(self): self.pds_messagebox.animate(start=CURRENT, stop=CURRENT, direction=OUT) disks = filter(lambda d: not d.format.hidden, ctx.storage.disks) if ctx.storageInitialized: if len(disks) == 1: ctx.storage.clearPartDisks = [disks[0].name] ctx.mainScreen.step_increment = 2 else: ctx.mainScreen.step_increment = 1 ctx.mainScreen.slotNext(dry_run=True) else: ctx.mainScreen.enableBack()
class CoverDelegate(QStyledItemDelegate): # {{{ needs_redraw = pyqtSignal() def __init__(self, parent): QStyledItemDelegate.__init__(self, parent) self.angle = 0 self.timer = QTimer(self) self.timer.timeout.connect(self.frame_changed) self.color = parent.palette().color(QPalette.WindowText) self.spinner_width = 64 def frame_changed(self, *args): self.angle = (self.angle + 30) % 360 self.needs_redraw.emit() def start_animation(self): self.angle = 0 self.timer.start(200) def stop_animation(self): self.timer.stop() def draw_spinner(self, painter, rect): width = rect.width() outer_radius = (width - 1) * 0.5 inner_radius = (width - 1) * 0.5 * 0.38 capsule_height = outer_radius - inner_radius capsule_width = int(capsule_height * (0.23 if width > 32 else 0.35)) capsule_radius = capsule_width // 2 painter.save() painter.setRenderHint(painter.Antialiasing) for i in xrange(12): color = QColor(self.color) color.setAlphaF(1.0 - (i / 12.0)) painter.setPen(Qt.NoPen) painter.setBrush(color) painter.save() painter.translate(rect.center()) painter.rotate(self.angle - i * 30.0) painter.drawRoundedRect(-capsule_width * 0.5, -(inner_radius + capsule_height), capsule_width, capsule_height, capsule_radius, capsule_radius) painter.restore() painter.restore() def paint(self, painter, option, index): QStyledItemDelegate.paint(self, painter, option, index) style = QApplication.style() waiting = self.timer.isActive() and bool(index.data(Qt.UserRole)) if waiting: rect = QRect(0, 0, self.spinner_width, self.spinner_width) rect.moveCenter(option.rect.center()) self.draw_spinner(painter, rect) else: # Ensure the cover is rendered over any selection rect style.drawItemPixmap(painter, option.rect, Qt.AlignTop | Qt.AlignHCenter, QPixmap(index.data(Qt.DecorationRole)))
class Widget(QWidget, ScreenWidget): name = "timeSetup" def __init__(self): QWidget.__init__(self) self.ui = Ui_DateTimeWidget() self.ui.setupUi(self) self.timer = QTimer(self) self.from_time_updater = True self.is_date_changed = False self.current_zone = "" self.tz_dict = {} self.continents = [] self.countries = [] for country, data in yali.localedata.locales.items(): if country == ctx.consts.lang: if data.has_key("timezone"): ctx.installData.timezone = data["timezone"] # Append continents and countries the time zone dictionary self.createTZDictionary() # Sort continent list self.sortContinents() # Append sorted continents to combobox self.loadContinents() # Load current continents country list self.getCountries(self.current_zone["continent"]) # Highlight the current zone self.index = self.ui.continentList.findText(self.current_zone["continent"]) self.ui.continentList.setCurrentIndex(self.index) self.index = self.ui.countryList.findText(self.current_zone["country"]) self.ui.countryList.setCurrentIndex(self.index) # Initialize widget signal and slots self.__initSignals__() self.ui.calendarWidget.setDate(QDate.currentDate()) self.pthread = None self.pds_messagebox = PMessageBox(self) self.pds_messagebox.enableOverlay() self.timer.start(1000) def __initSignals__(self): self.ui.timeEdit.timeChanged[QTime].connect(self.timerStop) self.ui.calendarWidget.dateChanged.connect(self.dateChanged) self.timer.timeout.connect(self.updateClock) self.ui.continentList.activated[str].connect(self.getCountries) def createTZDictionary(self): tz = TimeZoneList() zones = [ x.timeZone for x in tz.getEntries() ] zones.sort() for zone in zones: split = zone.split("/") # Human readable continent names continent_pretty_name = split[0].replace("_", " ") continent_pretty_name = continent_pretty_name # Some country names can be like Argentina/Catamarca so this fixes the splitting problem # caused by zone.split("/") # # Remove continent info and take the rest as the country name split.pop(0) country_pretty_name = " / ".join(split) # Human readable country names country_pretty_name = country_pretty_name.replace("_", " ") # Get current zone if zone == ctx.installData.timezone: self.current_zone = { "continent":continent_pretty_name, "country":country_pretty_name} # Append to dictionary if self.tz_dict.has_key(continent_pretty_name): self.tz_dict[continent_pretty_name].append([country_pretty_name, zone]) else: self.tz_dict[continent_pretty_name] = [[country_pretty_name, zone]] def sortContinents(self): for continent in self.tz_dict.keys(): self.continents.append(continent) self.continents.sort() def loadContinents(self): for continent in self.continents: self.ui.continentList.addItem(continent) def getCountries(self, continent): # Countries of the selected continent countries = self.tz_dict[str(continent)] self.ui.countryList.clear() for country, zone in countries: self.ui.countryList.addItem(country, zone) self.countries.append(country) def dateChanged(self): self.is_date_changed = True def timerStop(self): if self.from_time_updater: return # Human action detected; stop the timer. self.timer.stop() def updateClock(self): # What time is it ? cur = QTime.currentTime() self.from_time_updater = True self.ui.timeEdit.setTime(cur) self.from_time_updater = False def shown(self): self.timer.start(1000) if ctx.flags.install_type == ctx.STEP_BASE: self.pthread = PThread(self, self.startInit, self.dummy) def dummy(self): pass def setTime(self): ctx.interface.informationWindow.update(_("Adjusting time settings")) date = self.ui.calendarWidget.date() time = self.ui.timeEdit.time() args = "%02d%02d%02d%02d%04d.%02d" % (date.month(), date.day(), time.hour(), time.minute(), date.year(), time.second()) # Set current date and time ctx.logger.debug("Date/Time setting to %s" % args) yali.util.run_batch("date", [args]) # Sync date time with hardware ctx.logger.debug("YALI's time is syncing with the system.") yali.util.run_batch("hwclock", ["--systohc"]) ctx.interface.informationWindow.hide() def execute(self): if not self.timer.isActive() or self.is_date_changed: QTimer.singleShot(500, self.setTime) self.timer.stop() index = self.ui.countryList.currentIndex() ctx.installData.timezone = self.ui.countryList.itemData(index) ctx.logger.debug("Time zone selected as %s " % ctx.installData.timezone) if ctx.flags.install_type == ctx.STEP_BASE: #FIXME:Refactor hacky code ctx.installData.rootPassword = ctx.consts.default_password ctx.installData.hostName = yali.util.product_release() if ctx.storageInitialized: disks = filter(lambda d: not d.format.hidden, ctx.storage.disks) if len(disks) == 1: ctx.storage.clearPartDisks = [disks[0].name] ctx.mainScreen.step_increment = 2 else: ctx.mainScreen.step_increment = 1 return True else: self.pds_messagebox.setMessage(_("Storage Devices initialising...")) self.pds_messagebox.animate(start=MIDCENTER, stop=MIDCENTER) ctx.mainScreen.step_increment = 0 self.pthread.start() QTimer.singleShot(2, self.startStorageInitialize) return False return True def startInit(self): self.pds_messagebox.animate(start=MIDCENTER, stop=MIDCENTER) def startStorageInitialize(self): ctx.storageInitialized = yali.storage.initialize(ctx.storage, ctx.interface) self.initFinished() def initFinished(self): self.pds_messagebox.animate(start=CURRENT, stop=CURRENT, direction=OUT) disks = filter(lambda d: not d.format.hidden, ctx.storage.disks) if ctx.storageInitialized: if len(disks) == 1: ctx.storage.clearPartDisks = [disks[0].name] ctx.mainScreen.step_increment = 2 else: ctx.mainScreen.step_increment = 1 ctx.mainScreen.slotNext(dry_run=True) else: ctx.mainScreen.enableBack()