Added page browser, other misc stuff
git-svn-id: http://comictagger.googlecode.com/svn/trunk@35 6c5673fe-1810-88d6-992b-cd32ca31540c
This commit is contained in:
parent
448e2a9d89
commit
54f41a5ac0
@ -448,24 +448,32 @@ class ComicArchive:
|
||||
|
||||
def getCoverPage(self):
|
||||
|
||||
if self.getNumberOfPages() == 0:
|
||||
# assume first page is the cover (for now)
|
||||
return self.getPage( 0 )
|
||||
|
||||
def getPage( self, index ):
|
||||
|
||||
num_pages = self.getNumberOfPages()
|
||||
if num_pages == 0 or index >= num_pages:
|
||||
return None
|
||||
|
||||
# get the list file names in the archive, and sort
|
||||
files = self.archiver.getArchiveFilenameList()
|
||||
|
||||
# seems like the scanners are on Windows, and don't know about case-sensitivity!
|
||||
# seems like some archive creators are on Windows, and don't know about case-sensitivity!
|
||||
files.sort(key=lambda x: x.lower())
|
||||
|
||||
# find the first image file, assume it's the cover
|
||||
# make a sub-list of image files
|
||||
page_list = []
|
||||
for name in files:
|
||||
if ( name[-4:].lower() in [ ".jpg", "jpeg", ".png" ] ):
|
||||
break
|
||||
page_list.append(name)
|
||||
|
||||
image_data = self.archiver.readArchiveFile( name )
|
||||
image_data = self.archiver.readArchiveFile( page_list[index] )
|
||||
|
||||
return image_data
|
||||
|
||||
|
||||
def getNumberOfPages(self):
|
||||
|
||||
count = 0
|
||||
|
3
ctversion.py
Normal file
3
ctversion.py
Normal file
@ -0,0 +1,3 @@
|
||||
# This file should contan only these comments, and the line below.
|
||||
# Used by packaging makefiles and app
|
||||
version="0.0.1"
|
10
options.py
10
options.py
@ -20,6 +20,7 @@ limitations under the License.
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
import platform
|
||||
|
||||
class Enum(set):
|
||||
def __getattr__(self, name):
|
||||
@ -47,7 +48,7 @@ class Options:
|
||||
def parseCmdLineArgs(self):
|
||||
|
||||
# mac no likey this from .app bundle
|
||||
if getattr(sys, 'frozen', None):
|
||||
if platform.system() == "Darwin" and getattr(sys, 'frozen', None):
|
||||
return
|
||||
|
||||
# parse command line options
|
||||
@ -83,9 +84,8 @@ class Options:
|
||||
print( __doc__ )
|
||||
sys.exit(0)
|
||||
|
||||
# process arguments
|
||||
for arg in args:
|
||||
process(arg) # process() is defined elsewhere
|
||||
|
||||
if self.filename == "" and len(args) > 0:
|
||||
self.filename = args[0]
|
||||
|
||||
return opts
|
||||
|
100
pagebrowser.py
Normal file
100
pagebrowser.py
Normal file
@ -0,0 +1,100 @@
|
||||
"""
|
||||
A PyQT4 dialog to show pages of a comic archive
|
||||
"""
|
||||
|
||||
"""
|
||||
Copyright 2012 Anthony Beville
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from PyQt4 import QtCore, QtGui, uic
|
||||
import os
|
||||
from settings import ComicTaggerSettings
|
||||
|
||||
|
||||
class PageBrowserWindow(QtGui.QDialog):
|
||||
|
||||
|
||||
def __init__(self, parent):
|
||||
super(PageBrowserWindow, self).__init__(None)
|
||||
|
||||
uic.loadUi(os.path.join(ComicTaggerSettings.baseDir(), 'pagebrowser.ui' ), self)
|
||||
|
||||
self.lblPage.setPixmap(QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'nocover.png' )))
|
||||
self.lblPage.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Ignored)
|
||||
self.comic_archive = None
|
||||
self.current_pixmap = None
|
||||
self.page_count = 0
|
||||
self.current_page_num = 0
|
||||
|
||||
self.btnNext.clicked.connect( self.nextPage )
|
||||
self.btnPrev.clicked.connect( self.prevPage )
|
||||
self.show()
|
||||
|
||||
def setComicArchive(self, ca):
|
||||
|
||||
self.comic_archive = ca
|
||||
self.page_count = ca.getNumberOfPages()
|
||||
self.current_page_num = 0
|
||||
|
||||
self.setPage()
|
||||
|
||||
def nextPage(self):
|
||||
|
||||
if self.current_page_num + 1 < self.page_count:
|
||||
self.current_page_num += 1
|
||||
self.setPage()
|
||||
|
||||
def prevPage(self):
|
||||
|
||||
if self.current_page_num - 1 >= 0:
|
||||
self.current_page_num -= 1
|
||||
self.setPage()
|
||||
|
||||
def setPage( self ):
|
||||
image_data = self.comic_archive.getPage( self.current_page_num )
|
||||
|
||||
if image_data is not None:
|
||||
self.setCurrentPixmap( image_data )
|
||||
self.setDisplayPixmap( 0, 0)
|
||||
self.setWindowTitle("Page Browser - Page {0} (of {1}) ".format(self.current_page_num+1, self.page_count ) )
|
||||
|
||||
def setCurrentPixmap( self, image_data ):
|
||||
if image_data is not None:
|
||||
img = QtGui.QImage()
|
||||
img.loadFromData( image_data )
|
||||
self.current_pixmap = QtGui.QPixmap(QtGui.QPixmap(img))
|
||||
|
||||
def resizeEvent( self, resize_event ):
|
||||
if self.current_pixmap is not None:
|
||||
delta_w = resize_event.size().width() - resize_event.oldSize().width()
|
||||
delta_h = resize_event.size().height() - resize_event.oldSize().height()
|
||||
|
||||
self.setDisplayPixmap( delta_w , delta_h )
|
||||
|
||||
def setDisplayPixmap( self, delta_w , delta_h ):
|
||||
# the deltas let us know what the new width and height of the label will be
|
||||
new_h = self.lblPage.height() + delta_h
|
||||
new_w = self.lblPage.width() + delta_w
|
||||
|
||||
if new_h < 0:
|
||||
new_h = 0;
|
||||
if new_w < 0:
|
||||
new_w = 0;
|
||||
scaled_pixmap = self.current_pixmap.scaled(new_w, new_h, QtCore.Qt.KeepAspectRatio)
|
||||
self.lblPage.setPixmap( scaled_pixmap )
|
||||
|
||||
|
||||
|
129
pagebrowser.ui
Normal file
129
pagebrowser.ui
Normal file
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>dialogPageBrowser</class>
|
||||
<widget class="QDialog" name="dialogPageBrowser">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>429</width>
|
||||
<height>637</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Page Browser</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="lblPage">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Box</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMaximumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnPrev">
|
||||
<property name="text">
|
||||
<string><<</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
<property name="centerButtons">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnNext">
|
||||
<property name="text">
|
||||
<string>>></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>dialogPageBrowser</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>dialogPageBrowser</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>
|
@ -44,10 +44,12 @@ class ComicTaggerSettings:
|
||||
|
||||
@staticmethod
|
||||
def baseDir():
|
||||
if getattr(sys, 'frozen', None):
|
||||
return sys._MEIPASS
|
||||
if platform.system() == "Darwin" and getattr(sys, 'frozen', None):
|
||||
return sys._MEIPASS
|
||||
elif platform.system() == "Windows":
|
||||
return "."
|
||||
else:
|
||||
return os.path.dirname(__file__)
|
||||
return os.path.dirname(__file__)
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
20
tagger.py
20
tagger.py
@ -23,6 +23,8 @@ limitations under the License.
|
||||
import sys
|
||||
import signal
|
||||
import os
|
||||
import traceback
|
||||
import time
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
@ -82,10 +84,22 @@ def main():
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
tagger_window = TaggerWindow( opts, settings )
|
||||
tagger_window.show()
|
||||
sys.exit(app.exec_())
|
||||
img = QtGui.QPixmap(os.path.join(ComicTaggerSettings.baseDir(), 'graphics/tags.png' ))
|
||||
splash = QtGui.QSplashScreen(img)
|
||||
splash.show()
|
||||
splash.raise_()
|
||||
app.processEvents()
|
||||
app.processEvents()
|
||||
|
||||
try:
|
||||
tagger_window = TaggerWindow( opts, settings )
|
||||
tagger_window.show()
|
||||
splash.finish( tagger_window )
|
||||
sys.exit(app.exec_())
|
||||
except Exception, e:
|
||||
QtGui.QMessageBox.critical(QtGui.QMainWindow(), "Error", "Unhandled exception in app:\n" + traceback.format_exc() )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
@ -19,8 +19,11 @@ limitations under the License.
|
||||
"""
|
||||
|
||||
from PyQt4 import QtCore, QtGui, uic
|
||||
from PyQt4.QtCore import QUrl,pyqtSignal
|
||||
|
||||
import locale
|
||||
import platform
|
||||
import os
|
||||
|
||||
from volumeselectionwindow import VolumeSelectionWindow
|
||||
from options import Options, MetaDataStyle
|
||||
@ -30,17 +33,39 @@ from comicarchive import ComicArchive
|
||||
from crediteditorwindow import CreditEditorWindow
|
||||
from settingswindow import SettingsWindow
|
||||
from settings import ComicTaggerSettings
|
||||
from pagebrowser import PageBrowserWindow
|
||||
import utils
|
||||
import ctversion
|
||||
|
||||
|
||||
# this reads the environment and inits the right locale
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
|
||||
import os
|
||||
# helper func to allow a label to be clickable
|
||||
def clickable(widget):
|
||||
|
||||
class Filter(QtCore.QObject):
|
||||
|
||||
dblclicked = pyqtSignal()
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
|
||||
if obj == widget:
|
||||
if event.type() == QtCore.QEvent.MouseButtonDblClick:
|
||||
self.dblclicked.emit()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
filter = Filter(widget)
|
||||
widget.installEventFilter(filter)
|
||||
return filter.dblclicked
|
||||
|
||||
|
||||
class TaggerWindow( QtGui.QMainWindow):
|
||||
|
||||
appName = "ComicTagger"
|
||||
version = "1.0" # TODO read in from a file??
|
||||
version = ctversion.version
|
||||
|
||||
def __init__(self, opts, settings, parent = None):
|
||||
super(TaggerWindow, self).__init__(parent)
|
||||
@ -69,6 +94,7 @@ class TaggerWindow( QtGui.QMainWindow):
|
||||
self.setAcceptDrops(True)
|
||||
self.droppedFile=None
|
||||
|
||||
self.page_browser = None
|
||||
|
||||
self.populateComboBoxes()
|
||||
|
||||
@ -78,6 +104,7 @@ class TaggerWindow( QtGui.QMainWindow):
|
||||
self.btnAddCredit.clicked.connect(self.addCredit)
|
||||
self.btnRemoveCredit.clicked.connect(self.removeCredit)
|
||||
self.twCredits.cellDoubleClicked.connect(self.editCredit)
|
||||
clickable(self.lblCover).connect(self.showPageBrowser)
|
||||
self.connectDirtyFlagSignals()
|
||||
|
||||
self.updateStyleTweaks()
|
||||
@ -260,6 +287,9 @@ class TaggerWindow( QtGui.QMainWindow):
|
||||
img.loadFromData( image_data )
|
||||
self.lblCover.setPixmap(QtGui.QPixmap(img))
|
||||
self.lblCover.setScaledContents(True)
|
||||
|
||||
if self.page_browser is not None:
|
||||
self.page_browser.setComicArchive( self.comic_archive )
|
||||
|
||||
self.metadataToForm()
|
||||
self.clearDirtyFlag() # also updates the app title
|
||||
@ -565,6 +595,7 @@ class TaggerWindow( QtGui.QMainWindow):
|
||||
selector = VolumeSelectionWindow( self, self.settings.cv_api_key, series_name, issue_number, self.comic_archive, self.settings )
|
||||
selector.setModal(True)
|
||||
selector.exec_()
|
||||
|
||||
|
||||
if selector.result():
|
||||
#we should now have a volume ID
|
||||
@ -848,3 +879,13 @@ class TaggerWindow( QtGui.QMainWindow):
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def showPageBrowser( self ):
|
||||
if self.page_browser is None:
|
||||
self.page_browser = PageBrowserWindow( self )
|
||||
if self.comic_archive is not None:
|
||||
self.page_browser.setComicArchive( self.comic_archive )
|
||||
self.page_browser.finished.connect(self.pageBrowserClosed)
|
||||
|
||||
def pageBrowserClosed( self ):
|
||||
self.page_browser = None
|
||||
|
1346
taggerwindow.ui
1346
taggerwindow.ui
File diff suppressed because it is too large
Load Diff
14
todo.txt
14
todo.txt
@ -9,10 +9,13 @@ Infobox needs fixing up
|
||||
|
||||
Page Browser, mode-less dialog
|
||||
|
||||
Multi-match dialog
|
||||
|
||||
More auto-select logic using metadata
|
||||
|
||||
Auto-select:
|
||||
msgbox on autoselect failure, or warning
|
||||
Multi-match dialog
|
||||
More auto-select logic using metadata
|
||||
Page search on only match, but bad image match, to find cover??
|
||||
Check aspect ratio, and maybe break cover into two parts for hashing?
|
||||
|
||||
Stand-alone CLI
|
||||
Info dump
|
||||
optionless args
|
||||
@ -53,7 +56,8 @@ Lots of error checking
|
||||
Other settings possibilities:
|
||||
Last tag style
|
||||
Last "Open" folder (include dragged)
|
||||
Keep a history of queries somewhere??
|
||||
Clear caches
|
||||
|
||||
|
||||
Content Hashes!!
|
||||
|
||||
|
@ -101,13 +101,9 @@ class VolumeSelectionWindow(QtGui.QDialog):
|
||||
self.btnIssues.clicked.connect(self.showIssues)
|
||||
self.btnAutoSelect.clicked.connect(self.autoSelect)
|
||||
|
||||
self.show()
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
|
||||
self.performQuery()
|
||||
self.twList.selectRow(0)
|
||||
|
||||
|
||||
def requery( self, ):
|
||||
self.performQuery( refresh=True )
|
||||
self.twList.selectRow(0)
|
||||
@ -188,9 +184,10 @@ class VolumeSelectionWindow(QtGui.QDialog):
|
||||
self.search_thread.progressUpdate.connect( self.searchProgressUpdate )
|
||||
self.search_thread.start()
|
||||
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
#QtCore.QCoreApplication.processEvents()
|
||||
self.progdialog.exec_()
|
||||
|
||||
|
||||
def searchCanceled( self ):
|
||||
print "query cancelled"
|
||||
self.search_thread.searchComplete.disconnect( self.searchComplete )
|
||||
|
@ -1,25 +1,37 @@
|
||||
# This Makefile expects that certain GNU utils are available:
|
||||
# rm, cp, grep, cut, cat
|
||||
|
||||
TAGGER_BASE:= c:\Users\tony\Dropbox\tagger\comictagger
|
||||
DIST_DIR := $(TAGGER_BASE)\windows\dist
|
||||
NSIS_CMD := "C:\Program Files (x86)\NSIS\makensis.exe"
|
||||
VERSION := $(shell grep version "$(TAGGER_BASE)\ctversion.py" | cut -d= -f2)
|
||||
|
||||
all: clean
|
||||
all: clean dist package
|
||||
|
||||
win_dist:
|
||||
dist:
|
||||
cd "$(TAGGER_BASE)" &
|
||||
"C:\Python27\Scripts\cxfreeze.bat" $(TAGGER_BASE)\tagger.py --icon nsis\app.ico --base-name=Win32GUI
|
||||
"C:\Python27\Scripts\cxfreeze.bat" $(TAGGER_BASE)\tagger.py --icon nsis\app.ico
|
||||
# --base-name=Win32GUI
|
||||
cp -R C:\Python27\Lib\site-packages\PyQt4\plugins\imageformats $(DIST_DIR)
|
||||
cp "$(TAGGER_BASE)\UnRAR2\UnRARDLL\unrar.dll" $(DIST_DIR)
|
||||
cp "$(TAGGER_BASE)\*.ui" $(DIST_DIR)
|
||||
cp "$(TAGGER_BASE)\nocover.png" $(DIST_DIR)
|
||||
cp "$(TAGGER_BASE)\app.png" $(DIST_DIR)
|
||||
cp -r "$(TAGGER_BASE)\graphics" $(DIST_DIR)
|
||||
|
||||
rm "$(DIST_DIR)\QtWebKit4.dll"
|
||||
rm "$(DIST_DIR)\PyQt4.QtWebKit.pyd"
|
||||
|
||||
package:
|
||||
echo !define RELEASE_STR $(VERSION) > $(TAGGER_BASE)\windows\nsis\release.nsh
|
||||
$(NSIS_CMD) "$(TAGGER_BASE)\windows\nsis\comictagger.nsi"
|
||||
mv "$(TAGGER_BASE)\windows\nsis\ComicTagger*.exe" "$(TAGGER_BASE)\windows"
|
||||
|
||||
clean:
|
||||
rm -rf dist
|
||||
-rm -rf dist
|
||||
-rm -f "*~" *.pyc *.pyo
|
||||
-rm "$(TAGGER_BASE)\windows\*.exe"
|
||||
-rm -f "$(TAGGER_BASE)\windows\*.exe"
|
||||
|
||||
test:
|
||||
echo !define RELEASE_STR $(VERSION) > test.nsh
|
||||
|
@ -1 +1 @@
|
||||
make clean win_dist
|
||||
make clean dist package
|
@ -1 +0,0 @@
|
||||
!define RELEASE_STR "1.0"
|
Loading…
Reference in New Issue
Block a user