Read image data for reports and refactor file model class to make it more re-useable

This commit is contained in:
Kovid Goyal 2015-01-20 14:38:04 +05:30
parent a7505aa324
commit 260d11e79b
3 changed files with 153 additions and 37 deletions

View File

@ -7,9 +7,11 @@ __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import posixpath, os
from collections import namedtuple
from collections import namedtuple, defaultdict
from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, OEB_FONTS
from calibre.utils.icu import numeric_sort_key
from calibre.utils.magick.draw import identify
File = namedtuple('File', 'name dir basename size category')
@ -39,11 +41,51 @@ def safe_size(container, name):
except Exception:
return 0
def safe_img_data(container, name, mt):
if 'svg' in mt:
return 0, 0
try:
width, height, fmt = identify(container.name_to_abspath(name))
except Exception:
width = height = 0
return width, height
def file_data(container):
for name, path in container.name_path_map.iteritems():
yield File(name, posixpath.dirname(name), posixpath.basename(name), safe_size(container, name),
get_category(name, container.mime_map.get(name, '')))
Image = namedtuple('Image', 'name mime_type usage size width height')
L = namedtuple('Location', 'name line_number offset word')
def Location(name, line_number=None, offset=0, word=None):
return L(name, line_number, offset, word)
def sort_locations(locations):
def sort_key(l):
return (numeric_sort_key(l.name), l.line_number, l.offset, l.word)
return sorted(locations, key=sort_key)
def link_data(container):
image_usage = defaultdict(set)
link_sources = OEB_STYLES | OEB_DOCS
for name, mt in container.mime_map.iteritems():
if mt in link_sources:
for href, line_number, offset in container.iterlinks(name):
target = container.href_to_name(href, name)
if target and container.exists(target):
mt = container.mime_map.get(target)
if mt and mt.startswith('image/'):
image_usage[target].add(Location(name, line_number, offset))
image_data = []
for name, mt in container.mime_map.iteritems():
if mt.startswith('image/') and container.exists(name):
image_data.append(Image(name, mt, sort_locations(image_usage.get(name, set())), safe_size(container, name),
*safe_img_data(container, name, mt)))
return tuple(image_data)
def gather_data(container):
data = {'files':tuple(file_data(container))}
img_data = link_data(container)
data['images'] = img_data
return data

View File

@ -129,7 +129,7 @@ class Boss(QObject):
self.gui.manage_fonts.container_changed.connect(self.apply_container_update_to_gui)
self.gui.manage_fonts.embed_all_fonts.connect(self.manage_fonts_embed)
self.gui.manage_fonts.subset_all_fonts.connect(self.manage_fonts_subset)
self.gui.reports.edit_requested.connect(self.report_edit_requested)
self.gui.reports.edit_requested.connect(self.reports_edit_requested)
@property
def currently_editing(self):
@ -1157,10 +1157,11 @@ class Boss(QObject):
self.gui.reports.show()
self.gui.reports.raise_()
def report_edit_requested(self, name, location=None):
def reports_edit_requested(self, location):
name = location.name
mt = current_container().mime_map.get(name, guess_type(name))
editor = self.edit_file_requested(name, None, mt)
if editor is None and location is not None:
if editor is not None:
pass
def image_activated(self, name):

View File

@ -16,12 +16,12 @@ from PyQt5.Qt import (
QListWidgetItem, QLineEdit, QStackedWidget, QSplitter, QByteArray)
from calibre import human_readable
from calibre.ebooks.oeb.polish.report import gather_data
from calibre.ebooks.oeb.polish.report import gather_data, Location
from calibre.gui2 import error_dialog
from calibre.gui2.tweak_book import current_container, tprefs
from calibre.gui2.tweak_book.widgets import Dialog
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.utils.icu import primary_contains, primary_sort_key
from calibre.utils.icu import primary_contains, numeric_sort_key
def read_state(name, default=None):
data = tprefs.get('reports-ui-state')
@ -66,22 +66,13 @@ class ProxyModel(QSortFilterProxyModel): # {{{
return QSortFilterProxyModel.headerData(self, section, orientation, role)
# }}}
# Files {{{
class FilesModel(QAbstractTableModel):
class FileCollection(QAbstractTableModel):
COLUMN_HEADERS = [_('Folder'), _('Name'), _('Size (KB)'), _('Type')]
CATEGORY_NAMES = {
'image':_('Image'),
'text': _('Text'),
'font': _('Font'),
'style': _('Style'),
'opf': _('Metadata'),
'toc': _('Table of Contents'),
}
COLUMN_HEADERS = ()
def __init__(self, parent=None):
self.files = self.sort_keys = ()
self.total_size = self.images_size = self.fonts_size = 0
self.total_size = 0
QAbstractTableModel.__init__(self, parent)
def columnCount(self, parent=None):
@ -98,29 +89,46 @@ class FilesModel(QAbstractTableModel):
pass
return QAbstractTableModel.headerData(self, section, orientation, role)
def __call__(self, data):
self.beginResetModel()
self.files = data['files']
self.total_size = sum(map(itemgetter(3), self.files))
self.images_size = sum(map(itemgetter(3), (f for f in self.files if f.category == 'image')))
self.fonts_size = sum(map(itemgetter(3), (f for f in self.files if f.category == 'font')))
psk = primary_sort_key
self.sort_keys = tuple((psk(entry.dir), psk(entry.basename), entry.size, psk(self.CATEGORY_NAMES.get(entry.category, '')))
for entry in self.files)
self.endResetModel()
def sort_key(self, row, col):
try:
return self.sort_keys[row][col]
except IndexError:
pass
def name(self, index):
def location(self, index):
try:
return self.files[index.row()].name
return Location(self.files[index.row()].name)
except IndexError:
pass
# Files {{{
class FilesModel(FileCollection):
COLUMN_HEADERS = (_('Folder'), _('Name'), _('Size (KB)'), _('Type'))
CATEGORY_NAMES = {
'image':_('Image'),
'text': _('Text'),
'font': _('Font'),
'style': _('Style'),
'opf': _('Metadata'),
'toc': _('Table of Contents'),
}
def __init__(self, parent=None):
FileCollection.__init__(self, parent)
self.images_size = self.fonts_size = 0
def __call__(self, data):
self.beginResetModel()
self.files = data['files']
self.total_size = sum(map(itemgetter(3), self.files))
self.images_size = sum(map(itemgetter(3), (f for f in self.files if f.category == 'image')))
self.fonts_size = sum(map(itemgetter(3), (f for f in self.files if f.category == 'font')))
psk = numeric_sort_key
self.sort_keys = tuple((psk(entry.dir), psk(entry.basename), entry.size, psk(self.CATEGORY_NAMES.get(entry.category, '')))
for entry in self.files)
self.endResetModel()
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
col = index.column()
@ -140,7 +148,7 @@ class FilesModel(QAbstractTableModel):
class FilesWidget(QWidget):
edit_requested = pyqtSignal(object, object)
edit_requested = pyqtSignal(object)
def __init__(self, parent=None):
QWidget.__init__(self, parent)
@ -175,19 +183,83 @@ class FilesWidget(QWidget):
human_readable, (m.total_size, m.images_size, m.fonts_size))))
def double_clicked(self, index):
name = self.model.name(self.proxy.mapToSource(index))
if name is not None:
self.edit_requested.emit(name, None)
location = self.model.location(self.proxy.mapToSource(index))
if location is not None:
self.edit_requested.emit(location)
def save(self):
save_state('all-files-table', bytearray(self.files.horizontalHeader().saveState()))
# }}}
class ImagesModel(QAbstractTableModel):
COLUMN_HEADERS = [_('Name'), _('Size (KB)'), _('Times used'), _('Width'), _('Height'), _('Image')]
def __init__(self, parent=None):
self.files = self.sort_keys = ()
self.total_size = 0
QAbstractTableModel.__init__(self, parent)
def columnCount(self, parent=None):
return len(self.COLUMN_HEADERS)
def rowCount(self, parent=None):
return len(self.files)
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
try:
return self.COLUMN_HEADERS[section]
except IndexError:
pass
return QAbstractTableModel.headerData(self, section, orientation, role)
def __call__(self, data):
self.beginResetModel()
self.files = data['files']
self.total_size = sum(map(itemgetter(3), self.files))
self.images_size = sum(map(itemgetter(3), (f for f in self.files if f.category == 'image')))
self.fonts_size = sum(map(itemgetter(3), (f for f in self.files if f.category == 'font')))
psk = numeric_sort_key
self.sort_keys = tuple((psk(entry.dir), psk(entry.basename), entry.size, psk(self.CATEGORY_NAMES.get(entry.category, '')))
for entry in self.files)
self.endResetModel()
def sort_key(self, row, col):
try:
return self.sort_keys[row][col]
except IndexError:
pass
def name(self, index):
try:
return self.files[index.row()].name
except IndexError:
pass
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole:
col = index.column()
try:
entry = self.files[index.row()]
except IndexError:
return None
if col == 0:
return entry.dir
if col == 1:
return entry.basename
if col == 2:
sz = entry.size / 1024.
return ('%.2f' % sz if int(sz) != sz else type('')(sz))
if col == 3:
return self.CATEGORY_NAMES.get(entry.category)
# Wrapper UI {{{
class ReportsWidget(QWidget):
edit_requested = pyqtSignal(object, object)
edit_requested = pyqtSignal(object)
def __init__(self, parent=None):
QWidget.__init__(self, parent)
@ -226,7 +298,7 @@ class ReportsWidget(QWidget):
class Reports(Dialog):
data_gathered = pyqtSignal(object, object)
edit_requested = pyqtSignal(object, object)
edit_requested = pyqtSignal(object)
def __init__(self, parent=None):
Dialog.__init__(self, _('Reports'), 'reports-dialog', parent=parent)
@ -271,6 +343,7 @@ class Reports(Dialog):
ok, data = True, gather_data(current_container())
except Exception:
import traceback
traceback.print_exc()
ok, data = False, traceback.format_exc()
self.data_gathered.emit(ok, data)