Example #1
0
    def setup_ui(self):
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)
        self.gb = gb = QGroupBox(_('&Images in book'), self)
        self.v = v = QVBoxLayout(gb)
        gb.setLayout(v), gb.setFlat(True)
        self.names, self.names_filter = create_filterable_names_list(
            sorted(self.image_names, key=sort_key),
            filter_text=_('Filter the list of images'),
            parent=self)
        self.names.doubleClicked.connect(
            self.double_clicked, type=Qt.ConnectionType.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')

        def on_state_change(s):
            tprefs.set('add_cover_preserve_aspect_ratio',
                       s == Qt.CheckState.Checked)

        p.stateChanged.connect(on_state_change)
        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'),
                              QDialogButtonBox.ButtonRole.ActionRole)
        b.clicked.connect(self.import_image)
        b.setIcon(QIcon(I('document_open.png')))
        self.names.setFocus(Qt.FocusReason.OtherFocusReason)
        self.names.selectionModel().currentChanged.connect(
            self.current_image_changed)
        cname = get_raster_cover_name(self.container)
        if cname:
            row = self.names.model().find_name(cname)
            if row > -1:
                self.names.setCurrentIndex(self.names.model().index(row))
Example #2
0
    def setup_ui(self):
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)
        self.gb  = gb = QGroupBox(_('&Images in book'), self)
        self.v = v = QVBoxLayout(gb)
        gb.setLayout(v), gb.setFlat(True)
        self.names, self.names_filter = create_filterable_names_list(
            sorted(self.image_names, key=sort_key), filter_text=_('Filter the list of images'), parent=self)
        self.names.doubleClicked.connect(self.double_clicked, type=Qt.QueuedConnection)
        self.cover_view = CoverView(self)
        l.addWidget(self.names_filter)
        v.addWidget(self.names)

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

        self.h = h = QHBoxLayout()
        self.preserve = p = QCheckBox(_('Preserve aspect ratio'))
        p.setToolTip(textwrap.fill(_('If enabled the cover image you select will be embedded'
                       ' into the book in such a way that when viewed, its aspect'
                       ' ratio (ratio of width to height) will be preserved.'
                       ' This will mean blank spaces around the image if the screen'
                       ' the book is being viewed on has an aspect ratio different'
                       ' to the image.')))
        p.setChecked(tprefs['add_cover_preserve_aspect_ratio'])
        p.setVisible(self.container.book_type != 'azw3')

        def on_state_change(s):
            tprefs.set('add_cover_preserve_aspect_ratio', s == Qt.Checked)

        p.stateChanged.connect(on_state_change)
        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)
        cname = get_raster_cover_name(self.container)
        if cname:
            row = self.names.model().find_name(cname)
            if row > -1:
                self.names.setCurrentIndex(self.names.model().index(row))
Example #3
0
    def build(self, container, preserve_state=True):
        if preserve_state:
            state = self.get_state()
        self.clear()
        self.root = self.invisibleRootItem()
        self.root.setFlags(Qt.ItemIsDragEnabled)
        self.categories = {}
        for category, text in (
            ('text', _('Text')),
            ('styles', _('Styles')),
            ('images', _('Images')),
            ('fonts', _('Fonts')),
            ('misc', _('Miscellaneous')),
        ):
            self.categories[category] = i = QTreeWidgetItem(self.root, 0)
            i.setText(0, text)
            i.setData(0, Qt.DecorationRole,
                      self.top_level_pixmap_cache[category])
            f = i.font(0)
            f.setBold(True)
            i.setFont(0, f)
            i.setData(0, NAME_ROLE, category)
            flags = Qt.ItemIsEnabled
            if category == 'text':
                flags |= Qt.ItemIsDropEnabled
            i.setFlags(flags)

        processed, seen = {}, {}

        cover_page_name = get_cover_page_name(container)
        cover_image_name = get_raster_cover_name(container)
        manifested_names = set()
        for names in container.manifest_type_map.itervalues():
            manifested_names |= set(names)

        def get_category(name, mt):
            category = 'misc'
            if mt.startswith('image/'):
                category = 'images'
            elif mt in OEB_FONTS:
                category = 'fonts'
            elif mt in OEB_STYLES:
                category = 'styles'
            elif mt in OEB_DOCS:
                category = 'text'
            ext = name.rpartition('.')[-1].lower()
            if ext in {'ttf', 'otf', 'woff'}:
                # Probably wrong mimetype in the OPF
                category = 'fonts'
            return category

        def set_display_name(name, item):
            if name in processed:
                # We have an exact duplicate (can happen if there are
                # duplicates in the spine)
                item.setText(0, processed[name].text(0))
                item.setText(1, processed[name].text(1))
                return

            parts = name.split('/')
            text = parts[-1]
            while text in seen and parts:
                text = parts.pop() + '/' + text
            seen[text] = item
            item.setText(0, text)
            item.setText(1, hexlify(sort_key(text)))

        def render_emblems(item, emblems):
            emblems = tuple(emblems)
            if not emblems:
                return
            icon = self.rendered_emblem_cache.get(emblems, None)
            if icon is None:
                pixmaps = []
                for emblem in emblems:
                    pm = self.emblem_cache.get(emblem, None)
                    if pm is None:
                        pm = self.emblem_cache[emblem] = QPixmap(
                            I(emblem)).scaled(
                                self.iconSize(),
                                transformMode=Qt.SmoothTransformation)
                    pixmaps.append(pm)
                num = len(pixmaps)
                w, h = pixmaps[0].width(), pixmaps[0].height()
                if num == 1:
                    icon = self.rendered_emblem_cache[emblems] = QIcon(
                        pixmaps[0])
                else:
                    canvas = QPixmap((num * w) + ((num - 1) * 2), h)
                    canvas.fill(Qt.transparent)
                    painter = QPainter(canvas)
                    for i, pm in enumerate(pixmaps):
                        painter.drawPixmap(i * (w + 2), 0, pm)
                    painter.end()
                    icon = self.rendered_emblem_cache[emblems] = canvas
            item.setData(0, Qt.DecorationRole, icon)

        cannot_be_renamed = container.names_that_must_not_be_changed
        ncx_mime = guess_type('a.ncx')

        def create_item(name, linear=None):
            imt = container.mime_map.get(name, guess_type(name))
            icat = get_category(name, imt)
            category = 'text' if linear is not None else ({
                'text': 'misc'
            }.get(icat, icat))
            item = QTreeWidgetItem(
                self.categories['text' if linear is not None else category], 1)
            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
            if category == 'text':
                flags |= Qt.ItemIsDragEnabled
            if name not in cannot_be_renamed:
                flags |= Qt.ItemIsEditable
            item.setFlags(flags)
            item.setStatusTip(0, _('Full path: ') + name)
            item.setData(0, NAME_ROLE, name)
            item.setData(0, CATEGORY_ROLE, category)
            item.setData(0, LINEAR_ROLE, bool(linear))
            item.setData(0, MIME_ROLE, imt)
            set_display_name(name, item)
            tooltips = []
            emblems = []
            if name in {cover_page_name, cover_image_name}:
                emblems.append('default_cover.png')
                tooltips.append(
                    _('This file is the cover %s for this book') %
                    (_('image') if name == cover_image_name else _('page')))
            if name in container.opf_name:
                emblems.append('metadata.png')
                tooltips.append(
                    _('This file contains all the metadata and book structure information'
                      ))
            if imt == ncx_mime:
                emblems.append('toc.png')
                tooltips.append(
                    _('This file contains the metadata table of contents'))
            if name not in manifested_names and not container.ok_to_be_unmanifested(
                    name):
                emblems.append('dialog_question.png')
                tooltips.append(
                    _('This file is not listed in the book manifest'))
            if linear is False:
                emblems.append('arrow-down.png')
                tooltips.append(
                    _('This file is marked as non-linear in the spine\nDrag it to the top to make it linear'
                      ))
            if linear is None and icat == 'text':
                # Text item outside spine
                emblems.append('dialog_warning.png')
                tooltips.append(
                    _('This file is a text file that is not referenced in the spine'
                      ))
            if category == 'text' and name in processed:
                # Duplicate entry in spine
                emblems.append('dialog_error.png')
                tooltips.append(
                    _('This file occurs more than once in the spine'))

            render_emblems(item, emblems)
            if tooltips:
                item.setData(0, Qt.ToolTipRole, '\n'.join(tooltips))
            return item

        for name, linear in container.spine_names:
            processed[name] = create_item(name, linear=linear)

        for name in container.name_path_map:
            if name in processed:
                continue
            processed[name] = create_item(name)

        for name, c in self.categories.iteritems():
            c.setExpanded(True)
            if name != 'text':
                c.sortChildren(1, Qt.AscendingOrder)

        if preserve_state:
            self.set_state(state)

        if self.current_edited_name:
            item = self.item_from_name(self.current_edited_name)
            if item is not None:
                self.mark_item_as_current(item)
Example #4
0
def check_links(container):
    links_map = defaultdict(set)
    xml_types = {guess_type('a.opf'), guess_type('a.ncx')}
    errors = []
    a = errors.append

    def fl(x):
        x = repr(x)
        if x.startswith('u'):
            x = x[1:]
        return x

    for name, mt in container.mime_map.iteritems():
        if mt in OEB_DOCS or mt in OEB_STYLES or mt in xml_types:
            for href, lnum, col in container.iterlinks(name):
                if not href:
                    a(EmptyLink(_('The link is empty'), name, lnum, col))
                try:
                    tname = container.href_to_name(href, name)
                except ValueError:
                    tname = None  # Absolute paths to files on another drive in windows cause this
                if tname is not None:
                    if container.exists(tname):
                        if tname in container.mime_map:
                            links_map[name].add(tname)
                        else:
                            # Filesystem says the file exists, but it is not in
                            # the mime_map, so either there is a case mismatch
                            # or the link is a directory
                            apath = container.name_to_abspath(tname)
                            if os.path.isdir(apath):
                                a(
                                    BadLink(
                                        _('The linked resource %s is a directory'
                                          ) % fl(href), name, lnum, col))
                            else:
                                a(
                                    CaseMismatch(
                                        href,
                                        actual_case_for_name(container, tname),
                                        name, lnum, col))
                    else:
                        cname = corrected_case_for_name(container, tname)
                        if cname is not None:
                            a(CaseMismatch(href, cname, name, lnum, col))
                        else:
                            a(
                                DanglingLink(
                                    _('The linked resource %s does not exist')
                                    % fl(href), tname, name, lnum, col))
                else:
                    purl = urlparse(href)
                    if purl.scheme == 'file':
                        a(
                            FileLink(
                                _('The link %s is a file:// URL') % fl(href),
                                name, lnum, col))
                    elif purl.path and purl.path.startswith(
                            '/') and purl.scheme in {'', 'file'}:
                        a(
                            LocalLink(
                                _('The link %s points to a file outside the book'
                                  ) % fl(href), name, lnum, col))
                    elif purl.path and purl.scheme in {
                            '', 'file'
                    } and ':' in urlunquote(purl.path):
                        a(
                            InvalidCharInLink(
                                _('The link %s contains a : character, this will cause errors on Windows computers'
                                  ) % fl(href), name, lnum, col))

    spine_docs = {name for name, linear in container.spine_names}
    spine_styles = {
        tname
        for name in spine_docs for tname in links_map[name]
        if container.mime_map.get(tname, None) in OEB_STYLES
    }
    num = -1
    while len(spine_styles) > num:
        # Handle import rules in stylesheets
        num = len(spine_styles)
        spine_styles |= {
            tname
            for name in spine_styles for tname in links_map[name]
            if container.mime_map.get(tname, None) in OEB_STYLES
        }
    seen = set(OEB_DOCS) | set(OEB_STYLES)
    spine_resources = {
        tname
        for name in spine_docs | spine_styles for tname in links_map[name]
        if container.mime_map[tname] not in seen
    }
    unreferenced = set()

    cover_name = container.guide_type_map.get('cover', None)
    nav_items = frozenset(container.manifest_items_with_property('nav'))

    for name, mt in container.mime_map.iteritems():
        if mt in OEB_STYLES and name not in spine_styles:
            a(UnreferencedResource(name))
        elif mt in OEB_DOCS and name not in spine_docs and name not in nav_items:
            a(UnreferencedDoc(name))
        elif (mt in OEB_FONTS or mt.partition('/')[0] in {
                'image', 'audio', 'video'
        }) and name not in spine_resources and name != cover_name:
            if mt.partition('/')[
                    0] == 'image' and name == get_raster_cover_name(container):
                continue
            a(UnreferencedResource(name))
        else:
            continue
        unreferenced.add(name)

    manifest_names = set(container.manifest_id_map.itervalues())
    for name in container.mime_map:
        if name not in manifest_names and not container.ok_to_be_unmanifested(
                name):
            a(Unmanifested(name, unreferenced=name in unreferenced))
        if name == 'META-INF/calibre_bookmarks.txt':
            a(Bookmarks(name))

    return errors
Example #5
0
    def build(self, container, preserve_state=True):
        if container is None:
            return
        if preserve_state:
            state = self.get_state()
        self.clear()
        self.root = self.invisibleRootItem()
        self.root.setFlags(Qt.ItemIsDragEnabled)
        self.categories = {}
        for category, text, __ in CATEGORIES:
            self.categories[category] = i = QTreeWidgetItem(self.root, 0)
            i.setText(0, text)
            i.setData(0, Qt.DecorationRole, self.top_level_pixmap_cache[category])
            f = i.font(0)
            f.setBold(True)
            i.setFont(0, f)
            i.setData(0, NAME_ROLE, category)
            flags = Qt.ItemIsEnabled
            if category == 'text':
                flags |= Qt.ItemIsDropEnabled
            i.setFlags(flags)

        processed, seen = {}, {}

        cover_page_name = get_cover_page_name(container)
        cover_image_name = get_raster_cover_name(container)
        manifested_names = set()
        for names in container.manifest_type_map.itervalues():
            manifested_names |= set(names)

        def get_category(name, mt):
            category = 'misc'
            if mt.startswith('image/'):
                category = 'images'
            elif mt in OEB_FONTS:
                category = 'fonts'
            elif mt in OEB_STYLES:
                category = 'styles'
            elif mt in OEB_DOCS:
                category = 'text'
            ext = name.rpartition('.')[-1].lower()
            if ext in {'ttf', 'otf', 'woff'}:
                # Probably wrong mimetype in the OPF
                category = 'fonts'
            return category

        def set_display_name(name, item):
            if tprefs['file_list_shows_full_pathname']:
                text = name
            else:
                if name in processed:
                    # We have an exact duplicate (can happen if there are
                    # duplicates in the spine)
                    item.setText(0, processed[name].text(0))
                    item.setText(1, processed[name].text(1))
                    return

                parts = name.split('/')
                text = parts.pop()
                while text in seen and parts:
                    text = parts.pop() + '/' + text

            seen[text] = item
            item.setText(0, text)
            item.setText(1, hexlify(sort_key(text)))

        def render_emblems(item, emblems):
            emblems = tuple(emblems)
            if not emblems:
                return
            icon = self.rendered_emblem_cache.get(emblems, None)
            if icon is None:
                pixmaps = []
                for emblem in emblems:
                    pm = self.emblem_cache.get(emblem, None)
                    if pm is None:
                        pm = self.emblem_cache[emblem] = QIcon(I(emblem)).pixmap(self.iconSize())
                    pixmaps.append(pm)
                num = len(pixmaps)
                w, h = pixmaps[0].width(), pixmaps[0].height()
                if num == 1:
                    icon = self.rendered_emblem_cache[emblems] = QIcon(pixmaps[0])
                else:
                    canvas = QPixmap((num * w) + ((num-1)*2), h)
                    canvas.setDevicePixelRatio(pixmaps[0].devicePixelRatio())
                    canvas.fill(Qt.transparent)
                    painter = QPainter(canvas)
                    for i, pm in enumerate(pixmaps):
                        painter.drawPixmap(int(i * (w + 2)/canvas.devicePixelRatio()), 0, pm)
                    painter.end()
                    icon = self.rendered_emblem_cache[emblems] = canvas
            item.setData(0, Qt.DecorationRole, icon)

        cannot_be_renamed = container.names_that_must_not_be_changed
        ncx_mime = guess_type('a.ncx')
        nav_items = frozenset(container.manifest_items_with_property('nav'))

        def create_item(name, linear=None):
            imt = container.mime_map.get(name, guess_type(name))
            icat = get_category(name, imt)
            category = 'text' if linear is not None else ({'text':'misc'}.get(icat, icat))
            item = QTreeWidgetItem(self.categories['text' if linear is not None else category], 1)
            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
            if category == 'text':
                flags |= Qt.ItemIsDragEnabled
            if name not in cannot_be_renamed:
                flags |= Qt.ItemIsEditable
            item.setFlags(flags)
            item.setStatusTip(0, _('Full path: ') + name)
            item.setData(0, NAME_ROLE, name)
            item.setData(0, CATEGORY_ROLE, category)
            item.setData(0, LINEAR_ROLE, bool(linear))
            item.setData(0, MIME_ROLE, imt)
            set_display_name(name, item)
            tooltips = []
            emblems = []
            if name in {cover_page_name, cover_image_name}:
                emblems.append('default_cover.png')
                tooltips.append(_('This file is the cover %s for this book') % (_('image') if name == cover_image_name else _('page')))
            if name in container.opf_name:
                emblems.append('metadata.png')
                tooltips.append(_('This file contains all the metadata and book structure information'))
            if imt == ncx_mime or name in nav_items:
                emblems.append('toc.png')
                tooltips.append(_('This file contains the metadata table of contents'))
            if name not in manifested_names and not container.ok_to_be_unmanifested(name):
                emblems.append('dialog_question.png')
                tooltips.append(_('This file is not listed in the book manifest'))
            if linear is False:
                emblems.append('arrow-down.png')
                tooltips.append(_('This file is marked as non-linear in the spine\nDrag it to the top to make it linear'))
            if linear is None and icat == 'text':
                # Text item outside spine
                emblems.append('dialog_warning.png')
                tooltips.append(_('This file is a text file that is not referenced in the spine'))
            if category == 'text' and name in processed:
                # Duplicate entry in spine
                emblems.append('dialog_error.png')
                tooltips.append(_('This file occurs more than once in the spine'))

            render_emblems(item, emblems)
            if tooltips:
                item.setData(0, Qt.ToolTipRole, '\n'.join(tooltips))
            return item

        for name, linear in container.spine_names:
            processed[name] = create_item(name, linear=linear)

        for name in container.name_path_map:
            if name in processed:
                continue
            processed[name] = create_item(name)

        for name, c in self.categories.iteritems():
            c.setExpanded(True)
            if name != 'text':
                c.sortChildren(1, Qt.AscendingOrder)

        if preserve_state:
            self.set_state(state)

        if self.current_edited_name:
            item = self.item_from_name(self.current_edited_name)
            if item is not None:
                self.mark_item_as_current(item)
Example #6
0
def check_links(container):
    links_map = defaultdict(set)
    xml_types = {guess_type('a.opf'), guess_type('a.ncx')}
    errors = []
    a = errors.append

    def fl(x):
        x = repr(x)
        if x.startswith('u'):
            x = x[1:]
        return x

    for name, mt in iteritems(container.mime_map):
        if mt in OEB_DOCS or mt in OEB_STYLES or mt in xml_types:
            for href, lnum, col in container.iterlinks(name):
                if not href:
                    a(EmptyLink(_('The link is empty'), name, lnum, col))
                try:
                    tname = container.href_to_name(href, name)
                except ValueError:
                    tname = None  # Absolute paths to files on another drive in windows cause this
                if tname is not None:
                    if container.exists(tname):
                        if tname in container.mime_map:
                            links_map[name].add(tname)
                        else:
                            # Filesystem says the file exists, but it is not in
                            # the mime_map, so either there is a case mismatch
                            # or the link is a directory
                            apath = container.name_to_abspath(tname)
                            if os.path.isdir(apath):
                                a(BadLink(_('The linked resource %s is a directory') % fl(href), name, lnum, col))
                            else:
                                a(CaseMismatch(href, actual_case_for_name(container, tname), name, lnum, col))
                    else:
                        cname = corrected_case_for_name(container, tname)
                        if cname is not None:
                            a(CaseMismatch(href, cname, name, lnum, col))
                        else:
                            a(DanglingLink(_('The linked resource %s does not exist') % fl(href), tname, name, lnum, col))
                else:
                    purl = urlparse(href)
                    if purl.scheme == 'file':
                        a(FileLink(_('The link %s is a file:// URL') % fl(href), name, lnum, col))
                    elif purl.path and purl.path.startswith('/') and purl.scheme in {'', 'file'}:
                        a(LocalLink(_('The link %s points to a file outside the book') % fl(href), name, lnum, col))
                    elif purl.path and purl.scheme in {'', 'file'} and ':' in urlunquote(purl.path):
                        a(InvalidCharInLink(_('The link %s contains a : character, this will cause errors on Windows computers') % fl(href), name, lnum, col))

    spine_docs = {name for name, linear in container.spine_names}
    spine_styles = {tname for name in spine_docs for tname in links_map[name] if container.mime_map.get(tname, None) in OEB_STYLES}
    num = -1
    while len(spine_styles) > num:
        # Handle import rules in stylesheets
        num = len(spine_styles)
        spine_styles |= {tname for name in spine_styles for tname in links_map[name] if container.mime_map.get(tname, None) in OEB_STYLES}
    seen = set(OEB_DOCS) | set(OEB_STYLES)
    spine_resources = {tname for name in spine_docs | spine_styles for tname in links_map[name] if container.mime_map[tname] not in seen}
    unreferenced = set()

    cover_name = container.guide_type_map.get('cover', None)
    nav_items = frozenset(container.manifest_items_with_property('nav'))

    for name, mt in iteritems(container.mime_map):
        if mt in OEB_STYLES and name not in spine_styles:
            a(UnreferencedResource(name))
        elif mt in OEB_DOCS and name not in spine_docs and name not in nav_items:
            a(UnreferencedDoc(name))
        elif (mt in OEB_FONTS or mt.partition('/')[0] in {'image', 'audio', 'video'}) and name not in spine_resources and name != cover_name:
            if mt.partition('/')[0] == 'image' and name == get_raster_cover_name(container):
                continue
            a(UnreferencedResource(name))
        else:
            continue
        unreferenced.add(name)

    manifest_names = set(itervalues(container.manifest_id_map))
    for name in container.mime_map:
        if name not in manifest_names and not container.ok_to_be_unmanifested(name):
            a(Unmanifested(name, unreferenced=name in unreferenced))
        if name == 'META-INF/calibre_bookmarks.txt':
            a(Bookmarks(name))

    return errors
Example #7
0
    def build(self, container, preserve_state=True):
        if preserve_state:
            state = self.get_state()
        self.clear()
        self.root = self.invisibleRootItem()
        self.root.setFlags(Qt.ItemIsDragEnabled)
        self.categories = {}
        for category, text in (
            ("text", _("Text")),
            ("styles", _("Styles")),
            ("images", _("Images")),
            ("fonts", _("Fonts")),
            ("misc", _("Miscellaneous")),
        ):
            self.categories[category] = i = QTreeWidgetItem(self.root, 0)
            i.setText(0, text)
            i.setData(0, Qt.DecorationRole, self.top_level_pixmap_cache[category])
            f = i.font(0)
            f.setBold(True)
            i.setFont(0, f)
            i.setData(0, NAME_ROLE, category)
            flags = Qt.ItemIsEnabled
            if category == "text":
                flags |= Qt.ItemIsDropEnabled
            i.setFlags(flags)

        processed, seen = {}, {}

        cover_page_name = get_cover_page_name(container)
        cover_image_name = get_raster_cover_name(container)
        manifested_names = set()
        for names in container.manifest_type_map.itervalues():
            manifested_names |= set(names)

        font_types = {guess_type("a." + x) for x in ("ttf", "otf", "woff")}

        def get_category(name, mt):
            category = "misc"
            if mt.startswith("image/"):
                category = "images"
            elif mt in font_types:
                category = "fonts"
            elif mt in OEB_STYLES:
                category = "styles"
            elif mt in OEB_DOCS:
                category = "text"
            ext = name.rpartition(".")[-1].lower()
            if ext in {"ttf", "otf", "woff"}:
                # Probably wrong mimetype in the OPF
                category = "fonts"
            return category

        def set_display_name(name, item):
            if name in processed:
                # We have an exact duplicate (can happen if there are
                # duplicates in the spine)
                item.setText(0, processed[name].text(0))
                item.setText(1, processed[name].text(1))
                return

            parts = name.split("/")
            text = parts[-1]
            while text in seen and parts:
                text = parts.pop() + "/" + text
            seen[text] = item
            item.setText(0, text)
            item.setText(1, hexlify(sort_key(text)))

        def render_emblems(item, emblems):
            emblems = tuple(emblems)
            if not emblems:
                return
            icon = self.rendered_emblem_cache.get(emblems, None)
            if icon is None:
                pixmaps = []
                for emblem in emblems:
                    pm = self.emblem_cache.get(emblem, None)
                    if pm is None:
                        pm = self.emblem_cache[emblem] = QPixmap(I(emblem)).scaled(
                            self.iconSize(), transformMode=Qt.SmoothTransformation
                        )
                    pixmaps.append(pm)
                num = len(pixmaps)
                w, h = pixmaps[0].width(), pixmaps[0].height()
                if num == 1:
                    icon = self.rendered_emblem_cache[emblems] = QIcon(pixmaps[0])
                else:
                    canvas = QPixmap((num * w) + ((num - 1) * 2), h)
                    canvas.fill(Qt.transparent)
                    painter = QPainter(canvas)
                    for i, pm in enumerate(pixmaps):
                        painter.drawPixmap(i * (w + 2), 0, pm)
                    painter.end()
                    icon = self.rendered_emblem_cache[emblems] = canvas
            item.setData(0, Qt.DecorationRole, icon)

        ok_to_be_unmanifested = container.names_that_need_not_be_manifested
        cannot_be_renamed = container.names_that_must_not_be_changed

        def create_item(name, linear=None):
            imt = container.mime_map.get(name, guess_type(name))
            icat = get_category(name, imt)
            category = "text" if linear is not None else ({"text": "misc"}.get(icat, icat))
            item = QTreeWidgetItem(self.categories["text" if linear is not None else category], 1)
            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
            if category == "text":
                flags |= Qt.ItemIsDragEnabled
            if name not in cannot_be_renamed:
                flags |= Qt.ItemIsEditable
            item.setFlags(flags)
            item.setStatusTip(0, _("Full path: ") + name)
            item.setData(0, NAME_ROLE, name)
            item.setData(0, CATEGORY_ROLE, category)
            item.setData(0, LINEAR_ROLE, bool(linear))
            item.setData(0, MIME_ROLE, imt)
            set_display_name(name, item)
            # TODO: Add appropriate tooltips based on the emblems
            emblems = []
            if name in {cover_page_name, cover_image_name}:
                emblems.append("default_cover.png")
            if name not in manifested_names and name not in ok_to_be_unmanifested:
                emblems.append("dialog_question.png")
            if linear is False:
                emblems.append("arrow-down.png")
            if linear is None and icat == "text":
                # Text item outside spine
                emblems.append("dialog_warning.png")
            if category == "text" and name in processed:
                # Duplicate entry in spine
                emblems.append("dialog_warning.png")

            render_emblems(item, emblems)
            return item

        for name, linear in container.spine_names:
            processed[name] = create_item(name, linear=linear)

        all_files = list(container.manifest_type_map.iteritems())
        all_files.append((guess_type("a.opf"), [container.opf_name]))

        for name in container.name_path_map:
            if name in processed:
                continue
            processed[name] = create_item(name)

        for name, c in self.categories.iteritems():
            c.setExpanded(True)
            if name != "text":
                c.sortChildren(1, Qt.AscendingOrder)

        if preserve_state:
            self.set_state(state)
Example #8
0
    def build(self, container, preserve_state=True):
        if preserve_state:
            state = self.get_state()
        self.clear()
        self.root = self.invisibleRootItem()
        self.root.setFlags(Qt.ItemIsDragEnabled)
        self.categories = {}
        for category, text in (
            ('text', _('Text')),
            ('styles', _('Styles')),
            ('images', _('Images')),
            ('fonts', _('Fonts')),
            ('misc', _('Miscellaneous')),
        ):
            self.categories[category] = i = QTreeWidgetItem(self.root, 0)
            i.setText(0, text)
            i.setData(0, Qt.DecorationRole, self.top_level_pixmap_cache[category])
            f = i.font(0)
            f.setBold(True)
            i.setFont(0, f)
            i.setData(0, NAME_ROLE, category)
            flags = Qt.ItemIsEnabled
            if category == 'text':
                flags |= Qt.ItemIsDropEnabled
            i.setFlags(flags)

        processed, seen = {}, {}

        cover_page_name = get_cover_page_name(container)
        cover_image_name = get_raster_cover_name(container)
        manifested_names = set()
        for names in container.manifest_type_map.itervalues():
            manifested_names |= set(names)

        font_types = {guess_type('a.'+x) for x in ('ttf', 'otf', 'woff')}

        def get_category(mt):
            category = 'misc'
            if mt.startswith('image/'):
                category = 'images'
            elif mt in font_types:
                category = 'fonts'
            elif mt in OEB_STYLES:
                category = 'styles'
            elif mt in OEB_DOCS:
                category = 'text'
            return category

        def set_display_name(name, item):
            if name in processed:
                # We have an exact duplicate (can happen if there are
                # duplicates in the spine)
                item.setText(0, processed[name].text(0))
                return

            parts = name.split('/')
            text = parts[-1]
            while text in seen and parts:
                text = parts.pop() + '/' + text
            seen[text] = item
            item.setText(0, text)

        def render_emblems(item, emblems):
            emblems = tuple(emblems)
            if not emblems:
                return
            icon = self.rendered_emblem_cache.get(emblems, None)
            if icon is None:
                pixmaps = []
                for emblem in emblems:
                    pm = self.emblem_cache.get(emblem, None)
                    if pm is None:
                        pm = self.emblem_cache[emblem] = QPixmap(
                            I(emblem)).scaled(self.iconSize(), transformMode=Qt.SmoothTransformation)
                    pixmaps.append(pm)
                num = len(pixmaps)
                w, h = pixmaps[0].width(), pixmaps[0].height()
                if num == 1:
                    icon = self.rendered_emblem_cache[emblems] = QIcon(pixmaps[0])
                else:
                    canvas = QPixmap((num * w) + ((num-1)*2), h)
                    canvas.fill(Qt.transparent)
                    painter = QPainter(canvas)
                    for i, pm in enumerate(pixmaps):
                        painter.drawPixmap(i * (w + 2), 0, pm)
                    painter.end()
                    icon = self.rendered_emblem_cache[emblems] = canvas
            item.setData(0, Qt.DecorationRole, icon)

        ok_to_be_unmanifested = container.names_that_need_not_be_manifested

        def create_item(name, linear=None):
            imt = container.mime_map.get(name, guess_type(name))
            icat = get_category(imt)
            category = 'text' if linear is not None else ({'text':'misc'}.get(icat, icat))
            item = QTreeWidgetItem(self.categories['text' if linear is not None else category], 1)
            flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
            if category == 'text':
                flags |= Qt.ItemIsDragEnabled
            item.setFlags(flags)
            item.setStatusTip(0, _('Full path: ') + name)
            item.setData(0, NAME_ROLE, name)
            item.setData(0, CATEGORY_ROLE, category)
            set_display_name(name, item)
            # TODO: Add appropriate tooltips based on the emblems
            emblems = []
            if name in {cover_page_name, cover_image_name}:
                emblems.append('default_cover.png')
            if name not in manifested_names and name not in ok_to_be_unmanifested:
                emblems.append('dialog_question.png')
            if linear is False:
                emblems.append('arrow-down.png')
            if linear is None and icat == 'text':
                # Text item outside spine
                emblems.append('dialog_warning.png')
            if category == 'text' and name in processed:
                # Duplicate entry in spine
                emblems.append('dialog_warning.png')

            render_emblems(item, emblems)
            return item

        for name, linear in container.spine_names:
            processed[name] = create_item(name, linear=linear)

        all_files = list(container.manifest_type_map.iteritems())
        all_files.append((guess_type('a.opf'), [container.opf_name]))

        for name in container.name_path_map:
            if name in processed:
                continue
            processed[name] = create_item(name)

        for c in self.categories.itervalues():
            c.setExpanded(True)

        if preserve_state:
            self.set_state(state)