#!/usr/bin/env python

__license__   = 'GPL v3'
__copyright__ = '2022, Thiago Oliveira <thiago.eec@gmail.com>'
__docformat__ = 'restructuredtext en'

# Standard libraries
import os
import locale
from functools import partial
from datetime import datetime, timedelta

# PyQt libraries
try:
    from qt.core import (Qt, QtCore, QWidget, QLabel, QLineEdit, QPushButton, QGroupBox, QTabWidget, QPixmap,
                         QVBoxLayout, QGridLayout, QComboBox, QDialogButtonBox, QDialog, QCheckBox)
except ImportError:
    from PyQt5.Qt import (Qt, QWidget, QLabel, QLineEdit, QPushButton, QGroupBox, QTabWidget, QPixmap,
                          QVBoxLayout, QGridLayout, QComboBox, QDialogButtonBox, QDialog, QCheckBox)
    from PyQt5 import QtCore

# Calibre libraries
from calibre.utils.filenames import expanduser
from calibre.gui2 import choose_dir, error_dialog, gprefs
from calibre.gui2.keyboard import ShortcutConfig
from calibre.gui2.ui import get_gui
from calibre_plugins.Check_Books.utils import get_icon, get_arch, prefs, numeric_version
from calibre_plugins.Check_Books.__init__ import PLUGIN_NAME, PLUGIN_VERSION

# Check if the installed calibre version supports
# creating custom columns from within the plugin
SUPPORTS_CREATE_CUSTOM_COLUMN = False
try:
    from calibre.gui2.preferences.create_custom_column import CreateNewCustomColumn
    SUPPORTS_CREATE_CUSTOM_COLUMN = True
except ImportError as e:
    SUPPORTS_CREATE_CUSTOM_COLUMN = False

# Load translation files (.mo) on the folder 'translations'
load_translations()

# Custom columns map
DEFAULT_LOOKUP_ACE_REPORT_COLUMN        = '#ace_report'
DEFAULT_LOOKUP_EPUBCHECK_REPORT_COLUMN  = '#epubcheck_report'
DEFAULT_LOOKUP_ACE_RESULT_COLUMN        = '#ace_result'
DEFAULT_LOOKUP_EPUBCHECK_RESULT_COLUMN  = '#epubcheck_result'
DEFAULTS = {
                DEFAULT_LOOKUP_ACE_REPORT_COLUMN : {
                    'column_heading': _('ACE report'),
                    'datatype': 'comments',
                    'description': _('ACE report'),
                    'heading_position': 'side',
                    'interpret_as': 'html',
                },
                DEFAULT_LOOKUP_ACE_RESULT_COLUMN : {
                    'column_heading': _('ACE result'),
                    'datatype': 'bool',
                    'description': _('ACE result'),
                },
                DEFAULT_LOOKUP_EPUBCHECK_REPORT_COLUMN : {
                    'column_heading': _('EPUBCheck report'),
                    'datatype': 'comments',
                    'description': _('EPUBCheck report'),
                    'heading_position': 'side',
                    'interpret_as': 'html',
                },
                DEFAULT_LOOKUP_EPUBCHECK_RESULT_COLUMN : {
                    'column_heading': _('EPUBCheck result'),
                    'datatype': 'bool',
                    'description': _('EPUBCheck result'),
                },
            }

# Get user language
user_language = locale.getdefaultlocale()

# Set default preferences
prefs.defaults['report_path'] = os.path.join(expanduser('~'), 'Check Books')
prefs.defaults['calibre_library_folders'] = False
prefs.defaults['main_action_idx'] = 0
prefs.defaults['threads_idx'] = os.cpu_count()-2
prefs.defaults['user_lang'] = user_language[0]
prefs.defaults['ace_update'] = True
prefs.defaults['ace_check_interval'] = 7
prefs.defaults['ace_last_time_checked'] = str(datetime.now() - timedelta(days=7))
prefs.defaults['ace_report_format'] = 'JSON'
prefs.defaults['ace_report_column'] = ''
prefs.defaults['ace_result_column'] = ''
prefs.defaults['epubcheck_update'] = True
prefs.defaults['epubcheck_32bits'] = False
prefs.defaults['epubcheck_check_interval'] = 7
prefs.defaults['epubcheck_last_time_checked'] = str(datetime.now() - timedelta(days=7))
prefs.defaults['epubcheck_report_format'] = 'JSON'
prefs.defaults['epubcheck_report_column'] = ''
prefs.defaults['epubcheck_result_column'] = ''
prefs.defaults['epubcheck_usage'] = False
prefs.defaults['epubcheck_java_path'] = 'java'


# Set up Config Dialog
class ConfigWidget(QWidget):

    def __init__(self, gui, plugin_action, tab=0):
        QWidget.__init__(self)
        self.gui = get_gui()
        self.plugin_action = plugin_action
        layout = QVBoxLayout(self)
        self.setLayout(layout)

        tab_widget = QTabWidget(self)
        layout.addWidget(tab_widget)

        self.main_tab = MainTab(self)
        self.ace_tab = ACETab(self)
        self.epubcheck_tab = EPUBCheckTab(self)
        tab_widget.addTab(self.main_tab, _('&General'))
        tab_widget.addTab(self.ace_tab, _('&ACE'))
        tab_widget.addTab(self.epubcheck_tab, _('&EPUBCheck'))
        tab_widget.setCurrentIndex(tab)

        self.supports_create_custom_column = SUPPORTS_CREATE_CUSTOM_COLUMN
        self.must_restart = False
        self._get_create_new_custom_column_instance = None

        self.resize(self.sizeHint())

    def refresh_widget(self):
        self.key_calibre_library_folders = prefs['calibre_library_folders']
        calibre_library_folders_choice = self.main_tab.calibre_libary_folders_check.isChecked()
        if calibre_library_folders_choice:
            self.main_tab.directory_txtBox.setEnabled(not calibre_library_folders_choice)
            self.main_tab.directory_button.setEnabled(not calibre_library_folders_choice)
        else:
            self.main_tab.directory_txtBox.setEnabled(not calibre_library_folders_choice)
            self.main_tab.directory_button.setEnabled(not calibre_library_folders_choice)
        self.update()

    def edit_shortcuts(self):
        d = KeyboardConfigDialog(self.plugin_action.gui, PLUGIN_NAME)
        if d.exec_() == d.Accepted:
            self.plugin_action.gui.keyboard.finalize()

    def get_custom_columns(self, column_types):
        custom_columns = self.gui.library_view.model().custom_columns
        available_columns = {}
        for key, column in custom_columns.items():
            typ = column['datatype']
            if typ in column_types:
                available_columns[key] = column
        return available_columns

    def create_custom_column(self, combo_box, custom_columns, lookup_name=None, is_multiple=False):
        display_params = {
            'description': DEFAULTS[lookup_name]['description'],
        }
        datatype = DEFAULTS[lookup_name]['datatype']
        if datatype == 'comments':
            display_params['heading_position'] = DEFAULTS[lookup_name]['heading_position']
            display_params['interpret_as'] = DEFAULTS[lookup_name]['interpret_as']
        column_heading = DEFAULTS[lookup_name]['column_heading']
        new_lookup_name = lookup_name
        create_new_custom_column_instance = self.get_create_new_custom_column_instance()
        result = create_new_custom_column_instance.create_column(new_lookup_name, column_heading, datatype,
                                                                 is_multiple,
                                                                 display=display_params,
                                                                 generate_unused_lookup_name=True,
                                                                 freeze_lookup_name=False)
        if result[0] == CreateNewCustomColumn.Result.COLUMN_ADDED:
            combo_box.populate_combo(custom_columns, result[1], column_heading)
            self.gui.must_restart = True

    # @property
    def get_create_new_custom_column_instance(self):
        if self._get_create_new_custom_column_instance is None and self.supports_create_custom_column:
            self._get_create_new_custom_column_instance = CreateNewCustomColumn(self.gui)
        return self._get_create_new_custom_column_instance

    def about(self):
        # About text
        text = '<hr/>' \
               '<p>' + _('Created by Thiago Oliveira') + '</p>' \
                '<p>' + _('This plugin was built from ACE and EPUBCheck calibre plugins. '
                          'It provides a way to check your whole library and save your reports.') + '</p>'
        icon = 'images/icon.png'
        about_text = "{0}{1}".format(PLUGIN_NAME + ' v' + PLUGIN_VERSION, text)
        AboutDialog(self.gui, get_icon(icon), about_text).exec_()

    def get_directory(self):
        c = choose_dir(self, PLUGIN_NAME + 'dir_chooser',
                       _('Select a folder to save the report to'))
        if c:
            self.main_tab.directory_txtBox.setReadOnly(False)
            self.main_tab.directory_txtBox.setText(c)
            self.main_tab.directory_txtBox.setReadOnly(True)

    # def validate(self):
    #     # This is just to catch the situation where someone might
    #     # manually enter a non-existent path in the Default path textbox.
    #     # Shouldn't be possible at this point.
    #     if not os.path.exists(self.main_tab.directory_txtBox.text()):
    #         errmsg = _('<p>The path specified for the report folder does not exist.'
    #                    '<br/>Your latest preference changes will <b>NOT</b> be saved!</p>')
    #         error_dialog(None, PLUGIN_NAME + ' v' + PLUGIN_VERSION,
    #                      errmsg, show=True)
    #         return False
    #     return True

    # TODO: Verify the update check on the first run. Seems like epucheck.jar is not unziped yet at the time.
    def save_settings(self):
        # Save current dialog settings back to JSON config file
        prefs['report_path'] = str(self.main_tab.directory_txtBox.displayText())
        if numeric_version >= (6, 16, 0):
            prefs['calibre_library_folders'] = self.main_tab.calibre_libary_folders_check.isChecked()
        prefs['main_action_idx'] = self.main_tab.main_action_combo.currentIndex()
        prefs['threads_idx'] = self.main_tab.threads_combo.currentIndex()
        prefs['user_lang'] = self.main_tab.language_combo.currentText()
        prefs['ace_update'] = self.ace_tab.update_check.isChecked()
        prefs['ace_check_interval'] = int(self.ace_tab.check_interval_txtBox.text())
        prefs['ace_report_format'] = self.ace_tab.ace_report_combo.currentText()
        prefs['ace_report_column'] = self.ace_tab._ace_report_column_combo.get_selected_column()
        prefs['ace_result_column'] = self.ace_tab._ace_result_column_combo.get_selected_column()
        prefs['epubcheck_update'] = self.epubcheck_tab.update_check.isChecked()
        prefs['epubcheck_check_interval'] = int(self.epubcheck_tab.check_interval_txtBox.text())
        prefs['epubcheck_report_format'] = self.epubcheck_tab.epubcheck_report_combo.currentText()
        prefs['epubcheck_report_column'] = self.epubcheck_tab._epubcheck_report_column_combo.get_selected_column()
        prefs['epubcheck_result_column'] = self.epubcheck_tab._epubcheck_result_column_combo.get_selected_column()
        prefs['epubcheck_usage'] = self.epubcheck_tab.usage_check.isChecked()
        prefs['epubcheck_java_path'] = self.epubcheck_tab.java_path_txtBox.text()
        prefs['epubcheck_32bits'] = self.epubcheck_tab.java_bit_check.isChecked()


class MainTab(QWidget):

    def __init__(self, parent_dialog):
        self.parent_dialog = parent_dialog
        QWidget.__init__(self)
        layout = QGridLayout()
        self.setLayout(layout)

        # --- Folder Options ---
        directory_group_box = QGroupBox(_('Report folder:'), self)
        layout.addWidget(directory_group_box)
        directory_group_box_layout = QVBoxLayout()
        directory_group_box.setLayout(directory_group_box_layout)

        # Directory path Textbox
        # Load the textbox with the current preference setting
        self.directory_txtBox = QLineEdit(prefs['report_path'], self)
        self.directory_txtBox.setToolTip(_('Folder to save the report'))
        directory_group_box_layout.addWidget(self.directory_txtBox)
        self.directory_txtBox.setReadOnly(True)

        # Folder select button
        self.directory_button = QPushButton(_('&Select report folder'), self)
        self.directory_button.setToolTip(_('Select the folder where the report will be saved'))
        directory_group_box_layout.addWidget(self.directory_button)
        # Connect button to the getDirectory function
        self.directory_button.clicked.connect(self.parent_dialog.get_directory)

        # Use calibre library folders checkbox
        if numeric_version >= (6, 16, 0):
            self.calibre_libary_folders_check = QCheckBox(_('&Use calibre folders'), self)
            self.calibre_libary_folders_check.setToolTip(_('Reports will be saved on the book\'s "data" folder'))
            directory_group_box_layout.addWidget(self.calibre_libary_folders_check)
            # Load the checkbox with the current preference setting
            self.calibre_libary_folders_check.setChecked(prefs['calibre_library_folders'])
            self.calibre_libary_folders_check.stateChanged.connect(self.parent_dialog.refresh_widget)
            calibre_library_folders_choice = self.calibre_libary_folders_check.isChecked()
            if calibre_library_folders_choice:
                self.directory_txtBox.setEnabled(not calibre_library_folders_choice)
                self.directory_button.setEnabled(not calibre_library_folders_choice)
            else:
                self.directory_txtBox.setEnabled(not calibre_library_folders_choice)
                self.directory_button.setEnabled(not calibre_library_folders_choice)

        # --- Misc Options ---
        misc_group_box = QGroupBox(_('Options:'), self)
        layout.addWidget(misc_group_box)
        misc_group_box_layout = QGridLayout()
        misc_group_box.setLayout(misc_group_box_layout)

        # Main action combobox
        self.main_action_box_label = QLabel(_('&Main button:'), self)
        tooltip = _('Select which action will be used for the main button on calibre GUI')
        self.main_action_box_label.setToolTip(tooltip)
        self.main_action_combo = QComboBox()
        self.main_action_combo.setToolTip(tooltip)
        self.main_action_combo.setMinimumWidth(120)
        self.main_action_combo.addItems({_('ACE'), _('EPUBCheck')})
        self.main_action_combo.model().sort(0)
        self.main_action_box_label.setBuddy(self.main_action_combo)
        misc_group_box_layout.addWidget(self.main_action_box_label, 0, 0)
        misc_group_box_layout.addWidget(self.main_action_combo, 0, 1)
        self.main_action_combo.setCurrentIndex(prefs['main_action_idx'])

        # Parallel jobs combobox
        self.threads_box_label = QLabel(_('&Threads:'), self)
        tooltip = _('Select how many threads (parallel jobs) should be spawned')
        self.threads_box_label.setToolTip(tooltip)
        self.threads_combo = QComboBox()
        self.threads_combo.setToolTip(tooltip)
        self.threads_combo.setMinimumWidth(120)
        threads = list(map(str, range(1, os.cpu_count() + 1)))
        self.threads_combo.addItems(threads)
        self.threads_combo.model().sort(0)
        self.threads_box_label.setBuddy(self.threads_combo)
        misc_group_box_layout.addWidget(self.threads_box_label, 1, 0)
        misc_group_box_layout.addWidget(self.threads_combo, 1, 1)
        self.threads_combo.setCurrentIndex(prefs['threads_idx'])

        # Language combobox
        self.language_box_label = QLabel(_('&Language:'), self)
        tooltip = _('Choose the language for ACE and EPUBCheck reports. '
                    'Some languages may not work for both, in which case English is used.')
        self.language_box_label.setToolTip(tooltip)
        self.language_combo = QComboBox()
        self.language_combo.setToolTip(tooltip)
        self.language_combo.addItems({'da', 'de', 'en', 'es', 'eu', 'fr', 'it', 'ja',
                                      'ko_KR', 'nl', 'pl', 'pt_BR', 'zh_TW'})
        self.language_combo.model().sort(0)
        self.language_box_label.setBuddy(self.language_combo)
        misc_group_box_layout.addWidget(self.language_box_label, 2, 0)
        misc_group_box_layout.addWidget(self.language_combo, 2, 1)
        # Load the combobox with the current preference setting
        default_index = self.language_combo.findText(prefs['user_lang'])
        # Check if the user language is available. If not, fallbacks to English.
        if default_index == -1:
            self.language_combo.setCurrentText('en')
        else:
            self.language_combo.setCurrentIndex(default_index)

        # Keyboard shortcuts button
        self.keyboard_shortcuts_button = QPushButton(_('&Keyboard shortcuts'), self)
        self.keyboard_shortcuts_button.setToolTip(_('Edit the keyboard shortcuts associated with this plugin'))
        self.keyboard_shortcuts_button.clicked.connect(self.parent_dialog.edit_shortcuts)
        layout.addWidget(self.keyboard_shortcuts_button, 3, 0, 1, 1)

        # About button
        self.about_button = QPushButton(_('A&bout'), self)
        self.about_button.clicked.connect(self.parent_dialog.about)
        layout.addWidget(self.about_button)


class ACETab(QWidget):

    def __init__(self, parent_dialog):
        self.parent_dialog = parent_dialog
        QWidget.__init__(self)
        layout = QGridLayout()
        self.setLayout(layout)
        self.add_icon = get_icon('images/add_column.png')
        self.supports_create_custom_column = SUPPORTS_CREATE_CUSTOM_COLUMN

        # --- Options ---
        options_group_box = QGroupBox(_('Options:'), self)
        layout.addWidget(options_group_box)
        options_group_box_layout = QGridLayout()
        options_group_box.setLayout(options_group_box_layout)

        # ACE report format combobox
        self.ace_report_box_label = QLabel(_('&Report format:'), self)
        tooltip = _('Choose the preferred output for ACE reports')
        self.ace_report_box_label.setToolTip(tooltip)
        self.ace_report_combo = QComboBox()
        self.ace_report_combo.setMaximumWidth(110)
        self.ace_report_combo.setToolTip(tooltip)
        self.ace_report_combo.addItems({'JSON', 'HTML', 'Both'})
        self.ace_report_combo.model().sort(0, Qt.DescendingOrder)
        self.ace_report_box_label.setBuddy(self.ace_report_combo)
        options_group_box_layout.addWidget(self.ace_report_box_label, 0, 0)
        options_group_box_layout.addWidget(self.ace_report_combo, 0, 1)
        self.ace_report_combo.setCurrentText(prefs['ace_report_format'])

        # Check interval line edit
        self.check_interval_txtBox_label = QLabel(_('&Check interval:'), self)
        tooltip = _('Update check interval')
        self.check_interval_txtBox_label.setToolTip(tooltip)
        # Load the textbox with the current preference setting
        self.check_interval_txtBox = QLineEdit(str(prefs['ace_check_interval']), self)
        self.check_interval_txtBox.setAlignment(QtCore.Qt.AlignRight)
        self.check_interval_txtBox.setMaximumWidth(110)
        self.check_interval_txtBox.setToolTip(tooltip)
        self.check_interval_txtBox_label.setBuddy(self.check_interval_txtBox)
        options_group_box_layout.addWidget(self.check_interval_txtBox_label, 1, 0)
        options_group_box_layout.addWidget(self.check_interval_txtBox, 1, 1)

        # Update checkbox
        self.update_check = QCheckBox(_('Auto &update'), self)
        self.update_check.setToolTip(_('The plugin will attempt to update local ACE installation'))
        options_group_box_layout.addWidget(self.update_check, 2, 0)
        # Load the checkbox with the current preference setting
        self.update_check.setChecked(prefs['ace_update'])

        # --- Custom columns ---
        ace_group_box = QGroupBox(_('Custom columns:'), self)
        layout.addWidget(ace_group_box)
        ace_group_box_layout = QGridLayout()
        ace_group_box.setLayout(ace_group_box_layout)

        # ACE report link custom column
        self.ace_report_column_box_label = QLabel(_('Re&port:'), self)
        tooltip = _('Select a custom column to link the ACE report. Type: long text, like comments.')
        self.ace_report_column_box_label.setToolTip(tooltip)
        self.ace_report_column_combo = QComboBox()
        ace_report_custom_columns = self.parent_dialog.get_custom_columns(['comments'])
        self._ace_report_column_combo = CustomColumnComboBox(self, ace_report_custom_columns, initial_items=[''])
        self._ace_report_column_combo.setToolTip(tooltip)
        self._ace_report_column_combo.setMinimumWidth(150)
        self.ace_report_column_box_label.setBuddy(self._ace_report_column_combo)
        ace_group_box_layout.addWidget(self.ace_report_column_box_label, 0, 0)
        ace_group_box_layout.addWidget(self._ace_report_column_combo, 0, 1)
        self._ace_report_column_combo_index = self._ace_report_column_combo \
            .findText(prefs['ace_report_column'], QtCore.Qt.MatchStartsWith)
        if self._ace_report_column_combo_index == -1:
            self._ace_report_column_combo.setCurrentIndex(0)
        else:
            self._ace_report_column_combo.setCurrentIndex(self._ace_report_column_combo_index)

        # ACE report link - add custom column button
        if self.supports_create_custom_column:
            self.ace_report_add_button = QPushButton(self.add_icon, '', self)
            self.ace_report_add_button.setToolTip(_('Create new column for ACE report'))
            self.ace_report_add_button.setMaximumWidth(50)
            ace_group_box_layout.addWidget(self.ace_report_add_button, 0, 2)
            self.ace_report_add_button.clicked.connect(partial(self.parent_dialog.create_custom_column,
                                                               self._ace_report_column_combo,
                                                               ace_report_custom_columns,
                                                               DEFAULT_LOOKUP_ACE_REPORT_COLUMN, False))

        # ACE result custom column
        self.ace_result_column_box_label = QLabel(_('Re&sult:'), self)
        tooltip = _('Select a custom column to store ACE result. Type: yes/no.')
        self.ace_result_column_box_label.setToolTip(tooltip)
        self.ace_result_column_combo = QComboBox()
        ace_result_custom_columns = self.parent_dialog.get_custom_columns(['bool'])
        self._ace_result_column_combo = CustomColumnComboBox(self, ace_result_custom_columns, initial_items=[''])
        self._ace_result_column_combo.setToolTip(tooltip)
        self._ace_result_column_combo.setMinimumWidth(150)
        self.ace_result_column_box_label.setBuddy(self._ace_result_column_combo)
        ace_group_box_layout.addWidget(self.ace_result_column_box_label, 1, 0)
        ace_group_box_layout.addWidget(self._ace_result_column_combo, 1, 1)
        self._ace_result_column_combo_index = self._ace_result_column_combo \
            .findText(prefs['ace_result_column'], QtCore.Qt.MatchStartsWith)
        if self._ace_result_column_combo_index == -1:
            self._ace_result_column_combo.setCurrentIndex(0)
        else:
            self._ace_result_column_combo.setCurrentIndex(self._ace_result_column_combo_index)

        # ACE result - add custom column button
        if self.supports_create_custom_column:
            self.ace_result_add_button = QPushButton(self.add_icon, '', self)
            self.ace_result_add_button.setToolTip(_('Create new column for ACE result'))
            self.ace_result_add_button.setMaximumWidth(50)
            ace_group_box_layout.addWidget(self.ace_result_add_button, 1, 2)
            self.ace_result_add_button.clicked.connect(partial(self.parent_dialog.create_custom_column,
                                                               self._ace_result_column_combo,
                                                               ace_result_custom_columns,
                                                               DEFAULT_LOOKUP_ACE_RESULT_COLUMN, False))


class EPUBCheckTab(QWidget):

    def __init__(self, parent_dialog):
        self.parent_dialog = parent_dialog
        QWidget.__init__(self)
        layout = QGridLayout()
        self.setLayout(layout)
        self.add_icon = get_icon('images/add_column.png')
        self.supports_create_custom_column = SUPPORTS_CREATE_CUSTOM_COLUMN

        # --- Options ---
        options_group_box = QGroupBox(_('Options:'), self)
        layout.addWidget(options_group_box)
        options_group_box_layout = QGridLayout()
        options_group_box.setLayout(options_group_box_layout)

        # EPUBCheck report format combobox
        self.epubcheck_report_box_label = QLabel(_('&Report format:'), self)
        tooltip = _('Choose the preferred output for EPUBCheck reports')
        self.epubcheck_report_box_label.setToolTip(tooltip)
        self.epubcheck_report_combo = QComboBox()
        self.epubcheck_report_combo.setMaximumWidth(110)
        self.epubcheck_report_combo.setToolTip(tooltip)
        self.epubcheck_report_combo.addItems({'JSON', 'XML', 'XMP'})
        self.epubcheck_report_combo.model().sort(0)
        self.epubcheck_report_box_label.setBuddy(self.epubcheck_report_combo)
        options_group_box_layout.addWidget(self.epubcheck_report_box_label, 0, 0)
        options_group_box_layout.addWidget(self.epubcheck_report_combo, 0, 1)
        self.epubcheck_report_combo.setCurrentText(prefs['epubcheck_report_format'])

        # Check interval line edit
        self.check_interval_txtBox_label = QLabel(_('&Check interval:'), self)
        tooltip = _('Update check interval')
        self.check_interval_txtBox_label.setToolTip(tooltip)
        # Load the textbox with the current preference setting
        self.check_interval_txtBox = QLineEdit(str(prefs['epubcheck_check_interval']), self)
        self.check_interval_txtBox.setAlignment(QtCore.Qt.AlignRight)
        self.check_interval_txtBox.setToolTip(tooltip)
        self.check_interval_txtBox.setMaximumWidth(110)
        self.check_interval_txtBox_label.setBuddy(self.check_interval_txtBox)
        options_group_box_layout.addWidget(self.check_interval_txtBox_label, 1, 0)
        options_group_box_layout.addWidget(self.check_interval_txtBox, 1, 1)

        # Java path line edit
        # Load the textbox with the current preference setting
        self.java_path_txtBox_label = QLabel(_('&Java path:'), self)
        tooltip = _('Select Java local installation path')
        self.java_path_txtBox_label.setToolTip(tooltip)
        self.java_path_txtBox = QLineEdit(str(prefs['epubcheck_java_path']), self)
        self.java_path_txtBox.setAlignment(QtCore.Qt.AlignRight)
        self.java_path_txtBox.setToolTip(tooltip)
        self.java_path_txtBox.setMaximumWidth(110)
        self.java_path_txtBox_label.setBuddy(self.java_path_txtBox)
        options_group_box_layout.addWidget(self.java_path_txtBox_label, 2, 0)
        options_group_box_layout.addWidget(self.java_path_txtBox, 2, 1)

        # Update checkbox
        self.update_check = QCheckBox(_('Auto &update'), self)
        self.update_check.setToolTip(_('The plugin will attempt to update its embedded version of EPUBCheck'))
        options_group_box_layout.addWidget(self.update_check, 3, 0)
        # Load the checkbox with the current preference setting
        self.update_check.setChecked(prefs['epubcheck_update'])

        # Java architecture
        self.java_bit_check = QCheckBox(_('32 &bits Java'), self)
        self.java_bit_check.setToolTip(_('Uses a 32 bits Java install'))
        options_group_box_layout.addWidget(self.java_bit_check, 3, 1)
        # Load the checkbox with the current preference setting
        if prefs == {}:
            arch = get_arch(prefs['epubcheck_java_path'])
            if arch is None:
                print(_('Java architecture not detected!'))
            else:
                self.java_bit_check.setChecked(arch == '32')
                prefs.set('is32bit', arch == '32')
                prefs.commit()
        else:
            self.java_bit_check.setChecked(prefs['epubcheck_32bits'])

        # Usage messages
        self.usage_check = QCheckBox(_('USAGE &messages'), self)
        self.usage_check.setToolTip(_('Include USAGE messages in report'))
        options_group_box_layout.addWidget(self.usage_check, 4, 0)
        # Load the checkbox with the current preference setting
        self.usage_check.setChecked(prefs['epubcheck_usage'])

        # --- Custom columns ---
        custom_columns_group_box = QGroupBox(_('Custom columns:'), self)
        layout.addWidget(custom_columns_group_box)
        custom_columns_group_box_layout = QGridLayout()
        custom_columns_group_box.setLayout(custom_columns_group_box_layout)

        # EPUBCheck report link custom column
        self.epubcheck_report_column_box_label = QLabel(_('Re&port:'), self)
        tooltip = _('Select a custom column to link the EPUBCheck report. Type: long text, like comments.')
        self.epubcheck_report_column_box_label.setToolTip(tooltip)
        self.epubcheck_report_column_combo = QComboBox()
        epubcheck_report_custom_columns = self.parent_dialog.get_custom_columns(['comments'])
        self._epubcheck_report_column_combo = CustomColumnComboBox(self, epubcheck_report_custom_columns, initial_items=[''])
        self._epubcheck_report_column_combo.setToolTip(tooltip)
        self._epubcheck_report_column_combo.setMinimumWidth(150)
        self.epubcheck_report_column_box_label.setBuddy(self._epubcheck_report_column_combo)
        custom_columns_group_box_layout.addWidget(self.epubcheck_report_column_box_label, 0, 0)
        custom_columns_group_box_layout.addWidget(self._epubcheck_report_column_combo, 0, 1)
        self._epubcheck_report_column_combo_index = self._epubcheck_report_column_combo \
            .findText(prefs['epubcheck_report_column'], QtCore.Qt.MatchStartsWith)
        if self._epubcheck_report_column_combo_index == -1:
            self._epubcheck_report_column_combo.setCurrentIndex(0)
        else:
            self._epubcheck_report_column_combo.setCurrentIndex(self._epubcheck_report_column_combo_index)

        # EPUBCheck report link - add custom column button
        if self.supports_create_custom_column:
            self.epubcheck_report_add_button = QPushButton(self.add_icon, '', self)
            self.epubcheck_report_add_button.setToolTip(_('Create new column for EPUBCheck report'))
            self.epubcheck_report_add_button.setMaximumWidth(50)
            custom_columns_group_box_layout.addWidget(self.epubcheck_report_add_button, 0, 2)
            self.epubcheck_report_add_button.clicked.connect(partial(self.parent_dialog.create_custom_column,
                                                               self._epubcheck_report_column_combo,
                                                               epubcheck_report_custom_columns,
                                                               DEFAULT_LOOKUP_EPUBCHECK_REPORT_COLUMN, False))

        # EPUBCheck result custom column
        self.epubcheck_result_column_box_label = QLabel(_('Re&sult:'), self)
        tooltip = _('Select a custom column to link the EPUBCheck result. Type: yes/no.')
        self.epubcheck_result_column_box_label.setToolTip(tooltip)
        self.epubcheck_result_column_combo = QComboBox()
        epubcheck_result_custom_columns = self.parent_dialog.get_custom_columns(['bool'])
        self._epubcheck_result_column_combo = CustomColumnComboBox(self, epubcheck_result_custom_columns,
                                                                   initial_items=[''])
        self._epubcheck_result_column_combo.setToolTip(tooltip)
        self._epubcheck_result_column_combo.setMinimumWidth(150)
        self.epubcheck_result_column_box_label.setBuddy(self._epubcheck_result_column_combo)
        custom_columns_group_box_layout.addWidget(self.epubcheck_result_column_box_label, 1, 0)
        custom_columns_group_box_layout.addWidget(self._epubcheck_result_column_combo, 1, 1)
        self._epubcheck_result_column_combo_index = self._epubcheck_result_column_combo \
            .findText(prefs['epubcheck_result_column'], QtCore.Qt.MatchStartsWith)
        if self._epubcheck_result_column_combo_index == -1:
            self._epubcheck_result_column_combo.setCurrentIndex(0)
        else:
            self._epubcheck_result_column_combo.setCurrentIndex(self._epubcheck_result_column_combo_index)

        # EPUBCheck result - add custom column button
        if self.supports_create_custom_column:
            self.epubcheck_result_add_button = QPushButton(self.add_icon, '', self)
            self.epubcheck_result_add_button.setToolTip(_('Create new column for EPUBCheck result'))
            self.epubcheck_result_add_button.setMaximumWidth(50)
            custom_columns_group_box_layout.addWidget(self.epubcheck_result_add_button, 1, 2)
            self.epubcheck_result_add_button.clicked.connect(partial(self.parent_dialog.create_custom_column,
                                                                     self._epubcheck_result_column_combo,
                                                                     epubcheck_result_custom_columns,
                                                                     DEFAULT_LOOKUP_EPUBCHECK_RESULT_COLUMN, False))


class CustomColumnComboBox(QComboBox):

    def __init__(self, parent, custom_columns={}, selected_column='', initial_items=[''], new_column_created=[]):
        QComboBox.__init__(self, parent)
        self.populate_combo(custom_columns, selected_column, new_column_created, initial_items)

    def populate_combo(self, custom_columns, selected_column, new_colum_created=[], initial_items=['']):
        self.clear()
        self.column_names = list(initial_items)
        if len(initial_items) > 0:
            self.addItems(initial_items)
        selected_idx = 0
        for idx, value in enumerate(initial_items):
            if value == selected_column:
                selected_idx = idx

        # Add new column created to combobox
        if new_colum_created:
            custom_columns.update({selected_column: {'name': new_colum_created}})

        for key in sorted(custom_columns.keys()):
            self.column_names.append(key)
            self.addItem('%s (%s)' % (key, custom_columns[key]['name']))
            if key == selected_column:
                selected_idx = len(self.column_names) - 1
        self.setCurrentIndex(selected_idx)

    def get_selected_column(self):
        return self.column_names[self.currentIndex()]


class SizePersistedDialog(QDialog):
    '''
    This dialog is a base class for any dialogs that want their size/position
    restored when they are next opened.
    '''
    def __init__(self, parent, unique_pref_name):
        QDialog.__init__(self, parent)
        self.unique_pref_name = unique_pref_name
        self.geom = gprefs.get(unique_pref_name, None)
        self.finished.connect(self.dialog_closing)

    def resize_dialog(self):
        if self.geom is None:
            self.resize(self.sizeHint())
        else:
            self.restoreGeometry(self.geom)

    def dialog_closing(self, result):
        geom = bytearray(self.saveGeometry())
        gprefs[self.unique_pref_name] = geom
        self.persist_custom_prefs()

    def persist_custom_prefs(self):
        '''
        Invoked when the dialog is closing. Override this function to call
        save_custom_pref() if you have a setting you want persisted that you can
        retrieve in your __init__() using load_custom_pref() when next opened
        '''
        pass

    def load_custom_pref(self, name, default=None):
        return gprefs.get(self.unique_pref_name+':'+name, default)

    def save_custom_pref(self, name, value):
        gprefs[self.unique_pref_name+':'+name] = value


class KeyboardConfigDialog(SizePersistedDialog):
    '''
    This dialog is used to allow editing of keyboard shortcuts.
    '''
    def __init__(self, gui, group_name):
        SizePersistedDialog.__init__(self, gui, 'Keyboard shortcut dialog')
        self.gui = gui
        self.setWindowTitle(_('Keyboard shortcuts'))
        self.setWindowIcon(get_icon('images/icon.png'))
        layout = QVBoxLayout(self)
        self.setLayout(layout)

        self.keyboard_widget = ShortcutConfig(self)
        layout.addWidget(self.keyboard_widget)
        self.group_name = group_name

        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.commit)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)

        # Cause our dialog size to be restored from prefs or created on first usage
        self.resize_dialog()
        self.initialize()

    def initialize(self):
        self.keyboard_widget.initialize(self.gui.keyboard)
        self.keyboard_widget.highlight_group(self.group_name)

    def commit(self):
        self.keyboard_widget.commit()
        self.accept()


class AboutDialog(QDialog):

    def __init__(self, parent, icon, text):
        QDialog.__init__(self, parent)
        self.resize(480, 250)
        self.l = QGridLayout()
        self.setLayout(self.l)
        self.logo = QLabel()
        self.logo.setMaximumWidth(110)
        self.logo.setPixmap(QPixmap(icon.pixmap(100, 100)))
        self.label = QLabel(text)
        self.label.setOpenExternalLinks(True)
        self.label.setWordWrap(True)
        self.setWindowTitle(_('About ') + PLUGIN_NAME)
        self.setWindowIcon(icon)
        self.l.addWidget(self.logo, 0, 0)
        self.l.addWidget(self.label, 0, 1)
        self.bb = QDialogButtonBox(self)
        b = self.bb.addButton(_("OK"), self.bb.AcceptRole)
        b.setDefault(True)
        self.l.addWidget(self.bb, 2, 0, 1, -1)
        self.bb.accepted.connect(self.accept)
