#-*- coding:utf-8 -*-

#  Pybik -- A 3 dimensional magic cube game.
#  Copyright © 2009, 2011-2013  B. Clausius <barcc@gmx.de>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Ported from GNUbik
# Original filename: menus.c
# Original copyright and license: 2003, 2004  John Darrington,  GPL3+

# pylint: disable=C0321


import os
import re

# pylint: disable=W0614,W0401
from PyQt4.QtCore import *
from PyQt4.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
from PyQt4.QtGui import *

from .debug import *
# pylint: enable=W0614,W0401
from . import config
from .settings import settings
from .textures import textures
from .model import models

try:
    _
except NameError:
    _ = lambda t: t
    

class Dialog (QDialog):
    _instance = None
    _classes = []
    
    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.accepted.connect(self.on_response)
        self._ignore_changed = False
        self.bound = {}
        settings.keystore.changed.connect(self.on_settings_changed, Qt.QueuedConnection)
        
    @classmethod
    def run(cls, parent_window=None):
        if cls._instance is None:
            cls._instance = cls(parent_window)
            ret = cls._instance
            cls._classes.append(cls)
        else:
            ret = None
            cls._instance.move(cls._instance.pos())
        cls._instance.show()
        return ret
        
    @classmethod
    def delete(cls):
        while cls._classes:
            cls_ = cls._classes.pop(0)
            # pylint: disable=W0212
            cls_._instance.close()
            cls_._instance.deleteLater()
            # pylint: enable=W0212
            cls_._instance = None
            
    def bind(self, key, widget, getter, setter, signal):
        if isinstance(getter, str): getter = getattr(widget, getter)
        if isinstance(setter, str): setter = getattr(widget, setter)
        if isinstance(signal, str): signal = getattr(widget, signal)
        setter(getattr(settings, key))
        if signal is not None:
            signal.connect(lambda: self.on_widget_changed(key))
        self.bound[key] = (getter, setter, signal)
        
    @staticmethod
    def bind_reset(settings_key, button):
        button.clicked.connect(lambda: delattr(settings, settings_key))
        
    def on_response(self):
        pass
        
    def on_widget_changed(self, key):
        if self._ignore_changed:
            return
        try:
            getter = self.bound[key][0]
        except KeyError:
            return
        self._ignore_changed = True
        setattr(settings, key, getter())
        self._ignore_changed = False
        
    def on_settings_changed(self, key):
        if self._ignore_changed:
            return
        try:
            setter = self.bound[key][1]
        except KeyError:
            return
        self._ignore_changed = True
        setter(getattr(settings, key))
        self._ignore_changed = False
        
        
class ColorIconEngine (QIconEngineV2):
    def __init__(self):
        QIconEngineV2.__init__(self)
        self.color = 'black'
        
    def paint(self, painter, rect, unused_mode, unused_state):
        painter.fillRect(rect, QColor(self.color))
        
        
class ColorButton (QPushButton):
    color_changed = Signal(str)
    
    def __init__(self, replace_button):
        self._icon = ColorIconEngine()
        parent = replace_button.parentWidget()
        QPushButton.__init__(self, QIcon(self._icon), '', parent)
        height = self.iconSize().height()
        self.setIconSize(QSize(height * self.width() // self.height(), height))
        self.setSizePolicy(replace_button.sizePolicy())
        self.setObjectName(replace_button.objectName())
        
        layout = replace_button.parentWidget().layout()
        index = layout.indexOf(replace_button)
        position = layout.getItemPosition(index)
        layout.removeWidget(replace_button)
        layout.addWidget(self, *position)
        replace_button.deleteLater()
        self.clicked.connect(self.on_clicked)
        
    def set_color(self, color):
        self._icon.color = color
        self.update()
        
    def get_color(self):
        return self._icon.color
        
    def on_clicked(self):
        dialog = QColorDialog(self)
        dialog.setCurrentColor(QColor(self.get_color()))
        if dialog.exec_() == QDialog.Accepted:
            color = dialog.currentColor().name()
            self.set_color(color)
            self.color_changed.emit(color)
            
        
class ShortcutEditor (QLabel):
    key_pressed = Signal()
    
    def __init__(self, parent):
        QLabel.__init__(self, _('Press a key …'), parent)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setAutoFillBackground(True)
        self.key = None
        
    SAFE_MODIFIER_MASK = Qt.ShiftModifier | Qt.ControlModifier
    IGNORE_KEYS = [Qt.Key_Shift, Qt.Key_Control, Qt.Key_Meta, Qt.Key_Alt, Qt.Key_AltGr,
                   Qt.Key_CapsLock, Qt.Key_NumLock, Qt.Key_ScrollLock]
        
    def keyPressEvent(self, event):
        if event.key() in self.IGNORE_KEYS or event.count() != 1:
            return QLabel.keyPressEvent(self, event)
        mod = event.modifiers() & self.SAFE_MODIFIER_MASK
        key = QKeySequence(event.key() | mod).toString().split('+')
        if  event.modifiers() & Qt.KeypadModifier:
            key.insert(-1, 'KP')
        self.key = '+'.join(key)
        self.key_pressed.emit()
        
        
class ShortcutDelegate (QStyledItemDelegate):
    def __init__(self, parent):
        QStyledItemDelegate.__init__(self, parent)
        
    def createEditor(self, parent, unused_option, unused_index):
        editor = ShortcutEditor(parent)
        editor.key_pressed.connect(self.on_editor_key_pressed)
        return editor
        
    def setEditorData(self, editor, index):
        pass
        
    def setModelData(self, editor, model, index):
        if editor.key is not None:
            model.setData(index, editor.key, Qt.DisplayRole)
            
    def on_editor_key_pressed(self):
        editor = self.sender()
        self.commitData.emit(editor)
        self.closeEditor.emit(editor, QAbstractItemDelegate.NoHint)
        
        
class PreferencesDialog (Dialog):
    sample_buffers = 0
        
    def __init__(self, parent):
        Dialog.__init__(self, parent)
        self.ui = UI(self, 'preferences.ui')
        from .ui.preferences import retranslate
        retranslate(self)
        
        self.ui.button_animspeed_reset.setIcon(QIcon.fromTheme('edit-clear'))
        self.ui.button_lighting_reset.setIcon(QIcon.fromTheme('edit-clear'))
        self.ui.button_antialiasing_reset.setIcon(QIcon.fromTheme('edit-clear'))
        self.ui.button_mirror_faces_reset.setIcon(QIcon.fromTheme('edit-clear'))
        self.ui.button_movekey_add.setIcon(QIcon.fromTheme('list-add'))
        self.ui.button_movekey_remove.setIcon(QIcon.fromTheme('list-remove'))
        self.ui.button_movekey_reset.setIcon(QIcon.fromTheme('document-revert'))
        self.ui.button_color_reset.setIcon(QIcon.fromTheme('edit-clear'))
        self.ui.button_image_reset.setIcon(QIcon.fromTheme('edit-clear'))
        self.ui.button_background_color_reset.setIcon(QIcon.fromTheme('edit-clear'))
        self.ui.button_mousemode_quad.setIcon(QIcon(os.path.join(config.UI_DIR, "mousemode-quad.png")))
        self.ui.button_mousemode_ext.setIcon(QIcon(os.path.join(config.UI_DIR, "mousemode-ext.png")))
        
        self.ui.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        self.ui.label_needs_restarted.setVisible(False)
        
        # graphic tab
        self.ui.slider_animspeed.setValue(settings.draw.speed)
        self.ui.slider_animspeed.setRange(*settings.draw.speed_range)
        self.bind('draw.speed', self.ui.slider_animspeed, 'value', 'setValue', 'valueChanged')
        self.bind_reset('draw.speed', self.ui.button_animspeed_reset)
        
        self.bind('draw.lighting', self.ui.checkbox_lighting, 'isChecked', 'setChecked', 'toggled')
        self.bind_reset('draw.lighting', self.ui.button_lighting_reset)
        
        def reset_antialiasing():
            del settings.draw.samples
        self.ui.button_antialiasing_reset.clicked.connect(reset_antialiasing)
        for text in settings.draw.samples_range:
            self.ui.combobox_samples.addItem(_(text), text)
        self.ui.combobox_samples.setCurrentIndex(settings.draw.samples)
        self.bind('draw.samples', self.ui.combobox_samples,
                                  'currentIndex', 'setCurrentIndex', 'currentIndexChanged')
        
        self.ui.spinbox_mirror_faces.setRange(*settings.draw.mirror_distance_range)
        def set_mirror_faces(checked):
            self.ui.checkbox_mirror_faces.setChecked(checked)
            self.ui.spinbox_mirror_faces.setEnabled(checked)
        self.bind('draw.mirror_faces', self.ui.checkbox_mirror_faces, 'isChecked', set_mirror_faces, 'toggled')
        self.bind_reset('draw.mirror_faces', self.ui.button_mirror_faces_reset)
        self.bind('draw.mirror_distance', self.ui.spinbox_mirror_faces, 'value', 'setValue', 'valueChanged')
        self.bind_reset('draw.mirror_distance', self.ui.button_mirror_faces_reset)
        
        html = '<html><head/><body><p><span style=" color:#aa0000;">{}</span></p></body></html>'
        html = html.format(Qt.escape(_('The program needs to be restarted for the changes to take effect.')))
        self.ui.label_needs_restarted.setText(html)
        
        # mouse tab
        def set_selection_mode(unused_mode):
            if settings.draw.selection_nick == 'quadrant':
                self.ui.button_mousemode_quad.setChecked(True)
            elif settings.draw.selection_nick == 'simple':
                self.ui.button_mousemode_ext.setChecked(True)
        self.bind('draw.selection', None, None, set_selection_mode, None)
        
        # keys tab
        self.liststore_movekeys = QStandardItemModel(self)
        self.fill_liststore_movekeys()
        self.ui.listview_movekeys.setModel(self.liststore_movekeys)
        self.liststore_movekeys.itemChanged.connect(self.on_liststore_movekeys_itemChanged)
        self.shortcut_delegate = ShortcutDelegate(self.ui.listview_movekeys)
        self.ui.listview_movekeys.setItemDelegateForColumn(1, self.shortcut_delegate)
        
        # design tab
        self.image_dirname = os.environ.get('HOME', '') #TODO: Use Picture folder, Qt5 has a class for that
        self.active_face = 0
        self.liststore_faces = self.create_face_selector()
        
        self.button_color = ColorButton(self.ui.button_color)
        self.ui.button_color = None
        self.setTabOrder(self.ui.listview_faces, self.button_color)
        self.button_color.color_changed.connect(self.on_button_color_color_changed)
        self.button_background_color = ColorButton(self.ui.button_background_color)
        self.ui.button_background_color = None
        self.setTabOrder(self.ui.radiobutton_mosaic, self.button_background_color)
        
        # Only a single color, no picture or pattern
        self.ui.combobox_image.addItem(_('plain'), '')
        for filename in textures.stock_files:
            pixmap = QPixmap(textures.get_stock_pixbuf(filename))
            icon = QIcon(pixmap)
            self.ui.combobox_image.addItem(icon, '', filename)
        self.ui.combobox_image.addItem(_('select …'), '/')
        
        self.bind_reset_item('theme.face.{}.color', self.ui.button_color_reset)
        self.bind_reset_item('theme.face.{}.image', self.ui.button_image_reset)
        self.bind('theme.bgcolor', self.button_background_color, 'get_color', 'set_color', 'color_changed')
        self.bind_reset('theme.bgcolor', self.ui.button_background_color_reset)
        
        index = self.liststore_faces.index(self.active_face, 1)
        self.ui.listview_faces.setCurrentIndex(index)
        
    def create_face_selector(self):
        liststore_faces = QStandardItemModel()
        face_names = [_('Up'), _('Down'), _('Left'), _('Right'), _('Front'), _('Back')]
        for i in range(6):
            liststore_faces.appendRow((QStandardItem(i), QStandardItem(face_names[i])))
            filename = settings.theme.face[i].image
            if filename.startswith('/'):
                self.image_dirname = os.path.dirname(filename)
        self.ui.listview_faces.setModel(liststore_faces)
        self.ui.listview_faces.setModelColumn(1)
        #XXX: workaround, listview_faces should automatically set to the correct width
        fm = QFontMetrics(self.ui.listview_faces.font())
        width = max(fm.width(fn) for fn in face_names) + 8
        self.ui.listview_faces.setMaximumWidth(width)
        self.ui.listview_faces.selectionModel().currentChanged.connect(self.on_listview_faces_currentChanged)
        return liststore_faces
        
    def fill_liststore_movekeys(self):
        for move, key in settings.draw.accels:
            self.liststore_movekeys.appendRow([QStandardItem(move), QStandardItem(key)])
        self.liststore_movekeys.setHeaderData(0, Qt.Horizontal, _('Move'))
        self.liststore_movekeys.setHeaderData(1, Qt.Horizontal, _('Key'))
            
    def bind_reset_item(self, settings_key, button):
        def on_clicked():
            delattr(settings, settings_key.format(self.active_face))
        button.clicked.connect(on_clicked)
        
    @ staticmethod
    def _accel_mods_to_str(accel_mods):
        accel_str = ''
        for a in accel_mods.value_nicks:
            if accel_str:
                accel_str += '+'
            if a.endswith('-mask'):
                a = a[:-5]
            accel_str += a
        return accel_str
        
    def set_imagefile(self, imagefile):
        index_icon = len(textures.stock_files) + 1
        if not imagefile:
            index = 0
            icon = QIcon()
        elif imagefile.startswith('/'):
            index = index_icon
            icon = QIcon(imagefile)
        else:
            try:
                index = textures.stock_files.index(imagefile) + 1
            except ValueError:
                index = 0
            icon = QIcon()
        self.ui.combobox_image.setItemIcon(index_icon, icon)
        self.ui.combobox_image.setCurrentIndex(index)
        
    def on_settings_changed(self, key):
        Dialog.on_settings_changed(self, key)
        if self._ignore_changed:
            return
        self._ignore_changed = True
        if key == 'draw.samples':
            visible = (self.sample_buffers != 2**settings.draw.samples > 1)
            self.ui.label_needs_restarted.setVisible(visible)
        elif key == 'draw.accels':
            self.liststore_movekeys.clear()
            self.fill_liststore_movekeys()
        elif key == 'theme.face.{}.color'.format(self.active_face):
            self.button_color.set_color(settings.theme.face[self.active_face].color)
        elif key == 'theme.face.{}.image'.format(self.active_face):
            self.set_imagefile(settings.theme.face[self.active_face].image)
        elif key == 'theme.face.{}.mode':
            imagemode = settings.theme.face[self.active_face].mode_nick
            if imagemode == 'tiled':
                self.ui.radiobutton_tiled.setChecked(True)
            elif imagemode == 'mosaic':
                self.ui.radiobutton_mosaic.setChecked(True)
        self._ignore_changed = False
        
    ###
    
    @Slot(bool)
    def on_checkbox_mirror_faces_toggled(self, checked):
        self.ui.spinbox_mirror_faces.setEnabled(checked)
        
    ### mouse handlers ###
    
    def set_mousemode(self, checked, mode):
        if self._ignore_changed:
            return
        if checked:
            self._ignore_changed = True
            settings.draw.selection_nick = mode
            self._ignore_changed = False
            
    @Slot(bool)
    def on_button_mousemode_quad_toggled(self, checked):
        self.set_mousemode(checked, 'quadrant')
        
    @Slot(bool)
    def on_button_mousemode_ext_toggled(self, checked):
        self.set_mousemode(checked, 'simple')
        
    ### key handlers ###
    
    def get_move_key_list(self):
        move_keys = []
        for i in range(self.liststore_movekeys.rowCount()):
            move, key = [self.liststore_movekeys.item(i, j).data(Qt.DisplayRole) for j in (0, 1)]
            move_keys.append((move, key))
        return move_keys
            
    @Slot()
    def on_button_movekey_add_clicked(self):
        row = self.ui.listview_movekeys.currentIndex().row()
        self._ignore_changed = True
        self.liststore_movekeys.insertRow(row, (QStandardItem(''), QStandardItem('')))
        index = self.liststore_movekeys.index(row, 0)
        self.ui.listview_movekeys.setCurrentIndex(index)
        self.ui.listview_movekeys.edit(index)
        self._ignore_changed = False
        
    @Slot()
    def on_button_movekey_remove_clicked(self):
        row = self.ui.listview_movekeys.currentIndex().row()
        self._ignore_changed = True
        self.liststore_movekeys.takeRow(row)
        settings.draw.accels = self.get_move_key_list()
        self._ignore_changed = False
        
    @Slot()
    def on_button_movekey_reset_clicked(self):  # pylint: disable=R0201
        del settings.draw.accels
        
    def on_liststore_movekeys_itemChanged(self, unused_item):
        self._ignore_changed = True
        settings.draw.accels = self.get_move_key_list()
        self._ignore_changed = False
        
    ### appearance handlers ###
    
    def on_listview_faces_currentChanged(self, current):
        self.active_face = current.row()
        self._ignore_changed = True
        self.button_color.set_color(settings.theme.face[self.active_face].color)
        self.set_imagefile(settings.theme.face[self.active_face].image)
        imagemode = settings.theme.face[self.active_face].mode_nick
        if imagemode == 'tiled':
            self.ui.radiobutton_tiled.setChecked(True)
        elif imagemode == 'mosaic':
            self.ui.radiobutton_mosaic.setChecked(True)
        self._ignore_changed = False
        
    def on_button_color_color_changed(self, color):
        if self._ignore_changed:
            return
        self._ignore_changed = True
        settings.theme.face[self.active_face].color = color
        self._ignore_changed = False
        
    @Slot(int)
    def on_combobox_image_activated(self, index):
        if self._ignore_changed:
            return
        if index == 0:
            filename = ''
        else:
            try:
                filename = textures.stock_files[index-1]
            except IndexError:
                filename = QFileDialog.getOpenFileName(self, _("Open Image"), self.image_dirname)
                if isinstance(filename, (tuple, list)):
                    filename = filename[0]
                if filename == '':
                    # canceled, set the old image
                    filename = settings.theme.face[self.active_face].image
                    self.set_imagefile(filename)
                else:
                    self.image_dirname = os.path.dirname(filename)
        self._ignore_changed = True
        settings.theme.face[self.active_face].image = filename
        self._ignore_changed = False
        
    @Slot(bool)
    def on_radiobutton_tiled_toggled(self, checked):
        self.set_imagemode(checked, 'tiled')
    @Slot(bool)
    def on_radiobutton_mosaic_toggled(self, checked):
        self.set_imagemode(checked, 'mosaic')
        
    def set_imagemode(self, checked, mode):
        if self._ignore_changed:
            return
        if checked:
            self._ignore_changed = True
            settings.theme.face[self.active_face].mode_nick = mode
            self._ignore_changed = False
            
        
class SelectModelDialog (Dialog):
    response_ok = Signal(int, tuple, bool)
    
    def __init__(self, parent):
        Dialog.__init__(self, parent)
        self.ui = UI(self, 'model.ui')
        from .ui.model import retranslate
        retranslate(self)
        
        for text in settings.game.type_range:
            self.ui.combobox_model.addItem(_(text), text)
        mtype = settings.game.type
        self.ui.combobox_model.setCurrentIndex(mtype)
        self.on_combobox_model_activated(mtype)
        size = settings.game.size
        width, height, depth = size
        self.ui.spin_size1.setValue(width)
        self.ui.spin_size2.setValue(height)
        self.ui.spin_size3.setValue(depth)
        
    @Slot(int)
    def on_combobox_model_activated(self, index):
        mtype = models[index].type
        self.ui.label_width.setText({'Cube':_('Size:'), 'Tower':_('Basis:')}.get(mtype, _('Width:')))
        self.ui.label_width.setEnabled(mtype in ['Cube', 'Tower', 'Brick'])
        self.ui.label_heigth.setEnabled(mtype in ['Tower', 'Brick'])
        self.ui.label_depth.setEnabled(mtype == 'Brick')
        self.ui.spin_size1.setEnabled(mtype in ['Cube', 'Tower', 'Brick'])
        self.ui.spin_size2.setEnabled(mtype in ['Tower', 'Brick'])
        self.ui.spin_size3.setEnabled(mtype == 'Brick')
        
    def on_response(self):
        self.response_ok.emit(
                self.ui.combobox_model.currentIndex(),
                (self.ui.spin_size1.value(), self.ui.spin_size2.value(), self.ui.spin_size3.value()),
                self.ui.checkbox_solved.isChecked())
        
        
class ProgressDialog (QProgressDialog):
    def __init__(self, parent):
        QProgressDialog.__init__(self, parent)
        self.canceled_ = False
        self.value_max = 10
        self.value = 0
        self.setWindowModality(Qt.WindowModal)
        self.setMaximum(self.value_max)
        self.setMinimumDuration(0)
        self.setAutoReset(False)
        self.canceled.connect(self.on_canceled)
        
    def on_canceled(self):
        self.canceled_ = True
        self.setLabelText(_('Canceling operation, please wait'))
        
    def tick(self, step, message=None, value_max=None):
        if not self.isVisible():
            self.show()
        if message is not None:
            self.setLabelText(message)
        if value_max is not None:
            self.value_max = value_max
            self.value = 0
            self.setMaximum(value_max)
        if step < 0 or self.value > self.value_max:
            self.setValue(0)
            self.setMaximum(0)
        else:
            self.setValue(self.value)
            self.value += step
        return self.canceled_
        
    def done(self):
        self.canceled_ = False
        self.reset()
        
def linkedtext_to_html(text):
    html = QTextDocumentFragment.fromPlainText(text).toHtml()
    html = re.sub(r'&lt;((?:http:|https:|text:).*?)\|&gt;', r'<a href="\1">', html)
    html = re.sub(r'&lt;\|&gt;', r'</a>', html)
    return re.sub(r'&lt;((?:http:|https:).*?)&gt;', r'&lt;<a href="\1">\1</a>&gt;', html)
    
class UI:
    def __init__(self, toplevel, filename):
        self._toplevel = toplevel
        from PyQt4 import uic
        uic.loadUi(os.path.join(config.UI_DIR, filename), toplevel)
    def __getattr__(self, attrname):
        if attrname[0] == '_':
            return object.__getattr__(self, attrname)
        else:
            return self._toplevel.findChild(QObject, attrname)
            
        
class AboutDialog (QDialog):
    def __init__(self, parent):
        QDialog.__init__(self, parent)
        self.about = UI(self, 'about.ui')
        from .ui.about import retranslate
        retranslate(self)
        
        self.fill_header()
        self.fill_about_tab()
        self.fill_feedback_tab()
        self.fill_translation_tab()
        self.fill_license_tab()
        
        self.index_tab_about = self.about.tab_widget.indexOf(self.about.tab_about)
        self.index_tab_license = self.about.tab_widget.indexOf(self.about.tab_license)
        
        # About tab animation
        self.scrollbar = self.about.text_translators.verticalScrollBar()
        self.animation = QPropertyAnimation(self.scrollbar, 'value')
        self.animation.setLoopCount(-1)
        
    def fill_header(self):
        self.about.label_icon.setPixmap(QPixmap(os.path.join(config.PIXMAP_DIR, 'pybik-64')))
        self.about.label_appname.setText(_(config.APPNAME))
        self.about.label_version.setText(config.VERSION)
        self.about.label_description.setText(_(config.SHORT_DESCRIPTION))
        
    def fill_about_tab(self):
        self.about.label_copyright.setText(config.COPYRIGHT)
        self.about.label_website.setText(
                '<html><head/><body><p><a href="{}">{}</a></p></body></html>'
                .format(config.WEBSITE, _('Pybik project website')))
        html_template = '''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li {{ white-space: pre-wrap; }}
</style></head>
<body>{}</body></html>'''
        html_p_template = '<p align="center">{}</p>'
        html_language_template = '<span style=" font-weight:600;">{}</span>'
        html_person_template = '<a href="{}">{}</a>'
        html_p_items = []
        from .translators import translators
        try:
            import icu
        except ImportError:
            print('PyICU is not installed')
            langname_from_code = lambda code, name: name
        else:
            def langname_from_code(code, name):
                iculocale = icu.Locale.createFromName(code)
                lang = iculocale.getLanguage()
                if icu.Locale.createFromName(lang).getDisplayName() == lang:
                    return name
                return str(iculocale.getDisplayName()) or name
        import locale
        sortfunc = lambda v: locale.strxfrm(v[0])
        def gentranslators():
            for language, langcode, persons in translators:
                language = langname_from_code(langcode, language)
                yield language, persons
        for language, persons in sorted(gentranslators(), key=sortfunc):
            language = Qt.escape(language)
            html_items = [html_language_template.format(language)]
            for name, link in sorted(persons, key=sortfunc):
                name = Qt.escape(name)
                html_items.append(html_person_template.format(link, name))
            html_p_items.append(html_p_template.format('<br/>'.join(html_items)))
        html = html_template.format(''.join(html_p_items))
        self.about.text_translators.setHtml(html)
        self.about.text_translators.anchorClicked.connect(QDesktopServices.openUrl)
        self.about.text_translators.viewport().installEventFilter(self)
        
    def fill_feedback_tab(self):
        html = linkedtext_to_html(config.get_filebug_text())
        self.about.label_feedback.setText(html)
        
    def fill_translation_tab(self):
        html = linkedtext_to_html(_(config.TRANSLATION_TEXT))
        self.about.label_translation.setText(html)
        
    def fill_license_tab(self):
        self.about.text_license_short.hide()
        self.about.text_license_full.hide()
        html = linkedtext_to_html('\n\n'.join((_(config.LICENSE_INFO), _(config.LICENSE_FURTHER))))
        self.about.text_license_short.setHtml(html)
        try:
            with open(config.LICENSE_FILE, 'rt', encoding='utf-8') as license_file:
                text = license_file.read()
        except Exception as e:
            print('Unable to find license text:', e)
            text = _(config.LICENSE_NOT_FOUND)
            self.about.text_license_full.setLineWrapMode(QTextEdit.WidgetWidth)
        html = linkedtext_to_html(text)
        self.about.text_license_full.setHtml(html)
        self.about.tab_widget.currentChanged.connect(self._on_tab_widget_currentChanged)
        self.about.text_license_short.anchorClicked.connect(self._on_text_license_anchorClicked)
        
    def update_animation(self):
        smin = self.scrollbar.minimum()
        smax = self.scrollbar.maximum()
        if smax <= smin:
            return
        self.animation.setDuration((smax-smin) * 40)
        self.animation.setKeyValueAt(0., smin)
        self.animation.setKeyValueAt(0.04, smin)
        self.animation.setKeyValueAt(0.50, smax)
        self.animation.setKeyValueAt(0.54, smax)
        self.animation.setKeyValueAt(1., smin)
        
    def showEvent(self, event):
        if self.animation.state() == QAbstractAnimation.Stopped:
            self.update_animation()
            self.scrollbar.hide()
            self.animation.start()
            
    def resizeEvent(self, event):
        self.update_animation()
        
    def eventFilter(self, obj, event):
        #assert obj == self.about.text_translators.viewport()
        if event.type() in [QEvent.MouseButtonPress, QEvent.Wheel]:
            self.animation.pause()
            self.scrollbar.show()
        return False
        
    def _on_tab_widget_currentChanged(self, index):
        if index == self.index_tab_about:
            self.animation.resume()
            self.scrollbar.hide()
        else:
            self.animation.pause()
            self.scrollbar.show()
        if index == self.index_tab_license:
            self.about.text_license_short.setVisible(True)
            self.about.text_license_full.setVisible(False)
        QTimer.singleShot(0, self.adjustSize)
        
    def _on_text_license_anchorClicked(self, url):
        if url.toString() == 'text:FULL_LICENSE_TEXT':
            self.about.text_license_short.setVisible(False)
            self.about.text_license_full.setVisible(True)
        else:
            QDesktopServices.openUrl(url)
            
    def run(self):
        self.exec_()
        self.deleteLater()
        

