Exemplo n.º 1
0
class ProgressIndicator(QWidget):  # {{{

    def __init__(self, *args):
        QWidget.__init__(self, *args)
        self.setGeometry(0, 0, 300, 350)
        self.pi = _ProgressIndicator(self)
        self.status = QLabel(self)
        self.status.setWordWrap(True)
        self.status.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
        self.setVisible(False)
        self.pos = None

    def start(self, msg=''):
        view = self.parent()
        pwidth, pheight = view.size().width(), view.size().height()
        self.resize(pwidth, min(pheight, 250))
        if self.pos is None:
            self.move(0, (pheight-self.size().height())/2.)
        else:
            self.move(self.pos[0], self.pos[1])
        self.pi.resize(self.pi.sizeHint())
        self.pi.move(int((self.size().width()-self.pi.size().width())/2.), 0)
        self.status.resize(self.size().width(), self.size().height()-self.pi.size().height()-10)
        self.status.move(0, self.pi.size().height()+10)
        self.status.setText('<h1>'+msg+'</h1>')
        self.setVisible(True)
        self.pi.startAnimation()

    def stop(self):
        self.pi.stopAnimation()
        self.setVisible(False)
Exemplo n.º 2
0
class EbookViewer(MainWindow, Ui_EbookViewer):

    STATE_VERSION = 1
    FLOW_MODE_TT = _('Switch to paged mode - where the text is broken up '
            'into pages like a paper book')
    PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up '
            'into pages')

    def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None,
                 start_in_fullscreen=False):
        MainWindow.__init__(self, None)
        self.setupUi(self)
        self.view.initialize_view(debug_javascript)
        self.view.magnification_changed.connect(self.magnification_changed)
        self.show_toc_on_open = False
        self.current_book_has_toc = False
        self.base_window_title = unicode(self.windowTitle())
        self.iterator          = None
        self.current_page      = None
        self.pending_search    = None
        self.pending_search_dir= None
        self.pending_anchor    = None
        self.pending_reference = None
        self.pending_bookmark  = None
        self.pending_restore   = False
        self.existing_bookmarks= []
        self.selected_text     = None
        self.was_maximized     = False
        self.read_settings()
        self.dictionary_box.hide()
        self.close_dictionary_view.clicked.connect(lambda
                x:self.dictionary_box.hide())
        self.history = History(self.action_back, self.action_forward)
        self.metadata = Metadata(self)
        self.pos = DoubleSpinBox()
        self.pos.setDecimals(1)
        self.pos.setSuffix('/'+_('Unknown')+'     ')
        self.pos.setMinimum(1.)
        self.pos.value_changed.connect(self.update_pos_label)
        self.splitter.setCollapsible(0, False)
        self.splitter.setCollapsible(1, False)
        self.pos.setMinimumWidth(150)
        self.tool_bar2.insertWidget(self.action_find_next, self.pos)
        self.reference = Reference()
        self.tool_bar2.insertSeparator(self.action_find_next)
        self.tool_bar2.insertWidget(self.action_find_next, self.reference)
        self.tool_bar2.insertSeparator(self.action_find_next)
        self.setFocusPolicy(Qt.StrongFocus)
        self.search = SearchBox2(self)
        self.search.setMinimumContentsLength(20)
        self.search.initialize('viewer_search_history')
        self.search.setToolTip(_('Search for text in book'))
        self.search.setMinimumWidth(200)
        self.tool_bar2.insertWidget(self.action_find_next, self.search)
        self.view.set_manager(self)
        self.pi = ProgressIndicator(self)
        self.toc.setVisible(False)
        self.action_quit = QAction(_('&Quit'), self)
        self.addAction(self.action_quit)
        self.view_resized_timer = QTimer(self)
        self.view_resized_timer.timeout.connect(self.viewport_resize_finished)
        self.view_resized_timer.setSingleShot(True)
        self.resize_in_progress = False
        self.action_quit.triggered.connect(self.quit)
        self.action_copy.setDisabled(True)
        self.action_metadata.setCheckable(True)
        self.action_table_of_contents.setCheckable(True)
        self.toc.setMinimumWidth(80)
        self.action_reference_mode.setCheckable(True)
        self.action_reference_mode.triggered[bool].connect(self.view.reference_mode)
        self.action_metadata.triggered[bool].connect(self.metadata.setVisible)
        self.action_table_of_contents.toggled[bool].connect(self.set_toc_visible)
        self.action_copy.triggered[bool].connect(self.copy)
        self.action_font_size_larger.triggered.connect(self.font_size_larger)
        self.action_font_size_smaller.triggered.connect(self.font_size_smaller)
        self.action_open_ebook.triggered[bool].connect(self.open_ebook)
        self.action_next_page.triggered.connect(self.view.next_page)
        self.action_previous_page.triggered.connect(self.view.previous_page)
        self.action_find_next.triggered.connect(self.find_next)
        self.action_find_previous.triggered.connect(self.find_previous)
        self.action_full_screen.triggered[bool].connect(self.toggle_fullscreen)
        self.action_full_screen.setToolTip(_('Toggle full screen [%s]') %
                _(' or ').join([x for x in self.view.shortcuts.get_shortcuts('Fullscreen')]))
        self.action_back.triggered[bool].connect(self.back)
        self.action_forward.triggered[bool].connect(self.forward)
        self.action_preferences.triggered.connect(self.do_config)
        self.pos.editingFinished.connect(self.goto_page_num)
        self.vertical_scrollbar.valueChanged[int].connect(lambda
                x:self.goto_page(x/100.))
        self.search.search.connect(self.find)
        self.search.focus_to_library.connect(lambda: self.view.setFocus(Qt.OtherFocusReason))
        self.toc.pressed[QModelIndex].connect(self.toc_clicked)
        self.reference.goto.connect(self.goto)

        self.bookmarks_menu = QMenu()
        self.action_bookmark.setMenu(self.bookmarks_menu)
        self.set_bookmarks([])

        self.themes_menu = QMenu()
        self.action_load_theme.setMenu(self.themes_menu)
        self.tool_bar.widgetForAction(self.action_load_theme).setPopupMode(QToolButton.InstantPopup)
        self.load_theme_menu()

        if pathtoebook is not None:
            f = functools.partial(self.load_ebook, pathtoebook, open_at=open_at)
            QTimer.singleShot(50, f)
        self.view.setMinimumSize(100, 100)
        self.toc.setCursor(Qt.PointingHandCursor)
        self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
        self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu)
        self.tool_bar.widgetForAction(self.action_bookmark).setPopupMode(QToolButton.InstantPopup)
        self.action_full_screen.setCheckable(True)
        self.full_screen_label = QLabel('''
                <center>
                <h1>%s</h1>
                <h3>%s</h3>
                <h3>%s</h3>
                <h3>%s</h3>
                </center>
                '''%(_('Full screen mode'),
                    _('Right click to show controls'),
                    _('Tap in the left or right page margin to turn pages'),
                    _('Press Esc to quit')),
                    self)
        self.full_screen_label.setVisible(False)
        self.full_screen_label.setStyleSheet('''
        QLabel {
            text-align: center;
            background-color: white;
            color: black;
            border-width: 1px;
            border-style: solid;
            border-radius: 20px;
        }
        ''')
        self.window_mode_changed = None
        self.toggle_toolbar_action = QAction(_('Show/hide controls'), self)
        self.toggle_toolbar_action.setCheckable(True)
        self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars)
        self.toolbar_hidden = None
        self.addAction(self.toggle_toolbar_action)
        self.full_screen_label_anim = QPropertyAnimation(
                self.full_screen_label, 'size')
        self.clock_label = QLabel('99:99', self)
        self.clock_label.setVisible(False)
        self.clock_label.setFocusPolicy(Qt.NoFocus)
        self.info_label_style = '''
            QLabel {
                text-align: center;
                border-width: 1px;
                border-style: solid;
                border-radius: 8px;
                background-color: %s;
                color: %s;
                font-family: monospace;
                font-size: larger;
                padding: 5px;
        }'''
        self.original_frame_style = self.frame.frameStyle()
        self.pos_label = QLabel('2000/4000', self)
        self.pos_label.setVisible(False)
        self.pos_label.setFocusPolicy(Qt.NoFocus)
        self.clock_timer = QTimer(self)
        self.clock_timer.timeout.connect(self.update_clock)

        self.print_menu = QMenu()
        self.print_menu.addAction(QIcon(I('print-preview.png')), _('Print Preview'))
        self.action_print.setMenu(self.print_menu)
        self.tool_bar.widgetForAction(self.action_print).setPopupMode(QToolButton.MenuButtonPopup)
        self.action_print.triggered.connect(self.print_book)
        self.print_menu.actions()[0].triggered.connect(self.print_preview)
        self.open_history_menu = QMenu()
        self.clear_recent_history_action = QAction(
                _('Clear list of recently opened books'), self)
        self.clear_recent_history_action.triggered.connect(self.clear_recent_history)
        self.build_recent_menu()
        self.action_open_ebook.setMenu(self.open_history_menu)
        self.open_history_menu.triggered[QAction].connect(self.open_recent)
        w = self.tool_bar.widgetForAction(self.action_open_ebook)
        w.setPopupMode(QToolButton.MenuButtonPopup)

        for x in ('tool_bar', 'tool_bar2'):
            x = getattr(self, x)
            for action in x.actions():
                # So that the keyboard shortcuts for these actions will
                # continue to function even when the toolbars are hidden
                self.addAction(action)

        for plugin in self.view.document.all_viewer_plugins:
            plugin.customize_ui(self)
        self.view.document.settings_changed.connect(self.settings_changed)

        self.restore_state()
        self.settings_changed()
        self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode)
        if (start_in_fullscreen or self.view.document.start_in_fullscreen):
            self.action_full_screen.trigger()

    def toggle_paged_mode(self, checked, at_start=False):
        in_paged_mode = not self.action_toggle_paged_mode.isChecked()
        self.view.document.in_paged_mode = in_paged_mode
        self.action_toggle_paged_mode.setToolTip(self.FLOW_MODE_TT if
                self.action_toggle_paged_mode.isChecked() else
                self.PAGED_MODE_TT)
        if at_start:
            return
        self.reload()

    def settings_changed(self):
        for x in ('', '2'):
            x = getattr(self, 'tool_bar'+x)
            x.setVisible(self.view.document.show_controls)

    def reload(self):
        if hasattr(self, 'current_index') and self.current_index > -1:
            self.view.document.page_position.save(overwrite=False)
            self.pending_restore = True
            self.load_path(self.view.last_loaded_path)

    def set_toc_visible(self, yes):
        self.toc.setVisible(yes)

    def clear_recent_history(self, *args):
        vprefs.set('viewer_open_history', [])
        self.build_recent_menu()

    def build_recent_menu(self):
        m = self.open_history_menu
        m.clear()
        recent = vprefs.get('viewer_open_history', [])
        if recent:
            m.addAction(self.clear_recent_history_action)
            m.addSeparator()
        count = 0
        for path in recent:
            if count > 9:
                break
            if os.path.exists(path):
                m.addAction(RecentAction(path, m))
                count += 1

    def shutdown(self):
        if self.isFullScreen() and not self.view.document.start_in_fullscreen:
            self.action_full_screen.trigger()
            return False
        self.save_state()
        return True

    def quit(self):
        if self.shutdown():
            QApplication.instance().quit()

    def closeEvent(self, e):
        if self.shutdown():
            return MainWindow.closeEvent(self, e)
        else:
            e.ignore()

    def toggle_toolbars(self):
        for x in ('tool_bar', 'tool_bar2'):
            x = getattr(self, x)
            x.setVisible(not x.isVisible())

    def save_state(self):
        state = bytearray(self.saveState(self.STATE_VERSION))
        vprefs['viewer_toolbar_state'] = state
        if not self.isFullScreen():
            vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
        if self.current_book_has_toc:
            vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
        if self.toc.isVisible():
            vprefs.set('viewer_splitter_state',
                bytearray(self.splitter.saveState()))
        vprefs['multiplier'] = self.view.multiplier
        vprefs['in_paged_mode'] = not self.action_toggle_paged_mode.isChecked()

    def restore_state(self):
        state = vprefs.get('viewer_toolbar_state', None)
        if state is not None:
            try:
                state = QByteArray(state)
                self.restoreState(state, self.STATE_VERSION)
            except:
                pass
        mult = vprefs.get('multiplier', None)
        if mult:
            self.view.multiplier = mult
        # On windows Qt lets the user hide toolbars via a right click in a very
        # specific location, ensure they are visible.
        self.tool_bar.setVisible(True)
        self.tool_bar2.setVisible(True)
        self.action_toggle_paged_mode.setChecked(not vprefs.get('in_paged_mode',
            True))
        self.toggle_paged_mode(self.action_toggle_paged_mode.isChecked(),
                at_start=True)

    def lookup(self, word):
        from calibre.gui2.viewer.documentview import config
        opts = config().parse()
        settings = self.dictionary_view.page().settings()
        settings.setFontSize(settings.DefaultFontSize, opts.default_font_size)
        settings.setFontSize(settings.DefaultFixedFontSize, opts.mono_font_size)
        self.dictionary_view.setHtml('<html><body><p>'+
            _('Connecting to dict.org to lookup: <b>%s</b>&hellip;')%word +
            '</p></body></html>')
        self.dictionary_box.show()
        self._lookup = Lookup(word, parent=self)
        self._lookup.finished.connect(self.looked_up)
        self._lookup.start()

    def looked_up(self, *args):
        html = self._lookup.html_result
        self._lookup = None
        self.dictionary_view.setHtml(html)

    def get_remember_current_page_opt(self):
        from calibre.gui2.viewer.documentview import config
        c = config().parse()
        return c.remember_current_page

    def print_book(self):
        p = Printing(self.iterator, self)
        p.start_print()

    def print_preview(self):
        p = Printing(self.iterator, self)
        p.start_preview()

    def toggle_fullscreen(self):
        if self.isFullScreen():
            self.showNormal()
        else:
            self.showFullScreen()

    def showFullScreen(self):
        self.view.document.page_position.save()
        self.window_mode_changed = 'fullscreen'
        self.tool_bar.setVisible(False)
        self.tool_bar2.setVisible(False)
        self.was_maximized = self.isMaximized()
        if not self.view.document.fullscreen_scrollbar:
            self.vertical_scrollbar.setVisible(False)
            self.frame.layout().setSpacing(0)
        self._original_frame_margins = (
            self.centralwidget.layout().contentsMargins(),
            self.frame.layout().contentsMargins())
        self.frame.layout().setContentsMargins(0, 0, 0, 0)
        self.centralwidget.layout().setContentsMargins(0, 0, 0, 0)
        self.frame.setFrameStyle(self.frame.NoFrame|self.frame.Plain)

        super(EbookViewer, self).showFullScreen()

    def show_full_screen_label(self):
        f = self.full_screen_label
        height = 200
        width = int(0.7*self.view.width())
        f.resize(width, height)
        f.move((self.view.width() - width)//2, (self.view.height()-height)//2)
        if self.view.document.show_fullscreen_help:
            f.setVisible(True)
            a = self.full_screen_label_anim
            a.setDuration(500)
            a.setStartValue(QSize(width, 0))
            a.setEndValue(QSize(width, height))
            a.start()
            QTimer.singleShot(3500, self.full_screen_label.hide)
        self.view.document.switch_to_fullscreen_mode()
        if self.view.document.fullscreen_clock:
            self.show_clock()
        if self.view.document.fullscreen_pos:
            self.show_pos_label()

    def show_clock(self):
        self.clock_label.setVisible(True)
        self.clock_label.setText(QTime(22, 33,
            33).toString(Qt.SystemLocaleShortDate))
        self.clock_timer.start(1000)
        self.clock_label.setStyleSheet(self.info_label_style%(
                'rgba(0, 0, 0, 0)', self.view.document.colors()[1]))
        self.clock_label.resize(self.clock_label.sizeHint())
        sw = QApplication.desktop().screenGeometry(self.view)
        vswidth = (self.vertical_scrollbar.width() if
                self.vertical_scrollbar.isVisible() else 0)
        self.clock_label.move(sw.width() - vswidth - 15
                - self.clock_label.width(), sw.height() -
                self.clock_label.height()-10)
        self.update_clock()

    def show_pos_label(self):
        self.pos_label.setVisible(True)
        self.pos_label.setStyleSheet(self.info_label_style%(
                'rgba(0, 0, 0, 0)', self.view.document.colors()[1]))
        sw = QApplication.desktop().screenGeometry(self.view)
        self.pos_label.move(15, sw.height() - self.pos_label.height()-10)
        self.update_pos_label()

    def update_clock(self):
        self.clock_label.setText(QTime.currentTime().toString(Qt.SystemLocaleShortDate))

    def update_pos_label(self, *args):
        if self.pos_label.isVisible():
            try:
                value, maximum = args
            except:
                value, maximum = self.pos.value(), self.pos.maximum()
            text = '%g/%g'%(value, maximum)
            self.pos_label.setText(text)
            self.pos_label.resize(self.pos_label.sizeHint())

    def showNormal(self):
        self.view.document.page_position.save()
        self.clock_label.setVisible(False)
        self.pos_label.setVisible(False)
        self.frame.setFrameStyle(self.original_frame_style)
        self.frame.layout().setSpacing(-1)
        self.clock_timer.stop()
        self.vertical_scrollbar.setVisible(True)
        self.window_mode_changed = 'normal'
        self.settings_changed()
        self.full_screen_label.setVisible(False)
        if hasattr(self, '_original_frame_margins'):
            om = self._original_frame_margins
            self.centralwidget.layout().setContentsMargins(om[0])
            self.frame.layout().setContentsMargins(om[1])
        if self.was_maximized:
            super(EbookViewer, self).showMaximized()
        else:
            super(EbookViewer, self).showNormal()

    def handle_window_mode_toggle(self):
        if self.window_mode_changed:
            fs = self.window_mode_changed == 'fullscreen'
            self.window_mode_changed = None
            if fs:
                self.show_full_screen_label()
            else:
                self.view.document.switch_to_window_mode()
            self.view.document.page_position.restore()
            self.scrolled(self.view.scroll_fraction)

    def goto(self, ref):
        if ref:
            tokens = ref.split('.')
            if len(tokens) > 1:
                spine_index = int(tokens[0]) -1
                if spine_index == self.current_index:
                    self.view.goto(ref)
                else:
                    self.pending_reference = ref
                    self.load_path(self.iterator.spine[spine_index])

    def goto_bookmark(self, bm):
        spine_index = bm['spine']
        if spine_index > -1 and self.current_index == spine_index:
            if self.resize_in_progress:
                self.view.document.page_position.set_pos(bm['pos'])
            else:
                self.view.goto_bookmark(bm)
        else:
            self.pending_bookmark = bm
            if spine_index < 0 or spine_index >= len(self.iterator.spine):
                spine_index = 0
                self.pending_bookmark = None
            self.load_path(self.iterator.spine[spine_index])

    def toc_clicked(self, index, force=False):
        if force or QApplication.mouseButtons() & Qt.LeftButton:
            item = self.toc_model.itemFromIndex(index)
            if item.abspath is not None:
                if not os.path.exists(item.abspath):
                    return error_dialog(self, _('No such location'),
                            _('The location pointed to by this item'
                                ' does not exist.'), det_msg=item.abspath, show=True)
                url = QUrl.fromLocalFile(item.abspath)
                if item.fragment:
                    url.setFragment(item.fragment)
                self.link_clicked(url)
        self.view.setFocus(Qt.OtherFocusReason)

    def selection_changed(self, selected_text):
        self.selected_text = selected_text.strip()
        self.action_copy.setEnabled(bool(self.selected_text))

    def copy(self, x):
        if self.selected_text:
            QApplication.clipboard().setText(self.selected_text)

    def back(self, x):
        pos = self.history.back(self.pos.value())
        if pos is not None:
            self.goto_page(pos)

    def goto_page_num(self):
        num = self.pos.value()
        self.goto_page(num)

    def forward(self, x):
        pos = self.history.forward(self.pos.value())
        if pos is not None:
            self.goto_page(pos)

    def goto_start(self):
        self.goto_page(1)

    def goto_end(self):
        self.goto_page(self.pos.maximum())

    def goto_page(self, new_page, loaded_check=True):
        if self.current_page is not None or not loaded_check:
            for page in self.iterator.spine:
                if new_page >= page.start_page and new_page <= page.max_page:
                    try:
                        frac = float(new_page-page.start_page)/(page.pages-1)
                    except ZeroDivisionError:
                        frac = 0
                    if page == self.current_page:
                        self.view.scroll_to(frac)
                    else:
                        self.load_path(page, pos=frac)

    def open_ebook(self, checked):
        files = choose_files(self, 'ebook viewer open dialog',
                     _('Choose ebook'),
                     [(_('Ebooks'), available_input_formats())],
                     all_files=False,
                     select_only_single_file=True)
        if files:
            self.load_ebook(files[0])

    def open_recent(self, action):
        self.load_ebook(action.path)

    def font_size_larger(self):
        self.view.magnify_fonts()

    def font_size_smaller(self):
        self.view.shrink_fonts()

    def magnification_changed(self, val):
        tt = '%(action)s [%(sc)s]\n'+_('Current magnification: %(mag).1f')
        sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Font larger'))
        self.action_font_size_larger.setToolTip(
                tt %dict(action=unicode(self.action_font_size_larger.text()),
                         mag=val, sc=sc))
        sc = _(' or ').join(self.view.shortcuts.get_shortcuts('Font smaller'))
        self.action_font_size_smaller.setToolTip(
                tt %dict(action=unicode(self.action_font_size_smaller.text()),
                         mag=val, sc=sc))
        self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
        self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)

    def find(self, text, repeat=False, backwards=False):
        if not text:
            self.view.search('')
            return self.search.search_done(False)
        if self.view.search(text, backwards=backwards):
            self.scrolled(self.view.scroll_fraction)
            return self.search.search_done(True)
        index = self.iterator.search(text, self.current_index,
                backwards=backwards)
        if index is None:
            if self.current_index > 0:
                index = self.iterator.search(text, 0)
                if index is None:
                    info_dialog(self, _('No matches found'),
                                _('No matches found for: %s')%text).exec_()
                    return self.search.search_done(True)
            return self.search.search_done(True)
        self.pending_search = text
        self.pending_search_dir = 'backwards' if backwards else 'forwards'
        self.load_path(self.iterator.spine[index])

    def find_next(self):
        self.find(unicode(self.search.text()), repeat=True)

    def find_previous(self):
        self.find(unicode(self.search.text()), repeat=True, backwards=True)

    def do_search(self, text, backwards):
        self.pending_search = None
        self.pending_search_dir = None
        if self.view.search(text, backwards=backwards):
            self.scrolled(self.view.scroll_fraction)

    def internal_link_clicked(self, frac):
        self.update_page_number()  # Ensure page number is accurate as it is used for history
        self.history.add(self.pos.value())

    def link_clicked(self, url):
        path = os.path.abspath(unicode(url.toLocalFile()))
        frag = None
        if path in self.iterator.spine:
            self.update_page_number()  # Ensure page number is accurate as it is used for history
            self.history.add(self.pos.value())
            path = self.iterator.spine[self.iterator.spine.index(path)]
            if url.hasFragment():
                frag = unicode(url.fragment())
            if path != self.current_page:
                self.pending_anchor = frag
                self.load_path(path)
            else:
                oldpos = self.view.document.ypos
                if frag:
                    self.view.scroll_to(frag)
                else:
                    # Scroll to top
                    self.view.scroll_to(0)
                if self.view.document.ypos == oldpos:
                    # If we are coming from goto_next_section() call this will
                    # cause another goto next section call with the next toc
                    # entry, since this one did not cause any scrolling at all.
                    QTimer.singleShot(10, self.update_indexing_state)
        else:
            open_url(url)

    def load_started(self):
        self.open_progress_indicator(_('Loading flow...'))

    def load_finished(self, ok):
        self.close_progress_indicator()
        path = self.view.path()
        try:
            index = self.iterator.spine.index(path)
        except (ValueError, AttributeError):
            return -1
        self.current_page = self.iterator.spine[index]
        self.current_index = index
        self.set_page_number(self.view.scroll_fraction)
        QTimer.singleShot(100, self.update_indexing_state)
        if self.pending_search is not None:
            self.do_search(self.pending_search,
                    self.pending_search_dir=='backwards')
            self.pending_search = None
            self.pending_search_dir = None
        if self.pending_anchor is not None:
            self.view.scroll_to(self.pending_anchor)
            self.pending_anchor = None
        if self.pending_reference is not None:
            self.view.goto(self.pending_reference)
            self.pending_reference = None
        if self.pending_bookmark is not None:
            self.goto_bookmark(self.pending_bookmark)
            self.pending_bookmark = None
        if self.pending_restore:
            self.view.document.page_position.restore()
        return self.current_index

    def goto_next_section(self):
        if hasattr(self, 'current_index'):
            entry = self.toc_model.next_entry(self.current_index,
                    self.view.document.read_anchor_positions(),
                    self.view.viewport_rect, self.view.document.in_paged_mode)
            if entry is not None:
                self.pending_goto_next_section = (
                        self.toc_model.currently_viewed_entry, entry, False)
                self.toc_clicked(entry.index(), force=True)

    def goto_previous_section(self):
        if hasattr(self, 'current_index'):
            entry = self.toc_model.next_entry(self.current_index,
                    self.view.document.read_anchor_positions(),
                    self.view.viewport_rect, self.view.document.in_paged_mode,
                    backwards=True)
            if entry is not None:
                self.pending_goto_next_section = (
                        self.toc_model.currently_viewed_entry, entry, True)
                self.toc_clicked(entry.index(), force=True)

    def update_indexing_state(self, anchor_positions=None):
        pgns = getattr(self, 'pending_goto_next_section', None)
        if hasattr(self, 'current_index'):
            if anchor_positions is None:
                anchor_positions = self.view.document.read_anchor_positions()
            items = self.toc_model.update_indexing_state(self.current_index,
                        self.view.viewport_rect, anchor_positions,
                        self.view.document.in_paged_mode)
            if items:
                self.toc.scrollTo(items[-1].index())
            if pgns is not None:
                self.pending_goto_next_section = None
                # Check that we actually progressed
                if pgns[0] is self.toc_model.currently_viewed_entry:
                    entry = self.toc_model.next_entry(self.current_index,
                            self.view.document.read_anchor_positions(),
                            self.view.viewport_rect,
                            self.view.document.in_paged_mode,
                            backwards=pgns[2], current_entry=pgns[1])
                    if entry is not None:
                        self.pending_goto_next_section = (
                                self.toc_model.currently_viewed_entry, entry,
                                pgns[2])
                        self.toc_clicked(entry.index(), force=True)

    def load_path(self, path, pos=0.0):
        self.open_progress_indicator(_('Laying out %s')%self.current_title)
        self.view.load_path(path, pos=pos)

    def viewport_resize_started(self, event):
        old, curr = event.size(), event.oldSize()
        if not self.window_mode_changed and old.width() == curr.width():
            # No relayout changes, so page position does not need to be saved
            # This is needed as Qt generates a viewport resized event that
            # changes only the height after a file has been loaded. This can
            # cause the last read position bookmark to become slightly
            # inaccurate
            return
        if not self.resize_in_progress:
            # First resize, so save the current page position
            self.resize_in_progress = True
            if not self.window_mode_changed:
                # The special handling for window mode changed will already
                # have saved page position, so only save it if this is not a
                # mode change
                self.view.document.page_position.save()

        if self.resize_in_progress:
            self.view_resized_timer.start(75)

    def viewport_resize_finished(self):
        # There hasn't been a resize event for some time
        # restore the current page position.
        self.resize_in_progress = False
        if self.window_mode_changed:
            # This resize is part of a window mode change, special case it
            self.handle_window_mode_toggle()
        else:
            self.view.document.page_position.restore()
        self.view.document.after_resize()
        # For some reason scroll_fraction returns incorrect results in paged
        # mode for some time after a resize is finished. No way of knowing
        # exactly how long, so we update it in a second, in the hopes that it
        # will be enough *most* of the time.
        QTimer.singleShot(1000, self.update_page_number)

    def update_page_number(self):
        self.set_page_number(self.view.document.scroll_fraction)

    def close_progress_indicator(self):
        self.pi.stop()
        for o in ('tool_bar', 'tool_bar2', 'view', 'horizontal_scrollbar', 'vertical_scrollbar'):
            getattr(self, o).setEnabled(True)
        self.unsetCursor()
        self.view.setFocus(Qt.PopupFocusReason)

    def open_progress_indicator(self, msg=''):
        self.pi.start(msg)
        for o in ('tool_bar', 'tool_bar2', 'view', 'horizontal_scrollbar', 'vertical_scrollbar'):
            getattr(self, o).setEnabled(False)
        self.setCursor(Qt.BusyCursor)

    def load_theme_menu(self):
        from calibre.gui2.viewer.config import load_themes
        self.themes_menu.clear()
        for key in load_themes():
            title = key[len('theme_'):]
            self.themes_menu.addAction(title, partial(self.load_theme,
                key))

    def load_theme(self, theme_id):
        self.view.load_theme(theme_id)

    def do_config(self):
        self.view.config(self)
        self.load_theme_menu()
        from calibre.gui2 import config
        if not config['viewer_search_history']:
            self.search.clear_history()

    def bookmark(self, *args):
        num = 1
        bm = None
        while True:
            bm = _('Bookmark #%d')%num
            if bm not in self.existing_bookmarks:
                break
            num += 1
        title, ok = QInputDialog.getText(self, _('Add bookmark'),
                _('Enter title for bookmark:'), text=bm)
        title = unicode(title).strip()
        if ok and title:
            bm = self.view.bookmark()
            bm['spine'] = self.current_index
            bm['title'] = title
            self.iterator.add_bookmark(bm)
            self.set_bookmarks(self.iterator.bookmarks)

    def set_bookmarks(self, bookmarks):
        self.bookmarks_menu.clear()
        self.bookmarks_menu.addAction(_("Bookmark this location"), self.bookmark)
        self.bookmarks_menu.addAction(_("Manage Bookmarks"), self.manage_bookmarks)
        self.bookmarks_menu.addSeparator()
        current_page = None
        self.existing_bookmarks = []
        for bm in bookmarks:
            if bm['title'] == 'calibre_current_page_bookmark':
                if self.get_remember_current_page_opt():
                    current_page = bm
            else:
                self.existing_bookmarks.append(bm['title'])
                self.bookmarks_menu.addAction(bm['title'], partial(self.goto_bookmark, bm))
        return current_page

    def manage_bookmarks(self):
        bmm = BookmarkManager(self, self.iterator.bookmarks)
        if bmm.exec_() != BookmarkManager.Accepted:
            return

        bookmarks = bmm.get_bookmarks()

        if bookmarks != self.iterator.bookmarks:
            self.iterator.set_bookmarks(bookmarks)
            self.iterator.save_bookmarks()
            self.set_bookmarks(bookmarks)

    def save_current_position(self):
        if not self.get_remember_current_page_opt():
            return
        if hasattr(self, 'current_index'):
            try:
                bm = self.view.bookmark()
                bm['spine'] = self.current_index
                bm['title'] = 'calibre_current_page_bookmark'
                self.iterator.add_bookmark(bm)
            except:
                traceback.print_exc()

    def load_ebook(self, pathtoebook, open_at=None):
        if self.iterator is not None:
            self.save_current_position()
            self.iterator.__exit__()
        self.iterator = EbookIterator(pathtoebook)
        self.open_progress_indicator(_('Loading ebook...'))
        worker = Worker(target=partial(self.iterator.__enter__,
            extract_embedded_fonts_for_qt=True))
        worker.start()
        while worker.isAlive():
            worker.join(0.1)
            QApplication.processEvents()
        if worker.exception is not None:
            if isinstance(worker.exception, DRMError):
                from calibre.gui2.dialogs.drm_error import DRMErrorMessage
                DRMErrorMessage(self).exec_()
            else:
                r = getattr(worker.exception, 'reason', worker.exception)
                error_dialog(self, _('Could not open ebook'),
                        as_unicode(r) or _('Unknown error'),
                        det_msg=worker.traceback, show=True)
            self.close_progress_indicator()
        else:
            self.metadata.show_opf(self.iterator.opf,
                    self.iterator.book_format)
            self.view.current_language = self.iterator.language
            title = self.iterator.opf.title
            if not title:
                title = os.path.splitext(os.path.basename(pathtoebook))[0]
            if self.iterator.toc:
                self.toc_model = TOC(self.iterator.spine, self.iterator.toc)
                self.toc.setModel(self.toc_model)
                if self.show_toc_on_open:
                    self.action_table_of_contents.setChecked(True)
            else:
                self.toc_model = TOC(self.iterator.spine)
                self.toc.setModel(self.toc_model)
                self.action_table_of_contents.setChecked(False)
            if isbytestring(pathtoebook):
                pathtoebook = force_unicode(pathtoebook, filesystem_encoding)
            vh = vprefs.get('viewer_open_history', [])
            try:
                vh.remove(pathtoebook)
            except:
                pass
            vh.insert(0, pathtoebook)
            vprefs.set('viewer_open_history', vh[:50])
            self.build_recent_menu()

            self.action_table_of_contents.setDisabled(not self.iterator.toc)
            self.current_book_has_toc = bool(self.iterator.toc)
            self.current_title = title
            self.setWindowTitle(self.base_window_title+' - '+title +
                    ' [%s]'%self.iterator.book_format)
            self.pos.setMaximum(sum(self.iterator.pages))
            self.pos.setSuffix(' / %d'%sum(self.iterator.pages))
            self.vertical_scrollbar.setMinimum(100)
            self.vertical_scrollbar.setMaximum(100*sum(self.iterator.pages))
            self.vertical_scrollbar.setSingleStep(10)
            self.vertical_scrollbar.setPageStep(100)
            self.set_vscrollbar_value(1)
            self.current_index = -1
            QApplication.instance().alert(self, 5000)
            previous = self.set_bookmarks(self.iterator.bookmarks)
            if open_at is None and previous is not None:
                self.goto_bookmark(previous)
            else:
                if open_at is None:
                    self.next_document()
                else:
                    if open_at > self.pos.maximum():
                        open_at = self.pos.maximum()
                    if open_at < self.pos.minimum():
                        open_at = self.pos.minimum()
                    self.goto_page(open_at, loaded_check=False)

    def set_vscrollbar_value(self, pagenum):
        self.vertical_scrollbar.blockSignals(True)
        self.vertical_scrollbar.setValue(int(pagenum*100))
        self.vertical_scrollbar.blockSignals(False)

    def set_page_number(self, frac):
        if getattr(self, 'current_page', None) is not None:
            page = self.current_page.start_page + frac*float(self.current_page.pages-1)
            self.pos.set_value(page)
            self.set_vscrollbar_value(page)

    def scrolled(self, frac, onload=False):
        self.set_page_number(frac)
        if not onload:
            ap = self.view.document.read_anchor_positions()
            self.update_indexing_state(ap)

    def next_document(self):
        if (hasattr(self, 'current_index') and self.current_index <
                len(self.iterator.spine) - 1):
            self.load_path(self.iterator.spine[self.current_index+1])

    def previous_document(self):
        if hasattr(self, 'current_index') and self.current_index > 0:
            self.load_path(self.iterator.spine[self.current_index-1], pos=1.0)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            if self.metadata.isVisible():
                self.metadata.setVisible(False)
                event.accept()
                return
            if self.isFullScreen():
                self.toggle_fullscreen()
                event.accept()
                return
        try:
            key = self.view.shortcuts.get_match(event)
        except AttributeError:
            return MainWindow.keyPressEvent(self, event)
        action = {
            'Quit':self.action_quit,
            'Show metadata':self.action_metadata,
            'Copy':self.view.copy_action,
            'Font larger': self.action_font_size_larger,
            'Font smaller': self.action_font_size_smaller,
            'Fullscreen': self.action_full_screen,
            'Find next': self.action_find_next,
            'Find previous': self.action_find_previous,
            'Search online': self.view.search_online_action,
            'Lookup word': self.view.dictionary_action,
            'Next occurrence': self.view.search_action,
        }.get(key, None)
        if action is not None:
            event.accept()
            action.trigger()
            return
        if key == 'Focus Search':
            self.search.setFocus(Qt.OtherFocusReason)
        if not self.view.handle_key_press(event):
            event.ignore()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        if self.iterator is not None:
            self.save_current_position()
            self.iterator.__exit__(*args)

    def read_settings(self):
        c = config().parse()
        self.splitter.setSizes([1, 300])
        if c.remember_window_size:
            wg = vprefs.get('viewer_window_geometry', None)
            if wg is not None:
                self.restoreGeometry(wg)
            ss = vprefs.get('viewer_splitter_state', None)
            if ss is not None:
                self.splitter.restoreState(ss)
            self.show_toc_on_open = vprefs.get('viewer_toc_isvisible', False)
        av = available_height() - 30
        if self.height() > av:
            self.resize(self.width(), av)
        self.splitter.setCollapsible(0, False)
        self.splitter.setCollapsible(1, False)
Exemplo n.º 3
0
class TweakBook(QDialog):

    def __init__(self, parent, book_id, fmts, db):
        QDialog.__init__(self, parent)
        self.book_id, self.fmts, self.db_ref = book_id, fmts, weakref.ref(db)
        self._exploded = None
        self._cleanup_dirs = []
        self._cleanup_files = []

        self.setup_ui()
        self.setWindowTitle(_('Tweak Book') + ' - ' + db.title(book_id,
            index_is_id=True))

        button = self.fmt_choice_buttons[0]
        button_map = {unicode(x.text()):x for x in self.fmt_choice_buttons}
        of = prefs['output_format'].upper()
        df = tweaks.get('default_tweak_format', None)
        lf = gprefs.get('last_tweak_format', None)
        if df and df.lower() == 'remember' and lf in button_map:
            button = button_map[lf]
        elif df and df.upper() in button_map:
            button = button_map[df.upper()]
        elif of in button_map:
            button = button_map[of]
        button.setChecked(True)

        self.init_state()
        for button in self.fmt_choice_buttons:
            button.toggled.connect(self.init_state)

    def init_state(self, *args):
        self._exploded = None
        self.preview_button.setEnabled(False)
        self.rebuild_button.setEnabled(False)
        self.explode_button.setEnabled(True)

    def setup_ui(self): # {{{
        self._g = g = QHBoxLayout(self)
        self.setLayout(g)
        self._l = l = QVBoxLayout()
        g.addLayout(l)

        fmts = sorted(x.upper() for x in self.fmts)
        self.fmt_choice_box = QGroupBox(_('Choose the format to tweak:'), self)
        self._fl = fl = QHBoxLayout()
        self.fmt_choice_box.setLayout(self._fl)
        self.fmt_choice_buttons = [QRadioButton(y, self) for y in fmts]
        for x in self.fmt_choice_buttons:
            fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else
                    0)
        l.addWidget(self.fmt_choice_box)
        self.fmt_choice_box.setVisible(len(fmts) > 1)

        self.help_label = QLabel(_('''\
            <h2>About Tweak Book</h2>
            <p>Tweak Book allows you to fine tune the appearance of an ebook by
            making small changes to its internals. In order to use Tweak Book,
            you need to know a little bit about HTML and CSS, technologies that
            are used in ebooks. Follow the steps:</p>
            <br>
            <ol>
            <li>Click "Explode Book": This will "explode" the book into its
            individual internal components.<br></li>
            <li>Right click on any individual file and select "Open with..." to
            edit it in your favorite text editor.<br></li>
            <li>When you are done Tweaking: <b>close the file browser window
            and the editor windows you used to make your tweaks</b>. Then click
            the "Rebuild Book" button, to update the book in your calibre
            library.</li>
            </ol>'''))
        self.help_label.setWordWrap(True)
        self._fr = QFrame()
        self._fr.setFrameShape(QFrame.VLine)
        g.addWidget(self._fr)
        g.addWidget(self.help_label)

        self._b = b = QGridLayout()
        left, top, right, bottom = b.getContentsMargins()
        top += top
        b.setContentsMargins(left, top, right, bottom)
        l.addLayout(b, stretch=10)

        self.explode_button = QPushButton(QIcon(I('wizard.png')), _('&Explode Book'))
        self.preview_button = QPushButton(QIcon(I('view.png')), _('&Preview Book'))
        self.cancel_button  = QPushButton(QIcon(I('window-close.png')), _('&Cancel'))
        self.rebuild_button = QPushButton(QIcon(I('exec.png')), _('&Rebuild Book'))

        self.explode_button.setToolTip(
                _('Explode the book to edit its components'))
        self.preview_button.setToolTip(
                _('Preview the result of your tweaks'))
        self.cancel_button.setToolTip(
                _('Abort without saving any changes'))
        self.rebuild_button.setToolTip(
            _('Save your changes and update the book in the calibre library'))

        a = b.addWidget
        a(self.explode_button, 0, 0, 1, 1)
        a(self.preview_button, 0, 1, 1, 1)
        a(self.cancel_button,  1, 0, 1, 1)
        a(self.rebuild_button, 1, 1, 1, 1)

        for x in ('explode', 'preview', 'cancel', 'rebuild'):
            getattr(self, x+'_button').clicked.connect(getattr(self, x))

        self.msg = QLabel('dummy', self)
        self.msg.setVisible(False)
        self.msg.setStyleSheet('''
        QLabel {
            text-align: center;
            background-color: white;
            color: black;
            border-width: 1px;
            border-style: solid;
            border-radius: 20px;
            font-size: x-large;
            font-weight: bold;
        }
        ''')

        self.resize(self.sizeHint() + QSize(40, 10))
    # }}}

    def show_msg(self, msg):
        self.msg.setText(msg)
        self.msg.resize(self.size() - QSize(50, 25))
        self.msg.move((self.width() - self.msg.width())//2,
                (self.height() - self.msg.height())//2)
        self.msg.setVisible(True)

    def hide_msg(self):
        self.msg.setVisible(False)

    def explode(self):
        self.show_msg(_('Exploding, please wait...'))
        if len(self.fmt_choice_buttons) > 1:
            gprefs.set('last_tweak_format', self.current_format.upper())
        QTimer.singleShot(5, self.do_explode)

    def ask_question(self, msg):
        return question_dialog(self, _('Are you sure?'), msg)

    def do_explode(self):
        from calibre.ebooks.tweak import get_tools, Error, WorkerError
        tdir = PersistentTemporaryDirectory('_tweak_explode')
        self._cleanup_dirs.append(tdir)
        det_msg = None
        try:
            src = self.db.format(self.book_id, self.current_format,
                    index_is_id=True, as_path=True)
            self._cleanup_files.append(src)
            exploder = get_tools(self.current_format)[0]
            opf = exploder(src, tdir, question=self.ask_question)
        except WorkerError as e:
            det_msg = e.orig_tb
        except Error as e:
            return error_dialog(self, _('Failed to unpack'),
                (_('Could not explode the %s file.')%self.current_format) + ' '
                + as_unicode(e), show=True)
        except:
            import traceback
            det_msg = traceback.format_exc()
        finally:
            self.hide_msg()

        if det_msg is not None:
            return error_dialog(self, _('Failed to unpack'),
                _('Could not explode the %s file. Click "Show Details" for '
                    'more information.')%self.current_format, det_msg=det_msg,
                show=True)

        if opf is None:
            # The question was answered with No
            return

        self._exploded = tdir
        self.explode_button.setEnabled(False)
        self.preview_button.setEnabled(True)
        self.rebuild_button.setEnabled(True)
        open_local_file(tdir)

    def rebuild_it(self):
        from calibre.ebooks.tweak import get_tools, WorkerError
        src_dir = self._exploded
        det_msg = None
        of = PersistentTemporaryFile('_tweak_rebuild.'+self.current_format.lower())
        of.close()
        of = of.name
        self._cleanup_files.append(of)
        try:
            rebuilder = get_tools(self.current_format)[1]
            rebuilder(src_dir, of)
        except WorkerError as e:
            det_msg = e.orig_tb
        except:
            import traceback
            det_msg = traceback.format_exc()
        finally:
            self.hide_msg()

        if det_msg is not None:
            error_dialog(self, _('Failed to rebuild file'),
                    _('Failed to rebuild %s. For more information, click '
                        '"Show details".')%self.current_format,
                    det_msg=det_msg, show=True)
            return None

        return of

    def preview(self):
        self.show_msg(_('Rebuilding, please wait...'))
        QTimer.singleShot(5, self.do_preview)

    def do_preview(self):
        rebuilt = self.rebuild_it()
        if rebuilt is not None:
            self.parent().iactions['View']._view_file(rebuilt)

    def rebuild(self):
        self.show_msg(_('Rebuilding, please wait...'))
        QTimer.singleShot(5, self.do_rebuild)

    def do_rebuild(self):
        rebuilt = self.rebuild_it()
        if rebuilt is not None:
            fmt = os.path.splitext(rebuilt)[1][1:].upper()
            with open(rebuilt, 'rb') as f:
                self.db.add_format(self.book_id, fmt, f, index_is_id=True)
            self.accept()

    def cancel(self):
        self.reject()

    def cleanup(self):
        if isosx and self._exploded:
            try:
                import appscript
                self.finder = appscript.app('Finder')
                self.finder.Finder_windows[os.path.basename(self._exploded)].close()
            except:
                pass

        for f in self._cleanup_files:
            try:
                os.remove(f)
            except:
                pass

        for d in self._cleanup_dirs:
            try:
                shutil.rmtree(d)
            except:
                pass

    @property
    def db(self):
        return self.db_ref()

    @property
    def current_format(self):
        for b in self.fmt_choice_buttons:
            if b.isChecked():
                return unicode(b.text())
Exemplo n.º 4
0
    def __init__(self, parent, db, id_to_select, select_sort, select_link):
        QDialog.__init__(self, parent)
        Ui_EditAuthorsDialog.__init__(self)
        self.setupUi(self)
        # Remove help icon on title bar
        icon = self.windowIcon()
        self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint))
        self.setWindowIcon(icon)

        try:
            self.table_column_widths = \
                        gprefs.get('manage_authors_table_widths', None)
            geom = gprefs.get('manage_authors_dialog_geometry', bytearray(''))
            self.restoreGeometry(QByteArray(geom))
        except:
            pass

        self.buttonBox.accepted.connect(self.accepted)

        # Set up the column headings
        self.table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table.setColumnCount(3)
        self.down_arrow_icon = QIcon(I('arrow-down.png'))
        self.up_arrow_icon = QIcon(I('arrow-up.png'))
        self.blank_icon = QIcon(I('blank.png'))
        self.auth_col = QTableWidgetItem(_('Author'))
        self.table.setHorizontalHeaderItem(0, self.auth_col)
        self.auth_col.setIcon(self.blank_icon)
        self.aus_col = QTableWidgetItem(_('Author sort'))
        self.table.setHorizontalHeaderItem(1, self.aus_col)
        self.aus_col.setIcon(self.up_arrow_icon)
        self.aul_col = QTableWidgetItem(_('Link'))
        self.table.setHorizontalHeaderItem(2, self.aul_col)
        self.aus_col.setIcon(self.blank_icon)

        # Add the data
        self.authors = {}
        auts = db.get_authors_with_ids()
        self.table.setRowCount(len(auts))
        select_item = None
        for row, (id, author, sort, link) in enumerate(auts):
            author = author.replace('|', ',')
            self.authors[id] = (author, sort, link)
            aut = tableItem(author)
            aut.setData(Qt.UserRole, id)
            sort = tableItem(sort)
            link = tableItem(link)
            self.table.setItem(row, 0, aut)
            self.table.setItem(row, 1, sort)
            self.table.setItem(row, 2, link)
            if id == id_to_select:
                if select_sort:
                    select_item = sort
                elif select_link:
                    select_item = link
                else:
                    select_item = aut
        self.table.resizeColumnsToContents()
        if self.table.columnWidth(2) < 200:
            self.table.setColumnWidth(2, 200)

        # set up the cellChanged signal only after the table is filled
        self.table.cellChanged.connect(self.cell_changed)

        # set up sort buttons
        self.sort_by_author.setCheckable(True)
        self.sort_by_author.setChecked(False)
        self.sort_by_author.clicked.connect(self.do_sort_by_author)
        self.author_order = 1

        self.table.sortByColumn(1, Qt.AscendingOrder)
        self.sort_by_author_sort.clicked.connect(self.do_sort_by_author_sort)
        self.sort_by_author_sort.setCheckable(True)
        self.sort_by_author_sort.setChecked(True)
        self.author_sort_order = 1

        self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort)
        self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author)

        # Position on the desired item
        if select_item is not None:
            self.table.setCurrentItem(select_item)
            self.table.editItem(select_item)
            self.start_find_pos = select_item.row() * 2 + select_item.column()
        else:
            self.table.setCurrentCell(0, 0)
            self.start_find_pos = -1

        # set up the search box
        self.find_box.initialize('manage_authors_search')
        self.find_box.lineEdit().returnPressed.connect(self.do_find)
        self.find_box.editTextChanged.connect(self.find_text_changed)
        self.find_button.clicked.connect(self.do_find)

        l = QLabel(self.table)
        self.not_found_label = l
        l.setFrameStyle(QFrame.StyledPanel)
        l.setAutoFillBackground(True)
        l.setText(_('No matches found'))
        l.setAlignment(Qt.AlignVCenter)
        l.resize(l.sizeHint())
        l.move(10,20)
        l.setVisible(False)
        self.not_found_label.move(40, 40)
        self.not_found_label_timer = QTimer()
        self.not_found_label_timer.setSingleShot(True)
        self.not_found_label_timer.timeout.connect(
                self.not_found_label_timer_event, type=Qt.QueuedConnection)

        self.table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table.customContextMenuRequested .connect(self.show_context_menu)
Exemplo n.º 5
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.parent = parent
        self._layout = QVBoxLayout()
        self.setLayout(self._layout)
        self._layout.setContentsMargins(0,0,0,0)

        # Set up the find box & button
        search_layout = QHBoxLayout()
        self._layout.addLayout(search_layout)
        self.item_search = HistoryLineEdit(parent)
        self.item_search.setMinimumContentsLength(5)
        self.item_search.setSizeAdjustPolicy(self.item_search.AdjustToMinimumContentsLengthWithIcon)
        try:
            self.item_search.lineEdit().setPlaceholderText(
                                                _('Find item in tag browser'))
        except:
            pass             # Using Qt < 4.7
        self.item_search.setToolTip(_(
        'Search for items. This is a "contains" search; items containing the\n'
        'text anywhere in the name will be found. You can limit the search\n'
        'to particular categories using syntax similar to search. For example,\n'
        'tags:foo will find foo in any tag, but not in authors etc. Entering\n'
        '*foo will filter all categories at once, showing only those items\n'
        'containing the text "foo"'))
        search_layout.addWidget(self.item_search)
        # Not sure if the shortcut should be translatable ...
        sc = QShortcut(QKeySequence(_('ALT+f')), parent)
        sc.activated.connect(self.set_focus_to_find_box)

        self.search_button = QToolButton()
        self.search_button.setText(_('F&ind'))
        self.search_button.setToolTip(_('Find the first/next matching item'))
        search_layout.addWidget(self.search_button)

        self.expand_button = QToolButton()
        self.expand_button.setText('-')
        self.expand_button.setToolTip(_('Collapse all categories'))
        search_layout.addWidget(self.expand_button)
        search_layout.setStretch(0, 10)
        search_layout.setStretch(1, 1)
        search_layout.setStretch(2, 1)

        self.current_find_position = None
        self.search_button.clicked.connect(self.find)
        self.item_search.initialize('tag_browser_search')
        self.item_search.lineEdit().returnPressed.connect(self.do_find)
        self.item_search.lineEdit().textEdited.connect(self.find_text_changed)
        self.item_search.activated[QString].connect(self.do_find)
        self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive)

        parent.tags_view = TagsView(parent)
        self.tags_view = parent.tags_view
        self.expand_button.clicked.connect(self.tags_view.collapseAll)
        self._layout.addWidget(parent.tags_view)

        # Now the floating 'not found' box
        l = QLabel(self.tags_view)
        self.not_found_label = l
        l.setFrameStyle(QFrame.StyledPanel)
        l.setAutoFillBackground(True)
        l.setText('<p><b>'+_('No More Matches.</b><p> Click Find again to go to first match'))
        l.setAlignment(Qt.AlignVCenter)
        l.setWordWrap(True)
        l.resize(l.sizeHint())
        l.move(10,20)
        l.setVisible(False)
        self.not_found_label_timer = QTimer()
        self.not_found_label_timer.setSingleShot(True)
        self.not_found_label_timer.timeout.connect(self.not_found_label_timer_event,
                                                   type=Qt.QueuedConnection)

        parent.alter_tb = l = QPushButton(parent)
        l.setText(_('Alter Tag Browser'))
        l.setIcon(QIcon(I('tags.png')))
        l.m = QMenu()
        l.setMenu(l.m)
        self._layout.addWidget(l)

        sb = l.m.addAction(_('Sort by'))
        sb.m = l.sort_menu = QMenu(l.m)
        sb.setMenu(sb.m)
        sb.bg = QActionGroup(sb)

        # Must be in the same order as db2.CATEGORY_SORTS
        for i, x in enumerate((_('Sort by name'), _('Sort by number of books'),
                  _('Sort by average rating'))):
            a = sb.m.addAction(x)
            sb.bg.addAction(a)
            a.setCheckable(True)
            if i == 0:
                a.setChecked(True)
        sb.setToolTip(
                _('Set the sort order for entries in the Tag Browser'))
        sb.setStatusTip(sb.toolTip())

        ma = l.m.addAction(_('Search type when selecting multiple items'))
        ma.m = l.match_menu = QMenu(l.m)
        ma.setMenu(ma.m)
        ma.ag = QActionGroup(ma)

        # Must be in the same order as db2.MATCH_TYPE
        for i, x in enumerate((_('Match any of the items'), _('Match all of the items'))):
            a = ma.m.addAction(x)
            ma.ag.addAction(a)
            a.setCheckable(True)
            if i == 0:
                a.setChecked(True)
        ma.setToolTip(
                _('When selecting multiple entries in the Tag Browser '
                    'match any or all of them'))
        ma.setStatusTip(ma.toolTip())

        mt = l.m.addAction(_('Manage authors, tags, etc'))
        mt.setToolTip(_('All of these category_managers are available by right-clicking '
                       'on items in the tag browser above'))
        mt.m = l.manage_menu = QMenu(l.m)
        mt.setMenu(mt.m)
Exemplo n.º 6
0
class UnpackBook(QDialog):

    def __init__(self, parent, book_id, fmts, db):
        QDialog.__init__(self, parent)
        self.setWindowIcon(QIcon(I('unpack-book.png')))
        self.book_id, self.fmts, self.db_ref = book_id, fmts, weakref.ref(db)
        self._exploded = None
        self._cleanup_dirs = []
        self._cleanup_files = []

        self.setup_ui()
        self.setWindowTitle(_('Unpack Book') + ' - ' + db.title(book_id,
            index_is_id=True))

        button = self.fmt_choice_buttons[0]
        button_map = {unicode(x.text()):x for x in self.fmt_choice_buttons}
        of = prefs['output_format'].upper()
        df = tweaks.get('default_tweak_format', None)
        lf = gprefs.get('last_tweak_format', None)
        if df and df.lower() == 'remember' and lf in button_map:
            button = button_map[lf]
        elif df and df.upper() in button_map:
            button = button_map[df.upper()]
        elif of in button_map:
            button = button_map[of]
        button.setChecked(True)

        self.init_state()
        for button in self.fmt_choice_buttons:
            button.toggled.connect(self.init_state)

    def init_state(self, *args):
        self._exploded = None
        self.preview_button.setEnabled(False)
        self.rebuild_button.setEnabled(False)
        self.explode_button.setEnabled(True)

    def setup_ui(self):  # {{{
        self._g = g = QHBoxLayout(self)
        self.setLayout(g)
        self._l = l = QVBoxLayout()
        g.addLayout(l)

        fmts = sorted(x.upper() for x in self.fmts)
        self.fmt_choice_box = QGroupBox(_('Choose the format to unpack:'), self)
        self._fl = fl = QHBoxLayout()
        self.fmt_choice_box.setLayout(self._fl)
        self.fmt_choice_buttons = [QRadioButton(y, self) for y in fmts]
        for x in self.fmt_choice_buttons:
            fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else
                    0)
        l.addWidget(self.fmt_choice_box)
        self.fmt_choice_box.setVisible(len(fmts) > 1)

        self.help_label = QLabel(_('''\
            <h2>About Unpack Book</h2>
            <p>Unpack Book allows you to fine tune the appearance of an ebook by
            making small changes to its internals. In order to use Unpack Book,
            you need to know a little bit about HTML and CSS, technologies that
            are used in ebooks. Follow the steps:</p>
            <br>
            <ol>
            <li>Click "Explode Book": This will "explode" the book into its
            individual internal components.<br></li>
            <li>Right click on any individual file and select "Open with..." to
            edit it in your favorite text editor.<br></li>
            <li>When you are done: <b>close the file browser window
            and the editor windows you used to make your tweaks</b>. Then click
            the "Rebuild Book" button, to update the book in your calibre
            library.</li>
            </ol>'''))
        self.help_label.setWordWrap(True)
        self._fr = QFrame()
        self._fr.setFrameShape(QFrame.VLine)
        g.addWidget(self._fr)
        g.addWidget(self.help_label)

        self._b = b = QGridLayout()
        left, top, right, bottom = b.getContentsMargins()
        top += top
        b.setContentsMargins(left, top, right, bottom)
        l.addLayout(b, stretch=10)

        self.explode_button = QPushButton(QIcon(I('wizard.png')), _('&Explode Book'))
        self.preview_button = QPushButton(QIcon(I('view.png')), _('&Preview Book'))
        self.cancel_button  = QPushButton(QIcon(I('window-close.png')), _('&Cancel'))
        self.rebuild_button = QPushButton(QIcon(I('exec.png')), _('&Rebuild Book'))

        self.explode_button.setToolTip(
                _('Explode the book to edit its components'))
        self.preview_button.setToolTip(
                _('Preview the result of your changes'))
        self.cancel_button.setToolTip(
                _('Abort without saving any changes'))
        self.rebuild_button.setToolTip(
            _('Save your changes and update the book in the calibre library'))

        a = b.addWidget
        a(self.explode_button, 0, 0, 1, 1)
        a(self.preview_button, 0, 1, 1, 1)
        a(self.cancel_button,  1, 0, 1, 1)
        a(self.rebuild_button, 1, 1, 1, 1)

        for x in ('explode', 'preview', 'cancel', 'rebuild'):
            getattr(self, x+'_button').clicked.connect(getattr(self, x))

        self.msg = QLabel('dummy', self)
        self.msg.setVisible(False)
        self.msg.setStyleSheet('''
        QLabel {
            text-align: center;
            background-color: white;
            color: black;
            border-width: 1px;
            border-style: solid;
            border-radius: 20px;
            font-size: x-large;
            font-weight: bold;
        }
        ''')

        self.resize(self.sizeHint() + QSize(40, 10))
    # }}}

    def show_msg(self, msg):
        self.msg.setText(msg)
        self.msg.resize(self.size() - QSize(50, 25))
        self.msg.move((self.width() - self.msg.width())//2,
                (self.height() - self.msg.height())//2)
        self.msg.setVisible(True)

    def hide_msg(self):
        self.msg.setVisible(False)

    def explode(self):
        self.show_msg(_('Exploding, please wait...'))
        if len(self.fmt_choice_buttons) > 1:
            gprefs.set('last_tweak_format', self.current_format.upper())
        QTimer.singleShot(5, self.do_explode)

    def ask_question(self, msg):
        return question_dialog(self, _('Are you sure?'), msg)

    def do_explode(self):
        from calibre.ebooks.tweak import get_tools, Error, WorkerError
        tdir = PersistentTemporaryDirectory('_tweak_explode')
        self._cleanup_dirs.append(tdir)
        det_msg = None
        try:
            src = self.db.format(self.book_id, self.current_format,
                    index_is_id=True, as_path=True)
            self._cleanup_files.append(src)
            exploder = get_tools(self.current_format)[0]
            opf = exploder(src, tdir, question=self.ask_question)
        except WorkerError as e:
            det_msg = e.orig_tb
        except Error as e:
            return error_dialog(self, _('Failed to unpack'),
                (_('Could not explode the %s file.')%self.current_format) + ' '
                + as_unicode(e), show=True)
        except:
            import traceback
            det_msg = traceback.format_exc()
        finally:
            self.hide_msg()

        if det_msg is not None:
            return error_dialog(self, _('Failed to unpack'),
                _('Could not explode the %s file. Click "Show Details" for '
                    'more information.')%self.current_format, det_msg=det_msg,
                show=True)

        if opf is None:
            # The question was answered with No
            return

        self._exploded = tdir
        self.explode_button.setEnabled(False)
        self.preview_button.setEnabled(True)
        self.rebuild_button.setEnabled(True)
        open_local_file(tdir)

    def rebuild_it(self):
        from calibre.ebooks.tweak import get_tools, WorkerError
        src_dir = self._exploded
        det_msg = None
        of = PersistentTemporaryFile('_tweak_rebuild.'+self.current_format.lower())
        of.close()
        of = of.name
        self._cleanup_files.append(of)
        try:
            rebuilder = get_tools(self.current_format)[1]
            rebuilder(src_dir, of)
        except WorkerError as e:
            det_msg = e.orig_tb
        except:
            import traceback
            det_msg = traceback.format_exc()
        finally:
            self.hide_msg()

        if det_msg is not None:
            error_dialog(self, _('Failed to rebuild file'),
                    _('Failed to rebuild %s. For more information, click '
                        '"Show details".')%self.current_format,
                    det_msg=det_msg, show=True)
            return None

        return of

    def preview(self):
        self.show_msg(_('Rebuilding, please wait...'))
        QTimer.singleShot(5, self.do_preview)

    def do_preview(self):
        rebuilt = self.rebuild_it()
        if rebuilt is not None:
            self.parent().iactions['View']._view_file(rebuilt)

    def rebuild(self):
        self.show_msg(_('Rebuilding, please wait...'))
        QTimer.singleShot(5, self.do_rebuild)

    def do_rebuild(self):
        rebuilt = self.rebuild_it()
        if rebuilt is not None:
            fmt = os.path.splitext(rebuilt)[1][1:].upper()
            with open(rebuilt, 'rb') as f:
                self.db.add_format(self.book_id, fmt, f, index_is_id=True)
            self.accept()

    def cancel(self):
        self.reject()

    def cleanup(self):
        if isosx and self._exploded:
            try:
                import appscript
                self.finder = appscript.app('Finder')
                self.finder.Finder_windows[os.path.basename(self._exploded)].close()
            except:
                pass

        for f in self._cleanup_files:
            try:
                os.remove(f)
            except:
                pass

        for d in self._cleanup_dirs:
            try:
                shutil.rmtree(d)
            except:
                pass

    @property
    def db(self):
        return self.db_ref()

    @property
    def current_format(self):
        for b in self.fmt_choice_buttons:
            if b.isChecked():
                return unicode(b.text())
Exemplo n.º 7
0
    def __init__(self, parent, db, id_to_select, select_sort, select_link):
        QDialog.__init__(self, parent)
        Ui_EditAuthorsDialog.__init__(self)
        self.setupUi(self)
        # Remove help icon on title bar
        icon = self.windowIcon()
        self.setWindowFlags(self.windowFlags()
                            & (~Qt.WindowContextHelpButtonHint))
        self.setWindowIcon(icon)

        try:
            self.table_column_widths = \
                        gprefs.get('manage_authors_table_widths', None)
            geom = gprefs.get('manage_authors_dialog_geometry', bytearray(''))
            self.restoreGeometry(QByteArray(geom))
        except:
            pass

        self.buttonBox.accepted.connect(self.accepted)

        # Set up the column headings
        self.table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table.setColumnCount(3)
        self.down_arrow_icon = QIcon(I('arrow-down.png'))
        self.up_arrow_icon = QIcon(I('arrow-up.png'))
        self.blank_icon = QIcon(I('blank.png'))
        self.auth_col = QTableWidgetItem(_('Author'))
        self.table.setHorizontalHeaderItem(0, self.auth_col)
        self.auth_col.setIcon(self.blank_icon)
        self.aus_col = QTableWidgetItem(_('Author sort'))
        self.table.setHorizontalHeaderItem(1, self.aus_col)
        self.aus_col.setIcon(self.up_arrow_icon)
        self.aul_col = QTableWidgetItem(_('Link'))
        self.table.setHorizontalHeaderItem(2, self.aul_col)
        self.aus_col.setIcon(self.blank_icon)

        # Add the data
        self.authors = {}
        auts = db.get_authors_with_ids()
        self.table.setRowCount(len(auts))
        select_item = None
        for row, (id, author, sort, link) in enumerate(auts):
            author = author.replace('|', ',')
            self.authors[id] = (author, sort, link)
            aut = tableItem(author)
            aut.setData(Qt.UserRole, id)
            sort = tableItem(sort)
            link = tableItem(link)
            self.table.setItem(row, 0, aut)
            self.table.setItem(row, 1, sort)
            self.table.setItem(row, 2, link)
            if id == id_to_select:
                if select_sort:
                    select_item = sort
                elif select_link:
                    select_item = link
                else:
                    select_item = aut
        self.table.resizeColumnsToContents()
        if self.table.columnWidth(2) < 200:
            self.table.setColumnWidth(2, 200)

        # set up the cellChanged signal only after the table is filled
        self.table.cellChanged.connect(self.cell_changed)

        # set up sort buttons
        self.sort_by_author.setCheckable(True)
        self.sort_by_author.setChecked(False)
        self.sort_by_author.clicked.connect(self.do_sort_by_author)
        self.author_order = 1

        self.table.sortByColumn(1, Qt.AscendingOrder)
        self.sort_by_author_sort.clicked.connect(self.do_sort_by_author_sort)
        self.sort_by_author_sort.setCheckable(True)
        self.sort_by_author_sort.setChecked(True)
        self.author_sort_order = 1

        self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort)
        self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author)

        # Position on the desired item
        if select_item is not None:
            self.table.setCurrentItem(select_item)
            self.table.editItem(select_item)
            self.start_find_pos = select_item.row() * 2 + select_item.column()
        else:
            self.table.setCurrentCell(0, 0)
            self.start_find_pos = -1

        # set up the search box
        self.find_box.initialize('manage_authors_search')
        self.find_box.lineEdit().returnPressed.connect(self.do_find)
        self.find_box.editTextChanged.connect(self.find_text_changed)
        self.find_button.clicked.connect(self.do_find)

        l = QLabel(self.table)
        self.not_found_label = l
        l.setFrameStyle(QFrame.StyledPanel)
        l.setAutoFillBackground(True)
        l.setText(_('No matches found'))
        l.setAlignment(Qt.AlignVCenter)
        l.resize(l.sizeHint())
        l.move(10, 20)
        l.setVisible(False)
        self.not_found_label.move(40, 40)
        self.not_found_label_timer = QTimer()
        self.not_found_label_timer.setSingleShot(True)
        self.not_found_label_timer.timeout.connect(
            self.not_found_label_timer_event, type=Qt.QueuedConnection)

        self.table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table.customContextMenuRequested.connect(self.show_context_menu)
Exemplo n.º 8
0
class DiagnosticGui(Plugin):
    
    last_suppress_time = Time()
    last_twist_time = Time()
    gui_update_timer = QTimer()
    
    # Raw joystick data    
    joystick_data = []
    joystick_table_vals = []
    joystick_channel_text = ['CHAN_ENABLE: ', 'CHAN_ROTATE: ', 'CHAN_FORWARD: ', 'CHAN_LATERAL: ', 'CHAN_MODE: ', 'CHAN_EXTRA: ']
    joystick_bind_status = False
    joystick_bind_dot_counter = 0
    
    # Mode
    current_mode = -1
    
    # Topic pub timeouts    
    JOYSTICK_SUPPRESS_PERIOD = 0.2 # 5 Hz
    CMD_VEL_TIMEOUT_PERIOD = 0.2 # 5 Hz    
    joystick_suppressed = False
    command_received = False
    current_cmd = Twist()
    battery_percent = 0
    battery_voltage = 0
    
    # Checklist icons
    bad_icon = QPixmap()
    good_icon = QPixmap()
    none_icon = QPixmap()
    
    # Checklist status
    GOOD = 0
    BAD = 1
    NONE = 2
    
    # Checklist variables
    checklist_status = []
    
    BATT_MAN = 0
    ESTOP_MAN = 1
    DISABLE_MAN = 2
    JOYSTICK_MAN = 3
    SUPPRESS_MAN = 4    
    BATT_COMP = 5
    ESTOP_COMP = 6
    DISABLE_COMP = 7
    JOYSTICK_COMP = 8
    SUPPRESS_COMP = 9
    CMD_COMP = 10
    
    # Bumpers
    bumper_front_left = 0
    bumper_front_right = 0
    bumper_rear_left = 0
    bumper_rear_right = 0
    
    # Gyro cal
    gyro_cal_status = False
    gyro_x = 0.0
    gyro_y = 0.0
    gyro_z = 0.0
    cal_enabled = True
    cal_time = rospy.Time(0)
    
    # Max speed config
    max_speed_known = False
    max_speed_dirty = True
    max_linear_actual = 0.0
    max_angular_actual = 0.0
    max_linear_setting = 0.0
    max_angular_setting = 0.0
    
    # Wake time
    current_wake_time = rospy.Time(0)
    rel_wake_days = 0
    rel_wake_hours = 0
    rel_wake_minutes = 0
    rel_wake_secs = 0
    
    # Switching between tabs and full GUI
    is_currently_tab = False
    widget_count = 0
    current_tab_idx = -1
    raw_data_tab_idx = 5
    
    # Check connection to base
    base_connected = False
    last_joystick_time = rospy.Time(0)
    
    # Constants    
    stick_ind_lox = 80
    stick_ind_loy = 136
    stick_ind_rox = 286
    stick_ind_roy = 135
    stick_ind_range_pix = 88.0
    stick_ind_range_max = JoystickRaw.MAX
    stick_ind_range_min = JoystickRaw.MIN
    stick_ind_range_mid = JoystickRaw.CENTER
    stick_ind_range_factor = stick_ind_range_pix / (stick_ind_range_max - stick_ind_range_min)
    stick_ind_radius = 7    
    mode_ind_x1 = 52
    mode_ind_y1 = 37
    mode_ind_x2 = 44
    mode_ind_y2 = 13
    power_ind_x1 = 160
    power_ind_x2 = 206
    power_ind_y = 213    
    bumper_fl_x = 70
    bumper_fl_y = 60
    bumper_fr_x = 293
    bumper_fr_y = 60
    bumper_rl_x = 70
    bumper_rl_y = 282
    bumper_rr_x = 293
    bumper_rr_y = 282
    bumper_dx = 62
    bumper_dy = 54    
    joystick_table_left_edge = 440
    joystick_table_top_edge = 525    
    twist_table_left_edge = 700
    twist_table_top_edge = 580    
    battery_table_left_edge = 700
    battery_table_top_edge = 730
    
    def __init__(self, context):
        # Qt setup
        self.context_ = context
        self.initGui('full')      
        
        # ROS setup
        topic_timeout_timer = rospy.Timer(rospy.Duration(0.02), self.topicTimeoutCallback)
        self.subscribeTopics()
        self.advertiseTopics()
        
    def initGui(self, gui_type):
        if gui_type == 'full':
            self.spawnFullGui()
            self.initTables(self._widget, self.joystick_table_left_edge, self.joystick_table_top_edge) 
        else:
            self.spawnTabGui()
            self.initTables(self._widget.gui_tabs.widget(self.raw_data_tab_idx), 20, 20) 
            self._widget.gui_tabs.setCurrentIndex(self.current_tab_idx)
            
        self.resetGuiTimer()            
        self.initJoystickGraphics()
        self.initBumperGraphics()
        self.initChecklists()
        self.bindCallbacks()
        self.refreshMaxSpeed()
        
        # Initialize absolute wake time setter to current time
        datetime_now = QDateTime(QDate.currentDate(), QTime.currentTime())
        self._widget.absolute_wake_time_obj.setDateTime(datetime_now)
        temp_time = self._widget.absolute_wake_time_obj.time()
        temp_time = QTime(temp_time.hour(), temp_time.minute())
        self._widget.absolute_wake_time_obj.setTime(temp_time)
        
        # Set connection label text
        if self.base_connected:
            self._widget.disconnected_lbl.setVisible(False)
        else:
            self._widget.disconnected_lbl.setVisible(True)
            self._widget.disconnected_lbl.setText('<font color=#FF0000>NOT CONNECTED</font>')

    def topicTimeoutCallback(self, event):
        # Joystick suppression
        if (event.current_real - self.last_suppress_time).to_sec() < self.JOYSTICK_SUPPRESS_PERIOD and self.suppress_dt.to_sec() <= 1.0/9.0:
            self.joystick_suppressed = True
        else:
            self.joystick_suppressed = False
            
        # Command message
        if (event.current_real - self.last_twist_time).to_sec() < self.CMD_VEL_TIMEOUT_PERIOD and self.twist_dt.to_sec() <= 1.0/6.0:
            self.command_received = True
        else:
            self.command_received = False

    def initChecklists(self):
        self.bad_icon.load(os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'images', 'bad.png'))
        self.good_icon.load(os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'images', 'good.png'))
        self.none_icon.load(os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'images', 'none.png'))        
        self.checklist_status=[0 for i in range(self.CMD_COMP + 1)]
        self.checklist_status[self.BATT_MAN] = self.NONE
        self.checklist_status[self.ESTOP_MAN] = self.NONE
        self.checklist_status[self.DISABLE_MAN] = self.NONE
        self.checklist_status[self.JOYSTICK_MAN] = self.NONE
        self.checklist_status[self.SUPPRESS_MAN] = self.NONE        
        self.checklist_status[self.BATT_COMP] = self.NONE
        self.checklist_status[self.ESTOP_COMP] = self.NONE
        self.checklist_status[self.DISABLE_COMP] = self.NONE
        self.checklist_status[self.JOYSTICK_COMP] = self.NONE
        self.checklist_status[self.SUPPRESS_COMP] = self.NONE
        self.checklist_status[self.CMD_COMP] = self.NONE

    def initTabTables(self):
        self.joystick_table_vals = [QLabel(self._widget.gui_tabs.widget(self.raw_data_tab_idx)) for i in range(ChannelIndex.CHAN_EXTRA+1)]
        self.joystick_table_labels = [QLabel(self._widget.gui_tabs.widget(self.raw_data_tab_idx)) for i in range(ChannelIndex.CHAN_EXTRA+1)]
        self.joystick_table_heading = QLabel(self._widget.gui_tabs.widget(self.raw_data_tab_idx))
        

    def initTables(self, widget, left, top):
        # Joystick data table
        self.joystick_data = [0 for i in range(ChannelIndex.CHAN_EXTRA+1)]
        self.joystick_table_vals = [QLabel(widget) for i in range(ChannelIndex.CHAN_EXTRA+1)]
        self.joystick_table_labels = [QLabel(widget) for i in range(ChannelIndex.CHAN_EXTRA+1)]
        self.joystick_table_heading = QLabel(widget)
        
        self.joystick_table_heading.setText('Raw Joystick Data')
        self.joystick_table_heading.setFont(QFont('Ubuntu', 11, QFont.Bold))
        self.joystick_table_heading.move(left, top)
        for i in range(len(self.joystick_table_vals)):
            self.joystick_table_vals[i].move(left + 150, top + 30 * (i+1))
            self.joystick_table_vals[i].setText('0000')
            self.joystick_table_vals[i].setFixedWidth(200)
            
            self.joystick_table_labels[i].move(left, top + 30 * (i+1))
            self.joystick_table_labels[i].setText(self.joystick_channel_text[i])
            
        # Twist data table
        self.twist_table_heading = QLabel(widget)
        self.twist_table_heading.setText('Current Twist Command')
        self.twist_table_heading.setFont(QFont('Ubuntu', 11, QFont.Bold))
        self.twist_table_heading.move(left + 260, top)
        self.twist_table_labels = [QLabel(widget) for i in range(0, 3)]
        self.twist_table_vals = [QLabel(widget) for i in range(0, 3)]
        for i in range(len(self.twist_table_vals)):
            self.twist_table_vals[i].move(left + 260 + 150, top + 30 * (i+1))
            self.twist_table_vals[i].setText('Not Published')
            self.twist_table_vals[i].setFixedWidth(200)
            self.twist_table_labels[i].move(left + 260, top + 30 * (i+1))
            
        self.twist_table_labels[0].setText('Forward (m/s):')
        self.twist_table_labels[1].setText('Lateral (m/s):')
        self.twist_table_labels[2].setText('Rotation (rad/s):')
        
        # Battery percentage
        self.battery_heading = QLabel(widget)
        self.battery_heading.setText('Current Battery State')
        self.battery_heading.setFont(QFont('Ubuntu', 11, QFont.Bold))
        self.battery_heading.move(left + 260, top + 150)
        self.battery_label = QLabel(widget)
        self.battery_label.move(left + 260, top + 150 +30)
        self.battery_label.setText('000 %')
        self.battery_voltage_label = QLabel(widget)
        self.battery_voltage_label.move(left + 260, top + 150 +60)
        self.battery_voltage_label.setText('00.00 V')
        self.battery_voltage_label.setFixedWidth(200)
        
        # Mode
        self.mode_heading = QLabel(widget)
        self.mode_heading.setFont(QFont('Ubuntu', 11, QFont.Bold))
        self.mode_heading.move(left, top + 225)
        self.mode_heading.setText('Current Mode')
        self.mode_label = QLabel(widget)
        self.mode_label.move(left + 120, top + 225)        
        self.mode_label.setText('XXXXXXXXXXXXXXXXXXXXXX')

    def bindCallbacks(self):
        self._widget.start_bind_btn.clicked.connect(self.startBind)
        self._widget.stop_bind_btn.clicked.connect(self.stopBind)
        
        self._widget.gyro_cal_btn.clicked.connect(self.calGyro)
        self._widget.clear_cal_btn.clicked.connect(self.clearCal)
        
        self._widget.max_linear_txt.editingFinished.connect(self.maxLinearChanged)
        self._widget.max_angular_txt.editingFinished.connect(self.maxAngularChanged)
        self._widget.set_speed_btn.clicked.connect(self.setMaxSpeed)
        self._widget.clear_speed_btn.clicked.connect(self.clearMaxSpeed)
        
        self._widget.wake_time_days_txt.editingFinished.connect(self.wakeDaysChanged)
        self._widget.wake_time_hours_txt.editingFinished.connect(self.wakeHoursChanged)
        self._widget.wake_time_minutes_txt.editingFinished.connect(self.wakeMinutesChanged)
        self._widget.wake_time_secs_txt.editingFinished.connect(self.wakeSecsChanged)
        self._widget.set_relative_wake_time_btn.clicked.connect(self.setRelativeWakeTime)        
        self._widget.set_absolute_wake_time_btn.clicked.connect(self.setAbsoluteWakeTime)
        self._widget.clear_wake_time_btn.clicked.connect(self.clearWakeTime)
        
        if self.is_currently_tab:
            self._widget.gui_tabs.currentChanged.connect(self.setCurrentTab)
        
    def setCurrentTab(self, idx):
        self.current_tab_idx = self._widget.gui_tabs.currentIndex()
        
    def startBind(self):
        self.pub_start_bind.publish(std_msgs.msg.Empty())
        
    def stopBind(self):
        self.pub_stop_bind.publish(std_msgs.msg.Empty())
        
    def calGyro(self):
        gyro_cal_srv = rospy.ServiceProxy('/mobility_base/imu/calibrate', std_srvs.srv.Empty)
        try:
            gyro_cal_srv()
            self.cal_enabled = False
            self.cal_time = rospy.Time.now()
        except:
            pass
 
    def clearCal(self):
        clear_cal_srv = rospy.ServiceProxy('/mobility_base/imu/clear_cal', std_srvs.srv.Empty)
        try:
            clear_cal_srv()
        except:
            pass
        
    def maxLinearChanged(self):
        try:
            float_val = float(self._widget.max_linear_txt.text())
            self.max_linear_setting = float_val;
        except ValueError:
            if self.max_linear_actual <= 0 or not isfinite(self.max_linear_actual):
                self.max_linear_setting = 0.0
            else:
                self.max_linear_setting = self.max_linear_actual
        self.max_speed_dirty = True
        
    def maxAngularChanged(self):
        try:
            float_val = float(self._widget.max_angular_txt.text())
            self.max_angular_setting = float_val;
        except ValueError:
            if self.max_angular_actual <= 0 or not isfinite(self.max_angular_actual):
                self.max_angular_setting = 0.0
            else:
                self.max_angular_setting = self.max_angular_actual
        self.max_speed_dirty = True
        
    def setMaxSpeed(self):
        set_max_speed_srv = rospy.ServiceProxy('/mobility_base/set_max_speed', SetMaxSpeed)
        req = SetMaxSpeedRequest()
        req.linear = self.max_linear_setting
        req.angular = self.max_angular_setting
        try:
            set_max_speed_srv(req)
            self.max_speed_known = False
        except:
            pass
        
    def clearMaxSpeed(self):
        set_max_speed_srv = rospy.ServiceProxy('/mobility_base/set_max_speed', SetMaxSpeed)
        req = SetMaxSpeedRequest()
        req.linear = float('nan')
        req.angular = float('nan')
        try:
            set_max_speed_srv(req)
            self.max_speed_known = False
        except:
            pass

    def wakeDaysChanged(self):
        try:
            self.rel_wake_days = float(self._widget.wake_time_days_txt.text())
        except:
            self._widget.wake_time_days_txt.setText(str(self.rel_wake_days))
                        
    def wakeHoursChanged(self):
        try:
            self.rel_wake_hours = float(self._widget.wake_time_hours_txt.text())
        except:
            self._widget.wake_time_hours_txt.setText(str(self.rel_wake_hours))
            
    def wakeMinutesChanged(self):
        try:
            self.rel_wake_minutes = float(self._widget.wake_time_minutes_txt.text())
        except:
            self._widget.wake_time_minutes_txt.setText(str(self.rel_wake_minutes)) 

    def wakeSecsChanged(self):
        try:
            self.rel_wake_secs = float(self._widget.wake_time_secs_txt.text())
        except:
            self._widget.wake_time_secs_txt.setText(str(self.rel_wake_secs)) 

    def setRelativeWakeTime(self):
        new_wake_time = std_msgs.msg.Time()
        rel_wake_time = 86400 * self.rel_wake_days + 3600 * self.rel_wake_hours + 60 * self.rel_wake_minutes + self.rel_wake_secs
        new_wake_time.data = rospy.Time.now() + rospy.Duration(rel_wake_time)
        self.pub_set_wake_time.publish(new_wake_time)
        
    def setAbsoluteWakeTime(self):
        self.pub_set_wake_time.publish(rospy.Time(self._widget.absolute_wake_time_obj.dateTime().toTime_t()))
        
    def clearWakeTime(self):
        self.pub_set_wake_time.publish(rospy.Time(0))
    
    def refreshMaxSpeed(self):
        self.max_speed_dirty = True
        self.max_speed_known = False
    
    def updateGuiCallback(self):
        
        # Switch between tabs and full GUI
        if self.is_currently_tab:
            if self._widget.height() > 830 and self._widget.width() > 1205:
                self.is_currently_tab = False
                self.context_.remove_widget(self._widget)
                self.initGui('full')
        else:
            if self._widget.height() < 810 or self._widget.width() < 1185:
                self.is_currently_tab = True
                self.context_.remove_widget(self._widget)
                self.initGui('tab')
        
        # Check connection to base
        if self.base_connected and (rospy.Time.now() - self.last_joystick_time).to_sec() > 1.0:
            self.base_connected = False
            self._widget.disconnected_lbl.setText('<font color=#FF0000>NOT CONNECTED</font>')
            self._widget.disconnected_lbl.setVisible(True)
        
        if not self.base_connected and (rospy.Time.now() - self.last_joystick_time).to_sec() < 1.0:
            self.base_connected = True
#             self._widget.disconnected_lbl.setText('')
            self._widget.disconnected_lbl.setVisible(False)
        
        # Update checklists
        self.updateChecklist();

        # Manage 5 second disable of gyro calibration button
        if not self.cal_enabled:
            if (rospy.Time.now() - self.cal_time).to_sec() > 5.0:
                self._widget.gyro_cal_btn.setEnabled(True)
                self._widget.gyro_cal_btn.setText('Calibrate')
                self.cal_enabled = True
            else:
                self._widget.gyro_cal_btn.setEnabled(False)
                self._widget.gyro_cal_btn.setText(str(5 - 0.1*floor(10*(rospy.Time.now() - self.cal_time).to_sec()))) 
                
        # Update joystick graphics
        if not self.checkJoystickValid():
            self.updateCheckStatus(self._widget.joystick_bind_chk, self.NONE)
            
            if not self.joystick_bind_status:
                self._widget.joystick_bind_lbl.setText('')

            
            for l in self.joystick_power_ind:
                l.setVisible(True)
            self.updateRightStickIndicator(self.stick_ind_range_mid, self.stick_ind_range_mid)
            self.updateLeftStickIndicator(self.stick_ind_range_mid, self.stick_ind_range_mid)
        else:
            self.updateCheckStatus(self._widget.joystick_bind_chk, self.GOOD)
            self._widget.joystick_bind_lbl.setText('Joystick bound')

            for l in self.joystick_power_ind:
                l.setVisible(False)
            self.updateRightStickIndicator(self.joystick_data[ChannelIndex.CHAN_LATERAL], self.joystick_data[ChannelIndex.CHAN_FORWARD])
            self.updateLeftStickIndicator(self.joystick_data[ChannelIndex.CHAN_ROTATE], self.joystick_data[ChannelIndex.CHAN_ENABLE])
        if self.joystick_data[ChannelIndex.CHAN_MODE] < self.stick_ind_range_mid:
            self.mode_ind.setPen(self.cyan_pen)
            self._widget.modeLabel.setText("0 (Computer)")
        else:
            self.mode_ind.setPen(self.magenta_pen)
            self._widget.modeLabel.setText("1 (Manual)")
        
        # Update joystick data table
        for i in range(len(self.joystick_table_vals)):
            self.joystick_table_vals[i].setText(str(self.joystick_data[i]))
            
        # Update twist data table
        if self.command_received:
            self.twist_table_vals[0].setText(str(0.01 * floor(100 * self.current_cmd.linear.x)))
            self.twist_table_vals[1].setText(str(0.01 * floor(100 * self.current_cmd.linear.y)))
            self.twist_table_vals[2].setText(str(0.01 * floor(100 * self.current_cmd.angular.z)))
        else:
            self.twist_table_vals[0].setText('Not Published')
            self.twist_table_vals[1].setText('Not Published')
            self.twist_table_vals[2].setText('Not Published')
            
        # Update battery percentage
        self.battery_label.setText(str(self.battery_percent) + ' %')
        self.battery_voltage_label.setText(str(0.01 * floor(100 * self.battery_voltage)) + ' V')
        
        # Update mode
        self.mode_label.setText(self.getModeString(self.current_mode))
        
        # Update bumper graphics
        self.bumperVisibleSwitch(BumperIndex.BUMPER_1F, self.bumper_front_left)
        self.bumperVisibleSwitch(BumperIndex.BUMPER_2F, self.bumper_front_right)
        self.bumperVisibleSwitch(BumperIndex.BUMPER_3F, self.bumper_rear_left)
        self.bumperVisibleSwitch(BumperIndex.BUMPER_4F, self.bumper_rear_right)
        self.bumper_state_labels[0].setPlainText(str(self.bumper_front_left))
        self.bumper_state_labels[1].setPlainText(str(self.bumper_front_right))
        self.bumper_state_labels[2].setPlainText(str(self.bumper_rear_left))
        self.bumper_state_labels[3].setPlainText(str(self.bumper_rear_right))
        
        # Update gyro cal graphics
        if self.gyro_cal_status:            
            self.updateCheckStatus(self._widget.gyro_cal_chk, self.GOOD)
            self._widget.gyro_cal_lbl.setText('Gyro calibrated')
        else:
            self.updateCheckStatus(self._widget.gyro_cal_chk, self.BAD)
            self._widget.gyro_cal_lbl.setText('Gyro NOT calibrated')
    
        self._widget.gyro_x_lbl.setText('x: ' + str(1e-5*floor(1e5*self.gyro_x)))
        self._widget.gyro_y_lbl.setText('y: ' + str(1e-5*floor(1e5*self.gyro_y)))
        self._widget.gyro_z_lbl.setText('z: ' + str(1e-5*floor(1e5*self.gyro_z)))
    
        # Update max speed configuration
        if not self.max_speed_known:
            service_name = '/mobility_base/get_max_speed'
            get_max_speed_srv = rospy.ServiceProxy(service_name, GetMaxSpeed)
            
            try:
                resp = get_max_speed_srv()
                self.max_speed_known = True
                self.max_linear_actual = resp.linear
                self.max_angular_actual = resp.angular
                
                if self.max_linear_actual <= 0 or not isfinite(self.max_linear_actual):
                    self._widget.max_linear_lbl.setText('Unlimited')
                else:
                    self._widget.max_linear_lbl.setText(str(1e-2*round(1e2*self.max_linear_actual)))
                
                if self.max_angular_actual <= 0 or not isfinite(self.max_angular_actual):
                    self._widget.max_angular_lbl.setText('Unlimited')
                else:
                    self._widget.max_angular_lbl.setText(str(1e-2*round(1e2*self.max_angular_actual)))

            except:
                pass
#                 print service_name + " doesn't exist"
                
        if self.max_speed_dirty:        
            self._widget.max_linear_txt.setText(str(self.max_linear_setting))
            self._widget.max_angular_txt.setText(str(self.max_angular_setting))
            self.max_speed_dirty = False
       
        # Wake time
        if self.current_wake_time == rospy.Time(0):
            self._widget.wake_time_lbl.setText('Not Set')
        else:
            self._widget.wake_time_lbl.setText(time.strftime('%m/%d/%Y,  %H:%M:%S', time.localtime(self.current_wake_time.to_sec())))
            
    def checkJoystickValid(self):
        return self.joystick_data[ChannelIndex.CHAN_FORWARD] > 0 or self.joystick_data[ChannelIndex.CHAN_LATERAL] > 0 or self.joystick_data[ChannelIndex.CHAN_ROTATE] > 0 or self.joystick_data[ChannelIndex.CHAN_MODE] > 0
            
    def getModeString(self, mode_num):
        if mode_num == Mode.MODE_ESTOP:
            return 'MODE_ESTOP'
        elif mode_num == Mode.MODE_DISABLED:
            return 'MODE_DISABLED'
        elif mode_num == Mode.MODE_BATTERY_LIMP_HOME:
            return 'MODE_BATTERY_LIMP_HOME'
        elif mode_num == Mode.MODE_BATTERY_CRITICAL:
            return 'MODE_BATTERY_CRITICAL'
        elif mode_num == Mode.MODE_WIRELESS:
            return 'MODE_WIRELESS'
        elif mode_num == Mode.MODE_TIMEOUT:
            return 'MODE_TIMEOUT'
        elif mode_num == Mode.MODE_VELOCITY:
            return 'MODE_VELOCITY'
        elif mode_num == Mode.MODE_VELOCITY_RAW:
            return 'MODE_VELOCITY_RAW'
        else:
            return ''
        
    def updateChecklist(self):
        if self.current_mode >= Mode.MODE_TIMEOUT:
            self.checklist_status[self.BATT_MAN] = self.GOOD
            self.checklist_status[self.ESTOP_MAN] = self.GOOD
            self.checklist_status[self.DISABLE_MAN] = self.GOOD
            self.checklist_status[self.JOYSTICK_MAN] = self.BAD
            self.checklist_status[self.BATT_COMP] = self.GOOD
            self.checklist_status[self.ESTOP_COMP] = self.GOOD
            self.checklist_status[self.DISABLE_COMP] = self.GOOD
            self.checklist_status[self.JOYSTICK_COMP] = self.GOOD
        elif self.current_mode == Mode.MODE_WIRELESS:
            self.checklist_status[self.BATT_MAN] = self.GOOD
            self.checklist_status[self.ESTOP_MAN] = self.GOOD
            self.checklist_status[self.DISABLE_MAN] = self.GOOD
            self.checklist_status[self.JOYSTICK_MAN] = self.GOOD
            self.checklist_status[self.BATT_COMP] = self.GOOD
            self.checklist_status[self.ESTOP_COMP] = self.GOOD
            self.checklist_status[self.DISABLE_COMP] = self.GOOD
            self.checklist_status[self.JOYSTICK_COMP] = self.BAD
        elif self.current_mode == Mode.MODE_DISABLED:
            self.checklist_status[self.BATT_MAN] = self.NONE
            self.checklist_status[self.ESTOP_MAN] = self.GOOD
            self.checklist_status[self.DISABLE_MAN] = self.BAD
            self.checklist_status[self.JOYSTICK_MAN] = self.NONE
            self.checklist_status[self.BATT_COMP] = self.NONE
            self.checklist_status[self.ESTOP_COMP] = self.GOOD
            self.checklist_status[self.DISABLE_COMP] = self.BAD
            self.checklist_status[self.JOYSTICK_COMP] = self.NONE
        elif self.current_mode == Mode.MODE_ESTOP:
            self.checklist_status[self.BATT_MAN] = self.NONE
            self.checklist_status[self.ESTOP_MAN] = self.BAD
            self.checklist_status[self.DISABLE_MAN] = self.NONE
            self.checklist_status[self.JOYSTICK_MAN] = self.NONE
            self.checklist_status[self.BATT_COMP] = self.NONE
            self.checklist_status[self.ESTOP_COMP] = self.BAD
            self.checklist_status[self.DISABLE_COMP] = self.NONE
            self.checklist_status[self.JOYSTICK_COMP] = self.NONE
        elif self.current_mode == Mode.MODE_BATTERY_CRITICAL:
            self.checklist_status[self.BATT_MAN] = self.BAD
            self.checklist_status[self.ESTOP_MAN] = self.GOOD
            self.checklist_status[self.DISABLE_MAN] = self.GOOD
            self.checklist_status[self.JOYSTICK_MAN] = self.NONE
            self.checklist_status[self.BATT_COMP] = self.BAD
            self.checklist_status[self.ESTOP_COMP] = self.GOOD
            self.checklist_status[self.DISABLE_COMP] = self.GOOD
            self.checklist_status[self.JOYSTICK_COMP] = self.NONE
        elif self.current_mode == Mode.MODE_BATTERY_LIMP_HOME:
            self.checklist_status[self.BATT_MAN] = self.GOOD
            self.checklist_status[self.ESTOP_MAN] = self.GOOD
            self.checklist_status[self.DISABLE_MAN] = self.GOOD
            self.checklist_status[self.JOYSTICK_MAN] = self.NONE
            self.checklist_status[self.BATT_COMP] = self.BAD
            self.checklist_status[self.ESTOP_COMP] = self.GOOD
            self.checklist_status[self.DISABLE_COMP] = self.GOOD
            self.checklist_status[self.JOYSTICK_COMP] = self.NONE
        
        # Check if joystick is suppressed by topic
        if self.joystick_suppressed:
            self.checklist_status[self.SUPPRESS_COMP] = self.GOOD
            self.checklist_status[self.DISABLE_COMP] = self.NONE
            self.checklist_status[self.JOYSTICK_COMP] = self.NONE
        else:
            self.checklist_status[self.SUPPRESS_COMP] = self.NONE
        if (rospy.Time.now() - self.last_suppress_time).to_sec() > self.JOYSTICK_SUPPRESS_PERIOD:
            self.checklist_status[self.SUPPRESS_MAN] = self.GOOD
        else:
            self.checklist_status[self.SUPPRESS_MAN] = self.BAD
        
        # Command message received
        if self.command_received:
            self.checklist_status[self.CMD_COMP] = self.GOOD
        else:
            self.checklist_status[self.CMD_COMP] = self.BAD
            
        # Override twist checkbox if mode is in TIMEOUT
        if self.current_mode == Mode.MODE_TIMEOUT:
            self.checklist_status[self.CMD_COMP] = self.BAD
        
        # Update checklist graphics    
        self.updateCheckStatus(self._widget.batt_man_chk, self.checklist_status[self.BATT_MAN])
        self.updateCheckStatus(self._widget.estop_man_chk, self.checklist_status[self.ESTOP_MAN])
        self.updateCheckStatus(self._widget.disable_man_chk, self.checklist_status[self.DISABLE_MAN])
        self.updateCheckStatus(self._widget.joystick_man_chk, self.checklist_status[self.JOYSTICK_MAN])
        self.updateCheckStatus(self._widget.suppress_man_chk, self.checklist_status[self.SUPPRESS_MAN])
        self.updateCheckStatus(self._widget.batt_comp_chk, self.checklist_status[self.BATT_COMP])
        self.updateCheckStatus(self._widget.estop_comp_chk, self.checklist_status[self.ESTOP_COMP])
        self.updateCheckStatus(self._widget.disable_comp_chk, self.checklist_status[self.DISABLE_COMP])
        self.updateCheckStatus(self._widget.joystick_comp_chk, self.checklist_status[self.JOYSTICK_COMP])
        self.updateCheckStatus(self._widget.suppress_comp_chk, self.checklist_status[self.SUPPRESS_COMP])
        self.updateCheckStatus(self._widget.cmd_comp_chk, self.checklist_status[self.CMD_COMP])
        self.updateCheckStatus(self._widget.joystick_bind_chk, self.NONE)        

        
    def updateCheckStatus(self, obj, icon_type):
        if icon_type == self.GOOD:
            obj.setPixmap(self.good_icon)
        elif icon_type == self.BAD:
            obj.setPixmap(self.bad_icon)
        else:
            obj.setPixmap(self.none_icon)
        
    def updateTable(self):
        for i in range(0, len(self.joystick_table_vals)):
            self.joystick_table_vals[i].setText(55)
    
    def recvMode(self, mode_msg):
        self.current_mode = mode_msg.mode
    
    def recvSuppress(self, suppress_msg):
        self.suppress_dt = rospy.Time.now() - self.last_suppress_time
        self.last_suppress_time = rospy.Time.now()
    
    def recvTwist(self, twist_msg):
        self.twist_dt = rospy.Time.now() - self.last_twist_time
        self.last_twist_time = rospy.Time.now()
        self.current_cmd = twist_msg
    
    def recvJoystick(self, joystick_msg):
        self.joystick_data = joystick_msg.channels;
        self.last_joystick_time = rospy.Time.now()
    
    def recvBumpers(self, bumper_msg):
        self.bumper_front_left = bumper_msg.front_left
        self.bumper_front_right = bumper_msg.front_right
        self.bumper_rear_left = bumper_msg.rear_left
        self.bumper_rear_right = bumper_msg.rear_right
    
    def recvBattery(self, battery_msg):
        self.battery_percent = battery_msg.percent
        self.battery_voltage = battery_msg.voltage
    
    def recvBindStatus(self, bind_msg):
        self.joystick_bind_status = bind_msg.data
        
        if bind_msg.data:
            if self.joystick_bind_dot_counter == 0:
                self._widget.joystick_bind_lbl.setText('Binding.')
            elif self.joystick_bind_dot_counter == 1:
                self._widget.joystick_bind_lbl.setText('Binding..')
            elif self.joystick_bind_dot_counter == 2:
                self._widget.joystick_bind_lbl.setText('Binding...')
                
            self.joystick_bind_dot_counter = (self.joystick_bind_dot_counter + 1) % 3

    def recvGyroCalibrated(self, cal_msg):
        self.gyro_cal_status = cal_msg.data
            
    def recvImu(self, imu_msg):
        self.gyro_x = imu_msg.angular_velocity.x
        self.gyro_y = imu_msg.angular_velocity.y
        self.gyro_z = imu_msg.angular_velocity.z
    
    def recvWakeTime(self, time_msg):
        self.current_wake_time = time_msg.data
            
    def subscribeTopics(self):    
        sub_joystick = rospy.Subscriber('/mobility_base/joystick_raw', JoystickRaw, self.recvJoystick)
        sub_suppress = rospy.Subscriber('/mobility_base/suppress_wireless', std_msgs.msg.Empty, self.recvSuppress)
        sub_twist = rospy.Subscriber('/mobility_base/cmd_vel', Twist, self.recvTwist)
        sub_mode = rospy.Subscriber('/mobility_base/mode', Mode, self.recvMode)
        sub_bumpers = rospy.Subscriber('/mobility_base/bumper_states', BumperState, self.recvBumpers)
        sub_battery = rospy.Subscriber('/mobility_base/battery', BatteryState, self.recvBattery)
        sub_bind = rospy.Subscriber('/mobility_base/bind_status', std_msgs.msg.Bool, self.recvBindStatus)
        sub_gyro_calibrated = rospy.Subscriber('/mobility_base/imu/is_calibrated', std_msgs.msg.Bool, self.recvGyroCalibrated)
        sub_imu = rospy.Subscriber('/mobility_base/imu/data_raw', Imu, self.recvImu)
        sub_wake_time = rospy.Subscriber('/mobility_base/wake_time', std_msgs.msg.Time, self.recvWakeTime)
    
    def advertiseTopics(self):
        self.pub_start_bind = rospy.Publisher('/mobility_base/bind_start', std_msgs.msg.Empty, queue_size=1)
        self.pub_stop_bind = rospy.Publisher('/mobility_base/bind_stop', std_msgs.msg.Empty, queue_size=1)
        self.pub_set_wake_time = rospy.Publisher('/mobility_base/set_wake_time', std_msgs.msg.Time, queue_size=1)
    
    def initJoystickGraphics(self):
        # Pens
        self.cyan_pen = QPen(QColor(0, 255, 255))
        self.magenta_pen = QPen(QColor(255, 0, 255))
        self.red_pen = QPen(QColor(255, 0, 0))
        self.cyan_pen.setWidth(3)
        self.magenta_pen.setWidth(3)
        self.red_pen.setWidth(3)
        self.stick_ind_l = QGraphicsEllipseItem()
        self.stick_ind_r = QGraphicsEllipseItem()
        self.stick_line_l = QGraphicsLineItem()
        self.stick_line_r = QGraphicsLineItem()
        self.mode_ind = QGraphicsLineItem()
        
        # Left joystick indicator circle
        px_l = self.stick_ind_lox - self.stick_ind_radius
        py_l = self.stick_ind_loy - self.stick_ind_radius
        self.stick_ind_l.setRect(px_l, py_l, 2 * self.stick_ind_radius, 2 * self.stick_ind_radius)
        self.stick_ind_l.setBrush(QBrush(QColor(255, 0, 0)))
        self.stick_ind_l.setPen(QPen(QColor(0, 0, 0)))  
        
        # Right joystick indicator circle
        px_r = self.stick_ind_rox - self.stick_ind_radius
        py_r = self.stick_ind_roy - self.stick_ind_radius
        self.stick_ind_r.setRect(px_r, py_r, 2 * self.stick_ind_radius, 2 * self.stick_ind_radius)
        self.stick_ind_r.setBrush(QBrush(QColor(255, 0, 0)))
        self.stick_ind_r.setPen(QPen(QColor(0, 0, 0)))
        
        # Left joystick indicator line
        line_pen = QPen(QColor(255,0,0))
        line_pen.setWidth(4)
        self.stick_line_l.setLine(self.stick_ind_lox, self.stick_ind_loy, self.stick_ind_lox, self.stick_ind_loy)
        self.stick_line_l.setPen(line_pen)
        
        # Right joystick indicator line
        self.stick_line_r.setLine(self.stick_ind_rox, self.stick_ind_roy, self.stick_ind_rox, self.stick_ind_roy)
        self.stick_line_r.setPen(line_pen) 
        
        # Mode indicator line
        self.mode_ind.setLine(self.mode_ind_x1, self.mode_ind_y1, self.mode_ind_x2, self.mode_ind_y2)
        self.mode_ind.setPen(self.cyan_pen)
        
        # Joystick power indicator
        self.joystick_power_ind = []
        self.joystick_power_ind.append(QGraphicsLineItem(self.power_ind_x1, self.power_ind_y + 20, self.power_ind_x1, self.power_ind_y - 20))
        self.joystick_power_ind.append(QGraphicsLineItem(self.power_ind_x1, self.power_ind_y - 20, self.power_ind_x1 + 50, self.power_ind_y - 20))
        self.joystick_power_ind.append(QGraphicsLineItem(self.power_ind_x1+50, self.power_ind_y - 20, self.power_ind_x1+50, self.power_ind_y + 20))
        self.joystick_power_ind.append(QGraphicsLineItem(self.power_ind_x1+50, self.power_ind_y + 20, self.power_ind_x1, self.power_ind_y + 20))
        
        # Populate scene
        graphics_scene = QGraphicsScene()
        graphics_scene.addItem(self.stick_ind_l)
        graphics_scene.addItem(self.stick_ind_r)
        graphics_scene.addItem(self.stick_line_l)
        graphics_scene.addItem(self.stick_line_r)
        graphics_scene.addItem(self.mode_ind)
        for l in self.joystick_power_ind:
            l.setPen(self.red_pen)
            graphics_scene.addItem(l)
        graphics_scene.setSceneRect(0, 0, self._widget.joystickGraphicsView.width() - 4, self._widget.joystickGraphicsView.height() - 4)
        self._widget.joystickGraphicsView.setScene(graphics_scene)
        self._widget.joystickGraphicsView.setBackgroundBrush(QBrush(QImage(os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'images', 'dx6ilabels.jpg'))))
        self._widget.joystickGraphicsView.show()
        
    def initBumperGraphics(self):
        
        # Pens
        self.blue_pen = QPen(QColor(0,0,255))
        self.blue_pen.setWidth(10)
        
        self.bumper_lines = []

        # Text state labels
        self.bumper_state_labels = [QGraphicsTextItem() for i in range(0,4)]
        for i in range(len(self.bumper_state_labels)):
            self.bumper_state_labels[i].setFont(QFont('Ubuntu', 14, QFont.Bold))
            self.bumper_state_labels[i].setPlainText('00')
        self.bumper_state_labels[0].setPos(self.bumper_fl_x-10, self.bumper_fl_y + 55)
        self.bumper_state_labels[1].setPos(self.bumper_fr_x-10, self.bumper_fr_y + 55)
        self.bumper_state_labels[2].setPos(self.bumper_rl_x-10, self.bumper_rl_y - 80)
        self.bumper_state_labels[3].setPos(self.bumper_rr_x-10, self.bumper_rr_y - 80)
        
        # Bumper indicator lines
        self.bumperLine(self.bumper_fl_x - 20, self.bumper_fl_y - self.bumper_dy, True)
        self.bumperLine(self.bumper_fl_x - self.bumper_dx, self.bumper_fl_y - 20, False)
        self.bumperLine(self.bumper_fl_x + self.bumper_dx, self.bumper_fl_y - 20, False)
        self.bumperLine(self.bumper_fl_x - 20, self.bumper_fl_y + self.bumper_dy, True)
        self.bumperLine(self.bumper_fr_x - 20, self.bumper_fr_y - self.bumper_dy, True)
        self.bumperLine(self.bumper_fr_x - self.bumper_dx, self.bumper_fr_y - 20, False)
        self.bumperLine(self.bumper_fr_x + self.bumper_dx, self.bumper_fr_y - 20, False)
        self.bumperLine(self.bumper_fr_x - 20, self.bumper_fr_y + self.bumper_dy, True)
        self.bumperLine(self.bumper_rl_x - 20, self.bumper_rl_y - self.bumper_dy, True)
        self.bumperLine(self.bumper_rl_x - self.bumper_dx, self.bumper_rl_y - 20, False)
        self.bumperLine(self.bumper_rl_x + self.bumper_dx, self.bumper_rl_y - 20, False)
        self.bumperLine(self.bumper_rl_x - 20, self.bumper_rl_y + self.bumper_dy, True)
        self.bumperLine(self.bumper_rr_x - 20, self.bumper_rr_y - self.bumper_dy, True)
        self.bumperLine(self.bumper_rr_x - self.bumper_dx, self.bumper_rr_y - 20, False)
        self.bumperLine(self.bumper_rr_x + self.bumper_dx, self.bumper_rr_y - 20, False)
        self.bumperLine(self.bumper_rr_x - 20, self.bumper_rr_y + self.bumper_dy, True)
        
        # Populate scene
        graphics_scene = QGraphicsScene()
        for bumper in self.bumper_lines:
            graphics_scene.addItem(bumper)
        for label in self.bumper_state_labels:
            graphics_scene.addItem(label)
        graphics_scene.setSceneRect(0, 0, self._widget.bumperGraphicsView.width() - 4, self._widget.bumperGraphicsView.height() - 4)
        self._widget.bumperGraphicsView.setScene(graphics_scene)
        self._widget.bumperGraphicsView.setBackgroundBrush(QBrush(QImage(os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'images', 'mb_top.png'))))
        self._widget.bumperGraphicsView.show()
    
    def bumperLine(self, x, y, front):
        new_line = QGraphicsLineItem()
        if front:
            new_line.setLine(x, y, x+40, y)
        else:
            new_line.setLine(x, y, x, y+40)
        new_line.setPen(self.blue_pen)
        new_line.setVisible(False)
        self.bumper_lines.append(new_line)
        
    def bumperVisibleSwitch(self, idx, bumper_state):
        if (bumper_state & BumperState.BUMPER_FRONT):
            self.bumper_lines[idx].setVisible(True)
        else:
            self.bumper_lines[idx].setVisible(False)
        if (bumper_state & BumperState.BUMPER_LEFT):
            self.bumper_lines[idx+1].setVisible(True)
        else:
            self.bumper_lines[idx+1].setVisible(False)
        if (bumper_state & BumperState.BUMPER_RIGHT):
            self.bumper_lines[idx+2].setVisible(True)
        else:
            self.bumper_lines[idx+2].setVisible(False)  
        if (bumper_state & BumperState.BUMPER_REAR):
            self.bumper_lines[idx+3].setVisible(True)
        else:
            self.bumper_lines[idx+3].setVisible(False)
            

    def resetGuiTimer(self):
#         del self.gui_update_timer
        self.gui_update_timer = QTimer(self._widget)
        self.gui_update_timer.setInterval(100)
        self.gui_update_timer.setSingleShot(False)
        self.gui_update_timer.timeout.connect(lambda:self.updateGuiCallback())
        self.gui_update_timer.start()
        
    def spawnFullGui(self):
        super(DiagnosticGui, self).__init__(self.context_)
        # Give QObjects reasonable names
        self.setObjectName('DiagnosticGui')

        # Create QWidget
        self._widget = QWidget()

        # Get path to UI file which should be in the "resource" folder of this package
        ui_file = os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'resource', 'DiagnosticGui.ui')
        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget)
        # Give QObjects reasonable names
        self._widget.setObjectName('DiagnosticGui' + str(self.widget_count))
        self.widget_count += 1
        # Add widget to the user interface
        self.context_.add_widget(self._widget)
        
    def spawnTabGui(self):
        super(DiagnosticGui, self).__init__(self.context_)
        # Give QObjects reasonable names
        self.setObjectName('DiagnosticGui')

        # Create QWidget
        self._widget = QWidget()
        
        # Get path to UI file which should be in the "resource" folder of this package
        ui_file = os.path.join(rospkg.RosPack().get_path('mobility_base_tools'), 'resource', 'DiagnosticGuiTabs.ui')
        # Extend the widget with all attributes and children from UI file
        loadUi(ui_file, self._widget)
        # Give QObjects reasonable names
        self._widget.setObjectName('DiagnosticGui' + str(self.widget_count))
        self.widget_count += 1
        # Add widget to the user interface
        self.context_.add_widget(self._widget)
        
    def updateRightStickIndicator(self, lat_val, forward_val):
        horiz_val = -self.stick_ind_range_factor * (lat_val - self.stick_ind_range_mid)
        vert_val = -self.stick_ind_range_factor * (forward_val - self.stick_ind_range_mid)
        r = sqrt(horiz_val * horiz_val + vert_val * vert_val)
        if r > self.stick_ind_range_pix / 2:
            r = self.stick_ind_range_pix / 2
        ang = atan2(vert_val, horiz_val)    
        px = r * cos(ang)
        py = r * sin(ang)
        self.stick_ind_r.setPos(QPoint(px, py))
        self.stick_line_r.setLine(self.stick_ind_rox, self.stick_ind_roy, self.stick_ind_rox + px, self.stick_ind_roy + py)
    
    def updateLeftStickIndicator(self, yaw_val, enable_val):
        horiz_val = -self.stick_ind_range_factor * (yaw_val - self.stick_ind_range_mid)
        vert_val = -self.stick_ind_range_factor * (enable_val - self.stick_ind_range_mid)
        r = sqrt(horiz_val * horiz_val + vert_val * vert_val)
        if r > self.stick_ind_range_pix / 2:
            r = self.stick_ind_range_pix / 2
        ang = atan2(vert_val, horiz_val)    
        px = r * cos(ang)
        py = r * sin(ang)
        self.stick_ind_l.setPos(QPoint(px, py))
        self.stick_line_l.setLine(self.stick_ind_lox, self.stick_ind_loy, self.stick_ind_lox + px, self.stick_ind_loy + py)        
    
    def shutdown_plugin(self):
        pass