def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self.delegate = ItemDelegate(self) self.delegate.rename_requested.connect(self.rename_requested) self.setTextElideMode(Qt.ElideMiddle) self.setItemDelegate(self.delegate) self.setIconSize(QSize(16, 16)) self.header().close() self.setDragEnabled(True) self.setEditTriggers(self.EditKeyPressed) self.setSelectionMode(self.ExtendedSelection) self.viewport().setAcceptDrops(True) self.setDropIndicatorShown(True) self.setDragDropMode(self.InternalMove) self.setAutoScroll(True) self.setAutoScrollMargin(TOP_ICON_SIZE*2) self.setDefaultDropAction(Qt.MoveAction) self.setAutoExpandDelay(1000) self.setAnimated(True) self.setMouseTracking(True) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) self.root = self.invisibleRootItem() self.emblem_cache = {} self.rendered_emblem_cache = {} self.top_level_pixmap_cache = { name : QPixmap(I(icon)).scaled(TOP_ICON_SIZE, TOP_ICON_SIZE, transformMode=Qt.SmoothTransformation) for name, icon in { 'text':'keyboard-prefs.png', 'styles':'lookfeel.png', 'fonts':'font.png', 'misc':'mimetypes/dir.png', 'images':'view-image.png', }.iteritems()} self.itemDoubleClicked.connect(self.item_double_clicked)
def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QGridLayout(self) self.setLayout(l) l.setContentsMargins(0, 0, 0, 0) self.view = QTreeWidget(self) self.delegate = Delegate(self.view) self.view.setItemDelegate(self.delegate) self.view.setHeaderHidden(True) self.view.setAnimated(True) self.view.setContextMenuPolicy(Qt.CustomContextMenu) self.view.customContextMenuRequested.connect(self.show_context_menu, type=Qt.QueuedConnection) self.view.itemActivated.connect(self.emit_navigate) self.view.itemPressed.connect(self.item_pressed) pi = plugins['progress_indicator'][0] if hasattr(pi, 'set_no_activate_on_click'): pi.set_no_activate_on_click(self.view) self.view.itemDoubleClicked.connect(self.emit_navigate) l.addWidget(self.view) self.refresh_action = QAction(QIcon(I('view-refresh.png')), _('&Refresh'), self) self.refresh_action.triggered.connect(self.refresh) self.refresh_timer = t = QTimer(self) t.setInterval(1000), t.setSingleShot(True) t.timeout.connect(self.auto_refresh) self.toc_name = None self.currently_editing = None
def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QGridLayout(self) self.setLayout(l) l.setContentsMargins(0, 0, 0, 0) self.is_visible = False self.view = QTreeWidget(self) self.delegate = Delegate(self.view) self.view.setItemDelegate(self.delegate) self.view.setHeaderHidden(True) self.view.setAnimated(True) self.view.setContextMenuPolicy(Qt.CustomContextMenu) self.view.customContextMenuRequested.connect(self.show_context_menu, type=Qt.QueuedConnection) self.view.itemActivated.connect(self.emit_navigate) self.view.itemPressed.connect(self.item_pressed) pi = plugins['progress_indicator'][0] if hasattr(pi, 'set_no_activate_on_click'): pi.set_no_activate_on_click(self.view) self.view.itemDoubleClicked.connect(self.emit_navigate) l.addWidget(self.view) self.refresh_action = QAction(QIcon(I('view-refresh.png')), _('&Refresh'), self) self.refresh_action.triggered.connect(self.build)
def __init__(self, storage, show_files): QTreeWidget.__init__(self) self.show_files = show_files self.create_children(storage, self) self.name = storage.name self.object_id = storage.persistent_id self.setMinimumHeight(350) self.setHeaderHidden(True)
def __init__(self, *args): QTreeWidget.__init__(self, *args) self.setContextMenuPolicy(Qt.CustomContextMenu) QObject.connect(self, SIGNAL('customContextMenuRequested(const QPoint &)'), self._request_context_menu) QObject.connect(self, SIGNAL('itemExpanded(QTreeWidgetItem *)'), self._item_expanded_collapsed) QObject.connect(self, SIGNAL('itemCollapsed(QTreeWidgetItem *)'), self._item_expanded_collapsed)
def __init__(self, storage, show_files=False, item_func=browser_item): QTreeWidget.__init__(self) self.item_func = item_func self.show_files = show_files self.create_children(storage, self) self.name = storage.name self.object_id = storage.persistent_id self.setMinimumHeight(350) self.setHeaderHidden(True) self.storage = storage
def mouseReleaseEvent(self, ev): item = self.itemAt(self._mouse_press_pos) if item: col = self.header().logicalIndexAt(self._mouse_press_pos) # pass event to parent QTreeWidget.mouseReleaseEvent(self, ev) # now see if the item was expanded or collapsed because of the event. Only emit signal if this was # not the case (i.e. swallow the clicks that have to do with expansion/collapse of items) if item and item is not self._expanded_item: self.emit(SIGNAL("mouseButtonClicked"), ev.button(), item, self._mouse_press_pos, col)
def setup_ui(self): self.l = l = QVBoxLayout(self) self.setLayout(l) self.msg = m = QLabel( self.msg or _('Choose the folder into which the files will be placed')) l.addWidget(m) m.setWordWrap(True) self.folders = f = QTreeWidget(self) f.setHeaderHidden(True) f.itemDoubleClicked.connect(self.accept) l.addWidget(f) f.setContextMenuPolicy(Qt.CustomContextMenu) f.customContextMenuRequested.connect(self.show_context_menu) self.root = QTreeWidgetItem(f, ('/', )) def process(node, parent): parent.setIcon(0, QIcon(I('mimetypes/dir.png'))) for child in sorted(node, key=sort_key): c = QTreeWidgetItem(parent, (child, )) process(node[child], c) process(create_folder_tree(current_container()), self.root) self.root.setSelected(True) f.expandAll() l.addWidget(self.bb)
def __init__(self, parent=None, name=None): QTreeWidget.__init__(self, parent) global folderClosedIcon, folderLockedIcon, folderOpenIcon, fileIcon folderClosedIcon = QIcon(QPixmap(folder_closed_image)) folderLockedIcon = QIcon(QPixmap(folder_locked_image)) folderOpenIcon = QIcon(QPixmap(folder_open_image)) fileIcon = QIcon(QPixmap(pix_file_image)) self.setHeaderLabels(["", "Name"]) #self.addColumn("Name", 150) # added 150. mark 060303. [bruce then changed 'width=150' to '150' to avoid exception] # Calling addColumn() here causes DirView to change size after its parent (MMKit) is shown. # I've not been successful figuring out how to control the height of the DirView (QTreeWidget) # after adding this column. See comments in MWsemantics._findGoodLocation() for more # information about how I compensate for this. Mark 060222. #self.setGeometry(QRect(7,-1,191,150)) self.setMinimumSize(QSize(160,150))
def __init__(self, parent): QTreeWidget.__init__(self, parent) self.setHeaderLabel(_('Table of Contents')) self.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.setDragEnabled(True) self.setSelectionMode(self.ExtendedSelection) self.viewport().setAcceptDrops(True) self.setDropIndicatorShown(True) self.setDragDropMode(self.InternalMove) self.setAutoScroll(True) self.setAutoScrollMargin(ICON_SIZE * 2) self.setDefaultDropAction(Qt.MoveAction) self.setAutoExpandDelay(1000) self.setAnimated(True) self.setMouseTracking(True) self.in_drop_event = False self.root = self.invisibleRootItem() self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu)
def __init__(self, parent=None): QTreeWidget.__init__(self, parent) pi = plugins['progress_indicator'][0] if hasattr(pi, 'set_no_activate_on_click'): pi.set_no_activate_on_click(self) self.current_edited_name = None self.delegate = ItemDelegate(self) self.delegate.rename_requested.connect(self.rename_requested) self.setTextElideMode(Qt.ElideMiddle) self.setItemDelegate(self.delegate) self.setIconSize(QSize(16, 16)) self.header().close() self.setDragEnabled(True) self.setEditTriggers(self.EditKeyPressed) self.setSelectionMode(self.ExtendedSelection) self.viewport().setAcceptDrops(True) self.setDropIndicatorShown(True) self.setDragDropMode(self.InternalMove) self.setAutoScroll(True) self.setAutoScrollMargin(TOP_ICON_SIZE * 2) self.setDefaultDropAction(Qt.MoveAction) self.setAutoExpandDelay(1000) self.setAnimated(True) self.setMouseTracking(True) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) self.root = self.invisibleRootItem() self.emblem_cache = {} self.rendered_emblem_cache = {} self.top_level_pixmap_cache = { name: QPixmap(I(icon)).scaled(TOP_ICON_SIZE, TOP_ICON_SIZE, transformMode=Qt.SmoothTransformation) for name, icon in { 'text': 'keyboard-prefs.png', 'styles': 'lookfeel.png', 'fonts': 'font.png', 'misc': 'mimetypes/dir.png', 'images': 'view-image.png', }.iteritems() } self.itemActivated.connect(self.item_double_clicked)
def __init__(self, parent): QTreeWidget.__init__(self, parent) self.setHeaderLabel(_('Table of Contents')) self.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.setDragEnabled(True) self.setSelectionMode(self.ExtendedSelection) self.viewport().setAcceptDrops(True) self.setDropIndicatorShown(True) self.setDragDropMode(self.InternalMove) self.setAutoScroll(True) self.setAutoScrollMargin(ICON_SIZE*2) self.setDefaultDropAction(Qt.MoveAction) self.setAutoExpandDelay(1000) self.setAnimated(True) self.setMouseTracking(True) self.in_drop_event = False self.root = self.invisibleRootItem() self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu)
def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self.delegate = ItemDelegate(self) self.setTextElideMode(Qt.ElideMiddle) self.setItemDelegate(self.delegate) self.header().close() self.setDragEnabled(True) self.setSelectionMode(self.ExtendedSelection) self.viewport().setAcceptDrops(True) self.setDropIndicatorShown(True) self.setDragDropMode(self.InternalMove) self.setAutoScroll(True) self.setAutoScrollMargin(TOP_ICON_SIZE*2) self.setDefaultDropAction(Qt.MoveAction) self.setAutoExpandDelay(1000) self.setAnimated(True) self.setMouseTracking(True) self.in_drop_event = False self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) self.root = self.invisibleRootItem()
def __init__(self, parent=None, name=None): QTreeWidget.__init__(self, parent) global folderClosedIcon, folderLockedIcon, folderOpenIcon, fileIcon folderClosedIcon = QIcon(QPixmap(folder_closed_image)) folderLockedIcon = QIcon(QPixmap(folder_locked_image)) folderOpenIcon = QIcon(QPixmap(folder_open_image)) fileIcon = QIcon(QPixmap(pix_file_image)) self.setHeaderLabels(["", "Name"]) #self.addColumn("Name", 150) # added 150. mark 060303. [bruce then changed 'width=150' to '150' to avoid exception] # Calling addColumn() here causes DirView to change size after its parent (MMKit) is shown. # I've not been successful figuring out how to control the height of the DirView (QTreeWidget) # after adding this column. See comments in MWsemantics._findGoodLocation() for more # information about how I compensate for this. Mark 060222. #self.setGeometry(QRect(7,-1,191,150)) self.setMinimumSize(QSize(160,150)) # Trying to force height to be 150, but addColumn() overrides this. To see the problem, # simply comment out addColumn() above and enter Build mode. mark 060222. #self.setSizePolicy(QSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.MinimumExpanding,0,0,self.sizePolicy().hasHeightForWidth())) qt4todo('self.setTreeStepSize(20)') qt4todo('self.setColumnWidth(0, 150)') # Force the column width to 150 again. Fixes bug 1613. mark 060303.
def __init__(self, db, duplicates, parent=None): QDialog.__init__(self, parent) self.l = l = QGridLayout() self.setLayout(l) t = ngettext('Duplicate found', 'Duplicates found', len(duplicates)) if len(duplicates) > 1: t = '%d %s' % (len(duplicates), t) self.setWindowTitle(t) self.i = i = QIcon(I('dialog_question.png')) self.setWindowIcon(i) self.l1 = l1 = QLabel() self.l2 = l2 = QLabel( _('Books with the same titles as the following already ' 'exist in calibre. Select which books you want added anyway.')) l2.setWordWrap(True) l1.setPixmap(i.pixmap(128, 128)) l.addWidget(l1, 0, 0) l.addWidget(l2, 0, 1) self.dup_list = dl = QTreeWidget(self) l.addWidget(dl, 1, 0, 1, 2) dl.setHeaderHidden(True) dl.addTopLevelItems(list(self.process_duplicates(db, duplicates))) dl.expandAll() dl.setIndentation(30) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 2, 0, 1, 2) l.setColumnStretch(1, 10) self.ab = ab = bb.addButton(_('Select &all'), bb.ActionRole) ab.clicked.connect(self.select_all), ab.setIcon(QIcon(I('plus.png'))) self.nb = ab = bb.addButton(_('Select &none'), bb.ActionRole) ab.clicked.connect(self.select_none), ab.setIcon(QIcon(I('minus.png'))) self.cb = cb = bb.addButton(_('&Copy to clipboard'), bb.ActionRole) cb.setIcon(QIcon(I('edit-copy.png'))) cb.clicked.connect(self.copy_to_clipboard) self.resize(self.sizeHint()) geom = gprefs.get('duplicates-question-dialog-geometry', None) if geom is not None: self.restoreGeometry(geom) self.exec_()
def __init__(self, db, duplicates, parent=None): QDialog.__init__(self, parent) self.l = l = QGridLayout() self.setLayout(l) self.setWindowTitle(_('Duplicates found!')) self.i = i = QIcon(I('dialog_question.png')) self.setWindowIcon(i) self.l1 = l1 = QLabel() self.l2 = l2 = QLabel( _('Books with the same titles as the following already ' 'exist in calibre. Select which books you want added anyway.')) l2.setWordWrap(True) l1.setPixmap(i.pixmap(128, 128)) l.addWidget(l1, 0, 0) l.addWidget(l2, 0, 1) self.dup_list = dl = QTreeWidget(self) l.addWidget(dl, 1, 0, 1, 2) dl.setHeaderHidden(True) dl.addTopLevelItems(list(self.process_duplicates(db, duplicates))) dl.expandAll() dl.setIndentation(30) self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 2, 0, 1, 2) self.ab = ab = bb.addButton(_('Select &all'), bb.ActionRole) ab.clicked.connect(self.select_all) self.nb = ab = bb.addButton(_('Select &none'), bb.ActionRole) ab.clicked.connect(self.select_none) self.resize(self.sizeHint()) self.exec_()
def viewportEvent(self, event): if event.type() in (QEvent.Leave, QEvent.FocusOut) and self.model: self.model.setCurrentSource(None, origin=self) return QTreeWidget.viewportEvent(self, event)
class TOCViewer(QWidget): navigate_requested = pyqtSignal(object, object) refresh_requested = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QGridLayout(self) self.setLayout(l) l.setContentsMargins(0, 0, 0, 0) self.view = QTreeWidget(self) self.delegate = Delegate(self.view) self.view.setItemDelegate(self.delegate) self.view.setHeaderHidden(True) self.view.setAnimated(True) self.view.setContextMenuPolicy(Qt.CustomContextMenu) self.view.customContextMenuRequested.connect(self.show_context_menu, type=Qt.QueuedConnection) self.view.itemActivated.connect(self.emit_navigate) self.view.itemPressed.connect(self.item_pressed) pi = plugins['progress_indicator'][0] if hasattr(pi, 'set_no_activate_on_click'): pi.set_no_activate_on_click(self.view) self.view.itemDoubleClicked.connect(self.emit_navigate) l.addWidget(self.view) self.refresh_action = QAction(QIcon(I('view-refresh.png')), _('&Refresh'), self) self.refresh_action.triggered.connect(self.refresh) self.refresh_timer = t = QTimer(self) t.setInterval(1000), t.setSingleShot(True) t.timeout.connect(self.auto_refresh) self.toc_name = None self.currently_editing = None def start_refresh_timer(self, name): if self.isVisible() and self.toc_name == name: self.refresh_timer.start() def auto_refresh(self): if self.isVisible(): try: self.refresh() except Exception: # ignore errors during live refresh of the toc import traceback traceback.print_exc() def refresh(self): self.refresh_requested.emit( ) # Give boss a chance to commit dirty editors to the container self.build() def item_pressed(self, item): if QApplication.mouseButtons() & Qt.LeftButton: QTimer.singleShot(0, self.emit_navigate) def show_context_menu(self, pos): menu = QMenu(self) menu.addAction(actions['edit-toc']) menu.addAction(_('&Expand all'), self.view.expandAll) menu.addAction(_('&Collapse all'), self.view.collapseAll) menu.addAction(self.refresh_action) menu.exec_(self.view.mapToGlobal(pos)) def iteritems(self, parent=None): if parent is None: parent = self.invisibleRootItem() for i in xrange(parent.childCount()): child = parent.child(i) yield child for gc in self.iteritems(parent=child): yield gc def emit_navigate(self, *args): item = self.view.currentItem() if item is not None: dest = unicode(item.data(0, DEST_ROLE).toString()) frag = unicode(item.data(0, FRAG_ROLE).toString()) if not frag: frag = TOP self.navigate_requested.emit(dest, frag) def build(self): c = current_container() if c is None: return toc = get_toc(c, verify_destinations=False) self.toc_name = getattr(toc, 'toc_file_name', None) def process_node(toc, parent): for child in toc: node = QTreeWidgetItem(parent) node.setText(0, child.title or '') node.setData(0, DEST_ROLE, child.dest or '') node.setData(0, FRAG_ROLE, child.frag or '') tt = _('File: {0}\nAnchor: {1}').format( child.dest or '', child.frag or _('Top of file')) node.setData(0, Qt.ToolTipRole, tt) process_node(child, node) self.view.clear() process_node(toc, self.view.invisibleRootItem()) def showEvent(self, ev): if self.toc_name is None or not ev.spontaneous(): self.build() return super(TOCViewer, self).showEvent(ev) def update_if_visible(self): if self.isVisible(): self.build()
class TOCViewer(QWidget): navigate_requested = pyqtSignal(object, object) def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QGridLayout(self) self.setLayout(l) l.setContentsMargins(0, 0, 0, 0) self.is_visible = False self.view = QTreeWidget(self) self.delegate = Delegate(self.view) self.view.setItemDelegate(self.delegate) self.view.setHeaderHidden(True) self.view.setAnimated(True) self.view.setContextMenuPolicy(Qt.CustomContextMenu) self.view.customContextMenuRequested.connect(self.show_context_menu, type=Qt.QueuedConnection) self.view.itemActivated.connect(self.emit_navigate) self.view.itemPressed.connect(self.item_pressed) pi = plugins['progress_indicator'][0] if hasattr(pi, 'set_no_activate_on_click'): pi.set_no_activate_on_click(self.view) self.view.itemDoubleClicked.connect(self.emit_navigate) l.addWidget(self.view) self.refresh_action = QAction(QIcon(I('view-refresh.png')), _('&Refresh'), self) self.refresh_action.triggered.connect(self.build) def item_pressed(self, item): if QApplication.mouseButtons() & Qt.LeftButton: QTimer.singleShot(0, self.emit_navigate) def show_context_menu(self, pos): menu = QMenu(self) menu.addAction(actions['edit-toc']) menu.addAction(_('&Expand all'), self.view.expandAll) menu.addAction(_('&Collapse all'), self.view.collapseAll) menu.addAction(self.refresh_action) menu.exec_(self.view.mapToGlobal(pos)) def iteritems(self, parent=None): if parent is None: parent = self.invisibleRootItem() for i in xrange(parent.childCount()): child = parent.child(i) yield child for gc in self.iteritems(parent=child): yield gc def emit_navigate(self, *args): item = self.view.currentItem() if item is not None: dest = unicode(item.data(0, DEST_ROLE).toString()) frag = unicode(item.data(0, FRAG_ROLE).toString()) if not frag: frag = TOP self.navigate_requested.emit(dest, frag) def build(self): c = current_container() if c is None: return toc = get_toc(c, verify_destinations=False) def process_node(toc, parent): for child in toc: node = QTreeWidgetItem(parent) node.setText(0, child.title or '') node.setData(0, DEST_ROLE, child.dest or '') node.setData(0, FRAG_ROLE, child.frag or '') tt = _('File: {0}\nAnchor: {1}').format( child.dest or '', child.frag or _('Top of file')) node.setData(0, Qt.ToolTipRole, tt) process_node(child, node) self.view.clear() process_node(toc, self.view.invisibleRootItem()) def visibility_changed(self, visible): self.is_visible = visible if visible: self.build() def update_if_visible(self): if self.is_visible: self.build()
def keyPressEvent(self, ev): if ev.key() in (Qt.Key_Delete, Qt.Key_Backspace): ev.accept() self.request_delete() else: return QTreeWidget.keyPressEvent(self, ev)
class TOCViewer(QWidget): navigate_requested = pyqtSignal(object, object) refresh_requested = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.l = l = QGridLayout(self) self.setLayout(l) l.setContentsMargins(0, 0, 0, 0) self.view = QTreeWidget(self) self.delegate = Delegate(self.view) self.view.setItemDelegate(self.delegate) self.view.setHeaderHidden(True) self.view.setAnimated(True) self.view.setContextMenuPolicy(Qt.CustomContextMenu) self.view.customContextMenuRequested.connect(self.show_context_menu, type=Qt.QueuedConnection) self.view.itemActivated.connect(self.emit_navigate) self.view.itemPressed.connect(self.item_pressed) pi = plugins['progress_indicator'][0] if hasattr(pi, 'set_no_activate_on_click'): pi.set_no_activate_on_click(self.view) self.view.itemDoubleClicked.connect(self.emit_navigate) l.addWidget(self.view) self.refresh_action = QAction(QIcon(I('view-refresh.png')), _('&Refresh'), self) self.refresh_action.triggered.connect(self.refresh) self.refresh_timer = t = QTimer(self) t.setInterval(1000), t.setSingleShot(True) t.timeout.connect(self.auto_refresh) self.toc_name = None self.currently_editing = None def start_refresh_timer(self, name): if self.isVisible() and self.toc_name == name: self.refresh_timer.start() def auto_refresh(self): if self.isVisible(): try: self.refresh() except Exception: # ignore errors during live refresh of the toc import traceback traceback.print_exc() def refresh(self): self.refresh_requested.emit() # Give boss a chance to commit dirty editors to the container self.build() def item_pressed(self, item): if QApplication.mouseButtons() & Qt.LeftButton: QTimer.singleShot(0, self.emit_navigate) def show_context_menu(self, pos): menu = QMenu(self) menu.addAction(actions['edit-toc']) menu.addAction(_('&Expand all'), self.view.expandAll) menu.addAction(_('&Collapse all'), self.view.collapseAll) menu.addAction(self.refresh_action) menu.exec_(self.view.mapToGlobal(pos)) def iteritems(self, parent=None): if parent is None: parent = self.invisibleRootItem() for i in xrange(parent.childCount()): child = parent.child(i) yield child for gc in self.iteritems(parent=child): yield gc def emit_navigate(self, *args): item = self.view.currentItem() if item is not None: dest = unicode(item.data(0, DEST_ROLE).toString()) frag = unicode(item.data(0, FRAG_ROLE).toString()) if not frag: frag = TOP self.navigate_requested.emit(dest, frag) def build(self): c = current_container() if c is None: return toc = get_toc(c, verify_destinations=False) self.toc_name = getattr(toc, 'toc_file_name', None) def process_node(toc, parent): for child in toc: node = QTreeWidgetItem(parent) node.setText(0, child.title or '') node.setData(0, DEST_ROLE, child.dest or '') node.setData(0, FRAG_ROLE, child.frag or '') tt = _('File: {0}\nAnchor: {1}').format( child.dest or '', child.frag or _('Top of file')) node.setData(0, Qt.ToolTipRole, tt) process_node(child, node) self.view.clear() process_node(toc, self.view.invisibleRootItem()) def showEvent(self, ev): if self.toc_name is None or not ev.spontaneous(): self.build() return super(TOCViewer, self).showEvent(ev) def update_if_visible(self): if self.isVisible(): self.build()
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 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, 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 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 mousePressEvent(self, ev): self._expanded_item = None self._mouse_press_pos = ev.pos() QTreeWidget.mousePressEvent(self, ev)