Initial checking
git-svn-id: http://comictagger.googlecode.com/svn/trunk@2 6c5673fe-1810-88d6-992b-cd32ca31540c
This commit is contained in:
parent
2f21c2f4d2
commit
f642b46742
284
comicarchive.py
Normal file
284
comicarchive.py
Normal file
@ -0,0 +1,284 @@
|
||||
"""
|
||||
A python class to represent a single comic, be it file or folder of images
|
||||
"""
|
||||
|
||||
import zipfile
|
||||
import os
|
||||
import struct
|
||||
|
||||
from options import Options, MetaDataStyle
|
||||
from comicinfoxml import ComicInfoXml
|
||||
from comicbookinfo import ComicBookInfo
|
||||
from genericmetadata import GenericMetadata
|
||||
from filenameparser import FileNameParser
|
||||
|
||||
|
||||
# This is a custom function for writing a comment to a zip file,
|
||||
# since the built-in one doesn't seem to work on Windows and Mac OS/X
|
||||
|
||||
# Fortunately, the zip comment is at the end of the file, and it's
|
||||
# easy to manipulate. See this website for more info:
|
||||
# see: http://en.wikipedia.org/wiki/Zip_(file_format)#Structure
|
||||
|
||||
def writeZipComment( filename, comment ):
|
||||
|
||||
#get file size
|
||||
statinfo = os.stat(filename)
|
||||
file_length = statinfo.st_size
|
||||
|
||||
fo = open(filename, "r+b")
|
||||
|
||||
#the starting position, relative to EOF
|
||||
pos = -4
|
||||
|
||||
found = False
|
||||
value = bytearray()
|
||||
|
||||
# walk backwards to find the "End of Central Directory" record
|
||||
while ( not found ) and ( -pos != file_length ):
|
||||
# seek, relative to EOF
|
||||
fo.seek( pos, 2)
|
||||
|
||||
value = fo.read( 4 )
|
||||
|
||||
#look for the end of central directory signature
|
||||
if bytearray(value) == bytearray([ 0x50, 0x4b, 0x05, 0x06 ]):
|
||||
found = True
|
||||
else:
|
||||
# not found, step back another byte
|
||||
pos = pos - 1
|
||||
#print pos,"{1} int: {0:x}".format(bytearray(value)[0], value)
|
||||
|
||||
if found:
|
||||
|
||||
# now skip forward 20 bytes to the comment length word
|
||||
pos += 20
|
||||
fo.seek( pos, 2)
|
||||
|
||||
# Pack the length of the comment string
|
||||
format = "H" # one 2-byte integer
|
||||
comment_length = struct.pack(format, len(comment)) # pack integer in a binary string
|
||||
|
||||
# write out the length
|
||||
fo.write( comment_length )
|
||||
fo.seek( pos+2, 2)
|
||||
|
||||
# write out the comment itself
|
||||
fo.write( comment )
|
||||
fo.truncate()
|
||||
fo.close()
|
||||
|
||||
else:
|
||||
raise Exception('Failed to write comment to zip file!')
|
||||
|
||||
#------------------------------------------
|
||||
|
||||
|
||||
class ComicArchive:
|
||||
|
||||
def __init__( self, path ):
|
||||
self.path = path
|
||||
self.ci_xml_filename = 'ComicInfo.xml'
|
||||
|
||||
def isZip( self ):
|
||||
return zipfile.is_zipfile( self.path )
|
||||
|
||||
def isFolder( self ):
|
||||
return False
|
||||
|
||||
def isNonWritableArchive( self ):
|
||||
# TODO check for rar, maybe others
|
||||
# also check permissions
|
||||
return False
|
||||
|
||||
def seemsToBeAComicArchive( self ):
|
||||
# TODO this will need to be fleshed out to support RAR and Folder
|
||||
|
||||
ext = os.path.splitext(self.path)[1].lower()
|
||||
|
||||
if (
|
||||
( self.isZip() ) and
|
||||
( ext in [ '.zip', '.cbz' ] ) and
|
||||
( self.getNumberOfPages() > 3)
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def readMetadata( self, style ):
|
||||
|
||||
if style == MetaDataStyle.CIX:
|
||||
return self.readCIX()
|
||||
elif style == MetaDataStyle.CBI:
|
||||
return self.readCBI()
|
||||
else:
|
||||
return GenericMetadata()
|
||||
|
||||
def writeMetadata( self, metadata, style ):
|
||||
|
||||
if style == MetaDataStyle.CIX:
|
||||
self.writeCIX( metadata )
|
||||
elif style == MetaDataStyle.CBI:
|
||||
self.writeCBI( metadata )
|
||||
|
||||
def hasMetadata( self, syle ):
|
||||
|
||||
if style == MetaDataStyle.CIX:
|
||||
return self.hasCIX()
|
||||
elif style == MetaDataStyle.CBI:
|
||||
return self.hasCBI()
|
||||
else:
|
||||
return False
|
||||
|
||||
def clearMetadata( self, style ):
|
||||
return
|
||||
|
||||
def getCoverPage(self):
|
||||
|
||||
if self.getNumberOfPages() == 0:
|
||||
return None
|
||||
|
||||
zf = zipfile.ZipFile (self.path, 'r')
|
||||
|
||||
# get the list file names in the archive, and sort
|
||||
files = zf.namelist()
|
||||
files.sort()
|
||||
|
||||
# find the first image file, assume it's the cover
|
||||
for name in files:
|
||||
if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] ):
|
||||
break
|
||||
|
||||
image_data = zf.read( name )
|
||||
zf.close()
|
||||
|
||||
return image_data
|
||||
|
||||
def getNumberOfPages(self):
|
||||
|
||||
count = 0
|
||||
|
||||
if self.isZip():
|
||||
zf = zipfile.ZipFile (self.path, 'r')
|
||||
for item in zf.infolist():
|
||||
if ( item.filename[-4:].lower() in [ ".jpg", "jpeg", ".png" ] ):
|
||||
count += 1
|
||||
zf.close()
|
||||
|
||||
return count
|
||||
|
||||
def readCBI( self ):
|
||||
|
||||
if ( not self.hasCBI() ):
|
||||
print self.path, " isn't a zip or doesn't has CBI data!"
|
||||
return GenericMetadata()
|
||||
|
||||
zf = zipfile.ZipFile( self.path, "r" )
|
||||
cbi_string = zf.comment
|
||||
zf.close()
|
||||
|
||||
metadata = ComicBookInfo().metadataFromString( cbi_string )
|
||||
return metadata
|
||||
|
||||
def writeCBI( self, metadata ):
|
||||
|
||||
cbi_string = ComicBookInfo().stringFromMetadata( metadata )
|
||||
writeZipComment( self.path, cbi_string )
|
||||
|
||||
def readCIX( self ):
|
||||
|
||||
# !!!ATB TODO add support for folders
|
||||
|
||||
if (not self.isZip()) or ( not self.hasCIX()):
|
||||
print self.path, " isn't a zip or doesn't has ComicInfo.xml data!"
|
||||
return GenericMetadata()
|
||||
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
cix_string = zf.read( self.ci_xml_filename )
|
||||
zf.close()
|
||||
|
||||
metadata = ComicInfoXml().metadataFromString( cix_string )
|
||||
return metadata
|
||||
|
||||
def writeCIX(self, metadata):
|
||||
|
||||
# !!!ATB TODO add support for folders
|
||||
if (not self.isZip()):
|
||||
print self.path, "isn't a zip archive!"
|
||||
return
|
||||
|
||||
cix_string = ComicInfoXml().stringFromMetadata( metadata )
|
||||
|
||||
# check if an XML file already exists in archive
|
||||
if not self.hasCIX():
|
||||
|
||||
#simple case: just add the new archive file
|
||||
zf = zipfile.ZipFile(self.path, mode='a', compression=zipfile.ZIP_DEFLATED )
|
||||
zf.writestr( self.ci_xml_filename, cix_string )
|
||||
zf.close()
|
||||
|
||||
else:
|
||||
# If we need to replace it, well, at the moment, no other option
|
||||
# but to rebuild the whole zip again.
|
||||
# very sucky, but maybe another solution can be found
|
||||
|
||||
print "{0} already exists in {1}. Rebuilding it...".format( self.ci_xml_filename, self.path)
|
||||
zin = zipfile.ZipFile (self.path, 'r')
|
||||
zout = zipfile.ZipFile ('tmpnew.zip', 'w')
|
||||
for item in zin.infolist():
|
||||
buffer = zin.read(item.filename)
|
||||
if ( item.filename != self.ci_xml_filename ):
|
||||
zout.writestr(item, buffer)
|
||||
|
||||
# now write out the new xml file
|
||||
zout.writestr( self.ci_xml_filename, cix_string )
|
||||
|
||||
#preserve the old comment
|
||||
zout.comment = zin.comment
|
||||
|
||||
zout.close()
|
||||
zin.close()
|
||||
|
||||
# replace with the new file
|
||||
os.remove( self.path )
|
||||
os.rename( 'tmpnew.zip', self.path )
|
||||
|
||||
def hasCIX(self):
|
||||
|
||||
has = False
|
||||
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
if self.ci_xml_filename in zf.namelist():
|
||||
has = True
|
||||
zf.close()
|
||||
|
||||
return has
|
||||
|
||||
def hasCBI(self):
|
||||
if (not self.isZip() ):
|
||||
return False
|
||||
zf = zipfile.ZipFile( self.path, 'r' )
|
||||
comment = zf.comment
|
||||
zf.close()
|
||||
|
||||
return ComicBookInfo().validateString( comment )
|
||||
|
||||
def metadataFromFilename( self ):
|
||||
|
||||
metadata = GenericMetadata()
|
||||
|
||||
fnp = FileNameParser()
|
||||
fnp.parseFilename( self.path )
|
||||
|
||||
if fnp.issue != "":
|
||||
metadata.issueNumber = fnp.issue
|
||||
if fnp.series != "":
|
||||
metadata.series = fnp.series
|
||||
if fnp.volume != "":
|
||||
metadata.volumeNumber = fnp.volume
|
||||
if fnp.year != "":
|
||||
metadata.publicationYear = fnp.year
|
||||
|
||||
metadata.isEmpty = False
|
||||
|
||||
return metadata
|
118
comicbookinfo.py
Normal file
118
comicbookinfo.py
Normal file
@ -0,0 +1,118 @@
|
||||
"""
|
||||
A python class to encapsulate the ComicBookInfo data and file handling
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
|
||||
from genericmetadata import GenericMetadata
|
||||
import utils
|
||||
|
||||
class ComicBookInfo:
|
||||
|
||||
|
||||
def metadataFromString( self, string ):
|
||||
|
||||
cbi_container = json.loads( unicode(string, 'utf-8') )
|
||||
|
||||
metadata = GenericMetadata()
|
||||
|
||||
cbi = cbi_container[ 'ComicBookInfo/1.0' ]
|
||||
|
||||
#helper func
|
||||
# If item is not in CBI, return None
|
||||
def xlate( cbi_entry):
|
||||
if cbi_entry in cbi:
|
||||
return cbi[cbi_entry]
|
||||
else:
|
||||
return None
|
||||
|
||||
metadata.series = xlate( 'series' )
|
||||
metadata.title = xlate( 'title' )
|
||||
metadata.issueNumber = xlate( 'issue' )
|
||||
metadata.publisher = xlate( 'publisher' )
|
||||
metadata.publicationMonth = xlate( 'publicationMonth' )
|
||||
metadata.publicationYear = xlate( 'publicationYear' )
|
||||
metadata.issueCount = xlate( 'numberOfIssues' )
|
||||
metadata.comments = xlate( 'comments' )
|
||||
metadata.credits = xlate( 'credits' )
|
||||
metadata.genre = xlate( 'genre' )
|
||||
metadata.volumeNumber = xlate( 'volume' )
|
||||
metadata.volumeCount = xlate( 'numberOfVolumes' )
|
||||
metadata.language = xlate( 'language' )
|
||||
metadata.country = xlate( 'country' )
|
||||
metadata.criticalRating = xlate( 'rating' )
|
||||
metadata.tags = xlate( 'tags' )
|
||||
|
||||
#need to massage the language string to be ISO
|
||||
if metadata.language is not None:
|
||||
# reverse look-up
|
||||
pattern = metadata.language
|
||||
metadata.language = None
|
||||
for key in utils.getLanguageDict():
|
||||
if utils.getLanguageDict()[ key ] == pattern.encode('utf-8'):
|
||||
metadata.language = key
|
||||
break
|
||||
|
||||
metadata.isEmpty = False
|
||||
|
||||
return metadata
|
||||
|
||||
def stringFromMetadata( self, metadata ):
|
||||
|
||||
cbi_container = self.createJSONDictionary( metadata )
|
||||
return json.dumps( cbi_container )
|
||||
|
||||
#verify that the string actually contains CBI data in JSON format
|
||||
def validateString( self, string ):
|
||||
|
||||
try:
|
||||
cbi_container = json.loads( string )
|
||||
except:
|
||||
return False
|
||||
|
||||
return ( 'ComicBookInfo/1.0' in cbi_container )
|
||||
|
||||
|
||||
def createJSONDictionary( self, metadata ):
|
||||
|
||||
# Create the dictionary that we will convert to JSON text
|
||||
cbi = dict()
|
||||
cbi_container = {'appID' : 'ComicTagger/0.1',
|
||||
'lastModified' : str(datetime.now()),
|
||||
'ComicBookInfo/1.0' : cbi }
|
||||
|
||||
#helper func
|
||||
def assign( cbi_entry, md_entry):
|
||||
if md_entry is not None:
|
||||
cbi[cbi_entry] = md_entry
|
||||
|
||||
assign( 'series', metadata.series )
|
||||
assign( 'title', metadata.title )
|
||||
assign( 'issue', metadata.issueNumber )
|
||||
assign( 'publisher', metadata.publisher )
|
||||
assign( 'publicationMonth', metadata.publicationMonth )
|
||||
assign( 'publicationYear', metadata.publicationYear )
|
||||
assign( 'numberOfIssues', metadata.issueCount )
|
||||
assign( 'comments', metadata.comments )
|
||||
assign( 'genre', metadata.genre )
|
||||
assign( 'volume', metadata.volumeNumber )
|
||||
assign( 'numberOfVolumes', metadata.volumeCount )
|
||||
assign( 'language', utils.getLanguageFromISO(metadata.language) )
|
||||
assign( 'country', metadata.country )
|
||||
assign( 'rating', metadata.criticalRating )
|
||||
assign( 'credits', metadata.credits )
|
||||
assign( 'tags', metadata.tags )
|
||||
|
||||
return cbi_container
|
||||
|
||||
|
||||
def writeToExternalFile( self, filename, metadata ):
|
||||
|
||||
cbi_container = self.createJSONDictionary(metadata)
|
||||
|
||||
f = open(filename, 'w')
|
||||
f.write(json.dumps(cbi_container, indent=4))
|
||||
f.close
|
||||
|
248
comicinfoxml.py
Normal file
248
comicinfoxml.py
Normal file
@ -0,0 +1,248 @@
|
||||
"""
|
||||
A python class to encapsulate ComicRack's ComicInfo.xml data and file handling
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
from pprint import pprint
|
||||
import xml.etree.ElementTree as ET
|
||||
from genericmetadata import GenericMetadata
|
||||
|
||||
|
||||
class ComicInfoXml:
|
||||
|
||||
def metadataFromString( self, string ):
|
||||
|
||||
tree = ET.ElementTree(ET.fromstring( string ))
|
||||
return self.convertXMLToMetadata( tree )
|
||||
|
||||
def stringFromMetadata( self, metadata ):
|
||||
|
||||
tree = self.convertMetadataToXML( self, metadata )
|
||||
return ET.tostring(tree.getroot())
|
||||
|
||||
def indent( self, elem, level=0 ):
|
||||
# for making the XML output readable
|
||||
i = "\n" + level*" "
|
||||
if len(elem):
|
||||
if not elem.text or not elem.text.strip():
|
||||
elem.text = i + " "
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
for elem in elem:
|
||||
self.indent( elem, level+1 )
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
else:
|
||||
if level and (not elem.tail or not elem.tail.strip()):
|
||||
elem.tail = i
|
||||
|
||||
def convertMetadataToXML( self, filename, metadata ):
|
||||
|
||||
#shorthand for the metadata
|
||||
md = metadata
|
||||
|
||||
# build a tree structure
|
||||
root = ET.Element("ComicInfo")
|
||||
|
||||
|
||||
#helper func
|
||||
def assign( cix_entry, md_entry):
|
||||
if md_entry is not None:
|
||||
ET.SubElement(root, cix_entry).text = u"{0}".format(md_entry)
|
||||
|
||||
assign( 'Series', md.series )
|
||||
assign( 'Number', md.issueNumber )
|
||||
assign( 'Title', md.title )
|
||||
assign( 'Count', md.issueCount )
|
||||
assign( 'Volume', md.volumeNumber )
|
||||
assign( 'AlternateSeries', md.alternateSeries )
|
||||
assign( 'AlternateNumber', md.alternateNumber )
|
||||
assign( 'AlternateCount', md.alternateCount )
|
||||
assign( 'Summary', md.comments )
|
||||
assign( 'Notes', md.notes )
|
||||
assign( 'Year', md.publicationYear )
|
||||
assign( 'Month', md.publicationMonth )
|
||||
assign( 'Publisher', md.publisher )
|
||||
assign( 'Imprint', md.imprint )
|
||||
assign( 'Genre', md.genre )
|
||||
assign( 'Web', md.webLink )
|
||||
assign( 'PageCount', md.pageCount )
|
||||
assign( 'Format', md.format )
|
||||
assign( 'LanguageISO', md.language )
|
||||
assign( 'Manga', md.manga )
|
||||
assign( 'Characters', md.characters )
|
||||
assign( 'Teams', md.teams )
|
||||
assign( 'Locations', md.locations )
|
||||
assign( 'ScanInformation', md.scanInfo )
|
||||
assign( 'StoryArc', md.storyArc )
|
||||
assign( 'SeriesGroup', md.seriesGroup )
|
||||
assign( 'AgeRating', md.maturityRating )
|
||||
|
||||
if md.blackAndWhite is not None and md.blackAndWhite:
|
||||
ET.SubElement(root, 'BlackAndWhite').text = "Yes"
|
||||
|
||||
# need to specially process the credits, since they are structured differently than CIX
|
||||
credit_writer = None
|
||||
credit_penciller = None
|
||||
credit_inker = None
|
||||
credit_colorist = None
|
||||
credit_letterer = None
|
||||
credit_cover = None
|
||||
credit_editor = None
|
||||
|
||||
for credit in metadata.credits:
|
||||
if credit['role'].title() in set( ['Writer', 'Plotter'] ):
|
||||
if credit_writer == None:
|
||||
credit_writer = ET.SubElement(root, 'Writer')
|
||||
credit_writer.text = ""
|
||||
if len(credit_writer.text) > 0:
|
||||
credit_writer.text += ", "
|
||||
credit_writer.text += credit['person']
|
||||
|
||||
if credit['role'].title() in set( [ 'Inker', 'Artist', 'Finishes' ] ):
|
||||
if credit_inker == None:
|
||||
credit_inker = ET.SubElement(root, 'Inker')
|
||||
credit_inker.text = ""
|
||||
if len(credit_inker.text) > 0:
|
||||
credit_inker.text += ", "
|
||||
credit_inker.text += credit['person']
|
||||
|
||||
if credit['role'].title() in set( [ 'Artist', 'Penciller', 'Penciler', 'Breakdowns' ] ):
|
||||
if credit_penciller == None:
|
||||
credit_penciller = ET.SubElement(root, 'Penciller')
|
||||
credit_penciller.text = ""
|
||||
if len(credit_penciller.text) > 0:
|
||||
credit_penciller.text += ", "
|
||||
credit_penciller.text += credit['person']
|
||||
|
||||
if credit['role'].title() in set( [ 'Colorist', 'Colourist' ]):
|
||||
if credit_colorist == None:
|
||||
credit_colorist = ET.SubElement(root, 'Colorist')
|
||||
credit_colorist.text = ""
|
||||
if len(credit_colorist.text) > 0:
|
||||
credit_colorist.text += ", "
|
||||
credit_colorist.text += credit['person']
|
||||
|
||||
if credit['role'].title() == 'Letterer':
|
||||
if credit_letterer == None:
|
||||
credit_letterer = ET.SubElement(root, 'Letterer')
|
||||
credit_letterer.text = ""
|
||||
if len(credit_letterer.text) > 0:
|
||||
credit_letterer.text += ", "
|
||||
credit_letterer.text += credit['person']
|
||||
|
||||
if credit['role'].title() in set( [ 'Cover', 'Covers', 'CoverArtist', 'Cover Artist' ] ):
|
||||
if credit_cover == None:
|
||||
credit_cover = ET.SubElement(root, 'CoverArtist')
|
||||
credit_cover.text = ""
|
||||
if len(credit_cover.text) > 0:
|
||||
credit_cover.text += ", "
|
||||
credit_cover.text += credit['person']
|
||||
|
||||
if credit['role'].title() in set( [ 'Editor'] ):
|
||||
if credit_editor == None:
|
||||
credit_editor = ET.SubElement(root, 'Editor')
|
||||
credit_editor.text = ""
|
||||
if len(credit_editor.text) > 0:
|
||||
credit_editor.text += ", "
|
||||
credit_editor.text += credit['person']
|
||||
|
||||
# !!!ATB todo: loop and add the page entries under pages node
|
||||
#pages = ET.SubElement(root, 'Pages')
|
||||
|
||||
# self pretty-print
|
||||
self.indent(root)
|
||||
|
||||
# wrap it in an ElementTree instance, and save as XML
|
||||
tree = ET.ElementTree(root)
|
||||
return tree
|
||||
|
||||
|
||||
def convertXMLToMetadata( self, tree ):
|
||||
|
||||
root = tree.getroot()
|
||||
|
||||
if root.tag != 'ComicInfo':
|
||||
raise 1
|
||||
return None
|
||||
|
||||
metadata = GenericMetadata()
|
||||
md = metadata
|
||||
|
||||
|
||||
# Helper function
|
||||
def xlate( tag ):
|
||||
node = root.find( tag )
|
||||
if node is not None:
|
||||
return node.text
|
||||
else:
|
||||
return None
|
||||
|
||||
md.series = xlate( 'Series' )
|
||||
md.title = xlate( 'Title' )
|
||||
md.issueNumber = xlate( 'Number' )
|
||||
md.issueCount = xlate( 'Count' )
|
||||
md.volumeNumber = xlate( 'Volume' )
|
||||
md.alternateSeries = xlate( 'AlternateSeries' )
|
||||
md.alternateNumber = xlate( 'AlternateNumber' )
|
||||
md.alternateCount = xlate( 'AlternateCount' )
|
||||
md.comments = xlate( 'Summary' )
|
||||
md.notes = xlate( 'Notes' )
|
||||
md.publicationYear = xlate( 'Year' )
|
||||
md.publicationMonth = xlate( 'Month' )
|
||||
md.publisher = xlate( 'Publisher' )
|
||||
md.imprint = xlate( 'Imprint' )
|
||||
md.genre = xlate( 'Genre' )
|
||||
md.webLink = xlate( 'Web' )
|
||||
md.language = xlate( 'LanguageISO' )
|
||||
md.format = xlate( 'Format' )
|
||||
md.manga = xlate( 'Manga' )
|
||||
md.characters = xlate( 'Characters' )
|
||||
md.teams = xlate( 'Teams' )
|
||||
md.locations = xlate( 'Locations' )
|
||||
md.pageCount = xlate( 'PageCount' )
|
||||
md.scanInfo = xlate( 'ScanInformation' )
|
||||
md.storyArc = xlate( 'StoryArc' )
|
||||
md.seriesGroup = xlate( 'SeriesGroup' )
|
||||
md.maturityRating = xlate( 'AgeRating' )
|
||||
|
||||
tmp = xlate( 'BlackAndWhite' )
|
||||
md.blackAndWhite = False
|
||||
if tmp is not None and tmp.lower() in [ "yes", "true", "1" ]:
|
||||
md.blackAndWhite = True
|
||||
# Now extract the credit info
|
||||
for n in root:
|
||||
if ( n.tag == 'Writer' or
|
||||
n.tag == 'Penciller' or
|
||||
n.tag == 'Inker' or
|
||||
n.tag == 'Colorist' or
|
||||
n.tag == 'Letterer' or
|
||||
n.tag == 'Editor'
|
||||
):
|
||||
for name in n.text.split(','):
|
||||
metadata.addCredit( name.strip(), n.tag )
|
||||
|
||||
if n.tag == 'CoverArtist':
|
||||
for name in n.text.split(','):
|
||||
metadata.addCredit( name.strip(), "Cover" )
|
||||
|
||||
#!!! ATB parse page data now
|
||||
|
||||
|
||||
|
||||
metadata.isEmpty = False
|
||||
|
||||
return metadata
|
||||
|
||||
def writeToExternalFile( self, filename, metadata ):
|
||||
|
||||
tree = self.convertMetadataToXML( self, metadata )
|
||||
#ET.dump(tree)
|
||||
tree.write(filename, encoding='utf-8')
|
||||
|
||||
def readFromExternalFile( self, filename ):
|
||||
|
||||
tree = ET.parse( filename )
|
||||
return self.convertXMLToMetadata( tree )
|
||||
|
186
comicvinetalker.py
Normal file
186
comicvinetalker.py
Normal file
@ -0,0 +1,186 @@
|
||||
import json
|
||||
from pprint import pprint
|
||||
import urllib2, urllib
|
||||
import math
|
||||
import re
|
||||
|
||||
import utils
|
||||
|
||||
from genericmetadata import GenericMetadata
|
||||
|
||||
class ComicVineTalker:
|
||||
|
||||
api_key = '67ac5baeba16a2f56acf7ff136a1d591324c2253'
|
||||
|
||||
def searchForSeries( self, series_name ):
|
||||
|
||||
series_name = urllib.quote_plus(str(series_name))
|
||||
search_url = "http://api.comicvine.com/search/?api_key=" + self.api_key + "&format=json&resources=volume&query=" + series_name + "&field_list=name,id,start_year,publisher,image,description,count_of_issues&sort=start_year"
|
||||
|
||||
resp = urllib2.urlopen(search_url)
|
||||
content = resp.read()
|
||||
|
||||
cv_response = json.loads(content)
|
||||
|
||||
if cv_response[ 'status_code' ] != 1:
|
||||
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
|
||||
return None
|
||||
|
||||
search_results = list()
|
||||
|
||||
# see http://api.comicvine.com/documentation/#handling_responses
|
||||
|
||||
limit = cv_response['limit']
|
||||
current_result_count = cv_response['number_of_page_results']
|
||||
total_result_count = cv_response['number_of_total_results']
|
||||
|
||||
print ("Found {0} of {1} results".format( cv_response['number_of_page_results'], cv_response['number_of_total_results']))
|
||||
search_results.extend( cv_response['results'])
|
||||
offset = 0
|
||||
|
||||
# see if we need to keep asking for more pages...
|
||||
while ( current_result_count < total_result_count ):
|
||||
print ("getting another page of results...")
|
||||
offset += limit
|
||||
resp = urllib2.urlopen( search_url + "&offset="+str(offset) )
|
||||
content = resp.read()
|
||||
|
||||
cv_response = json.loads(content)
|
||||
|
||||
if cv_response[ 'status_code' ] != 1:
|
||||
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
|
||||
return None
|
||||
search_results.extend( cv_response['results'])
|
||||
current_result_count += cv_response['number_of_page_results']
|
||||
|
||||
|
||||
#for record in search_results:
|
||||
# print( "{0}: {1} ({2})".format(record['id'], smart_str(record['name']) , record['start_year'] ) )
|
||||
# print( "{0}: {1} ({2})".format(record['id'], record['name'] , record['start_year'] ) )
|
||||
|
||||
#print "{0}: {1} ({2})".format(search_results['results'][0]['id'], smart_str(search_results['results'][0]['name']) , search_results['results'][0]['start_year'] )
|
||||
|
||||
return search_results
|
||||
|
||||
def fetchVolumeData( self, series_id ):
|
||||
|
||||
volume_url = "http://api.comicvine.com/volume/" + str(series_id) + "/?api_key=" + self.api_key + "&format=json"
|
||||
#print "search_url = : ", volume_url
|
||||
|
||||
resp = urllib2.urlopen(volume_url)
|
||||
content = resp.read()
|
||||
|
||||
cv_response = json.loads(content)
|
||||
|
||||
if cv_response[ 'status_code' ] != 1:
|
||||
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
|
||||
return None
|
||||
|
||||
volume_results = cv_response['results']
|
||||
|
||||
return volume_results
|
||||
|
||||
|
||||
def fetchIssueData( self, series_id, issue_number ):
|
||||
|
||||
volume_results = self.fetchVolumeData( series_id )
|
||||
|
||||
found = False
|
||||
for record in volume_results['issues']:
|
||||
if float(record['issue_number']) == float(issue_number):
|
||||
found = True
|
||||
break
|
||||
|
||||
if (found):
|
||||
issue_url = "http://api.comicvine.com/issue/" + str(record['id']) + "/?api_key=" + self.api_key + "&format=json"
|
||||
resp = urllib2.urlopen(issue_url)
|
||||
content = resp.read()
|
||||
cv_response = json.loads(content)
|
||||
if cv_response[ 'status_code' ] != 1:
|
||||
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
|
||||
return None
|
||||
issue_results = cv_response['results']
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
# now, map the comicvine data to generic metadata
|
||||
metadata = GenericMetadata()
|
||||
|
||||
metadata.series = issue_results['volume']['name']
|
||||
|
||||
# format the issue number string nicely, since it's usually something like "2.00"
|
||||
num_f = float(issue_results['issue_number'])
|
||||
num_s = str( int(math.floor(num_f)) )
|
||||
if math.floor(num_f) != num_f:
|
||||
num_s = str( num_f )
|
||||
|
||||
metadata.issueNumber = num_s
|
||||
metadata.title = issue_results['name']
|
||||
metadata.publisher = volume_results['publisher']['name']
|
||||
metadata.publicationMonth = issue_results['publish_month']
|
||||
metadata.publicationYear = issue_results['publish_year']
|
||||
metadata.issueCount = volume_results['count_of_issues']
|
||||
metadata.comments = self.cleanup_html(issue_results['description'])
|
||||
|
||||
metadata.notes = "Tagged with ComicTagger using info from Comic Vine:\n"
|
||||
metadata.notes += issue_results['site_detail_url']
|
||||
|
||||
metadata.webLink = issue_results['site_detail_url']
|
||||
|
||||
person_credits = issue_results['person_credits']
|
||||
for person in person_credits:
|
||||
for role in person['roles']:
|
||||
# can we determine 'primary' from CV??
|
||||
role_name = role['role'].title()
|
||||
metadata.addCredit( person['name'], role['role'].title(), False )
|
||||
|
||||
character_credits = issue_results['character_credits']
|
||||
character_list = list()
|
||||
for character in character_credits:
|
||||
character_list.append( character['name'] )
|
||||
metadata.characters = utils.listToString( character_list )
|
||||
|
||||
team_credits = issue_results['team_credits']
|
||||
team_list = list()
|
||||
for team in team_credits:
|
||||
team_list.append( team['name'] )
|
||||
metadata.teams = utils.listToString( team_list )
|
||||
|
||||
location_credits = issue_results['location_credits']
|
||||
location_list = list()
|
||||
for location in location_credits:
|
||||
location_list.append( location['name'] )
|
||||
metadata.locations = utils.listToString( location_list )
|
||||
|
||||
story_arc_credits = issue_results['story_arc_credits']
|
||||
for arc in story_arc_credits:
|
||||
metadata.storyArc = arc['name']
|
||||
#just use the first one, if at all
|
||||
break
|
||||
|
||||
return metadata
|
||||
|
||||
def cleanup_html( self, string):
|
||||
p = re.compile(r'<[^<]*?>')
|
||||
|
||||
newstring = p.sub('',string)
|
||||
|
||||
newstring = newstring.replace(' ',' ')
|
||||
newstring = newstring.replace('&','&')
|
||||
|
||||
return newstring
|
||||
|
||||
def fetchIssueCoverURL( self, issue_id ):
|
||||
|
||||
issue_url = "http://api.comicvine.com/issue/" + str(issue_id) + "/?api_key=" + self.api_key + "&format=json&field_list=image"
|
||||
resp = urllib2.urlopen(issue_url)
|
||||
content = resp.read()
|
||||
cv_response = json.loads(content)
|
||||
if cv_response[ 'status_code' ] != 1:
|
||||
print ( "Comic Vine query failed with error: [{0}]. ".format( cv_response[ 'error' ] ))
|
||||
return None
|
||||
|
||||
return cv_response['results']['image']['super_url']
|
||||
|
||||
|
124
filenameparser.py
Normal file
124
filenameparser.py
Normal file
@ -0,0 +1,124 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# This is horrible, and needs to be re-written, but, well, it mostly works!
|
||||
|
||||
import re
|
||||
import os
|
||||
from urllib import unquote
|
||||
|
||||
class FileNameParser:
|
||||
def fixSpaces( self, string ):
|
||||
placeholders = ['[-_]',' +']
|
||||
for ph in placeholders:
|
||||
string = re.sub(ph, ' ', string )
|
||||
return string.strip()
|
||||
|
||||
# check for silly .1 or .5 style issue strings
|
||||
# allow up to 5 chars total
|
||||
def isPointIssue( self, word ):
|
||||
ret = False
|
||||
try:
|
||||
float(word)
|
||||
if (len(word) < 5 and not word.isdigit()):
|
||||
ret = True
|
||||
except ValueError:
|
||||
pass
|
||||
return ret
|
||||
|
||||
def getIssueNumber( self,filename ):
|
||||
|
||||
found = False
|
||||
issue = ''
|
||||
# guess based on position
|
||||
|
||||
# replace any name seperators with spaces
|
||||
tmpstr = self.fixSpaces(filename)
|
||||
word_list = tmpstr.split(' ')
|
||||
|
||||
# assume the last number in the filename that is under 4 digits is the issue number
|
||||
for word in reversed(word_list):
|
||||
if (
|
||||
(word.isdigit() and len(word) < 4) or
|
||||
(self.isPointIssue(word))
|
||||
):
|
||||
issue = word
|
||||
found = True
|
||||
#print 'Assuming issue number is ' + str(issue) + ' based on the position.'
|
||||
break
|
||||
|
||||
if not found:
|
||||
# try a regex
|
||||
issnum = re.search('(?<=[_#\s-])(\d+[a-zA-Z]|\d+\.\d|\d+)', filename)
|
||||
if issnum:
|
||||
issue = issnum.group()
|
||||
found = True
|
||||
#print 'Got the issue using regex. Issue is ' + issue
|
||||
|
||||
return issue.strip()
|
||||
|
||||
def getSeriesName(self, filename, issue ):
|
||||
|
||||
# use the issue number string to split the filename string
|
||||
# assume first element of list is the series name, plus cruft
|
||||
|
||||
#!!! this could fail in the case of small numerics in the series name!!!
|
||||
|
||||
tmpstr = self.fixSpaces(filename)
|
||||
if issue != "":
|
||||
series = tmpstr.split(issue)[0]
|
||||
else:
|
||||
# no issue to work off of
|
||||
#!!! TODO we should look for the year, and split from that
|
||||
# and if that doesn't exist, remove parenthetical words
|
||||
series = tmpstr
|
||||
|
||||
volume = ""
|
||||
|
||||
series = series.rstrip("#")
|
||||
|
||||
# search for volume number
|
||||
match = re.search('(?<=v)(\d+)\s*$', series)
|
||||
if match:
|
||||
volume = match.group()
|
||||
series = series.split("v"+volume)[0]
|
||||
volume = volume.lstrip("0")
|
||||
|
||||
return series.strip(), volume.strip()
|
||||
|
||||
def getYear( self,filename):
|
||||
|
||||
year = ""
|
||||
# look for four digit number with "(" ")" or "--" around it
|
||||
match = re.search('(\(\d\d\d\d\))|(--\d\d\d\d--)', filename)
|
||||
if match:
|
||||
year = match.group()
|
||||
# remove non-numerics
|
||||
year = re.sub("[^0-9]", "", year)
|
||||
return year
|
||||
|
||||
self.issue = ""
|
||||
self.series = ""
|
||||
self.volume = ""
|
||||
self.year = ""
|
||||
|
||||
|
||||
def parseFilename( self, filename ):
|
||||
|
||||
# remove the path
|
||||
filename = os.path.basename(filename)
|
||||
|
||||
# remove the extension
|
||||
filename = os.path.splitext(filename)[0]
|
||||
|
||||
#url decvocde, just in case
|
||||
filename = unquote(filename)
|
||||
|
||||
self.issue = self.getIssueNumber(filename)
|
||||
self.series, self.volume = self.getSeriesName(filename, self.issue)
|
||||
self.year = self.getYear(filename)
|
||||
|
||||
if self.issue != "":
|
||||
# strip off leading zeros
|
||||
self.issue = self.issue.lstrip("0")
|
||||
if self.issue == "":
|
||||
self.issue = "0"
|
96
genericmetadata.py
Normal file
96
genericmetadata.py
Normal file
@ -0,0 +1,96 @@
|
||||
"""
|
||||
A python class for internal metadata storage
|
||||
|
||||
The goal of this class is to handle ALL the data that might come from various
|
||||
tagging schemes and databases, such as ComicVine or GCD. This makes conversion
|
||||
possible, however lossy it might be
|
||||
|
||||
|
||||
"""
|
||||
from sets import Set
|
||||
|
||||
|
||||
# These page info classes are exactly the same as the CIX scheme, since it's unique
|
||||
class PageType:
|
||||
FrontCover = "FrontCover"
|
||||
InnerCover = "InnerCover"
|
||||
Roundup = "Roundup"
|
||||
Story = "Story"
|
||||
Advertisment = "Advertisment"
|
||||
Story = "Story"
|
||||
Editorial = "Editorial"
|
||||
Letters = "Letters"
|
||||
Preview = "Preview"
|
||||
BackCover = "BackCover"
|
||||
Other = "Other"
|
||||
Deleted = "Deleted"
|
||||
|
||||
class PageInfo:
|
||||
SeqNum = 0
|
||||
Type = PageType.FrontCover
|
||||
DoublePage = False
|
||||
ImageSize = 0
|
||||
Key = ""
|
||||
ImageWidth = 0
|
||||
ImageHeight = 0
|
||||
|
||||
|
||||
class GenericMetadata:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.isEmpty = True
|
||||
self.tagOrigin = None
|
||||
|
||||
self.series = None
|
||||
self.issueNumber = None
|
||||
self.title = None
|
||||
self.publisher = None
|
||||
self.publicationMonth = None
|
||||
self.publicationYear = None
|
||||
self.issueCount = None
|
||||
self.volumeNumber = None
|
||||
self.genre = None
|
||||
self.language = None # 2 letter iso code
|
||||
self.comments = None # use same way as Summary in CIX
|
||||
|
||||
self.volumeCount = None
|
||||
self.criticalRating = None
|
||||
self.country = None
|
||||
|
||||
self.alternateSeries = None
|
||||
self.alternateNumber = None
|
||||
self.alternateCount = None
|
||||
self.imprint = None
|
||||
self.notes = None
|
||||
self.webLink = None
|
||||
self.format = None
|
||||
self.manga = None
|
||||
self.blackAndWhite = None
|
||||
self.pageCount = None
|
||||
self.maturityRating = None
|
||||
|
||||
self.storyArc = None
|
||||
self.seriesGroup = None
|
||||
self.scanInfo = None
|
||||
|
||||
self.characters = None
|
||||
self.teams = None
|
||||
self.locations = None
|
||||
|
||||
self.credits = list()
|
||||
self.tags = list()
|
||||
self.pages = list()
|
||||
|
||||
|
||||
def addCredit( self, person, role, primary = False ):
|
||||
|
||||
credit = dict()
|
||||
credit['person'] = person
|
||||
credit['role'] = role
|
||||
if primary:
|
||||
credit['primary'] = primary
|
||||
|
||||
self.credits.append(credit)
|
||||
|
||||
|
118
issueselectionwindow.py
Normal file
118
issueselectionwindow.py
Normal file
@ -0,0 +1,118 @@
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui, uic
|
||||
|
||||
from PyQt4.QtCore import QUrl
|
||||
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
||||
|
||||
from comicvinetalker import *
|
||||
|
||||
class IssueSelectionWindow(QtGui.QDialog):
|
||||
|
||||
volume_id = 0
|
||||
|
||||
def __init__(self, parent, series_id, issue_number):
|
||||
super(IssueSelectionWindow, self).__init__(parent)
|
||||
|
||||
uic.loadUi('issueselectionwindow.ui', self)
|
||||
|
||||
self.series_id = series_id
|
||||
|
||||
if issue_number is None or issue_number == "":
|
||||
self.issue_number = 1
|
||||
else:
|
||||
self.issue_number = issue_number
|
||||
|
||||
self.initial_id = None
|
||||
self.performQuery()
|
||||
|
||||
self.twList.resizeColumnsToContents()
|
||||
self.twList.currentItemChanged.connect(self.currentItemChanged)
|
||||
self.twList.cellDoubleClicked.connect(self.cellDoubleClicked)
|
||||
|
||||
#now that the list has been sorted, find the initial record, and select it
|
||||
if self.initial_id is None:
|
||||
self.twList.selectRow( 0 )
|
||||
else:
|
||||
for r in range(0, self.twList.rowCount()):
|
||||
issue_id, b = self.twList.item( r, 0 ).data( QtCore.Qt.UserRole ).toInt()
|
||||
if (issue_id == self.initial_id):
|
||||
self.twList.selectRow( r )
|
||||
break
|
||||
|
||||
|
||||
def performQuery( self ):
|
||||
|
||||
while self.twList.rowCount() > 0:
|
||||
self.twList.removeRow(0)
|
||||
|
||||
comicVine = ComicVineTalker()
|
||||
volume_data = comicVine.fetchVolumeData( self.series_id )
|
||||
self.issue_list = volume_data['issues']
|
||||
|
||||
self.twList.setSortingEnabled(False)
|
||||
|
||||
row = 0
|
||||
for record in self.issue_list:
|
||||
self.twList.insertRow(row)
|
||||
|
||||
item_text = record['issue_number']
|
||||
item = QtGui.QTableWidgetItem(item_text)
|
||||
item.setData( QtCore.Qt.UserRole ,record['id'])
|
||||
item.setData(QtCore.Qt.DisplayRole, float(item_text))
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
|
||||
self.twList.setItem(row, 0, item)
|
||||
|
||||
item_text = u"{0}".format(record['name'])
|
||||
item = QtGui.QTableWidgetItem(item_text)
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
|
||||
self.twList.setItem(row, 1, item)
|
||||
|
||||
record['url'] = None
|
||||
|
||||
if float(record['issue_number']) == float(self.issue_number):
|
||||
self.initial_id = record['id']
|
||||
|
||||
row += 1
|
||||
|
||||
#TODO look for given issue in list, and select that one
|
||||
|
||||
self.twList.setSortingEnabled(True)
|
||||
self.twList.sortItems( 0 , QtCore.Qt.AscendingOrder )
|
||||
|
||||
def cellDoubleClicked( self, r, c ):
|
||||
self.accept()
|
||||
|
||||
def currentItemChanged( self, curr, prev ):
|
||||
|
||||
if curr is None:
|
||||
return
|
||||
if prev is not None and prev.row() == curr.row():
|
||||
return
|
||||
|
||||
|
||||
self.issue_id, b = self.twList.item( curr.row(), 0 ).data( QtCore.Qt.UserRole ).toInt()
|
||||
|
||||
# list selection was changed, update the the issue cover
|
||||
for record in self.issue_list:
|
||||
if record['id'] == self.issue_id:
|
||||
|
||||
self.issue_number = record['issue_number']
|
||||
|
||||
# We don't yet have an image URL for this issue. Make a request for URL, and hold onto it
|
||||
# TODO: this should be reworked... too much UI latency, maybe chain the NAMs??
|
||||
if record['url'] == None:
|
||||
record['url'] = ComicVineTalker().fetchIssueCoverURL( self.issue_id )
|
||||
|
||||
self.labelThumbnail.setText("loading...")
|
||||
self.nam = QNetworkAccessManager()
|
||||
|
||||
self.nam.finished.connect(self.finishedImageRequest)
|
||||
self.nam.get(QNetworkRequest(QUrl(record['url'])))
|
||||
break
|
||||
|
||||
# called when the image is done loading
|
||||
def finishedImageRequest(self, reply):
|
||||
img = QtGui.QImage()
|
||||
img.loadFromData(reply.readAll())
|
||||
self.labelThumbnail.setPixmap(QtGui.QPixmap(img))
|
||||
|
147
issueselectionwindow.ui
Normal file
147
issueselectionwindow.ui
Normal file
@ -0,0 +1,147 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>dialogIssueSelect</class>
|
||||
<widget class="QDialog" name="dialogIssueSelect">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>843</width>
|
||||
<height>454</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Issue</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>20</y>
|
||||
<width>571</width>
|
||||
<height>392</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||
</widget>
|
||||
<widget class="QLabel" name="labelThumbnail">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>589</x>
|
||||
<y>9</y>
|
||||
<width>241</width>
|
||||
<height>391</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="twList">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>569</width>
|
||||
<height>390</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="rowCount">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Issue</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
<property name="textAlignment">
|
||||
<set>AlignHCenter|AlignVCenter|AlignCenter</set>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>260</x>
|
||||
<y>420</y>
|
||||
<width>569</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>dialogIssueSelect</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>dialogIssueSelect</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
65
options.py
Normal file
65
options.py
Normal file
@ -0,0 +1,65 @@
|
||||
import sys
|
||||
import getopt
|
||||
|
||||
|
||||
class Enum(set):
|
||||
def __getattr__(self, name):
|
||||
if name in self:
|
||||
return name
|
||||
raise AttributeError
|
||||
|
||||
class MetaDataStyle:
|
||||
CBI = 0
|
||||
CIX = 1
|
||||
|
||||
|
||||
class Options:
|
||||
|
||||
def __init__(self):
|
||||
self.data_style = MetaDataStyle.CBI
|
||||
self.no_gui = False
|
||||
|
||||
# Some defaults for testing
|
||||
self.series_name = '' #'Watchmen'
|
||||
self.issue_number = '' #'1'
|
||||
self.filename = '' # "Watchmen #01.cbz"
|
||||
|
||||
def parseCmdLineArgs(self):
|
||||
|
||||
# parse command line options
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "cht:s:i:vf:", ["cli", "help", "type=", "series=", "issue=", "verbose", "file" ])
|
||||
except (getopt.error, msg):
|
||||
print( msg )
|
||||
print( "for help use --help" )
|
||||
sys.exit(2)
|
||||
# process options
|
||||
for o, a in opts:
|
||||
if o in ("-h", "--help"):
|
||||
print( __doc__ )
|
||||
sys.exit(0)
|
||||
if o in ("-v", "--verbose"):
|
||||
print( "Verbose output!" )
|
||||
if o in ("-c", "--cli"):
|
||||
self.no_gui = True
|
||||
if o in ("-s", "--series"):
|
||||
self.series_name = a
|
||||
if o in ("-i", "--issue"):
|
||||
self.issue_number = a
|
||||
if o in ("-f", "--file"):
|
||||
self.filename = a
|
||||
if o in ("-t", "--type"):
|
||||
if a == "cr":
|
||||
self.data_style = MetaDataStyle.CIX
|
||||
elif a == "cbl":
|
||||
self.data_style = MetaDataStyle.CBI
|
||||
else:
|
||||
print( __doc__ )
|
||||
sys.exit(0)
|
||||
|
||||
# process arguments
|
||||
for arg in args:
|
||||
process(arg) # process() is defined elsewhere
|
||||
|
||||
return opts
|
||||
|
88
tagger.py
Executable file
88
tagger.py
Executable file
@ -0,0 +1,88 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
A python script to tag CBZ files
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import json
|
||||
import xml
|
||||
from pprint import pprint
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import signal
|
||||
|
||||
from taggerwindow import TaggerWindow
|
||||
from options import Options, MetaDataStyle
|
||||
from comicarchive import ComicArchive
|
||||
|
||||
from comicvinetalker import ComicVineTalker
|
||||
from comicinfoxml import ComicInfoXml
|
||||
from comicbookinfo import ComicBookInfo
|
||||
|
||||
#-----------------------------
|
||||
def cliProcedure( opts ):
|
||||
|
||||
comicVine = ComicVineTalker()
|
||||
|
||||
cv_search_results = comicVine.searchForSeries( opts.series_name )
|
||||
|
||||
#error checking here: did we get any results?
|
||||
|
||||
# we will eventualy want user interaction to choose the appropriate result, but for now, assume the first one
|
||||
series_id = cv_search_results[0]['id']
|
||||
|
||||
print( "-->Auto-selecting volume ID:", cv_search_results[0]['id'] )
|
||||
print(" ")
|
||||
|
||||
# now get the particular issue data
|
||||
metadata = comicVine.fetchIssueData( series_id, opts.issue_number )
|
||||
|
||||
#pprint( cv_volume_data, indent=4 )
|
||||
|
||||
ca = ComicArchive(opts.filename)
|
||||
ca.writeMetadata( metadata, opts.data_style )
|
||||
|
||||
#debugging
|
||||
ComicBookInfo().writeToExternalFile( "test.json" )
|
||||
ComicBookInfo().writeToExternalFile( "test.xml" )
|
||||
|
||||
#-----------------------------
|
||||
|
||||
def main():
|
||||
opts = Options()
|
||||
opts.parseCmdLineArgs()
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
#ca = ComicArchive( opts.filename )
|
||||
|
||||
#metadata = ca.readMetadata( MetaDataStyle.CBI )
|
||||
#ca.writeMetadata( metadata, MetaDataStyle.CIX )
|
||||
|
||||
#ComicInfoXml().writeToExternalFile( "test.xml", metadata )
|
||||
#ComicBookInfo().writeToExternalFile("test.json", metadata)
|
||||
|
||||
#quit()
|
||||
|
||||
|
||||
|
||||
|
||||
if opts.no_gui:
|
||||
|
||||
cliProcedure( opts )
|
||||
|
||||
else:
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
tagger_window = TaggerWindow( opts )
|
||||
tagger_window.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
|
||||
|
480
taggerwindow.py
Normal file
480
taggerwindow.py
Normal file
@ -0,0 +1,480 @@
|
||||
|
||||
from PyQt4 import QtCore, QtGui, uic
|
||||
|
||||
from volumeselectionwindow import VolumeSelectionWindow
|
||||
from options import Options, MetaDataStyle
|
||||
from genericmetadata import GenericMetadata
|
||||
from comicvinetalker import ComicVineTalker
|
||||
from comicarchive import ComicArchive
|
||||
import utils
|
||||
import locale
|
||||
# this reads the environment and inits the right locale
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
|
||||
|
||||
import os
|
||||
class TaggerWindow( QtGui.QMainWindow):
|
||||
|
||||
appName = "ComicTagger"
|
||||
|
||||
def __init__(self, opts , parent = None):
|
||||
super(TaggerWindow, self).__init__(parent)
|
||||
|
||||
uic.loadUi('taggerwindow.ui', self)
|
||||
self.setWindowIcon(QtGui.QIcon('app.png'))
|
||||
self.center()
|
||||
self.raise_()
|
||||
|
||||
self.opts = opts
|
||||
self.data_style = opts.data_style
|
||||
|
||||
#set up a default metadata object
|
||||
self.metadata = GenericMetadata()
|
||||
self.comic_archive = None
|
||||
|
||||
self.configMenus()
|
||||
self.statusBar()
|
||||
self.updateAppTitle()
|
||||
self.setAcceptDrops(True)
|
||||
self.droppedFile=None
|
||||
|
||||
|
||||
self.populateComboBoxes()
|
||||
|
||||
# hook up the callbacks
|
||||
self.cbDataStyle.currentIndexChanged.connect(self.setDataStyle)
|
||||
|
||||
self.updateStyleTweaks()
|
||||
|
||||
|
||||
self.openArchive( opts.filename )
|
||||
|
||||
# fill in some explicit metadata stuff from our options
|
||||
# this overrides what we just read in
|
||||
if self.metadata.series is None:
|
||||
self.metadata.series = opts.series_name
|
||||
if self.metadata.issueNumber is None:
|
||||
self.metadata.issueNumber = opts.issue_number
|
||||
|
||||
|
||||
def updateAppTitle( self ):
|
||||
if self.comic_archive is None:
|
||||
self.setWindowTitle( self.appName )
|
||||
else:
|
||||
self.setWindowTitle( self.appName + " - " + self.comic_archive.path)
|
||||
|
||||
def configMenus( self):
|
||||
|
||||
# File Menu
|
||||
self.actionExit.setShortcut( 'Ctrl+Q' )
|
||||
self.actionExit.setStatusTip( 'Exit application' )
|
||||
self.actionExit.triggered.connect( QtGui.qApp.quit )
|
||||
|
||||
self.actionLoad.setShortcut( 'Ctrl+O' )
|
||||
self.actionLoad.setStatusTip( 'Load comic archive' )
|
||||
self.actionLoad.triggered.connect( self.selectFile )
|
||||
|
||||
self.actionWrite_Tags.setShortcut( 'Ctrl+S' )
|
||||
self.actionWrite_Tags.setStatusTip( 'Save tags to comic archive' )
|
||||
self.actionWrite_Tags.triggered.connect( self.commitMetadata )
|
||||
|
||||
#self.actionRepackage.setShortcut( )
|
||||
self.actionRepackage.setStatusTip( 'Re-create archive as CBZ' )
|
||||
self.actionRepackage.triggered.connect( self.repackageArchive )
|
||||
|
||||
# Tag Menu
|
||||
self.actionParse_Filename.setShortcut( 'Ctrl+F' )
|
||||
self.actionParse_Filename.setStatusTip( 'Try to extract tags from filename' )
|
||||
self.actionParse_Filename.triggered.connect( self.useFilename )
|
||||
|
||||
self.actionQuery_Online.setShortcut( 'Ctrl+W' )
|
||||
self.actionQuery_Online.setStatusTip( 'Search online for tags' )
|
||||
self.actionQuery_Online.triggered.connect( self.queryOnline )
|
||||
|
||||
# Help Menu
|
||||
self.actionAbout.setShortcut( 'Ctrl+A' )
|
||||
self.actionAbout.setStatusTip( 'Show the ' + self.appName + ' info' )
|
||||
self.actionAbout.triggered.connect( self.aboutApp )
|
||||
|
||||
# ToolBar
|
||||
|
||||
self.toolBar.addAction( self.actionLoad )
|
||||
self.toolBar.addAction( self.actionWrite_Tags )
|
||||
self.toolBar.addAction( self.actionParse_Filename )
|
||||
self.toolBar.addAction( self.actionQuery_Online )
|
||||
|
||||
|
||||
def repackageArchive( self ):
|
||||
QtGui.QMessageBox.information(self, self.tr("Repackage Comic Archive"), self.tr("TBD"))
|
||||
|
||||
def aboutApp( self ):
|
||||
QtGui.QMessageBox.information(self, self.tr("About " + self.appName ), self.tr(self.appName + " 0.1"))
|
||||
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
self.droppedFile=None
|
||||
if event.mimeData().hasUrls():
|
||||
url=event.mimeData().urls()[0]
|
||||
if url.isValid():
|
||||
if url.scheme()=="file":
|
||||
self.droppedFile=url.toLocalFile()
|
||||
event.accept()
|
||||
|
||||
def dropEvent(self, event):
|
||||
#print self.droppedFile # displays the file name
|
||||
self.openArchive( str(self.droppedFile) )
|
||||
|
||||
def openArchive( self, path ):
|
||||
|
||||
if path is None or path == "":
|
||||
return
|
||||
|
||||
ca = ComicArchive( path )
|
||||
|
||||
if ca is not None and ca.seemsToBeAComicArchive():
|
||||
|
||||
self.comic_archive = ca
|
||||
|
||||
self.metadata = self.comic_archive.readMetadata( self.data_style )
|
||||
|
||||
if self.metadata.isEmpty:
|
||||
self.metadata = self.comic_archive.metadataFromFilename( )
|
||||
|
||||
image_data = self.comic_archive.getCoverPage()
|
||||
if not image_data is None:
|
||||
img = QtGui.QImage()
|
||||
img.loadFromData( image_data )
|
||||
self.lblCover.setPixmap(QtGui.QPixmap(img))
|
||||
self.lblCover.setScaledContents(True)
|
||||
|
||||
#!!!ATB should I clear the form???
|
||||
self.metadataToForm()
|
||||
self.updateAppTitle()
|
||||
self.updateInfoBox()
|
||||
|
||||
|
||||
else:
|
||||
QtGui.QMessageBox.information(self, self.tr("Whoops!"), self.tr("That file doesn't appear to be a comic archive!"))
|
||||
|
||||
def updateInfoBox( self ):
|
||||
|
||||
ca = self.comic_archive
|
||||
info_text = os.path.basename( ca.path ) + "\n"
|
||||
info_text += str(ca.getNumberOfPages()) + " pages \n"
|
||||
if ca.hasCIX():
|
||||
info_text += "* ComicRack tags\n"
|
||||
if ca.hasCBI():
|
||||
info_text += "* ComicBookLover tags\n"
|
||||
|
||||
self.lblArchiveInfo.setText( info_text )
|
||||
|
||||
def metadataToForm( self ):
|
||||
# copy the the metadata object into to the form
|
||||
|
||||
#helper func
|
||||
def assignText( field, value):
|
||||
if value is not None:
|
||||
field.setText( u"{0}".format(value) )
|
||||
|
||||
md = self.metadata
|
||||
|
||||
assignText( self.leSeries, md.series )
|
||||
assignText( self.leIssueNum, md.issueNumber )
|
||||
assignText( self.leIssueCount, md.issueCount )
|
||||
assignText( self.leVolumeNum, md.volumeNumber )
|
||||
assignText( self.leVolumeCount, md.volumeCount )
|
||||
assignText( self.leTitle, md.title )
|
||||
assignText( self.lePublisher, md.publisher )
|
||||
assignText( self.lePubMonth, md.publicationMonth )
|
||||
assignText( self.lePubYear, md.publicationYear )
|
||||
assignText( self.leGenre, md.genre )
|
||||
assignText( self.leImprint, md.imprint )
|
||||
assignText( self.teComments, md.comments )
|
||||
assignText( self.teNotes, md.notes )
|
||||
assignText( self.leCriticalRating, md.criticalRating )
|
||||
assignText( self.leMaturityRating, md.maturityRating )
|
||||
assignText( self.leStoryArc, md.storyArc )
|
||||
assignText( self.leScanInfo, md.scanInfo )
|
||||
assignText( self.leSeriesGroup, md.seriesGroup )
|
||||
assignText( self.leAltSeries, md.alternateSeries )
|
||||
assignText( self.leAltIssueNum, md.alternateNumber )
|
||||
assignText( self.leAltIssueCount, md.alternateCount )
|
||||
assignText( self.leWebLink, md.webLink )
|
||||
assignText( self.teCharacters, md.characters )
|
||||
assignText( self.teTeams, md.teams )
|
||||
assignText( self.teLocations, md.locations )
|
||||
assignText( self.leFormat, md.format )
|
||||
|
||||
if md.language is not None:
|
||||
i = self.cbLanguage.findData( md.language )
|
||||
self.cbLanguage.setCurrentIndex( i )
|
||||
|
||||
if md.country is not None:
|
||||
i = self.cbCountry.findText( md.country )
|
||||
self.cbCountry.setCurrentIndex( i )
|
||||
|
||||
if md.manga is not None:
|
||||
i = self.cbManga.findData( md.manga )
|
||||
self.cbManga.setCurrentIndex( i )
|
||||
|
||||
if md.blackAndWhite is not None and md.blackAndWhite:
|
||||
self.cbBW.setChecked( True )
|
||||
|
||||
assignText( self.teTags, utils.listToString( md.tags ) )
|
||||
|
||||
# !!! Should we clear the credits table or just avoid duplicates?
|
||||
while self.twCredits.rowCount() > 0:
|
||||
self.twCredits.removeRow(0)
|
||||
|
||||
if md.credits is not None and len(md.credits) != 0:
|
||||
|
||||
self.twCredits.setSortingEnabled( False )
|
||||
|
||||
row = 0
|
||||
for credit in md.credits:
|
||||
|
||||
# before we add the credit, see if the role-person pair already exists:
|
||||
r = 0
|
||||
while r < self.twCredits.rowCount():
|
||||
if ( self.twCredits.item(r, 0).text() == credit['role'].title() and
|
||||
self.twCredits.item(r, 1).text() == credit['person'] ):
|
||||
break
|
||||
r = r + 1
|
||||
|
||||
# if we didn't make it through the table, it's there alread, so continue without adding
|
||||
if ( r != self.twCredits.rowCount() ):
|
||||
continue
|
||||
|
||||
self.twCredits.insertRow(row)
|
||||
|
||||
item_text = credit['role'].title()
|
||||
item = QtGui.QTableWidgetItem(item_text)
|
||||
#item.setData( QtCore.Qt.UserRole ,record['id'])
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
|
||||
self.twCredits.setItem(row, 0, item)
|
||||
|
||||
item_text = credit['person']
|
||||
item = QtGui.QTableWidgetItem(item_text)
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
|
||||
self.twCredits.setItem(row, 1, item)
|
||||
|
||||
row += 1
|
||||
|
||||
self.twCredits.setSortingEnabled( True )
|
||||
|
||||
|
||||
def formToMetadata( self ):
|
||||
|
||||
#helper func
|
||||
def xlate( data, type_str):
|
||||
s = u"{0}".format(data).strip()
|
||||
if s == "":
|
||||
return None
|
||||
elif type_str == "str":
|
||||
return s
|
||||
else:
|
||||
return int(s)
|
||||
|
||||
# copy the data from the form into the metadata
|
||||
md = self.metadata
|
||||
md.series = xlate( self.leSeries.text(), "str" )
|
||||
md.issueNumber = xlate( self.leIssueNum.text(), "str" )
|
||||
md.issueCount = xlate( self.leIssueCount.text(), "int" )
|
||||
md.volumeNumber = xlate( self.leVolumeNum.text(), "int" )
|
||||
md.volumeCount = xlate( self.leVolumeCount.text(), "int" )
|
||||
md.title = xlate( self.leTitle.text(), "str" )
|
||||
md.publisher = xlate( self.lePublisher.text(), "str" )
|
||||
md.publicationMonth = xlate( self.lePubMonth.text(), "int" )
|
||||
md.publicationYear = xlate( self.lePubYear.text(), "int" )
|
||||
md.genre = xlate( self.leGenre.text(), "str" )
|
||||
md.imprint = xlate( self.leImprint.text(), "str" )
|
||||
md.comments = xlate( self.teComments.toPlainText(), "str" )
|
||||
md.notes = xlate( self.teNotes.toPlainText(), "str" )
|
||||
md.criticalRating = xlate( self.leCriticalRating.text(), "int" )
|
||||
md.maturityRating = xlate( self.leMaturityRating.text(), "str" )
|
||||
|
||||
md.storyArc = xlate( self.leStoryArc.text(), "str" )
|
||||
md.scanInfo = xlate( self.leScanInfo.text(), "str" )
|
||||
md.seriesGroup = xlate( self.leSeriesGroup.text(), "str" )
|
||||
md.alternateSeries = xlate( self.leAltSeries.text(), "str" )
|
||||
md.alternateNumber = xlate( self.leAltIssueNum.text(), "int" )
|
||||
md.alternateCount = xlate( self.leAltIssueCount.text(), "int" )
|
||||
md.webLink = xlate( self.leWebLink.text(), "str" )
|
||||
md.characters = xlate( self.teCharacters.toPlainText(), "str" )
|
||||
md.teams = xlate( self.teTeams.toPlainText(), "str" )
|
||||
md.locations = xlate( self.teLocations.toPlainText(), "str" )
|
||||
|
||||
md.format = xlate( self.leFormat.text(), "str" )
|
||||
md.country = xlate( self.cbCountry.currentText(), "str" )
|
||||
|
||||
langiso = self.cbLanguage.itemData(self.cbLanguage.currentIndex()).toString()
|
||||
md.language = xlate( langiso, "str" )
|
||||
|
||||
manga_code = self.cbManga.itemData(self.cbManga.currentIndex()).toString()
|
||||
md.manga = xlate( manga_code, "str" )
|
||||
|
||||
# Make a list from the coma delimited tags string
|
||||
tmp = xlate( self.teTags.toPlainText(), "str" )
|
||||
if tmp != None:
|
||||
def striplist(l):
|
||||
return([x.strip() for x in l])
|
||||
|
||||
md.tags = striplist(tmp.split( "," ))
|
||||
|
||||
if ( self.cbBW.isChecked() ):
|
||||
md.blackAndWhite = True
|
||||
else:
|
||||
md.blackAndWhite = False
|
||||
|
||||
|
||||
def useFilename( self ):
|
||||
self.metadata = self.comic_archive.metadataFromFilename( )
|
||||
self.metadataToForm()
|
||||
|
||||
|
||||
def selectFile( self ):
|
||||
path = str(QtGui.QFileDialog.getOpenFileName())
|
||||
self.openArchive( path )
|
||||
|
||||
def queryOnline(self):
|
||||
|
||||
if str(self.leSeries.text()).strip() != "":
|
||||
series_name = str(self.leSeries.text()).strip()
|
||||
else:
|
||||
QtGui.QMessageBox.information(self, self.tr("Whoops"), self.tr("Need to enter a series name to query."))
|
||||
return
|
||||
|
||||
issue_number = str(self.leIssueNum.text()).strip()
|
||||
|
||||
selector = VolumeSelectionWindow( self, series_name, issue_number )
|
||||
selector.setModal(True)
|
||||
selector.exec_()
|
||||
|
||||
if selector.result():
|
||||
#we should now have a volume ID
|
||||
|
||||
comicVine = ComicVineTalker()
|
||||
self.metadata = comicVine.fetchIssueData( selector.volume_id, selector.issue_number )
|
||||
|
||||
# Now push the right data into the edit controls
|
||||
self.metadataToForm()
|
||||
#!!!ATB should I clear the form???
|
||||
|
||||
def commitMetadata(self):
|
||||
|
||||
if (not self.metadata is None and not self.comic_archive is None):
|
||||
self.formToMetadata()
|
||||
self.comic_archive.writeMetadata( self.metadata, self.data_style )
|
||||
self.updateInfoBox()
|
||||
|
||||
QtGui.QMessageBox.information(self, self.tr("Yeah!"), self.tr("File written."))
|
||||
|
||||
|
||||
else:
|
||||
QtGui.QMessageBox.information(self, self.tr("Whoops!"), self.tr("No data to commit!"))
|
||||
|
||||
|
||||
def setDataStyle(self, s):
|
||||
self.data_style, b = self.cbDataStyle.itemData(s).toInt()
|
||||
self.updateStyleTweaks()
|
||||
|
||||
|
||||
def updateStyleTweaks( self ):
|
||||
|
||||
# depending on the current data style, certain fields are disabled
|
||||
|
||||
inactive_color = QtGui.QColor(255, 170, 150)
|
||||
active_palette = self.leSeries.palette()
|
||||
|
||||
inactive_palette1 = self.leSeries.palette()
|
||||
inactive_palette1.setColor(QtGui.QPalette.Base, inactive_color)
|
||||
|
||||
inactive_palette2 = self.leSeries.palette()
|
||||
|
||||
inactive_palette3 = self.leSeries.palette()
|
||||
inactive_palette3.setColor(QtGui.QPalette.Base, inactive_color)
|
||||
|
||||
inactive_palette3.setColor(QtGui.QPalette.Base, inactive_color)
|
||||
|
||||
#helper func
|
||||
def enableWidget( item, enable ):
|
||||
inactive_palette3.setColor(item.backgroundRole(), inactive_color)
|
||||
inactive_palette2.setColor(item.backgroundRole(), inactive_color)
|
||||
inactive_palette3.setColor(item.foregroundRole(), inactive_color)
|
||||
|
||||
if enable:
|
||||
item.setPalette(active_palette)
|
||||
if type(item) == QtGui.QCheckBox:
|
||||
item.setEnabled( True )
|
||||
elif type(item) == QtGui.QComboBox:
|
||||
item.setEnabled( True )
|
||||
else:
|
||||
item.setReadOnly( False )
|
||||
else:
|
||||
if type(item) == QtGui.QCheckBox:
|
||||
item.setPalette(inactive_palette2)
|
||||
item.setEnabled( False )
|
||||
elif type(item) == QtGui.QComboBox:
|
||||
item.setPalette(inactive_palette3)
|
||||
item.setEnabled( False )
|
||||
else:
|
||||
item.setReadOnly( True )
|
||||
item.setPalette(inactive_palette1)
|
||||
|
||||
|
||||
cbi_only = [ self.leVolumeCount, self.cbCountry, self.leCriticalRating, self.teTags ]
|
||||
cix_only = [
|
||||
self.leImprint, self.teNotes, self.cbBW, self.cbManga,
|
||||
self.leStoryArc, self.leScanInfo, self.leSeriesGroup,
|
||||
self.leAltSeries, self.leAltIssueNum, self.leAltIssueCount,
|
||||
self.leWebLink, self.teCharacters, self.teTeams,
|
||||
self.teLocations, self.leMaturityRating, self.leFormat
|
||||
]
|
||||
|
||||
if self.data_style == MetaDataStyle.CIX:
|
||||
for item in cix_only:
|
||||
enableWidget( item, True )
|
||||
for item in cbi_only:
|
||||
enableWidget(item, False )
|
||||
|
||||
if self.data_style == MetaDataStyle.CBI:
|
||||
for item in cbi_only:
|
||||
enableWidget( item, True )
|
||||
for item in cix_only:
|
||||
enableWidget(item, False )
|
||||
|
||||
|
||||
|
||||
def center(self):
|
||||
screen = QtGui.QDesktopWidget().screenGeometry()
|
||||
size = self.geometry()
|
||||
self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
|
||||
|
||||
def populateComboBoxes( self ):
|
||||
|
||||
# Add the entries to the tag style combobox
|
||||
self.cbDataStyle.addItem( "ComicBookLover", MetaDataStyle.CBI )
|
||||
self.cbDataStyle.addItem( "ComicRack", MetaDataStyle.CIX )
|
||||
|
||||
# select the current style
|
||||
if ( self.data_style == MetaDataStyle.CBI ):
|
||||
self.cbDataStyle.setCurrentIndex ( 0 )
|
||||
elif ( self.data_style == MetaDataStyle.CIX ):
|
||||
self.cbDataStyle.setCurrentIndex ( 1 )
|
||||
|
||||
# Add the entries to the country combobox
|
||||
self.cbCountry.addItem( "", "" )
|
||||
for c in utils.countries:
|
||||
self.cbCountry.addItem( c[1], c[0] )
|
||||
|
||||
# Add the entries to the language combobox
|
||||
self.cbLanguage.addItem( "", "" )
|
||||
lang_dict = utils.getLanguageDict()
|
||||
for key in sorted(lang_dict, cmp=locale.strcoll, key=lang_dict.get):
|
||||
self.cbLanguage.addItem( lang_dict[key], key )
|
||||
|
||||
# Add the entries to the manga combobox
|
||||
self.cbManga.addItem( "", "" )
|
||||
self.cbManga.addItem( "Yes", "Yes" )
|
||||
self.cbManga.addItem( "Yes (Right to Left)", "YesAndRightToLeft" )
|
||||
self.cbManga.addItem( "No", "No" )
|
764
taggerwindow.ui
Normal file
764
taggerwindow.ui
Normal file
@ -0,0 +1,764 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>943</width>
|
||||
<height>507</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>ComicTagger</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<widget class="QLabel" name="lblCover">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>150</y>
|
||||
<width>211</width>
|
||||
<height>291</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
<width>211</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Tag Style:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cbDataStyle"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="horizontalLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>250</x>
|
||||
<y>10</y>
|
||||
<width>671</width>
|
||||
<height>431</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Details</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="formLayoutWidget_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>0</y>
|
||||
<width>371</width>
|
||||
<height>284</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Series</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="leSeries"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="leTitle"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Publisher</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lePublisher"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="leImprint"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Series Group</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="leSeriesGroup"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Imprint</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="leStoryArc"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_25">
|
||||
<property name="text">
|
||||
<string>Story Arc</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="leGenre"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_24">
|
||||
<property name="text">
|
||||
<string>Genre</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_26">
|
||||
<property name="text">
|
||||
<string>Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="leFormat"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>390</x>
|
||||
<y>0</y>
|
||||
<width>151</width>
|
||||
<height>140</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Issue</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="leIssueNum"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string># Issues</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="leIssueCount"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Volume</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="leVolumeNum"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string># Volumes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="leVolumeCount"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>390</x>
|
||||
<y>140</y>
|
||||
<width>112</width>
|
||||
<height>71</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lePubMonth"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Month</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lePubYear"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Year</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget_5">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>390</x>
|
||||
<y>210</y>
|
||||
<width>251</width>
|
||||
<height>81</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_5">
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cbLanguage"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="cbCountry"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Country</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget_7">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>550</x>
|
||||
<y>0</y>
|
||||
<width>103</width>
|
||||
<height>120</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_7">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="rowWrapPolicy">
|
||||
<enum>QFormLayout::WrapAllRows</enum>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Maturity Rating</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="leMaturityRating"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_17">
|
||||
<property name="text">
|
||||
<string>Critical Rating</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="leCriticalRating"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget_9">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>290</y>
|
||||
<width>371</width>
|
||||
<height>111</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_9">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<property name="text">
|
||||
<string>Alt. Series</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="leAltSeries"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
<string>Alt. Issue</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="leAltIssueNum"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
<string>Alt. # Issues</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="leAltIssueCount"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="formLayoutWidget_10">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>390</x>
|
||||
<y>300</y>
|
||||
<width>211</width>
|
||||
<height>71</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_10">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="cbManga">
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_31">
|
||||
<property name="text">
|
||||
<string>Manga</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="cbBW">
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Black && White</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Credits</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="formLayoutWidget_8">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>350</y>
|
||||
<width>481</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_8">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_20">
|
||||
<property name="text">
|
||||
<string>Scan Info</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="leScanInfo"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="twCredits">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>10</y>
|
||||
<width>481</width>
|
||||
<height>321</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="rowCount">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Credit</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btnAddCredit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>520</x>
|
||||
<y>20</y>
|
||||
<width>121</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add Credit</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btnRemoveCredit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>520</x>
|
||||
<y>60</y>
|
||||
<width>121</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove Credit</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btnEditCredit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>520</x>
|
||||
<y>100</y>
|
||||
<width>121</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Edit Credit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Notes</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="formLayoutWidget_6">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>10</y>
|
||||
<width>591</width>
|
||||
<height>371</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_6">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Comments</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>Notes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextEdit" name="teComments"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTextEdit" name="teNotes"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="leWebLink"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>Web</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
<attribute name="title">
|
||||
<string>Other</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="formLayoutWidget_11">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>17</y>
|
||||
<width>581</width>
|
||||
<height>371</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_11">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_30">
|
||||
<property name="text">
|
||||
<string>Teams</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_29">
|
||||
<property name="text">
|
||||
<string>Characters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_27">
|
||||
<property name="text">
|
||||
<string>Locations</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_28">
|
||||
<property name="text">
|
||||
<string>Other Tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTextEdit" name="teCharacters"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QTextEdit" name="teTeams"/>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QTextEdit" name="teLocations"/>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QTextEdit" name="teTags"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_5">
|
||||
<attribute name="title">
|
||||
<string>Pages</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QLabel" name="lblArchiveInfo">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>60</y>
|
||||
<width>211</width>
|
||||
<height>81</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>943</width>
|
||||
<height>28</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuComicTagger">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionLoad"/>
|
||||
<addaction name="actionWrite_Tags"/>
|
||||
<addaction name="actionRepackage"/>
|
||||
<addaction name="actionExit"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
<addaction name="actionAbout"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTags">
|
||||
<property name="title">
|
||||
<string>Tags</string>
|
||||
</property>
|
||||
<addaction name="actionParse_Filename"/>
|
||||
<addaction name="actionQuery_Online"/>
|
||||
</widget>
|
||||
<addaction name="menuComicTagger"/>
|
||||
<addaction name="menuTags"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar</string>
|
||||
</property>
|
||||
<property name="movable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="floatable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<action name="actionLoad">
|
||||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionWrite_Tags">
|
||||
<property name="text">
|
||||
<string>Save Tags</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRepackage">
|
||||
<property name="text">
|
||||
<string>Repackage</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExit">
|
||||
<property name="text">
|
||||
<string>Exit</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAbout">
|
||||
<property name="text">
|
||||
<string>About ComicTagger</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionParse_Filename">
|
||||
<property name="text">
|
||||
<string>Parse Filename</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionQuery_Online">
|
||||
<property name="text">
|
||||
<string>Query Online</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
311
test.ui
Normal file
311
test.ui
Normal file
@ -0,0 +1,311 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>943</width>
|
||||
<height>565</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>ComicTagger</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<widget class="QWidget" name="formLayoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>20</y>
|
||||
<width>251</width>
|
||||
<height>60</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Tag Style:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="cbDataStyle"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btnQuery">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>590</x>
|
||||
<y>170</y>
|
||||
<width>181</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Query Online</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btnCommit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>570</x>
|
||||
<y>20</y>
|
||||
<width>111</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="lblCover">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>590</x>
|
||||
<y>260</y>
|
||||
<width>181</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>80</y>
|
||||
<width>461</width>
|
||||
<height>411</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>130</x>
|
||||
<y>380</y>
|
||||
<width>66</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CR Entry</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page_2">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>10</y>
|
||||
<width>361</width>
|
||||
<height>351</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Tab 1</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="formLayoutWidget_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>20</y>
|
||||
<width>261</width>
|
||||
<height>291</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Issue</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_2"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Series</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Publication Year</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_3"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Publication Month</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_4"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Tab 2</string>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="formLayoutWidget_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>10</y>
|
||||
<width>261</width>
|
||||
<height>291</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Notes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_5"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_6"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Comments</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Tags</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_7"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Credits</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_8"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>120</x>
|
||||
<y>370</y>
|
||||
<width>66</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>CBL Entry</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="leFileSelection">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>620</x>
|
||||
<y>90</y>
|
||||
<width>113</width>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="tableWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>585</x>
|
||||
<y>371</y>
|
||||
<width>221</width>
|
||||
<height>91</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>943</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuComicTagger">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
</widget>
|
||||
<addaction name="menuComicTagger"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusBar"/>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
450
utils.py
Normal file
450
utils.py
Normal file
@ -0,0 +1,450 @@
|
||||
# coding=utf-8
|
||||
|
||||
def listToString( l ):
|
||||
string = ""
|
||||
if l is not None:
|
||||
for item in l:
|
||||
if len(string) > 0:
|
||||
string += ", "
|
||||
string += item
|
||||
return string
|
||||
|
||||
|
||||
|
||||
# -o- coding: utf-8 -o-
|
||||
# ISO639 python dict
|
||||
# oficial list in http://www.loc.gov/standards/iso639-2/php/code_list.php
|
||||
|
||||
lang_dict = {
|
||||
'ab': 'Abkhaz',
|
||||
'aa': 'Afar',
|
||||
'af': 'Afrikaans',
|
||||
'ak': 'Akan',
|
||||
'sq': 'Albanian',
|
||||
'am': 'Amharic',
|
||||
'ar': 'Arabic',
|
||||
'an': 'Aragonese',
|
||||
'hy': 'Armenian',
|
||||
'as': 'Assamese',
|
||||
'av': 'Avaric',
|
||||
'ae': 'Avestan',
|
||||
'ay': 'Aymara',
|
||||
'az': 'Azerbaijani',
|
||||
'bm': 'Bambara',
|
||||
'ba': 'Bashkir',
|
||||
'eu': 'Basque',
|
||||
'be': 'Belarusian',
|
||||
'bn': 'Bengali',
|
||||
'bh': 'Bihari',
|
||||
'bi': 'Bislama',
|
||||
'bs': 'Bosnian',
|
||||
'br': 'Breton',
|
||||
'bg': 'Bulgarian',
|
||||
'my': 'Burmese',
|
||||
'ca': 'Catalan; Valencian',
|
||||
'ch': 'Chamorro',
|
||||
'ce': 'Chechen',
|
||||
'ny': 'Chichewa; Chewa; Nyanja',
|
||||
'zh': 'Chinese',
|
||||
'cv': 'Chuvash',
|
||||
'kw': 'Cornish',
|
||||
'co': 'Corsican',
|
||||
'cr': 'Cree',
|
||||
'hr': 'Croatian',
|
||||
'cs': 'Czech',
|
||||
'da': 'Danish',
|
||||
'dv': 'Divehi; Maldivian;',
|
||||
'nl': 'Dutch',
|
||||
'dz': 'Dzongkha',
|
||||
'en': 'English',
|
||||
'eo': 'Esperanto',
|
||||
'et': 'Estonian',
|
||||
'ee': 'Ewe',
|
||||
'fo': 'Faroese',
|
||||
'fj': 'Fijian',
|
||||
'fi': 'Finnish',
|
||||
'fr': 'French',
|
||||
'ff': 'Fula',
|
||||
'gl': 'Galician',
|
||||
'ka': 'Georgian',
|
||||
'de': 'German',
|
||||
'el': 'Greek, Modern',
|
||||
'gn': 'Guaraní',
|
||||
'gu': 'Gujarati',
|
||||
'ht': 'Haitian',
|
||||
'ha': 'Hausa',
|
||||
'he': 'Hebrew (modern)',
|
||||
'hz': 'Herero',
|
||||
'hi': 'Hindi',
|
||||
'ho': 'Hiri Motu',
|
||||
'hu': 'Hungarian',
|
||||
'ia': 'Interlingua',
|
||||
'id': 'Indonesian',
|
||||
'ie': 'Interlingue',
|
||||
'ga': 'Irish',
|
||||
'ig': 'Igbo',
|
||||
'ik': 'Inupiaq',
|
||||
'io': 'Ido',
|
||||
'is': 'Icelandic',
|
||||
'it': 'Italian',
|
||||
'iu': 'Inuktitut',
|
||||
'ja': 'Japanese',
|
||||
'jv': 'Javanese',
|
||||
'kl': 'Kalaallisut',
|
||||
'kn': 'Kannada',
|
||||
'kr': 'Kanuri',
|
||||
'ks': 'Kashmiri',
|
||||
'kk': 'Kazakh',
|
||||
'km': 'Khmer',
|
||||
'ki': 'Kikuyu, Gikuyu',
|
||||
'rw': 'Kinyarwanda',
|
||||
'ky': 'Kirghiz, Kyrgyz',
|
||||
'kv': 'Komi',
|
||||
'kg': 'Kongo',
|
||||
'ko': 'Korean',
|
||||
'ku': 'Kurdish',
|
||||
'kj': 'Kwanyama, Kuanyama',
|
||||
'la': 'Latin',
|
||||
'lb': 'Luxembourgish',
|
||||
'lg': 'Luganda',
|
||||
'li': 'Limburgish',
|
||||
'ln': 'Lingala',
|
||||
'lo': 'Lao',
|
||||
'lt': 'Lithuanian',
|
||||
'lu': 'Luba-Katanga',
|
||||
'lv': 'Latvian',
|
||||
'gv': 'Manx',
|
||||
'mk': 'Macedonian',
|
||||
'mg': 'Malagasy',
|
||||
'ms': 'Malay',
|
||||
'ml': 'Malayalam',
|
||||
'mt': 'Maltese',
|
||||
'mi': 'Māori',
|
||||
'mr': 'Marathi (Marāṭhī)',
|
||||
'mh': 'Marshallese',
|
||||
'mn': 'Mongolian',
|
||||
'na': 'Nauru',
|
||||
'nv': 'Navajo, Navaho',
|
||||
'nb': 'Norwegian Bokmål',
|
||||
'nd': 'North Ndebele',
|
||||
'ne': 'Nepali',
|
||||
'ng': 'Ndonga',
|
||||
'nn': 'Norwegian Nynorsk',
|
||||
'no': 'Norwegian',
|
||||
'ii': 'Nuosu',
|
||||
'nr': 'South Ndebele',
|
||||
'oc': 'Occitan',
|
||||
'oj': 'Ojibwe, Ojibwa',
|
||||
'cu': 'Old Church Slavonic',
|
||||
'om': 'Oromo',
|
||||
'or': 'Oriya',
|
||||
'os': 'Ossetian, Ossetic',
|
||||
'pa': 'Panjabi, Punjabi',
|
||||
'pi': 'Pāli',
|
||||
'fa': 'Persian',
|
||||
'pl': 'Polish',
|
||||
'ps': 'Pashto, Pushto',
|
||||
'pt': 'Portuguese',
|
||||
'qu': 'Quechua',
|
||||
'rm': 'Romansh',
|
||||
'rn': 'Kirundi',
|
||||
'ro': 'Romanian, Moldavan',
|
||||
'ru': 'Russian',
|
||||
'sa': 'Sanskrit (Saṁskṛta)',
|
||||
'sc': 'Sardinian',
|
||||
'sd': 'Sindhi',
|
||||
'se': 'Northern Sami',
|
||||
'sm': 'Samoan',
|
||||
'sg': 'Sango',
|
||||
'sr': 'Serbian',
|
||||
'gd': 'Scottish Gaelic',
|
||||
'sn': 'Shona',
|
||||
'si': 'Sinhala, Sinhalese',
|
||||
'sk': 'Slovak',
|
||||
'sl': 'Slovene',
|
||||
'so': 'Somali',
|
||||
'st': 'Southern Sotho',
|
||||
'es': 'Spanish; Castilian',
|
||||
'su': 'Sundanese',
|
||||
'sw': 'Swahili',
|
||||
'ss': 'Swati',
|
||||
'sv': 'Swedish',
|
||||
'ta': 'Tamil',
|
||||
'te': 'Telugu',
|
||||
'tg': 'Tajik',
|
||||
'th': 'Thai',
|
||||
'ti': 'Tigrinya',
|
||||
'bo': 'Tibetan',
|
||||
'tk': 'Turkmen',
|
||||
'tl': 'Tagalog',
|
||||
'tn': 'Tswana',
|
||||
'to': 'Tonga',
|
||||
'tr': 'Turkish',
|
||||
'ts': 'Tsonga',
|
||||
'tt': 'Tatar',
|
||||
'tw': 'Twi',
|
||||
'ty': 'Tahitian',
|
||||
'ug': 'Uighur, Uyghur',
|
||||
'uk': 'Ukrainian',
|
||||
'ur': 'Urdu',
|
||||
'uz': 'Uzbek',
|
||||
've': 'Venda',
|
||||
'vi': 'Vietnamese',
|
||||
'vo': 'Volapük',
|
||||
'wa': 'Walloon',
|
||||
'cy': 'Welsh',
|
||||
'wo': 'Wolof',
|
||||
'fy': 'Western Frisian',
|
||||
'xh': 'Xhosa',
|
||||
'yi': 'Yiddish',
|
||||
'yo': 'Yoruba',
|
||||
'za': 'Zhuang, Chuang',
|
||||
'zu': 'Zulu',
|
||||
}
|
||||
|
||||
|
||||
countries = [
|
||||
('AF', 'Afghanistan'),
|
||||
('AL', 'Albania'),
|
||||
('DZ', 'Algeria'),
|
||||
('AS', 'American Samoa'),
|
||||
('AD', 'Andorra'),
|
||||
('AO', 'Angola'),
|
||||
('AI', 'Anguilla'),
|
||||
('AQ', 'Antarctica'),
|
||||
('AG', 'Antigua And Barbuda'),
|
||||
('AR', 'Argentina'),
|
||||
('AM', 'Armenia'),
|
||||
('AW', 'Aruba'),
|
||||
('AU', 'Australia'),
|
||||
('AT', 'Austria'),
|
||||
('AZ', 'Azerbaijan'),
|
||||
('BS', 'Bahamas'),
|
||||
('BH', 'Bahrain'),
|
||||
('BD', 'Bangladesh'),
|
||||
('BB', 'Barbados'),
|
||||
('BY', 'Belarus'),
|
||||
('BE', 'Belgium'),
|
||||
('BZ', 'Belize'),
|
||||
('BJ', 'Benin'),
|
||||
('BM', 'Bermuda'),
|
||||
('BT', 'Bhutan'),
|
||||
('BO', 'Bolivia'),
|
||||
('BA', 'Bosnia And Herzegowina'),
|
||||
('BW', 'Botswana'),
|
||||
('BV', 'Bouvet Island'),
|
||||
('BR', 'Brazil'),
|
||||
('BN', 'Brunei Darussalam'),
|
||||
('BG', 'Bulgaria'),
|
||||
('BF', 'Burkina Faso'),
|
||||
('BI', 'Burundi'),
|
||||
('KH', 'Cambodia'),
|
||||
('CM', 'Cameroon'),
|
||||
('CA', 'Canada'),
|
||||
('CV', 'Cape Verde'),
|
||||
('KY', 'Cayman Islands'),
|
||||
('CF', 'Central African Rep'),
|
||||
('TD', 'Chad'),
|
||||
('CL', 'Chile'),
|
||||
('CN', 'China'),
|
||||
('CX', 'Christmas Island'),
|
||||
('CC', 'Cocos Islands'),
|
||||
('CO', 'Colombia'),
|
||||
('KM', 'Comoros'),
|
||||
('CG', 'Congo'),
|
||||
('CK', 'Cook Islands'),
|
||||
('CR', 'Costa Rica'),
|
||||
('CI', 'Cote D`ivoire'),
|
||||
('HR', 'Croatia'),
|
||||
('CU', 'Cuba'),
|
||||
('CY', 'Cyprus'),
|
||||
('CZ', 'Czech Republic'),
|
||||
('DK', 'Denmark'),
|
||||
('DJ', 'Djibouti'),
|
||||
('DM', 'Dominica'),
|
||||
('DO', 'Dominican Republic'),
|
||||
('TP', 'East Timor'),
|
||||
('EC', 'Ecuador'),
|
||||
('EG', 'Egypt'),
|
||||
('SV', 'El Salvador'),
|
||||
('GQ', 'Equatorial Guinea'),
|
||||
('ER', 'Eritrea'),
|
||||
('EE', 'Estonia'),
|
||||
('ET', 'Ethiopia'),
|
||||
('FK', 'Falkland Islands (Malvinas)'),
|
||||
('FO', 'Faroe Islands'),
|
||||
('FJ', 'Fiji'),
|
||||
('FI', 'Finland'),
|
||||
('FR', 'France'),
|
||||
('GF', 'French Guiana'),
|
||||
('PF', 'French Polynesia'),
|
||||
('TF', 'French S. Territories'),
|
||||
('GA', 'Gabon'),
|
||||
('GM', 'Gambia'),
|
||||
('GE', 'Georgia'),
|
||||
('DE', 'Germany'),
|
||||
('GH', 'Ghana'),
|
||||
('GI', 'Gibraltar'),
|
||||
('GR', 'Greece'),
|
||||
('GL', 'Greenland'),
|
||||
('GD', 'Grenada'),
|
||||
('GP', 'Guadeloupe'),
|
||||
('GU', 'Guam'),
|
||||
('GT', 'Guatemala'),
|
||||
('GN', 'Guinea'),
|
||||
('GW', 'Guinea-bissau'),
|
||||
('GY', 'Guyana'),
|
||||
('HT', 'Haiti'),
|
||||
('HN', 'Honduras'),
|
||||
('HK', 'Hong Kong'),
|
||||
('HU', 'Hungary'),
|
||||
('IS', 'Iceland'),
|
||||
('IN', 'India'),
|
||||
('ID', 'Indonesia'),
|
||||
('IR', 'Iran'),
|
||||
('IQ', 'Iraq'),
|
||||
('IE', 'Ireland'),
|
||||
('IL', 'Israel'),
|
||||
('IT', 'Italy'),
|
||||
('JM', 'Jamaica'),
|
||||
('JP', 'Japan'),
|
||||
('JO', 'Jordan'),
|
||||
('KZ', 'Kazakhstan'),
|
||||
('KE', 'Kenya'),
|
||||
('KI', 'Kiribati'),
|
||||
('KP', 'Korea (North)'),
|
||||
('KR', 'Korea (South)'),
|
||||
('KW', 'Kuwait'),
|
||||
('KG', 'Kyrgyzstan'),
|
||||
('LA', 'Laos'),
|
||||
('LV', 'Latvia'),
|
||||
('LB', 'Lebanon'),
|
||||
('LS', 'Lesotho'),
|
||||
('LR', 'Liberia'),
|
||||
('LY', 'Libya'),
|
||||
('LI', 'Liechtenstein'),
|
||||
('LT', 'Lithuania'),
|
||||
('LU', 'Luxembourg'),
|
||||
('MO', 'Macau'),
|
||||
('MK', 'Macedonia'),
|
||||
('MG', 'Madagascar'),
|
||||
('MW', 'Malawi'),
|
||||
('MY', 'Malaysia'),
|
||||
('MV', 'Maldives'),
|
||||
('ML', 'Mali'),
|
||||
('MT', 'Malta'),
|
||||
('MH', 'Marshall Islands'),
|
||||
('MQ', 'Martinique'),
|
||||
('MR', 'Mauritania'),
|
||||
('MU', 'Mauritius'),
|
||||
('YT', 'Mayotte'),
|
||||
('MX', 'Mexico'),
|
||||
('FM', 'Micronesia'),
|
||||
('MD', 'Moldova'),
|
||||
('MC', 'Monaco'),
|
||||
('MN', 'Mongolia'),
|
||||
('MS', 'Montserrat'),
|
||||
('MA', 'Morocco'),
|
||||
('MZ', 'Mozambique'),
|
||||
('MM', 'Myanmar'),
|
||||
('NA', 'Namibia'),
|
||||
('NR', 'Nauru'),
|
||||
('NP', 'Nepal'),
|
||||
('NL', 'Netherlands'),
|
||||
('AN', 'Netherlands Antilles'),
|
||||
('NC', 'New Caledonia'),
|
||||
('NZ', 'New Zealand'),
|
||||
('NI', 'Nicaragua'),
|
||||
('NE', 'Niger'),
|
||||
('NG', 'Nigeria'),
|
||||
('NU', 'Niue'),
|
||||
('NF', 'Norfolk Island'),
|
||||
('MP', 'Northern Mariana Islands'),
|
||||
('NO', 'Norway'),
|
||||
('OM', 'Oman'),
|
||||
('PK', 'Pakistan'),
|
||||
('PW', 'Palau'),
|
||||
('PA', 'Panama'),
|
||||
('PG', 'Papua New Guinea'),
|
||||
('PY', 'Paraguay'),
|
||||
('PE', 'Peru'),
|
||||
('PH', 'Philippines'),
|
||||
('PN', 'Pitcairn'),
|
||||
('PL', 'Poland'),
|
||||
('PT', 'Portugal'),
|
||||
('PR', 'Puerto Rico'),
|
||||
('QA', 'Qatar'),
|
||||
('RE', 'Reunion'),
|
||||
('RO', 'Romania'),
|
||||
('RU', 'Russian Federation'),
|
||||
('RW', 'Rwanda'),
|
||||
('KN', 'Saint Kitts And Nevis'),
|
||||
('LC', 'Saint Lucia'),
|
||||
('VC', 'St Vincent/Grenadines'),
|
||||
('WS', 'Samoa'),
|
||||
('SM', 'San Marino'),
|
||||
('ST', 'Sao Tome'),
|
||||
('SA', 'Saudi Arabia'),
|
||||
('SN', 'Senegal'),
|
||||
('SC', 'Seychelles'),
|
||||
('SL', 'Sierra Leone'),
|
||||
('SG', 'Singapore'),
|
||||
('SK', 'Slovakia'),
|
||||
('SI', 'Slovenia'),
|
||||
('SB', 'Solomon Islands'),
|
||||
('SO', 'Somalia'),
|
||||
('ZA', 'South Africa'),
|
||||
('ES', 'Spain'),
|
||||
('LK', 'Sri Lanka'),
|
||||
('SH', 'St. Helena'),
|
||||
('PM', 'St.Pierre'),
|
||||
('SD', 'Sudan'),
|
||||
('SR', 'Suriname'),
|
||||
('SZ', 'Swaziland'),
|
||||
('SE', 'Sweden'),
|
||||
('CH', 'Switzerland'),
|
||||
('SY', 'Syrian Arab Republic'),
|
||||
('TW', 'Taiwan'),
|
||||
('TJ', 'Tajikistan'),
|
||||
('TZ', 'Tanzania'),
|
||||
('TH', 'Thailand'),
|
||||
('TG', 'Togo'),
|
||||
('TK', 'Tokelau'),
|
||||
('TO', 'Tonga'),
|
||||
('TT', 'Trinidad And Tobago'),
|
||||
('TN', 'Tunisia'),
|
||||
('TR', 'Turkey'),
|
||||
('TM', 'Turkmenistan'),
|
||||
('TV', 'Tuvalu'),
|
||||
('UG', 'Uganda'),
|
||||
('UA', 'Ukraine'),
|
||||
('AE', 'United Arab Emirates'),
|
||||
('UK', 'United Kingdom'),
|
||||
('US', 'United States'),
|
||||
('UY', 'Uruguay'),
|
||||
('UZ', 'Uzbekistan'),
|
||||
('VU', 'Vanuatu'),
|
||||
('VA', 'Vatican City State'),
|
||||
('VE', 'Venezuela'),
|
||||
('VN', 'Viet Nam'),
|
||||
('VG', 'Virgin Islands (British)'),
|
||||
('VI', 'Virgin Islands (U.S.)'),
|
||||
('EH', 'Western Sahara'),
|
||||
('YE', 'Yemen'),
|
||||
('YU', 'Yugoslavia'),
|
||||
('ZR', 'Zaire'),
|
||||
('ZM', 'Zambia'),
|
||||
('ZW', 'Zimbabwe')
|
||||
]
|
||||
|
||||
|
||||
|
||||
def getLanguageDict():
|
||||
return lang_dict
|
||||
|
||||
def getLanguageFromISO( iso ):
|
||||
if iso == None:
|
||||
return None
|
||||
else:
|
||||
return lang_dict[ iso ]
|
||||
|
135
volumeselectionwindow.py
Normal file
135
volumeselectionwindow.py
Normal file
@ -0,0 +1,135 @@
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui, uic
|
||||
|
||||
from PyQt4.QtCore import QUrl
|
||||
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
||||
|
||||
from comicvinetalker import ComicVineTalker
|
||||
from issueselectionwindow import IssueSelectionWindow
|
||||
|
||||
class VolumeSelectionWindow(QtGui.QDialog):
|
||||
|
||||
volume_id = 0
|
||||
|
||||
def __init__(self, parent, series_name, issue_number):
|
||||
super(VolumeSelectionWindow, self).__init__(parent)
|
||||
|
||||
uic.loadUi('volumeselectionwindow.ui', self)
|
||||
|
||||
self.series_name = series_name
|
||||
self.issue_number = issue_number
|
||||
|
||||
self.performQuery()
|
||||
|
||||
self.twList.resizeColumnsToContents()
|
||||
self.twList.currentItemChanged.connect(self.currentItemChanged)
|
||||
self.twList.cellDoubleClicked.connect(self.cellDoubleClicked)
|
||||
self.btnRequery.clicked.connect(self.requery)
|
||||
self.btnIssues.clicked.connect(self.showIssues)
|
||||
|
||||
self.twList.selectRow(0)
|
||||
|
||||
def requery( self ):
|
||||
self.performQuery()
|
||||
self.twList.selectRow(0)
|
||||
|
||||
def showIssues( self ):
|
||||
selector = IssueSelectionWindow( self, self.volume_id, self.issue_number )
|
||||
selector.setModal(True)
|
||||
selector.exec_()
|
||||
if selector.result():
|
||||
#we should now have a volume ID
|
||||
self.issue_number = selector.issue_number
|
||||
self.accept()
|
||||
return
|
||||
|
||||
def performQuery( self ):
|
||||
|
||||
while self.twList.rowCount() > 0:
|
||||
self.twList.removeRow(0)
|
||||
|
||||
comicVine = ComicVineTalker()
|
||||
self.cv_search_results = comicVine.searchForSeries( self.series_name )
|
||||
|
||||
self.twList.setSortingEnabled(False)
|
||||
|
||||
row = 0
|
||||
for record in self.cv_search_results:
|
||||
self.twList.insertRow(row)
|
||||
|
||||
item_text = record['name']
|
||||
item = QtGui.QTableWidgetItem(item_text)
|
||||
item.setData( QtCore.Qt.UserRole ,record['id'])
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
|
||||
self.twList.setItem(row, 0, item)
|
||||
|
||||
item_text = str(record['start_year'])
|
||||
item = QtGui.QTableWidgetItem(item_text)
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable| QtCore.Qt.ItemIsEnabled)
|
||||
self.twList.setItem(row, 1, item)
|
||||
|
||||
item_text = record['count_of_issues']
|
||||
item = QtGui.QTableWidgetItem(item_text)
|
||||
item.setData(QtCore.Qt.DisplayRole, record['count_of_issues'])
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.twList.setItem(row, 2, item)
|
||||
|
||||
if record['publisher'] is not None:
|
||||
item_text = record['publisher']['name']
|
||||
item = QtGui.QTableWidgetItem(item_text)
|
||||
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
self.twList.setItem(row, 3, item)
|
||||
|
||||
record['cover_image'] = None
|
||||
|
||||
row += 1
|
||||
|
||||
self.twList.setSortingEnabled(True)
|
||||
self.twList.sortItems( 2 , QtCore.Qt.DescendingOrder )
|
||||
|
||||
|
||||
def cellDoubleClicked( self, r, c ):
|
||||
self.showIssues()
|
||||
|
||||
def currentItemChanged( self, curr, prev ):
|
||||
|
||||
if curr is None:
|
||||
return
|
||||
if prev is not None and prev.row() == curr.row():
|
||||
return
|
||||
|
||||
|
||||
self.volume_id, b = self.twList.item( curr.row(), 0 ).data( QtCore.Qt.UserRole ).toInt()
|
||||
|
||||
# list selection was changed, update the info on the volume
|
||||
for record in self.cv_search_results:
|
||||
if record['id'] == self.volume_id:
|
||||
|
||||
self.teDetails.setText ( record['description'] )
|
||||
|
||||
if record['cover_image'] == None:
|
||||
url = record['image']['super_url']
|
||||
self.labelThumbnail.setText("loading...")
|
||||
self.nam = QNetworkAccessManager()
|
||||
|
||||
self.nam.finished.connect(self.finishRequest)
|
||||
self.nam.get(QNetworkRequest(QUrl(url)))
|
||||
self.pending_cover_record = record
|
||||
else:
|
||||
self.setCover(record['cover_image'])
|
||||
|
||||
# called when the image is done loading
|
||||
def finishRequest(self, reply):
|
||||
img = QtGui.QImage()
|
||||
img.loadFromData(reply.readAll())
|
||||
|
||||
self.pending_cover_record['cover_image'] = img
|
||||
self.pending_cover_record = None
|
||||
|
||||
self.setCover( img )
|
||||
|
||||
def setCover( self, img ):
|
||||
self.labelThumbnail.setPixmap(QtGui.QPixmap(img))
|
||||
|
||||
|
||||
|
214
volumeselectionwindow.ui
Normal file
214
volumeselectionwindow.ui
Normal file
@ -0,0 +1,214 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SelectSeries</class>
|
||||
<widget class="QDialog" name="SelectSeries">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>900</width>
|
||||
<height>480</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Series</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QLabel" name="labelThumbnail">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>30</y>
|
||||
<width>241</width>
|
||||
<height>391</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Panel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>272</x>
|
||||
<y>430</y>
|
||||
<width>611</width>
|
||||
<height>29</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnIssues">
|
||||
<property name="text">
|
||||
<string>Show Issues</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnRequery">
|
||||
<property name="text">
|
||||
<string>Re-Query</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>270</x>
|
||||
<y>33</y>
|
||||
<width>611</width>
|
||||
<height>391</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QTableWidget" name="twList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>250</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="rowCount">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Series</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Year</string>
|
||||
</property>
|
||||
<property name="textAlignment">
|
||||
<set>AlignHCenter|AlignVCenter|AlignCenter</set>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Issues</string>
|
||||
</property>
|
||||
<property name="textAlignment">
|
||||
<set>AlignHCenter|AlignVCenter|AlignCenter</set>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Publisher</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="teDetails">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SelectSeries</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SelectSeries</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
Loading…
x
Reference in New Issue
Block a user