about summary refs log blame commit diff
path: root/kindlecomicconverter/KCC_gui.py
blob: 33ef9849aa5c2e385844555587fdf533fe82915f (plain) (tree)
1
2
3
4
5
6
7
8
9
                       
 
                                                                   
                                                              
 



                                                                      
 







                                                                      
 

          
                                
                                                        
                      
                       
                                   
                                     
                                                     
                                 
                                   
                                 
                     
                                           
                        
                                                            
                         
                         
                      
                    

                           

 











                                                                     









































                                                                        

 


                                         
                                                                                                                     

                                                                                                                 

                                                                                                                   
 

                                                                                                                 





                                                                                                                 
                                                                                                          
                                    
                                                                                                                
                                  
                                                                                                            
 


                                                                                                                      
 
                                    
                       
                                     
                            
                     

                            





                      

                                                                                                        
                         
                  
                                                                                                        
                                                                     

                                               
                                                                                                 
                                                                                                                        
                                                                                                    

                                                                                                     
                                    
                 
                                                                      
                                                                              


                                                                                            

                                      




                                                    


                                                             

                                                                                               
                                                    
                                   


                                               
                                                                                                      

                                       
                                                                                      


                                         

                                                               
                     



                                           

 













                                     
                                                    
                                                                                    







                                      
                                   
                       
                                     

                                    

                                     

                                                 



                      
                   
                                                  

                    


                                 


                                                                            
                              
 
                                         
                  
                              


                                         
                 
                        
 


                                                                                 
                                      
                                           
                                
                                             
                                
                                            
                                    

                                              
                                      
                                  
                                            
                                  
                                              
                                  


                                                                     
                                        
                                             
                                        

                                       
                                    
                                     
                               

                                                             
 
                                            
                                                                              


                                                                   
                               
                      


                                        
                               
                                                                      
                                                         
                                                                       
                                                           
                 
                                                                        
                                                            


                                

                                                   
                                                            
                                         
                                       



                                            
                                             
                                      
                                                                   



                                                                                                                  
                                    
                                         
                                  
                                                






                                                                                                    

                                                    



                                                                                                              
                                        


                                                
                                           

                            
                               
                                         
                                                             
                                                                                          
                     
                                                                                           
                                                                   
                                                                  
                                                                         
                                                   

                                                                            
                             
                                           

                                                                        







                                                            
                                               
                                                                              
                                                                         


                                                       
                                                 
                                                                                               

                                                                                  
                                              
                                               

                                                                                             
                                                           
                                                         
                                              





                                                                         
                                                                
                                                                                                            

                                                                           

                                                     
                                                                                                     

                                                         


                                                                                                       
                                                                                                                      


                                                     
                                                                         
                                                            
                                                       
                                                                         
                                                                    

                                                                                              
                         
                                                 
                                                                                                

                                                    
                                               
                                                                              
                                                                         

                                                                                              
                                                                                                
                                                                                                            
                                                               
                                                                                                
                                                                                                                      
                                                     

                                           
                                                                                                

                                                               

                                             

                                 
                                 
                            


                                                                           
                              

 

                                                
                          
                                        
                                                                               
                                                    

                          
                       

                           

                                            


                                                                     

 
                                   
                        

                                  
                               




                                                                                                 

                                        

                         

                                  
                               


                                                                                                               
             
                                                                                                              




                                                                               

                                            
 
                                   







                                                                                                     
             


                                                                                               
                 

                                                                                   






                                                                               
                
                                           

                                                
                                             

                                                                                   


                                      
                        
                           
 
                       
                                    




                                                                                             

                                              

                                

                                              

                                

                                             

                                  



                          










                                              
                       



                                                                                                           


                                                  
                         



                                                                                                         


                                                   


                                       

                                                   
 
                                    






                                     
                                      
                 







                                            
             
                                                                    
                                    
                                               


                                           
 


                                                                
                                                
                                                                                              
                                                                                                     





                                                                
                                 

                                                   
                                
                                                 
             


                                                          

                           
                                                                
                                  
                              
                                      
                              
             

                              
                                   
                           

                                                           
                                                            
                                    
                                            
                                                       

                                                                                                 
 
                                              
                                                                

                                                       
             
                                                                   

                                                           




                                                           
 




                              

                                                       
                                             
                                                                                   
             
                                                                             
                   
                                                         

                                                                                                                

                                                       
                                                                                                        
                                        


                                              
 



                                                                                                


                                                                                                    
 

                                         
                                                                 
                               



                                                        
             




                                              
 
                           
                                
                                               
                                                                                       


                                        









                                                                                                                
                                 

                                      

                                        


                                                                                              

                                                                                                  


                                                                         
                                                                                      
                                      
                                      
                                       

                                                                                                                 

                                                                                               

                                                                                                        



                                                                                                                 
                               
 
                                  
                                
                                               
                                                                                       


                                        
                                             
                          
                                                              
                                                         

                                                                             
                                                                   
                                                                                                    






                                                                                     
                                                                                       



                                                                                        
                            
                        
 
                                     

                           

                                             
                                                           

                                      
                                   


                                                                               
                                      

                                            


                                                     

                                                
                     
                                                                                    
 




                                           
                                                                         

                                                    

                                       

                                          

                                       



                               





                                                           


                                                                                                 
                                 
                                                                                                



                                                                   
                                                                          
                                                                                                            
                                                                                                                        

                         
                                  




                                                                                                                       

                                                                                                    


                                                                                                             
                                          
                           

                      

                        
                                         
                            
                                                                                        
                                                                                   
                                                                     
                                                                        
                                                                              
                                                                          
                                                                            
                                                                         

                                           
                                        
                                    
                                    
                             
                              
                             
                            
                                 
                                                 
                                          
                                                 
                                                          


                                                    




                                               


                                                                                                                      
                                                             

                                                                                                                      

                                        
 
                         

                                                                                           
                                                                                         
                                                                    
                                                                                          
                                                                     

                                                                                          
                                                                                          
                                                                       
                                                                                   
                                                                 
                                                                                           
                                                                       
                                                                                             
                                                                          
                                                                                      
                                                                  
                                                                                         
                                                                       
                                                                                       
                                                                   
                                                                                          
                                                                       
                                                                                           
                                                                         
                                                                                           
                                                                       
                                                                                        
                                                                   
                                                                                  
                                                                 
                                                                                      
                                                                 
                                                                                      
                                                                 



                                                                                             

                       
                             

                            
                            
                            
                     
                        
                         
                            
                            

                           


                        

                              
                            

                       



                              

         
                                                                                                                       
                                                                                                                        

                                                                                                                        
                                                          
                                                 

                                                           
                                                  
                                                                                                        
                                
                                                                                    

                                                                                                                  



                                                                     
             


                                                                                              
                                  
 
                                                                








                                                                   
                                                                  

                                                          


                                                          

                                                        
                                                    
                                         
                                                           
 


                                                              
 
                          

                                   
                                                                      
                                        
                                                                        
                                 
                                                                     
                 
                                                                       
                                              

                                                                                             
                               

                                                       
                                                      
                                  
                                                      
                           
                                                                                                  
                                                 
                                   






                                                                       
                                                               
                 




                                                                                      
                          
                                 
                        
 


                                             


                                                                  

 
                                                       

                                                   
                                                 


                                                                   
             


                                                                      

                                                                                 


                                                                                                  



                                                                                          

                       
                                                        
                                                                              
                                                                                                     
                 
                                                                                                           

                     

                                                                                                  




                                                                
                                                                                      

                                     

                                                
                                             

                                                                                 









                                                                                              

                                                        





                                                          
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.

import os
import sys
from urllib.parse import unquote
from urllib.request import urlopen, urlretrieve, Request
from time import sleep
from shutil import move
from subprocess import STDOUT, PIPE
# noinspection PyUnresolvedReferences
from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork
from xml.dom.minidom import parse
from xml.sax.saxutils import escape
from psutil import Popen, Process
from copy import copy
from distutils.version import StrictVersion
from raven import Client
from .shared import md5Checksum, HTMLStripper, sanitizeTrace
from . import __version__
from . import comic2ebook
from . import metadata
from . import kindle
from . import KCC_ui
from . import KCC_ui_editor


class QApplicationMessaging(QtWidgets.QApplication):
    messageFromOtherInstance = QtCore.pyqtSignal(bytes)

    def __init__(self, argv):
        QtWidgets.QApplication.__init__(self, argv)
        self._key = 'KCC'
        self._timeout = 1000
        self._locked = False
        socket = QtNetwork.QLocalSocket(self)
        socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
        if not socket.waitForConnected(self._timeout):
            self._server = QtNetwork.QLocalServer(self)
            self._server.newConnection.connect(self.handleMessage)
            self._server.listen(self._key)
        else:
            self._locked = True
        socket.disconnectFromServer()

    def __del__(self):
        if not self._locked:
            self._server.close()

    def event(self, e):
        if e.type() == QtCore.QEvent.FileOpen:
            self.messageFromOtherInstance.emit(bytes(e.file(), 'UTF-8'))
            return True
        else:
            return QtWidgets.QApplication.event(self, e)

    def isRunning(self):
        return self._locked

    def handleMessage(self):
        socket = self._server.nextPendingConnection()
        if socket.waitForReadyRead(self._timeout):
            self.messageFromOtherInstance.emit(socket.readAll().data())

    def sendMessage(self, message):
        socket = QtNetwork.QLocalSocket(self)
        socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
        socket.waitForConnected(self._timeout)
        socket.write(bytes(message, 'UTF-8'))
        socket.waitForBytesWritten(self._timeout)
        socket.disconnectFromServer()


class QMainWindowKCC(QtWidgets.QMainWindow):
    progressBarTick = QtCore.pyqtSignal(str)
    modeConvert = QtCore.pyqtSignal(int)
    addMessage = QtCore.pyqtSignal(str, str, bool)
    addTrayMessage = QtCore.pyqtSignal(str, str)
    showDialog = QtCore.pyqtSignal(str, str)
    hideProgressBar = QtCore.pyqtSignal()
    forceShutdown = QtCore.pyqtSignal()


class Icons:
    def __init__(self):
        self.deviceKindle = QtGui.QIcon()
        self.deviceKindle.addPixmap(QtGui.QPixmap(":/Devices/icons/Kindle.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.deviceKobo = QtGui.QIcon()
        self.deviceKobo.addPixmap(QtGui.QPixmap(":/Devices/icons/Kobo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.deviceOther = QtGui.QIcon()
        self.deviceOther.addPixmap(QtGui.QPixmap(":/Devices/icons/Other.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

        self.MOBIFormat = QtGui.QIcon()
        self.MOBIFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/MOBI.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.CBZFormat = QtGui.QIcon()
        self.CBZFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/CBZ.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.EPUBFormat = QtGui.QIcon()
        self.EPUBFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/EPUB.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

        self.info = QtGui.QIcon()
        self.info.addPixmap(QtGui.QPixmap(":/Status/icons/info.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.warning = QtGui.QIcon()
        self.warning.addPixmap(QtGui.QPixmap(":/Status/icons/warning.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.error = QtGui.QIcon()
        self.error.addPixmap(QtGui.QPixmap(":/Status/icons/error.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

        self.programIcon = QtGui.QIcon()
        self.programIcon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)


class VersionThread(QtCore.QThread):
    def __init__(self):
        QtCore.QThread.__init__(self)
        self.newVersion = ''
        self.md5 = ''
        self.barProgress = 0
        self.answer = None

    def __del__(self):
        self.wait()

    def run(self):
        try:
            XML = parse(urlopen(Request('https://kcc.iosphe.re/Version/',
                                        headers={'User-Agent': 'KindleComicConverter/' + __version__})))
        except Exception:
            return
        latestVersion = XML.childNodes[0].getElementsByTagName('LatestVersion')[0].childNodes[0].toxml()
        if StrictVersion(latestVersion) > StrictVersion(__version__):
            if sys.platform.startswith('win'):
                self.newVersion = latestVersion
                self.md5 = XML.childNodes[0].getElementsByTagName('MD5')[0].childNodes[0].toxml()
                MW.showDialog.emit('<b>New version released!</b> <a href="https://github.com/ciromattia/kcc/releases/">'
                                   'See changelog.</a><br/><br/>Installed version: ' + __version__ +
                                   '<br/>Current version: ' + latestVersion +
                                   '<br/><br/>Would you like to start automatic update?', 'question')
                self.getNewVersion()
            else:
                MW.addMessage.emit('<a href="https://kcc.iosphe.re/">'
                                   '<b>The new version is available!</b></a> '
                                   '(<a href="https://github.com/ciromattia/kcc/releases/">'
                                   'Changelog</a>)', 'warning', False)

    def setAnswer(self, dialoganswer):
        self.answer = dialoganswer

    def getNewVersion(self):
        while self.answer is None:
            sleep(1)
        if self.answer == QtWidgets.QMessageBox.Yes:
            try:
                MW.modeConvert.emit(-1)
                MW.progressBarTick.emit('Downloading update')
                path = urlretrieve('https://kcc.iosphe.re/Windows/KindleComicConverter_win_' +
                                   self.newVersion + '.exe', reporthook=self.getNewVersionTick)
                if self.md5 != md5Checksum(path[0]):
                    raise Exception
                move(path[0], path[0] + '.exe')
                MW.hideProgressBar.emit()
                MW.modeConvert.emit(1)
                Popen(path[0] + '.exe  /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, shell=True)
                MW.forceShutdown.emit()
            except Exception:
                MW.addMessage.emit('Failed to download the update!', 'warning', False)
                MW.hideProgressBar.emit()
                MW.modeConvert.emit(1)

    def getNewVersionTick(self, size, blocksize, totalsize):
        progress = int((size / (totalsize // blocksize)) * 100)
        if size == 0:
            MW.progressBarTick.emit('100')
        if progress > self.barProgress:
            self.barProgress = progress
            MW.progressBarTick.emit('tick')


class ProgressThread(QtCore.QThread):
    def __init__(self):
        QtCore.QThread.__init__(self)
        self.running = False
        self.content = None
        self.progress = 0

    def __del__(self):
        self.wait()

    def run(self):
        self.running = True
        while self.running:
            sleep(1)
            if self.content and GUI.conversionAlive:
                MW.addMessage.emit(self.content + self.progress * '.', 'info', True)
                self.progress += 1
                if self.progress == 4:
                    self.progress = 0

    def stop(self):
        self.running = False


class WorkerThread(QtCore.QThread):
    def __init__(self):
        QtCore.QThread.__init__(self)
        self.conversionAlive = False
        self.errors = False
        self.kindlegenErrorCode = [0]
        self.workerOutput = []
        self.progressBarTick = MW.progressBarTick
        self.addMessage = MW.addMessage

    def __del__(self):
        self.wait()

    def sync(self):
        self.conversionAlive = GUI.conversionAlive

    def clean(self):
        GUI.progress.content = ''
        GUI.progress.stop()
        GUI.needClean = True
        MW.hideProgressBar.emit()
        MW.addMessage.emit('<b>Conversion interrupted.</b>', 'error', False)
        MW.addTrayMessage.emit('Conversion interrupted.', 'Critical')
        MW.modeConvert.emit(1)

    # noinspection PyUnboundLocalVariable
    def run(self):
        MW.modeConvert.emit(0)

        parser = comic2ebook.makeParser()
        options, _ = parser.parse_args()
        argv = ''
        currentJobs = []

        options.profile = GUI.profiles[str(GUI.deviceBox.currentText())]['Label']
        options.format = str(GUI.formatBox.currentText()).replace('/AZW3', '')
        if GUI.mangaBox.isChecked():
            options.righttoleft = True
        if GUI.rotateBox.checkState() == 1:
            options.splitter = 2
        elif GUI.rotateBox.checkState() == 2:
            options.splitter = 1
        if GUI.qualityBox.checkState() == 1:
            options.autoscale = True
        elif GUI.qualityBox.checkState() == 2:
            options.hq = True
        if GUI.webtoonBox.isChecked():
            options.webtoon = True
        if GUI.upscaleBox.checkState() == 1:
            options.stretch = True
        elif GUI.upscaleBox.checkState() == 2:
            options.upscale = True
        if GUI.gammaBox.isChecked() and float(GUI.gammaValue) > 0.09:
            options.gamma = float(GUI.gammaValue)
        if GUI.borderBox.checkState() == 1:
            options.white_borders = True
        elif GUI.borderBox.checkState() == 2:
            options.black_borders = True
        if GUI.outputSplit.isChecked():
            options.batchsplit = 2
        if GUI.colorBox.isChecked():
            options.forcecolor = True
        if GUI.currentMode > 2:
            options.customwidth = str(GUI.widthBox.value())
            options.customheight = str(GUI.heightBox.value())

        for i in range(GUI.jobList.count()):
            # Make sure that we don't consider any system message as job to do
            if GUI.jobList.item(i).icon().isNull():
                currentJobs.append(str(GUI.jobList.item(i).text()))
        GUI.jobList.clear()
        for job in currentJobs:
            sleep(0.5)
            if not self.conversionAlive:
                self.clean()
                return
            self.errors = False
            MW.addMessage.emit('<b>Source:</b> ' + job, 'info', False)
            if str(GUI.formatBox.currentText()) == 'CBZ':
                MW.addMessage.emit('Creating CBZ files', 'info', False)
                GUI.progress.content = 'Creating CBZ files'
            else:
                MW.addMessage.emit('Creating EPUB files', 'info', False)
                GUI.progress.content = 'Creating EPUB files'
            jobargv = list(argv)
            jobargv.append(job)
            try:
                comic2ebook.options = copy(options)
                comic2ebook.checkOptions()
                outputPath = comic2ebook.makeBook(job, self)
                MW.hideProgressBar.emit()
            except UserWarning as warn:
                if not self.conversionAlive:
                    self.clean()
                    return
                else:
                    GUI.progress.content = ''
                    self.errors = True
                    MW.addMessage.emit(str(warn), 'warning', False)
                    MW.addMessage.emit('Error during conversion! Please consult '
                                       '<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> '
                                       'for more details.', 'error', False)
                    MW.addTrayMessage.emit('Error during conversion!', 'Critical')
            except Exception as err:
                GUI.progress.content = ''
                self.errors = True
                _, _, traceback = sys.exc_info()
                if len(err.args) == 1:
                    MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
                                       % (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
                else:
                    MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
                                       % (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
                    GUI.sentry.extra_context({'realTraceback': err.args[1]})
                if ' is corrupted.' not in str(err):
                    GUI.sentry.captureException()
                MW.addMessage.emit('Error during conversion! Please consult '
                                   '<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> '
                                   'for more details.', 'error', False)
                MW.addTrayMessage.emit('Error during conversion!', 'Critical')
            if not self.conversionAlive:
                if 'outputPath' in locals():
                    for item in outputPath:
                        if os.path.exists(item):
                            os.remove(item)
                self.clean()
                return
            if not self.errors:
                GUI.progress.content = ''
                if str(GUI.formatBox.currentText()) == 'CBZ':
                    MW.addMessage.emit('Creating CBZ files... <b>Done!</b>', 'info', True)
                else:
                    MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', 'info', True)
                if str(GUI.formatBox.currentText()) == 'MOBI/AZW3':
                    MW.progressBarTick.emit('Creating MOBI files')
                    MW.progressBarTick.emit(str(len(outputPath) * 2 + 1))
                    MW.progressBarTick.emit('tick')
                    MW.addMessage.emit('Creating MOBI files', 'info', False)
                    GUI.progress.content = 'Creating MOBI files'
                    work = []
                    for item in outputPath:
                        work.append([item])
                    self.workerOutput = comic2ebook.makeMOBI(work, self)
                    self.kindlegenErrorCode = [0]
                    for errors in self.workerOutput:
                        if errors[0] != 0:
                            self.kindlegenErrorCode = errors
                            break
                    if not self.conversionAlive:
                        for item in outputPath:
                            if os.path.exists(item):
                                os.remove(item)
                            if os.path.exists(item.replace('.epub', '.mobi')):
                                os.remove(item.replace('.epub', '.mobi'))
                        self.clean()
                        return
                    if self.kindlegenErrorCode[0] == 0:
                        GUI.progress.content = ''
                        MW.addMessage.emit('Creating MOBI files... <b>Done!</b>', 'info', True)
                        MW.addMessage.emit('Processing MOBI files', 'info', False)
                        GUI.progress.content = 'Processing MOBI files'
                        self.workerOutput = []
                        for item in outputPath:
                            self.workerOutput.append(comic2ebook.makeMOBIFix(
                                item, comic2ebook.options.covers[outputPath.index(item)][1]))
                            MW.progressBarTick.emit('tick')
                        for success in self.workerOutput:
                            if not success[0]:
                                self.errors = True
                                break
                        if not self.errors:
                            for item in outputPath:
                                GUI.progress.content = ''
                                mobiPath = item.replace('.epub', '.mobi')
                                os.remove(mobiPath + '_toclean')
                                if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(mobiPath):
                                    try:
                                        move(mobiPath, GUI.targetDirectory)
                                    except Exception:
                                        pass
                            MW.addMessage.emit('Processing MOBI files... <b>Done!</b>', 'info', True)
                            k = kindle.Kindle()
                            if k.path and k.coverSupport:
                                for item in outputPath:
                                    comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle(
                                        k, comic2ebook.options.covers[outputPath.index(item)][1])
                                MW.addMessage.emit('Kindle detected. Uploading covers... <b>Done!</b>', 'info', False)
                        else:
                            GUI.progress.content = ''
                            for item in outputPath:
                                mobiPath = item.replace('.epub', '.mobi')
                                if os.path.exists(mobiPath):
                                    os.remove(mobiPath)
                                if os.path.exists(mobiPath + '_toclean'):
                                    os.remove(mobiPath + '_toclean')
                            MW.addMessage.emit('Failed to process MOBI file!', 'error', False)
                            MW.addTrayMessage.emit('Failed to process MOBI file!', 'Critical')
                    else:
                        GUI.progress.content = ''
                        epubSize = (os.path.getsize(self.kindlegenErrorCode[2])) // 1024 // 1024
                        for item in outputPath:
                            if os.path.exists(item):
                                os.remove(item)
                            if os.path.exists(item.replace('.epub', '.mobi')):
                                os.remove(item.replace('.epub', '.mobi'))
                        MW.addMessage.emit('KindleGen failed to create MOBI!', 'error', False)
                        MW.addTrayMessage.emit('KindleGen failed to create MOBI!', 'Critical')
                        if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '':
                            MW.showDialog.emit("KindleGen error:\n\n" + self.kindlegenErrorCode[1], 'error')
                        if self.kindlegenErrorCode[0] == 23026:
                            MW.addMessage.emit('Created EPUB file was too big.', 'error', False)
                            MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
                                               False)
                else:
                    for item in outputPath:
                        if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(item):
                            try:
                                move(item, GUI.targetDirectory)
                            except Exception:
                                pass
        GUI.progress.content = ''
        GUI.progress.stop()
        MW.hideProgressBar.emit()
        GUI.needClean = True
        if not self.errors:
            MW.addMessage.emit('<b>All jobs completed.</b>', 'info', False)
            MW.addTrayMessage.emit('All jobs completed.', 'Information')
        MW.modeConvert.emit(1)


class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
    def __init__(self):
        super().__init__()
        if self.isSystemTrayAvailable():
            QtWidgets.QSystemTrayIcon.__init__(self, GUI.icons.programIcon, MW)
            self.activated.connect(self.catchClicks)

    def catchClicks(self):
        MW.showNormal()
        MW.raise_()
        MW.activateWindow()

    def addTrayMessage(self, message, icon):
        icon = eval('QtWidgets.QSystemTrayIcon.' + icon)
        if self.supportsMessages() and not MW.isActiveWindow():
            self.showMessage('Kindle Comic Converter', message, icon)


class KCCGUI(KCC_ui.Ui_mainWindow):
    def selectDir(self):
        if self.needClean:
            self.needClean = False
            GUI.jobList.clear()
        dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select directory', self.lastPath)
        if dname != '':
            if sys.platform.startswith('win'):
                dname = dname.replace('/', '\\')
            self.lastPath = os.path.abspath(os.path.join(dname, os.pardir))
            GUI.jobList.addItem(dname)
            GUI.jobList.scrollToBottom()

    def selectFile(self):
        if self.needClean:
            self.needClean = False
            GUI.jobList.clear()
        if self.sevenzip:
            fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
                                                            'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)')
        else:
            fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, 'Comic (*.pdf)')
        for fname in fnames[0]:
            if fname != '':
                if sys.platform.startswith('win'):
                    fname = fname.replace('/', '\\')
                self.lastPath = os.path.abspath(os.path.join(fname, os.pardir))
                GUI.jobList.addItem(fname)
                GUI.jobList.scrollToBottom()

    def selectFileMetaEditor(self):
        sname = ''
        if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
            dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select directory', self.lastPath)
            if dname != '':
                sname = os.path.join(dname, 'ComicInfo.xml')
                if sys.platform.startswith('win'):
                    sname = sname.replace('/', '\\')
                self.lastPath = os.path.abspath(sname)
        else:
            if self.sevenzip:
                fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath,
                                                              'Comic (*.cbz *.cbr *.cb7)')
            else:
                fname = ['']
                self.showDialog("Editor is disabled due to a lack of 7z.", 'error')
            if fname[0] != '':
                if sys.platform.startswith('win'):
                    sname = fname[0].replace('/', '\\')
                else:
                    sname = fname[0]
                self.lastPath = os.path.abspath(os.path.join(sname, os.pardir))
        if sname != '':
            try:
                self.editor.loadData(sname)
            except Exception as err:
                _, _, traceback = sys.exc_info()
                GUI.sentry.captureException()
                self.showDialog("Failed to parse metadata!\n\n%s\n\nTraceback:\n%s"
                                % (str(err), sanitizeTrace(traceback)), 'error')
            else:
                self.editor.ui.exec_()

    def clearJobs(self):
        GUI.jobList.clear()

    def openWiki(self):
        # noinspection PyCallByClass
        QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki'))

    def modeChange(self, mode):
        if mode == 1:
            self.currentMode = 1
            GUI.gammaWidget.setVisible(False)
            GUI.customWidget.setVisible(False)
        elif mode == 2:
            self.currentMode = 2
            GUI.gammaWidget.setVisible(True)
            GUI.customWidget.setVisible(False)
        elif mode == 3:
            self.currentMode = 3
            GUI.gammaWidget.setVisible(True)
            GUI.customWidget.setVisible(True)

    def modeConvert(self, enable):
        if enable < 1:
            status = False
        else:
            status = True
        GUI.editorButton.setEnabled(status)
        GUI.wikiButton.setEnabled(status)
        GUI.deviceBox.setEnabled(status)
        GUI.directoryButton.setEnabled(status)
        GUI.clearButton.setEnabled(status)
        GUI.fileButton.setEnabled(status)
        GUI.formatBox.setEnabled(status)
        GUI.optionWidget.setEnabled(status)
        GUI.gammaWidget.setEnabled(status)
        GUI.customWidget.setEnabled(status)
        GUI.convertButton.setEnabled(True)
        if enable == 1:
            self.conversionAlive = False
            self.worker.sync()
            icon = QtGui.QIcon()
            icon.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
            GUI.convertButton.setIcon(icon)
            GUI.convertButton.setText('Convert')
            GUI.centralWidget.setAcceptDrops(True)
        elif enable == 0:
            self.conversionAlive = True
            self.worker.sync()
            icon = QtGui.QIcon()
            icon.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
            GUI.convertButton.setIcon(icon)
            GUI.convertButton.setText('Abort')
            GUI.centralWidget.setAcceptDrops(False)
        elif enable == -1:
            self.conversionAlive = True
            self.worker.sync()
            GUI.convertButton.setEnabled(False)
            GUI.centralWidget.setAcceptDrops(False)

    def togglegammaBox(self, value):
        if value:
            if self.currentMode != 3:
                self.modeChange(2)
        else:
            if self.currentMode != 3:
                self.modeChange(1)

    def togglewebtoonBox(self, value):
        if value:
            GUI.qualityBox.setEnabled(False)
            GUI.qualityBox.setChecked(False)
            GUI.mangaBox.setEnabled(False)
            GUI.mangaBox.setChecked(False)
            GUI.rotateBox.setEnabled(False)
            GUI.rotateBox.setChecked(False)
            GUI.upscaleBox.setEnabled(False)
            GUI.upscaleBox.setChecked(True)
        else:
            profile = GUI.profiles[str(GUI.deviceBox.currentText())]
            if profile['PVOptions']:
                GUI.qualityBox.setEnabled(True)
            GUI.mangaBox.setEnabled(True)
            GUI.rotateBox.setEnabled(True)
            GUI.upscaleBox.setEnabled(True)

    def togglequalityBox(self, value):
        profile = GUI.profiles[str(GUI.deviceBox.currentText())]
        if value == 2:
            if profile['Label'] in ['KV', 'KO']:
                self.addMessage('This option is intended for older Kindle models.', 'warning')
                self.addMessage('On this device, quality improvement will be negligible.', 'warning')
            GUI.upscaleBox.setEnabled(False)
            GUI.upscaleBox.setChecked(True)
        else:
            GUI.upscaleBox.setEnabled(True)
            GUI.upscaleBox.setChecked(profile['DefaultUpscale'])

    def changeGamma(self, value):
        valueRaw = int(5 * round(float(value) / 5))
        value = '%.2f' % (float(valueRaw) / 100)
        if float(value) <= 0.09:
            GUI.gammaLabel.setText('Gamma: Auto')
        else:
            GUI.gammaLabel.setText('Gamma: ' + str(value))
        GUI.gammaSlider.setValue(valueRaw)
        self.gammaValue = value

    def changeDevice(self):
        profile = GUI.profiles[str(GUI.deviceBox.currentText())]
        if profile['ForceExpert']:
            self.modeChange(3)
        elif GUI.gammaBox.isChecked():
            self.modeChange(2)
        else:
            self.modeChange(1)
        self.changeFormat()
        GUI.gammaSlider.setValue(0)
        self.changeGamma(0)
        if not GUI.webtoonBox.isChecked():
            GUI.qualityBox.setEnabled(profile['PVOptions'])
        GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
        if not profile['PVOptions']:
            GUI.qualityBox.setChecked(False)
        if str(GUI.deviceBox.currentText()) == 'Other':
            self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">'
                            'List of supported Non-Kindle devices.</a>', 'info')

    def changeFormat(self, outputformat=None):
        profile = GUI.profiles[str(GUI.deviceBox.currentText())]
        if outputformat is not None:
            GUI.formatBox.setCurrentIndex(outputformat)
        else:
            GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
        if not GUI.webtoonBox.isChecked():
            GUI.qualityBox.setEnabled(profile['PVOptions'])
        if str(GUI.formatBox.currentText()) == 'MOBI/AZW3':
            GUI.outputSplit.setEnabled(True)
        else:
            GUI.outputSplit.setEnabled(False)
            GUI.outputSplit.setChecked(False)

    def stripTags(self, html):
        s = HTMLStripper()
        s.feed(html)
        return s.get_data()

    def addMessage(self, message, icon, replace=False):
        if icon != '':
            icon = eval('self.icons.' + icon)
            item = QtWidgets.QListWidgetItem(icon, '   ' + self.stripTags(message))
        else:
            item = QtWidgets.QListWidgetItem('   ' + self.stripTags(message))
        if replace:
            GUI.jobList.takeItem(GUI.jobList.count() - 1)
        # Due to lack of HTML support in QListWidgetItem we overlay text field with QLabel
        # We still fill original text field with transparent content to trigger creation of horizontal scrollbar
        item.setForeground(QtGui.QColor('transparent'))
        label = QtWidgets.QLabel(message)
        label.setStyleSheet('background-image:url('');background-color:rgba(0,0,0,0);color:rgb(0,0,0);')
        label.setOpenExternalLinks(True)
        GUI.jobList.addItem(item)
        GUI.jobList.setItemWidget(item, label)
        GUI.jobList.scrollToBottom()

    def showDialog(self, message, kind):
        if kind == 'error':
            QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.Ok)
        elif kind == 'question':
            GUI.versionCheck.setAnswer(QtWidgets.QMessageBox.question(MW, 'KCC - Question', message,
                                                                          QtWidgets.QMessageBox.Yes,
                                                                          QtWidgets.QMessageBox.No))

    def updateProgressbar(self, command):
        if command == 'tick':
            GUI.progressBar.setValue(GUI.progressBar.value() + 1)
        elif command.isdigit():
            GUI.progressBar.setMaximum(int(command) - 1)
            GUI.toolWidget.hide()
            GUI.progressBar.reset()
            GUI.progressBar.show()
        else:
            GUI.progressBar.setFormat(command)

    def hideProgressBar(self):
        GUI.progressBar.hide()
        GUI.toolWidget.show()

    def convertStart(self):
        if self.conversionAlive:
            GUI.convertButton.setEnabled(False)
            self.addMessage('The process will be interrupted. Please wait.', 'warning')
            self.conversionAlive = False
            self.worker.sync()
        else:
            if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
                dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath)
                if dname != '':
                    if sys.platform.startswith('win'):
                        dname = dname.replace('/', '\\')
                    GUI.targetDirectory = dname
                else:
                    GUI.targetDirectory = ''
            else:
                GUI.targetDirectory = ''
            self.progress.start()
            if self.needClean:
                self.needClean = False
                GUI.jobList.clear()
            if GUI.jobList.count() == 0:
                self.addMessage('No files selected! Please choose files to convert.', 'error')
                self.needClean = True
                return
            if self.currentMode > 2 and (GUI.widthBox.value() == 0 or GUI.heightBox.value() == 0):
                GUI.jobList.clear()
                self.addMessage('Target resolution is not set!', 'error')
                self.needClean = True
                return
            if str(GUI.formatBox.currentText()) == 'MOBI/AZW3' and not self.kindleGen:
                self.detectKindleGen()
                if not self.kindleGen:
                    GUI.jobList.clear()
                    self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
                                    '1000765211"><b>KindleGen</b></a>! MOBI conversion is unavailable!', 'error')
                    if sys.platform.startswith('win'):
                        self.addMessage('Download it and place EXE in KCC directory.', 'error')
                    elif sys.platform.startswith('darwin'):
                        self.addMessage('Install it using <a href="http://brew.sh/">Brew</a>.', 'error')
                    else:
                        self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
                    self.needClean = True
                    return
            self.worker.start()

    def saveSettings(self, event):
        if self.conversionAlive:
            GUI.convertButton.setEnabled(False)
            self.addMessage('The process will be interrupted. Please wait.', 'warning')
            self.conversionAlive = False
            self.worker.sync()
            event.ignore()
        if not GUI.convertButton.isEnabled():
            event.ignore()
        self.settings.setValue('settingsVersion', __version__)
        self.settings.setValue('lastPath', self.lastPath)
        self.settings.setValue('lastDevice', GUI.deviceBox.currentIndex())
        self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
        self.settings.setValue('startNumber', self.startNumber + 1)
        self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
        self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(),
                                           'rotateBox': GUI.rotateBox.checkState(),
                                           'qualityBox': GUI.qualityBox.checkState(),
                                           'gammaBox': GUI.gammaBox.checkState(),
                                           'upscaleBox': GUI.upscaleBox.checkState(),
                                           'borderBox': GUI.borderBox.checkState(),
                                           'webtoonBox': GUI.webtoonBox.checkState(),
                                           'outputSplit': GUI.outputSplit.checkState(),
                                           'colorBox': GUI.colorBox.checkState(),
                                           'widthBox': GUI.widthBox.value(),
                                           'heightBox': GUI.heightBox.value(),
                                           'gammaSlider': float(self.gammaValue) * 100})
        self.settings.sync()
        self.tray.hide()

    def handleMessage(self, message):
        MW.raise_()
        MW.activateWindow()
        if type(message) is bytes:
            message = message.decode('UTF-8')
        if not self.conversionAlive and message != 'ARISE':
            if self.needClean:
                self.needClean = False
                GUI.jobList.clear()
            formats = ['.pdf']
            if self.sevenzip:
                formats.extend(['.cb7', '.7z', '.cbz', '.zip', '.cbr', '.rar'])
            if os.path.isdir(message):
                GUI.jobList.addItem(message)
                GUI.jobList.scrollToBottom()
            elif os.path.isfile(message):
                extension = os.path.splitext(message)
                if extension[1].lower() in formats:
                    GUI.jobList.addItem(message)
                    GUI.jobList.scrollToBottom()
                else:
                    self.addMessage('Unsupported file type for ' + message, 'error')

    def dragAndDrop(self, e):
        e.accept()

    def dragAndDropAccepted(self, e):
        for message in e.mimeData().urls():
            message = unquote(message.toString().replace('file:///', ''))
            if sys.platform.startswith('win'):
                message = message.replace('/', '\\')
            else:
                message = '/' + message
                if message[-1] == '/':
                    message = message[:-1]
            self.handleMessage(message)

    def forceShutdown(self):
        self.saveSettings(None)
        sys.exit(0)

    def detectKindleGen(self, startup=False):
        if not sys.platform.startswith('win'):
            try:
                os.chmod('/usr/local/bin/kindlegen', 0o755)
            except Exception:
                pass
        kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
        kindleGenExitCode.communicate()
        if kindleGenExitCode.returncode == 0:
            self.kindleGen = True
            versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
            for line in versionCheck.stdout:
                line = line.decode("utf-8")
                if 'Amazon kindlegen' in line:
                    versionCheck = line.split('V')[1].split(' ')[0]
                    if StrictVersion(versionCheck) < StrictVersion('2.9'):
                        self.addMessage('Your <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
                                        '1000765211">KindleGen</a> is outdated! MOBI conversion might fail.', 'warning')
                    break
        else:
            self.kindleGen = False
            if startup:
                self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">'
                                '<b>KindleGen</b></a>! MOBI conversion will be unavailable!', 'error')
                if sys.platform.startswith('win'):
                    self.addMessage('Download it and place EXE in KCC directory.', 'error')
                elif sys.platform.startswith('darwin'):
                    self.addMessage('Install it using <a href="http://brew.sh/">Brew</a>.', 'error')
                else:
                    self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')

    def __init__(self, kccapp, kccwindow):
        global APP, MW, GUI
        APP = kccapp
        MW = kccwindow
        GUI = self
        self.setupUi(MW)
        self.editor = KCCGUI_MetaEditor()
        self.icons = Icons()
        self.settings = QtCore.QSettings('KindleComicConverter', 'KindleComicConverter')
        self.settingsVersion = self.settings.value('settingsVersion', '', type=str)
        self.lastPath = self.settings.value('lastPath', '', type=str)
        self.lastDevice = self.settings.value('lastDevice', 0, type=int)
        self.currentFormat = self.settings.value('currentFormat', 0, type=int)
        self.startNumber = self.settings.value('startNumber', 0, type=int)
        self.windowSize = self.settings.value('windowSize', '0x0', type=str)
        self.options = self.settings.value('options', {'gammaSlider': 0})
        self.worker = WorkerThread()
        self.versionCheck = VersionThread()
        self.progress = ProgressThread()
        self.tray = SystemTrayIcon()
        self.conversionAlive = False
        self.needClean = True
        self.kindleGen = False
        self.gammaValue = 1.0
        self.currentMode = 1
        self.targetDirectory = ''
        self.sentry = Client(release=__version__)
        if sys.platform.startswith('win'):
            # noinspection PyUnresolvedReferences
            from psutil import BELOW_NORMAL_PRIORITY_CLASS
            self.p = Process(os.getpid())
            self.p.nice(BELOW_NORMAL_PRIORITY_CLASS)
            self.p.ionice(1)
        elif sys.platform.startswith('linux'):
            APP.setStyle('fusion')
            if self.windowSize == '0x0':
                MW.resize(500, 500)
        elif sys.platform.startswith('darwin'):
            for element in ['editorButton', 'wikiButton', 'directoryButton', 'clearButton', 'fileButton', 'deviceBox',
                            'convertButton', 'formatBox']:
                eval('GUI.' + element).setMinimumSize(QtCore.QSize(0, 0))
            GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
            for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'horizontalLayout', 'horizontalLayout_2']:
                eval('GUI.' + element).setContentsMargins(-1, 0, -1, 0)
            if self.windowSize == '0x0':
                MW.resize(500, 500)

        self.profiles = {
            "Kindle Oasis 2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
                               'DefaultUpscale': True, 'Label': 'KO'},
            "Kindle Oasis": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
                             'DefaultUpscale': True, 'Label': 'KV'},
            "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
                              'DefaultUpscale': True, 'Label': 'KV'},
            "Kindle PW 3/4": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
                              'DefaultUpscale': True, 'Label': 'KV'},
            "Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
                              'DefaultUpscale': False, 'Label': 'KPW'},
            "Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
                       'DefaultUpscale': False, 'Label': 'K578'},
            "Kindle DX/DXG": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 2,
                              'DefaultUpscale': False, 'Label': 'KDX'},
            "Kobo Mini/Touch": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
                                'DefaultUpscale': False, 'Label': 'KoMT'},
            "Kobo Glo": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
                         'DefaultUpscale': False, 'Label': 'KoG'},
            "Kobo Glo HD": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
                            'DefaultUpscale': False, 'Label': 'KoGHD'},
            "Kobo Aura": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
                          'DefaultUpscale': False, 'Label': 'KoA'},
            "Kobo Aura HD": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
                             'DefaultUpscale': True, 'Label': 'KoAHD'},
            "Kobo Aura H2O": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
                              'DefaultUpscale': True, 'Label': 'KoAH2O'},
            "Kobo Aura ONE": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
                              'DefaultUpscale': True, 'Label': 'KoAO'},
            "Kobo Forma": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
                           'DefaultUpscale': True, 'Label': 'KoF'},
            "Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1,
                      'DefaultUpscale': False, 'Label': 'OTHER'},
            "Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
                         'DefaultUpscale': False, 'Label': 'K1'},
            "Kindle 2": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
                         'DefaultUpscale': False, 'Label': 'K2'},
            "Kindle Keyboard": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
                                'DefaultUpscale': False, 'Label': 'K34'},
            "Kindle Touch": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
                             'DefaultUpscale': False, 'Label': 'K34'},
        }
        profilesGUI = [
            "Kindle Oasis 2",
            "Kindle Oasis",
            "Kindle Voyage",
            "Kindle PW 3/4",
            "Kindle PW 1/2",
            "Kindle",
            "Separator",
            "Kobo Forma",
            "Kobo Aura ONE",
            "Kobo Aura H2O",
            "Kobo Aura HD",
            "Kobo Aura",
            "Separator",
            "Other",
            "Separator",
            "Kindle Touch",
            "Kindle Keyboard",
            "Kindle DX/DXG",
            "Kindle 2",
            "Kindle 1",
            "Separator",
            "Kobo Glo HD",
            "Kobo Glo",
            "Kobo Mini/Touch",
        ]

        statusBarLabel = QtWidgets.QLabel('<b><a href="https://kcc.iosphe.re/">HOMEPAGE</a> - <a href="https://github.'
                                          'com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations">DO'
                                          'NATE</a> - <a href="http://www.mobileread.com/forums/showthread.php?t=207461'
                                          '">FORUM</a></b>')
        statusBarLabel.setAlignment(QtCore.Qt.AlignCenter)
        statusBarLabel.setOpenExternalLinks(True)
        GUI.statusBar.addPermanentWidget(statusBarLabel, 1)

        self.addMessage('<b>Welcome!</b>', 'info')
        self.addMessage('<b>Remember:</b> All options have additional information in tooltips.', 'info')
        if self.startNumber < 5:
            self.addMessage('Since you are a new user of <b>KCC</b> please see few '
                            '<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
                            'info')
        process = Popen('7z', stdout=PIPE, stderr=STDOUT, shell=True)
        process.communicate()
        if process.returncode == 0 or process.returncode == 7:
            self.sevenzip = True
        else:
            self.sevenzip = False
            self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7z</a>!'
                            ' Processing of archives will be disabled.', 'warning')
        self.detectKindleGen(True)

        APP.messageFromOtherInstance.connect(self.handleMessage)
        GUI.directoryButton.clicked.connect(self.selectDir)
        GUI.clearButton.clicked.connect(self.clearJobs)
        GUI.fileButton.clicked.connect(self.selectFile)
        GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
        GUI.wikiButton.clicked.connect(self.openWiki)
        GUI.convertButton.clicked.connect(self.convertStart)
        GUI.gammaSlider.valueChanged.connect(self.changeGamma)
        GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
        GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
        GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
        GUI.deviceBox.activated.connect(self.changeDevice)
        GUI.formatBox.activated.connect(self.changeFormat)
        MW.progressBarTick.connect(self.updateProgressbar)
        MW.modeConvert.connect(self.modeConvert)
        MW.addMessage.connect(self.addMessage)
        MW.showDialog.connect(self.showDialog)
        MW.hideProgressBar.connect(self.hideProgressBar)
        MW.forceShutdown.connect(self.forceShutdown)
        MW.closeEvent = self.saveSettings
        MW.addTrayMessage.connect(self.tray.addTrayMessage)

        GUI.centralWidget.setAcceptDrops(True)
        GUI.centralWidget.dragEnterEvent = self.dragAndDrop
        GUI.centralWidget.dropEvent = self.dragAndDropAccepted

        self.modeChange(1)
        for profile in profilesGUI:
            if profile == "Other":
                GUI.deviceBox.addItem(self.icons.deviceOther, profile)
            elif profile == "Separator":
                GUI.deviceBox.insertSeparator(GUI.deviceBox.count() + 1)
            elif 'Ko' in profile:
                GUI.deviceBox.addItem(self.icons.deviceKobo, profile)
            else:
                GUI.deviceBox.addItem(self.icons.deviceKindle, profile)
        for f in ['MOBI/AZW3', 'EPUB', 'CBZ']:
            GUI.formatBox.addItem(eval('self.icons.' + f.replace('/AZW3', '') + 'Format'), f)
        if self.lastDevice > GUI.deviceBox.count():
            self.lastDevice = 0
        if profilesGUI[self.lastDevice] == "Separator":
            self.lastDevice = 0
        if self.currentFormat > GUI.formatBox.count():
            self.currentFormat = 0
        GUI.deviceBox.setCurrentIndex(self.lastDevice)
        self.changeDevice()
        if self.currentFormat != self.profiles[str(GUI.deviceBox.currentText())]['DefaultFormat']:
            self.changeFormat(self.currentFormat)
        for option in self.options:
            if str(option) == "widthBox":
                GUI.widthBox.setValue(int(self.options[option]))
            elif str(option) == "heightBox":
                GUI.heightBox.setValue(int(self.options[option]))
            elif str(option) == "gammaSlider":
                if GUI.gammaSlider.isEnabled():
                    GUI.gammaSlider.setValue(int(self.options[option]))
                    self.changeGamma(int(self.options[option]))
            else:
                try:
                    if eval('GUI.' + str(option)).isEnabled():
                        eval('GUI.' + str(option)).setCheckState(self.options[option])
                except AttributeError:
                    pass
        self.worker.sync()
        self.versionCheck.start()
        self.tray.show()

        if self.windowSize != '0x0':
            x, y = self.windowSize.split('x')
            MW.resize(int(x), int(y))
        MW.setWindowTitle("Kindle Comic Converter " + __version__)
        MW.show()
        MW.raise_()


class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog):
    def loadData(self, file):
        self.parser = metadata.MetadataParser(file)
        if self.parser.format in ['RAR', 'RAR5']:
            self.editorWidget.setEnabled(False)
            self.okButton.setEnabled(False)
            self.statusLabel.setText('CBR metadata are read-only.')
        else:
            self.editorWidget.setEnabled(True)
            self.okButton.setEnabled(True)
            self.statusLabel.setText('Separate authors with a comma.')
        for field in (self.seriesLine, self.volumeLine, self.numberLine):
            field.setText(self.parser.data[field.objectName().capitalize()[:-4]])
        for field in (self.writerLine, self.pencillerLine, self.inkerLine, self.coloristLine):
            field.setText(', '.join(self.parser.data[field.objectName().capitalize()[:-4] + 's']))
        if self.seriesLine.text() == '':
            if file.endswith('.xml'):
                self.seriesLine.setText(file.split('\\')[-2])
            else:
                self.seriesLine.setText(file.split('\\')[-1].split('/')[-1].split('.')[0])

    def saveData(self):
        for field in (self.volumeLine, self.numberLine):
            if field.text().isnumeric() or self.cleanData(field.text()) == '':
                self.parser.data[field.objectName().capitalize()[:-4]] = self.cleanData(field.text())
            else:
                self.statusLabel.setText(field.objectName().capitalize()[:-4] + ' field must be a number.')
                break
        else:
            self.parser.data['Series'] = self.cleanData(self.seriesLine.text())
            for field in (self.writerLine, self.pencillerLine, self.inkerLine, self.coloristLine):
                values = self.cleanData(field.text()).split(',')
                tmpData = []
                for value in values:
                    if self.cleanData(value) != '':
                        tmpData.append(self.cleanData(value))
                self.parser.data[field.objectName().capitalize()[:-4] + 's'] = tmpData
            try:
                self.parser.saveXML()
            except Exception as err:
                _, _, traceback = sys.exc_info()
                GUI.sentry.captureException()
                GUI.showDialog("Failed to save metadata!\n\n%s\n\nTraceback:\n%s"
                               % (str(err), sanitizeTrace(traceback)), 'error')
            self.ui.close()

    def cleanData(self, s):
        return escape(s.strip())

    def __init__(self):
        self.ui = QtWidgets.QDialog()
        self.parser = None
        self.setupUi(self.ui)
        self.ui.setWindowFlags(self.ui.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
        self.okButton.clicked.connect(self.saveData)
        self.cancelButton.clicked.connect(self.ui.close)
        if sys.platform.startswith('linux'):
            self.ui.resize(450, 260)
            self.ui.setMinimumSize(QtCore.QSize(450, 260))
        elif sys.platform.startswith('darwin'):
            self.ui.resize(450, 310)
            self.ui.setMinimumSize(QtCore.QSize(450, 310))