Пример #1
0
    def __init__(self, parent=None):
        QSplitter.__init__(self, parent)
        self.setChildrenCollapsible(False)

        self.items = i = QListWidget(self)
        i.setContextMenuPolicy(Qt.CustomContextMenu)
        i.customContextMenuRequested.connect(self.context_menu)
        self.items.setSpacing(3)
        self.items.itemDoubleClicked.connect(self.current_item_activated)
        self.items.currentItemChanged.connect(self.current_item_changed)
        self.items.setSelectionMode(self.items.NoSelection)
        self.delegate = Delegate(self.items)
        self.items.setItemDelegate(self.delegate)
        self.addWidget(i)
        self.help = h = QTextBrowser(self)
        h.anchorClicked.connect(self.link_clicked)
        h.setOpenLinks(False)
        self.addWidget(h)
        self.clear_help(_('Check has not been run'))
        self.setStretchFactor(0, 100)
        self.setStretchFactor(1, 50)

        state = tprefs.get('check-book-splitter-state', None)
        if state is not None:
            self.restoreState(state)
Пример #2
0
def make():
    demo = QSplitter(Qt.Horizontal)

    plot1 = Plot(demo, 30)
    plot1.setFloorStyle(FLOORISO)
    plot1.setCoordinateStyle(BOX)
    saddle = Saddle(plot1)
    saddle.create()
    plot1.setTitle("Autoswitching axes")
    plot1.setBackgroundColor(RGBA(1.0, 1.0, 0.6))
    plot1.makeCurrent()
    plot1.updateData()
    plot1.updateGL()
    
    plot2 = Plot(demo, 80)
    plot2.setZoom(0.8)
    hat = Hat(plot2)
    hat.create()
    plot2.setPlotStyle(HIDDENLINE)
    plot2.setFloorStyle(FLOORDATA)
    plot2.setCoordinateStyle(FRAME)
    plot2.setBackgroundColor(RGBA(1.0, 1.0, 0.6))
    plot2.makeCurrent()
    plot2.updateData()
    plot2.updateGL()

    demo.resize(800, 400)
    demo.show()
                
    return demo
Пример #3
0
    def __init__(self, parent=None):
        QSplitter.__init__(self, parent)
        self.setChildrenCollapsible(False)

        self.items = i = QListWidget(self)
        i.setContextMenuPolicy(Qt.CustomContextMenu)
        i.customContextMenuRequested.connect(self.context_menu)
        self.items.setSpacing(3)
        self.items.itemDoubleClicked.connect(self.current_item_activated)
        self.items.currentItemChanged.connect(self.current_item_changed)
        self.items.setSelectionMode(self.items.NoSelection)
        self.delegate = Delegate(self.items)
        self.items.setItemDelegate(self.delegate)
        self.addWidget(i)
        self.help = h = QTextBrowser(self)
        h.anchorClicked.connect(self.link_clicked)
        h.setOpenLinks(False)
        self.addWidget(h)
        self.clear_help(_('Check has not been run'))
        self.setStretchFactor(0, 100)
        self.setStretchFactor(1, 50)

        state = tprefs.get('check-book-splitter-state', None)
        if state is not None:
            self.restoreState(state)
Пример #4
0
    def __init__(self, name, label, icon, initial_show=True,
            initial_side_size=120, connect_button=True,
            orientation=Qt.Horizontal, side_index=0, parent=None, shortcut=None):
        QSplitter.__init__(self, parent)
        self.resize_timer = QTimer(self)
        self.resize_timer.setSingleShot(True)
        self.desired_side_size = initial_side_size
        self.desired_show = initial_show
        self.resize_timer.setInterval(5)
        self.resize_timer.timeout.connect(self.do_resize)
        self.setOrientation(orientation)
        self.side_index = side_index
        self._name = name
        self.label = label
        self.initial_side_size = initial_side_size
        self.initial_show = initial_show
        self.splitterMoved.connect(self.splitter_moved, type=Qt.QueuedConnection)
        self.button = LayoutButton(icon, label, self, shortcut=shortcut)
        if connect_button:
            self.button.clicked.connect(self.double_clicked)

        if shortcut is not None:
            self.action_toggle = QAction(QIcon(icon), _('Toggle') + ' ' + label,
                    self)
            self.action_toggle.triggered.connect(self.toggle_triggered)
            if parent is not None:
                parent.addAction(self.action_toggle)
                if hasattr(parent, 'keyboard'):
                    parent.keyboard.register_shortcut('splitter %s %s'%(name,
                        label), unicode(self.action_toggle.text()),
                        default_keys=(shortcut,), action=self.action_toggle)
                else:
                    self.action_toggle.setShortcut(shortcut)
            else:
                self.action_toggle.setShortcut(shortcut)
Пример #5
0
    def __init__(self, parent=None, show_open_in_editor=False):
        QSplitter.__init__(self, parent)
        self._failed_img = None

        self.left, self.right = TextBrowser(parent=self), TextBrowser(right=True, parent=self, show_open_in_editor=show_open_in_editor)
        self.addWidget(self.left), self.addWidget(self.right)
        self.split_words = re.compile(r"\w+|\W", re.UNICODE)
        self.clear()
Пример #6
0
 def do_resize(self, *args):
     orig = self.desired_side_size
     QSplitter.resizeEvent(self, self._resize_ev)
     if orig > 20 and self.desired_show:
         c = 0
         while abs(self.side_index_size - orig) > 10 and c < 5:
             self.apply_state(self.get_state(), save_desired=False)
             c += 1
Пример #7
0
    def __init__(self, parent=None, show_open_in_editor=False):
        QSplitter.__init__(self, parent)
        self._failed_img = None

        self.left, self.right = TextBrowser(parent=self), TextBrowser(
            right=True, parent=self, show_open_in_editor=show_open_in_editor)
        self.addWidget(self.left), self.addWidget(self.right)
        self.split_words = re.compile(r"\w+|\W", re.UNICODE)
        self.clear()
Пример #8
0
    def setup_ui(self):
        self.l = l = QGridLayout(self)
        self.setLayout(l)

        self.bb.setStandardButtons(self.bb.Close)
        self.rearrange_button = b = self.bb.addButton(
            _('Re-arrange favorites'), self.bb.ActionRole)
        b.setCheckable(True)
        b.setChecked(False)
        b.setVisible(False)
        b.setDefault(True)

        self.splitter = s = QSplitter(self)
        s.setChildrenCollapsible(False)

        self.search = h = HistoryLineEdit2(self)
        h.setToolTip(
            textwrap.fill(
                _('Search for unicode characters by using the English names or nicknames.'
                  ' You can also search directly using a character code. For example, the following'
                  ' searches will all yield the no-break space character: U+A0, nbsp, no-break'
                  )))
        h.initialize('charmap_search')
        h.setPlaceholderText(_('Search by name, nickname or character code'))
        self.search_button = b = QPushButton(_('&Search'))
        h.returnPressed.connect(self.do_search)
        b.clicked.connect(self.do_search)
        self.clear_button = cb = QToolButton(self)
        cb.setIcon(QIcon(I('clear_left.png')))
        cb.setText(_('Clear search'))
        cb.clicked.connect(self.clear_search)
        l.addWidget(h), l.addWidget(b, 0, 1), l.addWidget(cb, 0, 2)

        self.category_view = CategoryView(self)
        l.addWidget(s, 1, 0, 1, 3)
        self.char_view = CharView(self)
        self.rearrange_button.toggled[bool].connect(
            self.set_allow_drag_and_drop)
        self.category_view.category_selected.connect(self.show_chars)
        self.char_view.show_name.connect(self.show_char_info)
        self.char_view.char_selected.connect(self.char_selected)
        s.addWidget(self.category_view), s.addWidget(self.char_view)

        self.char_info = la = QLabel('\xa0')
        la.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        l.addWidget(la, 2, 0, 1, 3)

        self.rearrange_msg = la = QLabel(
            _('Drag and drop characters to re-arrange them. Click the re-arrange button again when you are done.'
              ))
        la.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        la.setVisible(False)
        l.addWidget(la, 3, 0, 1, 3)
        l.addWidget(self.bb, 4, 0, 1, 3)
        self.char_view.setFocus(Qt.OtherFocusReason)
Пример #9
0
    def setup_ui(self):
        self.block_show = False
        self.properties = []
        self.l = l  = QVBoxLayout(self)
        self.setLayout(l)
        h = QHBoxLayout()
        l.addLayout(h)
        self.la = la = QLabel(_('&Edit theme:'))
        h.addWidget(la)
        self.theme = t = QComboBox(self)
        la.setBuddy(t)
        t.addItems(sorted(custom_theme_names()))
        t.setMinimumWidth(200)
        if t.count() > 0:
            t.setCurrentIndex(0)
        t.currentIndexChanged[int].connect(self.show_theme)
        h.addWidget(t)

        self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add &new theme'), self)
        b.clicked.connect(self.create_new_theme)
        h.addWidget(b)

        self.remove_button = b = QPushButton(QIcon(I('minus.png')), _('&Remove theme'), self)
        b.clicked.connect(self.remove_theme)
        h.addWidget(b)
        h.addStretch(1)

        self.scroll = s = QScrollArea(self)
        self.w = w = QWidget(self)
        s.setWidget(w), s.setWidgetResizable(True)
        self.cl = cl = QVBoxLayout()
        w.setLayout(cl)

        from calibre.gui2.tweak_book.editor.text import TextEdit
        self.preview = p = TextEdit(self, expected_geometry=(73, 50))
        p.load_text(HELP_TEXT.format(
                *['<b>%s</b>' % x for x in (
                    'Normal', 'Visual', 'CursorLine', 'LineNr', 'MatchParen',
                    'Function', 'Type', 'Statement', 'Constant', 'SpecialCharacter', 'Error', 'SpellError', 'Comment'
                )]
            ))
        p.setMaximumWidth(p.size_hint.width() + 5)
        s.setMinimumWidth(600)
        self.splitter = sp = QSplitter(self)
        l.addWidget(sp)
        sp.addWidget(s), sp.addWidget(p)

        self.bb.clear()
        self.bb.addButton(self.bb.Close)
        l.addWidget(self.bb)

        if self.theme.count() > 0:
            self.show_theme()
Пример #10
0
    def setup_ui(self):
        self.setAttribute(Qt.WA_DeleteOnClose, False)
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)

        self.bb.clear()
        self.bb.addButton(self.bb.Close)
        self.splitter = s = QSplitter(self)
        l.addWidget(s), l.addWidget(self.bb)

        self.fonts_view = fv = QTableView(self)
        self.model = m = AllFonts(fv)
        fv.horizontalHeader().setStretchLastSection(True)
        fv.setModel(m)
        fv.setSortingEnabled(True)
        fv.setShowGrid(False)
        fv.setAlternatingRowColors(True)
        fv.setSelectionMode(fv.ExtendedSelection)
        fv.setSelectionBehavior(fv.SelectRows)
        fv.horizontalHeader().setSortIndicator(1, Qt.AscendingOrder)
        self.container = c = QWidget()
        l = c.l = QVBoxLayout(c)
        c.setLayout(l)
        s.addWidget(fv), s.addWidget(c)

        self.cb = b = QPushButton(_('&Change selected fonts'))
        b.setIcon(QIcon(I('auto_author_sort.png')))
        b.clicked.connect(self.change_fonts)
        l.addWidget(b)
        self.rb = b = QPushButton(_('&Remove selected fonts'))
        b.clicked.connect(self.remove_fonts)
        b.setIcon(QIcon(I('trash.png')))
        l.addWidget(b)
        self.eb = b = QPushButton(_('&Embed all fonts'))
        b.setIcon(QIcon(I('embed-fonts.png')))
        b.clicked.connect(self.embed_fonts)
        l.addWidget(b)
        self.sb = b = QPushButton(_('&Subset all fonts'))
        b.setIcon(QIcon(I('subset-fonts.png')))
        b.clicked.connect(self.subset_fonts)
        l.addWidget(b)
        self.refresh_button = b = self.bb.addButton(_('&Refresh'), self.bb.ActionRole)
        b.setToolTip(_('Rescan the book for fonts in case you have made changes'))
        b.setIcon(QIcon(I('view-refresh.png')))
        b.clicked.connect(self.refresh)

        self.la = la = QLabel('<p>' + _(
        ''' All the fonts declared in this book are shown to the left, along with whether they are embedded or not.
            You can remove or replace any selected font and also embed any declared fonts that are not already embedded.'''))
        la.setWordWrap(True)
        l.addWidget(la)

        l.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
Пример #11
0
    def setup_ui(self):
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)
        self.gb = gb = QGroupBox(_('&Images in book'), self)
        self.v = v = QVBoxLayout(gb)
        gb.setLayout(v), gb.setFlat(True)
        self.names, self.names_filter = create_filterable_names_list(
            sorted(self.image_names, key=sort_key),
            filter_text=_('Filter the list of images'),
            parent=self)
        self.names.doubleClicked.connect(self.double_clicked,
                                         type=Qt.QueuedConnection)
        self.cover_view = CoverView(self)
        l.addWidget(self.names_filter)
        v.addWidget(self.names)

        self.splitter = s = QSplitter(self)
        l.addWidget(s)
        s.addWidget(gb)
        s.addWidget(self.cover_view)

        self.h = h = QHBoxLayout()
        self.preserve = p = QCheckBox(_('Preserve aspect ratio'))
        p.setToolTip(
            textwrap.fill(
                _('If enabled the cover image you select will be embedded'
                  ' into the book in such a way that when viewed, its aspect'
                  ' ratio (ratio of width to height) will be preserved.'
                  ' This will mean blank spaces around the image if the screen'
                  ' the book is being viewed on has an aspect ratio different'
                  ' to the image.')))
        p.setChecked(tprefs['add_cover_preserve_aspect_ratio'])
        p.setVisible(self.container.book_type != 'azw3')
        p.stateChanged.connect(lambda s: tprefs.set(
            'add_cover_preserve_aspect_ratio', s == Qt.Checked))
        self.info_label = il = QLabel('\xa0')
        h.addWidget(p), h.addStretch(1), h.addWidget(il)
        l.addLayout(h)

        l.addWidget(self.bb)
        b = self.bb.addButton(_('Import &image'), self.bb.ActionRole)
        b.clicked.connect(self.import_image)
        b.setIcon(QIcon(I('document_open.png')))
        self.names.setFocus(Qt.OtherFocusReason)
        self.names.selectionModel().currentChanged.connect(
            self.current_image_changed)
Пример #12
0
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.setLayout(QVBoxLayout())

        self.la = la = QLabel(
            '<b>' + _('Select a destination for the Table of Contents entry'))
        self.layout().addWidget(la)
        self.splitter = sp = QSplitter(self)
        self.layout().addWidget(sp)
        self.layout().setStretch(1, 10)
        sp.setOpaqueResize(False)
        sp.setChildrenCollapsible(False)

        self.dest_list = dl = QListWidget(self)
        dl.setMinimumWidth(250)
        dl.currentItemChanged.connect(self.current_changed)
        sp.addWidget(dl)

        w = self.w = QWidget(self)
        l = w.l = QGridLayout()
        w.setLayout(l)
        self.view = WebView(self)
        self.view.elem_clicked.connect(self.elem_clicked)
        l.addWidget(self.view, 0, 0, 1, 3)
        sp.addWidget(w)

        self.search_text = s = QLineEdit(self)
        s.setPlaceholderText(_('Search for text...'))
        l.addWidget(s, 1, 0)
        self.ns_button = b = QPushButton(QIcon(I('arrow-down.png')),
                                         _('Find &next'), self)
        b.clicked.connect(self.find_next)
        l.addWidget(b, 1, 1)
        self.ps_button = b = QPushButton(QIcon(I('arrow-up.png')),
                                         _('Find &previous'), self)
        l.addWidget(b, 1, 2)
        b.clicked.connect(self.find_previous)

        self.f = f = QFrame()
        f.setFrameShape(f.StyledPanel)
        f.setMinimumWidth(250)
        l = f.l = QVBoxLayout()
        f.setLayout(l)
        sp.addWidget(f)

        f.la = la = QLabel('<p>' + _(
            'Here you can choose a destination for the Table of Contents\' entry'
            ' to point to. First choose a file from the book in the left-most panel. The'
            ' file will open in the central panel.<p>'
            'Then choose a location inside the file. To do so, simply click on'
            ' the place in the central panel that you want to use as the'
            ' destination. As you move the mouse around the central panel, a'
            ' thick green line appears, indicating the precise location'
            ' that will be selected when you click.'))
        la.setStyleSheet('QLabel { margin-bottom: 20px }')
        la.setWordWrap(True)
        l.addWidget(la)

        f.la2 = la = QLabel('<b>' + _('&Name of the ToC entry:'))
        l.addWidget(la)
        self.name = QLineEdit(self)
        la.setBuddy(self.name)
        l.addWidget(self.name)

        self.base_msg = '<b>' + _('Currently selected destination:') + '</b>'
        self.dest_label = la = QLabel(self.base_msg)
        la.setWordWrap(True)
        la.setStyleSheet('QLabel { margin-top: 20px }')
        l.addWidget(la)

        l.addStretch()

        state = gprefs.get('toc_edit_splitter_state', None)
        if state is not None:
            sp.restoreState(state)
Пример #13
0
    def __init__(self, parent=None):
        QSplitter.__init__(self, parent)

        self.left, self.right = TextBrowser(self), TextBrowser(self)
        self.addWidget(self.left), self.addWidget(self.right)
Пример #14
0
    def do_layout(self):
        self.central_widget.clear()
        self.tabs = []
        self.labels = []
        sto = QWidget.setTabOrder

        self.on_drag_enter.connect(self.handle_drag_enter)
        self.tabs.append(DragTrackingWidget(self, self.on_drag_enter))
        self.central_widget.addTab(self.tabs[0], _("&Metadata"))
        self.tabs[0].l = QGridLayout()
        self.tabs[0].setLayout(self.tabs[0].l)

        self.tabs.append(QWidget(self))
        self.central_widget.addTab(self.tabs[1], _("&Cover and formats"))
        self.tabs[1].l = QGridLayout()
        self.tabs[1].setLayout(self.tabs[1].l)

        # accept drop events so we can automatically switch to the second tab to
        # drop covers and formats
        self.tabs[0].setAcceptDrops(True)

        # Tab 0
        tab0 = self.tabs[0]

        tl = QGridLayout()
        gb = QGroupBox(_('&Basic metadata'), self.tabs[0])
        self.tabs[0].l.addWidget(gb, 0, 0, 1, 1)
        gb.setLayout(tl)

        self.button_box_layout.insertWidget(1, self.fetch_metadata_button)
        self.button_box_layout.insertWidget(2, self.config_metadata_button)
        sto(self.button_box, self.fetch_metadata_button)
        sto(self.fetch_metadata_button, self.config_metadata_button)
        sto(self.config_metadata_button, self.title)

        def create_row(row, widget, tab_to, button=None, icon=None, span=1):
            ql = BuddyLabel(widget)
            tl.addWidget(ql, row, 1, 1, 1)
            tl.addWidget(widget, row, 2, 1, 1)
            if button is not None:
                tl.addWidget(button, row, 3, span, 1)
                if icon is not None:
                    button.setIcon(QIcon(I(icon)))
            if tab_to is not None:
                if button is not None:
                    sto(widget, button)
                    sto(button, tab_to)
                else:
                    sto(widget, tab_to)

        tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1)
        tl.addWidget(self.manage_authors_button, 2, 0, 1, 1)
        tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1)
        tl.addWidget(self.tags_editor_button, 6, 0, 1, 1)

        create_row(0, self.title, self.title_sort,
                   button=self.deduce_title_sort_button, span=2,
                   icon='auto_author_sort.png')
        create_row(1, self.title_sort, self.authors)
        create_row(2, self.authors, self.author_sort,
                   button=self.deduce_author_sort_button,
                   span=2, icon='auto_author_sort.png')
        create_row(3, self.author_sort, self.series)
        create_row(4, self.series, self.series_index,
                   button=self.clear_series_button, icon='trash.png')
        create_row(5, self.series_index, self.tags)
        create_row(6, self.tags, self.rating, button=self.clear_tags_button)
        create_row(7, self.rating, self.pubdate, button=self.clear_ratings_button)
        create_row(8, self.pubdate, self.publisher,
                   button=self.pubdate.clear_button, icon='trash.png')
        create_row(9, self.publisher, self.languages)
        create_row(10, self.languages, self.timestamp)
        create_row(11, self.timestamp, self.identifiers,
                   button=self.timestamp.clear_button, icon='trash.png')
        create_row(12, self.identifiers, self.comments,
                   button=self.clear_identifiers_button, icon='trash.png')
        sto(self.clear_identifiers_button, self.swap_title_author_button)
        sto(self.swap_title_author_button, self.manage_authors_button)
        sto(self.manage_authors_button, self.tags_editor_button)
        sto(self.tags_editor_button, self.paste_isbn_button)
        tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
                   13, 1, 1 ,1)

        w = getattr(self, 'custom_metadata_widgets_parent', None)
        if w is not None:
            gb = QGroupBox(_('C&ustom metadata'), tab0)
            gbl = QVBoxLayout()
            gb.setLayout(gbl)
            sr = QScrollArea(tab0)
            sr.setWidgetResizable(True)
            sr.setFrameStyle(QFrame.NoFrame)
            sr.setWidget(w)
            gbl.addWidget(sr)
            self.tabs[0].l.addWidget(gb, 0, 1, 1, 1)
            sto(self.identifiers, gb)

        w = QGroupBox(_('&Comments'), tab0)
        sp = QSizePolicy()
        sp.setVerticalStretch(10)
        sp.setHorizontalPolicy(QSizePolicy.Expanding)
        sp.setVerticalPolicy(QSizePolicy.Expanding)
        w.setSizePolicy(sp)
        l = QHBoxLayout()
        w.setLayout(l)
        l.addWidget(self.comments)
        tab0.l.addWidget(w, 1, 0, 1, 2)

        # Tab 1
        tab1 = self.tabs[1]

        wsp = QWidget(tab1)
        wgl = QVBoxLayout()
        wsp.setLayout(wgl)

        # right-hand side of splitter
        gb = QGroupBox(_('Change cover'), tab1)
        l = QGridLayout()
        gb.setLayout(l)
        for i, b in enumerate(self.cover.buttons[:3]):
            l.addWidget(b, 0, i, 1, 1)
            sto(b, self.cover.buttons[i+1])
        hl = QHBoxLayout()
        for b in self.cover.buttons[3:]:
            hl.addWidget(b)
        sto(self.cover.buttons[-2], self.cover.buttons[-1])
        l.addLayout(hl, 1, 0, 1, 3)
        wgl.addWidget(gb)
        wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding,
            QSizePolicy.Expanding))
        wgl.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding,
            QSizePolicy.Expanding))
        wgl.addWidget(self.formats_manager)

        self.splitter = QSplitter(Qt.Horizontal, tab1)
        tab1.l.addWidget(self.splitter)
        self.splitter.addWidget(self.cover)
        self.splitter.addWidget(wsp)

        self.formats_manager.formats.setMaximumWidth(10000)
        self.formats_manager.formats.setIconSize(QSize(64, 64))
Пример #15
0
 def resizeEvent(self, ev):
     self.frame_resized.emit(ev)
     return QSplitter.resizeEvent(self, ev)
Пример #16
0
    def __init__(self, parent, db):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('Check Library -- Problems Found'))
        self.setWindowIcon(QIcon(I('debug.png')))

        self._tl = QHBoxLayout()
        self.setLayout(self._tl)
        self.splitter = QSplitter(self)
        self.left = QWidget(self)
        self.splitter.addWidget(self.left)
        self.helpw = QTextEdit(self)
        self.splitter.addWidget(self.helpw)
        self._tl.addWidget(self.splitter)
        self._layout = QVBoxLayout()
        self.left.setLayout(self._layout)
        self.helpw.setReadOnly(True)
        self.helpw.setText(
            _('''\
        <h1>Help</h1>

        <p>calibre stores the list of your books and their metadata in a
        database. The actual book files and covers are stored as normal
        files in the calibre library folder. The database contains a list of the files
        and covers belonging to each book entry. This tool checks that the
        actual files in the library folder on your computer match the
        information in the database.</p>

        <p>The result of each type of check is shown to the left. The various
        checks are:
        </p>
        <ul>
        <li><b>Invalid titles</b>: These are files and folders appearing
        in the library where books titles should, but that do not have the
        correct form to be a book title.</li>
        <li><b>Extra titles</b>: These are extra files in your calibre
        library that appear to be correctly-formed titles, but have no corresponding
        entries in the database</li>
        <li><b>Invalid authors</b>: These are files appearing
        in the library where only author folders should be.</li>
        <li><b>Extra authors</b>: These are folders in the
        calibre library that appear to be authors but that do not have entries
        in the database</li>
        <li><b>Missing book formats</b>: These are book formats that are in
        the database but have no corresponding format file in the book's folder.
        <li><b>Extra book formats</b>: These are book format files found in
        the book's folder but not in the database.
        <li><b>Unknown files in books</b>: These are extra files in the
        folder of each book that do not correspond to a known format or cover
        file.</li>
        <li><b>Missing cover files</b>: These represent books that are marked
        in the database as having covers but the actual cover files are
        missing.</li>
        <li><b>Cover files not in database</b>: These are books that have
        cover files but are marked as not having covers in the database.</li>
        <li><b>Folder raising exception</b>: These represent folders in the
        calibre library that could not be processed/understood by this
        tool.</li>
        </ul>

        <p>There are two kinds of automatic fixes possible: <i>Delete
        marked</i> and <i>Fix marked</i>.</p>
        <p><i>Delete marked</i> is used to remove extra files/folders/covers that
        have no entries in the database. Check the box next to the item you want
        to delete. Use with caution.</p>

        <p><i>Fix marked</i> is applicable only to covers and missing formats
        (the three lines marked 'fixable'). In the case of missing cover files,
        checking the fixable box and pushing this button will tell calibre that
        there is no cover for all of the books listed. Use this option if you
        are not going to restore the covers from a backup. In the case of extra
        cover files, checking the fixable box and pushing this button will tell
        calibre that the cover files it found are correct for all the books
        listed. Use this when you are not going to delete the file(s). In the
        case of missing formats, checking the fixable box and pushing this
        button will tell calibre that the formats are really gone. Use this if
        you are not going to restore the formats from a backup.</p>

        '''))

        self.log = QTreeWidget(self)
        self.log.itemChanged.connect(self.item_changed)
        self.log.itemExpanded.connect(self.item_expanded_or_collapsed)
        self.log.itemCollapsed.connect(self.item_expanded_or_collapsed)
        self._layout.addWidget(self.log)

        self.check_button = QPushButton(_('&Run the check again'))
        self.check_button.setDefault(False)
        self.check_button.clicked.connect(self.run_the_check)
        self.copy_button = QPushButton(_('Copy &to clipboard'))
        self.copy_button.setDefault(False)
        self.copy_button.clicked.connect(self.copy_to_clipboard)
        self.ok_button = QPushButton(_('&Done'))
        self.ok_button.setDefault(True)
        self.ok_button.clicked.connect(self.accept)
        self.delete_button = QPushButton(_('Delete &marked'))
        self.delete_button.setToolTip(
            _('Delete marked files (checked subitems)'))
        self.delete_button.setDefault(False)
        self.delete_button.clicked.connect(self.delete_marked)
        self.fix_button = QPushButton(_('&Fix marked'))
        self.fix_button.setDefault(False)
        self.fix_button.setEnabled(False)
        self.fix_button.setToolTip(
            _('Fix marked sections (checked fixable items)'))
        self.fix_button.clicked.connect(self.fix_items)
        self.bbox = QDialogButtonBox(self)
        self.bbox.addButton(self.check_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.delete_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.fix_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.copy_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.ok_button, QDialogButtonBox.AcceptRole)

        h = QHBoxLayout()
        ln = QLabel(_('Names to ignore:'))
        h.addWidget(ln)
        self.name_ignores = QLineEdit()
        self.name_ignores.setText(
            db.prefs.get('check_library_ignore_names', ''))
        self.name_ignores.setToolTip(
            _('Enter comma-separated standard file name wildcards, such as synctoy*.dat'
              ))
        ln.setBuddy(self.name_ignores)
        h.addWidget(self.name_ignores)
        le = QLabel(_('Extensions to ignore'))
        h.addWidget(le)
        self.ext_ignores = QLineEdit()
        self.ext_ignores.setText(
            db.prefs.get('check_library_ignore_extensions', ''))
        self.ext_ignores.setToolTip(
            _('Enter comma-separated extensions without a leading dot. Used only in book folders'
              ))
        le.setBuddy(self.ext_ignores)
        h.addWidget(self.ext_ignores)
        self._layout.addLayout(h)

        self._layout.addWidget(self.bbox)
        self.resize(950, 500)
        self.bbox.setEnabled(True)
Пример #17
0
    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
        # note: to support MDI, self.assy would probably need to be a
        # different assembly for each PartWindow.
        # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()

        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwLeftArea (mt and pm) and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3)  # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = LeftFrame(self)
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle(QFrame.Panel | QFrame.Sunken)

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible(0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
        # _pwProjectTabWidget subclasses QTabWidget
        # Note [bruce 070829]: to fix bug 2522 I need to intercept
        # self.pwProjectTabWidget.removeTab, so I made it a subclass of
        # QTabWidget. It needs to know the GLPane, but that's not created
        # yet, so we set it later using KLUGE_setGLPane (below).
        # Note: No parent supplied. Could this be the source of the
        # minor vsplitter resizing problem I was trying to resolve a few
        # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(self.modelTreeTab,
                                       geticon("ui/modeltree/Model_Tree.png"),
                                       "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = ModelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName(
            "propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"), "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.

        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
        # note: our owner (MWsemantics) assumes
        # there is just this one GLPane for assy, and stores it
        # into assy as assy.o and assy.glpane. [bruce 080216 comment]

        # Add what's this text to self.glpane.
        # [bruce 080912 moved this here from part of a method in class GLPane.
        #  In this code's old location, Mark wrote [2007-06-01]: "Problem -
        #  I don't believe this text is processed by fix_whatsthis_text_and_links()
        #  in whatsthis_utilities.py." Now that this code is here, I don't know
        #  whether that's still true. ]
        from ne1_ui.WhatsThisText_for_MainWindow import whats_this_text_for_glpane
        self.glpane.setWhatsThis(whats_this_text_for_glpane())

        # update [re the above comment], bruce 081209:
        # I added the following explicit call of fix_whatsthis_text_and_links,
        # but it doesn't work to replace Ctrl with Cmd on Mac;
        # see today's comment in fix_whatsthis_text_and_links for likely reason.
        # So I will leave this here, but also leave in place the kluges
        # in whats_this_text_for_glpane to do that replacement itself.
        # The wiki help link in this whatsthis text doesn't work,
        # but I guess that is an independent issue, related to lack
        # of use of class QToolBar_WikiHelp or similar code, for GLPane
        # or this class or the main window class.
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self.glpane)  # doesn't yet work

        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
        # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
        # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle(QFrame.Panel | QFrame.Sunken)

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()

        #This widget implementation is subject to heavy revision. The purpose
        #is to implement a NFR that Mark urgently needs : The NFR is: Need a
        #way to quickly find a node in the MT by entering its name.
        #-- Ninad 2008-11-06
        self.pwSpecialDockWidgetInLeftChannel = SelectNodeByNameDockWidget(
            self.glpane.win)
        leftChannelVBoxLayout.addWidget(self.pwSpecialDockWidgetInLeftChannel)

        # See the resizeEvent() docstring for more information about
        # resizeTimer.
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        return
Пример #18
0
    def __init__(self, parent, view, row, link_delegate):
        QDialog.__init__(self, parent)
        self.normal_brush = QBrush(Qt.white)
        self.marked_brush = QBrush(Qt.lightGray)
        self.marked = None
        self.gui = parent
        self.splitter = QSplitter(self)
        self._l = l = QVBoxLayout(self)
        self.setLayout(l)
        l.addWidget(self.splitter)

        self.cover = CoverView(self)
        self.cover.resizeEvent = self.cover_view_resized
        self.cover.cover_changed.connect(self.cover_changed)
        self.cover_pixmap = None
        self.cover.sizeHint = self.details_size_hint
        self.splitter.addWidget(self.cover)

        self.details = QWebView(self)
        self.details.sizeHint = self.details_size_hint
        self.details.page().setLinkDelegationPolicy(
            self.details.page().DelegateAllLinks)
        self.details.linkClicked.connect(self.link_clicked)
        self.css = css()
        self.link_delegate = link_delegate
        self.details.setAttribute(Qt.WA_OpaquePaintEvent, False)
        palette = self.details.palette()
        self.details.setAcceptDrops(False)
        palette.setBrush(QPalette.Base, Qt.transparent)
        self.details.page().setPalette(palette)

        self.c = QWidget(self)
        self.c.l = l2 = QGridLayout(self.c)
        self.c.setLayout(l2)
        l2.addWidget(self.details, 0, 0, 1, -1)
        self.splitter.addWidget(self.c)

        self.fit_cover = QCheckBox(_('Fit &cover within view'), self)
        self.fit_cover.setChecked(
            gprefs.get('book_info_dialog_fit_cover', True))
        l2.addWidget(self.fit_cover, l2.rowCount(), 0, 1, -1)
        self.previous_button = QPushButton(QIcon(I('previous.png')),
                                           _('&Previous'), self)
        self.previous_button.clicked.connect(self.previous)
        l2.addWidget(self.previous_button, l2.rowCount(), 0)
        self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self)
        self.next_button.clicked.connect(self.next)
        l2.addWidget(self.next_button, l2.rowCount() - 1, 1)

        self.view = view
        self.current_row = None
        self.refresh(row)
        self.view.selectionModel().currentChanged.connect(self.slave)
        self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
        self.ns = QShortcut(QKeySequence('Alt+Right'), self)
        self.ns.activated.connect(self.next)
        self.ps = QShortcut(QKeySequence('Alt+Left'), self)
        self.ps.activated.connect(self.previous)
        self.next_button.setToolTip(
            _('Next [%s]') %
            unicode(self.ns.key().toString(QKeySequence.NativeText)))
        self.previous_button.setToolTip(
            _('Previous [%s]') %
            unicode(self.ps.key().toString(QKeySequence.NativeText)))

        geom = QCoreApplication.instance().desktop().availableGeometry(self)
        screen_height = geom.height() - 100
        screen_width = geom.width() - 100
        self.resize(max(int(screen_width / 2), 700), screen_height)
        saved_layout = gprefs.get('book_info_dialog_layout', None)
        if saved_layout is not None:
            try:
                self.restoreGeometry(saved_layout[0])
                self.splitter.restoreState(saved_layout[1])
            except Exception:
                pass
Пример #19
0
class Ui_PartWindow(QWidget):
    """
    The Ui_PartWindow class provides a Part Window UI object composed of three
    primary areas:

    - The "left area" contains the Project TabWidget which contains
    the Model Tree and Property Manager (tabs). Other tabs (widgets)
    can be introduced when needed.

    - The "right area" contains the 3D Graphics Area (i.e. glpane) displaying
    the current part.

    - The "bottom area" lives below the left and right areas, spanning
    the full width of the part window. It can be used whenever a landscape
    layout is needed (i.e. the Sequence Editor). Typically, this area is
    not used.

    A "part window" splitter lives between the left and right areas that
    allow the user to resize the shared area occupied by them. There is no
    splitter between the top and bottom areas.

    This class supports and is limited to a B{Single Document Interface (SDI)}.
    In time, NE1 will migrate to and support a Multiple Document Interface (MDI)
    that will allow multiple project documents (i.e. parts, assemblies,
    simulations, text files, graphs, tables, etc. documents) to be available
    within the common workspace of the NE1 main window.

    @see: U{B{NE1 Main Window Framework}
    <http://www.nanoengineer-1.net/mediawiki/index.php?title=NE1_Main_Window_Framework>}
    """
    widgets = [] # For debugging purposes.

    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
            # note: to support MDI, self.assy would probably need to be a
            # different assembly for each PartWindow.
            # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()

        # Used in expanding or collapsing the Model Tree/ PM area
        self._previous_pwLeftAreaWidth = PM_DEFAULT_WIDTH

        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwProjectTabWidget and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3) # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = QFrame()
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)
        pwLeftArea.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Fixed),
                        QSizePolicy.Policy(QSizePolicy.Expanding)))

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible (0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
           # _pwProjectTabWidget subclasses QTabWidget
           # Note [bruce 070829]: to fix bug 2522 I need to intercept
           # self.pwProjectTabWidget.removeTab, so I made it a subclass of
           # QTabWidget. It needs to know the GLPane, but that's not created
           # yet, so we set it later using KLUGE_setGLPane (below).
           # Note: No parent supplied. Could this be the source of the
           # minor vsplitter resizing problem I was trying to resolve a few
           # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(
            self.modelTreeTab,
            geticon("ui/modeltree/Model_Tree.png"),
            "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = modelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName("propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"),
            "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.
        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
            # note: our owner (MWsemantics) assumes
            # there is just this one GLPane for assy, and stores it
            # into assy as assy.o and assy.glpane. [bruce 080216 comment]
        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
            # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
            # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)
        pwBottomArea.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Expanding),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()

    def updateWindowTitle(self, changed = False):
        #by mark; bruce 050810 revised this in several ways, fixed bug 785
        """
        Update the window title (caption) at the top of the part window.
        Example:  "partname.mmp"

        This implements the standard way most applications indicate that a
        document has unsaved changes. On Mac OS X the close button will have
        a modified look; on other platforms the window title will have
        an '*' (asterisk).

        @note: We'll want to experiment with this to make sure it

        @param changed: If True, the document has unsaved changes.
        @type  changed: boolean

        @see: U{B{windowTitle}<http://doc.trolltech.com/4/qwidget.html#windowTitle-prop>},
              U{B{windowModified}<http://doc.trolltech.com/4/qwidget.html#windowModified-prop>}
        """
        caption_fullpath = env.prefs[captionFullPath_prefs_key]

        try:
            # self.assy.filename is always an empty string, even after a
            # file has been opened with a complete name. Need to ask Bruce
            # about this problem, resulting in a bug (i.e. the window title
            # is always "Untitled". Mark 2008-01-02.
            junk, basename = os.path.split(self.assy.filename)
            assert basename
                # it's normal for this to fail, when there is no file yet

            if caption_fullpath:
                partname = os.path.normpath(self.assy.filename)
                    #fixed bug 453-1 ninad060721
            else:
                partname = basename

        except:
            partname = 'Untitled'

        # If you're wondering about the "[*]" placeholder below, see:
        # http://doc.trolltech.com/4/qwidget.html#windowModified-prop
        self.setWindowTitle(self.trUtf8(partname + '[*]'))
        self.setWindowModified(changed)
        return

    def collapseLeftArea(self):
        """
        Collapse the left area.
        """
        self._previous_pwLeftAreaWidth = self.pwLeftArea.width()
        self.pwLeftArea.setFixedWidth(0)
        self.pwSplitter.setMaximumWidth(self.pwLeftArea.width())

    def expandLeftArea(self):
        """
        Expand the left area.

        @see: L{MWsemantics._showFullScreenCommonCode()} for an example
        showing how it is used.
        """
        if self._previous_pwLeftAreaWidth == 0:
            self._previous_pwLeftAreaWidth = PM_DEFAULT_WIDTH
        self.pwLeftArea.setMaximumWidth(
            self._previous_pwLeftAreaWidth)
        self.pwSplitter.setMaximumWidth(self.pwLeftArea.width())

    def updatePropertyManagerTab(self, tab): #Ninad 061207
        "Update the Properties Manager tab with 'tab' "

        self.parent.glpane.gl_update_confcorner()
            #bruce 070627, since PM affects confcorner appearance

        if self.propertyManagerScrollArea.widget():
            # The following is necessary to get rid of those C object
            # deleted errors (and the resulting bugs)
            lastwidgetobject = self.propertyManagerScrollArea.takeWidget()
            if lastwidgetobject:
                # bruce 071018 revised this code; see my comment on same
                # code in PM_Dialog
                try:
                    lastwidgetobject.update_props_if_needed_before_closing
                except AttributeError:
                    if 1 or debug_flags.atom_debug:
                        msg1 = "Last PropMgr %r doesn't have method" % lastwidgetobject
                        msg2 =" update_props_if_needed_before_closing. That's"
                        msg3 = " OK (for now, only implemented for Plane PM). "
                        msg4 = "Ignoring Exception: "
                        print_compact_traceback(msg1 + msg2 + msg3 + msg4)
                else:
                    lastwidgetobject.update_props_if_needed_before_closing()

            lastwidgetobject.hide()
            # @ ninad 061212 perhaps hiding the widget is not needed

        self.pwProjectTabWidget.removeTab(
            self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea))

        # Set the PropertyManager tab scroll area to the appropriate widget.
        self.propertyManagerScrollArea.setWidget(tab)

        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"),
            "")

        self.pwProjectTabWidget.setCurrentIndex(
            self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea))

    def KLUGE_current_PropertyManager(self):
        #bruce 070627; revised 070829 as part of fixing bug 2523
        """
        Return the current Property Manager widget (whether or not its tab is
        chosen, but only if it has a tab), or None if there is not one.

        @warning: This method's existence (not only its implementation)
        is a kluge, since the right way to access that would be by asking
        the "command sequencer";
        but that's not yet implemented, so this is the best we can do for now.
        Also, it would be better to get the top command and talk to it, not
        its PM (a QWidget). Also, whatever calls this will be making
        assumptions about that PM which are really only the command's business.
        So in short, every call of this is in need of cleanup once we have a
        working "command sequencer". (That's true of many things related to
        PMs, not only this method.)

        @warning: The return values are (presumably) widgets, but they can
        also be mode objects and generator objects, due to excessive use of
        multiple inheritance in the current PM code. So be careful what you
        do with them -- they might have lots of extra methods/attrs,
        and setting your own attrs in them might mess things up.
        """
        res = self.propertyManagerScrollArea.widget()
        if not hasattr(res, 'done_btn'):
            # not sure what widget this is otherwise, but it is a widget
            # (not None) for the default mode, at least on startup, so just
            # return None in this case
            return None
        # Sometimes this PM remains present from a prior command, even when
        # there is no longer a tab for the PM. As part of fixing bug 2523
        # we have to avoid returning it in that case. How we do that is a kluge,
        # but hopefully this entire kluge function can be dispensed with soon.
        # This change also fixes bug 2522 on the Mac (but not on Windows --
        # for that, we needed to intercept removeTab in separate code above).
        index = self.pwProjectTabWidget.indexOf(
            self.propertyManagerScrollArea)
        if index == -1:
            return None
        # Due to bugs in other code, sometimes the PM tab is left in place,
        # though the PM itself is hidden. To avoid finding the PM in that case,
        # also check whether it's hidden. This will fix the CC part of a new bug
        # just reported by Keith in email (when hitting Ok in DNA Gen).
        if res.isHidden(): # probably a QWidget method [bruce 080205 comment]
            return None
        return res

    def dismiss(self):
        self.parent.removePartWindow(self)
    
    def setSplitterPosition(self, leftAreaWidth = PM_DEFAULT_WIDTH): 
        """
        Set the position of the splitter between the left area and graphics area
        so that the starting width of the model tree/property manager is 
        I{leftAreaWdith} pixels wide.
        """
        # This code fixes bug 2424. Mark 2007-06-27.
        #
        # Bug 2424 was difficult to fix for many reasons:
        #
        # - QSplitter.sizes() does not return valid values until the main window
        #   is displayed. Specifically, the value of the second index (the glpane
        #   width) is always zero until the main window is displayed. I suspect 
        #   that the initial size of the glpane (in its constructor) is not set
        #   (or set to 0) and may be contributing to the confusion. This is only
        #   a theory.
        #
        # - QSplitter.setSizes() only works if width1 (the PropMgr width) and
        #   width2 (the glpane width) equal a "magic combined width". See 
        #   more about this in the Method description below.
        #
        # - Qt's QSplitter.moveSplitter() function doesn't work.
        #   
        # Method for bug fix:
        #
        # Get the widths of the left area and the glpane using QSplitter.sizes().
        # These (2) widths add up and equal a total width. You can only
        # feed QSplitter.setSizes() two values that add up to the total width
        # or it will do nothing.
    
        pw = self
        w1, w2 = pw.pwSplitter.sizes()
        total_combined_width = w1 + w2
        new_glpane_width = total_combined_width - leftAreaWidth
        pw.pwSplitter.setSizes([leftAreaWidth, new_glpane_width])
        return
Пример #20
0
class Ui_PartWindow(QWidget):
    """
    The Ui_PartWindow class provides a Part Window UI object composed of three
    primary areas:

    - The "left area" contains the Project TabWidget which contains
    the Model Tree and Property Manager (tabs). Other tabs (widgets)
    can be added as needed.

    - The "right area" contains the Graphics Area (i.e. glpane) displaying
    the current model.

    - The "bottom area" lives below the left and right areas, spanning
    the full width of the part window. It can be used whenever a landscape
    layout is needed (i.e. the Sequence Editor). Typically, this area is
    not used and is hidden by default.

    A "part window" splitter lives between the left and right areas that
    allow the user to resize the shared area occupied by them. There is no
    splitter between the top and bottom areas.

    This class supports and is limited to a B{Single Document Interface (SDI)}.
    In time, NE1 will migrate to and support a Multiple Document Interface (MDI)
    to allow multiple project documents (i.e. parts, assemblies,
    simulations, text files, graphs, tables, etc. documents) to be available
    within the common workspace of the NE1 main window.

    @see: U{B{NE1 Main Window Framework}
    <http://www.nanoengineer-1.net/mediawiki/index.php?title=NE1_Main_Window_Framework>}
    """
    widgets = [] # For debugging purposes.
    splitterPosition = PM_DEFAULT_WIDTH
    _previous_splitterPosition = PM_DEFAULT_WIDTH
        # Used for restoring the splitter position when collapsing/expanding
        # the left area.

    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
            # note: to support MDI, self.assy would probably need to be a
            # different assembly for each PartWindow.
            # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()
        
        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwLeftArea (mt and pm) and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3) # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = LeftFrame(self)
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible (0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
           # _pwProjectTabWidget subclasses QTabWidget
           # Note [bruce 070829]: to fix bug 2522 I need to intercept
           # self.pwProjectTabWidget.removeTab, so I made it a subclass of
           # QTabWidget. It needs to know the GLPane, but that's not created
           # yet, so we set it later using KLUGE_setGLPane (below).
           # Note: No parent supplied. Could this be the source of the
           # minor vsplitter resizing problem I was trying to resolve a few
           # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(
            self.modelTreeTab,
            geticon("ui/modeltree/Model_Tree.png"),
            "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = ModelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName("propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"),
            "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.
        
        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)
       

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
            # note: our owner (MWsemantics) assumes
            # there is just this one GLPane for assy, and stores it
            # into assy as assy.o and assy.glpane. [bruce 080216 comment]
        
        # Add what's this text to self.glpane.
        # [bruce 080912 moved this here from part of a method in class GLPane.
        #  In this code's old location, Mark wrote [2007-06-01]: "Problem -
        #  I don't believe this text is processed by fix_whatsthis_text_and_links()
        #  in whatsthis_utilities.py." Now that this code is here, I don't know
        #  whether that's still true. ]
        from ne1_ui.WhatsThisText_for_MainWindow import whats_this_text_for_glpane
        self.glpane.setWhatsThis( whats_this_text_for_glpane() )
        
        # update [re the above comment], bruce 081209:
        # I added the following explicit call of fix_whatsthis_text_and_links,
        # but it doesn't work to replace Ctrl with Cmd on Mac;
        # see today's comment in fix_whatsthis_text_and_links for likely reason.
        # So I will leave this here, but also leave in place the kluges
        # in whats_this_text_for_glpane to do that replacement itself.
        # The wiki help link in this whatsthis text doesn't work,
        # but I guess that is an independent issue, related to lack
        # of use of class QToolBar_WikiHelp or similar code, for GLPane
        # or this class or the main window class.
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self.glpane) # doesn't yet work
        
        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
            # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
            # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()
        
        #This widget implementation is subject to heavy revision. The purpose
        #is to implement a NFR that Mark urgently needs : The NFR is: Need a 
        #way to quickly find a node in the MT by entering its name.
        #-- Ninad 2008-11-06
        self.pwSpecialDockWidgetInLeftChannel = SelectNodeByNameDockWidget(self.glpane.win)
        leftChannelVBoxLayout.addWidget(self.pwSpecialDockWidgetInLeftChannel)
                
        # See the resizeEvent() docstring for more information about
        # resizeTimer.
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        return
    
    def getLeftChannelDockWidget(self):
        return self.pwSpecialDockWidgetInLeftChannel

    def updateWindowTitle(self, changed = False):
        #by mark; bruce 050810 revised this in several ways, fixed bug 785
        """
        Update the window title (caption) at the top of the part window.
        Example:  "partname.mmp"

        This implements the standard way most applications indicate that a
        document has unsaved changes. On Mac OS X the close button will have
        a modified look; on other platforms the window title will have
        an '*' (asterisk).

        @note: We'll want to experiment with this to make sure it

        @param changed: If True, the document has unsaved changes.
        @type  changed: boolean

        @see: U{B{windowTitle}<http://doc.trolltech.com/4/qwidget.html#windowTitle-prop>},
              U{B{windowModified}<http://doc.trolltech.com/4/qwidget.html#windowModified-prop>}
        """
        # WARNING: there is mostly-duplicated code in this method and in
        # MWsemantics.update_mainwindow_caption. See the comment in that
        # method for more info and todos. [bruce 081227 comment]
        caption_fullpath = env.prefs[captionFullPath_prefs_key]

        partname = "Untitled" # fallback value if no file yet
        if self.assy.filename: #bruce 081227 cleanup: try -> if, etc
            # self.assy.filename is always an empty string, even after a
            # file has been opened with a complete name. Need to ask Bruce
            # about this problem, resulting in a bug (i.e. the window title
            # is always "Untitled". Mark 2008-01-02.
            junk, basename = os.path.split(self.assy.filename)
            if basename:
                if caption_fullpath:
                    partname = os.path.normpath(self.assy.filename)
                        #fixed bug 453-1 ninad060721
                else:
                    partname = basename

        # WARNING: the following code differs in the two versions
        # of this routine.
        # The "[*]" placeholder below is modified or removed by Qt; see:
        # http://doc.trolltech.com/4/qwidget.html#windowModified-prop
        self.setWindowTitle(self.trUtf8(partname + '[*]'))
        self.setWindowModified(changed) # replaces '[*]' by '' or '*'
        return

    def collapseLeftArea(self, hideLeftArea = True):
        """
        Make the left area collapsible (via the splitter). The left area
        will be hidden (collapsed,actually) if I{hideLeftArea} is True 
        (the default).
        """
        self._previous_splitterPosition = self.pwLeftArea.width()
        if hideLeftArea:
            self.pwSplitter.setCollapsible(0, True)
            self.setSplitterPosition(pos = 0)
        return

    def expandLeftArea(self):
        """
        Expand the left area.

        @see: L{MWsemantics._showFullScreenCommonCode()} for an example
        showing how it is used.
        """
        self.setSplitterPosition(pos = self._previous_splitterPosition)
        self.pwSplitter.setCollapsible(0, False)
        self.pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        self.pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)
        return

    def updatePropertyManagerTab(self, tab): #Ninad 061207
        "Update the Properties Manager tab with 'tab' "

        self.parent.glpane.gl_update_confcorner()
            #bruce 070627, since PM affects confcorner appearance

        if self.propertyManagerScrollArea.widget():
            # The following is necessary to get rid of those C object
            # deleted errors (and the resulting bugs)
            lastwidgetobject = self.propertyManagerScrollArea.takeWidget()
            if lastwidgetobject:
                # bruce 071018 revised this code; see my comment on same
                # code in PM_Dialog
                try:
                    lastwidgetobject.update_props_if_needed_before_closing
                except AttributeError:
                    if 1 or debug_flags.atom_debug:
                        msg1 = "Last PropMgr %r doesn't have method" % lastwidgetobject
                        msg2 =" update_props_if_needed_before_closing. That's"
                        msg3 = " OK (for now, only implemented for Plane PM). "
                        msg4 = "Ignoring Exception: "
                        print_compact_traceback(msg1 + msg2 + msg3 + msg4)
                else:
                    lastwidgetobject.update_props_if_needed_before_closing()

            lastwidgetobject.hide()
            # @ ninad 061212 perhaps hiding the widget is not needed

        self.pwProjectTabWidget.removeTab(
            self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea))

        # Set the PropertyManager tab scroll area to the appropriate widget.
        self.propertyManagerScrollArea.setWidget(tab)

        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"),
            "")

        self.pwProjectTabWidget.setCurrentIndex(
            self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea))
        return

    def KLUGE_current_PropertyManager(self):
        #bruce 070627; revised 070829 as part of fixing bug 2523
        """
        Return the current Property Manager widget (whether or not its tab is
        chosen, but only if it has a tab), or None if there is not one.

        @warning: This method's existence (not only its implementation)
        is a kluge, since the right way to access that would be by asking
        the "command sequencer";
        but that's not yet implemented, so this is the best we can do for now.
        Also, it would be better to get the top command and talk to it, not
        its PM (a QWidget). Also, whatever calls this will be making
        assumptions about that PM which are really only the command's business.
        So in short, every call of this is in need of cleanup once we have a
        working "command sequencer". (That's true of many things related to
        PMs, not only this method.)

        @warning: The return values are (presumably) widgets, but they can
        also be mode objects and generator objects, due to excessive use of
        multiple inheritance in the current PM code. So be careful what you
        do with them -- they might have lots of extra methods/attrs,
        and setting your own attrs in them might mess things up.
        """
        res = self.propertyManagerScrollArea.widget()
        if not hasattr(res, 'done_btn'):
            # not sure what widget this is otherwise, but it is a widget
            # (not None) for the default mode, at least on startup, so just
            # return None in this case
            return None
        # Sometimes this PM remains present from a prior command, even when
        # there is no longer a tab for the PM. As part of fixing bug 2523
        # we have to avoid returning it in that case. How we do that is a kluge,
        # but hopefully this entire kluge function can be dispensed with soon.
        # This change also fixes bug 2522 on the Mac (but not on Windows --
        # for that, we needed to intercept removeTab in separate code above).
        index = self.pwProjectTabWidget.indexOf(
            self.propertyManagerScrollArea)
        if index == -1:
            return None
        # Due to bugs in other code, sometimes the PM tab is left in place,
        # though the PM itself is hidden. To avoid finding the PM in that case,
        # also check whether it's hidden. This will fix the CC part of a new bug
        # just reported by Keith in email (when hitting Ok in DNA Gen).
        if res.isHidden(): # probably a QWidget method [bruce 080205 comment]
            return None
        return res

    def dismiss(self):
        self.parent.removePartWindow(self)
        return
    
    def setSplitterPosition(self, pos = PM_DEFAULT_WIDTH, setDefault = True): 
        """
        Set the position of the splitter between the left area and graphics area
        so that the width of the container holding the model tree (and 
        property manager) is I{pos} pixels wide.
        
        @param pos: The splitter position (in pixel units).
        @type  pos: int
        
        @param setDefault: If True (the default), I{pos} becomes the new default
                           position.
        @type  setDefault: boolean
        """
        self.pwSplitter.moveSplitter(pos, 1)
        if _DEBUG:
            print "New Splitter Position: %d (setDefault=%d)" \
                  % (pos, setDefault)
        if setDefault:
            self.splitterPosition = pos
        return
    
    def resizeEvent(self, event):
        """
        This reimplementation of QWidget.resizeEvent is here to deal with the
        undesired behavior of the splitter while resizing the part window.
        Normally, the splitter will drift back and forth while resizing
        the part window. This forces the splitter to stay fixed during
        resize operations.
        """
        # When self.resizeTimer.isActive() = True, the partwindow is being
        # resized. This is checked by the resizeEvent handler in LeftFrame
        # to determine if the splitter is being moved by the user or 
        # programmably by self's resizeEvent.
        if self.resizeTimer.isActive():
            self.resizeTimer.stop() # Stop the timer.
        self.resizeTimer.start( 500 )  # (Re)strand a .5 second singleshot timer.
        self.setSplitterPosition(self.splitterPosition, setDefault = False)
        QWidget.resizeEvent(self, event)
        return
Пример #21
0
class CheckLibraryDialog(QDialog):

    def __init__(self, parent, db):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('Check Library -- Problems Found'))
        self.setWindowIcon(QIcon(I('debug.png')))

        self._tl = QHBoxLayout()
        self.setLayout(self._tl)
        self.splitter = QSplitter(self)
        self.left = QWidget(self)
        self.splitter.addWidget(self.left)
        self.helpw = QTextEdit(self)
        self.splitter.addWidget(self.helpw)
        self._tl.addWidget(self.splitter)
        self._layout = QVBoxLayout()
        self.left.setLayout(self._layout)
        self.helpw.setReadOnly(True)
        self.helpw.setText(_('''\
        <h1>Help</h1>

        <p>calibre stores the list of your books and their metadata in a
        database. The actual book files and covers are stored as normal
        files in the calibre library folder. The database contains a list of the files
        and covers belonging to each book entry. This tool checks that the
        actual files in the library folder on your computer match the
        information in the database.</p>

        <p>The result of each type of check is shown to the left. The various
        checks are:
        </p>
        <ul>
        <li><b>Invalid titles</b>: These are files and folders appearing
        in the library where books titles should, but that do not have the
        correct form to be a book title.</li>
        <li><b>Extra titles</b>: These are extra files in your calibre
        library that appear to be correctly-formed titles, but have no corresponding
        entries in the database</li>
        <li><b>Invalid authors</b>: These are files appearing
        in the library where only author folders should be.</li>
        <li><b>Extra authors</b>: These are folders in the
        calibre library that appear to be authors but that do not have entries
        in the database</li>
        <li><b>Missing book formats</b>: These are book formats that are in
        the database but have no corresponding format file in the book's folder.
        <li><b>Extra book formats</b>: These are book format files found in
        the book's folder but not in the database.
        <li><b>Unknown files in books</b>: These are extra files in the
        folder of each book that do not correspond to a known format or cover
        file.</li>
        <li><b>Missing cover files</b>: These represent books that are marked
        in the database as having covers but the actual cover files are
        missing.</li>
        <li><b>Cover files not in database</b>: These are books that have
        cover files but are marked as not having covers in the database.</li>
        <li><b>Folder raising exception</b>: These represent folders in the
        calibre library that could not be processed/understood by this
        tool.</li>
        </ul>

        <p>There are two kinds of automatic fixes possible: <i>Delete
        marked</i> and <i>Fix marked</i>.</p>
        <p><i>Delete marked</i> is used to remove extra files/folders/covers that
        have no entries in the database. Check the box next to the item you want
        to delete. Use with caution.</p>

        <p><i>Fix marked</i> is applicable only to covers and missing formats
        (the three lines marked 'fixable'). In the case of missing cover files,
        checking the fixable box and pushing this button will tell calibre that
        there is no cover for all of the books listed. Use this option if you
        are not going to restore the covers from a backup. In the case of extra
        cover files, checking the fixable box and pushing this button will tell
        calibre that the cover files it found are correct for all the books
        listed. Use this when you are not going to delete the file(s). In the
        case of missing formats, checking the fixable box and pushing this
        button will tell calibre that the formats are really gone. Use this if
        you are not going to restore the formats from a backup.</p>

        '''))

        self.log = QTreeWidget(self)
        self.log.itemChanged.connect(self.item_changed)
        self.log.itemExpanded.connect(self.item_expanded_or_collapsed)
        self.log.itemCollapsed.connect(self.item_expanded_or_collapsed)
        self._layout.addWidget(self.log)

        self.check_button = QPushButton(_('&Run the check again'))
        self.check_button.setDefault(False)
        self.check_button.clicked.connect(self.run_the_check)
        self.copy_button = QPushButton(_('Copy &to clipboard'))
        self.copy_button.setDefault(False)
        self.copy_button.clicked.connect(self.copy_to_clipboard)
        self.ok_button = QPushButton(_('&Done'))
        self.ok_button.setDefault(True)
        self.ok_button.clicked.connect(self.accept)
        self.delete_button = QPushButton(_('Delete &marked'))
        self.delete_button.setToolTip(_('Delete marked files (checked subitems)'))
        self.delete_button.setDefault(False)
        self.delete_button.clicked.connect(self.delete_marked)
        self.fix_button = QPushButton(_('&Fix marked'))
        self.fix_button.setDefault(False)
        self.fix_button.setEnabled(False)
        self.fix_button.setToolTip(_('Fix marked sections (checked fixable items)'))
        self.fix_button.clicked.connect(self.fix_items)
        self.bbox = QDialogButtonBox(self)
        self.bbox.addButton(self.check_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.delete_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.fix_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.copy_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.ok_button, QDialogButtonBox.AcceptRole)

        h = QHBoxLayout()
        ln = QLabel(_('Names to ignore:'))
        h.addWidget(ln)
        self.name_ignores = QLineEdit()
        self.name_ignores.setText(db.prefs.get('check_library_ignore_names', ''))
        self.name_ignores.setToolTip(
            _('Enter comma-separated standard file name wildcards, such as synctoy*.dat'))
        ln.setBuddy(self.name_ignores)
        h.addWidget(self.name_ignores)
        le = QLabel(_('Extensions to ignore'))
        h.addWidget(le)
        self.ext_ignores = QLineEdit()
        self.ext_ignores.setText(db.prefs.get('check_library_ignore_extensions', ''))
        self.ext_ignores.setToolTip(
            _('Enter comma-separated extensions without a leading dot. Used only in book folders'))
        le.setBuddy(self.ext_ignores)
        h.addWidget(self.ext_ignores)
        self._layout.addLayout(h)

        self._layout.addWidget(self.bbox)
        self.resize(950, 500)
        self.bbox.setEnabled(True)

    def do_exec(self):
        self.run_the_check()

        probs = 0
        for c in self.problem_count:
            probs += self.problem_count[c]
        if probs == 0:
            return False
        self.exec_()
        return True

    def accept(self):
        self.db.prefs['check_library_ignore_extensions'] = \
                                            unicode(self.ext_ignores.text())
        self.db.prefs['check_library_ignore_names'] = \
                                            unicode(self.name_ignores.text())
        QDialog.accept(self)

    def box_to_list(self, txt):
        return [f.strip() for f in txt.split(',') if f.strip()]

    def run_the_check(self):
        checker = CheckLibrary(self.db.library_path, self.db)
        checker.scan_library(self.box_to_list(unicode(self.name_ignores.text())),
                             self.box_to_list(unicode(self.ext_ignores.text())))

        plaintext = []

        def builder(tree, checker, check):
            attr, h, checkable, fixable = check
            list = getattr(checker, attr, None)
            if list is None:
                self.problem_count[attr] = 0
                return
            else:
                self.problem_count[attr] = len(list)

            tl = Item()
            tl.setText(0, h)
            if fixable and list:
                tl.setText(1, _('(fixable)'))
                tl.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
                tl.setCheckState(1, False)
            self.top_level_items[attr] = tl

            for problem in list:
                it = Item()
                if checkable:
                    it.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
                    it.setCheckState(1, False)
                else:
                    it.setFlags(Qt.ItemIsEnabled)
                it.setText(0, problem[0])
                it.setData(0, Qt.UserRole, problem[2])
                it.setText(1, problem[1])
                tl.addChild(it)
                self.all_items.append(it)
                plaintext.append(','.join([h, problem[0], problem[1]]))
            tree.addTopLevelItem(tl)

        t = self.log
        t.clear()
        t.setColumnCount(2)
        t.setHeaderLabels([_('Name'), _('Path from library')])
        self.all_items = []
        self.top_level_items = {}
        self.problem_count = {}
        for check in CHECKS:
            builder(t, checker, check)

        t.resizeColumnToContents(0)
        t.resizeColumnToContents(1)
        self.delete_button.setEnabled(False)
        self.text_results = '\n'.join(plaintext)

    def item_expanded_or_collapsed(self, item):
        self.log.resizeColumnToContents(0)
        self.log.resizeColumnToContents(1)

    def item_changed(self, item, column):
        self.fix_button.setEnabled(False)
        for it in self.top_level_items.values():
            if it.checkState(1):
                self.fix_button.setEnabled(True)

        self.delete_button.setEnabled(False)
        for it in self.all_items:
            if it.checkState(1):
                self.delete_button.setEnabled(True)
                return

    def delete_marked(self):
        if not confirm('<p>'+_('The marked files and folders will be '
               '<b>permanently deleted</b>. Are you sure?')
               +'</p>', 'check_library_editor_delete', self):
            return

        # Sort the paths in reverse length order so that we can be sure that
        # if an item is in another item, the sub-item will be deleted first.
        items = sorted(self.all_items,
                       key=lambda x: len(x.text(1)),
                       reverse=True)
        for it in items:
            if it.checkState(1):
                try:
                    p = os.path.join(self.db.library_path ,unicode(it.text(1)))
                    if os.path.isdir(p):
                        delete_tree(p)
                    else:
                        delete_file(p)
                except:
                    prints('failed to delete',
                            os.path.join(self.db.library_path,
                                unicode(it.text(1))))
        self.run_the_check()

    def fix_missing_formats(self):
        tl = self.top_level_items['missing_formats']
        child_count = tl.childCount()
        for i in range(0, child_count):
            item = tl.child(i)
            id = item.data(0, Qt.UserRole).toInt()[0]
            all = self.db.formats(id, index_is_id=True, verify_formats=False)
            all = set([f.strip() for f in all.split(',')]) if all else set()
            valid = self.db.formats(id, index_is_id=True, verify_formats=True)
            valid = set([f.strip() for f in valid.split(',')]) if valid else set()
            for fmt in all-valid:
                self.db.remove_format(id, fmt, index_is_id=True, db_only=True)

    def fix_missing_covers(self):
        tl = self.top_level_items['missing_covers']
        child_count = tl.childCount()
        for i in range(0, child_count):
            item = tl.child(i)
            id = item.data(0, Qt.UserRole).toInt()[0]
            self.db.set_has_cover(id, False)

    def fix_extra_covers(self):
        tl = self.top_level_items['extra_covers']
        child_count = tl.childCount()
        for i in range(0, child_count):
            item = tl.child(i)
            id = item.data(0, Qt.UserRole).toInt()[0]
            self.db.set_has_cover(id, True)

    def fix_items(self):
        for check in CHECKS:
            attr = check[0]
            fixable = check[3]
            tl = self.top_level_items[attr]
            if fixable and tl.checkState(1):
                func = getattr(self, 'fix_' + attr, None)
                if func is not None and callable(func):
                    func()
        self.run_the_check()

    def copy_to_clipboard(self):
        QApplication.clipboard().setText(self.text_results)
Пример #22
0
 def resizeEvent(self, ev):
     self.frame_resized.emit(ev)
     return QSplitter.resizeEvent(self, ev)
Пример #23
0
    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
            # note: to support MDI, self.assy would probably need to be a
            # different assembly for each PartWindow.
            # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()
        
        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwLeftArea (mt and pm) and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3) # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = LeftFrame(self)
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible (0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
           # _pwProjectTabWidget subclasses QTabWidget
           # Note [bruce 070829]: to fix bug 2522 I need to intercept
           # self.pwProjectTabWidget.removeTab, so I made it a subclass of
           # QTabWidget. It needs to know the GLPane, but that's not created
           # yet, so we set it later using KLUGE_setGLPane (below).
           # Note: No parent supplied. Could this be the source of the
           # minor vsplitter resizing problem I was trying to resolve a few
           # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(
            self.modelTreeTab,
            geticon("ui/modeltree/Model_Tree.png"),
            "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = ModelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName("propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"),
            "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.
        
        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)
       

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
            # note: our owner (MWsemantics) assumes
            # there is just this one GLPane for assy, and stores it
            # into assy as assy.o and assy.glpane. [bruce 080216 comment]
        
        # Add what's this text to self.glpane.
        # [bruce 080912 moved this here from part of a method in class GLPane.
        #  In this code's old location, Mark wrote [2007-06-01]: "Problem -
        #  I don't believe this text is processed by fix_whatsthis_text_and_links()
        #  in whatsthis_utilities.py." Now that this code is here, I don't know
        #  whether that's still true. ]
        from ne1_ui.WhatsThisText_for_MainWindow import whats_this_text_for_glpane
        self.glpane.setWhatsThis( whats_this_text_for_glpane() )
        
        # update [re the above comment], bruce 081209:
        # I added the following explicit call of fix_whatsthis_text_and_links,
        # but it doesn't work to replace Ctrl with Cmd on Mac;
        # see today's comment in fix_whatsthis_text_and_links for likely reason.
        # So I will leave this here, but also leave in place the kluges
        # in whats_this_text_for_glpane to do that replacement itself.
        # The wiki help link in this whatsthis text doesn't work,
        # but I guess that is an independent issue, related to lack
        # of use of class QToolBar_WikiHelp or similar code, for GLPane
        # or this class or the main window class.
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self.glpane) # doesn't yet work
        
        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
            # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
            # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()
        
        #This widget implementation is subject to heavy revision. The purpose
        #is to implement a NFR that Mark urgently needs : The NFR is: Need a 
        #way to quickly find a node in the MT by entering its name.
        #-- Ninad 2008-11-06
        self.pwSpecialDockWidgetInLeftChannel = SelectNodeByNameDockWidget(self.glpane.win)
        leftChannelVBoxLayout.addWidget(self.pwSpecialDockWidgetInLeftChannel)
                
        # See the resizeEvent() docstring for more information about
        # resizeTimer.
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        return
Пример #24
0
    def __init__(self, parent, hide_on_close=False):
        QMainWindow.__init__(self, parent)
        self.setWindowIcon(pixmaps.tigger_starface.icon())
        self._currier = PersistentCurrier()
        self.hide()
        # init column constants
        for icol, col in enumerate(self.ViewModelColumns):
            setattr(self, "Column%s" % col.capitalize(), icol)
        # init GUI
        self.setWindowTitle("Tigger")
        # self.setIcon(pixmaps.purr_logo.pm())
        cw = QWidget(self)
        self.setCentralWidget(cw)
        cwlo = QVBoxLayout(cw)
        cwlo.setMargin(5)
        # make splitter
        spl1 = self._splitter1 = QSplitter(Qt.Vertical, cw)
        spl1.setOpaqueResize(False)
        cwlo.addWidget(spl1)
        # Create listview of LSM entries
        self.tw = SkyModelTreeWidget(spl1)
        self.tw.hide()

        # split bottom pane
        spl2 = self._splitter2 = QSplitter(Qt.Horizontal, spl1)
        spl2.setOpaqueResize(False)
        self._skyplot_stack = QWidget(spl2)
        self._skyplot_stack_lo = QVBoxLayout(self._skyplot_stack)
        self._skyplot_stack_lo.setContentsMargins(0, 0, 0, 0)

        # add plot
        self.skyplot = SkyModelPlotter(self._skyplot_stack, self)
        self.skyplot.resize(128, 128)
        self.skyplot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        self._skyplot_stack_lo.addWidget(self.skyplot, 1000)
        self.skyplot.hide()
        QObject.connect(self.skyplot, SIGNAL("imagesChanged"), self._imagesChanged)
        QObject.connect(self.skyplot, SIGNAL("showMessage"), self.showMessage)
        QObject.connect(self.skyplot, SIGNAL("showErrorMessage"), self.showErrorMessage)

        self._grouptab_stack = QWidget(spl2)
        self._grouptab_stack_lo = lo = QVBoxLayout(self._grouptab_stack)
        self._grouptab_stack_lo.setContentsMargins(0, 0, 0, 0)
        # add groupings table
        self.grouptab = ModelGroupsTable(self._grouptab_stack)
        self.grouptab.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        QObject.connect(self, SIGNAL("hasSkyModel"), self.grouptab.setEnabled)
        lo.addWidget(self.grouptab, 1000)
        lo.addStretch(1)
        self.grouptab.hide()

        # add image controls -- parentless for now (setLayout will reparent them anyway)
        self.imgman = ImageManager()
        self.skyplot.setImageManager(self.imgman)
        QObject.connect(self.imgman, SIGNAL("imagesChanged"), self._imagesChanged)
        QObject.connect(self.imgman, SIGNAL("showMessage"), self.showMessage)
        QObject.connect(self.imgman, SIGNAL("showErrorMessage"), self.showErrorMessage)

        # enable status line
        self.statusBar().show()
        # Create and populate main menu
        menubar = self.menuBar()
        # File menu
        file_menu = menubar.addMenu("&File")
        qa_open = file_menu.addAction("&Open model...", self._openFileCallback, Qt.CTRL + Qt.Key_O)
        qa_merge = file_menu.addAction("&Merge in model...", self._mergeFileCallback, Qt.CTRL + Qt.SHIFT + Qt.Key_O)
        QObject.connect(self, SIGNAL("hasSkyModel"), qa_merge.setEnabled)
        file_menu.addSeparator()
        qa_save = file_menu.addAction("&Save model", self.saveFile, Qt.CTRL + Qt.Key_S)
        QObject.connect(self, SIGNAL("isUpdated"), qa_save.setEnabled)
        qa_save_as = file_menu.addAction("Save model &as...", self.saveFileAs)
        QObject.connect(self, SIGNAL("hasSkyModel"), qa_save_as.setEnabled)
        qa_save_selection_as = file_menu.addAction("Save selection as...", self.saveSelectionAs)
        QObject.connect(self, SIGNAL("hasSelection"), qa_save_selection_as.setEnabled)
        file_menu.addSeparator()
        qa_close = file_menu.addAction("&Close model", self.closeFile, Qt.CTRL + Qt.Key_W)
        QObject.connect(self, SIGNAL("hasSkyModel"), qa_close.setEnabled)
        qa_quit = file_menu.addAction("Quit", self.close, Qt.CTRL + Qt.Key_Q)

        # Image menu
        menubar.addMenu(self.imgman.getMenu())
        # Plot menu
        menubar.addMenu(self.skyplot.getMenu())

        # LSM Menu
        em = QMenu("&LSM", self)
        self._qa_em = menubar.addMenu(em)
        self._qa_em.setVisible(False)
        QObject.connect(self, SIGNAL("hasSkyModel"), self._qa_em.setVisible)
        self._column_view_menu = QMenu("&Show columns", self)
        self._qa_cv_menu = em.addMenu(self._column_view_menu)
        em.addSeparator()
        em.addAction("Select &all", self._selectAll, Qt.CTRL + Qt.Key_A)
        em.addAction("&Invert selection", self._selectInvert, Qt.CTRL + Qt.Key_I)
        em.addAction("Select b&y attribute...", self._showSourceSelector, Qt.CTRL + Qt.Key_Y)
        em.addSeparator()
        qa_add_tag = em.addAction("&Tag selection...", self.addTagToSelection, Qt.CTRL + Qt.Key_T)
        QObject.connect(self, SIGNAL("hasSelection"), qa_add_tag.setEnabled)
        qa_del_tag = em.addAction("&Untag selection...", self.removeTagsFromSelection, Qt.CTRL + Qt.Key_U)
        QObject.connect(self, SIGNAL("hasSelection"), qa_del_tag.setEnabled)
        qa_del_sel = em.addAction("&Delete selection", self._deleteSelection)
        QObject.connect(self, SIGNAL("hasSelection"), qa_del_sel.setEnabled)

        # Tools menu
        tm = self._tools_menu = QMenu("&Tools", self)
        self._qa_tm = menubar.addMenu(tm)
        self._qa_tm.setVisible(False)
        QObject.connect(self, SIGNAL("hasSkyModel"), self._qa_tm.setVisible)

        # Help menu
        menubar.addSeparator()
        hm = self._help_menu = menubar.addMenu("&Help")
        hm.addAction("&About...", self._showAboutDialog)
        self._about_dialog = None

        # message handlers
        self.qerrmsg = QErrorMessage(self)

        # set initial state
        self.setAcceptDrops(True)
        self.model = None
        self.filename = None
        self._display_filename = None
        self._open_file_dialog = self._merge_file_dialog = self._save_as_dialog = self._save_sel_as_dialog = self._open_image_dialog = None
        self.emit(SIGNAL("isUpdated"), False)
        self.emit(SIGNAL("hasSkyModel"), False)
        self.emit(SIGNAL("hasSelection"), False)
        self._exiting = False

        # set initial layout
        self._current_layout = None
        self.setLayout(self.LayoutEmpty)
        dprint(1, "init complete")
Пример #25
0
    def setup_ui(self):
        self.block_show = False
        self.properties = []
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)
        h = QHBoxLayout()
        l.addLayout(h)
        self.la = la = QLabel(_('&Edit theme:'))
        h.addWidget(la)
        self.theme = t = QComboBox(self)
        la.setBuddy(t)
        t.addItems(sorted(custom_theme_names()))
        t.setMinimumWidth(200)
        if t.count() > 0:
            t.setCurrentIndex(0)
        t.currentIndexChanged[int].connect(self.show_theme)
        h.addWidget(t)

        self.add_button = b = QPushButton(QIcon(I('plus.png')),
                                          _('Add &new theme'), self)
        b.clicked.connect(self.create_new_theme)
        h.addWidget(b)

        self.remove_button = b = QPushButton(QIcon(I('minus.png')),
                                             _('&Remove theme'), self)
        b.clicked.connect(self.remove_theme)
        h.addWidget(b)
        h.addStretch(1)

        self.scroll = s = QScrollArea(self)
        self.w = w = QWidget(self)
        s.setWidget(w), s.setWidgetResizable(True)
        self.cl = cl = QVBoxLayout()
        w.setLayout(cl)

        from calibre.gui2.tweak_book.editor.text import TextEdit
        self.preview = p = TextEdit(self, expected_geometry=(73, 50))
        p.load_text(
            textwrap.dedent(
                _('''\
            <h2>Creating a custom theme</h2>

            <p id="attribute" lang="und">You can create a custom syntax highlighting
            theme, with your own colors and font styles. The most important
            types of highlighting rules are described below. Note that not
            every rule supports every kind of customization, for example,
            changing font or underline styles for the <code>Cursor</code> rule
            does not have any effect as that rule is used only for the color of
            the blinking cursor.</p>

            <p>As you make changes to your them on the left, the changes will
            be reflected live in this panel.</p>

            <p xml:lang="und">
            {0}
                The most important rule. Sets the
                foreground and background colors for the editor as well as the
                style of "normal" text, that is, text that does not match any
                special syntax.

            {1}
                Defines the colors for text selected by the mouse.

            {2}
                Defines the color for the line containing the cursor.

            {3}
                Defines the colors for the line numbers on the left.

            {4}
                Defines the colors for matching tags in HTML and matching
                braces in CSS.

            {5}
                Used for highlighting tags in HTML

            {6}
                Used for highlighting attributes in HTML

            {7}
                Tag names in HTML

            {8}
                Namespace prefixes in XML and constants in CSS

            {9}
                Non-breaking spaces/hyphens in HTML

            {10}
                Syntax errors such as <this <>

            {11}
                Misspelled words such as <span lang="en">thisword</span>

            {12}
                Comments like <!-- this one -->
            </p>

            <style type="text/css">
            /* Some CSS so you can see how the highlighting rules affect it */

            p.someclass {{
                font-family: serif;
                font-size: 12px;
                line-height: 1.2;
            }}
            </style>
            ''')).format(*[
                    '<b>%s</b>' % x
                    for x in ('Normal', 'Visual', 'CursorLine', 'LineNr',
                              'MatchParen', 'Function', 'Type', 'Statement',
                              'Constant', 'SpecialCharacter', 'Error',
                              'SpellError', 'Comment')
                ]))
        p.setMaximumWidth(p.size_hint.width() + 5)
        s.setMinimumWidth(600)
        self.splitter = sp = QSplitter(self)
        l.addWidget(sp)
        sp.addWidget(s), sp.addWidget(p)

        self.bb.clear()
        self.bb.addButton(self.bb.Close)
        l.addWidget(self.bb)

        if self.theme.count() > 0:
            self.show_theme()
Пример #26
0
class BookInfo(QDialog):

    closed = pyqtSignal(object)

    def __init__(self, parent, view, row, link_delegate):
        QDialog.__init__(self, parent)
        self.normal_brush = QBrush(Qt.white)
        self.marked_brush = QBrush(Qt.lightGray)
        self.marked = None
        self.gui = parent
        self.splitter = QSplitter(self)
        self._l = l = QVBoxLayout(self)
        self.setLayout(l)
        l.addWidget(self.splitter)

        self.cover = CoverView(self)
        self.cover.resizeEvent = self.cover_view_resized
        self.cover.cover_changed.connect(self.cover_changed)
        self.cover_pixmap = None
        self.cover.sizeHint = self.details_size_hint
        self.splitter.addWidget(self.cover)

        self.details = QWebView(self)
        self.details.sizeHint = self.details_size_hint
        self.details.page().setLinkDelegationPolicy(
            self.details.page().DelegateAllLinks)
        self.details.linkClicked.connect(self.link_clicked)
        self.css = css()
        self.link_delegate = link_delegate
        self.details.setAttribute(Qt.WA_OpaquePaintEvent, False)
        palette = self.details.palette()
        self.details.setAcceptDrops(False)
        palette.setBrush(QPalette.Base, Qt.transparent)
        self.details.page().setPalette(palette)

        self.c = QWidget(self)
        self.c.l = l2 = QGridLayout(self.c)
        self.c.setLayout(l2)
        l2.addWidget(self.details, 0, 0, 1, -1)
        self.splitter.addWidget(self.c)

        self.fit_cover = QCheckBox(_('Fit &cover within view'), self)
        self.fit_cover.setChecked(
            gprefs.get('book_info_dialog_fit_cover', True))
        l2.addWidget(self.fit_cover, l2.rowCount(), 0, 1, -1)
        self.previous_button = QPushButton(QIcon(I('previous.png')),
                                           _('&Previous'), self)
        self.previous_button.clicked.connect(self.previous)
        l2.addWidget(self.previous_button, l2.rowCount(), 0)
        self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self)
        self.next_button.clicked.connect(self.next)
        l2.addWidget(self.next_button, l2.rowCount() - 1, 1)

        self.view = view
        self.current_row = None
        self.refresh(row)
        self.view.selectionModel().currentChanged.connect(self.slave)
        self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
        self.ns = QShortcut(QKeySequence('Alt+Right'), self)
        self.ns.activated.connect(self.next)
        self.ps = QShortcut(QKeySequence('Alt+Left'), self)
        self.ps.activated.connect(self.previous)
        self.next_button.setToolTip(
            _('Next [%s]') %
            unicode(self.ns.key().toString(QKeySequence.NativeText)))
        self.previous_button.setToolTip(
            _('Previous [%s]') %
            unicode(self.ps.key().toString(QKeySequence.NativeText)))

        geom = QCoreApplication.instance().desktop().availableGeometry(self)
        screen_height = geom.height() - 100
        screen_width = geom.width() - 100
        self.resize(max(int(screen_width / 2), 700), screen_height)
        saved_layout = gprefs.get('book_info_dialog_layout', None)
        if saved_layout is not None:
            try:
                self.restoreGeometry(saved_layout[0])
                self.splitter.restoreState(saved_layout[1])
            except Exception:
                pass

    def link_clicked(self, qurl):
        link = unicode(qurl.toString())
        self.link_delegate(link)

    def done(self, r):
        saved_layout = (bytearray(self.saveGeometry()),
                        bytearray(self.splitter.saveState()))
        gprefs.set('book_info_dialog_layout', saved_layout)
        ret = QDialog.done(self, r)
        self.view.selectionModel().currentChanged.disconnect(self.slave)
        self.view = self.link_delegate = self.gui = None
        self.closed.emit(self)
        return ret

    def cover_changed(self, data):
        if self.current_row is not None:
            id_ = self.view.model().id(self.current_row)
            self.view.model().db.set_cover(id_, data)
        if self.gui.cover_flow:
            self.gui.cover_flow.dataChanged()
        ci = self.view.currentIndex()
        if ci.isValid():
            self.view.model().current_changed(ci, ci)
        self.cover_pixmap = QPixmap()
        self.cover_pixmap.loadFromData(data)
        if self.fit_cover.isChecked():
            self.resize_cover()

    def details_size_hint(self):
        return QSize(350, 550)

    def toggle_cover_fit(self, state):
        gprefs.set('book_info_dialog_fit_cover', self.fit_cover.isChecked())
        self.resize_cover()

    def cover_view_resized(self, event):
        QTimer.singleShot(1, self.resize_cover)

    def slave(self, current, previous):
        if current.row() != previous.row():
            row = current.row()
            self.refresh(row)

    def move(self, delta=1):
        self.view.selectionModel().currentChanged.disconnect(self.slave)
        try:
            idx = self.view.currentIndex()
            if idx.isValid():
                m = self.view.model()
                ni = m.index(idx.row() + delta, idx.column())
                if ni.isValid():
                    self.view.setCurrentIndex(ni)
                    self.refresh(ni.row())
                    if self.view.isVisible():
                        self.view.scrollTo(ni)
        finally:
            self.view.selectionModel().currentChanged.connect(self.slave)

    def next(self):
        self.move()

    def previous(self):
        self.move(-1)

    def resize_cover(self):
        if self.cover_pixmap is None:
            return
        pixmap = self.cover_pixmap
        if self.fit_cover.isChecked():
            scaled, new_width, new_height = fit_image(
                pixmap.width(), pixmap.height(),
                self.cover.size().width() - 10,
                self.cover.size().height() - 10)
            if scaled:
                pixmap = pixmap.scaled(new_width, new_height,
                                       Qt.KeepAspectRatio,
                                       Qt.SmoothTransformation)
        self.cover.set_pixmap(pixmap)
        self.update_cover_tooltip()

    def update_cover_tooltip(self):
        tt = ''
        if self.marked:
            tt = _('This book is marked') if self.marked in {
                True, 'true'
            } else _('This book is marked as: %s') % self.marked
            tt += '\n\n'
        if self.cover_pixmap is not None:
            sz = self.cover_pixmap.size()
            tt += _('Cover size: %(width)d x %(height)d') % dict(
                width=sz.width(), height=sz.height())
        self.cover.setToolTip(tt)

    def refresh(self, row):
        if isinstance(row, QModelIndex):
            row = row.row()
        if row == self.current_row:
            return
        mi = self.view.model().get_book_display_info(row)
        if mi is None:
            # Indicates books was deleted from library, or row numbers have
            # changed
            return

        self.previous_button.setEnabled(False if row == 0 else True)
        self.next_button.setEnabled(False if row ==
                                    self.view.model().rowCount(QModelIndex()) -
                                    1 else True)
        self.current_row = row
        self.setWindowTitle(mi.title)
        self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1])
        self.resize_cover()
        html = render_html(mi, self.css, True, self, all_fields=True)
        self.details.setHtml(html)
        self.marked = mi.marked
        self.cover.setBackgroundBrush(
            self.marked_brush if mi.marked else self.normal_brush)
        self.update_cover_tooltip()
Пример #27
0
class CheckLibraryDialog(QDialog):
    def __init__(self, parent, db):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('Check Library -- Problems Found'))
        self.setWindowIcon(QIcon(I('debug.png')))

        self._tl = QHBoxLayout()
        self.setLayout(self._tl)
        self.splitter = QSplitter(self)
        self.left = QWidget(self)
        self.splitter.addWidget(self.left)
        self.helpw = QTextEdit(self)
        self.splitter.addWidget(self.helpw)
        self._tl.addWidget(self.splitter)
        self._layout = QVBoxLayout()
        self.left.setLayout(self._layout)
        self.helpw.setReadOnly(True)
        self.helpw.setText(
            _('''\
        <h1>Help</h1>

        <p>calibre stores the list of your books and their metadata in a
        database. The actual book files and covers are stored as normal
        files in the calibre library folder. The database contains a list of the files
        and covers belonging to each book entry. This tool checks that the
        actual files in the library folder on your computer match the
        information in the database.</p>

        <p>The result of each type of check is shown to the left. The various
        checks are:
        </p>
        <ul>
        <li><b>Invalid titles</b>: These are files and folders appearing
        in the library where books titles should, but that do not have the
        correct form to be a book title.</li>
        <li><b>Extra titles</b>: These are extra files in your calibre
        library that appear to be correctly-formed titles, but have no corresponding
        entries in the database</li>
        <li><b>Invalid authors</b>: These are files appearing
        in the library where only author folders should be.</li>
        <li><b>Extra authors</b>: These are folders in the
        calibre library that appear to be authors but that do not have entries
        in the database</li>
        <li><b>Missing book formats</b>: These are book formats that are in
        the database but have no corresponding format file in the book's folder.
        <li><b>Extra book formats</b>: These are book format files found in
        the book's folder but not in the database.
        <li><b>Unknown files in books</b>: These are extra files in the
        folder of each book that do not correspond to a known format or cover
        file.</li>
        <li><b>Missing cover files</b>: These represent books that are marked
        in the database as having covers but the actual cover files are
        missing.</li>
        <li><b>Cover files not in database</b>: These are books that have
        cover files but are marked as not having covers in the database.</li>
        <li><b>Folder raising exception</b>: These represent folders in the
        calibre library that could not be processed/understood by this
        tool.</li>
        </ul>

        <p>There are two kinds of automatic fixes possible: <i>Delete
        marked</i> and <i>Fix marked</i>.</p>
        <p><i>Delete marked</i> is used to remove extra files/folders/covers that
        have no entries in the database. Check the box next to the item you want
        to delete. Use with caution.</p>

        <p><i>Fix marked</i> is applicable only to covers and missing formats
        (the three lines marked 'fixable'). In the case of missing cover files,
        checking the fixable box and pushing this button will tell calibre that
        there is no cover for all of the books listed. Use this option if you
        are not going to restore the covers from a backup. In the case of extra
        cover files, checking the fixable box and pushing this button will tell
        calibre that the cover files it found are correct for all the books
        listed. Use this when you are not going to delete the file(s). In the
        case of missing formats, checking the fixable box and pushing this
        button will tell calibre that the formats are really gone. Use this if
        you are not going to restore the formats from a backup.</p>

        '''))

        self.log = QTreeWidget(self)
        self.log.itemChanged.connect(self.item_changed)
        self.log.itemExpanded.connect(self.item_expanded_or_collapsed)
        self.log.itemCollapsed.connect(self.item_expanded_or_collapsed)
        self._layout.addWidget(self.log)

        self.check_button = QPushButton(_('&Run the check again'))
        self.check_button.setDefault(False)
        self.check_button.clicked.connect(self.run_the_check)
        self.copy_button = QPushButton(_('Copy &to clipboard'))
        self.copy_button.setDefault(False)
        self.copy_button.clicked.connect(self.copy_to_clipboard)
        self.ok_button = QPushButton(_('&Done'))
        self.ok_button.setDefault(True)
        self.ok_button.clicked.connect(self.accept)
        self.delete_button = QPushButton(_('Delete &marked'))
        self.delete_button.setToolTip(
            _('Delete marked files (checked subitems)'))
        self.delete_button.setDefault(False)
        self.delete_button.clicked.connect(self.delete_marked)
        self.fix_button = QPushButton(_('&Fix marked'))
        self.fix_button.setDefault(False)
        self.fix_button.setEnabled(False)
        self.fix_button.setToolTip(
            _('Fix marked sections (checked fixable items)'))
        self.fix_button.clicked.connect(self.fix_items)
        self.bbox = QDialogButtonBox(self)
        self.bbox.addButton(self.check_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.delete_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.fix_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.copy_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.ok_button, QDialogButtonBox.AcceptRole)

        h = QHBoxLayout()
        ln = QLabel(_('Names to ignore:'))
        h.addWidget(ln)
        self.name_ignores = QLineEdit()
        self.name_ignores.setText(
            db.prefs.get('check_library_ignore_names', ''))
        self.name_ignores.setToolTip(
            _('Enter comma-separated standard file name wildcards, such as synctoy*.dat'
              ))
        ln.setBuddy(self.name_ignores)
        h.addWidget(self.name_ignores)
        le = QLabel(_('Extensions to ignore'))
        h.addWidget(le)
        self.ext_ignores = QLineEdit()
        self.ext_ignores.setText(
            db.prefs.get('check_library_ignore_extensions', ''))
        self.ext_ignores.setToolTip(
            _('Enter comma-separated extensions without a leading dot. Used only in book folders'
              ))
        le.setBuddy(self.ext_ignores)
        h.addWidget(self.ext_ignores)
        self._layout.addLayout(h)

        self._layout.addWidget(self.bbox)
        self.resize(950, 500)
        self.bbox.setEnabled(True)

    def do_exec(self):
        self.run_the_check()

        probs = 0
        for c in self.problem_count:
            probs += self.problem_count[c]
        if probs == 0:
            return False
        self.exec_()
        return True

    def accept(self):
        self.db.prefs['check_library_ignore_extensions'] = \
                                            unicode(self.ext_ignores.text())
        self.db.prefs['check_library_ignore_names'] = \
                                            unicode(self.name_ignores.text())
        QDialog.accept(self)

    def box_to_list(self, txt):
        return [f.strip() for f in txt.split(',') if f.strip()]

    def run_the_check(self):
        checker = CheckLibrary(self.db.library_path, self.db)
        checker.scan_library(
            self.box_to_list(unicode(self.name_ignores.text())),
            self.box_to_list(unicode(self.ext_ignores.text())))

        plaintext = []

        def builder(tree, checker, check):
            attr, h, checkable, fixable = check
            list = getattr(checker, attr, None)
            if list is None:
                self.problem_count[attr] = 0
                return
            else:
                self.problem_count[attr] = len(list)

            tl = Item()
            tl.setText(0, h)
            if fixable and list:
                tl.setText(1, _('(fixable)'))
                tl.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
                tl.setCheckState(1, False)
            self.top_level_items[attr] = tl

            for problem in list:
                it = Item()
                if checkable:
                    it.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
                    it.setCheckState(1, False)
                else:
                    it.setFlags(Qt.ItemIsEnabled)
                it.setText(0, problem[0])
                it.setData(0, Qt.UserRole, problem[2])
                it.setText(1, problem[1])
                tl.addChild(it)
                self.all_items.append(it)
                plaintext.append(','.join([h, problem[0], problem[1]]))
            tree.addTopLevelItem(tl)

        t = self.log
        t.clear()
        t.setColumnCount(2)
        t.setHeaderLabels([_('Name'), _('Path from library')])
        self.all_items = []
        self.top_level_items = {}
        self.problem_count = {}
        for check in CHECKS:
            builder(t, checker, check)

        t.resizeColumnToContents(0)
        t.resizeColumnToContents(1)
        self.delete_button.setEnabled(False)
        self.text_results = '\n'.join(plaintext)

    def item_expanded_or_collapsed(self, item):
        self.log.resizeColumnToContents(0)
        self.log.resizeColumnToContents(1)

    def item_changed(self, item, column):
        self.fix_button.setEnabled(False)
        for it in self.top_level_items.values():
            if it.checkState(1):
                self.fix_button.setEnabled(True)

        self.delete_button.setEnabled(False)
        for it in self.all_items:
            if it.checkState(1):
                self.delete_button.setEnabled(True)
                return

    def delete_marked(self):
        if not confirm(
                '<p>' + _('The marked files and folders will be '
                          '<b>permanently deleted</b>. Are you sure?') +
                '</p>', 'check_library_editor_delete', self):
            return

        # Sort the paths in reverse length order so that we can be sure that
        # if an item is in another item, the sub-item will be deleted first.
        items = sorted(self.all_items,
                       key=lambda x: len(x.text(1)),
                       reverse=True)
        for it in items:
            if it.checkState(1):
                try:
                    p = os.path.join(self.db.library_path, unicode(it.text(1)))
                    if os.path.isdir(p):
                        delete_tree(p)
                    else:
                        delete_file(p)
                except:
                    prints(
                        'failed to delete',
                        os.path.join(self.db.library_path,
                                     unicode(it.text(1))))
        self.run_the_check()

    def fix_missing_formats(self):
        tl = self.top_level_items['missing_formats']
        child_count = tl.childCount()
        for i in range(0, child_count):
            item = tl.child(i)
            id = item.data(0, Qt.UserRole).toInt()[0]
            all = self.db.formats(id, index_is_id=True, verify_formats=False)
            all = set([f.strip() for f in all.split(',')]) if all else set()
            valid = self.db.formats(id, index_is_id=True, verify_formats=True)
            valid = set([f.strip()
                         for f in valid.split(',')]) if valid else set()
            for fmt in all - valid:
                self.db.remove_format(id, fmt, index_is_id=True, db_only=True)

    def fix_missing_covers(self):
        tl = self.top_level_items['missing_covers']
        child_count = tl.childCount()
        for i in range(0, child_count):
            item = tl.child(i)
            id = item.data(0, Qt.UserRole).toInt()[0]
            self.db.set_has_cover(id, False)

    def fix_extra_covers(self):
        tl = self.top_level_items['extra_covers']
        child_count = tl.childCount()
        for i in range(0, child_count):
            item = tl.child(i)
            id = item.data(0, Qt.UserRole).toInt()[0]
            self.db.set_has_cover(id, True)

    def fix_items(self):
        for check in CHECKS:
            attr = check[0]
            fixable = check[3]
            tl = self.top_level_items[attr]
            if fixable and tl.checkState(1):
                func = getattr(self, 'fix_' + attr, None)
                if func is not None and callable(func):
                    func()
        self.run_the_check()

    def copy_to_clipboard(self):
        QApplication.clipboard().setText(self.text_results)
Пример #28
0
    def do_layout(self):
        self.central_widget.clear()
        self.tabs = []
        self.labels = []
        sto = QWidget.setTabOrder

        self.on_drag_enter.connect(self.handle_drag_enter)
        self.tabs.append(DragTrackingWidget(self, self.on_drag_enter))
        self.central_widget.addTab(self.tabs[0], _("&Metadata"))
        self.tabs[0].l = QGridLayout()
        self.tabs[0].setLayout(self.tabs[0].l)

        self.tabs.append(QWidget(self))
        self.central_widget.addTab(self.tabs[1], _("&Cover and formats"))
        self.tabs[1].l = QGridLayout()
        self.tabs[1].setLayout(self.tabs[1].l)

        # accept drop events so we can automatically switch to the second tab to
        # drop covers and formats
        self.tabs[0].setAcceptDrops(True)

        # Tab 0
        tab0 = self.tabs[0]

        tl = QGridLayout()
        gb = QGroupBox(_('&Basic metadata'), self.tabs[0])
        self.tabs[0].l.addWidget(gb, 0, 0, 1, 1)
        gb.setLayout(tl)

        self.button_box_layout.insertWidget(1, self.fetch_metadata_button)
        self.button_box_layout.insertWidget(2, self.config_metadata_button)
        sto(self.button_box, self.fetch_metadata_button)
        sto(self.fetch_metadata_button, self.config_metadata_button)
        sto(self.config_metadata_button, self.title)

        def create_row(row, widget, tab_to, button=None, icon=None, span=1):
            ql = BuddyLabel(widget)
            tl.addWidget(ql, row, 1, 1, 1)
            tl.addWidget(widget, row, 2, 1, 1)
            if button is not None:
                tl.addWidget(button, row, 3, span, 1)
                if icon is not None:
                    button.setIcon(QIcon(I(icon)))
            if tab_to is not None:
                if button is not None:
                    sto(widget, button)
                    sto(button, tab_to)
                else:
                    sto(widget, tab_to)

        tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1)
        tl.addWidget(self.manage_authors_button, 2, 0, 1, 1)
        tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1)
        tl.addWidget(self.tags_editor_button, 6, 0, 1, 1)

        create_row(0,
                   self.title,
                   self.title_sort,
                   button=self.deduce_title_sort_button,
                   span=2,
                   icon='auto_author_sort.png')
        create_row(1, self.title_sort, self.authors)
        create_row(2,
                   self.authors,
                   self.author_sort,
                   button=self.deduce_author_sort_button,
                   span=2,
                   icon='auto_author_sort.png')
        create_row(3, self.author_sort, self.series)
        create_row(4,
                   self.series,
                   self.series_index,
                   button=self.clear_series_button,
                   icon='trash.png')
        create_row(5, self.series_index, self.tags)
        create_row(6, self.tags, self.rating, button=self.clear_tags_button)
        create_row(7,
                   self.rating,
                   self.pubdate,
                   button=self.clear_ratings_button)
        create_row(8,
                   self.pubdate,
                   self.publisher,
                   button=self.pubdate.clear_button,
                   icon='trash.png')
        create_row(9, self.publisher, self.languages)
        create_row(10, self.languages, self.timestamp)
        create_row(11,
                   self.timestamp,
                   self.identifiers,
                   button=self.timestamp.clear_button,
                   icon='trash.png')
        create_row(12,
                   self.identifiers,
                   self.comments,
                   button=self.clear_identifiers_button,
                   icon='trash.png')
        sto(self.clear_identifiers_button, self.swap_title_author_button)
        sto(self.swap_title_author_button, self.manage_authors_button)
        sto(self.manage_authors_button, self.tags_editor_button)
        sto(self.tags_editor_button, self.paste_isbn_button)
        tl.addItem(QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.Expanding),
                   13, 1, 1, 1)

        w = getattr(self, 'custom_metadata_widgets_parent', None)
        if w is not None:
            gb = QGroupBox(_('C&ustom metadata'), tab0)
            gbl = QVBoxLayout()
            gb.setLayout(gbl)
            sr = QScrollArea(tab0)
            sr.setWidgetResizable(True)
            sr.setFrameStyle(QFrame.NoFrame)
            sr.setWidget(w)
            gbl.addWidget(sr)
            self.tabs[0].l.addWidget(gb, 0, 1, 1, 1)
            sto(self.identifiers, gb)

        w = QGroupBox(_('&Comments'), tab0)
        sp = QSizePolicy()
        sp.setVerticalStretch(10)
        sp.setHorizontalPolicy(QSizePolicy.Expanding)
        sp.setVerticalPolicy(QSizePolicy.Expanding)
        w.setSizePolicy(sp)
        l = QHBoxLayout()
        w.setLayout(l)
        l.addWidget(self.comments)
        tab0.l.addWidget(w, 1, 0, 1, 2)

        # Tab 1
        tab1 = self.tabs[1]

        wsp = QWidget(tab1)
        wgl = QVBoxLayout()
        wsp.setLayout(wgl)

        # right-hand side of splitter
        gb = QGroupBox(_('Change cover'), tab1)
        l = QGridLayout()
        gb.setLayout(l)
        for i, b in enumerate(self.cover.buttons[:3]):
            l.addWidget(b, 0, i, 1, 1)
            sto(b, self.cover.buttons[i + 1])
        hl = QHBoxLayout()
        for b in self.cover.buttons[3:]:
            hl.addWidget(b)
        sto(self.cover.buttons[-2], self.cover.buttons[-1])
        l.addLayout(hl, 1, 0, 1, 3)
        wgl.addWidget(gb)
        wgl.addItem(
            QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Expanding))
        wgl.addItem(
            QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Expanding))
        wgl.addWidget(self.formats_manager)

        self.splitter = QSplitter(Qt.Horizontal, tab1)
        tab1.l.addWidget(self.splitter)
        self.splitter.addWidget(self.cover)
        self.splitter.addWidget(wsp)

        self.formats_manager.formats.setMaximumWidth(10000)
        self.formats_manager.formats.setIconSize(QSize(64, 64))
Пример #29
0
class Ui_PartWindow(QWidget):
    """
    The Ui_PartWindow class provides a Part Window UI object composed of three
    primary areas:

    - The "left area" contains the Project TabWidget which contains
    the Model Tree and Property Manager (tabs). Other tabs (widgets)
    can be added as needed.

    - The "right area" contains the Graphics Area (i.e. glpane) displaying
    the current model.

    - The "bottom area" lives below the left and right areas, spanning
    the full width of the part window. It can be used whenever a landscape
    layout is needed (i.e. the Sequence Editor). Typically, this area is
    not used and is hidden by default.

    A "part window" splitter lives between the left and right areas that
    allow the user to resize the shared area occupied by them. There is no
    splitter between the top and bottom areas.

    This class supports and is limited to a B{Single Document Interface (SDI)}.
    In time, NE1 will migrate to and support a Multiple Document Interface (MDI)
    to allow multiple project documents (i.e. parts, assemblies,
    simulations, text files, graphs, tables, etc. documents) to be available
    within the common workspace of the NE1 main window.

    @see: U{B{NE1 Main Window Framework}
    <http://www.nanoengineer-1.net/mediawiki/index.php?title=NE1_Main_Window_Framework>}
    """
    widgets = []  # For debugging purposes.
    splitterPosition = PM_DEFAULT_WIDTH
    _previous_splitterPosition = PM_DEFAULT_WIDTH

    # Used for restoring the splitter position when collapsing/expanding
    # the left area.

    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
        # note: to support MDI, self.assy would probably need to be a
        # different assembly for each PartWindow.
        # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()

        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwLeftArea (mt and pm) and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3)  # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = LeftFrame(self)
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle(QFrame.Panel | QFrame.Sunken)

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible(0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
        # _pwProjectTabWidget subclasses QTabWidget
        # Note [bruce 070829]: to fix bug 2522 I need to intercept
        # self.pwProjectTabWidget.removeTab, so I made it a subclass of
        # QTabWidget. It needs to know the GLPane, but that's not created
        # yet, so we set it later using KLUGE_setGLPane (below).
        # Note: No parent supplied. Could this be the source of the
        # minor vsplitter resizing problem I was trying to resolve a few
        # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(self.modelTreeTab,
                                       geticon("ui/modeltree/Model_Tree.png"),
                                       "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = ModelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName(
            "propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"), "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.

        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
        # note: our owner (MWsemantics) assumes
        # there is just this one GLPane for assy, and stores it
        # into assy as assy.o and assy.glpane. [bruce 080216 comment]

        # Add what's this text to self.glpane.
        # [bruce 080912 moved this here from part of a method in class GLPane.
        #  In this code's old location, Mark wrote [2007-06-01]: "Problem -
        #  I don't believe this text is processed by fix_whatsthis_text_and_links()
        #  in whatsthis_utilities.py." Now that this code is here, I don't know
        #  whether that's still true. ]
        from ne1_ui.WhatsThisText_for_MainWindow import whats_this_text_for_glpane
        self.glpane.setWhatsThis(whats_this_text_for_glpane())

        # update [re the above comment], bruce 081209:
        # I added the following explicit call of fix_whatsthis_text_and_links,
        # but it doesn't work to replace Ctrl with Cmd on Mac;
        # see today's comment in fix_whatsthis_text_and_links for likely reason.
        # So I will leave this here, but also leave in place the kluges
        # in whats_this_text_for_glpane to do that replacement itself.
        # The wiki help link in this whatsthis text doesn't work,
        # but I guess that is an independent issue, related to lack
        # of use of class QToolBar_WikiHelp or similar code, for GLPane
        # or this class or the main window class.
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self.glpane)  # doesn't yet work

        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
        # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
        # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle(QFrame.Panel | QFrame.Sunken)

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()

        #This widget implementation is subject to heavy revision. The purpose
        #is to implement a NFR that Mark urgently needs : The NFR is: Need a
        #way to quickly find a node in the MT by entering its name.
        #-- Ninad 2008-11-06
        self.pwSpecialDockWidgetInLeftChannel = SelectNodeByNameDockWidget(
            self.glpane.win)
        leftChannelVBoxLayout.addWidget(self.pwSpecialDockWidgetInLeftChannel)

        # See the resizeEvent() docstring for more information about
        # resizeTimer.
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        return

    def getLeftChannelDockWidget(self):
        return self.pwSpecialDockWidgetInLeftChannel

    def updateWindowTitle(self, changed=False):
        #by mark; bruce 050810 revised this in several ways, fixed bug 785
        """
        Update the window title (caption) at the top of the part window.
        Example:  "partname.mmp"

        This implements the standard way most applications indicate that a
        document has unsaved changes. On Mac OS X the close button will have
        a modified look; on other platforms the window title will have
        an '*' (asterisk).

        @note: We'll want to experiment with this to make sure it

        @param changed: If True, the document has unsaved changes.
        @type  changed: boolean

        @see: U{B{windowTitle}<http://doc.trolltech.com/4/qwidget.html#windowTitle-prop>},
              U{B{windowModified}<http://doc.trolltech.com/4/qwidget.html#windowModified-prop>}
        """
        # WARNING: there is mostly-duplicated code in this method and in
        # MWsemantics.update_mainwindow_caption. See the comment in that
        # method for more info and todos. [bruce 081227 comment]
        caption_fullpath = env.prefs[captionFullPath_prefs_key]

        partname = "Untitled"  # fallback value if no file yet
        if self.assy.filename:  #bruce 081227 cleanup: try -> if, etc
            # self.assy.filename is always an empty string, even after a
            # file has been opened with a complete name. Need to ask Bruce
            # about this problem, resulting in a bug (i.e. the window title
            # is always "Untitled". Mark 2008-01-02.
            junk, basename = os.path.split(self.assy.filename)
            if basename:
                if caption_fullpath:
                    partname = os.path.normpath(self.assy.filename)
                    #fixed bug 453-1 ninad060721
                else:
                    partname = basename

        # WARNING: the following code differs in the two versions
        # of this routine.
        # The "[*]" placeholder below is modified or removed by Qt; see:
        # http://doc.trolltech.com/4/qwidget.html#windowModified-prop
        self.setWindowTitle(self.trUtf8(partname + '[*]'))
        self.setWindowModified(changed)  # replaces '[*]' by '' or '*'
        return

    def collapseLeftArea(self, hideLeftArea=True):
        """
        Make the left area collapsible (via the splitter). The left area
        will be hidden (collapsed,actually) if I{hideLeftArea} is True 
        (the default).
        """
        self._previous_splitterPosition = self.pwLeftArea.width()
        if hideLeftArea:
            self.pwSplitter.setCollapsible(0, True)
            self.setSplitterPosition(pos=0)
        return

    def expandLeftArea(self):
        """
        Expand the left area.

        @see: L{MWsemantics._showFullScreenCommonCode()} for an example
        showing how it is used.
        """
        self.setSplitterPosition(pos=self._previous_splitterPosition)
        self.pwSplitter.setCollapsible(0, False)
        self.pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        self.pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)
        return

    def updatePropertyManagerTab(self, tab):  #Ninad 061207
        "Update the Properties Manager tab with 'tab' "

        self.parent.glpane.gl_update_confcorner()
        #bruce 070627, since PM affects confcorner appearance

        if self.propertyManagerScrollArea.widget():
            # The following is necessary to get rid of those C object
            # deleted errors (and the resulting bugs)
            lastwidgetobject = self.propertyManagerScrollArea.takeWidget()
            if lastwidgetobject:
                # bruce 071018 revised this code; see my comment on same
                # code in PM_Dialog
                try:
                    lastwidgetobject.update_props_if_needed_before_closing
                except AttributeError:
                    if 1 or debug_flags.atom_debug:
                        msg1 = "Last PropMgr %r doesn't have method" % lastwidgetobject
                        msg2 = " update_props_if_needed_before_closing. That's"
                        msg3 = " OK (for now, only implemented for Plane PM). "
                        msg4 = "Ignoring Exception: "
                        print_compact_traceback(msg1 + msg2 + msg3 + msg4)
                else:
                    lastwidgetobject.update_props_if_needed_before_closing()

            lastwidgetobject.hide()
            # @ ninad 061212 perhaps hiding the widget is not needed

        self.pwProjectTabWidget.removeTab(
            self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea))

        # Set the PropertyManager tab scroll area to the appropriate widget.
        self.propertyManagerScrollArea.setWidget(tab)

        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"), "")

        self.pwProjectTabWidget.setCurrentIndex(
            self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea))
        return

    def KLUGE_current_PropertyManager(self):
        #bruce 070627; revised 070829 as part of fixing bug 2523
        """
        Return the current Property Manager widget (whether or not its tab is
        chosen, but only if it has a tab), or None if there is not one.

        @warning: This method's existence (not only its implementation)
        is a kluge, since the right way to access that would be by asking
        the "command sequencer";
        but that's not yet implemented, so this is the best we can do for now.
        Also, it would be better to get the top command and talk to it, not
        its PM (a QWidget). Also, whatever calls this will be making
        assumptions about that PM which are really only the command's business.
        So in short, every call of this is in need of cleanup once we have a
        working "command sequencer". (That's true of many things related to
        PMs, not only this method.)

        @warning: The return values are (presumably) widgets, but they can
        also be mode objects and generator objects, due to excessive use of
        multiple inheritance in the current PM code. So be careful what you
        do with them -- they might have lots of extra methods/attrs,
        and setting your own attrs in them might mess things up.
        """
        res = self.propertyManagerScrollArea.widget()
        if not hasattr(res, 'done_btn'):
            # not sure what widget this is otherwise, but it is a widget
            # (not None) for the default mode, at least on startup, so just
            # return None in this case
            return None
        # Sometimes this PM remains present from a prior command, even when
        # there is no longer a tab for the PM. As part of fixing bug 2523
        # we have to avoid returning it in that case. How we do that is a kluge,
        # but hopefully this entire kluge function can be dispensed with soon.
        # This change also fixes bug 2522 on the Mac (but not on Windows --
        # for that, we needed to intercept removeTab in separate code above).
        index = self.pwProjectTabWidget.indexOf(self.propertyManagerScrollArea)
        if index == -1:
            return None
        # Due to bugs in other code, sometimes the PM tab is left in place,
        # though the PM itself is hidden. To avoid finding the PM in that case,
        # also check whether it's hidden. This will fix the CC part of a new bug
        # just reported by Keith in email (when hitting Ok in DNA Gen).
        if res.isHidden():  # probably a QWidget method [bruce 080205 comment]
            return None
        return res

    def dismiss(self):
        self.parent.removePartWindow(self)
        return

    def setSplitterPosition(self, pos=PM_DEFAULT_WIDTH, setDefault=True):
        """
        Set the position of the splitter between the left area and graphics area
        so that the width of the container holding the model tree (and 
        property manager) is I{pos} pixels wide.
        
        @param pos: The splitter position (in pixel units).
        @type  pos: int
        
        @param setDefault: If True (the default), I{pos} becomes the new default
                           position.
        @type  setDefault: boolean
        """
        self.pwSplitter.moveSplitter(pos, 1)
        if _DEBUG:
            print "New Splitter Position: %d (setDefault=%d)" \
                  % (pos, setDefault)
        if setDefault:
            self.splitterPosition = pos
        return

    def resizeEvent(self, event):
        """
        This reimplementation of QWidget.resizeEvent is here to deal with the
        undesired behavior of the splitter while resizing the part window.
        Normally, the splitter will drift back and forth while resizing
        the part window. This forces the splitter to stay fixed during
        resize operations.
        """
        # When self.resizeTimer.isActive() = True, the partwindow is being
        # resized. This is checked by the resizeEvent handler in LeftFrame
        # to determine if the splitter is being moved by the user or
        # programmably by self's resizeEvent.
        if self.resizeTimer.isActive():
            self.resizeTimer.stop()  # Stop the timer.
        self.resizeTimer.start(500)  # (Re)strand a .5 second singleshot timer.
        self.setSplitterPosition(self.splitterPosition, setDefault=False)
        QWidget.resizeEvent(self, event)
        return
Пример #30
0
    def __init__(self, parent, db):
        QDialog.__init__(self, parent)
        self.db = db

        self.setWindowTitle(_('Check Library -- Problems Found'))
        self.setWindowIcon(QIcon(I('debug.png')))

        self._tl = QHBoxLayout()
        self.setLayout(self._tl)
        self.splitter = QSplitter(self)
        self.left = QWidget(self)
        self.splitter.addWidget(self.left)
        self.helpw = QTextEdit(self)
        self.splitter.addWidget(self.helpw)
        self._tl.addWidget(self.splitter)
        self._layout = QVBoxLayout()
        self.left.setLayout(self._layout)
        self.helpw.setReadOnly(True)
        self.helpw.setText(_('''\
        <h1>Help</h1>

        <p>calibre stores the list of your books and their metadata in a
        database. The actual book files and covers are stored as normal
        files in the calibre library folder. The database contains a list of the files
        and covers belonging to each book entry. This tool checks that the
        actual files in the library folder on your computer match the
        information in the database.</p>

        <p>The result of each type of check is shown to the left. The various
        checks are:
        </p>
        <ul>
        <li><b>Invalid titles</b>: These are files and folders appearing
        in the library where books titles should, but that do not have the
        correct form to be a book title.</li>
        <li><b>Extra titles</b>: These are extra files in your calibre
        library that appear to be correctly-formed titles, but have no corresponding
        entries in the database</li>
        <li><b>Invalid authors</b>: These are files appearing
        in the library where only author folders should be.</li>
        <li><b>Extra authors</b>: These are folders in the
        calibre library that appear to be authors but that do not have entries
        in the database</li>
        <li><b>Missing book formats</b>: These are book formats that are in
        the database but have no corresponding format file in the book's folder.
        <li><b>Extra book formats</b>: These are book format files found in
        the book's folder but not in the database.
        <li><b>Unknown files in books</b>: These are extra files in the
        folder of each book that do not correspond to a known format or cover
        file.</li>
        <li><b>Missing cover files</b>: These represent books that are marked
        in the database as having covers but the actual cover files are
        missing.</li>
        <li><b>Cover files not in database</b>: These are books that have
        cover files but are marked as not having covers in the database.</li>
        <li><b>Folder raising exception</b>: These represent folders in the
        calibre library that could not be processed/understood by this
        tool.</li>
        </ul>

        <p>There are two kinds of automatic fixes possible: <i>Delete
        marked</i> and <i>Fix marked</i>.</p>
        <p><i>Delete marked</i> is used to remove extra files/folders/covers that
        have no entries in the database. Check the box next to the item you want
        to delete. Use with caution.</p>

        <p><i>Fix marked</i> is applicable only to covers and missing formats
        (the three lines marked 'fixable'). In the case of missing cover files,
        checking the fixable box and pushing this button will tell calibre that
        there is no cover for all of the books listed. Use this option if you
        are not going to restore the covers from a backup. In the case of extra
        cover files, checking the fixable box and pushing this button will tell
        calibre that the cover files it found are correct for all the books
        listed. Use this when you are not going to delete the file(s). In the
        case of missing formats, checking the fixable box and pushing this
        button will tell calibre that the formats are really gone. Use this if
        you are not going to restore the formats from a backup.</p>

        '''))

        self.log = QTreeWidget(self)
        self.log.itemChanged.connect(self.item_changed)
        self.log.itemExpanded.connect(self.item_expanded_or_collapsed)
        self.log.itemCollapsed.connect(self.item_expanded_or_collapsed)
        self._layout.addWidget(self.log)

        self.check_button = QPushButton(_('&Run the check again'))
        self.check_button.setDefault(False)
        self.check_button.clicked.connect(self.run_the_check)
        self.copy_button = QPushButton(_('Copy &to clipboard'))
        self.copy_button.setDefault(False)
        self.copy_button.clicked.connect(self.copy_to_clipboard)
        self.ok_button = QPushButton(_('&Done'))
        self.ok_button.setDefault(True)
        self.ok_button.clicked.connect(self.accept)
        self.delete_button = QPushButton(_('Delete &marked'))
        self.delete_button.setToolTip(_('Delete marked files (checked subitems)'))
        self.delete_button.setDefault(False)
        self.delete_button.clicked.connect(self.delete_marked)
        self.fix_button = QPushButton(_('&Fix marked'))
        self.fix_button.setDefault(False)
        self.fix_button.setEnabled(False)
        self.fix_button.setToolTip(_('Fix marked sections (checked fixable items)'))
        self.fix_button.clicked.connect(self.fix_items)
        self.bbox = QDialogButtonBox(self)
        self.bbox.addButton(self.check_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.delete_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.fix_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.copy_button, QDialogButtonBox.ActionRole)
        self.bbox.addButton(self.ok_button, QDialogButtonBox.AcceptRole)

        h = QHBoxLayout()
        ln = QLabel(_('Names to ignore:'))
        h.addWidget(ln)
        self.name_ignores = QLineEdit()
        self.name_ignores.setText(db.prefs.get('check_library_ignore_names', ''))
        self.name_ignores.setToolTip(
            _('Enter comma-separated standard file name wildcards, such as synctoy*.dat'))
        ln.setBuddy(self.name_ignores)
        h.addWidget(self.name_ignores)
        le = QLabel(_('Extensions to ignore'))
        h.addWidget(le)
        self.ext_ignores = QLineEdit()
        self.ext_ignores.setText(db.prefs.get('check_library_ignore_extensions', ''))
        self.ext_ignores.setToolTip(
            _('Enter comma-separated extensions without a leading dot. Used only in book folders'))
        le.setBuddy(self.ext_ignores)
        h.addWidget(self.ext_ignores)
        self._layout.addLayout(h)

        self._layout.addWidget(self.bbox)
        self.resize(950, 500)
        self.bbox.setEnabled(True)
Пример #31
0
class BookInfo(QDialog):

    closed = pyqtSignal(object)

    def __init__(self, parent, view, row, link_delegate):
        QDialog.__init__(self, parent)
        self.normal_brush = QBrush(Qt.white)
        self.marked_brush = QBrush(Qt.lightGray)
        self.marked = None
        self.gui = parent
        self.splitter = QSplitter(self)
        self._l = l = QVBoxLayout(self)
        self.setLayout(l)
        l.addWidget(self.splitter)

        self.cover = CoverView(self)
        self.cover.resizeEvent = self.cover_view_resized
        self.cover.cover_changed.connect(self.cover_changed)
        self.cover_pixmap = None
        self.cover.sizeHint = self.details_size_hint
        self.splitter.addWidget(self.cover)

        self.details = QWebView(self)
        self.details.sizeHint = self.details_size_hint
        self.details.page().setLinkDelegationPolicy(self.details.page().DelegateAllLinks)
        self.details.linkClicked.connect(self.link_clicked)
        s = self.details.page().settings()
        s.setAttribute(s.JavascriptEnabled, False)
        self.css = css()
        self.link_delegate = link_delegate
        self.details.setAttribute(Qt.WA_OpaquePaintEvent, False)
        palette = self.details.palette()
        self.details.setAcceptDrops(False)
        palette.setBrush(QPalette.Base, Qt.transparent)
        self.details.page().setPalette(palette)

        self.c = QWidget(self)
        self.c.l = l2 = QGridLayout(self.c)
        self.c.setLayout(l2)
        l2.addWidget(self.details, 0, 0, 1, -1)
        self.splitter.addWidget(self.c)

        self.fit_cover = QCheckBox(_('Fit &cover within view'), self)
        self.fit_cover.setChecked(gprefs.get('book_info_dialog_fit_cover', True))
        l2.addWidget(self.fit_cover, l2.rowCount(), 0, 1, -1)
        self.previous_button = QPushButton(QIcon(I('previous.png')), _('&Previous'), self)
        self.previous_button.clicked.connect(self.previous)
        l2.addWidget(self.previous_button, l2.rowCount(), 0)
        self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self)
        self.next_button.clicked.connect(self.next)
        l2.addWidget(self.next_button, l2.rowCount() - 1, 1)

        self.view = view
        self.current_row = None
        self.refresh(row)
        self.view.selectionModel().currentChanged.connect(self.slave)
        self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
        self.ns = QShortcut(QKeySequence('Alt+Right'), self)
        self.ns.activated.connect(self.next)
        self.ps = QShortcut(QKeySequence('Alt+Left'), self)
        self.ps.activated.connect(self.previous)
        self.next_button.setToolTip(_('Next [%s]')%
                unicode(self.ns.key().toString(QKeySequence.NativeText)))
        self.previous_button.setToolTip(_('Previous [%s]')%
                unicode(self.ps.key().toString(QKeySequence.NativeText)))

        geom = QCoreApplication.instance().desktop().availableGeometry(self)
        screen_height = geom.height() - 100
        screen_width = geom.width() - 100
        self.resize(max(int(screen_width/2), 700), screen_height)
        saved_layout = gprefs.get('book_info_dialog_layout', None)
        if saved_layout is not None:
            try:
                self.restoreGeometry(saved_layout[0])
                self.splitter.restoreState(saved_layout[1])
            except Exception:
                pass

    def link_clicked(self, qurl):
        link = unicode(qurl.toString())
        self.link_delegate(link)

    def done(self, r):
        saved_layout = (bytearray(self.saveGeometry()), bytearray(self.splitter.saveState()))
        gprefs.set('book_info_dialog_layout', saved_layout)
        ret = QDialog.done(self, r)
        self.view.selectionModel().currentChanged.disconnect(self.slave)
        self.view = self.link_delegate = self.gui = None
        self.closed.emit(self)
        return ret

    def cover_changed(self, data):
        if self.current_row is not None:
            id_ = self.view.model().id(self.current_row)
            self.view.model().db.set_cover(id_, data)
        if self.gui.cover_flow:
            self.gui.cover_flow.dataChanged()
        ci = self.view.currentIndex()
        if ci.isValid():
            self.view.model().current_changed(ci, ci)
        self.cover_pixmap = QPixmap()
        self.cover_pixmap.loadFromData(data)
        if self.fit_cover.isChecked():
            self.resize_cover()

    def details_size_hint(self):
        return QSize(350, 550)

    def toggle_cover_fit(self, state):
        gprefs.set('book_info_dialog_fit_cover', self.fit_cover.isChecked())
        self.resize_cover()

    def cover_view_resized(self, event):
        QTimer.singleShot(1, self.resize_cover)

    def slave(self, current, previous):
        if current.row() != previous.row():
            row = current.row()
            self.refresh(row)

    def move(self, delta=1):
        self.view.selectionModel().currentChanged.disconnect(self.slave)
        try:
            idx = self.view.currentIndex()
            if idx.isValid():
                m = self.view.model()
                ni = m.index(idx.row() + delta, idx.column())
                if ni.isValid():
                    self.view.setCurrentIndex(ni)
                    self.refresh(ni.row())
                    if self.view.isVisible():
                        self.view.scrollTo(ni)
        finally:
            self.view.selectionModel().currentChanged.connect(self.slave)

    def next(self):
        self.move()

    def previous(self):
        self.move(-1)

    def resize_cover(self):
        if self.cover_pixmap is None:
            return
        pixmap = self.cover_pixmap
        if self.fit_cover.isChecked():
            scaled, new_width, new_height = fit_image(pixmap.width(),
                    pixmap.height(), self.cover.size().width()-10,
                    self.cover.size().height()-10)
            if scaled:
                pixmap = pixmap.scaled(new_width, new_height,
                        Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self.cover.set_pixmap(pixmap)
        self.update_cover_tooltip()

    def update_cover_tooltip(self):
        tt = ''
        if self.marked:
            tt = _('This book is marked') if self.marked in {True, 'true'} else _(
                'This book is marked as: %s') % self.marked
            tt += '\n\n'
        if self.cover_pixmap is not None:
            sz = self.cover_pixmap.size()
            tt += _('Cover size: %(width)d x %(height)d')%dict(width=sz.width(), height=sz.height())
        self.cover.setToolTip(tt)

    def refresh(self, row):
        if isinstance(row, QModelIndex):
            row = row.row()
        if row == self.current_row:
            return
        mi = self.view.model().get_book_display_info(row)
        if mi is None:
            # Indicates books was deleted from library, or row numbers have
            # changed
            return

        self.previous_button.setEnabled(False if row == 0 else True)
        self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True)
        self.current_row = row
        self.setWindowTitle(mi.title)
        self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1])
        self.resize_cover()
        html = render_html(mi, self.css, True, self, all_fields=True)
        self.details.setHtml(html)
        self.marked = mi.marked
        self.cover.setBackgroundBrush(self.marked_brush if mi.marked else self.normal_brush)
        self.update_cover_tooltip()
Пример #32
0
    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
            # note: to support MDI, self.assy would probably need to be a
            # different assembly for each PartWindow.
            # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()

        # Used in expanding or collapsing the Model Tree/ PM area
        self._previous_pwLeftAreaWidth = PM_DEFAULT_WIDTH

        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwProjectTabWidget and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3) # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = QFrame()
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)
        pwLeftArea.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Fixed),
                        QSizePolicy.Policy(QSizePolicy.Expanding)))

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible (0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
           # _pwProjectTabWidget subclasses QTabWidget
           # Note [bruce 070829]: to fix bug 2522 I need to intercept
           # self.pwProjectTabWidget.removeTab, so I made it a subclass of
           # QTabWidget. It needs to know the GLPane, but that's not created
           # yet, so we set it later using KLUGE_setGLPane (below).
           # Note: No parent supplied. Could this be the source of the
           # minor vsplitter resizing problem I was trying to resolve a few
           # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(
            self.modelTreeTab,
            geticon("ui/modeltree/Model_Tree.png"),
            "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = modelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName("propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"),
            "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.
        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
            # note: our owner (MWsemantics) assumes
            # there is just this one GLPane for assy, and stores it
            # into assy as assy.o and assy.glpane. [bruce 080216 comment]
        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
            # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
            # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)
        pwBottomArea.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Expanding),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()
Пример #33
0
    def __init__(self, parent, view, row, link_delegate):
        QDialog.__init__(self, parent)
        self.normal_brush = QBrush(Qt.white)
        self.marked_brush = QBrush(Qt.lightGray)
        self.marked = None
        self.gui = parent
        self.splitter = QSplitter(self)
        self._l = l = QVBoxLayout(self)
        self.setLayout(l)
        l.addWidget(self.splitter)

        self.cover = CoverView(self)
        self.cover.resizeEvent = self.cover_view_resized
        self.cover.cover_changed.connect(self.cover_changed)
        self.cover_pixmap = None
        self.cover.sizeHint = self.details_size_hint
        self.splitter.addWidget(self.cover)

        self.details = QWebView(self)
        self.details.sizeHint = self.details_size_hint
        self.details.page().setLinkDelegationPolicy(self.details.page().DelegateAllLinks)
        self.details.linkClicked.connect(self.link_clicked)
        s = self.details.page().settings()
        s.setAttribute(s.JavascriptEnabled, False)
        self.css = css()
        self.link_delegate = link_delegate
        self.details.setAttribute(Qt.WA_OpaquePaintEvent, False)
        palette = self.details.palette()
        self.details.setAcceptDrops(False)
        palette.setBrush(QPalette.Base, Qt.transparent)
        self.details.page().setPalette(palette)

        self.c = QWidget(self)
        self.c.l = l2 = QGridLayout(self.c)
        self.c.setLayout(l2)
        l2.addWidget(self.details, 0, 0, 1, -1)
        self.splitter.addWidget(self.c)

        self.fit_cover = QCheckBox(_('Fit &cover within view'), self)
        self.fit_cover.setChecked(gprefs.get('book_info_dialog_fit_cover', True))
        l2.addWidget(self.fit_cover, l2.rowCount(), 0, 1, -1)
        self.previous_button = QPushButton(QIcon(I('previous.png')), _('&Previous'), self)
        self.previous_button.clicked.connect(self.previous)
        l2.addWidget(self.previous_button, l2.rowCount(), 0)
        self.next_button = QPushButton(QIcon(I('next.png')), _('&Next'), self)
        self.next_button.clicked.connect(self.next)
        l2.addWidget(self.next_button, l2.rowCount() - 1, 1)

        self.view = view
        self.current_row = None
        self.refresh(row)
        self.view.selectionModel().currentChanged.connect(self.slave)
        self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
        self.ns = QShortcut(QKeySequence('Alt+Right'), self)
        self.ns.activated.connect(self.next)
        self.ps = QShortcut(QKeySequence('Alt+Left'), self)
        self.ps.activated.connect(self.previous)
        self.next_button.setToolTip(_('Next [%s]')%
                unicode(self.ns.key().toString(QKeySequence.NativeText)))
        self.previous_button.setToolTip(_('Previous [%s]')%
                unicode(self.ps.key().toString(QKeySequence.NativeText)))

        geom = QCoreApplication.instance().desktop().availableGeometry(self)
        screen_height = geom.height() - 100
        screen_width = geom.width() - 100
        self.resize(max(int(screen_width/2), 700), screen_height)
        saved_layout = gprefs.get('book_info_dialog_layout', None)
        if saved_layout is not None:
            try:
                self.restoreGeometry(saved_layout[0])
                self.splitter.restoreState(saved_layout[1])
            except Exception:
                pass