diff --git a/comicarchive.py b/comicarchive.py index c44c329..2b55cd6 100644 --- a/comicarchive.py +++ b/comicarchive.py @@ -419,8 +419,8 @@ class ComicArchive: self.path = path self.ci_xml_filename = 'ComicInfo.xml' self.comet_default_filename = 'CoMet.xml' - self.comet_filename = None - + self.resetCache() + if self.zipTest(): self.archive_type = self.ArchiveType.Zip self.archiver = ZipArchiver( self.path ) @@ -436,6 +436,14 @@ class ComicArchive: self.archive_type = self.ArchiveType.Unknown self.archiver = UnknownArchiver( self.path ) + # Clears the cached data + def resetCache( self ): + self.has_cix = None + self.has_cbi = None + self.comet_filename = None + self.page_count = None + self.page_list = None + def setExternalRarProgram( self, rar_exe_path ): if self.isRar(): self.archiver.rar_exe_path = rar_exe_path @@ -512,12 +520,16 @@ class ComicArchive: def writeMetadata( self, metadata, style ): + retcode = None if style == MetaDataStyle.CIX: - return self.writeCIX( metadata ) + retcode = self.writeCIX( metadata ) elif style == MetaDataStyle.CBI: - return self.writeCBI( metadata ) + retcode = self.writeCBI( metadata ) elif style == MetaDataStyle.COMET: - return self.writeCoMet( metadata ) + retcode = self.writeCoMet( metadata ) + self.resetCache() + return retcode + def hasMetadata( self, style ): @@ -561,29 +573,32 @@ class ComicArchive: def getPageNameList( self , sort_list=True): - # get the list file names in the archive, and sort - files = self.archiver.getArchiveFilenameList() - - # seems like some archive creators are on Windows, and don't know about case-sensitivity! - if sort_list: - files.sort(key=lambda x: x.lower()) - - # make a sub-list of image files - page_list = [] - for name in files: - if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] and os.path.basename(name)[0] != "." ): - page_list.append(name) + if self.page_list is None: + # get the list file names in the archive, and sort + files = self.archiver.getArchiveFilenameList() + + # seems like some archive creators are on Windows, and don't know about case-sensitivity! + if sort_list: + files.sort(key=lambda x: x.lower()) + + # make a sub-list of image files + self.page_list = [] + for name in files: + if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] and os.path.basename(name)[0] != "." ): + self.page_list.append(name) - return page_list + return self.page_list def getNumberOfPages( self ): - return len( self.getPageNameList( sort_list=False ) ) + if self.page_count is None: + self.page_count = len( self.getPageNameList( ) ) + return self.page_count def readCBI( self ): raw_cbi = self.readRawCBI() if raw_cbi is None: - md =GenericMetadata() + md = GenericMetadata() else: md = ComicBookInfo().metadataFromString( raw_cbi ) @@ -598,12 +613,16 @@ class ComicArchive: return self.archiver.getArchiveComment() def hasCBI(self): - #if ( not ( self.isZip() or self.isRar()) or not self.seemsToBeAComicArchive() ): - if not self.seemsToBeAComicArchive(): - return False + if self.has_cbi is None: - comment = self.archiver.getArchiveComment() - return ComicBookInfo().validateString( comment ) + #if ( not ( self.isZip() or self.isRar()) or not self.seemsToBeAComicArchive() ): + if not self.seemsToBeAComicArchive(): + self.has_cbi = False + else: + comment = self.archiver.getArchiveComment() + self.has_cbi = ComicBookInfo().validateString( comment ) + + return self.has_cbi def writeCBI( self, metadata ): self.applyArchiveInfoToMetadata( metadata ) @@ -633,7 +652,6 @@ class ComicArchive: def readRawCIX( self ): if not self.hasCIX(): - print self.path, "doesn't have ComicInfo.xml data!" return None return self.archiver.readArchiveFile( self.ci_xml_filename ) @@ -652,12 +670,15 @@ class ComicArchive: return self.archiver.removeArchiveFile( self.ci_xml_filename ) def hasCIX(self): - if not self.seemsToBeAComicArchive(): - return False - elif self.ci_xml_filename in self.archiver.getArchiveFilenameList(): - return True - else: - return False + if self.has_cix is None: + + if not self.seemsToBeAComicArchive(): + self.has_cix = False + elif self.ci_xml_filename in self.archiver.getArchiveFilenameList(): + self.has_cix = True + else: + self.has_cix = False + return self.has_cix def readCoMet( self ): diff --git a/fileselectionlist.py b/fileselectionlist.py new file mode 100644 index 0000000..af80e74 --- /dev/null +++ b/fileselectionlist.py @@ -0,0 +1,217 @@ +# coding=utf-8 +""" +A PyQt4 widget for managing list of files +""" + +""" +Copyright 2012 Anthony Beville + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os + +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from PyQt4 import uic +from PyQt4.QtCore import pyqtSignal + +from settings import ComicTaggerSettings +from comicarchive import ComicArchive +from genericmetadata import GenericMetadata, PageType +from options import MetaDataStyle + +class FileTableWidget( QTableWidget ): + + def __init__(self, parent ): + super(FileTableWidget, self).__init__(parent) + + + self.setColumnCount(5) + self.setHorizontalHeaderLabels (["File", "Folder", "CR", "CBL", ""]) + self.horizontalHeader().setStretchLastSection( True ) + + +class FileTableWidgetItem(QTableWidgetItem): + def __lt__(self, other): + return (self.data(Qt.UserRole).toBool() < + other.data(Qt.UserRole).toBool()) + + +class FileInfo( ): + def __init__(self, path, ca, cix_md, cbi_md ): + self.path = path + self.cix_md = cix_md + self.cbi_md = cbi_md + self.ca = ca + +class FileSelectionList(QWidget): + + selectionChanged = pyqtSignal(QVariant) + + def __init__(self, parent , settings ): + super(FileSelectionList, self).__init__(parent) + + uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'fileselectionlist.ui' ), self) + + self.settings = settings + #self.twList = FileTableWidget( self ) + #gridlayout = QGridLayout( self ) + #gridlayout.addWidget( self.twList ) + self.setAcceptDrops(True) + + self.twList.itemSelectionChanged.connect( self.itemSelectionChangedCB ) + + def dragEnterEvent(self, event): + self.droppedFiles = None + if event.mimeData().hasUrls(): + + # walk through the URL list and build a file list + for url in event.mimeData().urls(): + if url.isValid() and url.scheme() == "file": + if self.droppedFiles is None: + self.droppedFiles = [] + self.droppedFiles.append(url.toLocalFile()) + + if self.droppedFiles is not None: + event.accept() + + def dropEvent(self, event): + self.addPathList( self.droppedFiles) + event.accept() + + def addPathList( self, pathlist ): + filelist = [] + for p in pathlist: + # if path is a folder, walk it recursivly, and all files underneath + if os.path.isdir( unicode(p)): + for root,dirs,files in os.walk( unicode(p) ): + for f in files: + filelist.append(os.path.join(root,unicode(f))) + else: + filelist.append(unicode(p)) + + # we now have a list of files to add + + progdialog = QProgressDialog("", "Cancel", 0, len(filelist), self) + progdialog.setWindowTitle( "Adding Files" ) + progdialog.setWindowModality(Qt.WindowModal) + + self.twList.setSortingEnabled(False) + for idx,f in enumerate(filelist): + QCoreApplication.processEvents() + if progdialog.wasCanceled(): + break + progdialog.setValue(idx) + self.addPathItem( f ) + + progdialog.close() + self.twList.setSortingEnabled(True) + + #Maybe set a max size?? + self.twList.resizeColumnsToContents() + + + def isListDupe( self, path ): + r = 0 + while r < self.twList.rowCount(): + fi = self.twList.item(r, 0).data( Qt.UserRole ).toPyObject() + if fi.path == path: + return True + r = r + 1 + + return False + + def addPathItem( self, path): + path = unicode( path ) + #print "processing", path + + if self.isListDupe(path): + return + + ca = ComicArchive( path ) + if self.settings.rar_exe_path != "": + ca.setExternalRarProgram( self.settings.rar_exe_path ) + + if ca.seemsToBeAComicArchive() : + + row = self.twList.rowCount() + self.twList.insertRow( row ) + + cix_md = None + cbi_md = None + + has_cix = ca.hasCIX() + if has_cix: + cix_md = ca.readCIX() + + has_cbi = ca.hasCBI() + if has_cbi: + cbi_md = ca.readCBI() + + fi = FileInfo( path, ca, cix_md, cbi_md ) + + item_text = os.path.split(path)[1] + item = QTableWidgetItem(item_text) + item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled) + item.setData( Qt.UserRole , fi ) + item.setData( Qt.ToolTipRole ,item_text) + self.twList.setItem(row, 0, item) + + item_text = os.path.split(path)[0] + item = QTableWidgetItem(item_text) + item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled) + item.setData( Qt.ToolTipRole ,item_text) + self.twList.setItem(row, 1, item) + + # Attempt to use a special checkbox widget in the cell. + # Couldn't figure out how to disable it with "enabled" colors + #w = QWidget() + #cb = QCheckBox(w) + #cb.setCheckState(Qt.Checked) + #layout = QHBoxLayout() + #layout.addWidget( cb ) + #layout.setAlignment(Qt.AlignHCenter) + #layout.setMargin(2) + #w.setLayout(layout) + #self.twList.setCellWidget( row, 2, w ) + + item = FileTableWidgetItem() + item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled) + item.setTextAlignment(Qt.AlignHCenter) + if has_cix: + item.setCheckState(Qt.Checked) + item.setData(Qt.UserRole, True) + else: + item.setData(Qt.UserRole, False) + self.twList.setItem(row, 2, item) + + item = FileTableWidgetItem() + item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled) + item.setTextAlignment(Qt.AlignHCenter) + if has_cbi: + item.setCheckState(Qt.Checked) + item.setData(Qt.UserRole, True) + else: + item.setData(Qt.UserRole, False) + self.twList.setItem(row, 3, item) + + def itemSelectionChangedCB( self ): + idx = self.twList.currentRow() + + fi = self.twList.item(idx, 0).data( Qt.UserRole ).toPyObject() + + #if fi.cix_md is not None: + # print u"{0}".format(fi.cix_md) + + self.selectionChanged.emit( QVariant(fi)) diff --git a/fileselectionlist.ui b/fileselectionlist.ui new file mode 100644 index 0000000..8bba869 --- /dev/null +++ b/fileselectionlist.ui @@ -0,0 +1,69 @@ + + + pageListEditor + + + + 0 + 0 + 527 + 323 + + + + Form + + + + + + true + + + QAbstractItemView::SelectRows + + + 61 + + + 36 + + + false + + + + File + + + + + Path + + + + + CR + + + + + + AlignHCenter|AlignVCenter|AlignCenter + + + + + CBL + + + AlignHCenter|AlignVCenter|AlignCenter + + + + + + + + + diff --git a/pagelisteditor.py b/pagelisteditor.py index 47c317c..8e477b0 100644 --- a/pagelisteditor.py +++ b/pagelisteditor.py @@ -27,6 +27,7 @@ from PyQt4 import uic from settings import ComicTaggerSettings from genericmetadata import GenericMetadata, PageType from options import MetaDataStyle +from pageloader import PageLoader def itemMoveEvents( widget ): @@ -79,6 +80,7 @@ class PageListEditor(QWidget): self.comic_archive = None self.pages_list = None + self.page_loader = None self.current_pixmap = QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' )) self.setDisplayPixmap( 0, 0) @@ -151,17 +153,19 @@ class PageListEditor(QWidget): #idx = int(str (self.listWidget.item( row ).text())) idx = int(self.listWidget.item( row ).data(Qt.UserRole).toPyObject()[0]['Image']) - + + if self.page_loader is not None: + self.page_loader.abandoned = True + if self.comic_archive is not None: - image_data = self.comic_archive.getPage( idx ) - else: - image_data = None - - if image_data is not None: - img = QImage() - img.loadFromData( image_data ) - self.current_pixmap = QPixmap(QPixmap(img)) - self.setDisplayPixmap( 0, 0) + self.page_loader = PageLoader( self.comic_archive, idx ) + self.page_loader.loadComplete.connect( self.actualChangePageImage ) + self.page_loader.start() + + def actualChangePageImage( self, img ): + self.page_loader = None + self.current_pixmap = QPixmap(img) + self.setDisplayPixmap( 0, 0) def getFirstFrontCover( self ): frontCover = 0 diff --git a/pageloader.py b/pageloader.py new file mode 100644 index 0000000..b7cc184 --- /dev/null +++ b/pageloader.py @@ -0,0 +1,77 @@ +""" +A PyQT4 class to load a page image from a ComicArchive in a background thread +""" + +""" +Copyright 2012 Anthony Beville + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from PyQt4 import QtCore, QtGui, uic +from PyQt4.QtCore import pyqtSignal + +from comicarchive import ComicArchive + +""" +This class holds onto a reference of each instance in a list +since problems occur if the ref count goes to zero and the GC +tries to reap the object while the thread is going. + +If the client class wants to stop the thread, they should mark +it as "abandoned", and no signals will be issued +""" + +class PageLoader( QtCore.QThread ): + + loadComplete = pyqtSignal( QtGui.QImage ) + + instanceList = [] + mutex = QtCore.QMutex() + + """ + Remove all finished threads from the list + """ + @staticmethod + def reapInstances(): + for obj in reversed(PageLoader.instanceList ): + if obj.isFinished(): + PageLoader.instanceList.remove(obj) + + def __init__(self, ca, page_num ): + QtCore.QThread.__init__(self) + self.ca = ca + self.page_num = page_num + self.abandoned = False + + # remove any old instances, and then add ourself + PageLoader.mutex.lock() + PageLoader.reapInstances() + PageLoader.instanceList.append( self ) + PageLoader.mutex.unlock() + + def run(self): + image_data = self.ca.getPage( self.page_num ) + if self.abandoned: + return + + if image_data is not None: + img = QtGui.QImage() + img.loadFromData( image_data ) + + if self.abandoned: + return + + self.loadComplete.emit( img ) + + \ No newline at end of file diff --git a/taggerwindow.py b/taggerwindow.py index 8311e51..f34310f 100644 --- a/taggerwindow.py +++ b/taggerwindow.py @@ -44,8 +44,10 @@ from filenameparser import FileNameParser from logwindow import LogWindow from optionalmsgdialog import OptionalMessageDialog from pagelisteditor import PageListEditor +from fileselectionlist import FileSelectionList from cbltransformer import CBLTransformer from renamewindow import RenameWindow +from pageloader import PageLoader import utils import ctversion @@ -91,18 +93,32 @@ class TaggerWindow( QtGui.QMainWindow): #signal.signal(signal.SIGINT, self.sigint_handler) uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'taggerwindow.ui' ), self) + self.settings = settings self.pageListEditor = PageListEditor( self.tabPages ) gridlayout = QtGui.QGridLayout( self.tabPages ) gridlayout.addWidget( self.pageListEditor ) + #--------------------------- + self.fileSelectionList = FileSelectionList( self.widgetListHolder, self.settings ) + gridlayout = QtGui.QGridLayout( self.widgetListHolder ) + gridlayout.addWidget( self.fileSelectionList ) + + self.fileSelectionList.selectionChanged.connect( self.fileListSelectionChanged ) + # ATB: Disable the list for now... + self.splitter.setSizes([100,0]) + self.splitter.setHandleWidth(0) + self.splitter.handle(1).setDisabled(True) + + #--------------------------- + + self.setWindowIcon(QtGui.QIcon(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/app.png' ))) self.lblCover.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/nocover.png' ))) #print platform.system(), platform.release() self.dirtyFlag = False - self.settings = settings self.data_style = settings.last_selected_data_style #set up a default metadata object @@ -117,6 +133,7 @@ class TaggerWindow( QtGui.QMainWindow): self.droppedFile = None self.page_browser = None + self.page_loader = None self.populateComboBoxes() @@ -362,7 +379,6 @@ class TaggerWindow( QtGui.QMainWindow): msgBox.setStandardButtons( QtGui.QMessageBox.Ok ) msgBox.exec_() - def dragEnterEvent(self, event): self.droppedFile=None if event.mimeData().hasUrls(): @@ -371,12 +387,12 @@ class TaggerWindow( QtGui.QMainWindow): if url.scheme()=="file": self.droppedFile=url.toLocalFile() event.accept() - + def dropEvent(self, event): if self.dirtyFlagVerification( "Open Archive", "If you open a new archive now, data in the form will be lost. Are you sure?"): self.openArchive( unicode(self.droppedFile)) - + def openArchive( self, path, explicit_style=None, clear_form=True ): if path is None or path == "": @@ -431,35 +447,44 @@ class TaggerWindow( QtGui.QMainWindow): else: return - if self.metadata.isEmpty: - self.metadata = self.comic_archive.metadataFromFilename( ) - self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() ) - - self.updateCoverImage() - - if self.page_browser is not None: - self.page_browser.setComicArchive( self.comic_archive ) - self.page_browser.metadata = self.metadata - - self.metadataToForm() - self.pageListEditor.setData( self.comic_archive, self.metadata.pages ) - self.clearDirtyFlag() # also updates the app title - self.updateInfoBox() - self.updateMenus() - #self.updatePagesInfo() + self.loadCurrentArchive() else: QtGui.QMessageBox.information(self, self.tr("Whoops!"), self.tr("That file doesn't appear to be a comic archive!")) - def updateCoverImage( self ): - cover_idx = self.metadata.getCoverPageIndexList()[0] - image_data = self.comic_archive.getPage( cover_idx ) - if not image_data is None: - img = QtGui.QImage() - img.loadFromData( image_data ) - self.lblCover.setPixmap(QtGui.QPixmap(img)) - self.lblCover.setScaledContents(True) + def loadCurrentArchive( self ): + if self.metadata.isEmpty: + self.metadata = self.comic_archive.metadataFromFilename( ) + self.metadata.setDefaultPageList( self.comic_archive.getNumberOfPages() ) + + self.updateCoverImage() + if self.page_browser is not None: + self.page_browser.setComicArchive( self.comic_archive ) + self.page_browser.metadata = self.metadata + + self.metadataToForm() + self.pageListEditor.setData( self.comic_archive, self.metadata.pages ) + self.clearDirtyFlag() # also updates the app title + self.updateInfoBox() + self.updateMenus() + + def updateCoverImage( self ): + if self.page_loader is not None: + self.page_loader.abandoned = True + + cover_idx = self.metadata.getCoverPageIndexList()[0] + + self.page_loader = PageLoader( self.comic_archive, cover_idx ) + self.page_loader.loadComplete.connect( self.actualUpdateCoverImage ) + self.page_loader.start() + + def actualUpdateCoverImage( self, img ): + self.page_loader = None + self.lblCover.setPixmap(QtGui.QPixmap(img)) + self.lblCover.setScaledContents(True) + + def updateMenus( self ): # First just disable all the questionable items @@ -1389,5 +1414,19 @@ class TaggerWindow( QtGui.QMainWindow): self.comic_archive = None self.openArchive( dlg.new_name ) + def fileListSelectionChanged( self, qvarFI ): + fi = qvarFI.toPyObject() + #if fi.cix_md is not None: + # print u"{0}".format(fi.cix_md) + self.comic_archive = None + self.clearForm() - \ No newline at end of file + self.comic_archive = fi.ca + if self.data_style == MetaDataStyle.CIX: + self.metadata = fi.cix_md + else: + self.metadata = fi.cbi_md + if self.metadata is None: + self.metadata = GenericMetadata() + + self.loadCurrentArchive() diff --git a/taggerwindow.ui b/taggerwindow.ui index df4b273..50beaf1 100644 --- a/taggerwindow.ui +++ b/taggerwindow.ui @@ -6,8 +6,8 @@ 0 0 - 959 - 541 + 968 + 547 @@ -16,6 +16,9 @@ 0 + + false + ComicTagger @@ -29,353 +32,665 @@ 0 - - - - - + + true + + + + + + true + + + true + + + Qt::Horizontal + + + - - - QFormLayout::AllNonFixedFieldsGrow - - - Qt::AlignHCenter|Qt::AlignTop - - - - - Tag Style: + + + + + QFormLayout::AllNonFixedFieldsGrow - + + Qt::AlignHCenter|Qt::AlignTop + + + + + Tag Style: + + + + + + + - - - - - - - - - - 0 - 0 - - - - - 220 - 0 - - - - - 220 - 16777215 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - 0 - 0 - - - - - 75 - true - - - - - - - true - - - - - + + + + + 0 + 0 + + + + + 220 + 0 + + + + + 220 + 16777215 + + + + QFrame::StyledPanel + + + QFrame::Raised + + - + - + 0 0 - - - 200 - 16777215 - - - false + 75 + true - - QFrame::NoFrame - - - QFrame::Sunken - - false + true - + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + + false + + + + QFrame::NoFrame + + + QFrame::Sunken + + + + + + false + + + + + + + + 0 + 0 + + + + + true + + + + + + + + + + + - + 0 0 - - - true - - - - - - - - 0 - 0 - - - - - - - - - + + + + + + + 0 + 0 + + + + + 220 + 330 + + + + + 220 + 330 + + + + QFrame::Panel + + + QFrame::Sunken + + + + + + true + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + - - - - 0 - 0 - + + + QLayout::SetDefaultConstraint - - - 220 - 330 - - - - - 220 - 330 - - - - QFrame::Panel - - - QFrame::Sunken - - - - - - true - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 20 - - - - - - - - - - QLayout::SetDefaultConstraint - - - - - true - - - 0 - - - - Details - - - - - - Qt::ScrollBarAsNeeded - - - Qt::ScrollBarAsNeeded - - - false - - - - true - - - - 0 - 0 - 685 - 384 - - - - - - 10 - 10 - 361 - 341 - + + + + true + + + true + + + 0 + + + + Details + + + + + + true - - - 4 + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + false + + + + true - - - - QLayout::SetNoConstraint + + + 0 + 0 + 685 + 384 + + + + true + + + + + 10 + 10 + 361 + 341 + + + + + 4 - - QFormLayout::AllNonFixedFieldsGrow + + + + QLayout::SetNoConstraint + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Series + + + + + + + + + + Title + + + + + + + + + + Publisher + + + + + + + + + + Imprint + + + + + + + + + + + 85 + 0 + + + + Series Group + + + + + + + + + + Story Arc + + + + + + + + + + Genre + + + + + + + + + + Alt. Series + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Maximum + + + + 20 + 10 + + + + + + + + + + 6 + + + 4 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + + 85 + 0 + + + + Alt. Issue + + + + + + + + 0 + 0 + + + + + + + + + + + + Alt. # Issues + + + + + + + + + + + + + + + + 380 + 10 + 301 + 341 + + + + + 6 - - - - Series + + 3 + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Issue + + + + + + + + + + Volume + + + + + + + + + + Year + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Qt::ImhDigitsOnly + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + # Issues + + + + + + + Qt::ImhDigitsOnly + + + + + + + # Volumes + + + + + + + Qt::ImhDigitsOnly + + + + + + + Month + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Qt::ImhDigitsOnly + + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow - - - - - - - - - Title + + 6 - + + + + Maturity Rating + + + + + + + false + + + true + + + + + + + Manga + + + + + + + false + + + + + + + Format + + + + + + + + 0 + 0 + + + + true + + + + + + + Black & White + + + + + + + Qt::LeftToRight + + + false + + + + + + + - - - - - - - Publisher - - - - - - - - - - Imprint - - - - - - - - - - - 85 - 0 - - - - Series Group - - - - - - - - - - Story Arc - - - - - - - - - - Genre - - - - - - - - - - Alt. Series - - - - - - - - + + Qt::Vertical @@ -390,654 +705,372 @@ - - - - - - 6 - - - 4 - - + QFormLayout::AllNonFixedFieldsGrow - - - - - 85 - 0 - - - - Alt. Issue - - - - + - + 0 0 - - - - - - - - Alt. # Issues - - - - - - - - - - - - - - - - 380 - 10 - 301 - 341 - - - - - 6 - - - 3 - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Issue - - - - - - - - - - Volume - - - - - - - - - - Year - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - - - - Qt::ImhDigitsOnly - - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - # Issues - - - - - - - Qt::ImhDigitsOnly - - - - + - # Volumes + Country - - - Qt::ImhDigitsOnly - - - - - - - Month - - - - - + - + 0 0 - - - 50 - 16777215 - - - - Qt::ImhDigitsOnly + + + + + + Language - - - - - QFormLayout::AllNonFixedFieldsGrow - - - 6 - - - - - Maturity Rating - - - - - - - false - - - true - - - - - - - Manga - - - - - - - false - - - - - - - Format - - - - - - - - 0 - 0 - - - - true - - - - - - - Black & White - - - - - - - Qt::LeftToRight - - - false - - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Maximum - - - - 20 - 10 - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - - 0 - 0 - - - - - - - - Country - - - - - - - - 0 - 0 - - - - - - - - Language - - - - - - - - - - - - - - - Credits - - - - - - - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - 0 - - - 3 - - - 2 - - - true - - - false - - - - Primary - - - AlignHCenter|AlignVCenter|AlignCenter - - - - - Credit - - - - - Name - - + + + + + + + + Credits + + + + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + 0 + + + 3 + + + 2 + + + true + + + false + + + + Primary + + + AlignHCenter|AlignVCenter|AlignCenter + + + + + Credit + + + + + Name + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Scan Info + + + + + + + + + - - - QFormLayout::ExpandingFieldsGrow - - - + + + - Scan Info + Add Credit - - + + + + Remove Credit + + + + + + + Edit Credit + + + + + + + Qt::Vertical + + + + 20 + 40 + + + - - - - + + + + + Notes + + + + + + QFormLayout::ExpandingFieldsGrow + + + - Add Credit + Comments - - + + + + + - Remove Credit + Notes - - + + + + + - Edit Credit + Web - - - - Qt::Vertical + + + + + + + User Rating - + + + + + + + 0 + 0 + + + - 20 - 40 + 80 + 16777215 - + - - - - - - Notes - - - - - - QFormLayout::ExpandingFieldsGrow - + + + + Other + + - - - Comments + + + QFormLayout::ExpandingFieldsGrow - - - - - - - - - Notes - - - - - - - - - - Web - - - - - - - - - - User Rating - - - - - - - - 0 - 0 - - - - - 80 - 16777215 - - - + + + + Characters + + + + + + + true + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + Teams + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + Locations + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + + Other Tags + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 100 + + + + + - - - - - - Other - - - - - - QFormLayout::ExpandingFieldsGrow - - - - - Characters - - - - - - - true - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - - Teams - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - - Locations - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - - Other Tags - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 100 - - - - - - - - - - - Pages - - - + + + + Pages + + + + + - - + + + + false + + + @@ -1046,8 +1079,8 @@ 0 0 - 959 - 25 + 968 + 28