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)
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
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)
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()
def do_resize(self, *args): orig = self.desired_side_size QSplitter.resizeEvent(self, self._resize_ev) if orig > 20 and self.desired_show: c = 0 while abs(self.side_index_size - orig) > 10 and c < 5: self.apply_state(self.get_state(), save_desired=False) c += 1
def __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()
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)
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()
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)
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)
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)
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)
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))
def resizeEvent(self, ev): self.frame_resized.emit(ev) return QSplitter.resizeEvent(self, ev)
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 __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 __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
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
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
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)
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 __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")
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()
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()
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)
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))
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
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)
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()
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 __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