mirror of https://github.com/evilhero/mylar
436 lines
13 KiB
Python
436 lines
13 KiB
Python
# coding=utf-8
|
|
"""
|
|
A PyQt4 widget for managing list of comic archive files
|
|
"""
|
|
|
|
"""
|
|
Copyright 2012-2014 Anthony Beville
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
from PyQt4.QtCore import *
|
|
from PyQt4.QtGui import *
|
|
from PyQt4 import uic
|
|
from PyQt4.QtCore import pyqtSignal
|
|
|
|
from settings import ComicTaggerSettings
|
|
from comicarchive import ComicArchive
|
|
from comicarchive import MetaDataStyle
|
|
from genericmetadata import GenericMetadata, PageType
|
|
from optionalmsgdialog import OptionalMessageDialog
|
|
import utils
|
|
import platform
|
|
import os
|
|
|
|
class FileTableWidget( QTableWidget ):
|
|
|
|
def __init__(self, parent ):
|
|
super(FileTableWidget, self).__init__(parent)
|
|
|
|
|
|
self.setColumnCount(5)
|
|
self.setHorizontalHeaderLabels (["File", "Folder", "CR", "CBL", ""])
|
|
self.horizontalHeader().setStretchLastSection( True )
|
|
|
|
|
|
class FileTableWidgetItem(QTableWidgetItem):
|
|
def __lt__(self, other):
|
|
return (self.data(Qt.UserRole).toBool() <
|
|
other.data(Qt.UserRole).toBool())
|
|
|
|
|
|
class FileInfo( ):
|
|
def __init__(self, ca ):
|
|
self.ca = ca
|
|
|
|
class FileSelectionList(QWidget):
|
|
|
|
selectionChanged = pyqtSignal(QVariant)
|
|
listCleared = pyqtSignal()
|
|
|
|
fileColNum = 0
|
|
CRFlagColNum = 1
|
|
CBLFlagColNum = 2
|
|
typeColNum = 3
|
|
readonlyColNum = 4
|
|
folderColNum = 5
|
|
dataColNum = fileColNum
|
|
|
|
|
|
def __init__(self, parent , settings ):
|
|
super(FileSelectionList, self).__init__(parent)
|
|
|
|
uic.loadUi(ComicTaggerSettings.getUIFile('fileselectionlist.ui' ), self)
|
|
|
|
self.settings = settings
|
|
|
|
utils.reduceWidgetFontSize( self.twList )
|
|
|
|
self.twList.currentItemChanged.connect( self.currentItemChangedCB )
|
|
|
|
self.currentItem = None
|
|
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
|
self.modifiedFlag = False
|
|
|
|
selectAllAction = QAction("Select All", self)
|
|
removeAction = QAction("Remove Selected Items", self)
|
|
self.separator = QAction("",self)
|
|
self.separator.setSeparator(True)
|
|
|
|
selectAllAction.setShortcut( 'Ctrl+A' )
|
|
removeAction.setShortcut( 'Ctrl+X' )
|
|
|
|
selectAllAction.triggered.connect(self.selectAll)
|
|
removeAction.triggered.connect(self.removeSelection)
|
|
|
|
self.addAction(selectAllAction)
|
|
self.addAction(removeAction)
|
|
self.addAction(self.separator)
|
|
|
|
def getSorting(self):
|
|
col = self.twList.horizontalHeader().sortIndicatorSection()
|
|
order = self.twList.horizontalHeader().sortIndicatorOrder()
|
|
return col, order
|
|
|
|
def setSorting(self, col, order):
|
|
col = self.twList.horizontalHeader().setSortIndicator( col, order)
|
|
|
|
def addAppAction( self, action ):
|
|
self.insertAction( None , action )
|
|
|
|
def setModifiedFlag( self, modified ):
|
|
self.modifiedFlag = modified
|
|
|
|
def selectAll( self ):
|
|
self.twList.setRangeSelected( QTableWidgetSelectionRange ( 0, 0, self.twList.rowCount()-1, 5 ), True )
|
|
|
|
def deselectAll( self ):
|
|
self.twList.setRangeSelected( QTableWidgetSelectionRange ( 0, 0, self.twList.rowCount()-1, 5 ), False )
|
|
|
|
def removeArchiveList( self, ca_list ):
|
|
self.twList.setSortingEnabled(False)
|
|
for ca in ca_list:
|
|
for row in range(self.twList.rowCount()):
|
|
row_ca = self.getArchiveByRow( row )
|
|
if row_ca == ca:
|
|
self.twList.removeRow(row)
|
|
break
|
|
self.twList.setSortingEnabled(True)
|
|
|
|
def getArchiveByRow( self, row):
|
|
fi = self.twList.item(row, FileSelectionList.dataColNum).data( Qt.UserRole ).toPyObject()
|
|
return fi.ca
|
|
|
|
def getCurrentArchive( self ):
|
|
return self.getArchiveByRow( self.twList.currentRow() )
|
|
|
|
def removeSelection( self ):
|
|
row_list = []
|
|
for item in self.twList.selectedItems():
|
|
if item.column() == 0:
|
|
row_list.append(item.row())
|
|
|
|
if len(row_list) == 0:
|
|
return
|
|
|
|
if self.twList.currentRow() in row_list:
|
|
if not self.modifiedFlagVerification( "Remove Archive",
|
|
"If you close this archive, data in the form will be lost. Are you sure?"):
|
|
return
|
|
|
|
row_list.sort()
|
|
row_list.reverse()
|
|
|
|
self.twList.currentItemChanged.disconnect( self.currentItemChangedCB )
|
|
self.twList.setSortingEnabled(False)
|
|
|
|
for i in row_list:
|
|
self.twList.removeRow(i)
|
|
|
|
self.twList.setSortingEnabled(True)
|
|
self.twList.currentItemChanged.connect( self.currentItemChangedCB )
|
|
|
|
if self.twList.rowCount() > 0:
|
|
# since on a removal, we select row 0, make sure callback occurs if we're already there
|
|
if self.twList.currentRow() == 0:
|
|
self.currentItemChangedCB( self.twList.currentItem(), None)
|
|
self.twList.selectRow(0)
|
|
else:
|
|
self.listCleared.emit()
|
|
|
|
def addPathList( self, pathlist ):
|
|
|
|
filelist = utils.get_recursive_filelist( pathlist )
|
|
# we now have a list of files to add
|
|
|
|
progdialog = QProgressDialog("", "Cancel", 0, len(filelist), self)
|
|
progdialog.setWindowTitle( "Adding Files" )
|
|
#progdialog.setWindowModality(Qt.WindowModal)
|
|
progdialog.setWindowModality(Qt.ApplicationModal)
|
|
progdialog.show()
|
|
|
|
firstAdded = None
|
|
self.twList.setSortingEnabled(False)
|
|
for idx,f in enumerate(filelist):
|
|
QCoreApplication.processEvents()
|
|
if progdialog.wasCanceled():
|
|
break
|
|
progdialog.setValue(idx)
|
|
progdialog.setLabelText(f)
|
|
utils.centerWindowOnParent( progdialog )
|
|
QCoreApplication.processEvents()
|
|
row = self.addPathItem( f )
|
|
if firstAdded is None and row is not None:
|
|
firstAdded = row
|
|
|
|
progdialog.close()
|
|
if ( self.settings.show_no_unrar_warning and
|
|
self.settings.unrar_exe_path == "" and
|
|
self.settings.rar_exe_path == "" and
|
|
platform.system() != "Windows"):
|
|
for f in filelist:
|
|
ext = os.path.splitext(f)[1].lower()
|
|
if ext == ".rar" or ext ==".cbr":
|
|
checked = OptionalMessageDialog.msg( self, "No unrar tool",
|
|
"""
|
|
It looks like you've tried to open at least one CBR or RAR file.<br><br>
|
|
In order for ComicTagger to read this kind of file, you will have to configure
|
|
the location of the unrar tool in the settings. Until then, ComicTagger
|
|
will not be able recognize these kind of files.
|
|
"""
|
|
)
|
|
self.settings.show_no_unrar_warning = not checked
|
|
break
|
|
|
|
if firstAdded is not None:
|
|
self.twList.selectRow(firstAdded)
|
|
else:
|
|
if len(pathlist) == 1 and os.path.isfile(pathlist[0]):
|
|
QMessageBox.information(self, self.tr("File Open"), self.tr("Selected file doesn't seem to be a comic archive."))
|
|
else:
|
|
QMessageBox.information(self, self.tr("File/Folder Open"), self.tr("No comic archives were found."))
|
|
|
|
|
|
self.twList.setSortingEnabled(True)
|
|
|
|
# Adjust column size
|
|
self.twList.resizeColumnsToContents()
|
|
self.twList.setColumnWidth(FileSelectionList.CRFlagColNum, 35)
|
|
self.twList.setColumnWidth(FileSelectionList.CBLFlagColNum, 35)
|
|
self.twList.setColumnWidth(FileSelectionList.readonlyColNum, 35)
|
|
self.twList.setColumnWidth(FileSelectionList.typeColNum, 45)
|
|
if self.twList.columnWidth(FileSelectionList.fileColNum) > 250:
|
|
self.twList.setColumnWidth(FileSelectionList.fileColNum, 250)
|
|
if self.twList.columnWidth(FileSelectionList.folderColNum ) > 200:
|
|
self.twList.setColumnWidth(FileSelectionList.folderColNum, 200)
|
|
|
|
def isListDupe( self, path ):
|
|
r = 0
|
|
while r < self.twList.rowCount():
|
|
ca = self.getArchiveByRow( r )
|
|
if ca.path == path:
|
|
return True
|
|
r = r + 1
|
|
|
|
return False
|
|
|
|
def getCurrentListRow( self, path ):
|
|
r = 0
|
|
while r < self.twList.rowCount():
|
|
ca = self.getArchiveByRow( r )
|
|
if ca.path == path:
|
|
return r
|
|
r = r + 1
|
|
|
|
return -1
|
|
|
|
def addPathItem( self, path):
|
|
path = unicode( path )
|
|
path = os.path.abspath( path )
|
|
#print "processing", path
|
|
|
|
if self.isListDupe(path):
|
|
return self.getCurrentListRow(path)
|
|
|
|
ca = ComicArchive( path, self.settings.rar_exe_path )
|
|
|
|
if ca.seemsToBeAComicArchive() :
|
|
row = self.twList.rowCount()
|
|
self.twList.insertRow( row )
|
|
|
|
fi = FileInfo( ca )
|
|
|
|
filename_item = QTableWidgetItem()
|
|
folder_item = QTableWidgetItem()
|
|
cix_item = FileTableWidgetItem()
|
|
cbi_item = FileTableWidgetItem()
|
|
readonly_item = FileTableWidgetItem()
|
|
type_item = QTableWidgetItem()
|
|
|
|
filename_item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
|
|
filename_item.setData( Qt.UserRole , fi )
|
|
self.twList.setItem(row, FileSelectionList.fileColNum, filename_item)
|
|
|
|
folder_item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
|
|
self.twList.setItem(row, FileSelectionList.folderColNum, folder_item)
|
|
|
|
type_item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
|
|
self.twList.setItem(row, FileSelectionList.typeColNum, type_item)
|
|
|
|
cix_item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
|
|
cix_item.setTextAlignment(Qt.AlignHCenter)
|
|
self.twList.setItem(row, FileSelectionList.CRFlagColNum, cix_item)
|
|
|
|
cbi_item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
|
|
cbi_item.setTextAlignment(Qt.AlignHCenter)
|
|
self.twList.setItem(row, FileSelectionList.CBLFlagColNum, cbi_item)
|
|
|
|
readonly_item.setFlags(Qt.ItemIsSelectable| Qt.ItemIsEnabled)
|
|
readonly_item.setTextAlignment(Qt.AlignHCenter)
|
|
self.twList.setItem(row, FileSelectionList.readonlyColNum, readonly_item)
|
|
|
|
self.updateRow( row )
|
|
|
|
return row
|
|
|
|
def updateRow( self, row ):
|
|
fi = self.twList.item( row, FileSelectionList.dataColNum ).data( Qt.UserRole ).toPyObject()
|
|
|
|
filename_item = self.twList.item( row, FileSelectionList.fileColNum )
|
|
folder_item = self.twList.item( row, FileSelectionList.folderColNum )
|
|
cix_item = self.twList.item( row, FileSelectionList.CRFlagColNum )
|
|
cbi_item = self.twList.item( row, FileSelectionList.CBLFlagColNum )
|
|
type_item = self.twList.item( row, FileSelectionList.typeColNum )
|
|
readonly_item = self.twList.item( row, FileSelectionList.readonlyColNum )
|
|
|
|
item_text = os.path.split(fi.ca.path)[0]
|
|
folder_item.setText( item_text )
|
|
folder_item.setData( Qt.ToolTipRole, item_text )
|
|
|
|
item_text = os.path.split(fi.ca.path)[1]
|
|
filename_item.setText( item_text )
|
|
filename_item.setData( Qt.ToolTipRole, item_text )
|
|
|
|
if fi.ca.isZip():
|
|
item_text = "ZIP"
|
|
elif fi.ca.isRar():
|
|
item_text = "RAR"
|
|
else:
|
|
item_text = ""
|
|
type_item.setText( item_text )
|
|
type_item.setData( Qt.ToolTipRole, item_text )
|
|
|
|
|
|
if fi.ca.hasCIX():
|
|
cix_item.setCheckState(Qt.Checked)
|
|
cix_item.setData(Qt.UserRole, True)
|
|
else:
|
|
cix_item.setData(Qt.UserRole, False)
|
|
cix_item.setCheckState(Qt.Unchecked)
|
|
|
|
if fi.ca.hasCBI():
|
|
cbi_item.setCheckState(Qt.Checked)
|
|
cbi_item.setData(Qt.UserRole, True)
|
|
else:
|
|
cbi_item.setData(Qt.UserRole, False)
|
|
cbi_item.setCheckState(Qt.Unchecked)
|
|
|
|
if not fi.ca.isWritable():
|
|
readonly_item.setCheckState(Qt.Checked)
|
|
readonly_item.setData(Qt.UserRole, True)
|
|
else:
|
|
readonly_item.setData(Qt.UserRole, False)
|
|
readonly_item.setCheckState(Qt.Unchecked)
|
|
|
|
|
|
# Reading these will force them into the ComicArchive's cache
|
|
fi.ca.readCIX()
|
|
fi.ca.hasCBI()
|
|
|
|
def getSelectedArchiveList( self ):
|
|
ca_list = []
|
|
for r in range( self.twList.rowCount() ):
|
|
item = self.twList.item(r, FileSelectionList.dataColNum)
|
|
if self.twList.isItemSelected(item):
|
|
fi = item.data( Qt.UserRole ).toPyObject()
|
|
ca_list.append(fi.ca)
|
|
|
|
return ca_list
|
|
|
|
def updateCurrentRow( self ):
|
|
self.updateRow( self.twList.currentRow() )
|
|
|
|
def updateSelectedRows( self ):
|
|
self.twList.setSortingEnabled(False)
|
|
for r in range( self.twList.rowCount() ):
|
|
item = self.twList.item(r, FileSelectionList.dataColNum)
|
|
if self.twList.isItemSelected(item):
|
|
self.updateRow( r )
|
|
self.twList.setSortingEnabled(True)
|
|
|
|
def currentItemChangedCB( self, curr, prev ):
|
|
|
|
new_idx = curr.row()
|
|
old_idx = -1
|
|
if prev is not None:
|
|
old_idx = prev.row()
|
|
#print "old {0} new {1}".format(old_idx, new_idx)
|
|
|
|
if old_idx == new_idx:
|
|
return
|
|
|
|
# don't allow change if modified
|
|
if prev is not None and new_idx != old_idx:
|
|
if not self.modifiedFlagVerification( "Change Archive",
|
|
"If you change archives now, data in the form will be lost. Are you sure?"):
|
|
self.twList.currentItemChanged.disconnect( self.currentItemChangedCB )
|
|
self.twList.setCurrentItem( prev )
|
|
self.twList.currentItemChanged.connect( self.currentItemChangedCB )
|
|
# Need to defer this revert selection, for some reason
|
|
QTimer.singleShot(1, self.revertSelection)
|
|
return
|
|
|
|
fi = self.twList.item( new_idx, FileSelectionList.dataColNum ).data( Qt.UserRole ).toPyObject()
|
|
self.selectionChanged.emit( QVariant(fi))
|
|
|
|
def revertSelection( self ):
|
|
self.twList.selectRow( self.twList.currentRow() )
|
|
|
|
|
|
def modifiedFlagVerification( self, title, desc):
|
|
if self.modifiedFlag:
|
|
reply = QMessageBox.question(self,
|
|
self.tr(title),
|
|
self.tr(desc),
|
|
QMessageBox.Yes, QMessageBox.No )
|
|
|
|
if reply != QMessageBox.Yes:
|
|
return False
|
|
return True
|
|
|
|
|
|
# Attempt to use a special checkbox widget in the cell.
|
|
# Couldn't figure out how to disable it with "enabled" colors
|
|
#w = QWidget()
|
|
#cb = QCheckBox(w)
|
|
#cb.setCheckState(Qt.Checked)
|
|
#layout = QHBoxLayout()
|
|
#layout.addWidget( cb )
|
|
#layout.setAlignment(Qt.AlignHCenter)
|
|
#layout.setMargin(2)
|
|
#w.setLayout(layout)
|
|
#self.twList.setCellWidget( row, 2, w ) |