class Collection(QtCore.QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.db = QSqlDatabase.addDatabase('QSQLITE')
        self._pages = None
        self.fileName = None

    def isOpen(self):
        return self.db.isValid() and self.fileName

    def open(self, fileName):
        self.fileName = None

        file = QtCore.QFileInfo(fileName)
        if file.isFile():
            self.db.setDatabaseName(fileName)
            if not self.db.open() or not self.db.tables():
                print(self.db.lastError().text())
                QMessageBox.critical(self.parent(),
                                self.tr("Open collection"),
                                self.tr("Can't open collection %s") % fileName)
                return False
        else:
            QMessageBox.critical(self.parent(),
                                self.tr("Open collection"),
                                self.tr("Collection %s not exists") % fileName)
            return False

        if ('coins' not in self.db.tables()) or ('settings' not in self.db.tables()):
            QMessageBox.critical(self.parent(),
                    self.tr("Open collection"),
                    self.tr("Collection %s in wrong format") % fileName)
            return False

        self.settings = CollectionSettings(self.db)
        if self.settings['Type'] != version.AppName:
            QMessageBox.critical(self.parent(),
                    self.tr("Open collection"),
                    self.tr("Collection %s in wrong format") % fileName)
            return False
        if int(self.settings['Version']) > self.settings.Default['Version']:
            QMessageBox.critical(self.parent(),
                    self.tr("Open collection"),
                    self.tr("Collection %s a newer version.\n"
                            "Please update OpenNumismat") % fileName)
            return False

        if self.settings['Password'] != cryptPassword():
            dialog = PasswordDialog(
                self.settings['Password'], self.getCollectionName(),
                self.parent())
            result = dialog.exec_()
            if result == QDialog.Rejected:
                return False

        self.fields = CollectionFields(self.db)

        self.fileName = fileName

        if not updateCollection(self):
            self.fileName = None
            return False

        self._pages = CollectionPages(self.db)

        self.description = CollectionDescription(self)

        self.__speedup()

        return True

    def create(self, fileName):
        self.fileName = None

        if QtCore.QFileInfo(fileName).exists():
            QMessageBox.critical(self.parent(),
                                    self.tr("Create collection"),
                                    self.tr("Specified file already exists"))
            return False

        self.db.setDatabaseName(fileName)
        if not self.db.open():
            print(self.db.lastError().text())
            QMessageBox.critical(self.parent(),
                                       self.tr("Create collection"),
                                       self.tr("Can't open collection"))
            return False

        self.fields = CollectionFields(self.db)

        self.createCoinsTable()

        self._pages = CollectionPages(self.db)

        self.settings = CollectionSettings(self.db)

        self.description = CollectionDescription(self)

        self.__speedup()

        self.fileName = fileName

        return True

    def __speedup(self):
        settings = Settings()
        if settings['speedup'] == 1:
            sql = "PRAGMA synchronous=NORMAL"
            QSqlQuery(sql, self.db)
            sql = "PRAGMA journal_mode=MEMORY"
            QSqlQuery(sql, self.db)
        elif settings['speedup'] == 2:
            sql = "PRAGMA synchronous=OFF"
            QSqlQuery(sql, self.db)
            sql = "PRAGMA journal_mode=MEMORY"
            QSqlQuery(sql, self.db)

    def createCoinsTable(self):
        sqlFields = []
        for field in self.fields:
            if field.name == 'id':
                sqlFields.append('id INTEGER PRIMARY KEY')
            else:
                sqlFields.append("%s %s" % (field.name, Type.toSql(field.type)))

        sql = "CREATE TABLE coins (" + ", ".join(sqlFields) + ")"
        QSqlQuery(sql, self.db)

        sql = "CREATE TABLE photos (id INTEGER PRIMARY KEY, title TEXT, image BLOB)"
        QSqlQuery(sql, self.db)

        sql = "CREATE TABLE images (id INTEGER PRIMARY KEY, image BLOB)"
        QSqlQuery(sql, self.db)

    def isReferenceAttached(self):
        return ('sections' in self.db.tables())

    def loadReference(self, fileName):
        if self.isReferenceAttached():
            self.reference = Reference(self.fields, self.parent(), db=self.db)
        else:
            self.reference = Reference(self.fields, self.parent())
            self.reference.open(fileName)
        self.reference.load()

    def getFileName(self):
        return QtCore.QDir(self.fileName).absolutePath()

    def getCollectionName(self):
        return Collection.fileNameToCollectionName(self.fileName)

    def getDescription(self):
        return self.description

    def model(self):
        return self.createModel()

    def pages(self):
        return self._pages

    def createModel(self):
        model = CollectionModel(self)
        model.title = self.getCollectionName()
        model.setEditStrategy(QSqlTableModel.OnManualSubmit)
        model.setTable('coins')
        model.select()
        for field in self.fields:
            model.setHeaderData(field.id, QtCore.Qt.Horizontal, field.title)

        return model

    def fillReference(self):
        sections = self.reference.allSections()
        progressDlg = Gui.ProgressDialog(self.tr("Updating reference"),
                            self.tr("Cancel"), len(sections), self.parent())

        for columnName in sections:
            progressDlg.step()
            if progressDlg.wasCanceled():
                break

            refSection = self.reference.section(columnName)
            if isinstance(refSection, CrossReferenceSection):
                rel = refSection.model.relationModel(1)
                for i in range(rel.rowCount()):
                    data = rel.data(rel.index(i, rel.fieldIndex('value')))
                    parentId = rel.data(rel.index(i, rel.fieldIndex('id')))
                    query = QSqlQuery(self.db)
                    sql = "SELECT DISTINCT %s FROM coins WHERE %s<>'' AND %s IS NOT NULL AND %s=?" % (columnName, columnName, columnName, refSection.parent_name)
                    query.prepare(sql)
                    query.addBindValue(data)
                    query.exec_()
                    refSection.fillFromQuery(parentId, query)
                    refSection.reload()
            else:
                sql = "SELECT DISTINCT %s FROM coins WHERE %s<>'' AND %s IS NOT NULL" % (columnName, columnName, columnName)
                query = QSqlQuery(sql, self.db)
                refSection.fillFromQuery(query)
                refSection.reload()

        progressDlg.reset()

    def editReference(self):
        dialog = AllReferenceDialog(self.reference, self.parent())
        dialog.exec_()

    def attachReference(self):
        result = QMessageBox.information(
            self.parent(), self.tr("Attach"),
            self.tr("Attach current reference to a collection file?"),
            QMessageBox.Yes | QMessageBox.Cancel,
            QMessageBox.Cancel)
        if result == QMessageBox.Yes:
            progressDlg = Gui.ProgressDialog(
                self.tr("Attaching reference"), None,
                len(self.reference.sections), self.parent())

            query = QSqlQuery(self.db)
            query.prepare("ATTACH ? AS ref")
            query.addBindValue(self.reference.fileName)
            res = query.exec_()
            if not res:
                progressDlg.reset()
                QMessageBox.critical(self.parent(),
                            self.tr("Attaching reference"),
                            self.tr("Can't attach reference:\n%s" %
                                    query.lastError().text()))
                return

            reference = Reference(self.fields, self.parent(), db=self.db)
            reference.db.transaction()
            reference.create()

            for section in self.reference.sections:
                progressDlg.step()

                if res:
                    query = QSqlQuery(self.db)
                    query.prepare("INSERT INTO %s SELECT * FROM ref.%s" %
                                  (section.table_name, section.table_name))
                    res = query.exec_()

            if res:
                reference.db.commit()
                self.reference = reference
            else:
                reference.db.rollback()
                progressDlg.reset()
                QMessageBox.critical(self.parent(),
                            self.tr("Attaching reference"),
                            self.tr("Can't attach reference:\n%s") %
                                    query.lastError().text())

            QSqlQuery("DETACH ref", reference.db)

            self.reference.load()

            progressDlg.reset()

            self.__updateAttachAction()

    def detachReference(self):
        fileName, _selectedFilter = QFileDialog.getSaveFileName(
            self.parent(), self.tr("Save reference as"),
            filter=self.tr("Reference (*.ref)"))
        if fileName:
            if os.path.isfile(fileName):
                os.remove(fileName)

            progressDlg = Gui.ProgressDialog(
                self.tr("Detaching reference"), None,
                len(self.reference.sections), self.parent())

            reference = Reference(self.fields, self.parent())
            if not reference.open(fileName, interactive=False):
                return

            query = QSqlQuery(reference.db)
            query.prepare("ATTACH ? AS ref")
            query.addBindValue(self.fileName)
            res = query.exec_()
            if not res:
                progressDlg.reset()
                QMessageBox.critical(self.parent(),
                            self.tr("Detach reference"),
                            self.tr("Can't detach reference:\n%s") %
                                    query.lastError().text())
                return

            reference.db.transaction()

            for section in self.reference.sections:
                progressDlg.step()

                if res:
                    query = QSqlQuery(reference.db)
                    query.prepare("INSERT INTO %s SELECT * FROM ref.%s" % (section.table_name, section.table_name))
                    res = query.exec_()

            if res:
                reference.db.commit()
            else:
                reference.db.rollback()
                progressDlg.reset()
                QMessageBox.critical(self.parent(),
                            self.tr("Create reference"),
                            self.tr("Can't create reference:\n%s") % fileName)
                return

            for section in self.reference.sections:
                section.model.clear()

            self.db.transaction()

            for table_name in self.db.tables():
                if 'ref_' in table_name:
                    if res:
                        query = QSqlQuery(self.db)
                        query.prepare("DROP TABLE %s" % table_name)
                        res = query.exec_()

            if res:
                query = QSqlQuery(self.db)
                query.prepare("DROP TABLE sections")
                res = query.exec_()

            if res:
                query = QSqlQuery(self.db)
                query.prepare("DROP TABLE ref")
                res = query.exec_()

            if res:
                self.db.commit()
                self.reference = reference
            else:
                self.db.rollback()
                QMessageBox.critical(self.parent(),
                            self.tr("Create reference"),
                            self.tr("Can't clear attached reference:\n%s") %
                                    query.lastError().text())

            QSqlQuery("DETACH ref", reference.db)

            self.reference.load()

            progressDlg.reset()

            self.__updateAttachAction()

    def __updateAttachAction(self):
        try:
            self.attachReferenceAct.disconnect()
        except TypeError:
            pass

        if self.isReferenceAttached():
            self.attachReferenceAct.setText(self.tr("Detach current reference"))
            self.attachReferenceAct.triggered.connect(self.detachReference)
        else:
            self.attachReferenceAct.setText(self.tr("Attach current reference"))
            self.attachReferenceAct.triggered.connect(self.attachReference)

    def referenceMenu(self, parent=None):
        fillReferenceAct = QAction(self.tr("Fill from collection"), parent)
        fillReferenceAct.triggered.connect(self.fillReference)

        editReferenceAct = QAction(self.tr("Edit..."), parent)
        editReferenceAct.triggered.connect(self.editReference)

        separator = QAction(parent)
        separator.setSeparator(True)

        self.attachReferenceAct = QAction(parent)
        self.__updateAttachAction()

        acts = (fillReferenceAct, editReferenceAct,
                separator, self.attachReferenceAct)

        return acts

    @waitCursorDecorator
    def backup(self):
        backupDir = QtCore.QDir(Settings()['backup'])
        if not backupDir.exists():
            backupDir.mkpath(backupDir.absolutePath())

        backupFileName = backupDir.filePath("%s_%s.db" % (self.getCollectionName(), QtCore.QDateTime.currentDateTime().toString('yyMMddhhmmss')))
        srcFile = QtCore.QFile(self.fileName)
        if not srcFile.copy(backupFileName):
            QMessageBox.critical(self.parent(),
                            self.tr("Backup collection"),
                            self.tr("Can't make a collection backup at %s") %
                                                                backupFileName)
            return False

        return True

    def isNeedBackup(self):
        settings = Settings()
        autobackup_depth = settings['autobackup_depth']
        filter_ = ('%s_????????????.db' % self.getCollectionName(),)
        files = QtCore.QDirIterator(settings['backup'], filter_, QtCore.QDir.Files)
        while files.hasNext():
            file = files.next()
            file_info = QtCore.QFileInfo(file)
            if file_info.completeSuffix() == 'db':
                file_title = file_info.baseName()
                file_date = file_title[-12:-6]

                query = QSqlQuery(self.db)
                query.prepare("SELECT count(*) FROM coins WHERE updatedat > ?")
                date = "20%s-%s-%sT23:59:59" % (file_date[0:2], file_date[2:4], file_date[4:6])
                query.addBindValue(date)
                query.exec_()
                query.first()
                if query.record().value(0) < autobackup_depth:
                    return False

        return True

    @waitCursorDecorator
    def vacuum(self):
        QSqlQuery("VACUUM", self.db)

    @staticmethod
    def fileNameToCollectionName(fileName):
        file = QtCore.QFileInfo(fileName)
        return file.baseName()

    def exportToMobile(self, params):
        IMAGE_FORMAT = 'jpg'
        SKIPPED_FIELDS = ('signatureimg', 'varietyimg', 'edgeimg', 'photo1', 'photo2', 'photo3', 'photo4', 'photo5', 'photo6',
            'obversedesigner', 'reversedesigner', 'catalognum2', 'catalognum3', 'catalognum4',
            'saledate', 'saleprice', 'totalsaleprice', 'buyer', 'saleplace', 'saleinfo',
            'paydate', 'payprice', 'totalpayprice', 'saller', 'payplace', 'payinfo',
            'url', 'obversedesigner', 'reversedesigner')

        if os.path.isfile(params['file']):
            os.remove(params['file'])

        db = QSqlDatabase.addDatabase('QSQLITE', 'mobile')
        db.setDatabaseName(params['file'])
        if not db.open():
            print(db.lastError().text())
            QMessageBox.critical(self.parent(),
                                       self.tr("Create mobile collection"),
                                       self.tr("Can't open collection"))
            return

        mobile_settings = {'Version': 5, 'Type': 'Mobile', 'Filter': params['filter']}

        QSqlQuery("PRAGMA synchronous=OFF", db)
        QSqlQuery("PRAGMA journal_mode=MEMORY", db)

        sql = """CREATE TABLE settings (
            title CHAR NOT NULL UNIQUE,
            value CHAR)"""
        QSqlQuery(sql, db)
        for key, value in mobile_settings.items():
            query = QSqlQuery(db)
            query.prepare("""INSERT INTO settings (title, value)
                    VALUES (?, ?)""")
            query.addBindValue(key)
            query.addBindValue(str(value))
            query.exec_()

        sql = """CREATE TABLE updates (
            title CHAR NOT NULL UNIQUE,
            value CHAR)"""
        QSqlQuery(sql, db)

        sql = """CREATE TABLE photos (
            id INTEGER PRIMARY KEY,
            image BLOB)"""
        QSqlQuery(sql, db)

        sqlFields = []
        fields = CollectionFieldsBase()
        for field in fields:
            if field.name == 'id':
                sqlFields.append('id INTEGER PRIMARY KEY')
            elif field.name == 'image':
                sqlFields.append('image INTEGER')
            elif field.name in SKIPPED_FIELDS:
                continue
            else:
                sqlFields.append("%s %s" % (field.name, Type.toSql(field.type)))

        sql = "CREATE TABLE coins (" + ", ".join(sqlFields) + ")"
        QSqlQuery(sql, db)

        model = self.model()
        while model.canFetchMore():
            model.fetchMore()

        dest_model = QSqlTableModel(self.parent(), db)
        dest_model.setEditStrategy(QSqlTableModel.OnManualSubmit)
        dest_model.setTable('coins')
        dest_model.select()

        height = 64
        if params['density'] == 'HDPI':
            height *= 1.5
        elif params['density'] == 'XHDPI':
            height *= 2
        elif params['density'] == 'XXHDPI':
            height *= 3
        elif params['density'] == 'XXXHDPI':
            height *= 4
        maxHeight = height * 4

        is_obverse_enabled = params['image'] in (ExportDialog.IMAGE_OBVERSE, ExportDialog.IMAGE_BOTH)
        is_reverse_enabled = params['image'] in (ExportDialog.IMAGE_REVERSE, ExportDialog.IMAGE_BOTH)

        fields = CollectionFieldsBase()
        count = model.rowCount()
        progressDlg = Gui.ProgressDialog(self.tr("Exporting records"),
                                        self.tr("Cancel"), count, self.parent())

        for i in range(count):
            progressDlg.step()
            if progressDlg.wasCanceled():
                break

            coin = model.record(i)
            if coin.value('status') in ('pass', 'sold'):
                continue

            dest_record = dest_model.record()

            for field in fields:
                if field.name in ('id', 'image', 'obverseimg', 'reverseimg'):
                    continue
                if field.name in SKIPPED_FIELDS:
                    continue

                val = coin.value(field.name)
                if val is None or val == '':
                    continue

                dest_record.setValue(field.name, val)

            # Process images
            is_obverse_present = not coin.isNull('obverseimg')
            is_reverse_present = not coin.isNull('reverseimg')
            if is_obverse_present or is_reverse_present:
                obverseImage = QImage()
                reverseImage = QImage()

                if is_obverse_present:
                    ba = QtCore.QByteArray()
                    buffer = QtCore.QBuffer(ba)
                    buffer.open(QtCore.QIODevice.WriteOnly)

                    obverseImage.loadFromData(coin.value('obverseimg'))
                    if not obverseImage.isNull() and not params['fullimage'] and obverseImage.height() > maxHeight:
                        scaledImage = obverseImage.scaled(maxHeight, maxHeight,
                                Qt.KeepAspectRatio, Qt.SmoothTransformation)
                        scaledImage.save(buffer, IMAGE_FORMAT, 50)
                        save_data = ba
                    else:
                        if not obverseImage.isNull():
                            obverseImage.save(buffer, IMAGE_FORMAT, 50)
                            save_data = ba
                        else:
                            save_data = coin.value('obverseimg')

                    query = QSqlQuery(db)
                    query.prepare("""INSERT INTO photos (image)
                            VALUES (?)""")
                    query.addBindValue(save_data)
                    query.exec_()
                    img_id = query.lastInsertId()
                    dest_record.setValue('obverseimg', img_id)
                if not obverseImage.isNull():
                    obverseImage = obverseImage.scaledToHeight(height,
                                                            Qt.SmoothTransformation)

                if is_reverse_present:
                    ba = QtCore.QByteArray()
                    buffer = QtCore.QBuffer(ba)
                    buffer.open(QtCore.QIODevice.WriteOnly)

                    reverseImage.loadFromData(coin.value('reverseimg'))
                    if not reverseImage.isNull() and not params['fullimage'] and reverseImage.height() > maxHeight:
                        scaledImage = reverseImage.scaled(maxHeight, maxHeight,
                                Qt.KeepAspectRatio, Qt.SmoothTransformation)
                        scaledImage.save(buffer, IMAGE_FORMAT, 50)
                        save_data = ba
                    else:
                        if not reverseImage.isNull():
                            reverseImage.save(buffer, IMAGE_FORMAT, 50)
                            save_data = ba
                        else:
                            save_data = coin.value('reverseimg')

                    query = QSqlQuery(db)
                    query.prepare("""INSERT INTO photos (image)
                            VALUES (?)""")
                    query.addBindValue(save_data)
                    query.exec_()
                    img_id = query.lastInsertId()
                    dest_record.setValue('reverseimg', img_id)
                if not reverseImage.isNull():
                    reverseImage = reverseImage.scaledToHeight(height,
                                                        Qt.SmoothTransformation)

                if not is_obverse_enabled:
                    obverseImage = QImage()
                if not is_reverse_enabled:
                    reverseImage = QImage()

                image = QImage(obverseImage.width() + reverseImage.width(),
                                     height, QImage.Format_RGB32)
                image.fill(Qt.white)

                paint = QPainter(image)
                if is_obverse_present and is_obverse_enabled:
                    paint.drawImage(QtCore.QRectF(0, 0, obverseImage.width(), height), obverseImage,
                                    QtCore.QRectF(0, 0, obverseImage.width(), height))
                if is_reverse_present and is_reverse_enabled:
                    paint.drawImage(QtCore.QRectF(obverseImage.width(), 0, reverseImage.width(), height), reverseImage,
                                    QtCore.QRectF(0, 0, reverseImage.width(), height))
                paint.end()

                ba = QtCore.QByteArray()
                buffer = QtCore.QBuffer(ba)
                buffer.open(QtCore.QIODevice.WriteOnly)

                # Store as PNG for better view
                image.save(buffer, 'png')
                dest_record.setValue('image', ba)

            dest_model.insertRecord(-1, dest_record)

        progressDlg.setLabelText(self.tr("Saving..."))
        dest_model.submitAll()

        progressDlg.setLabelText(self.tr("Compact..."))
        QSqlQuery("""UPDATE coins
SET
  reverseimg = (select t2.id from coins t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.reverseimg = t1.id where t1.id <> t2.id and t3.id = coins.id)
WHERE coins.id in (select t3.id from coins t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.reverseimg = t1.id where t1.id <> t2.id)
""", db)
        QSqlQuery("""UPDATE coins
SET
  obverseimg = (select t2.id from coins t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.obverseimg = t1.id where t1.id <> t2.id and t3.id = coins.id)
WHERE coins.id in (select t3.id from coins t3 join (select id, image from photos group by image having count(*) > 1) t2 on t1.image = t2.image join photos t1 on t3.obverseimg = t1.id where t1.id <> t2.id)
""", db)

        QSqlQuery("""DELETE FROM photos
            WHERE id NOT IN (SELECT id FROM photos GROUP BY image)""", db)

        db.close()
        QSqlDatabase.removeDatabase('mobile')

        progressDlg.setLabelText(self.tr("Vacuum..."))
        db = QSqlDatabase.addDatabase('QSQLITE', 'mobile')
        db.setDatabaseName(params['file'])
        db.open()
        QSqlQuery("PRAGMA synchronous=OFF", db)
        QSqlQuery("PRAGMA journal_mode=MEMORY", db)
        QSqlQuery("VACUUM", db)
        db.close()
        QSqlDatabase.removeDatabase('mobile')

        progressDlg.reset()
        
    def exportToJson(self):
        file = self.getFileName()
        json_file_name = '.json'.join(file.rsplit('.db', 1))
        json_file_name, _selectedFilter = QFileDialog.getSaveFileName(
            self.parent(), self.tr("Save as"), json_file_name, "*.json")
        if json_file_name:
            json_file = codecs.open(json_file_name, "w", "utf-8")

            image_path = '_images'.join(json_file_name.rsplit('.json', 1))
            shutil.rmtree(image_path, ignore_errors=True)
            os.makedirs(image_path)
        
            model = self.model()
    
            sort_column_id = model.fields.sort_id.id
            model.sort(sort_column_id, Qt.AscendingOrder)
        
            while model.canFetchMore():
                model.fetchMore()
            count = model.rowCount()

            desc = self.getDescription()
            data = {'title': desc.title, 'description': desc.description,
                    'author': desc.author, 'type': "OpenNumismat", 'count': count}
            json_file.write('{"description": ')
            json.dump(data, json_file, indent=2, sort_keys=True, ensure_ascii=False)
            json_file.write(',\n"coins": [\n')
            
            img_file_dict = {}
            
            progressDlg = Gui.ProgressDialog(self.tr("Exporting records"),
                                            self.tr("Cancel"), count, self.parent())

            fields = CollectionFieldsBase()
            for i in range(count):
                progressDlg.step()
                if progressDlg.wasCanceled():
                    break
                
                data = {}
                coin = model.record(i)
                for field in fields:
                    val = coin.value(field.name)
                    if val is None or val == '':
                        continue
        
                    if field.name in ('id', 'createdat', 'updatedat', 'sort_id') or field.type == Type.PreviewImage:
                        continue
                    if field.name in ('saledate', 'paydate', 'issuedate') and val == '2000-01-01':
                        continue
        
                    if field.type == Type.Image:
                        hash_ = QCryptographicHash.hash(val, QCryptographicHash.Sha1)
                        if hash_ in img_file_dict:
                            img_file_title = img_file_dict[hash_]
                        else:
                            img_file_title = "%d_%s.jpg" % (i + 1, field.name)
                            img_file_name = os.path.join(image_path, img_file_title)
                            img_file = open(img_file_name, 'wb')
                            img_file.write(val)
                            img_file.close()
                            
                            img_file_dict[hash_] = img_file_title

                        data[field.name] = img_file_title
                    else:
                        data[field.name] = val
        
                json.dump(data, json_file, indent=2, sort_keys=True, ensure_ascii=False)
                if i < count - 1:
                    json_file.write(',\n')
            
            json_file.write(']\n}')
            json_file.close()

            progressDlg.reset()

    def merge(self, fileName):
        query = QSqlQuery(self.db)
        query.prepare("ATTACH ? AS src")
        query.addBindValue(fileName)
        res = query.exec_()
        if not res:
            QMessageBox.critical(self.parent(),
                                 self.tr("Synchronizing"),
                                 self.tr("Can't open collection:\n%s") %
                                 query.lastError().text())
            return

        sql = "SELECT value FROM src.settings WHERE title='Type'"
        query = QSqlQuery(sql, self.db)
        query.first()
        type_ = query.record().value(0)
        if type_ != version.AppName:
            QMessageBox.critical(self.parent(),
                    self.tr("Synchronizing"),
                    self.tr("Collection %s in wrong format") % fileName)
            return

        sql = "SELECT value FROM src.settings WHERE title='Version'"
        query = QSqlQuery(sql, self.db)
        query.first()
        ver = query.record().value(0)
        if int(ver) != CollectionSettings.Default['Version']:
            QMessageBox.critical(self.parent(),
                    self.tr("Synchronizing"),
                    self.tr("Collection %s in old format.\n(Try to open it before merging.)") % fileName)
            return

        sql = "SELECT value FROM src.settings WHERE title='Password'"
        query = QSqlQuery(sql, self.db)
        query.first()
        pas = query.record().value(0)
        if pas != cryptPassword():
            dialog = PasswordDialog(
                pas, self.fileNameToCollectionName(fileName), self.parent())
            result = dialog.exec_()
            if result == QDialog.Rejected:
                return

        query = QSqlQuery("SELECT COUNT(*) FROM src.coins", self.db)
        query.first()
        count = query.record().value(0)

        progressDlg = Gui.ProgressDialog(
            self.tr("Synchronizing"), self.tr("Cancel"), count, self.parent())

        fields_query = QSqlQuery("PRAGMA table_info(coins)", self.db)
        fields_query.exec_()
        fields = []
        while fields_query.next():
            fields.append(fields_query.record().value(1))
        fields.remove('id')
        sql_fields = ','.join(fields)

        inserted_count = 0
        updated_count = 0
        query = QSqlQuery("SELECT DISTINCT createdat FROM src.coins", self.db)
        while query.next():
            progressDlg.step()
            if progressDlg.wasCanceled():
                break

            self.db.transaction()

            sql = "SELECT 1 FROM coins WHERE createdat=? LIMIT 1"
            select_query = QSqlQuery(sql, self.db)
            select_query.addBindValue(query.record().value(0))
            select_query.exec_()
            if select_query.first():
                dst_fields = ImageFields + \
                             ('id', 'image', 'sort_id')
                sql_dst_fields = ','.join(['coins.%s AS coins_%s' % (f, f) for f in dst_fields])
                sql_src_fields = ','.join(['src_coins.%s' % f for f in fields])
                sql = "SELECT %s, %s FROM coins\
                    INNER JOIN src.coins src_coins ON coins.id=src_coins.id\
                    WHERE src_coins.createdat=? AND\
                          src_coins.createdat=coins.createdat AND\
                          src_coins.updatedat>coins.updatedat" % (sql_src_fields, sql_dst_fields)
                sel_query = QSqlQuery(sql, self.db)
                sel_query.addBindValue(query.record().value(0))
                sel_query.exec_()
                if sel_query.first():
                    sql = "UPDATE coins SET %s WHERE id=?" % ','.join(['%s=?' % f for f in fields])
                    up_query = QSqlQuery(sql, self.db)
                    for field in fields:
                        if field == 'image':
                            img_id = sel_query.record().value(field)
                            old_img_id = sel_query.record().value('coins_image')
                            if img_id and old_img_id:
                                sql = "UPDATE images SET image=(SELECT image FROM src.images WHERE id=?) WHERE id=?"
                                img_query = QSqlQuery(sql, self.db)
                                img_query.addBindValue(img_id)
                                img_query.addBindValue(old_img_id)
                                img_query.exec_()
                            elif img_id:
                                sql = "INSERT INTO images (image) SELECT image FROM src.images WHERE id=?"
                                img_query = QSqlQuery(sql, self.db)
                                img_query.addBindValue(img_id)
                                img_query.exec_()
                                img_id = img_query.lastInsertId()
                            elif old_img_id:
                                sql = "DELETE FROM images WHERE id=?"
                                img_query = QSqlQuery(sql, self.db)
                                img_query.addBindValue(old_img_id)
                                img_query.exec_()
                                img_id = None

                            up_query.addBindValue(img_id)
                        elif field in ImageFields:
                            img_id = sel_query.record().value(field)
                            old_img_id = sel_query.record().value('coins_%s' % field)
                            if img_id and old_img_id:
                                sql = "UPDATE photos SET title=(SELECT title FROM src.photos WHERE id=?), image=(SELECT image FROM src.photos WHERE id=?) WHERE id=?"
                                img_query = QSqlQuery(sql, self.db)
                                img_query.addBindValue(img_id)
                                img_query.addBindValue(img_id)
                                img_query.addBindValue(old_img_id)
                                img_query.exec_()
                                img_id = old_img_id
                            elif img_id:
                                sql = "INSERT INTO photos (title, image) SELECT title, image FROM src.photos WHERE id=?"
                                img_query = QSqlQuery(sql, self.db)
                                img_query.addBindValue(img_id)
                                img_query.exec_()
                                img_id = img_query.lastInsertId()
                            elif old_img_id:
                                sql = "DELETE FROM photos WHERE id=?"
                                img_query = QSqlQuery(sql, self.db)
                                img_query.addBindValue(old_img_id)
                                img_query.exec_()
                                img_id = None

                            up_query.addBindValue(img_id)
                        elif field == 'sort_id':
                            up_query.addBindValue(sel_query.record().value('coins_sort_id'))
                        else:
                            up_query.addBindValue(sel_query.record().value(field))
                    up_query.addBindValue(sel_query.record().value('coins_id'))

                    up_query.exec_()
                    updated_count += 1
            else:
                sql = "SELECT %s FROM src.coins WHERE createdat=?" % sql_fields
                sel_query = QSqlQuery(sql, self.db)
                sel_query.addBindValue(query.record().value(0))
                sel_query.exec_()
                while sel_query.next():
                    sql = "INSERT INTO coins (%s) VALUES (%s)" % (sql_fields, ','.join(['?'] * len(fields)))
                    ins_query = QSqlQuery(sql, self.db)
                    for field in fields:
                        if field == 'image':
                            old_img_id = sel_query.record().value(field)
                            if old_img_id:
                                sql = "INSERT INTO images (image) SELECT image FROM src.images WHERE id=?"
                                img_query = QSqlQuery(sql, self.db)
                                img_query.addBindValue(old_img_id)
                                img_query.exec_()

                                img_id = img_query.lastInsertId()
                            else:
                                img_id = None
                            ins_query.addBindValue(img_id)
                        elif field in ImageFields:
                            old_img_id = sel_query.record().value(field)
                            if old_img_id:
                                sql = "INSERT INTO photos (title, image) SELECT title, image FROM src.photos WHERE id=?"
                                img_query = QSqlQuery(sql, self.db)
                                img_query.addBindValue(old_img_id)
                                img_query.exec_()

                                img_id = img_query.lastInsertId()
                            else:
                                img_id = None
                            ins_query.addBindValue(img_id)
                        elif field == 'sort_id':
                            sort_query = QSqlQuery("SELECT MAX(sort_id) FROM coins", self.db)
                            sort_query.first()
                            sort_id = sort_query.record().value(0)
                            if not sort_id:
                                sort_id = 0
                            ins_query.addBindValue(sort_id + 1)
                        else:
                            ins_query.addBindValue(sel_query.record().value(field))

                    ins_query.exec_()
                    inserted_count += 1

            self.db.commit()

        query = QSqlQuery("DETACH src", self.db)
        query.exec_()

        progressDlg.reset()

        if inserted_count or updated_count:
            text = self.tr("Inserted %d coins, updated %d coins.\nThe application will need to restart now.") % (inserted_count, updated_count)
            QMessageBox.information(self.parent(), self.tr("Synchronizing"),
                                    text)
            # TODO: refresh
            self.parent().restart()
        else:
            text = self.tr("Collections looks like identical")
            QMessageBox.information(self.parent(), self.tr("Synchronizing"),
                                    text) class DescriptionDialog(QDialog):
    def __init__(self, description, parent=None):
        super().__init__(parent,
                         Qt.WindowCloseButtonHint | Qt.WindowSystemMenuHint)

        self.description = description

        self.setWindowTitle(self.tr("Description"))

        mainLayout = QFormLayout()

        self.titleWidget = QLineEdit(self.description.title, self)
        mainLayout.addRow(self.tr("Title"), self.titleWidget)
        self.descriptionWidget = QTextEdit(self)
        self.descriptionWidget.setText(self.description.description)
        self.descriptionWidget.setAcceptRichText(False)
        self.descriptionWidget.setTabChangesFocus(True)
        mainLayout.addRow(self.tr("Description"), self.descriptionWidget)
        self.authorWidget = QLineEdit(self.description.author, self)
        mainLayout.addRow(self.tr("Author"), self.authorWidget)

        buttonBox = QDialogButtonBox(Qt.Horizontal)
        buttonBox.addButton(QDialogButtonBox.Ok)
        buttonBox.addButton(QDialogButtonBox.Cancel)
        buttonBox.accepted.connect(self.save)
        buttonBox.rejected.connect(self.reject)

        layout = QVBoxLayout()
        layout.addLayout(mainLayout)
        layout.addWidget(buttonBox)

        self.setLayout(layout)

    def save(self):
        self.description.title = self.titleWidget.text()
        self.description.description = self.descriptionWidget.toPlainText()
        self.description.author = self.authorWidget.text()
        self.description.save()

        self.accept()
 class PasswordSetDialog(QDialog):
    def __init__(self, settings, parent=None):
        super().__init__(parent,
                         Qt.WindowCloseButtonHint | Qt.WindowSystemMenuHint)
        self.settings = settings

        self.setWindowTitle(self.tr("Set password"))

        mainLayout = QFormLayout()

        self.passwordWidget = QLineEdit(self)
        self.passwordWidget.setEchoMode(QLineEdit.Password)
        if checkPassword(self.settings['Password'], ''):
            self.passwordWidget.setDisabled(True)
        mainLayout.addRow(self.tr("Current password"), self.passwordWidget)
        self.newPasswordWidget = QLineEdit(self)
        self.newPasswordWidget.setEchoMode(QLineEdit.Password)
        mainLayout.addRow(self.tr("New password"), self.newPasswordWidget)
        self.confirmPasswordWidget = QLineEdit(self)
        self.confirmPasswordWidget.setEchoMode(QLineEdit.Password)
        mainLayout.addRow(self.tr("Confirm password"),
                          self.confirmPasswordWidget)

        buttonBox = QDialogButtonBox(Qt.Horizontal)
        buttonBox.addButton(QDialogButtonBox.Ok)
        buttonBox.addButton(QDialogButtonBox.Cancel)
        buttonBox.accepted.connect(self.save)
        buttonBox.rejected.connect(self.reject)

        layout = QVBoxLayout()
        layout.addLayout(mainLayout)
        layout.addWidget(buttonBox)

        self.setLayout(layout)

    def save(self):
        text = self.passwordWidget.text()
        if checkPassword(self.settings['Password'], text):
            text = self.newPasswordWidget.text()
            newPassword = cryptPassword(text)
            text = self.confirmPasswordWidget.text()
            confirmPassword = cryptPassword(text)
            if newPassword != confirmPassword:
                QMessageBox.warning(self, self.tr("Change password"),
                            self.tr("The new password and confirmation "
                                    "password must match"))
                return

            self.settings['Password'] = newPassword
            self.settings.save()
            self.accept()
        else:
            QMessageBox.critical(self, self.tr("Change password"),
                                       self.tr("Incorrect password"))
            self.reject()
 class Report(QtCore.QObject):
    def __init__(self, model, template, dstPath, parent=None):
        super().__init__(parent)

        self.model = model
        self.srcFolder = template
        self.img_file_dict = {}

        fileInfo = QtCore.QFileInfo(dstPath)
        if fileInfo.exists() and fileInfo.isDir():
            self.dstFolder = dstPath
            self.fileName = None
        else:
            self.dstFolder = fileInfo.dir().path()
            self.fileName = fileInfo.fileName()

    def generate(self, indexes, single_file=False):
        if os.path.exists(os.path.join(self.srcFolder, 'coin.htm')):
            has_item_template = True
        else:
            has_item_template = False
            single_file = True

        self.mapping = {'single_file': single_file,
                        'date': QtCore.QDate.currentDate().toString(QtCore.Qt.DefaultLocaleLongDate)}

        self.mapping['collection'] = {'title': self.model.description.title,
                            'description': self.model.description.description,
                            'author': self.model.description.author}

        if not self.fileName:
            if len(indexes) == 1 and has_item_template:
                self.fileName = "coin_%d.htm" % self.__getId(indexes[0])
            else:
                self.fileName = "coins.htm"
        static_files = QtCore.QFileInfo(self.fileName).baseName() + '_files'
        self.contentDir = os.path.join(self.dstFolder, static_files)

        self.mapping['static_files'] = static_files

        copyFolder(os.path.join(self.srcFolder, 'files'), self.contentDir)

        loader = FileSystemLoader(self.srcFolder)
        self.env = Environment(loader=loader, autoescape=True)

        titles_mapping = {}
        for field in self.model.fields:
            titles_mapping[field.name] = field.title
        self.mapping['titles'] = titles_mapping

        if len(indexes) == 1 and has_item_template:
            self.mapping['record'] = self.__recordMapping(indexes[0])
            dstFile = self.__render('coin.htm', self.fileName)
        else:
            progressDlg = Gui.ProgressDialog(self.tr("Generating report"),
                            self.tr("Cancel"), len(indexes), self.parent())

            record_data = []
            for index in indexes:
                progressDlg.step()
                if progressDlg.wasCanceled():
                    return None

                recordMapping = self.__recordMapping(index)
                record_data.append(recordMapping)
                if not single_file:
                    self.mapping['record'] = recordMapping
                    self.__render('coin.htm', "coin_%d.htm" % self.__getId(index))

            self.mapping['records'] = record_data

            dstFile = self.__render('coins.htm', self.fileName)

            progressDlg.reset()

        return dstFile

    def __render(self, template, fileName):
        template = self.env.get_template(template)
        res = template.render(self.mapping)

        dstFile = os.path.join(self.dstFolder, fileName)
        f = codecs.open(dstFile, 'w', 'utf-8')
        f.write(res)
        f.close()

        return dstFile

    def __getId(self, index):
        field_index = self.model.index(index.row(), self.model.fieldIndex('id'))
        return self.model.data(field_index, Qt.UserRole)

    def __recordMapping(self, index):
        imgFields = ('image', 'obverseimg', 'reverseimg', 'edgeimg',
                     'varietyimg', 'photo1', 'photo2', 'photo3', 'photo4',
                     'photo5', 'photo6', 'signatureimg')

        record_mapping = {}
        record_mapping['status_raw'] = ''
        record_mapping['issuedate_raw'] = ''
        record_mapping['diameter_raw'] = ''
        row = index.row()
        for field in self.model.fields:
            field_index = self.model.index(row, self.model.fieldIndex(field.name))
            value = self.model.data(field_index, Qt.DisplayRole)

            if value is None or value == '':
                record_mapping[field.name] = ''
            else:
                if field.name in imgFields:
                    if field.name == 'image':
                        ext = 'png'
                    else:
                        ext = 'jpg'
                    
                    hash_ = QCryptographicHash.hash(value, QCryptographicHash.Sha1)
                    if hash_ in self.img_file_dict:
                        img_file_title = self.img_file_dict[hash_]
                    else:
                        img_file_title = "%s_%d.%s" % (field.name, self.__getId(index), ext)
                        self.img_file_dict[hash_] = img_file_title

                        imgFile = os.path.join(self.contentDir, img_file_title)
                        image = QImage()
                        image.loadFromData(value)
                        image.save(imgFile)

                    record_mapping[field.name] = img_file_title
                else:
                    record_mapping[field.name] = value
                    if field.name == 'status':
                        record_mapping['status_raw'] = self.model.data(field_index, Qt.UserRole)
                    elif field.name == 'issuedate':
                        record_mapping['issuedate_raw'] = self.model.data(field_index, Qt.UserRole)
                    elif field.name == 'diameter':
                        record_mapping['diameter_raw'] = self.model.data(field_index, Qt.UserRole)

        return record_mapping
 class TabView(QTabWidget):
    def __init__(self, parent):
        super().__init__(parent)

        tabBar = TabBar(self)
        self.setTabBar(tabBar)

        self.setMovable(True)
        self.setTabsClosable(True)
        self.tabCloseRequested.connect(self.closePage)
        self.currentChanged.connect(self.activatedPage)
        self.tabBar().customContextMenuRequested.connect(
                                                self.tabBarContextMenuEvent)
        self.tabBar().doubleClicked.connect(self.tabDClicked)
        self.tabBar().tabMoved.connect(self.tabMoved)
        self.oldPage = None
        self.__pages_changed = False

        self.__createActions()

    def mouseDoubleClickEvent(self, _event):
        self.newList()

    def tabDClicked(self, index):
        self.renamePage(index)

    def tabMoved(self, _from, _to):
        self.__pages_changed = True

    def actions(self):
        return self.__actions

    def __createActions(self):
        self.__actions = {}

        newListAct = QAction(self.tr("&New..."), self)
        newListAct.triggered.connect(self.newList)
        self.__actions['new'] = newListAct

        cloneListAct = QAction(self.tr("Clone"), self)
        cloneListAct.triggered.connect(self._clone)
        self.__actions['clone'] = cloneListAct

        openPageMenu = QMenu(self.tr("Open"), self)
        self.__actions['open'] = openPageMenu

        removeAllAct = QAction(QIcon(':/cross.png'),
                               self.tr("Remove all"), self)
        removeAllAct.triggered.connect(self.removeClosedPages)
        self.__actions['removeAll'] = removeAllAct

        renameListAct = QAction(self.tr("Rename..."), self)
        renameListAct.triggered.connect(self.renamePage)
        self.__actions['rename'] = renameListAct

        selectColumnsAct = QAction(self.tr("Select columns..."), self)
        selectColumnsAct.triggered.connect(self.selectColumns)
        self.__actions['select'] = selectColumnsAct

        closeListAct = QAction(self.tr("Close"), self)
        closeListAct.setShortcut(QKeySequence.Close)
        closeListAct.triggered.connect(self.closePage)
        self.__actions['close'] = closeListAct

        removeListAct = QAction(QIcon(':/cross.png'),
                                self.tr("Remove"), self)
        removeListAct.triggered.connect(self.removePage)
        self.__actions['remove'] = removeListAct

    def tabBarContextMenuEvent(self, pos):
        index = self.tabBar().tabAt(pos)
        self.setCurrentIndex(index)

        menu = QMenu(self)
        menu.addAction(self.__actions['rename'])
        menu.setDefaultAction(self.__actions['rename'])
        menu.addAction(self.__actions['clone'])
        menu.addSeparator()
        menu.addAction(self.__actions['remove'])
        menu.exec_(self.mapToGlobal(pos))

    def _clone(self):
        index = self.currentIndex()
        oldLabel = self.tabText(index)
        oldWidget = self.widget(index)

        pageTitle = oldLabel + self.tr(" (clone)")
        pageParam = self.collection.pages().addPage(pageTitle)
        pageParam.listParam = oldWidget.listView.listParam.clone()
        pageParam.listParam.page.id = pageParam.id
        pageParam.listParam.save()

        pageView = self.__createPage(pageParam)
        self.collection.pages().openPage(pageView)

    def clearStatusBar(self):
        if self.oldPage:
            statusBar = self.parent().statusBar()
            statusBar.removeWidget(self.oldPage.listView.listCountLabel)
            statusBar.removeWidget(self.oldPage.listView.listSelectedLabel)

    def updatePage(self, page):
        parent = self.parent()
        statusBar = parent.statusBar()
        statusBar.addPermanentWidget(page.listView.listCountLabel)
        page.listView.listCountLabel.show()
        statusBar.addPermanentWidget(page.listView.listSelectedLabel)
        page.listView.listSelectedLabel.show()

        type_ = page.param.type
        if type_ == CollectionPageTypes.Card:
            parent.viewButton.setDefaultAction(parent.cardViewAct)
        elif type_ == CollectionPageTypes.Icon:
            parent.viewButton.setDefaultAction(parent.iconViewAct)
        else:
            parent.viewButton.setDefaultAction(parent.tableViewAct)

        self.__actions['select'].setEnabled(type_ == CollectionPageTypes.List)

        mode = page.listView.isDragMode()
        parent.enableDragAct.setChecked(mode)

    def activatedPage(self, index):
        enabled = (index >= 0)
        self.__actions['rename'].setEnabled(enabled)
        self.__actions['close'].setEnabled(enabled)
        self.__actions['remove'].setEnabled(enabled)

        if index >= 0:
            page = self.widget(index)
            page.model().select()

            self.parent().updateInfoType(page.param.info_type)
            self.parent().quickSearch.setText(page.listView.searchText)

            self.clearStatusBar()
            self.updatePage(page)

            self.oldPage = page
        else:
            self.parent().quickSearch.clear()
            self.oldPage = None

    def clear(self):
        self.currentChanged.disconnect(self.activatedPage)
        for _ in range(self.count()):
            w = self.widget(0)
            self.removeTab(0)
            w.deleteLater()
        self.currentChanged.connect(self.activatedPage)

    def setCollection(self, collection):
        self.collection = collection

        self.currentChanged.disconnect(self.activatedPage)

        for pageParam in collection.pages().pagesParam():
            if pageParam.isopen:
                self.__createPage(pageParam)

        self.currentChanged.connect(self.activatedPage)

        self.setCurrentIndex(0)
        if self.count() == 1:
            self.activatedPage(0)

        # If no pages exists => create default page
        if self.count() == 0:
            self.__createListPage(self.tr("Coins"))

    def currentModel(self):
        index = self.currentIndex()
        page = self.widget(index)
        return page.model()

    def newList(self):
        label, ok = QInputDialog.getText(self, self.tr("New list"),
                self.tr("Enter list title"), text=self.tr("New list"),
                flags=(Qt.WindowCloseButtonHint | Qt.WindowSystemMenuHint))
        if ok and label:
            self.__createListPage(label)

    def renamePage(self, index=None):
        index = self.currentIndex()
        oldLabel = self.tabText(index)
        label, ok = QInputDialog.getText(self, self.tr("Rename list"),
                self.tr("Enter new list title"), text=oldLabel,
                flags=(Qt.WindowCloseButtonHint | Qt.WindowSystemMenuHint))
        if ok and label:
            self.setTabText(index, label)
            page = self.widget(index)
            self.collection.pages().renamePage(page, label)
            self.setCurrentIndex(index)

    def selectColumns(self):
        listView = self.currentListView()
        listView.selectColumns()

    def closePage(self, index=None):
        if self.count() <= 1:
            QMessageBox.information(self, self.tr("Remove page"),
                    self.tr("Can't close latest opened page.\n"
                            "Add a new one first."))
            return

        if index is None:
            index = self.currentIndex()
        page = self.widget(index)
        self.removeTab(index)
        self.collection.pages().closePage(page)

    def removePage(self):
        if self.count() <= 1:
            QMessageBox.information(self, self.tr("Remove page"),
                    self.tr("Can't remove latest opened page.\n"
                            "Add a new one first."))
            return

        index = self.currentIndex()
        pageTitle = self.tabText(index)
        result = QMessageBox.question(self, self.tr("Remove page"),
                self.tr("Remove the page '%s' permanently?") % pageTitle,
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No)
        if result == QMessageBox.Yes:
            page = self.widget(index)
            self.removeTab(index)
            self.collection.pages().removePage(page.param)

    def removeClosedPages(self):
        result = QMessageBox.question(self, self.tr("Remove pages"),
                self.tr("Remove all closed pages permanently?"),
                QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No)
        if result == QMessageBox.Yes:
            closedPages = self.collection.pages().closedPages()
            for pageParam in closedPages:
                self.collection.pages().removePage(pageParam)

    def savePagePositions(self, only_if_changed=False):
        if not only_if_changed or self.__pages_changed:
            pages = []
            for i in range(self.count()):
                pages.append(self.widget(i))
            self.collection.pages().savePositions(pages)

            self.__pages_changed = False

    def openPage(self, pageParam):
        pageView = self.__createPage(pageParam)
        self.collection.pages().openPage(pageView)

    def openPageEvent(self):
        pageParam = self.sender().data()
        self.openPage(pageParam)

    def updateOpenPageMenu(self):
        menu = self.__actions['open']
        menu.clear()
        closedPages = self.collection.pages().closedPages()
        hasClosedPages = len(closedPages)
        menu.setEnabled(hasClosedPages)
        if hasClosedPages:
            for pageParam in closedPages:
                act = QAction(pageParam.title, self)
                act.setData(pageParam)
                act.triggered.connect(self.openPageEvent)
                menu.addAction(act)

            menu.addSeparator()
            menu.addAction(self.__actions['removeAll'])

    def currentListView(self):
        return self.currentWidget().listView

    def currentPageView(self):
        return self.currentWidget()

    def __createListPage(self, title):
        pageParam = self.collection.pages().addPage(title)
        self.__createPage(pageParam)

    def __createPage(self, pageParam):
        settings = self.collection.settings
        pageParam.images_at_bottom = settings['images_at_bottom']
        pageParam.treeParam.convert_fraction = settings['convert_fraction']

        pageView = PageView(pageParam, self)
        pageView.setModel(self.collection.model(), self.collection.reference)
        self.addTab(pageView, pageParam.title)
        self.setCurrentWidget(pageView)

        return pageView class Settings(BaseSettings):
    default_template = os.path.join(OpenNumismat.PRJ_PATH, 'templates', 'full')
    Default = {'locale': _getLocale(),
               'backup': OpenNumismat.HOME_PATH + "/backup/",
               'autobackup': True,
               'autobackup_depth': 25,
               'reference': OpenNumismat.HOME_PATH + "/reference.ref",
               'error': True,
               'speedup': 1,
               'updates': False,
               'template': default_template,
               'check_coin_title': True,
               'check_coin_duplicate': True,
               'images_by_default': 2,
               'colnect_enabled': True,
               'colnect_locale': _getLocale(),
               'colnect_autoclose': False,
               'colnect_skip_currency': True,
               'map_type': 0,
               'built_in_viewer': True,
               'font_size': 0}

    def __init__(self, autoSave=False):
        super().__init__(autoSave)

        self.settings = QSettings()

    def keys(self):
        return self.Default.keys()

    def _getValue(self, key):
        if key in ('error', 'updates', 'autobackup',
                   'check_coin_title', 'check_coin_duplicate',
                   'colnect_enabled', 'colnect_autoclose',
                   'colnect_skip_currency', 'built_in_viewer'):
            value = self.settings.value('mainwindow/' + key, self.Default[key],
                                        type=bool)
        elif key in ('images_by_default', 'autobackup_depth',
                     'speedup', 'map_type', 'font_size'):
            value = self.settings.value('mainwindow/' + key, self.Default[key],
                                        type=int)
        else:
            value = self.settings.value('mainwindow/' + key, self.Default[key])

            if key == 'template':
                if not os.path.isdir(value):
                    value = self.default_template

        return value

    def _saveValue(self, key, val):
        self.settings.setValue('mainwindow/' + key, val) class SettingsDialog(QDialog):
    def __init__(self, collection, parent=None):
        super().__init__(parent,
                         Qt.WindowCloseButtonHint | Qt.WindowSystemMenuHint)

        mainPage = MainSettingsPage(collection, self)
        collectionPage = CollectionSettingsPage(collection, self)
        fieldsPage = FieldsSettingsPage(collection, self)
        colnectPage = ColnectSettingsPage(collection, self)

        self.setWindowTitle(self.tr("Settings"))

        self.tab = QTabWidget(self)
        self.tab.addTab(mainPage, self.tr("Main"))
        index = self.tab.addTab(collectionPage, self.tr("Collection"))
        if not collection.isOpen():
            self.tab.setTabEnabled(index, False)
        index = self.tab.addTab(fieldsPage, self.tr("Fields"))
        if not collection.isOpen():
            self.tab.setTabEnabled(index, False)
        index = self.tab.addTab(colnectPage, "Colnect")
        if not collection.isOpen():
            self.tab.setTabEnabled(index, False)

        buttonBox = QDialogButtonBox(Qt.Horizontal)
        buttonBox.addButton(QDialogButtonBox.Ok)
        buttonBox.addButton(QDialogButtonBox.Cancel)
        buttonBox.accepted.connect(self.save)
        buttonBox.rejected.connect(self.reject)

        layout = QVBoxLayout()
        layout.addWidget(self.tab)
        layout.addWidget(buttonBox)

        self.setLayout(layout)

    def save(self):
        for i in range(self.tab.count()):
            page = self.tab.widget(i)
            page.save()

        self.accept()
 class LatestCollections(QObject):
    DefaultCollectionName = OpenNumismat.HOME_PATH + "/demo.db"
    SettingsKey = 'collection/latest'
    LatestCount = 7

    def __init__(self, parent=None):
        super().__init__(parent)

        self.settings = QSettings()

    # Create menu actions
    def actions(self):
        actions = []
        for i in range(LatestCollections.LatestCount):
            key = self.__key(i)
            fileName = self.settings.value(key)
            if fileName:
                title = Collection.fileNameToCollectionName(fileName)
                act = QAction(title, self)
                act.setData(fileName)
                actions.append(act)

        return actions

    # Return latest opened collection file name
    def latest(self):
        fileName = self.settings.value(self.SettingsKey)
        if not fileName:
            fileName = LatestCollections.DefaultCollectionName

        return fileName

    def add(self, fileName):
        # Get stored latest collections
        values = []
        for i in range(LatestCollections.LatestCount):
            val = self.settings.value(self.__key(i))
            if val:
                values.append(val)

        values.insert(0, fileName)
        # Uniqify collections name (order preserving)
        checked = []
        for e in values:
            if e not in checked:
                checked.append(e)
        values = checked

        # Store updated latest collections
        for i, value in enumerate(values):
            self.settings.setValue(self.__key(i), value)

        # Remove unused settings entries
        for i in range(len(values), LatestCollections.LatestCount):
            self.settings.remove(self.__key(i))

        # Store latest collection for auto opening
        self.settings.setValue(self.SettingsKey, fileName)

    def delete(self, fileName):
        for i in range(LatestCollections.LatestCount):
            value = self.settings.value(self.__key(i))
            if value == fileName:
                self.settings.remove(self.__key(i))

    def __key(self, i):
        return self.SettingsKey + str(i + 1)
 def waitCursorDecorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        res = f(*args, **kwds)
        QApplication.restoreOverrideCursor()
        return res

    return wrapper class ExportDialog(QDialog):
    IMAGE_OBVERSE = 0
    IMAGE_REVERSE = 1
    IMAGE_BOTH = 2

    latestDir = OpenNumismat.HOME_PATH
    params = {}

    def __init__(self, collection, parent=None):
        super().__init__(parent,
                         Qt.WindowCloseButtonHint | Qt.WindowSystemMenuHint)

        self.collection = collection

        self.setWindowTitle(self.tr("Export to mobile"))

        form = QFormLayout()
        form.setRowWrapPolicy(QFormLayout.WrapLongRows)

        # density
        self.densitySelector = QComboBox(self)
        densities = ('MDPI', 'HDPI', 'XHDPI', 'XXHDPI', 'XXXHDPI')
        self.densitySelector.addItems(densities)
        self.densitySelector.setSizePolicy(QSizePolicy.Fixed,
                                           QSizePolicy.Fixed)
        form.addRow(self.tr("Target density of the display"), self.densitySelector)

        settings = QSettings()
        density = settings.value('density', 'XHDPI')
        self.densitySelector.setCurrentText(density)

        # filtering
        self.filterSelector = QComboBox(self)
        self.filterSelector.addItem(self.tr("Countries"), 'country')
        self.filterSelector.addItem(self.tr("Series"), 'series')
        self.filterSelector.addItem(self.tr("Denomination"), 'denomination')
        self.filterSelector.setSizePolicy(QSizePolicy.Fixed,
                                          QSizePolicy.Fixed)
        form.addRow(self.tr("Default filter by"), self.filterSelector)

        # image
        groupBox = QGroupBox(self.tr("Image"))
        vbox = QFormLayout(self)

        self.obverseRadio = QRadioButton(self.tr("Obverse"), self)
        self.reverseRadio = QRadioButton(self.tr("Reverse"), self)
        self.bothRadio = QRadioButton(self.tr("Both"), self)
        self.bothRadio.setChecked(True)

        vbox.addRow(self.obverseRadio)
        vbox.addRow(self.reverseRadio)
        vbox.addRow(self.bothRadio)

        groupBox.setLayout(vbox)
        form.addRow(groupBox)

        self.fullImage = QCheckBox(self.tr("Export a full-sized image"), self)
        form.addRow(self.fullImage)

        # file
        self.destination = QLineEdit(self)
        style = QApplication.style()
        icon = style.standardIcon(QStyle.SP_DialogOpenButton)
        self.destinationButton = QPushButton(icon, '', self)
        self.destinationButton.clicked.connect(self.destinationButtonClicked)

        hLayout = QHBoxLayout()
        hLayout.addWidget(self.destination)
        hLayout.addWidget(self.destinationButton)
        hLayout.setContentsMargins(QMargins())

        form.addRow(self.tr("Destination"), hLayout)

        buttonBox = QDialogButtonBox(Qt.Horizontal)
        self.acceptButton = buttonBox.addButton(QDialogButtonBox.Ok)
        self.acceptButton.setEnabled(False)
        buttonBox.addButton(QDialogButtonBox.Cancel)
        buttonBox.accepted.connect(self.start)
        buttonBox.rejected.connect(self.reject)

        layout = QVBoxLayout()
        layout.addLayout(form)
        layout.addWidget(buttonBox)

        self.setLayout(layout)

        self.setFixedSize(self.sizeHint())

    def destinationButtonClicked(self):
        destination = self.destination.text()
        if not destination and self.collection:
            destination = self.latestDir + '/' + self.collection.getCollectionName() + '_mobile.db'
        file, _selectedFilter = QFileDialog.getSaveFileName(
            self, self.tr("Select destination"), destination, "*.db")
        if file:
            file_info = QFileInfo(file)
            self.latestDir = file_info.absolutePath()

            self.destination.setText(file)
            self.acceptButton.setEnabled(True)

    def start(self):
        file = self.destination.text().strip()
        if not file:
            QMessageBox.warning(self,
                              self.tr("Create mobile collection"),
                              self.tr("Destination file not specified"))
            return

        density = self.densitySelector.currentText()
        settings = QSettings()
        settings.setValue('density', density)

        self.params['filter'] = self.filterSelector.itemData(self.filterSelector.currentIndex())
        self.params['density'] = density
        self.params['file'] = file
        if self.obverseRadio.isChecked():
            self.params['image'] = self.IMAGE_OBVERSE
        elif self.reverseRadio.isChecked():
            self.params['image'] = self.IMAGE_REVERSE
        else:
            self.params['image'] = self.IMAGE_BOTH
        self.params['fullimage'] = self.fullImage.isChecked()

        self.accept()
     class FigureCanvas:
    class FigureCanvas:
        class QWebView:
class GeoChartCanvas(QWebView):
class BaseCanvas(FigureCanvas):
class BarCanvas(BaseCanvas):
class BarHCanvas(BaseCanvas):
class PieCanvas(BaseCanvas):
class StackedBarCanvas(BaseCanvas):
class ProgressCanvas(BaseCanvas):
class StatisticsView(QWidget):
    HTML = """
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta charset="utf-8">
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
      google.charts.load('current', {
        'packages':['geochart'],
        'language': '%s',
        'mapsApiKey': '%s'
      });
      google.charts.setOnLoadCallback(drawRegionsMap);

      var data;
      var chart;
      var element;
      var region = %s;

      function drawRegionsMap() {
        data = google.visualization.arrayToDataTable([
          %s
        ]);

        element = document.getElementById('regions_div');
        chart = new google.visualization.GeoChart(element);
        chart.draw(data, {region: region});
      }
      window.onresize = function(event) {
        width = element.style.width;
        height = element.style.height;
        chart.draw(data, {width: width, height: height, region: region});
      }
    </script>
  </head>
  <body>
    <div id="regions_div" style="width: 100%%; height: 100%%;"></div>
  </body>
</html>
    """
    def setData(self, xx, yy, region):
    def setMulticolor(self, multicolor=False):
    def save(self, fileName, selectedFilter):
    def __init__(self, parent=None, width=5, height=4, dpi=100):
    def setMulticolor(self, multicolor=False):
    def setLabel(self, text):
    def setLabelY(self, text):
    def hover(self, event):
    def tooltip(self, pos):
    def save(self, fileName, selectedFilter):
    def setData(self, xx, yy):
    def setData(self, xx, yy):
    def setData(self, xx, yy):
    def setLabelZ(self, text):
    def setData(self, xx, yy, zz):
    def tooltip(self, pos):
    def setData(self, xx, yy):
    def __init__(self, statisticsParam, parent=None):
    def setModel(self, model):
    def clear(self):
    def modelChanged(self):
    def fieldChaged(self, _text):
    def subfieldChaged(self, _text):
    def chartChaged(self, _text):
    def showConfig(self, chart):
    def periodChaged(self, _text):
    def itemsChaged(self, _text):
    def colorChanged(self, state):
    def regionChanged(self, _text):
    def __layoutToWidget(self, layout):
    def save(self): class SummaryDialog(QDialog):

    def __init__(self, model, parent=None):
        super().__init__(parent,
                         Qt.WindowCloseButtonHint | Qt.WindowSystemMenuHint)

        self.setWindowTitle(self.tr("Summary"))

        buttonBox = QDialogButtonBox(Qt.Horizontal)
        buttonBox.addButton(QDialogButtonBox.Ok)
        buttonBox.accepted.connect(self.accept)

        self.textBox = QTextEdit(self)
        self.textBox.setReadOnly(True)

        layout = QVBoxLayout()
        layout.addWidget(self.textBox)
        layout.addWidget(buttonBox)

        self.setLayout(layout)

        lines_all = self.fillSummary(model)
        lines_selected = self.fillSummary(model, model.filter())

        lines = [self.tr("[Selected]")] + lines_selected + ["", self.tr("[All]")] + lines_all
        self.textBox.setText('\n'.join(lines))

    def makeSql(self, sql, filter_):
        if filter_:
            if 'WHERE' in sql:
                sql = "%s AND %s" % (sql, filter_)
            else:
                sql = "%s WHERE %s" % (sql, filter_)

        return sql

    def fillSummary(self, model, filter_=None):
        lines = []

        sql = "SELECT count(*) FROM coins"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            totalCount = query.record().value(0)
            lines.append(self.tr("Total count: %d") % totalCount)

        count_owned = 0
        quantity_owned = 0
        sql = "SELECT quantity FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'duplicate')"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        while query.next():
            record = query.record()
            quantity = int(record.value('quantity') or 1)
            count_owned += 1
            quantity_owned += quantity
        if count_owned == quantity_owned:
            lines.append(self.tr("Count owned: %d") % count_owned)
        else:
            lines.append(self.tr("Count owned: %d/%d") % (quantity_owned, count_owned))

        count_gold = 0
        quantity_gold = 0
        gold_filter = self.materialFilter("Gold", self.tr("Gold"), "Au")
        sql = "SELECT quantity FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'duplicate') AND " \
                "%s" % gold_filter
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        while query.next():
            record = query.record()
            quantity = int(record.value('quantity') or 1)
            count_gold += 1
            quantity_gold += quantity
        if count_gold:
            if count_gold == quantity_gold:
                lines.append(self.tr("Gold coins: %d") % count_gold)
            else:
                lines.append(self.tr("Gold coins: %d/%d") % (quantity_gold, count_gold))
        
        if count_gold:
            sql = "SELECT fineness, weight, quantity FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'duplicate') AND " \
                    "%s AND " \
                    "ifnull(fineness,'')<>'' AND ifnull(weight,'')<>''" % gold_filter
            sql = self.makeSql(sql, filter_)
            query = QSqlQuery(sql, model.database())
            gold_weight = 0
            gold_count = 0
            gold_quantity = 0
            while query.next():
                record = query.record()
                fineness = record.value('fineness')
                weight = record.value('weight')
                quantity = int(record.value('quantity') or 1)
                gold_weight += float(weight) * float("0.%s" % fineness) * quantity
                gold_count += 1
                gold_quantity += quantity
            if gold_weight:
                if gold_count == gold_quantity:
                    comment = self.tr("(calculated for %d coins)") % gold_quantity
                else:
                    comment = self.tr("(calculated for %d/%d coins)") % (gold_quantity, gold_count)
                gold_weight_str = locale.format_string("%.2f", gold_weight, grouping=True)
                lines.append(' '.join((self.tr("Gold weight: %s gramm") % gold_weight_str, comment)))

        count_silver = 0
        quantity_silver = 0
        silver_filter = self.materialFilter("Silver", self.tr("Silver"), "Ag")
        sql = "SELECT quantity FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'duplicate') AND " \
                "%s" % silver_filter
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        while query.next():
            record = query.record()
            quantity = int(record.value('quantity') or 1)
            count_silver += 1
            quantity_silver += quantity
        if count_silver:
            if count_silver == quantity_silver:
                lines.append(self.tr("Silver coins: %d") % count_silver)
            else:
                lines.append(self.tr("Silver coins: %d/%d") % (quantity_silver, count_silver))
        
        if count_silver:
            sql = "SELECT fineness, weight, quantity FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'duplicate') AND " \
                    "%s AND " \
                    "ifnull(fineness,'')<>'' AND ifnull(weight,'')<>''" % silver_filter
            sql = self.makeSql(sql, filter_)
            query = QSqlQuery(sql, model.database())
            silver_weight = 0
            silver_count = 0
            silver_quantity = 0
            while query.next():
                record = query.record()
                fineness = record.value('fineness')
                weight = record.value('weight')
                quantity = int(record.value('quantity') or 1)
                silver_weight += float(weight) * float("0.%s" % fineness) * quantity
                silver_count += 1
                silver_quantity += quantity
            if silver_weight:
                if silver_count == silver_quantity:
                    comment = self.tr("(calculated for %d coins)") % silver_quantity
                else:
                    comment = self.tr("(calculated for %d/%d coins)") % (silver_quantity, silver_count)
                silver_weight_str = locale.format_string("%.2f", silver_weight, grouping=True)
                lines.append(' '.join((self.tr("Silver weight: %s gramm") % silver_weight_str, comment)))

        sql = "SELECT count(*) FROM coins WHERE status='wish'"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            count = query.record().value(0)
            lines.append(self.tr("Count wish: %d") % count)

        count_sold = 0
        sql = "SELECT count(*) FROM coins WHERE status='sold'"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            count_sold = query.record().value(0)
            if count_sold > 0:
                lines.append(self.tr("Count sales: %d") % count_sold)

        sql = "SELECT count(*) FROM coins WHERE status='bidding'"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            count = query.record().value(0)
            if count > 0:
                lines.append(self.tr("Count biddings: %d") % count)

        sql = "SELECT count(*) FROM coins WHERE status='missing'"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            count = query.record().value(0)
            if count > 0:
                lines.append(self.tr("Count missing: %d") % count)

        paid = 0
        commission = ""
        sql = "SELECT SUM(totalpayprice) FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'sold', 'missing', 'duplicate') AND totalpayprice<>'' AND totalpayprice IS NOT NULL"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            paid = query.record().value(0)
            if paid:
                sql = "SELECT SUM(payprice) FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'sold', 'missing', 'duplicate') AND payprice<>'' AND payprice IS NOT NULL"
                sql = self.makeSql(sql, filter_)
                query = QSqlQuery(sql, model.database())
                if query.first():
                    paid_without_commission = query.record().value(0)
                    if paid_without_commission:
                        commission = self.tr("(commission %d%%)") % ((paid - paid_without_commission) / paid_without_commission * 100)
                paid_str = locale.currency(paid, grouping=True, symbol=False)
                lines.append(' '.join((self.tr("Paid: %s") % paid_str, commission)))

                if count_owned:
                    val = paid / count_owned
                    val_str = locale.currency(val, grouping=True, symbol=False)
                    lines.append(self.tr("Average paid per item: %s") % val_str)

        earned = 0
        commission = ""
        sql = "SELECT SUM(totalsaleprice) FROM coins WHERE status='sold' AND totalsaleprice<>'' AND totalsaleprice IS NOT NULL"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            earned = query.record().value(0)
            if earned:
                sql = "SELECT SUM(saleprice) FROM coins WHERE status='sold' AND saleprice<>'' AND saleprice IS NOT NULL"
                sql = self.makeSql(sql, filter_)
                query = QSqlQuery(sql, model.database())
                if query.first():
                    earn_without_commission = query.record().value(0)
                    if earn_without_commission:
                        commission = self.tr("(commission %d%%)") % ((earn_without_commission - earned) / earn_without_commission * 100)
                earned_str = locale.currency(earned, grouping=True, symbol=False)
                lines.append(' '.join((self.tr("Earned: %s") % earned_str, commission)))

                if count_sold:
                    val = earned / count_sold
                    val_str = locale.currency(val, grouping=True, symbol=False)
                    lines.append(self.tr("Average earn per item: %s") % val_str)

        if paid and earned:
            total = paid - earned
            total_str = locale.currency(total, grouping=True, symbol=False)
            lines.append(self.tr("Total (paid - earned): %s") % total_str)

        sql = "SELECT paydate FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'sold', 'missing', 'duplicate') AND paydate<>'' AND paydate IS NOT NULL ORDER BY paydate LIMIT 1"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            date = QDate.fromString(query.record().value(0), Qt.ISODate)
            paydate = date.toString(Qt.SystemLocaleShortDate)
            lines.append(self.tr("First purchase: %s") % paydate)

        sql = "SELECT UPPER(grade), price1, price2, price3, price4, quantity FROM coins WHERE status IN ('owned', 'ordered', 'sale', 'duplicate') AND (ifnull(price1,'')<>'' OR ifnull(price2,'')<>'' OR ifnull(price3,'')<>'' OR ifnull(price4,'')<>'')"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        est_owned = 0
        count = 0
        coins_quantity = 0
        comment = ""
        while query.next():
            record = query.record()
            grade = record.value(0)
            price1 = record.value(1)
            price2 = record.value(2)
            price3 = record.value(3)
            price4 = record.value(4)
            quantity = int(record.value(5) or 1)

            try:
                if grade[:2] in ('UN', 'MS'):
                    price = price4 if price4 else price3 * 1.6 if price3 else price2 * 2.2 if price2 else price1 * 5.5 if price1 else 0
                elif grade[:2] in ('XF', 'EF'):
                    price = price3 if price3 else price4 * 0.6 if price4 else price2 * 1.4 if price2 else price1 * 3.5 if price1 else 0
                elif grade[:2] in ('AU',):
                    priceU = price4 if price4 else price3 * 1.6 if price3 else price2 * 2.2 if price2 else price1 * 5.5 if price1 else 0
                    priceX = price3 if price3 else price4 * 0.6 if price4 else price2 * 1.4 if price2 else price1 * 3.5 if price1 else 0
                    price = priceX + (priceU - priceX) * 0.6
                elif grade[:2] in ('VF',):
                    price = price2 if price2 else price3 * 0.7 if price3 else price4 * 0.45 if price4 else price1 * 2.5 if price1 else 0
                elif grade[:2] in ('F', 'FI'):
                    price = price1 if price1 else price2 * 0.4 if price2 else price3 * 0.3 if price3 else price4 * 0.18 if price4 else 0
                elif grade[:2] in ('VG',):
                    price = price1 * 0.5 if price1 else price2 * 0.2 if price2 else price3 * 0.14 if price3 else price4 * 0.09 if price4 else 0
                else:
                    price = price4 if price4 else price3 if price3 else price2 if price2 else price1 if price1 else 0

                if price:
                    est_owned += price * quantity
                    count += 1
                    coins_quantity += quantity

            except TypeError:
                continue

        if count:
            if count == coins_quantity:
                comment = self.tr("(calculated for %d coins)") % count
            else:
                comment = self.tr("(calculated for %d/%d coins)") % (coins_quantity, count)

        lines.append(' '.join((self.tr("Estimation owned: %d") % est_owned, comment)))

        sql = "SELECT price1, price2, price3, price4 FROM coins WHERE status='wish' AND (ifnull(price1,'')<>'' OR ifnull(price2,'')<>'' OR ifnull(price3,'')<>'' OR ifnull(price4,'')<>'')"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        est_wish = 0
        count = 0
        comment = ""
        while query.next():
            record = query.record()
            price1 = record.value(0)
            price2 = record.value(1)
            price3 = record.value(2)
            price4 = record.value(3)

            price = price4 if price4 else price3 if price3 else price2 if price2 else price1 if price1 else 0

            try:
                if price:
                    est_wish += price
                    count += 1
            except TypeError:
                continue

        if count:
            comment = self.tr("(calculated for %d coins)") % count

        lines.append(' '.join((self.tr("Estimation wish: %d") % est_wish, comment)))

        sql = "SELECT count(*) FROM photos"
        sql = self.makeSql(sql, filter_)
        query = QSqlQuery(sql, model.database())
        if query.first():
            count = query.record().value(0)
            lines.append(self.tr("Count images: %d") % count)

        return lines

    def materialFilter(self, *materials):
        filters = []
        for material in materials:
            for material_variant in (material.lower(), material.upper(), material.capitalize()):
                filters.append("'%s'" % material_variant)

        return 'material IN (%s)' % ','.join(filters)
 class ColnectCache(QObject):
class ColnectConnector(QObject):
class ColnectDialog(QDialog):
class ImportColnect(_Import2):
    FILE_NAME = "opennumismat-colnect"
    HEIGHT = 62
    def __init__(self, parent=None):
    def open(self):
    def close(self):
    def get(self, type_, url):
    def set(self, type_, url, data):
    def _compact(self):
    def _file_name():
    def clear():
    def __init__(self, parent):
    def makeItem(self, category, data, record):
    def getFields(self, category):
    def getImage(self, image_id, name, full):
    def _imageUrl(self, image_id, name, full):
    def _urlize(self, name):
    def _baseUrl(self):
    def getData(self, action):
    def getCountries(self, category):
    def getYears(self, category, country):
    def getSeries(self, category, country):
    def getColors(self, category):
    def getDistributions(self, category, country):
    def getValues(self, category, country):
    def getCurrencies(self, category, country):
    def close(self):
    def __init__(self, model, parent=None):
    def sectionDoubleClicked(self, index):
    def _partsEnable(self, enabled):
    def tableClicked(self, index):
    def addCoin(self, index, close):
    def _clearTable(self):
    def categoryChanged(self, _index):
    def countryChanged(self, _index):
    def partChanged(self, _index):
    def preview(self):
    def _getFieldData(self, data, fields, field_name):
    def _getImage(self, image_id, name):
    def _itemUrl(self, category, item_id):
    def clicked(self, button):
    def accept(self):
    def __init__(self, parent=None):
    def isAvailable():
    def _connect(self, src):
    def _getRowsCount(self, connection):
    def _setRecord(self, record, row):
 class CollectionPageTypes:
    Default = 0
    TypeMask = 0x0F
    List = 0
    Icon = 1
    Card = 2
    InfoTypeMask = 0xF0
    Details = 0
    Statistics = 0x10
    Map = 0x20
import sys
import urllib.request
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from OpenNumismat.Collection.Collection import Collection
from OpenNumismat.Collection.Description import DescriptionDialog
from OpenNumismat.Collection.Password import PasswordSetDialog
from OpenNumismat.Reports import Report
from OpenNumismat.TabView import TabView
from OpenNumismat.Settings import Settings
from OpenNumismat.SettingsDialog import SettingsDialog
from OpenNumismat.LatestCollections import LatestCollections
from OpenNumismat.Tools.CursorDecorators import waitCursorDecorator
from OpenNumismat import version
from OpenNumismat.Collection.Export import ExportDialog
from OpenNumismat.StatisticsView import statisticsAvailable, importedQtWebKit
from OpenNumismat.SummaryDialog import SummaryDialog
from OpenNumismat.Collection.Import.Colnect import ColnectDialog, colnectAvailable
from OpenNumismat.Collection.CollectionPages import CollectionPageTypes
from OpenNumismat.Collection.Import import *
        from xml.dom.minidom import parseString
        if pageIndex is not None:
            self.viewTab.setCurrentIndex(int(pageIndex))

        geometry = settings.value('mainwindow/geometry')
        if geometry:
            self.restoreGeometry(geometry)
        winState = settings.value('mainwindow/winState')
        if winState:
            self.restoreState(winState)

        self.autoUpdate()

    def createStatusBar(self):
        self.collectionFileLabel = QLabel()
        self.statusBar().addWidget(self.collectionFileLabel)

    def __updateLatest(self, menu=None):
        if menu:
            self.__menu = menu
        for act in self.latestActions:
            self.__menu.removeAction(act)

        self.latestActions = []
        latest = LatestCollections(self)
        for act in latest.actions():
            self.latestActions.append(act)
            act.triggered.connect(self.openLatestCollectionEvent)
            self.__menu.insertAction(self.exitAct, act)
        self.__menu.insertSeparator(self.exitAct)

    def cancelFilteringEvent(self):
        self.quickSearch.clear()

        listView = self.viewTab.currentListView()
        listView.clearAllFilters()

    def cancelSortingEvent(self):
        listView = self.viewTab.currentListView()
        listView.clearSorting()

    def saveSortingEvent(self):
        listView = self.viewTab.currentListView()
        listView.saveSorting()

    def enableDragEvent(self):
        listView = self.viewTab.currentListView()
        if self.enableDragAct.isChecked():
            res = listView.tryDragMode()
            self.enableDragAct.setChecked(res)
        else:
            self.enableDragAct.setChecked(False)
            listView.selectMode()

    def settingsEvent(self):
        dialog = SettingsDialog(self.collection, self)
        res = dialog.exec_()
        if res == QDialog.Accepted:
            result = QMessageBox.question(self, self.tr("Settings"),
                        self.tr("The application will need to restart to apply "
                                "the new settings. Restart it now?"),
                        QMessageBox.Yes | QMessageBox.No,
                        QMessageBox.Yes)
            if result == QMessageBox.Yes:
                self.restart()

    def changeViewEvent(self):
        type_ = self.sender().data()
        page = self.viewTab.currentPageView().param
        self.viewTab.collection.pages().changeView(page, type_)

        self.viewTab.clearStatusBar()
        page = self.viewTab.currentPageView()
        page.changeView(type_)
        self.viewTab.updatePage(page)

    def colnectEvent(self):
        model = self.viewTab.currentModel()
        dialog = ColnectDialog(model, self)
        dialog.exec_()

    def updateInfoType(self, info_type):
        self.detailsAct.setChecked(False)
        if statisticsAvailable:
            self.statisticsAct.setChecked(False)
        if importedQtWebKit:
            self.mapAct.setChecked(False)

        if info_type == CollectionPageTypes.Statistics:
            self.statisticsAct.setChecked(True)
        elif info_type == CollectionPageTypes.Map:
            self.mapAct.setChecked(True)
        else:
            self.detailsAct.setChecked(True)

    def detailsEvent(self, checked):
        self.updateInfoType(CollectionPageTypes.Details)
        if checked:
            page = self.viewTab.currentPageView()
            self.collection.pages().changeInfoType(page.param,
                                                   CollectionPageTypes.Details)
            page.showInfo(CollectionPageTypes.Details)

    def statisticsEvent(self, checked):
        self.updateInfoType(CollectionPageTypes.Statistics)
        if checked:
            page = self.viewTab.currentPageView()
            self.collection.pages().changeInfoType(page.param,
                                                   CollectionPageTypes.Statistics)
            page.showInfo(CollectionPageTypes.Statistics)

    def mapEvent(self, checked):
        self.updateInfoType(CollectionPageTypes.Map)
        if checked:
            page = self.viewTab.currentPageView()
            self.collection.pages().changeInfoType(page.param,
                                                   CollectionPageTypes.Map)
            page.showInfo(CollectionPageTypes.Map)

    def summaryEvent(self):
        model = self.viewTab.currentModel()

        dialog = SummaryDialog(model, self)