Example #1
0
class HelpWindow(QtWidgets.QWidget):

    # TODO: do not hardcode this path like this.
    __doc_path = os.path.abspath(
        os.path.join(
            os.path.abspath(os.path.dirname(__file__)),
            "..", "..", "doc", "index.html"))

    def __init__(self, parent=None):
        super().__init__(parent)

        # The help display.
        self.help_display = QWebView(parent=self)
        self.help_display.load(QtCore.QUrl("file://" + self.__doc_path))

        # Close button.
        self.close_button = QtWidgets.QPushButton("Close")
        self.close_button.clicked.connect(self.close)

        # Layout for the widgets.
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.help_display)
        layout.addWidget(self.close_button)
        self.setLayout(layout)

        # Title
        self.setWindowTitle("{} help".format(version.progname))
Example #2
0
 def __init__(self, vertical, parent=None):
     QWebView.__init__(self, parent)
     s = self.settings()
     s.setAttribute(s.JavascriptEnabled, False)
     self.vertical = vertical
     self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
     self.linkClicked.connect(self.link_activated)
     self._link_clicked = False
     self.setAttribute(Qt.WA_OpaquePaintEvent, False)
     palette = self.palette()
     self.setAcceptDrops(False)
     palette.setBrush(QPalette.Base, Qt.transparent)
     self.page().setPalette(palette)
     for x, icon in [
         ('remove_format', 'trash.png'), ('save_format', 'save.png'),
         ('restore_format', 'edit-undo.png'), ('copy_link','edit-copy.png'),
         ('compare_format', 'diff.png'),
         ('set_cover_format', 'default_cover.png'),
     ]:
         ac = QAction(QIcon(I(icon)), '', self)
         ac.current_fmt = None
         ac.current_url = None
         ac.triggered.connect(getattr(self, '%s_triggerred'%x))
         setattr(self, '%s_action'%x, ac)
     self.manage_action = QAction(self)
     self.manage_action.current_fmt = self.manage_action.current_url = None
     self.manage_action.triggered.connect(self.manage_action_triggered)
     self.remove_item_action = ac = QAction(QIcon(I('minus.png')), '...', self)
     ac.data = (None, None, None)
     ac.triggered.connect(self.remove_item_triggered)
     self.setFocusPolicy(Qt.NoFocus)
Example #3
0
def PdfConverter(username):

        htmllink = "bootstrap_mod/usertemp/"+username+".html"
        app1 = QApplication(sys.argv)

        web = QWebView()

        link =QUrl.fromLocalFile(QFileInfo(htmllink).absoluteFilePath())

        web.load(QUrl(link))

        printer = QPrinter()
        printer.setPageSize(QPrinter.A4)
        printer.setOutputFormat(QPrinter.PdfFormat)
        Pdf_Generated_Name="bootstrap_mod/usertemp/"+username+".pdf"
        printer.setOutputFileName(Pdf_Generated_Name)

        web.print(printer)
        QApplication.exit()
        def convertIt():
                web.print(printer)
                print("Pdf generated")
                QApplication.exit()

        web.loadFinished.connect(convertIt)
        sys.exit(app1.exec_())
        return 0
Example #4
0
    def __init__(self, *args):
        QWebView.__init__(self, *args)
        self.gui = None
        self.tags = ''
        self.create_browser = None

        self._page = NPWebPage()
        self.setPage(self._page)
        self.cookie_jar = QNetworkCookieJar()
        self.page().networkAccessManager().setCookieJar(self.cookie_jar)

        http_proxy = get_proxies().get('http', None)
        if http_proxy:
            proxy_parts = urlparse(http_proxy)
            proxy = QNetworkProxy()
            proxy.setType(QNetworkProxy.HttpProxy)
            if proxy_parts.username:
                proxy.setUser(proxy_parts.username)
            if proxy_parts.password:
                proxy.setPassword(proxy_parts.password)
            if proxy_parts.hostname:
                proxy.setHostName(proxy_parts.hostname)
            if proxy_parts.port:
                proxy.setPort(proxy_parts.port)
            self.page().networkAccessManager().setProxy(proxy)

        self.page().setForwardUnsupportedContent(True)
        self.page().unsupportedContent.connect(self.start_download)
        self.page().downloadRequested.connect(self.start_download)
        self.page().networkAccessManager().sslErrors.connect(self.ignore_ssl_errors)
Example #5
0
	def __init__(self, editBox,
	             editorPositionToSourceLineFunc,
	             sourceLineToEditorPositionFunc):

		QWebView.__init__(self)

		self.editBox = editBox

		if not globalSettings.handleWebLinks:
			self.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks)
			self.page().linkClicked.connect(QDesktopServices.openUrl)
		self.settings().setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False)
		self.settings().setDefaultTextEncoding('utf-8')
		# Avoid caching of CSS
		self.settings().setObjectCacheCapacities(0,0,0)

		self.syncscroll = SyncScroll(self.page().mainFrame(),
					     editorPositionToSourceLineFunc,
					     sourceLineToEditorPositionFunc)

		# Events relevant to sync scrolling
		self.editBox.cursorPositionChanged.connect(self._handleCursorPositionChanged)
		self.editBox.verticalScrollBar().valueChanged.connect(self.syncscroll.handleEditorScrolled)
		self.editBox.resized.connect(self._handleEditorResized)

		# Scroll the preview when the mouse wheel is used to scroll
		# beyond the beginning/end of the editor
		self.editBox.scrollLimitReached.connect(self._handleWheelEvent)
Example #6
0
 def __init__(self, vertical, parent=None):
     QWebView.__init__(self, parent)
     s = self.settings()
     s.setAttribute(s.JavascriptEnabled, False)
     self.vertical = vertical
     self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
     self.linkClicked.connect(self.link_activated)
     self._link_clicked = False
     self.setAttribute(Qt.WA_OpaquePaintEvent, False)
     palette = self.palette()
     self.setAcceptDrops(False)
     palette.setBrush(QPalette.Base, Qt.transparent)
     self.page().setPalette(palette)
     self.css = P("templates/book_details.css", data=True).decode("utf-8")
     for x, icon in [
         ("remove_format", "trash.png"),
         ("save_format", "save.png"),
         ("restore_format", "edit-undo.png"),
         ("copy_link", "edit-copy.png"),
         ("manage_author", "user_profile.png"),
         ("compare_format", "diff.png"),
     ]:
         ac = QAction(QIcon(I(icon)), "", self)
         ac.current_fmt = None
         ac.current_url = None
         ac.triggered.connect(getattr(self, "%s_triggerred" % x))
         setattr(self, "%s_action" % x, ac)
Example #7
0
 def __init__(self, parent=None):
     QWebView.__init__(self, parent)
     self.inspector = QWebInspector(self)
     w = QApplication.instance().desktop().availableGeometry(self).width()
     self._size_hint = QSize(int(w/3), int(w/2))
     self._page = WebPage(self)
     self.setPage(self._page)
     self.inspector.setPage(self._page)
     self.clear()
Example #8
0
class BrowserView(QDialog):  # {{{

    def __init__(self, page, parent=None):
        QDialog.__init__(self, parent)
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)
        self.webview = QWebView(self)
        l.addWidget(self.webview)
        self.resize(QSize(1024, 768))
        self.webview.setPage(page)
Example #9
0
 def __init__(self, url):
     self.app = QApplication(sys.argv)
     QWebView.__init__(self)
     self.setPage(SilentWebPage())
     self._loaded = False # Used within _wait_load.
     self.loadFinished.connect(self._loadFinished)
     # The version of webkit I have prints some stuff to stdout which I do not want.
     # See https://bugreports.qt-project.org/browse/QTBUG-31074
     with suppress_stdout_stderr():
         self.load(QUrl(url))
         #self.show()
         self._wait_load()
Example #10
0
def js_tester(qtbot):
    """Fixture to test javascript snippets.

    Provides a QWebView with a 640x480px size and a JSTester instance.

    Args:
        qtbot: pytestqt.plugin.QtBot fixture.
    """
    webview = QWebView()
    qtbot.add_widget(webview)
    webview.resize(640, 480)
    return JSTester(webview, qtbot)
Example #11
0
 def __init__(self, parent):
     QWebView.__init__(self, parent)
     self._page = Page()
     self._page.elem_clicked.connect(self.elem_clicked)
     self.setPage(self._page)
     raw = '''
     body { background-color: white  }
     .calibre_toc_hover:hover { cursor: pointer !important; border-top: solid 5px green !important }
     '''
     raw = '::selection {background:#ffff00; color:#000;}\n'+raw
     data = 'data:text/css;charset=utf-8;base64,'
     data += b64encode(raw.encode('utf-8'))
     self.settings().setUserStyleSheetUrl(QUrl(data))
Example #12
0
    def __init__(self, parent=None):
        QWebView.__init__(self, parent)
        self.setAcceptDrops(False)
        self.setMaximumWidth(300)
        self.setMinimumWidth(300)

        palette = self.palette()
        palette.setBrush(QPalette.Base, Qt.transparent)
        self.page().setPalette(palette)
        self.setAttribute(Qt.WA_OpaquePaintEvent, False)

        self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
        self.linkClicked.connect(self.link_clicked)
Example #13
0
    def __init__(self, home_url):
        super().__init__()

        # store home url
        self.home_url = home_url

        # create and add tool bar on top (non floatable or movable)
        tool_bar = QToolBar(self)

        # create actions, connect to methods, add to tool bar
        action_home = QAction(self)
        action_home.setIcon(QIcon('icon.home.png'))
        action_home.setToolTip('Home')
        action_home.triggered.connect(self.actionHomeTriggered)
        tool_bar.addAction(action_home)

        action_backward = QAction(self)
        action_backward.setEnabled(False)
        action_backward.setIcon(QIcon('icon.backward.png'))
        action_backward.triggered.connect(self.actionBackwardTriggered)
        tool_bar.addAction(action_backward)
        self.action_backward = action_backward

        action_forward = QAction(self)
        action_forward.setEnabled(False)
        action_forward.setIcon(QIcon('icon.forward.png'))
        action_forward.triggered.connect(self.actionForwardTriggered)
        tool_bar.addAction(action_forward)
        self.action_forward = action_forward

        # create and add web view, connect linkClicked signal with our newPage method
        web_view = QWebView()
        # must set DelegationPolicy to include all links
        web_view.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        web_view.linkClicked.connect(self.newPage)
        self.web_view = web_view

        # set Layout
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(tool_bar)
        layout.addWidget(web_view)

        # Initialize history (initially there is no current page)
        self.history = []
        self.current_page_index = -1

        # Finally set the home page
        self.actionHomeTriggered()
Example #14
0
    def __init__(self):
        QWebView.__init__(self)
        settings = self.settings()
        settings.setAttribute(settings.AutoLoadImages, False)

        self.authorize = False
        self.feed_list = []
        self.page_loaded = False
        self.scroll_down = False
        self.page_height = 0
        self.run_timer = True
        self.trg_func = self.cap
        self.textpage = None

        self.loadFinished.connect(self.set_textpage)
 def __init__(self, url):
     super(MainWindow, self).__init__()
     self.view = QWebView(self)
     self.view.load(url)
     self.view.setFixedSize(890, 550)
     # comment out the following line to allow refresh for debugging
     self.view.setContextMenuPolicy(Qt.NoContextMenu)
Example #16
0
 def __init__(self):
     super(WebRender, self).__init__()
     vbox, temporary_directory = QVBoxLayout(self), mkdtemp()
     # Web Frame
     self.webFrame = QWebView()  # QWebView = QWebFrame + QWebSettings
     self.webFrame.setStyleSheet("QWebView{ background:#fff }")  # no dark bg
     settings = self.webFrame.settings()  # QWebSettings instance
     settings.setDefaultTextEncoding("utf-8")
     settings.setIconDatabasePath(temporary_directory)
     settings.setLocalStoragePath(temporary_directory)
     settings.setOfflineStoragePath(temporary_directory)
     settings.setOfflineWebApplicationCachePath(temporary_directory)
     settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
     settings.setAttribute(QWebSettings.LocalStorageEnabled, True)
     settings.setAttribute(QWebSettings.OfflineStorageDatabaseEnabled, True)
     settings.setAttribute(QWebSettings.PluginsEnabled, True)
     settings.setAttribute(QWebSettings.DnsPrefetchEnabled, True)
     settings.setAttribute(QWebSettings.JavascriptCanOpenWindows, True)
     settings.setAttribute(QWebSettings.JavascriptCanCloseWindows, True)
     settings.setAttribute(QWebSettings.JavascriptCanAccessClipboard, True)
     settings.setAttribute(QWebSettings.SpatialNavigationEnabled, True)
     settings.setAttribute(
         QWebSettings.LocalContentCanAccessRemoteUrls, True)
     settings.setAttribute(
         QWebSettings.OfflineWebApplicationCacheEnabled, True)
     vbox.addWidget(self.webFrame)
Example #17
0
    def initUI(self):
        self.setWindowIcon(QIcon("image/tray.png"))
        self.setWindowTitle("梦音乐")
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setObjectName("window")
        self.setGeometry(300,0,600, 600)
        # 底部
        ql = QLabel(self)
        ql.setGeometry(0,580,600,20)
        ql.setStyleSheet("QLabel{ background:#2D2D2D }")

        # 内嵌web网页
        self.web = QWebView(self)
        self.web.setStyleSheet("QWidget{ background-color:white }")
        self.web.setGeometry(0, 0, 600, 580)
        self.web.load(QUrl.fromLocalFile(os.path.abspath("web/index.html")))
        # self.web.load(QUrl("http://localhost/index.php/Home/Index/index.html"))
        
        self.web.page().mainFrame().javaScriptWindowObjectCleared.connect(
            self.populateJavaScriptWindowObject)
        # 关闭按钮
        btn = QPushButton("", self)
        btn.setGeometry(540, 5, 44, 34)
        # btn.setCursor(QCursor(Qt.PointingHandCursor))
        btn.setStyleSheet(  
            "QPushButton{ border-image:url(image/newimg/ic_common_title_bar_back_2.png) } QPushButton:hover{ border-image:url(image/newimg/ic_common_title_bar_back.png) } ")
        btn.clicked.connect(self.myclose)
Example #18
0
    def cb_initialize_plugin(self):

        # ---------------------------
        # Read configuration
        # ---------------------------
        self.config = self.pl_get_current_config_ref()
        content = self.config['content']['value']
        isUrl   = self.config['isUrl']['value']



        # --------------------------------
        # Create Widget
        # --------------------------------
        self.WebView = QWebView()


        # This call is important, because the background structure needs to know the used widget!
        # In the background the qmidiwindow will becreated and the widget will be added
        self.pl_set_widget_for_internal_usage( self.WebView )
        print(isUrl)
        if isUrl == '1':
            url = QtCore.QUrl(content)
            self.WebView.load(url)
        else:
            self.WebView.setHtml(content)
        self.WebView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.WebView.customContextMenuRequested.connect(self.show_context_menu)

        return True
Example #19
0
    def __init__(self, opts, log, cover_data=None, toc=None):
        from calibre.gui2 import must_use_qt
        must_use_qt()
        QObject.__init__(self)

        self.logger = self.log = log
        self.opts = opts
        self.cover_data = cover_data
        self.paged_js = None
        self.toc = toc

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(opts, self.log)
        self.view.setPage(self.page)
        self.view.setRenderHints(QPainter.Antialiasing|
                    QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
        self.view.loadFinished.connect(self.render_html,
                type=Qt.QueuedConnection)
        for x in (Qt.Horizontal, Qt.Vertical):
            self.view.page().mainFrame().setScrollBarPolicy(x,
                    Qt.ScrollBarAlwaysOff)
        self.report_progress = lambda x, y: x
        self.current_section = ''
        self.current_tl_section = ''
Example #20
0
    def __init__(self, opts, log, cover_data=None, toc=None):
        from calibre.gui2 import must_use_qt
        must_use_qt()
        QObject.__init__(self)

        self.logger = self.log = log
        self.mathjax_dir = P('mathjax', allow_user_override=False)
        current_log(log)
        self.opts = opts
        self.cover_data = cover_data
        self.paged_js = None
        self.toc = toc

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(opts, self.log)
        self.view.setPage(self.page)
        self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
        self.view.loadFinished.connect(self.render_html,
                type=Qt.QueuedConnection)
        self.view.loadProgress.connect(self.load_progress)
        self.ignore_failure = None
        self.hang_check_timer = t = QTimer(self)
        t.timeout.connect(self.hang_check)
        t.setInterval(1000)

        for x in (Qt.Horizontal, Qt.Vertical):
            self.view.page().mainFrame().setScrollBarPolicy(x,
                    Qt.ScrollBarAlwaysOff)
        self.report_progress = lambda x, y: x
        self.current_section = ''
        self.current_tl_section = ''
Example #21
0
    def __init__(self, opts, log, cover_data=None, toc=None):
        from calibre.gui2 import must_use_qt
        from calibre.utils.podofo import get_podofo
        must_use_qt()
        QObject.__init__(self)

        self.logger = self.log = log
        self.podofo = get_podofo()
        self.doc = self.podofo.PDFDoc()

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(opts, self.log)
        self.view.setPage(self.page)
        self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
        self.view.loadFinished.connect(self._render_html,
                type=Qt.QueuedConnection)
        for x in (Qt.Horizontal, Qt.Vertical):
            self.view.page().mainFrame().setScrollBarPolicy(x,
                    Qt.ScrollBarAlwaysOff)
        self.render_queue = []
        self.combine_queue = []
        self.tmp_path = PersistentTemporaryDirectory(u'_pdf_output_parts')

        self.opts = opts
        self.cover_data = cover_data
        self.paged_js = None
        self.toc = toc
Example #22
0
    def __init__(self, container, do_embed=False):
        self.container = container
        self.log = self.logger = container.log
        self.do_embed = do_embed
        must_use_qt()
        self.parser = CSSParser(loglevel=logging.CRITICAL, log=logging.getLogger('calibre.css'))
        self.first_letter_pat = regex.compile(r'^[\p{Ps}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]+', regex.VERSION1 | regex.UNICODE)

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(self.log)
        self.view.setPage(self.page)
        self.page.setViewportSize(QSize(1200, 1600))

        self.view.loadFinished.connect(self.collect,
                type=Qt.QueuedConnection)

        self.render_queue = list(container.spine_items)
        self.font_stats = {}
        self.font_usage_map = {}
        self.font_spec_map = {}
        self.font_rule_map = {}
        self.all_font_rules = {}

        QTimer.singleShot(0, self.render_book)

        if self.loop.exec_() == 1:
            raise Exception('Failed to gather statistics from book, see log for details')
    def __init__(self, book_mi, annotations, parent=None):
        #QDialog.__init__(self, parent)
        self.prefs = plugin_prefs
        super(PreviewDialog, self).__init__(parent, 'annotations_preview_dialog')
        self.pl = QVBoxLayout(self)
        self.setLayout(self.pl)

        self.label = QLabel()
        self.label.setText("<b>%s annotations &middot; %s</b>" % (book_mi.reader_app, book_mi.title))
        self.label.setAlignment(Qt.AlignHCenter)
        self.pl.addWidget(self.label)

        self.wv = QWebView()
        self.wv.setHtml(annotations)
        self.pl.addWidget(self.wv)

        self.buttonbox = QDialogButtonBox(QDialogButtonBox.Close)
#        self.buttonbox.addButton('Close', QDialogButtonBox.AcceptRole)
        self.buttonbox.setOrientation(Qt.Horizontal)
#        self.buttonbox.accepted.connect(self.close)
        self.buttonbox.rejected.connect(self.close)
#        self.connect(self.buttonbox, pyqtSignal('accepted()'), self.close)
#        self.connect(self.buttonbox, pyqtSignal('rejected()'), self.close)
        self.pl.addWidget(self.buttonbox)

        # Sizing
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()
    def __init__(self, parent, icon, prefs, html=None, page=None, title=''):
        self.prefs = prefs
        #QDialog.__init__(self, parent=parent)
        super(HelpView, self).__init__(parent, 'help_dialog')
        self.setWindowTitle(title)
        self.setWindowIcon(icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        self.wv = QWebView()
        if html is not None:
            self.wv.setHtml(html)
        elif page is not None:
            self.wv.load(QUrl(page))
        self.wv.setMinimumHeight(100)
        self.wv.setMaximumHeight(16777215)
        self.wv.setMinimumWidth(400)
        self.wv.setMaximumWidth(16777215)
        self.wv.setGeometry(0, 0, 400, 100)
        self.wv.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.l.addWidget(self.wv)

        # Sizing
        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()
Example #25
0
 def __init__(self):
     self.debug = 1
     self.app = QApplication([])
     self.desktop = QApplication.desktop()
     self.web = QWebView()
     self.icon = QIcon(ICON)
     QWebSettings.setIconDatabasePath(DATA_DIR)
Example #26
0
	def __init__(self, tab,
	             editorPositionToSourceLineFunc,
	             sourceLineToEditorPositionFunc):

		QWebView.__init__(self)
		self.tab = tab

		self.syncscroll = SyncScroll(self.page().mainFrame(),
		                             editorPositionToSourceLineFunc,
		                             sourceLineToEditorPositionFunc)
		ReTextWebPreview.__init__(self, tab.editBox)

		self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
		self.page().linkClicked.connect(self._handleLinkClicked)
		self.settings().setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False)
		# Avoid caching of CSS
		self.settings().setObjectCacheCapacities(0,0,0)
Example #27
0
    def __init__(self, callback, xpos, ypos, width, height):
        super(Window, self).__init__()

        self._xpos = xpos
        self._ypos = ypos
        self._width = width
        self._height = height
        self._callback = callback

        self.setStyleSheet(
            "QWidget { background-color : #1B1D1E; color : #FFFFFF;  }")
        self.setAsBar()

        view = QWebView(self)
        view.setContextMenuPolicy(Qt.CustomContextMenu)
        view.statusBarMessage.connect(self.qt_statusbar_callback)

        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(view)

        view.setPage(QWebPage())
        view.setHtml(htmltemplate(self._width, self._height,
                                  "bottom" if ypos == 0 else "top"))

        self.view = view
        self.updateContent.connect(self.handle_updateContent)
Example #28
0
	def getWebView(self):
		webView = QWebView()
		if not globalSettings.handleWebLinks:
			webView.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks)
			webView.page().linkClicked.connect(QDesktopServices.openUrl)
		webView.settings().setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False)
		return webView
Example #29
0
    def __init__(self, url):
        super(MainWindow, self).__init__()

        self.progress = 0

        fd = QFile(":/jquery.min.js")

        if fd.open(QIODevice.ReadOnly | QFile.Text):
            self.jQuery = QTextStream(fd).readAll()
            fd.close()
        else:
            self.jQuery = ''

        QNetworkProxyFactory.setUseSystemConfiguration(True)

        self.view = QWebView(self)
        self.view.load(url)
        self.view.loadFinished.connect(self.adjustLocation)
        self.view.titleChanged.connect(self.adjustTitle)
        self.view.loadProgress.connect(self.setProgress)
        self.view.loadFinished.connect(self.finishLoading)

        self.locationEdit = QLineEdit(self)
        self.locationEdit.setSizePolicy(QSizePolicy.Expanding,
                self.locationEdit.sizePolicy().verticalPolicy())
        self.locationEdit.returnPressed.connect(self.changeLocation)

        toolBar = self.addToolBar("Navigation")
        toolBar.addAction(self.view.pageAction(QWebPage.Back))
        toolBar.addAction(self.view.pageAction(QWebPage.Forward))
        toolBar.addAction(self.view.pageAction(QWebPage.Reload))
        toolBar.addAction(self.view.pageAction(QWebPage.Stop))
        toolBar.addWidget(self.locationEdit)

        viewMenu = self.menuBar().addMenu("&View")
        viewSourceAction = QAction("Page Source", self)
        viewSourceAction.triggered.connect(self.viewSource)
        viewMenu.addAction(viewSourceAction)

        effectMenu = self.menuBar().addMenu("&Effect")
        effectMenu.addAction("Highlight all links", self.highlightAllLinks)

        self.rotateAction = QAction(
                self.style().standardIcon(QStyle.SP_FileDialogDetailedView),
                "Turn images upside down", self, checkable=True,
                toggled=self.rotateImages)
        effectMenu.addAction(self.rotateAction)

        toolsMenu = self.menuBar().addMenu("&Tools")
        toolsMenu.addAction("Remove GIF images", self.removeGifImages)
        toolsMenu.addAction("Remove all inline frames",
                self.removeInlineFrames)
        toolsMenu.addAction("Remove all object elements",
                self.removeObjectElements)
        toolsMenu.addAction("Remove all embedded elements",
                self.removeEmbeddedElements)
        self.setCentralWidget(self.view)
Example #30
0
    def __init__(self):
        super(WebRender, self).__init__()

        vbox = QVBoxLayout(self)
        #Web Frame
        self.webFrame = QWebView()
        QWebSettings.globalSettings().setAttribute(
            QWebSettings.DeveloperExtrasEnabled, True)
        vbox.addWidget(self.webFrame)
 def __init__(self):
     # QWebView
     self.view = QWebView.__init__(self)
     # self.view.setPage(MyBrowser())
     self.setWindowTitle('Loading...')
     self.titleChanged.connect(self.adjustTitle)
Example #32
0
class StatsCollector(object):
    def __init__(self, container, do_embed=False):
        self.container = container
        self.log = self.logger = container.log
        self.do_embed = do_embed
        must_use_qt()
        self.parser = CSSParser(loglevel=logging.CRITICAL,
                                log=logging.getLogger('calibre.css'))
        self.first_letter_pat = regex.compile(
            r'^[\p{Ps}\p{Ps}\p{Pe}\p{Pi}\p{Pf}\p{Po}]+',
            regex.VERSION1 | regex.UNICODE)

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(self.log)
        self.view.setPage(self.page)
        self.page.setViewportSize(QSize(1200, 1600))

        self.view.loadFinished.connect(self.collect, type=Qt.QueuedConnection)

        self.render_queue = list(container.spine_items)
        self.font_stats = {}
        self.font_usage_map = {}
        self.font_spec_map = {}
        self.font_rule_map = {}
        self.all_font_rules = {}

        QTimer.singleShot(0, self.render_book)

        if self.loop.exec_() == 1:
            raise Exception(
                'Failed to gather statistics from book, see log for details')

    def log_exception(self, *args):
        orig = self.log.filter_level
        try:
            self.log.filter_level = self.log.DEBUG
            self.log.exception(*args)
        finally:
            self.log.filter_level = orig

    def render_book(self):
        try:
            if not self.render_queue:
                self.loop.exit()
            else:
                self.render_next()
        except:
            self.log_exception('Rendering failed')
            self.loop.exit(1)

    def render_next(self):
        item = unicode(self.render_queue.pop(0))
        self.current_item = item
        load_html(item, self.view)

    def collect(self, ok):
        if not ok:
            self.log.error('Failed to render document: %s' %
                           self.container.relpath(self.current_item))
            self.loop.exit(1)
            return
        try:
            self.page.load_js()
            self.collect_font_stats()
        except:
            self.log_exception('Failed to collect font stats from: %s' %
                               self.container.relpath(self.current_item))
            self.loop.exit(1)
            return

        self.render_book()

    def href_to_name(self, href, warn_name):
        if not href.startswith('file://'):
            self.log.warn('Non-local URI in', warn_name, ':', href, 'ignoring')
            return None
        src = href[len('file://'):]
        if iswindows and len(src) > 2 and (src[0], src[2]) == ('/', ':'):
            src = src[1:]
        src = src.replace('/', os.sep)
        src = unquote(src)
        name = self.container.abspath_to_name(src)
        if not self.container.has_name(name):
            self.log.warn('Missing resource', href, 'in', warn_name,
                          'ignoring')
            return None
        return name

    def collect_font_stats(self):
        self.page.evaljs('window.font_stats.get_font_face_rules()')
        font_face_rules = self.page.bridge_value
        if not isinstance(font_face_rules, list):
            raise Exception(
                'Unknown error occurred while reading font-face rules')

        # Weed out invalid font-face rules
        rules = []
        import tinycss
        parser = tinycss.make_full_parser()
        for rule in font_face_rules:
            ff = rule.get('font-family', None)
            if not ff:
                continue
            style = self.parser.parseStyle('font-family:%s' % ff,
                                           validate=False)
            ff = [
                x.value for x in style.getProperty('font-family').propertyValue
            ]
            if not ff or ff[0] == 'inherit':
                continue
            rule['font-family'] = frozenset(icu_lower(f) for f in ff)
            src = rule.get('src', None)
            if not src:
                continue
            try:
                tokens = parser.parse_stylesheet(
                    '@font-face { src: %s }' %
                    src).rules[0].declarations[0].value
            except Exception:
                self.log.warn('Failed to parse @font-family src: %s' % src)
                continue
            for token in tokens:
                if token.type == 'URI':
                    uv = token.value
                    if uv:
                        sn = self.href_to_name(uv, '@font-face rule')
                        if sn is not None:
                            rule['src'] = sn
                            break
            else:
                self.log.warn(
                    'The @font-face rule refers to a font file that does not exist in the book: %s'
                    % src)
                continue
            normalize_font_properties(rule)
            rule['width'] = widths[rule['font-stretch']]
            rule['weight'] = int(rule['font-weight'])
            rules.append(rule)

        if not rules and not self.do_embed:
            return

        self.font_rule_map[self.container.abspath_to_name(
            self.current_item)] = rules
        for rule in rules:
            self.all_font_rules[rule['src']] = rule

        for rule in rules:
            if rule['src'] not in self.font_stats:
                self.font_stats[rule['src']] = set()

        self.page.evaljs('window.font_stats.get_font_usage()')
        font_usage = self.page.bridge_value
        if not isinstance(font_usage, list):
            raise Exception('Unknown error occurred while reading font usage')
        self.page.evaljs('window.font_stats.get_pseudo_element_font_usage()')
        pseudo_element_font_usage = self.page.bridge_value
        if not isinstance(pseudo_element_font_usage, list):
            raise Exception(
                'Unknown error occurred while reading pseudo element font usage'
            )
        font_usage += get_pseudo_element_font_usage(pseudo_element_font_usage,
                                                    self.first_letter_pat,
                                                    self.parser)
        exclude = {'\n', '\r', '\t'}
        self.font_usage_map[self.container.abspath_to_name(
            self.current_item)] = fu = defaultdict(dict)
        bad_fonts = {
            'serif', 'sans-serif', 'monospace', 'cursive', 'fantasy',
            'sansserif', 'inherit'
        }
        for font in font_usage:
            text = set()
            for t in font['text']:
                text |= frozenset(t)
            text.difference_update(exclude)
            if not text:
                continue
            normalize_font_properties(font)
            for rule in get_matching_rules(rules, font):
                self.font_stats[rule['src']] |= text
            if self.do_embed:
                ff = [icu_lower(x) for x in font.get('font-family', [])]
                if ff and ff[0] not in bad_fonts:
                    keys = {
                        'font-weight', 'font-style', 'font-stretch',
                        'font-family'
                    }
                    key = frozenset(((k, ff[0] if k == 'font-family' else v)
                                     for k, v in font.iteritems()
                                     if k in keys))
                    val = fu[key]
                    if not val:
                        val.update({
                            k: (font[k][0] if k == 'font-family' else font[k])
                            for k in keys
                        })
                        val['text'] = set()
                    val['text'] |= text
        self.font_usage_map[self.container.abspath_to_name(
            self.current_item)] = dict(fu)

        if self.do_embed:
            self.page.evaljs('window.font_stats.get_font_families()')
            font_families = self.page.bridge_value
            if not isinstance(font_families, dict):
                raise Exception(
                    'Unknown error occurred while reading font families')
            self.font_spec_map[self.container.abspath_to_name(
                self.current_item)] = fs = set()
            for font_dict, text, pseudo in pseudo_element_font_usage:
                font_families[font_dict['font-family']] = True
            for raw in font_families.iterkeys():
                for x in parse_font_families(self.parser, raw):
                    if x.lower() not in bad_fonts:
                        fs.add(x)
class PageCoordinator(QObject):
    marker = '//!>'
    schedule_key = 'schedule'
    job_recalculate_minutes = 15
    job_limit = 100

    def __init__(self, instances, parent=None, debug_file=None, queue_size=1000):
        super().__init__(parent)
        if debug_file:
            # When in debugging mode we only load and single instance and show it to the user
            self.instances = 1
        else:
            self.instances = instances

        self.web_pages = []
        self.job_list = []
        self.job_queue = Queue(maxsize=queue_size)

        for _ in range(self.instances):
            wp = WebPageCustom(self)
            wp.job_finished.connect(self.distribute_jobs)
            wp.new_job_received.connect(self.queue_new_job)
            self.web_pages.append(wp)

        if debug_file:
            # When in debugging mode we only load and single instance and show it to the user
            custom_webpage = self.web_pages[0]
            self.main_window = QMainWindow()
            self.web_view = QWebView(self.main_window)
            self.web_view.setPage(custom_webpage)
            self.main_window.setCentralWidget(self.web_view)
            self.main_window.showFullScreen()
            self.main_window.setWindowTitle("SpiderJuice Debug Window")
            self.main_window.show()
            self.queue_new_job(Job(file=debug_file))
        else:
            self.parse_local_jobs()
            self.recalculate_timer = QTimer(self)
            self.recalculate_timer.timeout.connect(self.shedule_for_next_15_min)
            self.recalculate_timer.setTimerType(Qt.VeryCoarseTimer)
            self.recalculate_timer.start(self.job_recalculate_minutes * 60 * 1000)
            self.shedule_for_next_15_min()

    def parse_local_jobs(self):
        logger.info('Parsing Local Jobs')
        for file in glob.glob("{base}/jobs/*.js".format(base=BASE_PROJECT_DIR)):
            with open(file, encoding='utf-8', mode='r') as job_file:
                job_conf = {}
                while True:
                    line = job_file.readline().strip()
                    if not line.startswith(self.marker):
                        break

                    line = line.split(self.marker, 1)[1]
                    if ':' not in line:
                        break

                    key, value = line.split(':', 1)
                    job_conf[key.strip()] = value.strip()

                if self.schedule_key in job_conf:
                    job_conf['file'] = file
                    self.job_list.append(Job(**job_conf))

    @pyqtSlot(Job)
    def queue_new_job(self, job):
        try:
            self.job_queue.put(job, block=False)
            self.distribute_jobs()
        except queue.Full as e:
            logger.exception('The queue is full. Ignored Job {}, {}'.format(job, e))

    @pyqtSlot()
    def distribute_jobs(self):
        self.check_no_work()
        if self.job_queue.empty():
            return

        for web_page in self.web_pages:
            if self.job_queue.empty():
                return

            if not web_page.is_busy():
                job = self.job_queue.get(block=False)
                web_page.load_job(job)

    def check_no_work(self):
        if not self.job_queue.empty():
            return
        for web_page in self.web_pages:
            if web_page.is_busy():
                return

        # Means that nothing is running
        proc = psutil.Process(os.getpid())
        logger.info('Running Garbage Collector. {}: {}'.format(proc.memory_percent(), proc.memory_info()))
        gc.collect()
        logger.info('After Garbage Collector. {}: {}'.format(proc.memory_percent(), proc.memory_info()))

    @pyqtSlot()
    def shedule_for_next_15_min(self):
        now = datetime.now()
        for job in self.job_list:
            if job.schedule == 'once':
                self.queue_new_job(job)
                continue

            cron_iter = croniter(job.schedule, datetime.now())
            for _ in range(self.job_limit):
                next_job_time = cron_iter.get_next(datetime)
                second_till_next_job = (next_job_time - now).total_seconds()
                job_recalculate_seconds = self.job_recalculate_minutes * 60
                if second_till_next_job <= job_recalculate_seconds:
                    milliseconds = second_till_next_job * 1000
                    QTimer.singleShot(milliseconds, Qt.VeryCoarseTimer, lambda: self.queue_new_job(job))
                else:
                    break

    @pyqtSlot(dict)
    def add_job_to_queue(self, data):
        """Safe to call from another thread"""
        logger.debug('Recieved data: {}'.format(data))
Example #34
0
class PDFWriter(QObject):
    def _pass_json_value_getter(self):
        val = json.dumps(self.bridge_value)
        return val

    def _pass_json_value_setter(self, value):
        self.bridge_value = json.loads(unicode(value))

    _pass_json_value = pyqtProperty(str,
                                    fget=_pass_json_value_getter,
                                    fset=_pass_json_value_setter)

    @pyqtSlot(result=unicode)
    def title(self):
        return self.doc_title

    @pyqtSlot(result=unicode)
    def author(self):
        return self.doc_author

    @pyqtSlot(result=unicode)
    def section(self):
        return self.current_section

    def __init__(self, opts, log, cover_data=None, toc=None):
        from calibre.gui2 import must_use_qt
        must_use_qt()
        QObject.__init__(self)

        self.logger = self.log = log
        self.opts = opts
        self.cover_data = cover_data
        self.paged_js = None
        self.toc = toc

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(opts, self.log)
        self.view.setPage(self.page)
        self.view.setRenderHints(QPainter.Antialiasing
                                 | QPainter.TextAntialiasing
                                 | QPainter.SmoothPixmapTransform)
        self.view.loadFinished.connect(self.render_html,
                                       type=Qt.QueuedConnection)
        for x in (Qt.Horizontal, Qt.Vertical):
            self.view.page().mainFrame().setScrollBarPolicy(
                x, Qt.ScrollBarAlwaysOff)
        self.report_progress = lambda x, y: x
        self.current_section = ''

    def dump(self, items, out_stream, pdf_metadata):
        opts = self.opts
        page_size = get_page_size(self.opts)
        xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY()
        # We cannot set the side margins in the webview as there is no right
        # margin for the last page (the margins are implemented with
        # -webkit-column-gap)
        ml, mr = opts.margin_left, opts.margin_right
        self.doc = PdfDevice(out_stream,
                             page_size=page_size,
                             left_margin=ml,
                             top_margin=0,
                             right_margin=mr,
                             bottom_margin=0,
                             xdpi=xdpi,
                             ydpi=ydpi,
                             errors=self.log.error,
                             debug=self.log.debug,
                             compress=not opts.uncompressed_pdf,
                             mark_links=opts.pdf_mark_links)
        self.footer = opts.pdf_footer_template
        if self.footer:
            self.footer = self.footer.strip()
        if not self.footer and opts.pdf_page_numbers:
            self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>'
        self.header = opts.pdf_header_template
        if self.header:
            self.header = self.header.strip()
        min_margin = 36
        if self.footer and opts.margin_bottom < min_margin:
            self.log.warn(
                'Bottom margin is too small for footer, increasing it.')
            opts.margin_bottom = min_margin
        if self.header and opts.margin_top < min_margin:
            self.log.warn('Top margin is too small for header, increasing it.')
            opts.margin_top = min_margin

        self.page.setViewportSize(QSize(self.doc.width(), self.doc.height()))
        self.render_queue = items
        self.total_items = len(items)

        mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom))
        self.margin_top, self.margin_bottom = map(lambda x: int(floor(x)),
                                                  (mt, mb))

        self.painter = QPainter(self.doc)
        self.doc.set_metadata(title=pdf_metadata.title,
                              author=pdf_metadata.author,
                              tags=pdf_metadata.tags,
                              mi=pdf_metadata.mi)
        self.doc_title = pdf_metadata.title
        self.doc_author = pdf_metadata.author
        self.painter.save()
        try:
            if self.cover_data is not None:
                p = QPixmap()
                try:
                    p.loadFromData(self.cover_data)
                except TypeError:
                    self.log.warn(
                        'This ebook does not have a raster cover, cannot generate cover for PDF'
                        '. Cover type: %s' % type(self.cover_data))
                if not p.isNull():
                    self.doc.init_page()
                    draw_image_page(QRect(*self.doc.full_page_rect),
                                    self.painter,
                                    p,
                                    preserve_aspect_ratio=self.opts.
                                    preserve_cover_aspect_ratio)
                    self.doc.end_page()
        finally:
            self.painter.restore()

        QTimer.singleShot(0, self.render_book)
        if self.loop.exec_() == 1:
            raise Exception('PDF Output failed, see log for details')

        if self.toc is not None and len(self.toc) > 0:
            self.doc.add_outline(self.toc)

        self.painter.end()

        if self.doc.errors_occurred:
            raise Exception('PDF Output failed, see log for details')

    def render_inline_toc(self):
        self.rendered_inline_toc = True
        from calibre.ebooks.pdf.render.toc import toc_as_html
        raw = toc_as_html(self.toc, self.doc, self.opts)
        pt = PersistentTemporaryFile('_pdf_itoc.htm')
        pt.write(raw)
        pt.close()
        self.render_queue.append(pt.name)
        self.render_next()

    def render_book(self):
        if self.doc.errors_occurred:
            return self.loop.exit(1)
        try:
            if not self.render_queue:
                if self.opts.pdf_add_toc and self.toc is not None and len(
                        self.toc) > 0 and not hasattr(self,
                                                      'rendered_inline_toc'):
                    return self.render_inline_toc()
                self.loop.exit()
            else:
                self.render_next()
        except:
            self.logger.exception('Rendering failed')
            self.loop.exit(1)

    def render_next(self):
        item = unicode(self.render_queue.pop(0))

        self.logger.debug('Processing %s...' % item)
        self.current_item = item
        load_html(item, self.view)

    def render_html(self, ok):
        if ok:
            try:
                self.do_paged_render()
            except:
                self.log.exception('Rendering failed')
                self.loop.exit(1)
                return
        else:
            # The document is so corrupt that we can't render the page.
            self.logger.error('Document cannot be rendered.')
            self.loop.exit(1)
            return
        done = self.total_items - len(self.render_queue)
        self.report_progress(
            done / self.total_items,
            _('Rendered %s' % os.path.basename(self.current_item)))
        self.render_book()

    @property
    def current_page_num(self):
        return self.doc.current_page_num

    def load_mathjax(self):
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        mjpath = P(u'viewer/mathjax').replace(os.sep, '/')
        if iswindows:
            mjpath = u'/' + mjpath
        if bool(
                evaljs('''
                    window.mathjax.base = %s;
                    mathjax.check_for_math(); mathjax.math_present
                    ''' % (json.dumps(mjpath, ensure_ascii=False)))):
            self.log.debug('Math present, loading MathJax')
            while not bool(evaljs('mathjax.math_loaded')):
                self.loop.processEvents(self.loop.ExcludeUserInputEvents)
            evaljs(
                'document.getElementById("MathJax_Message").style.display="none";'
            )

    def get_sections(self, anchor_map):
        sections = defaultdict(list)
        ci = os.path.abspath(os.path.normcase(self.current_item))
        if self.toc is not None:
            for toc in self.toc.flat():
                path = toc.abspath or None
                frag = toc.fragment or None
                if path is None:
                    continue
                path = os.path.abspath(os.path.normcase(path))
                if path == ci:
                    col = 0
                    if frag and frag in anchor_map:
                        col = anchor_map[frag]['column']
                    sections[col].append(toc.text or _('Untitled'))

        return sections

    def do_paged_render(self):
        if self.paged_js is None:
            import uuid
            from calibre.utils.resources import compiled_coffeescript as cc
            self.paged_js = cc('ebooks.oeb.display.utils')
            self.paged_js += cc('ebooks.oeb.display.indexing')
            self.paged_js += cc('ebooks.oeb.display.paged')
            self.paged_js += cc('ebooks.oeb.display.mathjax')
            self.hf_uuid = str(uuid.uuid4()).replace('-', '')

        self.view.page().mainFrame().addToJavaScriptWindowObject(
            "py_bridge", self)
        self.view.page().longjs_counter = 0
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        evaljs(self.paged_js)
        self.load_mathjax()

        evaljs('''
        Object.defineProperty(py_bridge, 'value', {
               get : function() { return JSON.parse(this._pass_json_value); },
               set : function(val) { this._pass_json_value = JSON.stringify(val); }
        });

        document.body.style.backgroundColor = "white";
        paged_display.set_geometry(1, %d, %d, %d);
        paged_display.layout();
        paged_display.fit_images();
        py_bridge.value = book_indexing.all_links_and_anchors();
        window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll
        ''' % (self.margin_top, 0, self.margin_bottom))

        amap = self.bridge_value
        if not isinstance(amap, dict):
            amap = {
                'links': [],
                'anchors': {}
            }  # Some javascript error occurred
        sections = self.get_sections(amap['anchors'])
        col = 0

        if self.header:
            self.bridge_value = self.header
            evaljs('paged_display.header_template = py_bridge.value')
        if self.footer:
            self.bridge_value = self.footer
            evaljs('paged_display.footer_template = py_bridge.value')
        if self.header or self.footer:
            evaljs('paged_display.create_header_footer("%s");' % self.hf_uuid)

        start_page = self.current_page_num

        mf = self.view.page().mainFrame()
        while True:
            if col in sections:
                self.current_section = sections[col][0]
            elif col - 1 in sections:
                # Ensure we are using the last section on the previous page as
                # the section for this page, since this page has no sections
                self.current_section = sections[col - 1][-1]
            self.doc.init_page()
            if self.header or self.footer:
                evaljs('paged_display.update_header_footer(%d)' %
                       self.current_page_num)
            self.painter.save()
            mf.render(self.painter)
            self.painter.restore()
            try:
                nsl = int(evaljs('paged_display.next_screen_location()'))
            except (TypeError, ValueError):
                break
            self.doc.end_page()
            if nsl <= 0:
                break
            evaljs(
                'window.scrollTo(%d, 0); paged_display.position_header_footer();'
                % nsl)
            if self.doc.errors_occurred:
                break
            col += 1

        if not self.doc.errors_occurred:
            self.doc.add_links(self.current_item, start_page, amap['links'],
                               amap['anchors'])
Example #35
0
                    subprocess.Popen(shlex.split(relaunch_cmd),
                                     stderr=None,
                                     stdout=None)
                    sys.exit()
            except IOError:
                continue


if __name__ == "__main__":

    order_num = sys.argv[-1]

    collector_log = util.parserLog(
        '/var/log/sbp/flashscore/collector_statistics.log',
        'flashscore-collector-statistics')
    app = QApplication(sys.argv)
    web = QWebView()
    webpage = Collector(parent=web,
                        page_link=common.live_link,
                        debug=True,
                        logger=collector_log,
                        order_num=order_num)
    web.setPage(webpage)
    web.setGeometry(780, 0, 1200, 768)
    web.show()

    try:
        sys.exit(app.exec_())
    except Exception as e:
        collector_log.critical('APP: {}'.format(e))
Example #36
0
class App(QMainWindow):
    @property
    def QSETTINGS(self):
        return QSettings(QSettings.IniFormat, QSettings.UserScope, "mdviewer",
                         "session")

    def __init__(self, parent=None, filename=""):
        QMainWindow.__init__(self, parent)
        self.filename = filename or os.path.join(app_dir, u"README.md")

        # Set environment variables
        self.set_env()

        # Configure Preview window
        self.set_window_title()
        self.resize(self.QSETTINGS.value("size", QSize(800, 400)))
        self.move(self.QSETTINGS.value("pos", QPoint(0, 0)))

        # Activate WebView
        self.web_view = QWebView()
        self.setCentralWidget(self.web_view)

        self.scroll_pos = {}

        # Configure and start file watcher thread
        self.thread1 = WatcherThread(self.filename)
        self.thread1.update.connect(self.update)
        self.watcher = QFileSystemWatcher([self.filename])
        self.watcher.fileChanged.connect(self.thread1.run)
        self.thread1.run()

        self.web_view.loadFinished.connect(self.after_update)

        # Get style sheet
        self.stylesheet = self.QSETTINGS.value("stylesheet", "default.css")

        # Set GUI menus and toolbars
        self.set_menus()
        self.set_search_bar()

    def set_env(self):
        path, name = os.path.split(os.path.abspath(self.filename))
        ext = os.path.splitext(name)[-1].lower()
        os.environ["MDVIEWER_EXT"] = ext[1:]
        os.environ["MDVIEWER_FILE"] = name
        os.environ["MDVIEWER_ORIGIN"] = path

    def set_window_title(self):
        _path, name = os.path.split(os.path.abspath(self.filename))
        self.setWindowTitle(u"%s – MDviewer" % (name))

    def update(self, text, warn):
        "Update Preview."

        self.web_view.settings().setAttribute(QWebSettings.JavascriptEnabled,
                                              True)
        self.web_view.settings().setAttribute(QWebSettings.PluginsEnabled,
                                              True)
        self.web_view.settings().setAttribute(
            QWebSettings.DeveloperExtrasEnabled, True)

        # Configure link behavior
        self.web_view.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.web_view.linkClicked.connect(
            lambda url: self.handle_link_clicked(url))

        # Save scroll position
        if not self.web_view.page().currentFrame().scrollPosition() == QPoint(
                0, 0):
            self.scroll_pos[self.filename] = self.web_view.page().currentFrame(
            ).scrollPosition()

        # Update Preview
        self.web_view.setHtml(text,
                              baseUrl=QUrl.fromLocalFile(
                                  os.path.join(os.getcwd(), self.filename)))

        # Load JavaScript and core CSS
        scr = os.path.join(app_dir, "mdviewer.js")
        css = os.path.join(app_dir, "mdviewer.css")
        add_resources = """
        (function () {
            var head = document.querySelector("head");
            var css = document.createElement("link");
            css.rel = "stylesheet";
            css.href = "%s";
            css.id = "coreCSS";
            head.appendChild(css);
            var script = document.createElement("script");
            script.type = "text/javascript";
            script.src = "%s";
            script.id = "coreJS";
            script.setAttribute("defer", "");
            head.appendChild(script);
        })()
        """ % (css, scr)
        self.web_view.page().currentFrame().evaluateJavaScript(add_resources)

        # Display processor warnings
        if warn: QMessageBox.warning(self, "Processor message", warn)

    def after_update(self):
        "Restore scroll position."

        try:
            pos = self.scroll_pos[self.filename]
        except KeyError:
            pass
        else:
            self.web_view.page().currentFrame().evaluateJavaScript(
                "window.scrollTo(%s, %s)" % (pos.x(), pos.y()))

    def open_file(self):
        filename, _filter = QFileDialog.getOpenFileName(
            self, "Open File", os.path.dirname(self.filename))
        if filename != "":
            self.filename = self.thread1.filename = filename
            self.set_env()
            self.set_window_title()
            self.thread1.run()
        else:
            pass

    def save_html(self):
        filename, _filter = QFileDialog.getSaveFileName(
            self, "Save File", os.path.dirname(self.filename))
        if filename != "":
            proc = Settings.get("processor_path", "pandoc")
            args = Settings.get("processor_args", "")
            args = ("%s" % (args)).split() + [self.filename]
            caller = QProcess()
            caller.start(proc, args)
            caller.waitForFinished()
            html = str(caller.readAllStandardOutput(), "utf8")
            with io.open(filename, "w", encoding="utf8") as f:
                f.writelines(html)
                f.close()
        else:
            pass

    def find(self, text, btn=""):
        page = self.web_view.page()
        back = page.FindFlags(1) if btn is self.prev else page.FindFlags(0)
        case = page.FindFlags(2) if self.case.isChecked() else page.FindFlags(
            0)
        wrap = page.FindFlags(4) if self.wrap.isChecked() else page.FindFlags(
            0)
        page.findText("", page.FindFlags(8))
        page.findText(text, back | wrap | case)

    def set_search_bar(self):
        self.search_bar = QToolBar()
        self.search_bar.setMovable(False)
        self.search_bar.setFloatable(False)
        self.search_bar.setVisible(False)
        self.search_bar.layout().setSpacing(1)
        self.addToolBar(0x8, self.search_bar)

        self.text = QLineEdit(self)
        self.text.setClearButtonEnabled(True)
        self.text.setPlaceholderText(u"Search")
        self.case = QCheckBox(u"Case sensitive", self)
        self.wrap = QCheckBox(u"Wrap", self)
        self.next = QPushButton(u"Next", self)
        self.next.setToolTip(u"Find next")
        self.next.setShortcut(QKeySequence("Return"))
        self.next.setDisabled(True)
        self.prev = QPushButton(u"Previous", self)
        self.prev.setToolTip(u"Find previous")
        self.prev.setShortcut(QKeySequence("Shift+Return"))
        self.prev.setDisabled(True)
        self.done = QPushButton(u"Done", self)
        self.done.setToolTip(u"Hide Search bar")
        self.done.setShortcut(QKeySequence("Esc"))

        def _enable_nav():
            if self.text.text() == "":
                self.next.setDisabled(True)
                self.prev.setDisabled(True)
            else:
                self.next.setDisabled(False)
                self.prev.setDisabled(False)

        def _toggle_btn(btn=""):
            self.text.setFocus()
            self.find(self.text.text(), btn)

        def _hide():
            if self.search_bar.isVisible():
                self.search_bar.hide()

        self.search_bar.addWidget(self.done)
        self.search_bar.addSeparator()
        self.search_bar.addWidget(self.case)
        self.search_bar.addWidget(self.wrap)
        self.search_bar.addWidget(self.text)
        self.search_bar.addSeparator()
        self.search_bar.addWidget(self.next)
        self.search_bar.addWidget(self.prev)
        for btn in (self.prev, self.next):
            btn.pressed[()].connect(lambda btn=btn: _toggle_btn(btn))
        self.done.pressed.connect(_hide)
        self.text.textChanged.connect(self.find)
        self.text.textChanged.connect(_enable_nav)

    def show_search_bar(self):
        self.search_bar.show()
        self.text.setFocus()
        self.text.selectAll()

    def print_doc(self):
        dialog = QPrintPreviewDialog()
        dialog.paintRequested.connect(self.web_view.print_)
        dialog.exec_()

    def quit(self, QCloseEvent):
        self.QSETTINGS.setValue("size", self.size())
        self.QSETTINGS.setValue("pos", self.pos())
        self.QSETTINGS.setValue("stylesheet", self.stylesheet)

        QtWidgets.qApp.quit()

    def zoom_in(self):
        self.web_view.setZoomFactor(self.web_view.zoomFactor() + .1)

    def zoom_out(self):
        self.web_view.setZoomFactor(self.web_view.zoomFactor() - .1)

    def zoom_reset(self):
        self.web_view.setZoomFactor(1)

    def scroll_down(self):
        self.web_view.page().currentFrame().scroll(
            0, +self.web_view.page().viewportSize().height())

    def scroll_up(self):
        self.web_view.page().currentFrame().scroll(
            0, -self.web_view.page().viewportSize().height())

    def toggle_toc(self):
        self.web_view.page().currentFrame().evaluateJavaScript("toggleTOC()")

    def handle_link_clicked(self, url):
        if url.isLocalFile():
            if url.toLocalFile() == os.path.abspath(
                    self.filename) and url.hasFragment():
                self.web_view.page().currentFrame().scrollToAnchor(
                    url.fragment())
                return
            else:
                QDesktopServices.openUrl(url)
        else:
            QDesktopServices.openUrl(url)

    @staticmethod
    def set_stylesheet(self, stylesheet="default.css"):
        path = os.path.join(css_dir, stylesheet)
        url = QUrl.fromLocalFile(path)
        self.web_view.settings().setUserStyleSheetUrl(url)
        self.stylesheet = stylesheet

    def about(self):
        msg_about = QMessageBox(0,
                                "About MDviewer",
                                u"MDviewer\n\nVersion: %s" % (VERSION),
                                parent=self)
        msg_about.show()

    def report_issue(self):
        url = QUrl.fromUserInput(
            "https://github.com/saf-dmitry/mdviewer/issues")
        QDesktopServices.openUrl(url)

    def set_menus(self):
        menubar = self.menuBar()

        file_menu = menubar.addMenu("&File")

        for d in ({
                "name": u"&Open...",
                "shct": "Ctrl+O",
                "func": self.open_file
        }, {
                "name": u"&Save HTML...",
                "shct": "Ctrl+S",
                "func": self.save_html
        }, {
                "name": u"&Find...",
                "shct": "Ctrl+F",
                "func": self.show_search_bar
        }, {
                "name": u"&Print...",
                "shct": "Ctrl+P",
                "func": self.print_doc
        }, {
                "name": u"&Quit",
                "shct": "Ctrl+Q",
                "func": self.quit
        }):
            action = QAction(d["name"], self)
            action.setShortcut(QKeySequence(d["shct"]))
            action.triggered.connect(d["func"])
            file_menu.addAction(action)

        view_menu = menubar.addMenu("&View")

        for d in ({
                "name": u"Zoom &In",
                "shct": "Ctrl++",
                "func": self.zoom_in
        }, {
                "name": u"Zoom &Out",
                "shct": "Ctrl+-",
                "func": self.zoom_out
        }, {
                "name": u"&Actual Size",
                "shct": "Ctrl+0",
                "func": self.zoom_reset
        }):
            action = QAction(d["name"], self)
            action.setShortcut(QKeySequence(d["shct"]))
            action.triggered.connect(d["func"])
            view_menu.addAction(action)

        style_menu = menubar.addMenu("&Style")
        style_menu.setStyleSheet("menu-scrollable: 1")
        style_menu.setDisabled(True)

        if os.path.exists(css_dir):
            files = sorted(os.listdir(css_dir))
            files = [f for f in files if f.endswith(".css")]
            if len(files) > 0:
                style_menu.setDisabled(False)
                group = QActionGroup(self, exclusive=True)
                for i, f in enumerate(files, start=1):
                    name = os.path.splitext(f)[0].replace("&", "&&")
                    action = group.addAction(QtWidgets.QAction(name, self))
                    action.triggered.connect(lambda x, stylesheet=f: self.
                                             set_stylesheet(self, stylesheet))
                    if i < 10: action.setShortcut(QKeySequence("Ctrl+%d" % i))
                    action.setCheckable(True)
                    style_menu.addAction(action)
                    if f == self.stylesheet: action.trigger()

        help_menu = menubar.addMenu("&Help")

        for d in (
            {
                "name": u"&About...",
                "func": self.about
            },
            {
                "name": u"&Report an Issue",
                "func": self.report_issue
            },
        ):
            action = QAction(d["name"], self)
            action.triggered.connect(d["func"])
            help_menu.addAction(action)

        # Redefine reload action
        reload_action = self.web_view.page().action(QWebPage.Reload)
        reload_action.setShortcut(QKeySequence.Refresh)
        reload_action.triggered.connect(self.thread1.run)
        self.web_view.addAction(reload_action)

        # Define additional shortcuts
        QShortcut(QKeySequence("j"), self, activated=self.scroll_down)
        QShortcut(QKeySequence("k"), self, activated=self.scroll_up)
        QShortcut(QKeySequence("t"), self, activated=self.toggle_toc)

    def closeEvent(self, event):
        self.quit(event)
        event.accept()
Example #37
0
class PreviewerHTML(QWidget):
    """
    Class implementing a previewer widget for HTML, Markdown and ReST files.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(PreviewerHTML, self).__init__(parent)

        self.__layout = QVBoxLayout(self)

        self.titleLabel = QLabel(self)
        self.titleLabel.setWordWrap(True)
        self.titleLabel.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__layout.addWidget(self.titleLabel)

        try:
            from PyQt5.QtWebEngineWidgets import QWebEngineView
            self.previewView = QWebEngineView(self)
            self.__usesWebKit = False
        except ImportError:
            from PyQt5.QtWebKitWidgets import QWebPage, QWebView
            self.previewView = QWebView(self)
            self.previewView.page().setLinkDelegationPolicy(
                QWebPage.DelegateAllLinks)
            self.__usesWebKit = True

        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.previewView.sizePolicy().hasHeightForWidth())
        self.previewView.setSizePolicy(sizePolicy)
        self.previewView.setContextMenuPolicy(Qt.NoContextMenu)
        self.previewView.setUrl(QUrl("about:blank"))
        self.__layout.addWidget(self.previewView)

        self.jsCheckBox = QCheckBox(self.tr("Enable JavaScript"), self)
        self.jsCheckBox.setToolTip(
            self.tr("Select to enable JavaScript for HTML previews"))
        self.__layout.addWidget(self.jsCheckBox)

        self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"),
                                     self)
        self.ssiCheckBox.setToolTip(
            self.tr("Select to enable support for Server Side Includes"))
        self.__layout.addWidget(self.ssiCheckBox)

        self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked)
        self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked)
        self.previewView.titleChanged.connect(self.on_previewView_titleChanged)
        if self.__usesWebKit:
            self.previewView.linkClicked.connect(
                self.on_previewView_linkClicked)

        self.jsCheckBox.setChecked(Preferences.getUI("ShowFilePreviewJS"))
        self.ssiCheckBox.setChecked(Preferences.getUI("ShowFilePreviewSSI"))

        self.__scrollBarPositions = {}
        self.__vScrollBarAtEnd = {}
        self.__hScrollBarAtEnd = {}

        self.__processingThread = PreviewProcessingThread()
        self.__processingThread.htmlReady.connect(self.__setHtml)

        self.__previewedPath = None
        self.__previewedEditor = None

    def shutdown(self):
        """
        Public method to perform shutdown actions.
        """
        self.__processingThread.wait()

    @pyqtSlot(bool)
    def on_jsCheckBox_clicked(self, checked):
        """
        Private slot to enable/disable JavaScript.
        
        @param checked state of the checkbox (boolean)
        """
        Preferences.setUI("ShowFilePreviewJS", checked)
        self.__setJavaScriptEnabled(checked)

    def __setJavaScriptEnabled(self, enable):
        """
        Private method to enable/disable JavaScript.
        
        @param enable flag indicating the enable state (boolean)
        """
        self.jsCheckBox.setChecked(enable)

        settings = self.previewView.settings()
        settings.setAttribute(settings.JavascriptEnabled, enable)

        self.processEditor()

    @pyqtSlot(bool)
    def on_ssiCheckBox_clicked(self, checked):
        """
        Private slot to enable/disable SSI.
        
        @param checked state of the checkbox (boolean)
        """
        Preferences.setUI("ShowFilePreviewSSI", checked)
        self.processEditor()

    def processEditor(self, editor=None):
        """
        Public slot to process an editor's text.
        
        @param editor editor to be processed (Editor)
        """
        if editor is None:
            editor = self.__previewedEditor
        else:
            self.__previewedEditor = editor

        if editor is not None:
            fn = editor.getFileName()

            if fn:
                extension = os.path.normcase(os.path.splitext(fn)[1][1:])
            else:
                extension = ""
            if extension in \
                Preferences.getEditor("PreviewHtmlFileNameExtensions") or \
               editor.getLanguage() == "HTML":
                language = "HTML"
            elif extension in \
                    Preferences.getEditor("PreviewMarkdownFileNameExtensions"):
                language = "Markdown"
            elif extension in \
                    Preferences.getEditor("PreviewRestFileNameExtensions"):
                language = "ReST"
            else:
                self.__setHtml(
                    fn,
                    self.tr(
                        "<p>No preview available for this type of file.</p>"))
                return

            if fn:
                project = e5App().getObject("Project")
                if project.isProjectFile(fn):
                    rootPath = project.getProjectPath()
                else:
                    rootPath = os.path.dirname(os.path.abspath(fn))
            else:
                rootPath = ""

            self.__processingThread.process(
                fn, language, editor.text(), self.ssiCheckBox.isChecked(),
                rootPath, Preferences.getEditor("PreviewRestUseSphinx"))

    def __setHtml(self, filePath, html):
        """
        Private method to set the HTML to the view and restore the scroll bars
        positions.
        
        @param filePath file path of the previewed editor (string)
        @param html processed HTML text ready to be shown (string)
        """
        self.__previewedPath = Utilities.normcasepath(
            Utilities.fromNativeSeparators(filePath))
        self.__saveScrollBarPositions()
        if self.__usesWebKit:
            self.previewView.page().mainFrame().contentsSizeChanged.connect(
                self.__restoreScrollBarPositions)
        else:
            self.previewView.page().loadFinished.connect(
                self.__restoreScrollBarPositions)
        self.previewView.setHtml(html, baseUrl=QUrl.fromLocalFile(filePath))

    @pyqtSlot(str)
    def on_previewView_titleChanged(self, title):
        """
        Private slot to handle a change of the title.
        
        @param title new title (string)
        """
        if title:
            self.titleLabel.setText(self.tr("Preview - {0}").format(title))
        else:
            self.titleLabel.setText(self.tr("Preview"))

    def __saveScrollBarPositions(self):
        """
        Private method to save scroll bar positions for a previewed editor.
        """
        if self.__usesWebKit:
            frame = self.previewView.page().mainFrame()
            if frame.contentsSize() == QSize(0, 0):
                return  # no valid data, nothing to save

            pos = frame.scrollPosition()
            self.__scrollBarPositions[self.__previewedPath] = pos
            self.__hScrollBarAtEnd[self.__previewedPath] = \
                frame.scrollBarMaximum(Qt.Horizontal) == pos.x()
            self.__vScrollBarAtEnd[self.__previewedPath] = \
                frame.scrollBarMaximum(Qt.Vertical) == pos.y()
        else:
            from PyQt5.QtCore import QPoint
            pos = self.__execJavaScript("(function() {"
                                        "var res = {"
                                        "    x: 0,"
                                        "    y: 0,"
                                        "};"
                                        "res.x = window.scrollX;"
                                        "res.y = window.scrollY;"
                                        "return res;"
                                        "})()")
            pos = QPoint(pos["x"], pos["y"])
            self.__scrollBarPositions[self.__previewedPath] = pos
            self.__hScrollBarAtEnd[self.__previewedPath] = False
            self.__vScrollBarAtEnd[self.__previewedPath] = False

    def __restoreScrollBarPositions(self):
        """
        Private method to restore scroll bar positions for a previewed editor.
        """
        if self.__usesWebKit:
            try:
                self.previewView.page().mainFrame().contentsSizeChanged.\
                    disconnect(self.__restoreScrollBarPositions)
            except TypeError:
                # not connected, simply ignore it
                pass

            if self.__previewedPath not in self.__scrollBarPositions:
                return

            frame = self.previewView.page().mainFrame()
            frame.setScrollPosition(
                self.__scrollBarPositions[self.__previewedPath])

            if self.__hScrollBarAtEnd[self.__previewedPath]:
                frame.setScrollBarValue(Qt.Horizontal,
                                        frame.scrollBarMaximum(Qt.Horizontal))

            if self.__vScrollBarAtEnd[self.__previewedPath]:
                frame.setScrollBarValue(Qt.Vertical,
                                        frame.scrollBarMaximum(Qt.Vertical))
        else:
            if self.__previewedPath not in self.__scrollBarPositions:
                return

            pos = self.__scrollBarPositions[self.__previewedPath]
            self.previewView.page().runJavaScript(
                "window.scrollTo({0}, {1});".format(pos.x(), pos.y()))

    @pyqtSlot(QUrl)
    def on_previewView_linkClicked(self, url):
        """
        Private slot handling the clicking of a link.
        
        @param url url of the clicked link (QUrl)
        """
        e5App().getObject("UserInterface").launchHelpViewer(url.toString())

    def __execJavaScript(self, script):
        """
        Private function to execute a JavaScript function Synchroneously.
        
        @param script JavaScript script source to be executed
        @type str
        @return result of the script
        @rtype depending upon script result
        """
        from PyQt5.QtCore import QEventLoop
        loop = QEventLoop()
        resultDict = {"res": None}

        def resultCallback(res, resDict=resultDict):
            if loop and loop.isRunning():
                resDict["res"] = res
                loop.quit()

        self.previewView.page().runJavaScript(script, resultCallback)

        loop.exec_()
        return resultDict["res"]
Example #38
0
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent widget (QWidget)
        """
        super(PreviewerHTML, self).__init__(parent)

        self.__layout = QVBoxLayout(self)

        self.titleLabel = QLabel(self)
        self.titleLabel.setWordWrap(True)
        self.titleLabel.setTextInteractionFlags(Qt.NoTextInteraction)
        self.__layout.addWidget(self.titleLabel)

        try:
            from PyQt5.QtWebEngineWidgets import QWebEngineView
            self.previewView = QWebEngineView(self)
            self.__usesWebKit = False
        except ImportError:
            from PyQt5.QtWebKitWidgets import QWebPage, QWebView
            self.previewView = QWebView(self)
            self.previewView.page().setLinkDelegationPolicy(
                QWebPage.DelegateAllLinks)
            self.__usesWebKit = True

        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.previewView.sizePolicy().hasHeightForWidth())
        self.previewView.setSizePolicy(sizePolicy)
        self.previewView.setContextMenuPolicy(Qt.NoContextMenu)
        self.previewView.setUrl(QUrl("about:blank"))
        self.__layout.addWidget(self.previewView)

        self.jsCheckBox = QCheckBox(self.tr("Enable JavaScript"), self)
        self.jsCheckBox.setToolTip(
            self.tr("Select to enable JavaScript for HTML previews"))
        self.__layout.addWidget(self.jsCheckBox)

        self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"),
                                     self)
        self.ssiCheckBox.setToolTip(
            self.tr("Select to enable support for Server Side Includes"))
        self.__layout.addWidget(self.ssiCheckBox)

        self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked)
        self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked)
        self.previewView.titleChanged.connect(self.on_previewView_titleChanged)
        if self.__usesWebKit:
            self.previewView.linkClicked.connect(
                self.on_previewView_linkClicked)

        self.jsCheckBox.setChecked(Preferences.getUI("ShowFilePreviewJS"))
        self.ssiCheckBox.setChecked(Preferences.getUI("ShowFilePreviewSSI"))

        self.__scrollBarPositions = {}
        self.__vScrollBarAtEnd = {}
        self.__hScrollBarAtEnd = {}

        self.__processingThread = PreviewProcessingThread()
        self.__processingThread.htmlReady.connect(self.__setHtml)

        self.__previewedPath = None
        self.__previewedEditor = None
Example #39
0
class PDFWriter(QObject):
    @pyqtSlot(result=unicode_type)
    def title(self):
        return self.doc_title

    @pyqtSlot(result=unicode_type)
    def author(self):
        return self.doc_author

    @pyqtSlot(result=unicode_type)
    def section(self):
        return self.current_section

    @pyqtSlot(result=unicode_type)
    def tl_section(self):
        return self.current_tl_section

    def __init__(self, opts, log, cover_data=None, toc=None):
        from calibre.gui2 import must_use_qt
        must_use_qt()
        QObject.__init__(self)

        self.logger = self.log = log
        self.mathjax_dir = P('mathjax', allow_user_override=False)
        current_log(log)
        self.opts = opts
        self.cover_data = cover_data
        self.paged_js = None
        self.toc = toc

        self.loop = QEventLoop()
        self.view = QWebView()
        self.page = Page(opts, self.log)
        self.view.setPage(self.page)
        self.view.setRenderHints(QPainter.Antialiasing
                                 | QPainter.TextAntialiasing
                                 | QPainter.SmoothPixmapTransform)
        self.view.loadFinished.connect(self.render_html,
                                       type=Qt.QueuedConnection)
        self.view.loadProgress.connect(self.load_progress)
        self.ignore_failure = None
        self.hang_check_timer = t = QTimer(self)
        t.timeout.connect(self.hang_check)
        t.setInterval(1000)

        for x in (Qt.Horizontal, Qt.Vertical):
            self.view.page().mainFrame().setScrollBarPolicy(
                x, Qt.ScrollBarAlwaysOff)
        self.report_progress = lambda x, y: x
        self.current_section = ''
        self.current_tl_section = ''

    def dump(self, items, out_stream, pdf_metadata):
        opts = self.opts
        page_size = get_page_size(self.opts)
        xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY()

        def margin(which):
            val = getattr(opts, 'pdf_page_margin_' + which)
            if val == 0.0:
                val = getattr(opts, 'margin_' + which)
            return val

        ml, mr, mt, mb = map(margin, 'left right top bottom'.split())
        # We cannot set the side margins in the webview as there is no right
        # margin for the last page (the margins are implemented with
        # -webkit-column-gap)
        self.doc = PdfDevice(out_stream,
                             page_size=page_size,
                             left_margin=ml,
                             top_margin=0,
                             right_margin=mr,
                             bottom_margin=0,
                             xdpi=xdpi,
                             ydpi=ydpi,
                             errors=self.log.error,
                             debug=self.log.debug,
                             compress=not opts.uncompressed_pdf,
                             opts=opts,
                             mark_links=opts.pdf_mark_links,
                             page_margins=(ml, mr, mt, mb))
        self.footer = opts.pdf_footer_template
        if self.footer:
            self.footer = self.footer.strip()
        if not self.footer and opts.pdf_page_numbers:
            self.footer = '<p style="text-align:center; text-indent: 0">_PAGENUM_</p>'
        self.header = opts.pdf_header_template
        if self.header:
            self.header = self.header.strip()
        min_margin = 1.5 * opts._final_base_font_size
        if self.footer and mb < min_margin:
            self.log.warn(
                'Bottom margin is too small for footer, increasing it to %.1fpts'
                % min_margin)
            mb = min_margin
        if self.header and mt < min_margin:
            self.log.warn(
                'Top margin is too small for header, increasing it to %.1fpts'
                % min_margin)
            mt = min_margin

        self.page.setViewportSize(QSize(self.doc.width(), self.doc.height()))
        self.render_queue = items
        self.total_items = len(items)

        mt, mb = map(self.doc.to_px, (mt, mb))
        self.margin_top, self.margin_bottom = map(lambda x: int(floor(x)),
                                                  (mt, mb))

        self.painter = QPainter(self.doc)
        try:
            self.book_language = pdf_metadata.mi.languages[0]
        except Exception:
            self.book_language = 'eng'
        self.doc.set_metadata(title=pdf_metadata.title,
                              author=pdf_metadata.author,
                              tags=pdf_metadata.tags,
                              mi=pdf_metadata.mi)
        self.doc_title = pdf_metadata.title
        self.doc_author = pdf_metadata.author
        self.painter.save()
        try:
            if self.cover_data is not None:
                p = QPixmap()
                try:
                    p.loadFromData(self.cover_data)
                except TypeError:
                    self.log.warn(
                        'This ebook does not have a raster cover, cannot generate cover for PDF'
                        '. Cover type: %s' % type(self.cover_data))
                if not p.isNull():
                    self.doc.init_page()
                    draw_image_page(QRect(*self.doc.full_page_rect),
                                    self.painter,
                                    p,
                                    preserve_aspect_ratio=self.opts.
                                    preserve_cover_aspect_ratio)
                    self.doc.end_page()
        finally:
            self.painter.restore()

        QTimer.singleShot(0, self.render_book)
        if self.loop.exec_() == 1:
            raise Exception('PDF Output failed, see log for details')

        if self.toc is not None and len(self.toc) > 0:
            self.doc.add_outline(self.toc)

        self.painter.end()

        if self.doc.errors_occurred:
            raise Exception('PDF Output failed, see log for details')

    def render_inline_toc(self):
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        self.rendered_inline_toc = True
        from calibre.ebooks.pdf.render.toc import toc_as_html
        raw = toc_as_html(self.toc, self.doc, self.opts, evaljs)
        pt = PersistentTemporaryFile('_pdf_itoc.htm')
        pt.write(raw)
        pt.close()
        self.render_queue.append(pt.name)
        self.render_next()

    def render_book(self):
        if self.doc.errors_occurred:
            return self.loop.exit(1)
        try:
            if not self.render_queue:
                if self.opts.pdf_add_toc and self.toc is not None and len(
                        self.toc) > 0 and not hasattr(self,
                                                      'rendered_inline_toc'):
                    return self.render_inline_toc()
                self.loop.exit()
            else:
                self.render_next()
        except:
            self.logger.exception('Rendering failed')
            self.loop.exit(1)

    def render_next(self):
        item = unicode_type(self.render_queue.pop(0))

        self.logger.debug('Processing %s...' % item)
        self.current_item = item
        load_html(item, self.view)
        self.last_load_progress_at = monotonic()
        self.hang_check_timer.start()

    def load_progress(self, progress):
        self.last_load_progress_at = monotonic()

    def hang_check(self):
        if monotonic() - self.last_load_progress_at > 60:
            self.log.warn('Timed out waiting for %s to render' %
                          self.current_item)
            self.ignore_failure = self.current_item
            self.view.stop()

    def render_html(self, ok):
        self.hang_check_timer.stop()
        if self.ignore_failure == self.current_item:
            ok = True
        self.ignore_failure = None
        if ok:
            try:
                self.do_paged_render()
            except:
                self.log.exception('Rendering failed')
                self.loop.exit(1)
                return
        else:
            # The document is so corrupt that we can't render the page.
            self.logger.error('Document %s cannot be rendered.' %
                              self.current_item)
            self.loop.exit(1)
            return
        done = self.total_items - len(self.render_queue)
        self.report_progress(
            done / self.total_items,
            _('Rendered %s' % os.path.basename(self.current_item)))
        self.render_book()

    @property
    def current_page_num(self):
        return self.doc.current_page_num

    def load_mathjax(self):
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        mjpath = self.mathjax_dir.replace(os.sep, '/')
        if iswindows:
            mjpath = u'/' + mjpath
        if bool(
                evaljs('''
                    window.mathjax.base = %s;
                    mathjax.check_for_math(); mathjax.math_present
                    ''' % (json.dumps(mjpath, ensure_ascii=False)))):
            self.log.debug('Math present, loading MathJax')
            while not bool(evaljs('mathjax.math_loaded')):
                self.loop.processEvents(self.loop.ExcludeUserInputEvents)
            # give the MathJax fonts time to load
            for i in range(5):
                self.loop.processEvents(self.loop.ExcludeUserInputEvents)
            evaljs(
                'document.getElementById("MathJax_Message").style.display="none";'
            )

    def load_header_footer_images(self):
        from calibre.utils.monotonic import monotonic
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        st = monotonic()
        while not evaljs('paged_display.header_footer_images_loaded()'):
            self.loop.processEvents(self.loop.ExcludeUserInputEvents)
            if monotonic() - st > 5:
                self.log.warn(
                    'Header and footer images have not loaded in 5 seconds, ignoring'
                )
                break

    def get_sections(self, anchor_map, only_top_level=False):
        sections = defaultdict(list)
        ci = os.path.abspath(os.path.normcase(self.current_item))
        if self.toc is not None:
            tocentries = self.toc.top_level_items(
            ) if only_top_level else self.toc.flat()
            for toc in tocentries:
                path = toc.abspath or None
                frag = toc.fragment or None
                if path is None:
                    continue
                path = os.path.abspath(os.path.normcase(path))
                if path == ci:
                    col = 0
                    if frag and frag in anchor_map:
                        col = anchor_map[frag]['column']
                    sections[col].append(toc.text or _('Untitled'))

        return sections

    def hyphenate(self, evaljs):
        evaljs(u'''\
        Hyphenator.config(
            {
            'minwordlength'    : 6,
            // 'hyphenchar'     : '|',
            'displaytogglebox' : false,
            'remoteloading'    : false,
            'doframes'         : true,
            'defaultlanguage'  : 'en',
            'storagetype'      : 'session',
            'onerrorhandler'   : function (e) {
                                    console.log(e);
                                }
            });
        Hyphenator.hyphenate(document.body, "%s");
        ''' % self.hyphenate_lang)

    def convert_page_margins(self, doc_margins):
        ans = [0, 0, 0, 0]

        def convert(name, idx, vertical=True):
            m = doc_margins.get(name)
            if m is None:
                ans[idx] = getattr(self.doc.engine, '{}_margin'.format(name))
            else:
                ans[idx] = m

        convert('left', 0,
                False), convert('top',
                                1), convert('right', 2,
                                            False), convert('bottom', 3)
        return ans

    def do_paged_render(self):
        if self.paged_js is None:
            import uuid
            from calibre.utils.resources import compiled_coffeescript as cc
            self.paged_js = cc('ebooks.oeb.display.utils').decode('utf-8')
            self.paged_js += cc('ebooks.oeb.display.indexing').decode('utf-8')
            self.paged_js += cc('ebooks.oeb.display.paged').decode('utf-8')
            self.paged_js += cc('ebooks.oeb.display.mathjax').decode('utf-8')
            if self.opts.pdf_hyphenate:
                self.paged_js += P('viewer/hyphenate/Hyphenator.js',
                                   data=True).decode('utf-8')
                hjs, self.hyphenate_lang = load_hyphenator_dicts(
                    {}, self.book_language)
                self.paged_js += hjs
            self.hf_uuid = str(uuid.uuid4()).replace('-', '')

        self.view.page().mainFrame().addToJavaScriptWindowObject(
            "py_bridge", self)
        self.view.page().longjs_counter = 0
        evaljs = self.view.page().mainFrame().evaluateJavaScript
        evaljs(self.paged_js)
        self.load_mathjax()
        if self.opts.pdf_hyphenate:
            self.hyphenate(evaljs)

        margin_top, margin_bottom = self.margin_top, self.margin_bottom
        page_margins = None
        if self.opts.pdf_use_document_margins:
            doc_margins = evaljs(
                'document.documentElement.getAttribute("data-calibre-pdf-output-page-margins")'
            )
            try:
                doc_margins = json.loads(doc_margins)
            except Exception:
                doc_margins = None
            if doc_margins and isinstance(doc_margins, dict):
                doc_margins = {
                    k: float(v)
                    for k, v in iteritems(doc_margins)
                    if isinstance(v, numbers.Number)
                    and k in {'right', 'top', 'left', 'bottom'}
                }
                if doc_margins:
                    margin_top = margin_bottom = 0
                    page_margins = self.convert_page_margins(doc_margins)

        amap = json.loads(
            evaljs('''
        document.body.style.backgroundColor = "white";
        // Qt WebKit cannot handle opacity with the Pdf backend
        s = document.createElement('style');
        s.textContent = '* {opacity: 1 !important}';
        document.documentElement.appendChild(s);
        paged_display.set_geometry(1, %d, %d, %d);
        paged_display.layout();
        paged_display.fit_images();
        ret = book_indexing.all_links_and_anchors();
        window.scrollTo(0, 0); // This is needed as getting anchor positions could have caused the viewport to scroll
        JSON.stringify(ret);
        ''' % (margin_top, 0, margin_bottom)))

        if not isinstance(amap, dict):
            amap = {
                'links': [],
                'anchors': {}
            }  # Some javascript error occurred
        for val in itervalues(amap['anchors']):
            if isinstance(val, dict) and 'column' in val:
                val['column'] = int(val['column'])
        for href, val in amap['links']:
            if isinstance(val, dict) and 'column' in val:
                val['column'] = int(val['column'])
        sections = self.get_sections(amap['anchors'])
        tl_sections = self.get_sections(amap['anchors'], True)
        col = 0

        if self.header:
            evaljs('paged_display.header_template = ' +
                   json.dumps(self.header))
        if self.footer:
            evaljs('paged_display.footer_template = ' +
                   json.dumps(self.footer))
        if self.header or self.footer:
            evaljs('paged_display.create_header_footer("%s");' % self.hf_uuid)

        start_page = self.current_page_num

        mf = self.view.page().mainFrame()

        def set_section(col, sections, attr):
            # If this page has no section, use the section from the previous page
            idx = col if col in sections else col - 1 if col - 1 in sections else None
            if idx is not None:
                setattr(self, attr, sections[idx][0])

        from calibre.ebooks.pdf.render.toc import calculate_page_number

        while True:
            set_section(col, sections, 'current_section')
            set_section(col, tl_sections, 'current_tl_section')
            self.doc.init_page(page_margins)
            num = calculate_page_number(self.current_page_num,
                                        self.opts.pdf_page_number_map, evaljs)
            if self.header or self.footer:
                if evaljs('paged_display.update_header_footer(%d)' %
                          num) is True:
                    self.load_header_footer_images()

            self.painter.save()
            mf.render(self.painter, mf.ContentsLayer)
            self.painter.restore()
            try:
                nsl = int(evaljs('paged_display.next_screen_location()'))
            except (TypeError, ValueError):
                break
            self.doc.end_page(nsl <= 0)
            if nsl <= 0:
                break
            evaljs(
                'window.scrollTo(%d, 0); paged_display.position_header_footer();'
                % nsl)
            if self.doc.errors_occurred:
                break
            col += 1

        if not self.doc.errors_occurred and self.doc.current_page_num > 1:
            self.doc.add_links(self.current_item, start_page, amap['links'],
                               amap['anchors'])
Example #40
0
 def setUrl(self, qurl):
     self.page().current_root = current_container().root
     return QWebView.setUrl(self, qurl)
Example #41
0
    def __init__(self, parent=None):
        QWebView.__init__(self, parent)
        self.base_url = None
        self._parent = weakref.ref(parent)
        self.readonly = False

        self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL)

        extra_shortcuts = {
            'ToggleBold': 'Bold',
            'ToggleItalic': 'Italic',
            'ToggleUnderline': 'Underline',
        }

        for wac, name, icon, text, checkable in [
            ('ToggleBold', 'bold', 'format-text-bold', _('Bold'), True),
            ('ToggleItalic', 'italic', 'format-text-italic', _('Italic'),
             True),
            ('ToggleUnderline', 'underline', 'format-text-underline',
             _('Underline'), True),
            ('ToggleStrikethrough', 'strikethrough',
             'format-text-strikethrough', _('Strikethrough'), True),
            ('ToggleSuperscript', 'superscript', 'format-text-superscript',
             _('Superscript'), True),
            ('ToggleSubscript', 'subscript', 'format-text-subscript',
             _('Subscript'), True),
            ('InsertOrderedList', 'ordered_list', 'format-list-ordered',
             _('Ordered list'), True),
            ('InsertUnorderedList', 'unordered_list', 'format-list-unordered',
             _('Unordered list'), True),
            ('AlignLeft', 'align_left', 'format-justify-left', _('Align left'),
             False),
            ('AlignCenter', 'align_center', 'format-justify-center',
             _('Align center'), False),
            ('AlignRight', 'align_right', 'format-justify-right',
             _('Align right'), False),
            ('AlignJustified', 'align_justified', 'format-justify-fill',
             _('Align justified'), False),
            ('Undo', 'undo', 'edit-undo', _('Undo'), False),
            ('Redo', 'redo', 'edit-redo', _('Redo'), False),
            ('RemoveFormat', 'remove_format', 'edit-clear',
             _('Remove formatting'), False),
            ('Copy', 'copy', 'edit-copy', _('Copy'), False),
            ('Paste', 'paste', 'edit-paste', _('Paste'), False),
            ('Cut', 'cut', 'edit-cut', _('Cut'), False),
            ('Indent', 'indent', 'format-indent-more',
             _('Increase indentation'), False),
            ('Outdent', 'outdent', 'format-indent-less',
             _('Decrease indentation'), False),
            ('SelectAll', 'select_all', 'edit-select-all', _('Select all'),
             False),
        ]:
            ac = PageAction(wac, icon, text, checkable, self)
            setattr(self, 'action_' + name, ac)
            ss = extra_shortcuts.get(wac, None)
            if ss:
                ac.setShortcut(QKeySequence(getattr(QKeySequence, ss)))
            if wac == 'RemoveFormat':
                ac.triggered.connect(self.remove_format_cleanup,
                                     type=Qt.QueuedConnection)

        self.action_color = QAction(QIcon(I('format-text-color.png')),
                                    _('Foreground color'), self)
        self.action_color.triggered.connect(self.foreground_color)

        self.action_background = QAction(QIcon(I('format-fill-color.png')),
                                         _('Background color'), self)
        self.action_background.triggered.connect(self.background_color)

        self.action_block_style = QAction(QIcon(I('format-text-heading.png')),
                                          _('Style text block'), self)
        self.action_block_style.setToolTip(_('Style the selected text block'))
        self.block_style_menu = QMenu(self)
        self.action_block_style.setMenu(self.block_style_menu)
        self.block_style_actions = []
        for text, name in [
            (_('Normal'), 'p'),
            (_('Heading') + ' 1', 'h1'),
            (_('Heading') + ' 2', 'h2'),
            (_('Heading') + ' 3', 'h3'),
            (_('Heading') + ' 4', 'h4'),
            (_('Heading') + ' 5', 'h5'),
            (_('Heading') + ' 6', 'h6'),
            (_('Pre-formatted'), 'pre'),
            (_('Blockquote'), 'blockquote'),
            (_('Address'), 'address'),
        ]:
            ac = BlockStyleAction(text, name, self)
            self.block_style_menu.addAction(ac)
            self.block_style_actions.append(ac)

        self.action_insert_link = QAction(QIcon(I('insert-link.png')),
                                          _('Insert link or image'), self)
        self.action_insert_hr = QAction(QIcon(I('format-text-hr.png')),
                                        _('Insert separator'), self)
        self.action_insert_link.triggered.connect(self.insert_link)
        self.action_insert_hr.triggered.connect(self.insert_hr)
        self.pageAction(QWebPage.ToggleBold).changed.connect(
            self.update_link_action)
        self.action_insert_link.setEnabled(False)
        self.action_insert_hr.setEnabled(False)
        self.action_clear = QAction(QIcon(I('trash.png')), _('Clear'), self)
        self.action_clear.triggered.connect(self.clear_text)

        self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
        self.page().linkClicked.connect(self.link_clicked)
        secure_web_page(self.page().settings())

        self.setHtml('')
        self.set_readonly(False)
        self.page().contentsChanged.connect(self.data_changed)
Example #42
0
    def __init__(self, parent=None):
        QWebView.__init__(self, parent)

        self.requestQueue = []
        self.isProcessingExclusively = False
Example #43
0
class HandlerClass:
    def __init__(self, halcomp, widgets, paths):
        self.h = halcomp
        self.w = widgets
        self.PATHS = paths
        self.gcodes = GCodes()

        self.valid = QtGui.QDoubleValidator(-999.999, 999.999, 3)
        
        KEYBIND.add_call('Key_F12','on_keycall_F12')
        KEYBIND.add_call('Key_Pause', 'on_keycall_pause')
                
        # some global variables
        self.run_time = 0
        self.time_tenths = 0
        self.timerOn = False
        self.home_all = False
        self.max_linear_velocity = INFO.MAX_LINEAR_VELOCITY * 60
        self.system_list = ["G54","G55","G56","G57","G58","G59","G59.1","G59.2","G59.3"]
        self.slow_jog_factor = 10
        self.reload_tool = 0
        self.last_loaded_program = ""
        self.first_turnon = True
        self.lineedit_list = ["work_height", "touch_height", "sensor_height",
                              "laser_x", "laser_y", "sensor_x", "sensor_y",
                              "search_vel", "probe_vel", "max_probe", "eoffset_count"]
        self.onoff_list = ["frame_program", "frame_tool", "frame_dro", "frame_override", "frame_status"]
        self.auto_list = ["chk_eoffsets", "cmb_gcode_history"]
        self.axis_a_list = ["label_axis_a", "dro_axis_a", "action_zero_a", "axistoolbutton_a",
                            "action_home_a", "widget_jog_angular", "widget_increments_angular",
                            "a_plus_jogbutton", "a_minus_jogbutton"]
        self.html = """<html>
<head>
<title>Test page for the download:// scheme</title>
</head>
<body>
<h1>Setup Tab</h1>
<p>If you select a file with .html as a file ending, it will be shown here..</p>
<img src="file://%s" alt="lcnc_swoop" />
<hr />

<a href="http://www.linuxcnc.org/docs/2.8/html/gui/qtdragon.html">QtDragon Documentation link</a>
</body>
</html>
""" %(os.path.join(paths.IMAGEDIR,'lcnc_swoop.png'))

        STATUS.connect('general', self.dialog_return)
        STATUS.connect('state-on', lambda w: self.enable_onoff(True))
        STATUS.connect('state-off', lambda w: self.enable_onoff(False))
        STATUS.connect('mode-manual', lambda w: self.enable_auto(True))
        STATUS.connect('mode-mdi', lambda w: self.enable_auto(True))
        STATUS.connect('mode-auto', lambda w: self.enable_auto(False))
        STATUS.connect('gcode-line-selected', lambda w, line: self.set_start_line(line))
        STATUS.connect('hard-limits-tripped', self.hard_limit_tripped)
        STATUS.connect('interp-idle', lambda w: self.set_start_line(0))
        STATUS.connect('program-pause-changed', lambda w, state: self.w.spindle_pause.setEnabled(state))
        STATUS.connect('user-system-changed', self.user_system_changed)
        STATUS.connect('file-loaded', self.file_loaded)
        STATUS.connect('homed', self.homed)
        STATUS.connect('all-homed', self.all_homed)
        STATUS.connect('not-all-homed', self.not_all_homed)
        STATUS.connect('periodic', lambda w: self.update_runtimer())
        STATUS.connect('command-running', lambda w: self.start_timer())
        STATUS.connect('command-stopped', lambda w: self.stop_timer())

    def class_patch__(self):
        self.old_fman = FM.load
        FM.load = self.load_code

    def initialized__(self):
        self.init_pins()
        self.init_preferences()
        self.init_widgets()
        self.w.stackedWidget_log.setCurrentIndex(0)
        self.w.stackedWidget.setCurrentIndex(0)
        self.w.stackedWidget_dro.setCurrentIndex(0)
        self.w.spindle_pause.setEnabled(False)
        self.w.btn_dimensions.setChecked(True)
        self.w.page_buttonGroup.buttonClicked.connect(self.main_tab_changed)
        self.w.filemanager.onUserClicked()    
        self.w.filemanager_usb.onMediaClicked()
        self.w.filemanager.list.setAlternatingRowColors(False)
        self.w.filemanager_usb.list.setAlternatingRowColors(False)

    # hide widgets for A axis if not present
        if "A" not in INFO.AVAILABLE_AXES:
            for i in self.axis_a_list:
                self.w[i].hide()
            self.w.lbl_increments_linear.setText("INCREMENTS")
    # set validators for lineEdit widgets
        for val in self.lineedit_list:
            self.w['lineEdit_' + val].setValidator(self.valid)
        
    #############################
    # SPECIAL FUNCTIONS SECTION #
    #############################
    def init_pins(self):
        # spindle control pins
        pin = self.h.newpin("spindle_amps", hal.HAL_FLOAT, hal.HAL_IN)
        hal_glib.GPin(pin).connect("value_changed", self.spindle_pwr_changed)
        pin = self.h.newpin("spindle_volts", hal.HAL_FLOAT, hal.HAL_IN)
        hal_glib.GPin(pin).connect("value_changed", self.spindle_pwr_changed)
        pin = self.h.newpin("spindle_fault", hal.HAL_U32, hal.HAL_IN)
        hal_glib.GPin(pin).connect("value_changed", self.spindle_fault_changed)
        pin = self.h.newpin("modbus-errors", hal.HAL_U32, hal.HAL_IN)
        hal_glib.GPin(pin).connect("value_changed", self.mb_errors_changed)
        # external offset control pins
        self.h.newpin("eoffset_enable", hal.HAL_BIT, hal.HAL_OUT)
        self.h.newpin("eoffset_clear", hal.HAL_BIT, hal.HAL_OUT)
        self.h.newpin("eoffset_count", hal.HAL_S32, hal.HAL_OUT)
        pin = self.h.newpin("eoffset_value", hal.HAL_FLOAT, hal.HAL_IN)
        hal_glib.GPin(pin).connect("value_changed", self.eoffset_changed)

    def init_preferences(self):
        if not self.w.PREFS_:
            self.add_status("CRITICAL - no preference file found, enable preferences in screenoptions widget")
            return
        self.last_loaded_program = self.w.PREFS_.getpref('last_loaded_file', None, str,'BOOK_KEEPING')
        self.reload_tool = self.w.PREFS_.getpref('Tool to load', 0, int,'CUSTOM_FORM_ENTRIES')
        self.w.lineEdit_laser_x.setText(str(self.w.PREFS_.getpref('Laser X', 100, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_laser_y.setText(str(self.w.PREFS_.getpref('Laser Y', -20, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_sensor_x.setText(str(self.w.PREFS_.getpref('Sensor X', 10, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_sensor_y.setText(str(self.w.PREFS_.getpref('Sensor Y', 10, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_work_height.setText(str(self.w.PREFS_.getpref('Work Height', 20, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_touch_height.setText(str(self.w.PREFS_.getpref('Touch Height', 40, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_sensor_height.setText(str(self.w.PREFS_.getpref('Sensor Height', 40, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_search_vel.setText(str(self.w.PREFS_.getpref('Search Velocity', 40, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_probe_vel.setText(str(self.w.PREFS_.getpref('Probe Velocity', 10, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_max_probe.setText(str(self.w.PREFS_.getpref('Max Probe', 10, float, 'CUSTOM_FORM_ENTRIES')))
        self.w.lineEdit_eoffset_count.setText(str(self.w.PREFS_.getpref('Eoffset count', 0, int, 'CUSTOM_FORM_ENTRIES')))
        self.w.chk_eoffsets.setChecked(self.w.PREFS_.getpref('External offsets', False, bool, 'CUSTOM_FORM_ENTRIES'))
        self.w.chk_reload_program.setChecked(self.w.PREFS_.getpref('Reload program', False, bool,'CUSTOM_FORM_ENTRIES'))
        self.w.chk_reload_tool.setChecked(self.w.PREFS_.getpref('Reload tool', False, bool,'CUSTOM_FORM_ENTRIES'))
        self.w.chk_use_keyboard.setChecked(self.w.PREFS_.getpref('Use keyboard', False, bool, 'CUSTOM_FORM_ENTRIES'))
        self.w.chk_run_from_line.setChecked(self.w.PREFS_.getpref('Run from line', False, bool, 'CUSTOM_FORM_ENTRIES'))
        self.w.chk_use_virtual.setChecked(self.w.PREFS_.getpref('Use virtual keyboard', False, bool, 'CUSTOM_FORM_ENTRIES'))
        self.w.chk_alpha_mode.setChecked(self.w.PREFS_.getpref('Use alpha display mode', False, bool, 'CUSTOM_FORM_ENTRIES'))
        self.w.chk_inhibit_selection.setChecked(self.w.PREFS_.getpref('Inhibit display mouse selection', True, bool, 'CUSTOM_FORM_ENTRIES'))

    def closing_cleanup__(self):
        if not self.w.PREFS_: return
        self.w.PREFS_.putpref('last_loaded_directory', os.path.dirname(self.last_loaded_program), str, 'BOOK_KEEPING')
        self.w.PREFS_.putpref('last_loaded_file', self.last_loaded_program, str, 'BOOK_KEEPING')
        self.w.PREFS_.putpref('Tool to load', STATUS.get_current_tool(), int, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Laser X', self.w.lineEdit_laser_x.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Laser Y', self.w.lineEdit_laser_y.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Sensor X', self.w.lineEdit_sensor_x.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Sensor Y', self.w.lineEdit_sensor_y.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Work Height', self.w.lineEdit_work_height.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Touch Height', self.w.lineEdit_touch_height.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Sensor Height', self.w.lineEdit_sensor_height.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Search Velocity', self.w.lineEdit_search_vel.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Probe Velocity', self.w.lineEdit_probe_vel.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Max Probe', self.w.lineEdit_max_probe.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Eoffset count', self.w.lineEdit_eoffset_count.text().encode('utf-8'), int, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('External offsets', self.w.chk_eoffsets.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Reload program', self.w.chk_reload_program.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Reload tool', self.w.chk_reload_tool.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Use keyboard', self.w.chk_use_keyboard.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Run from line', self.w.chk_run_from_line.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Use virtual keyboard', self.w.chk_use_virtual.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Use alpha display mode', self.w.chk_alpha_mode.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
        self.w.PREFS_.putpref('Inhibit display mouse selection', self.w.chk_inhibit_selection.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')

    def init_widgets(self):
        self.w.main_tab_widget.setCurrentIndex(0)
        self.w.slider_jog_linear.setMaximum(self.max_linear_velocity)
        self.w.slider_jog_linear.setValue(INFO.DEFAULT_LINEAR_JOG_VEL)
        self.w.slider_jog_angular.setMaximum(INFO.MAX_ANGULAR_JOG_VEL)
        self.w.slider_jog_angular.setValue(INFO.DEFAULT_ANGULAR_JOG_VEL)
        self.w.slider_maxv_ovr.setMaximum(self.max_linear_velocity)
        self.w.slider_maxv_ovr.setValue(self.max_linear_velocity)
        self.w.slider_feed_ovr.setMaximum(INFO.MAX_FEED_OVERRIDE)
        self.w.slider_feed_ovr.setValue(100)
        self.w.slider_rapid_ovr.setMaximum(100)
        self.w.slider_rapid_ovr.setValue(100)
        self.w.slider_spindle_ovr.setMinimum(INFO.MIN_SPINDLE_OVERRIDE)
        self.w.slider_spindle_ovr.setMaximum(INFO.MAX_SPINDLE_OVERRIDE)
        self.w.slider_spindle_ovr.setValue(100)
        self.w.chk_override_limits.setChecked(False)
        self.w.chk_override_limits.setEnabled(False)
        self.w.lbl_maxv_percent.setText("100 %")
        self.w.lbl_max_rapid.setText(str(self.max_linear_velocity))
        self.w.lbl_home_x.setText(INFO.get_error_safe_setting('JOINT_0', 'HOME',"50"))
        self.w.lbl_home_y.setText(INFO.get_error_safe_setting('JOINT_1', 'HOME',"50"))
        self.w.cmb_gcode_history.addItem("No File Loaded")
        self.w.cmb_gcode_history.wheelEvent = lambda event: None
        self.w.jogincrements_linear.wheelEvent = lambda event: None
        self.w.jogincrements_angular.wheelEvent = lambda event: None
        self.w.gcode_editor.hide()

        #set up gcode list
        self.gcodes.setup_list()
        # clickable frames
        self.w.frame_cycle_start.mousePressEvent = self.btn_start_clicked
        self.w.frame_home_all.mousePressEvent = self.btn_home_all_clicked
        # web view widget for SETUP page
        self.web_view = QWebView()
        self.w.verticalLayout_setup.addWidget(self.web_view)
        self.web_view.setHtml(self.html)
        # check for virtual keyboard enabled
        if self.w.chk_use_virtual.isChecked():
            self.w.btn_keyboard.show()
        else:
            self.w.btn_keyboard.hide()
        TOOLBAR.configure_statusbar(self.w.statusbar,'message_recall')

    def processed_focus_event__(self, receiver, event):
        if not self.w.chk_use_virtual.isChecked() or STATUS.is_auto_mode(): return
        if isinstance(receiver, QtWidgets.QLineEdit):
            if not receiver.isReadOnly():
                self.w.stackedWidget_dro.setCurrentIndex(1)
        elif isinstance(receiver, QtWidgets.QTableView):
            self.w.stackedWidget_dro.setCurrentIndex(1)
        elif isinstance(receiver, QtWidgets.QCommonStyle):
            return
    
    def processed_key_event__(self,receiver,event,is_pressed,key,code,shift,cntrl):
        # when typing in MDI, we don't want keybinding to call functions
        # so we catch and process the events directly.
        # We do want ESC, F1 and F2 to call keybinding functions though
        if code not in(QtCore.Qt.Key_Escape,QtCore.Qt.Key_F1 ,QtCore.Qt.Key_F2,
                    QtCore.Qt.Key_F12):

            # search for the top widget of whatever widget received the event
            # then check if it's one we want the keypress events to go to
            flag = False
            receiver2 = receiver
            while receiver2 is not None and not flag:
                if isinstance(receiver2, QtWidgets.QDialog):
                    flag = True
                    break
                if isinstance(receiver2, QtWidgets.QLineEdit):
                    flag = True
                    break
                if isinstance(receiver2, MDI_WIDGET):
                    flag = True
                    break
                if isinstance(receiver2, GCODE):
                    flag = True
                    break
                if isinstance(receiver2, TOOL_TABLE):
                    flag = True
                    break
                if isinstance(receiver2, OFFSET_VIEW):
                    flag = True
                    break
                receiver2 = receiver2.parent()

            if flag:
                if isinstance(receiver2, GCODE):
                    # if in manual do our keybindings - otherwise
                    # send events to gcode widget
                    if STATUS.is_man_mode() == False:
                        if is_pressed:
                            receiver.keyPressEvent(event)
                            event.accept()
                        return True
                elif is_pressed:
                    receiver.keyPressEvent(event)
                    event.accept()
                    return True
                else:
                    event.accept()
                    return True

        if event.isAutoRepeat():return True
        # ok if we got here then try keybindings
        try:
            KEYBIND.call(self,event,is_pressed,shift,cntrl)
            event.accept()
            return True
        except NameError as e:
            if is_pressed:
                LOG.debug('Exception in KEYBINDING: {}'.format (e))
                self.add_status('Exception in KEYBINDING: {}'.format (e))
        except Exception as e:
            if is_pressed:
                LOG.debug('Exception in KEYBINDING:', exc_info=e)
                print 'Error in, or no function for: %s in handler file for-%s'%(KEYBIND.convert(event),key)
        event.accept()
        return True

    #########################
    # CALLBACKS FROM STATUS #
    #########################

    def spindle_pwr_changed(self, data):
        # this calculation assumes the voltage is line to neutral
        # and that the synchronous motor spindle has a power factor of 0.9
        power = self.h['spindle_volts'] * self.h['spindle_amps'] * 2.7 # 3 x V x I x PF
        amps = "{:1.1f}".format(self.h['spindle_amps'])
        pwr = "{:1.1f}".format(power)
        self.w.lbl_spindle_amps.setText(amps)
        self.w.lbl_spindle_power.setText(pwr)

    def spindle_fault_changed(self, data):
        fault = hex(self.h['spindle_fault'])
        self.w.lbl_spindle_fault.setText(fault)

    def mb_errors_changed(self, data):
        errors = self.h['modbus-errors']
        self.w.lbl_mb_errors.setText(str(errors))

    def eoffset_changed(self, data):
        eoffset = "{:2.3f}".format(self.h['eoffset_value'])
        self.w.lbl_eoffset_value.setText(eoffset)

    def dialog_return(self, w, message):
        rtn = message.get('RETURN')
        name = message.get('NAME')
        plate_code = bool(message.get('ID') == '_touchplate_')
        sensor_code = bool(message.get('ID') == '_toolsensor_')
        wait_code = bool(message.get('ID') == '_wait_resume_')
        if plate_code and name == 'MESSAGE' and rtn is True:
            self.touchoff('touchplate')
        elif sensor_code and name == 'MESSAGE' and rtn is True:
            self.touchoff('sensor')
        elif wait_code and name == 'MESSAGE':
            self.h['eoffset_clear'] = False

    def user_system_changed(self, obj, data):
        sys = self.system_list[int(data) - 1]
        self.w.offset_table.selectRow(int(data) + 3)
        self.w.actionbutton_rel.setText(sys)

    def file_loaded(self, obj, filename):
        if filename is not None:
            self.add_status("Loaded file {}".format(filename))
            self.w.progressBar.setValue(0)
            self.last_loaded_program = filename
            self.w.lbl_runtime.setText("00:00:00")
        else:
            self.add_status("Filename not valid")

    def percent_loaded_changed(self, fraction):
        if fraction <0:
            self.w.progressBar.setValue(0)
            self.w.progressBar.setFormat('Progress')
        else:
            self.w.progressBar.setValue(fraction)
            self.w.progressBar.setFormat('Loading: {}%'.format(fraction))

    def percent_completed_changed(self, fraction):
        self.w.progressBar.setValue(fraction)
        if fraction <0:
            self.w.progressBar.setValue(0)
            self.w.progressBar.setFormat('Progress')
        else:
            self.w.progressBar.setFormat('Completed: {}%'.format(fraction))

    def homed(self, obj, joint):
        i = int(joint)
        axis = INFO.GET_NAME_FROM_JOINT.get(i).lower()
        try:
            self.w["dro_axis_{}".format(axis)].setProperty('homed', True)
            self.w["dro_axis_{}".format(axis)].setStyle(self.w["dro_axis_{}".format(axis)].style())
        except:
            pass

    def all_homed(self, obj):
        self.home_all = True
        self.w.lbl_home_all.setText("ALL\nHOMED")
        if self.first_turnon is True:
            self.first_turnon = False
            if self.w.chk_reload_tool.isChecked():
                command = "M61 Q{}".format(self.reload_tool)
                ACTION.CALL_MDI(command)
            if self.last_loaded_program is not None and self.w.chk_reload_program.isChecked():
                if os.path.isfile(self.last_loaded_program):
                    self.w.cmb_gcode_history.addItem(self.last_loaded_program)
                    self.w.cmb_gcode_history.setCurrentIndex(self.w.cmb_gcode_history.count() - 1)
                    ACTION.OPEN_PROGRAM(self.last_loaded_program)
                    self.w.manual_mode_button.setChecked(True)

    def not_all_homed(self, obj, list):
        self.home_all = False
        self.w.lbl_home_all.setText("HOME\nALL")
        for i in INFO.AVAILABLE_JOINTS:
            if str(i) in list:
                axis = INFO.GET_NAME_FROM_JOINT.get(i).lower()
                try:
                    self.w["dro_axis_{}".format(axis)].setProperty('homed', False)
                    self.w["dro_axis_{}".format(axis)].setStyle(self.w["dro_axis_{}".format(axis)].style())
                except:
                    pass

    def hard_limit_tripped(self, obj, tripped, list_of_tripped):
        self.add_status("Hard limits tripped")
        self.w.chk_override_limits.setEnabled(tripped)
        if not tripped:
            self.w.chk_override_limits.setChecked(False)

    def update_runtimer(self):
        if self.timerOn is False or STATUS.is_auto_paused(): return
        self.time_tenths += 1
        if self.time_tenths == 10:
            self.time_tenths = 0
            self.run_time += 1
            hours, remainder = divmod(self.run_time, 3600)
            minutes, seconds = divmod(remainder, 60)
            self.w.lbl_runtime.setText("{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds))

    def start_timer(self):
        if STATUS.is_auto_running():
            self.run_time = 0
            self.timerOn = True

    def stop_timer(self):
        self.timerOn = False

    #######################
    # CALLBACKS FROM FORM #
    #######################

    # main button bar
    def main_tab_changed(self, btn):
        if STATUS.is_auto_mode():
            self.add_status("Cannot switch pages while in AUTO mode")
            self.w.btn_main.setChecked(True)
            return
        index = btn.property("index")
        if index is None: return
        self.w.main_tab_widget.setCurrentIndex(index)
        if index == TAB_MAIN:
            self.w.stackedWidget.setCurrentIndex(0)
        elif index == TAB_FILE:
            self.w.stackedWidget.setCurrentIndex(1)
        elif index == TAB_OFFSETS:
            self.w.stackedWidget.setCurrentIndex(2)
        elif index == TAB_TOOL:
            self.w.stackedWidget.setCurrentIndex(3)
        else:
            self.w.stackedWidget.setCurrentIndex(0)

    # gcode frame
    def cmb_gcode_history_clicked(self):
        if self.w.cmb_gcode_history.currentIndex() == 0: return
        filename = self.w.cmb_gcode_history.currentText().encode('utf-8')
        if filename == self.last_loaded_program:
            self.add_status("Selected program is already loaded")
        else:
            ACTION.OPEN_PROGRAM(filename)

    # program frame
    def btn_start_clicked(self, obj):
        if self.w.main_tab_widget.currentIndex() != 0:
            return
        if not STATUS.is_auto_mode():
            self.add_status("Must be in AUTO mode to run a program")
            return
        start_line = int(self.w.lbl_start_line.text().encode('utf-8'))
        self.add_status("Started program from line {}".format(start_line))
        self.run_time = 0
        if start_line == 1:
            ACTION.RUN(start_line)
        else:
            # instantiate preset dialog
            info = '<b>Running from Line: {} <\b>'.format(start_line)
            mess = {'NAME':'RUNFROMLINE', 'TITLE':'Preset Dialog', 'ID':'_RUNFROMLINE', 'MESSAGE':info, 'LINE':start_line}
            ACTION.CALL_DIALOG(mess)

    def btn_reload_file_clicked(self):
        if self.last_loaded_program:
            self.w.progressBar.setValue(0)
            self.add_status("Loaded program file {}".format(self.last_loaded_program))
            ACTION.OPEN_PROGRAM(self.last_loaded_program)

    # DRO frame
    def btn_home_all_clicked(self, obj):
        if self.home_all is False:
            ACTION.SET_MACHINE_HOMING(-1)
        else:
            ACTION.SET_MACHINE_UNHOMED(-1)

    def btn_home_clicked(self):
        joint = self.w.sender().property('joint')
        axis = INFO.GET_NAME_FROM_JOINT.get(joint).lower()
        if self.w["dro_axis_{}".format(axis)].property('homed') is True:
            ACTION.SET_MACHINE_UNHOMED(joint)
        else:
            ACTION.SET_MACHINE_HOMING(joint)

    # tool frame
    def disable_pause_buttons(self, state):
        self.w.action_pause.setEnabled(not state)
        self.w.action_step.setEnabled(not state)
        if state:
        # set external offsets to lift spindle
            self.h['eoffset_enable'] = self.w.chk_eoffsets.isChecked()
            fval = float(self.w.lineEdit_eoffset_count.text())
            self.h['eoffset_count'] = int(fval)
        else:
            self.h['eoffset_count'] = 0
            self.h['eoffset_clear'] = True
        # instantiate warning box
            info = "Wait for spindle at speed signal before resuming"
            mess = {'NAME':'MESSAGE', 'ICON':'WARNING', 'ID':'_wait_resume_', 'MESSAGE':'CAUTION', 'MORE':info, 'TYPE':'OK'}
            ACTION.CALL_DIALOG(mess)

    # override frame
    def slow_button_clicked(self, state):
        slider = self.w.sender().property('slider')
        current = self.w[slider].value()
        max = self.w[slider].maximum()
        if state:
            self.w.sender().setText("SLOW")
            self.w[slider].setMaximum(max / self.slow_jog_factor)
            self.w[slider].setValue(current / self.slow_jog_factor)
            self.w[slider].setPageStep(10)
        else:
            self.w.sender().setText("FAST")
            self.w[slider].setMaximum(max * self.slow_jog_factor)
            self.w[slider].setValue(current * self.slow_jog_factor)
            self.w[slider].setPageStep(100)

    def slider_maxv_changed(self, value):
        maxpc = (float(value) / self.max_linear_velocity) * 100
        self.w.lbl_maxv_percent.setText("{:3.0f} %".format(maxpc))

    def slider_rapid_changed(self, value):
        rapid = (float(value) / 100) * self.max_linear_velocity
        self.w.lbl_max_rapid.setText("{:4.0f}".format(rapid))

    def btn_maxv_100_clicked(self):
        self.w.slider_maxv_ovr.setValue(self.max_linear_velocity)

    def btn_maxv_50_clicked(self):
        self.w.slider_maxv_ovr.setValue(self.max_linear_velocity / 2)

    # file tab
    def btn_gcode_edit_clicked(self, state):
        if not STATUS.is_on_and_idle():
            return
        if state:
            self.w.filemanager.hide()
            self.w.widget_file_copy.hide()
            self.w.gcode_editor.show()
            self.w.gcode_editor.editMode()
        else:
            self.w.filemanager.show()
            self.w.widget_file_copy.show()
            self.w.gcode_editor.hide()
            self.w.gcode_editor.readOnlyMode()

    def btn_load_file_clicked(self):
        fname = self.w.filemanager.getCurrentSelected()
        if fname[1] is True:
            self.load_code(fname[0])

    def btn_copy_file_clicked(self):
        if self.w.sender() == self.w.btn_copy_right:
            source = self.w.filemanager_usb.getCurrentSelected()
            target = self.w.filemanager.getCurrentSelected()
        elif self.w.sender() == self.w.btn_copy_left:
            source = self.w.filemanager.getCurrentSelected()
            target = self.w.filemanager_usb.getCurrentSelected()
        else:
            return
        if source[1] is False:
            self.add_status("Specified source is not a file")
            return
        if target[1] is True:
            destination = os.path.join(os.path.dirname(target[0]), os.path.basename(source[0]))
        else:
            destination = os.path.join(target[0], os.path.basename(source[0]))
        try:
            copyfile(source[0], destination)
            self.add_status("Copied file from {} to {}".format(source[0], destination))
        except Exception as e:
            self.add_status("Unable to copy file. %s" %e)

    # offsets tab
    def btn_goto_sensor_clicked(self):
        x = float(self.w.lineEdit_sensor_x.text())
        y = float(self.w.lineEdit_sensor_y.text())
        if not STATUS.is_metric_mode():
            x = x / 25.4
            y = y / 25.4
        ACTION.CALL_MDI("G90")
        ACTION.CALL_MDI_WAIT("G53 G0 Z0")
        command = "G53 G0 X{:3.4f} Y{:3.4f}".format(x, y)
        ACTION.CALL_MDI_WAIT(command, 10)
 
    def btn_ref_laser_clicked(self):
        x = float(self.w.lineEdit_laser_x.text())
        y = float(self.w.lineEdit_laser_y.text())
        if not STATUS.is_metric_mode():
            x = x / 25.4
            y = y / 25.4
        self.add_status("Laser offsets set")
        command = "G10 L20 P0 X{:3.4f} Y{:3.4f}".format(x, y)
        ACTION.CALL_MDI(command)
    
    # tool tab
    def btn_m61_clicked(self):
        checked = self.w.tooloffsetview.get_checked_list()
        if len(checked) > 1:
            self.add_status("Select only 1 tool to load")
        elif checked:
            self.add_status("Loaded tool {}".format(checked[0]))
            ACTION.CALL_MDI("M61 Q{}".format(checked[0]))
        else:
            self.add_status("No tool selected")

    def btn_touchoff_clicked(self):
        if STATUS.get_current_tool() == 0:
            self.add_status("Cannot touchoff with no tool loaded")
            return
        if not STATUS.is_all_homed():
            self.add_status("Must be homed to perform tool touchoff")
            return
        # instantiate dialog box
        sensor = self.w.sender().property('sensor')
        info = "Ensure tooltip is within {} mm of tool sensor and click OK".format(self.w.lineEdit_max_probe.text())
        mess = {'NAME':'MESSAGE', 'ID':sensor, 'MESSAGE':'TOOL TOUCHOFF', 'MORE':info, 'TYPE':'OKCANCEL'}
        ACTION.CALL_DIALOG(mess)
        
    # status tab
    def btn_clear_status_clicked(self):
        STATUS.emit('update-machine-log', None, 'DELETE')

    def btn_save_status_clicked(self):
        text = self.w.machinelog.toPlainText()
        filename = self.w.lbl_clock.text().encode('utf-8')
        filename = 'status_' + filename.replace(' ','_') + '.txt'
        self.add_status("Saving Status file to {}".format(filename))
        with open(filename, 'w') as f:
            f.write(text)

    def btn_dimensions_clicked(self, state):
        self.w.gcodegraphics.show_extents_option = state
        self.w.gcodegraphics.clear_live_plotter()
        
    def chk_override_limits_checked(self, state):
        if state:
            print("Override limits set")
            ACTION.SET_LIMITS_OVERRIDE()
        else:
            print("Override limits not set")

    def chk_run_from_line_checked(self, state):
        if not state:
            self.w.lbl_start_line.setText('1')

    def chk_alpha_mode_clicked(self, state):
        self.w.gcodegraphics.set_alpha_mode(state)

    def chk_inhibit_display_selection_clicked(self, state):
        self.w.gcodegraphics.set_inhibit_selection(state)

    # settings tab
    def chk_use_virtual_changed(self, state):
        if not state:
            self.w.stackedWidget_dro.setCurrentIndex(0)

    #####################
    # GENERAL FUNCTIONS #
    #####################
    def load_code(self, fname):
        if fname is None: return
        if fname.endswith(".ngc") or fname.endswith(".py"):
            self.w.cmb_gcode_history.addItem(fname)
            self.w.cmb_gcode_history.setCurrentIndex(self.w.cmb_gcode_history.count() - 1)
            ACTION.OPEN_PROGRAM(fname)
            self.add_status("Loaded program file : {}".format(fname))
            self.w.main_tab_widget.setCurrentIndex(TAB_MAIN)
        elif fname.endswith(".html"):
            self.web_view.load(QtCore.QUrl.fromLocalFile(fname))
            self.add_status("Loaded HTML file : {}".format(fname))
            self.w.main_tab_widget.setCurrentIndex(TAB_SETUP)
            self.w.btn_setup.setChecked(True)
        else:
            self.add_status("Unknown or invalid filename")

    def disable_spindle_pause(self):
        self.h['eoffset_count'] = 0
        if self.w.spindle_pause.isChecked():
            self.w.spindle_pause.setChecked(False)

    def touchoff(self, selector):
        if selector == 'touchplate':
            z_offset = float(self.w.lineEdit_touch_height.text())
        elif selector == 'sensor':
            z_offset = float(self.w.lineEdit_sensor_height.text()) - float(self.w.lineEdit_work_height.text())
        else:
            self.add_alarm("Unknown touchoff routine specified")
            return
        self.add_status("Touchoff to {} started".format(selector))
        max_probe = self.w.lineEdit_max_probe.text()
        search_vel = self.w.lineEdit_search_vel.text()
        probe_vel = self.w.lineEdit_probe_vel.text()
        ACTION.CALL_MDI("G21 G49")
        ACTION.CALL_MDI("G10 L20 P0 Z0")
        ACTION.CALL_MDI("G91")
        command = "G38.2 Z-{} F{}".format(max_probe, search_vel)
        if ACTION.CALL_MDI_WAIT(command, 10) == -1:
            ACTION.CALL_MDI("G90")
            return
        if ACTION.CALL_MDI_WAIT("G1 Z4.0"):
            ACTION.CALL_MDI("G90")
            return
        ACTION.CALL_MDI("G4 P0.5")
        command = "G38.2 Z-4.4 F{}".format(probe_vel)
        if ACTION.CALL_MDI_WAIT(command, 10) == -1:
            ACTION.CALL_MDI("G90")
            return
        command = "G10 L20 P0 Z{}".format(z_offset)
        ACTION.CALL_MDI_WAIT(command)
        command = "G1 Z10 F{}".format(search_vel)
        ACTION.CALL_MDI_WAIT(command)
        ACTION.CALL_MDI("G90")

    def kb_jog(self, state, joint, direction, fast = False, linear = True):
        if not STATUS.is_man_mode() or not STATUS.machine_is_on():
            self.add_status('Machine must be ON and in Manual mode to jog')
            return
        if linear:
            distance = STATUS.get_jog_increment()
            rate = STATUS.get_jograte()/60
        else:
            distance = STATUS.get_jog_increment_angular()
            rate = STATUS.get_jograte_angular()/60
        if state:
            if fast:
                rate = rate * 2
            ACTION.JOG(joint, direction, rate, distance)
        else:
            ACTION.JOG(joint, 0, 0, 0)

    def add_status(self, message):
        self._m = message
        print message
        self.w.statusbar.showMessage(self._m, 5000)
        STATUS.emit('update-machine-log', self._m, 'TIME')

    def enable_auto(self, state):
        for widget in self.auto_list:
            self.w[widget].setEnabled(state)
        if state is True:
            self.w.jogging_frame.show()
        else:
            self.w.jogging_frame.hide()
            self.w.main_tab_widget.setCurrentIndex(TAB_MAIN)
            self.w.stackedWidget.setCurrentIndex(0)
        self.w.stackedWidget_dro.setCurrentIndex(0)

    def enable_onoff(self, state):
        if state:
            self.add_status("Machine ON")
        else:
            self.add_status("Machine OFF")
        self.w.spindle_pause.setChecked(False)
        self.h['eoffset_count'] = 0
        for widget in self.onoff_list:
            self.w[widget].setEnabled(state)

    def set_start_line(self, line):
        if line == 0:
            self.w.lbl_start_line.setText('1')
        elif self.w.chk_run_from_line.isChecked():
            self.w.lbl_start_line.setText(str(line))
        else:
            self.w.lbl_start_line.setText('1')

    def use_keyboard(self):
        if self.w.chk_use_keyboard.isChecked():
            return True
        else:
            self.add_status('Keyboard shortcuts are disabled')
            return False

    def add_alarm(self, message):
        STATUS.emit('update-machine-log', message, 'TIME')

    #####################
    # KEY BINDING CALLS #
    #####################

    def on_keycall_ESTOP(self,event,state,shift,cntrl):
        if state:
            ACTION.SET_ESTOP_STATE(True)

    def on_keycall_POWER(self,event,state,shift,cntrl):
        if state:
            ACTION.SET_MACHINE_STATE(False)

    def on_keycall_ABORT(self,event,state,shift,cntrl):
        if state:
            ACTION.ABORT()

    def on_keycall_HOME(self,event,state,shift,cntrl):
        if state and not STATUS.is_all_homed() and self.use_keyboard():
            ACTION.SET_MACHINE_HOMING(-1)

    def on_keycall_pause(self,event,state,shift,cntrl):
        if state and STATUS.is_auto_mode() and self.use_keyboard():
            ACTION.PAUSE()

    def on_keycall_XPOS(self,event,state,shift,cntrl):
        if self.use_keyboard():
            self.kb_jog(state, 0, 1, shift)

    def on_keycall_XNEG(self,event,state,shift,cntrl):
        if self.use_keyboard():
            self.kb_jog(state, 0, -1, shift)

    def on_keycall_YPOS(self,event,state,shift,cntrl):
        if self.use_keyboard():
            self.kb_jog(state, 1, 1, shift)

    def on_keycall_YNEG(self,event,state,shift,cntrl):
        if self.use_keyboard():
            self.kb_jog(state, 1, -1, shift)

    def on_keycall_ZPOS(self,event,state,shift,cntrl):
        if self.use_keyboard():
            self.kb_jog(state, 2, 1, shift)

    def on_keycall_ZNEG(self,event,state,shift,cntrl):
        if self.use_keyboard():
            self.kb_jog(state, 2, -1, shift)
    
    def on_keycall_APOS(self,event,state,shift,cntrl):
        if self.use_keyboard():
            self.kb_jog(state, 3, 1, shift, False)

    def on_keycall_ANEG(self,event,state,shift,cntrl):
        if self.use_keyboard():
            self.kb_jog(state, 3, -1, shift, False)

    def on_keycall_F12(self,event,state,shift,cntrl):
        if state:
            STYLEEDITOR.load_dialog()

    ##############################
    # required class boiler code #
    ##############################
    def __getitem__(self, item):
        return getattr(self, item)

    def __setitem__(self, item, value):
        return setattr(self, item, value)