# (c) 2005 Canonical
# Author: Michael Vogt <michael.vogt@ubuntu.com>
#
# Released under the GPL
#

from __future__ import print_function

import gettext
import grp
import locale
import os
import pwd
import re
import subprocess
import sys
import time
from gettext import gettext as _

import gi
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import GObject, Gdk, Gtk #, Pango

import apt

import aptdaemon.client
from defer import inline_callbacks
from aptdaemon.enums import *
from aptdaemon.gtk3widgets import AptProgressDialog

from LanguageSelector.LanguageSelector import *
from LanguageSelector.ImConfig import ImConfig
from LanguageSelector.macros import *
from LanguageSelector.utils import language2locale
from LanguageSelector.LangCache import ExceptionPkgCacheBroken
 
(LIST_LANG,                     # language (i18n/human-readable)
 LIST_LANG_INFO                 # the short country code (e.g. de, pt_BR)
 ) = range(2)

(LANGTREEVIEW_LANGUAGE,
 LANGTREEVIEW_CODE) = range(2)
 
(IM_CHOICE,
 IM_NAME) = range(2)


def xor(a,b):
    " helper to simplify the reading "
    return a ^ b

def blockSignals(f):
    " decorator to ensure that the signals are blocked "
    def wrapper(*args, **kwargs):
        args[0]._blockSignals = True
        res = f(*args, **kwargs)
        args[0]._blockSignals = False
        return res
    return wrapper

def honorBlockedSignals(f):
    " decorator to ensure that the signals are blocked "
    def wrapper(*args, **kwargs):
        if args[0]._blockSignals:
            return
        res = f(*args, **kwargs)
        return res
    return wrapper

def insensitive(f):
    """
    decorator to ensure that a given function is run insensitive
    warning: this will not stack well so don't use it for nested
    stuff (a @insensitive func calling a @insensitve one)
    """
    def wrapper(*args, **kwargs):
        args[0].setSensitive(False)
        res = f(*args, **kwargs)
        args[0].setSensitive(True)
        return res
    return wrapper



# intervals of the start up progress
# 3x caching and menu creation
STEPS_UPDATE_CACHE = [33, 66, 100]

class GtkProgress(apt.progress.base.OpProgress):
    def __init__(self, host_window, progressbar, parent,
                 steps=STEPS_UPDATE_CACHE):
        # used for the "one run progressbar"
        self.steps = steps[:]
        self.base = 0
        self.old = 0
        self.next = int(self.steps.pop(0))

        self._parent = parent
        self._window = host_window
        self._progressbar = progressbar
        self._window.realize()
        host_window.get_window().set_functions(Gdk.WMFunction.MOVE)
        self._window.set_transient_for(parent)

    def update(self, percent=None):
        #print(percent)
        #print(self.Op)
        #print(self.SubOp)
        self._window.show()
        self._parent.set_sensitive(False)
        if percent is None:
            self.old = 0
            self._progressbar.pulse()
        else:
            # if the old percent was higher, a new progress was started
            if self.old > percent:
                # set the borders to the next interval
                self.base = self.next
                try:
                    self.next = int(self.steps.pop(0))
                except:
                    pass
            progress = self.base + percent/100 * (self.next - self.base)
            self.old = percent
            self._progressbar.set_fraction(progress/100.0)
        while Gtk.events_pending():
            Gtk.main_iteration()
    def done(self):
        self._parent.set_sensitive(True)
    def hide(self):
        self._window.hide()

class GtkLanguageSelector(LanguageSelectorBase):

    def __init__(self, datadir, options):
        LanguageSelectorBase.__init__(self, datadir)
        self._datadir = datadir

        self.widgets = Gtk.Builder()
        self.widgets.set_translation_domain('language-selector')
        self.widgets.add_from_file(datadir+"/data/LanguageSelector.ui")
        self.widgets.connect_signals(self)

        try:
            in_grp_admin = grp.getgrnam("admin")[2] in os.getgroups()
        except KeyError:
            in_grp_admin = False
        try:
            in_grp_sudo = grp.getgrnam("sudo")[2] in os.getgroups()
        except KeyError:
            in_grp_sudo = False

        self.is_admin = (os.getuid() == 0 or in_grp_sudo or in_grp_admin)

        # see if we have any other human users on this system
        self.has_other_users = False
        num = 0
        for l in pwd.getpwall():
            if l.pw_uid >= 500 and l.pw_uid < 65534:
                num += 1
            if num >= 2:
                self.has_other_users = True
                break
        
        #build the comboboxes (with model)
        combo = self.combobox_locale_chooser
        model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
        cell = Gtk.CellRendererText()
        combo.pack_start(cell, True)
        combo.add_attribute(cell, 'text', LANGTREEVIEW_LANGUAGE)
        combo.set_model(model)
#        self.combo_syslang_dirty = False

#        combo = self.combobox_user_language
#        model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
#        cell = Gtk.CellRendererText()
#        combo.pack_start(cell, True)
#        combo.add_attribute(cell, 'text', COMBO_LANGUAGE)
#        combo.set_model(model)
#        self.combo_userlang_dirty = False
        self.options = options

        # get aptdaemon client
        self.ac = aptdaemon.client.AptClient()

        combo = self.combobox_input_method
        model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
        cell = Gtk.CellRendererText()
        combo.pack_start(cell, True)
        combo.add_attribute(cell, 'text', IM_NAME)
        combo.set_model(model)
        self.ImConfig = ImConfig()
        self._blockSignals = False

        # build the treeview
        self.setupLanguageTreeView()
        self.setupInstallerTreeView()
        self.updateLanguageView()
#        self.updateUserDefaultCombo()
        self.updateLocaleChooserCombo()
        self.check_input_methods()
#        self.updateSyncButton()
        
        # apply button
        self.button_apply.set_sensitive(False)

        # 'Apply System-Wide...' and 'Install/Remove Languages...' buttons
        #
        # Distinction depending on the is_admin test dropped as a
        # temporary(?) fix of https://launchpad.net/bugs/1008344
#        if self.is_admin:
        self.button_apply_system_wide_languages.set_sensitive(True)
        self.button_install_remove_languages.set_sensitive(True)
        self.button_apply_system_wide_locale.set_sensitive(True)
#        else:
#            self.button_apply_system_wide_languages.set_sensitive(False)
#            self.button_install_remove_languages.set_sensitive(False)
#            self.button_apply_system_wide_locale.set_sensitive(False)

        # show it
        self.window_main.show()
        self.setSensitive(False)

        if self.is_admin:
            # check if the package list is up-to-date
            if not self._cache.havePackageLists:
                d = Gtk.MessageDialog(message_type=Gtk.MessageType.INFO,
                                      buttons=Gtk.ButtonsType.CANCEL)
                d.set_modal(True)
                d.set_transient_for(self.window_main)
                d.set_markup("<big><b>%s</b></big>\n\n%s" % (
                    _("No language information available"),
                    _("The system does not have information about the "
                      "available languages yet. Do you want to perform "
                      "a network update to get them now? ")))
                d.set_title=("")
                d.add_button(_("_Update"), Gtk.ResponseType.YES)
                res = d.run()
                d.destroy()
                if res == Gtk.ResponseType.YES:
                    self.setSensitive(False)
                    self.update()
                    self.updateLanguageView()
                    self.setSensitive(True)

            # see if something is missing
            if self.options.verify_installed:
                self.verifyInstalledLangPacks()

        if not self.ImConfig.available():
            self.combobox_input_method.set_sensitive(False)
        self.setSensitive(True)

    def __getattr__(self, name):
        '''Convenient access to GtkBuilder objects'''

        o = self.widgets.get_object(name)
        if o is None:
            raise AttributeError('No such widget: ' + name)
        return o

    def setSensitive(self, value):
        win = self.window_main.get_window()
        if value:
            self.window_main.set_sensitive(True)
            if win:
                win.set_cursor(None)
        else:
            self.window_main.set_sensitive(False)
            if win:
                win.set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
        while Gtk.events_pending():
            Gtk.main_iteration()

#    @blockSignals
#    def updateSyncButton(self):
#        " check if the sync languages button should be enabled or not "
#        button = self.checkbutton_sync_languages
#        combo = self.combobox_system_language
#        # no admin user, gray out
#        if self.is_admin == False:
#            button.set_active(False)
#            button.set_sensitive(False)
#            combo.set_sensitive(False)
#            return
#        # admin user, check stuff
#        button.set_sensitive(True)
#        combo.set_sensitive(True)
#        # do not enable the keep the same button if the system has other
#        # users or if the language settings are inconsistent already
#        userlang = self.combobox_user_language.get_active()
#        systemlang = self.combobox_system_language.get_active()
#        if (not self.has_other_users and userlang == systemlang):
#            button.set_active(True)
#        else:
#            button.set_active(False)

    def setupInstallerTreeView(self):
        """ do all the treeview setup here """
        def toggle_cell_func(column, cell, model, iter, data):
            langInfo = model.get_value(iter, LIST_LANG_INFO)

            # check for active and inconsitent 
            inconsistent = langInfo.inconsistent
            #if inconsistent:
            #    print("%s is inconsistent" % langInfo.language)

            cell.set_property("active", langInfo.fullInstalled)
            cell.set_property("inconsistent", inconsistent)
            
        def lang_view_func(cell_layout, renderer, model, iter, data):
            langInfo = model.get_value(iter, LIST_LANG_INFO)
            langName = model.get_value(iter, LIST_LANG)
            if (langInfo.changes) :
                markup = "<b>%s</b>" % langName
            else:
                markup = "%s" % langName
            renderer.set_property("markup", markup)

        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn(_("Language"), renderer, text=LIST_LANG)
        column.set_property("expand", True)
        column.set_cell_data_func (renderer, lang_view_func, None)
        self.treeview_languages.append_column(column)

        renderer= Gtk.CellRendererToggle()
        renderer.connect("toggled", self.on_toggled)
        column = Gtk.TreeViewColumn(_("Installed"), renderer)
        column.set_cell_data_func (renderer, toggle_cell_func, None)
        self.treeview_languages.append_column(column)
        # build the store
        self._langlist = Gtk.ListStore(str, GObject.TYPE_PYOBJECT)
        self.treeview_languages.set_model(self._langlist)

    def setupLanguageTreeView(self):
        """ do all the treeview setup here """
        def lang_view_func(cell_layout, renderer, model, iter, data):
            langInfo = model.get_value(iter, LANGTREEVIEW_CODE)
            greyFlag = False
            myiter = model.get_iter_first()
            while myiter:
                str = model.get_value(myiter,LANGTREEVIEW_CODE)
                if str == langInfo: 
                    greyFlag = False
                    break
                if str == "en":
                    greyFlag = True
                    break
                myiter = model.iter_next(myiter)
            if greyFlag:
                markup = "<span foreground=\"grey\">%s</span>" \
                       % self._localeinfo.translate(langInfo, native=True, allCountries=True)
            else:
                markup = "%s" % self._localeinfo.translate(langInfo, native=True, allCountries=True)
            renderer.set_property("markup", markup)

        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn(_("Language"), renderer, text=LANGTREEVIEW_LANGUAGE)
        column.set_property("expand", True)
        column.set_cell_data_func (renderer, lang_view_func, None)
        self.treeview_locales.append_column(column)

        # build the store
        self._language_options = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
        self.treeview_locales.set_model(self._language_options)

    def _get_langinfo_on_cursor(self):
        (path, column) = self.treeview_languages.get_cursor()
        if not path:
            return None
        iter = self._langlist.get_iter(path)
        langInfo = self._langlist.get_value(iter, LIST_LANG_INFO)
        return langInfo

    def debug_pkg_status(self):
        langInfo = self._get_langinfo_on_cursor()
        for pkg in langInfo.languagePkgList.items() :
            print("%s, available: %s, installed: %s, doChange: %s" % (pkg[0], pkg[1].available, pkg[1].installed, pkg[1].doChange))
        print("inconsistent? : %s" % langInfo.inconsistent)

    def check_status(self):
        changed = False
        countInstall = 0
        countRemove = 0
        for (lang, langInfo) in self._langlist:
            if langInfo.changes:
                changed = True
                for item in langInfo.languagePkgList.values():
                    if item.doChange:
                        if item.installed:
                            countRemove = countRemove + 1
                        else:
                            countInstall = countInstall + 1
        #print("%(INSTALL)d to install, %(REMOVE)d to remove" % (countInstall, countRemove))
        # Translators: %(INSTALL)d is parsed; either keep it exactly as is or remove it entirely, but don't translate "INSTALL".
        textInstall = gettext.ngettext("%(INSTALL)d to install", "%(INSTALL)d to install", countInstall) % {'INSTALL': countInstall}
        # Translators: %(REMOVE)d is parsed; either keep it exactly as is or remove it entirely, but don't translate "REMOVE".
        textRemove = gettext.ngettext("%(REMOVE)d to remove", "%(REMOVE)d to remove", countRemove) % {'REMOVE': countRemove}
        if countRemove == 0 and countInstall == 0: 
            self.label_install_remove.set_text("")
        elif countRemove == 0: 
            self.label_install_remove.set_text(textInstall)
        elif countInstall == 0: 
            self.label_install_remove.set_text(textRemove)
        else: 
            # Translators: this string will concatenate the "%n to install" and "%n to remove" strings, you can replace the comma if you need to.
            self.label_install_remove.set_text(_("%s, %s") % (textInstall, textRemove))
        
        if changed:
            self.button_apply.set_sensitive(True)
        else:
            self.button_apply.set_sensitive(False)

#    @honorBlockedSignals
#    @insensitive
#    def on_combobox_system_language_changed(self, widget):
#        #print("on_combobox_system_language_changed()")
#        if self.writeSystemDefaultLang():
#            # queue a restart of gdm (if it is runing) to make the new
#            # locales usable
#            gdmscript = "/etc/init.d/gdm"
#            if os.path.exists("/var/run/gdm.pid") and os.path.exists(gdmscript):
#                self.runAsRoot(["invoke-rc.d","gdm","reload"])
#        self.updateSystemDefaultCombo()
#        if self.checkbutton_sync_languages.get_active() == True:
#            self.combobox_user_language.set_active(self.combobox_system_language.get_active())
#            self.updateUserDefaultCombo()

#    @honorBlockedSignals
#    @insensitive
#    def on_combobox_user_language_changed(self, widget):
#        #print("on_combobox_user_language_changed()")
#        self.check_input_methods()
#        self.writeUserDefaultLang()
#        self.updateUserDefaultCombo()
#        if self.checkbutton_sync_languages.get_active() == True:
#            self.combobox_system_language.set_active(self.combobox_user_language.get_active())
#            self.updateSystemDefaultCombo()

    @blockSignals
    def check_input_methods(self):
        if not self.ImConfig.available():
            return

        combo = self.combobox_input_method
        model = combo.get_model()
        if not model:
            return
        model.clear()

        # find the default
        currentIM = self.ImConfig.getCurrentInputMethod()

        # find out about the other options
        names = dict(xim=_('none'), ibus='IBus', scim='SCIM', hangul='Hangul', thai='Thai')
        for (i, IM) in enumerate(self.ImConfig.getAvailableInputMethods()):
            name = names[IM] if IM in names else IM
            iter = model.append()
            model.set_value(iter, IM_CHOICE, IM)
            model.set_value(iter, IM_NAME, name)
            if IM == currentIM:
                combo.set_active(i)
#        self.check_status()

#    def writeInputMethodConfig(self):
#        """ 
#        write new input method defaults - currently we only support all_ALL
#        """
#        combo = self.combobox_user_language
#        model = combo.get_model()
#        if combo.get_active() < 0:
#            return
#        (lang, code) = model[combo.get_active()]
#        # check if we need to do something
#        new_value = self.checkbutton_enable_input_methods.get_active()
#        if self.imSwitch.enabledForLocale(code) != new_value:
#            if new_value:
#                self.imSwitch.enable(code)
#            else:
#                self.imSwitch.disable(code)
#            #self.showRebootRequired()
#            #self.checkReloginNotification()

#    @honorBlockedSignals
#    def on_checkbutton_enable_input_methods_toggled(self, widget):
#        #print("on_checkbutton_enable_input_methods_toggled()")
#        active = self.checkbutton_enable_input_methods.get_active()
#        self.combo_userlang_dirty = True
#        self.setSensitive(False)
#        self.writeInputMethodConfig()
#        self.setSensitive(True)

#    @honorBlockedSignals
#    def on_checkbutton_sync_languages_toggled(self, widget):
#        #print("on_checkbutton_sync_languages_toggled()")
#        if self.checkbutton_sync_languages.get_active() == True:
#            self.combobox_user_language.set_active(self.combobox_system_language.get_active())
#            self.updateSystemDefaultCombo()
        
    def build_commit_lists(self):
        print(self._cache.get_changes())

        try:
            for (lang, langInfo) in self._langlist:
                self._cache.tryChangeDetails(langInfo)
        except ExceptionPkgCacheBroken:
            self.error(
                _("Software database is broken"),
                _("It is impossible to install or remove any software. "
                  "Please use the package manager \"Synaptic\" or run "
                  "\"sudo apt-get install -f\" in a terminal to fix "
                  "this issue at first."))
            sys.exit(1)
        (to_inst, to_rm) = self._cache.getChangesList()
        #print("inst: %s" % to_inst)
        #print("rm: %s" % to_rm)
        print(self._cache.get_changes())
        return (to_inst, to_rm)

    def error(self, summary, msg):
        d = Gtk.MessageDialog(message_type=Gtk.MessageType.ERROR,
                              buttons=Gtk.ButtonsType.CLOSE)
        d.set_modal(True)
        d.set_transient_for(self.window_main)
        d.set_markup("<big><b>%s</b></big>\n\n%s" % (summary, msg))
        d.set_title=("")
        d.run()
        d.destroy()

    def _show_error_dialog(self, error):
        msg = str(error)
        self.error(msg, "")

    def verify_commit_lists(self, inst_list, rm_list):
        """ verify if the selected package can actually be installed """
        res = True
        try:
            for pkg in inst_list:
                if pkg in self._cache:
                    self._cache[pkg].mark_install()
            for pkg in rm_list:
                if pkg in self._cache:
                    self._cache[pkg].mark_delete()
        except SystemError:
            res = False

        # undo the selections
        self._cache.clear()
        if self._cache._depcache.broken_count != 0:
            self.error(_("Could not install the selected language support"),
                       _("This is perhaps a bug of this application. Please "
                         "file a bug report at "
                         "https://bugs.launchpad.net/ubuntu/+source/language-selector/+filebug"))

            # something went pretty bad, re-get a cache
            progress = GtkProgress(self.dialog_progress,
                                   self.progressbar_cache,
                                   self.window_main)
            self._cache = apt.Cache(self._localeinfo, progress)
            progress.hide()
            res = False
        return res

    def commitAllChanges(self):
        """ 
        commit helper, builds the commit lists, verifies it
        
        returns the number of install/removed packages
        """
        self.setSensitive(False)
        # install the new packages (if any)
        (inst_list, rm_list) = self.build_commit_lists()
        if not self.verify_commit_lists(inst_list, rm_list):
            self.error(
                _("Could not install the full language support"),
                _("Usually this is related to an error in your "
                  "software archive or software manager. Check your "
                  "preferences in Software Sources (click the icon "
                  "at the very right of the top bar and select "
                  "\"System Settings... -> Software Sources\")."))
            self.setSensitive(True)
            return 0
        #print("inst_list: %s " % inst_list)
        #print("rm_list: %s " % rm_list)
        self.commit(inst_list, rm_list)

        self.setSensitive(True)
        return len(inst_list)+len(rm_list)

    
    def _run_transaction(self, transaction):
        dia = AptProgressDialog(transaction)
        dia.set_transient_for(self.window_main)
        dia.connect("finished", self._on_finished)
        dia.run(error_handler=self._on_error)
        
    def _wait_for_aptdaemon_finish(self):
        while not self._transaction_finished:
            while Gtk.events_pending():
                Gtk.main_iteration()
            time.sleep(0.02)

    def _on_finished(self, dialog):
        dialog.hide()
        self._transaction_finished = True

    def _on_error(self, error):
        if hasattr(error, 'get_dbus_name') and error.get_dbus_name() == \
                    'org.freedesktop.PolicyKit.Error.NotAuthorized':
                self.error(
                        _("Could not install the full language support"),
                        _('Failed to authorize to install packages.'))
        else:
            self.error(
                    _("Could not install the full language support"),
                    str(error))
        self._transaction_finished = True

    def update_aptdaemon(self):
        self._transaction_finished = False
        self._update_aptdaemon()
        self._wait_for_aptdaemon_finish()

    @inline_callbacks
    def _update_aptdaemon(self):
        try:
            trans = yield self.ac.update_cache(defer=True)
            self._run_transaction(trans)
        except Exception as e:
            self._show_error_dialog(e)

    def commit_aptdaemon(self, inst, rm):
        self._transaction_finished = False
        self._commit_aptdaemon(inst, rm)
        self._wait_for_aptdaemon_finish()

    @inline_callbacks
    def _commit_aptdaemon(self, inst, rm):
        if len(inst) == 0 and len(rm) == 0:
            return
        try:
            trans = yield self.ac.commit_packages(
                install=inst, reinstall=[], remove=rm, purge=[], upgrade=[],
                downgrade=[], defer=True)
            self._run_transaction(trans)
        except Exception as e:
            self._show_error_dialog(e)

    # we default with update/commit to aptdaemon
    update = update_aptdaemon
    commit = commit_aptdaemon

    def hide_on_delete(self, widget, event):
        return Gtk.Widget.hide_on_delete(widget)

    def verifyInstalledLangPacks(self):
        """ called at the start to inform about possible missing
            langpacks (e.g. gnome/kde langpack transition)
        """
        #print("verifyInstalledLangPacks")
        missing = self.getMissingLangPacks()

        #print("Missing: %s " % missing)
        if len(missing) > 0:
            # FIXME: add "details"
            d = Gtk.MessageDialog(message_type=Gtk.MessageType.QUESTION)
            d.set_modal(True)
            d.set_transient_for(self.window_main)
            d.set_markup("<big><b>%s</b></big>\n\n%s" % (
                _("The language support is not installed completely"),
                _("Some translations or writing aids available for your "
                  "chosen languages are not installed yet. Do you want "
                  "to install them now?")))
            d.add_buttons(_("_Remind Me Later"), Gtk.ResponseType.NO,
                          _("_Install"), Gtk.ResponseType.YES)
            d.set_default_response(Gtk.ResponseType.YES)
            d.set_title("")
            expander = Gtk.Expander.new(_("Details"))
            scroll = Gtk.ScrolledWindow()
            scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
            scroll.set_min_content_height(130)
            textview = Gtk.TextView()
            textview.set_cursor_visible(False)
            textview.set_editable(False)
            buf = textview.get_buffer()
            pkgs = ""
            for pkg in missing:
                pkgs += "%s\n" % pkg
            buf.set_text(pkgs, -1)
            buf.place_cursor(buf.get_start_iter())
            expander.add(scroll)
            scroll.add(textview)
            d.get_message_area().pack_start(expander, True, True, 0)
            expander.show_all()
            res = d.run()
            d.destroy()
            if res == Gtk.ResponseType.YES:
                self.setSensitive(False)
                self.commit(missing, [])
                self.updateLanguageView()
                self.setSensitive(True)

    def updateLanguageView(self):
        #print("updateLanguageView()")
        self._langlist.clear()

        progress = GtkProgress(self.dialog_progress, self.progressbar_cache,
                               self.window_main)
        try:
            if self.is_admin:
                self.openCache(progress)
                progress.hide()
            else:
                self.openCache(None)
        except ExceptionPkgCacheBroken:
            self.error(
                _("Software database is broken"),
                _("It is impossible to install or remove any software. "
                  "Please use the package manager \"Synaptic\" or run "
                  "\"sudo apt-get install -f\" in a terminal to fix "
                  "this issue at first."))
            sys.exit(1)

        languageList = self._cache.getLanguageInformation()
        #print("ll size: ", len(languageList))
        #print("ll type: ", type(languageList))
        for lang in languageList:
            #print("langInfo: %s (%s)" % (lang.language, lang.languageCode))
            #inconsistent = lang.inconsistent
            #if inconsistent:
            #    print("inconsistent", lang.language)

            # hack for Vietnamese users; see https://launchpad.net/bugs/783090
            # Even if calling self._localeinfo.translate() with native=True triggers
            # a UnicodeWarning for Norwegian Bokmaal, the output should be correct.
            lang_name = None
            if 'LANGUAGE' in os.environ:
                current_language = os.environ['LANGUAGE']
                if re.match('vi[^a-z]', current_language):
                    lang_name = self._localeinfo.translate(lang.languageCode, native=True)
            if not lang_name:
                lang_name = self._localeinfo.translate(lang.languageCode)

            self._langlist.append([lang_name, lang])
        self._langlist.set_sort_column_id(LIST_LANG, Gtk.SortType.ASCENDING)

    def writeSystemFormats(self):
        combo = self.combobox_locale_chooser
        model = combo.get_model()
        if combo.get_active() < 0:
            return False
        (lang, code) = model[combo.get_active()]
        old_code = self._localeinfo.getSystemDefaultLanguage()[0]
        # no changes, nothing to do
        macr = macros.LangpackMacros(self._datadir, old_code)
        if macr["LOCALE"] == code:
            return False
        self.writeSysFormatsSetting(sysFormats=code)
        return True

    def writeUserFormats(self):
        combo = self.combobox_locale_chooser
        model = combo.get_model()
        if combo.get_active() < 0:
            return
        (lang, code) = model[combo.get_active()]
        temp = self._localeinfo.getUserDefaultLanguage()[0]
        if temp == None:
            old_code = self._localeinfo.getSystemDefaultLanguage()[0]
        else:
            old_code = temp
        # no changes, nothing to do
        macr = macros.LangpackMacros(self._datadir, old_code)
        if macr["LOCALE"] == code:
            return False
        self.writeUserFormatsSetting(userFormats=code)
        return True

    def writeSystemLanguage(self, languageString):
        (formats_locale, old_string) = self._localeinfo.getSystemDefaultLanguage()
        # no changes, nothing to do
        if old_string == languageString:
            return False
        self.writeSysLanguageSetting(sysLanguage=languageString)
        if self._localeinfo.isSetSystemFormats():
            return True
        # set the system formats (certain LC_* variables) explicitly
        # in order to prevent surprises when LANG is changed
        self.writeSysFormatsSetting(sysFormats=formats_locale)
        return True

    def writeUserLanguage(self, languageString):
        temp = self._localeinfo.getUserDefaultLanguage()[1]
        if len(temp) == 0:
            old_string = self._localeinfo.getSystemDefaultLanguage()[1]
        else:
            old_string = temp
        # no changes, nothing to do
        if old_string == languageString:
            return False
        self.writeUserLanguageSetting(userLanguage=languageString)
        return True

    @blockSignals
    def updateLocaleChooserCombo(self):
        #print("updateLocaleChooserCombo()")
        combo = self.combobox_locale_chooser
        #XXX get_cell_renderers does not exist in GTK3
        #cell = combo.get_child().get_cell_renderers()[0]
        # FIXME: use something else than a hardcoded value here
        #cell.set_property("wrap-width",300)
        #cell.set_property("wrap-mode",Pango.WRAP_WORD)
        model = combo.get_model()

        if not model:
            return

        model.clear()

        # find the default
        defaultLangName = None
        (defaultLangCode, languageString) = self._localeinfo.getUserDefaultLanguage()
        if len(defaultLangCode) == 0:
            defaultLangCode = self._localeinfo.getSystemDefaultLanguage()[0]
        if len(defaultLangCode) > 0:
            macr = macros.LangpackMacros(self._datadir, defaultLangCode)
            defaultLangCode = macr["LOCALE"]
            defaultLangName = self._localeinfo.translate(defaultLangCode, native=True)

        # find out about the other options        

        """ languages for message translation """
        self._language_options.clear()
        options = subprocess.check_output(
            ['/usr/share/language-tools/language-options'],
            universal_newlines=True)
        mylist = []
        for (i, option) in enumerate( options.split("\n") ):
            mylist.append([self._localeinfo.translate(option, native=True), option])
        if len(languageString) > 0:
            self.userEnvLanguage = languageString
            languages = languageString.split(":")
        else:
            if 'LANGUAGE' in os.environ:
                self.userEnvLanguage = os.environ.get("LANGUAGE")
                languages = self.userEnvLanguage.split(":")
            else:
                self.userEnvLanguage = self._localeinfo.makeEnvString(defaultLangCode)
                languages = self.userEnvLanguage.split(":")
        mylist_sorted = self.bubbleSort(mylist, languages)
        for i in mylist_sorted:
            self._language_options.append(i)

        """ locales for misc. format preferences """
        for (i, loc) in enumerate( self._localeinfo.generated_locales() ):
            iter = model.append()
            model.set_value(iter, LANGTREEVIEW_LANGUAGE,
                    self._localeinfo.translate(loc, native=True))
            model.set_value(iter, LANGTREEVIEW_CODE, loc)
            if (defaultLangName and
                   self._localeinfo.translate(loc, native=True) == defaultLangName):
                combo.set_active(i)
        self.updateExampleBox()

    def bubbleSort(self, sortlist, presort=None):
        """
        Sort the list 'sortlist' using bubble sort.
        Optionally, if a list 'presort' is given, put this list first and bubble sort the rest.
        """
        for i in range(0,len(sortlist)-1):
            for j in range(0,len(sortlist)-1):
                data1 = sortlist[j][1]  
                data2 = sortlist[j+1][1]
                try:
                    v1 = presort.index(data1)
                except:
                    v1 = 100000
                try:
                    v2 = presort.index(data2)
                except:
                    v2 = 100000
                if (v1>v2):
                    sortlist[j],sortlist[j+1] = sortlist[j+1], sortlist[j]   
                elif (v1 >= 100000 and v2 >= 100000 and data1 > data2):
                    sortlist[j],sortlist[j+1] = sortlist[j+1], sortlist[j]
        return sortlist

#        # reset the state of the apply button
#        self.combo_syslang_dirty = False
#        self.check_status()

    # FIXME: updateUserDefaultCombo and updateSystemDefaultCombo
    #        duplicate too much code
#    @blockSignals
#    def updateUserDefaultCombo(self):
#        #print("updateUserDefault()")
#        combo = self.combobox_user_language
#        cell = combo.get_child().get_cell_renderers()[0]
#        # FIXME: use something else than a hardcoded value here
#        cell.set_property("wrap-width",300)
#        cell.set_property("wrap-mode",Pango.WRAP_WORD)
#        model = combo.get_model()
#        model.clear()

#        # find the default
#        defaultLangName = None
#        defaultLangCode = self.getUserDefaultLanguage()
#        if defaultLangCode == None:
#            defaultLangCode = self.getSystemDefaultLanguage()
#        if defaultLangCode:
#            defaultLangName = self._localeinfo.translate(defaultLangCode)

#        # find out about the other options        
#        for (i, loc) in enumerate(self._localeinfo.generated_locales()):
#            iter = model.append()
#            model.set(iter,
#                      COMBO_LANGUAGE,self._localeinfo.translate(loc),
#                      COMBO_CODE, loc)
#            if (defaultLangName and 
#                   self._localeinfo.translate(loc) == defaultLangName):
#                combo.set_active(i)
#            
#        # reset the state of the apply button
#        self.combo_userlang_dirty = False
#        self.check_status()

    def updateExampleBox(self):
        combo = self.combobox_locale_chooser
        model = combo.get_model()
        if combo.get_active() < 0:
            return
        self.label_example_currency.set_text('')
        self.label_example_number.set_text('')
        self.label_example_date.set_text('')
        (lang, code) = model[combo.get_active()]
        macr = macros.LangpackMacros(self._datadir, code)
        mylocale = macr["SYSLOCALE"]
        try:
            locale.setlocale(locale.LC_ALL, mylocale)
        except locale.Error:
            self.label_example_date.set_text('[ '
            + _("Failed to apply the '%s' format\n"
                "choice. The examples may show up if you\n"
                "close and re-open Language Support.") % mylocale + ' ]')
            return
        self.label_example_currency.set_text(locale.currency(20457.99, grouping=True))
        self.label_example_number.set_text(locale.format("%.2f", 1234567.89, grouping=True))
        self.label_example_date.set_text(time.strftime(locale.nl_langinfo(locale.D_T_FMT)))


    ####################################################
    # window_installer signal handlers                 #
    ####################################################

    def on_treeview_languages_row_activated(self, treeview, path, view_column):
        self.on_toggled(None, path.to_string())

    # the global toggle
    def on_toggled(self, renderer, path_string):
        """ called when on install toggle """
        iter = self._langlist.get_iter_from_string(path_string)
        langInfo = self._langlist.get_value(iter, LIST_LANG_INFO)

        # special handling for inconsistent state
        if langInfo.inconsistent :
            for pkg in langInfo.languagePkgList.values() :
                if (pkg.available and not pkg.installed) : 
                    pkg.doChange = True
        elif langInfo.fullInstalled :
            for pkg in langInfo.languagePkgList.values() :
                if (pkg.available) :
                    if (not pkg.installed and pkg.doChange) :
                        pkg.doChange = False
                    elif (pkg.installed and not pkg.doChange) :
                        pkg.doChange = True
        else :
            for pkg in langInfo.languagePkgList.values() :
                if (pkg.available) :
                    if (pkg.installed and pkg.doChange) :
                        pkg.doChange = False
                    elif (not pkg.installed and not pkg.doChange) :
                        pkg.doChange = True

        self.check_status()
        self.treeview_languages.queue_draw()
        #self.debug_pkg_status()

    def on_button_cancel_clicked(self, widget):
        #print("button_cancel")
        self.window_installer.hide()

    def on_button_apply_clicked(self, widget):
        self.window_installer.hide()
        if self.commitAllChanges() > 0:
            self.updateLanguageView()
        self.updateLocaleChooserCombo()
        #self.updateSystemDefaultCombo()

    ####################################################
    # window_main signal handlers                      #
    ####################################################
    def on_delete_event(self, event, data):
        app = self.window_main.get_application()
        if app:
            app.remove_window(self.window_main)

    def on_button_quit_clicked(self, widget):
        app = self.window_main.get_application()
        if app:
            app.remove_window(self.window_main)

    @honorBlockedSignals
    def on_window_main_key_press_event(self, widget, event):
        keyname = Gdk.keyval_name(event.keyval)
        if event.get_state() & Gdk.ModifierType.CONTROL_MASK and keyname == "w":
            app = self.window_main.get_application()
            if app:
                app.remove_window(self.window_main)
        if (event.get_state() | Gdk.ModifierType.MOD2_MASK) == Gdk.ModifierType.MOD2_MASK and keyname == "Escape":
            app = self.window_main.get_application()
            if app:
                app.remove_window(self.window_main)
        return None

    ####################################################
    # window_main signal handlers (Language tab)       #
    ####################################################
#    @honorBlockedSignals
#    def on_treeview_locales_drag_failed(self, widget):
#        return None

#    @honorBlockedSignals
#    def on_treeview_locales_drag_begin(self, widget):
#        return None

#    @honorBlockedSignals
#    def on_treeview_locales_drag_drop(self, widget):
#        return None

    @honorBlockedSignals
    def on_treeview_locales_drag_end(self, widget, drag_content):
        #print("on_treeview_locales_drag_end")
        model = widget.get_model()
        myiter = model.get_iter_first()
        envLanguage = ""
        while myiter:   
            str = model.get_value(myiter,LANGTREEVIEW_CODE)
            if (envLanguage != ""):
                envLanguage = envLanguage + ":"
            envLanguage = envLanguage + str
            if str == "en":
                break
            myiter = model.iter_next(myiter)
        #print(envLanguage)
        self.writeUserLanguage(envLanguage)
        self.userEnvLanguage = envLanguage
        self.check_input_methods()
        self.updateLocaleChooserCombo()
        #os.environ["LANGUAGE"]=envLanguage

#    @honorBlockedSignals
#    def on_treeview_locales_drag_leave(self, widget):
#        return None

#    @honorBlockedSignals
#    def on_treeview_locales_drag_data_received(self, widget):
#        return None

    @honorBlockedSignals
    @insensitive
    def on_button_apply_system_wide_languages_clicked(self, widget):
        self.writeSystemLanguage(self.userEnvLanguage)
        return None

    def on_button_install_remove_languages_clicked(self, widget):
        self.window_installer.show()

    @honorBlockedSignals
    def on_combobox_input_method_changed(self, widget):
        combo = self.combobox_input_method
        model = combo.get_model()
        if combo.get_active() < 0:
            return
        (IM_choice, IM_name) = model[combo.get_active()]
        self.ImConfig.setInputMethod(IM_choice)

    
    ####################################################
    # window_main signal handlers (Text tab)           #
    ####################################################
    @honorBlockedSignals
    @insensitive
    def on_combobox_locale_chooser_changed(self, widget):
        self.check_input_methods()
        self.writeUserFormats()
        self.updateLocaleChooserCombo()
        self.updateExampleBox()

    @honorBlockedSignals
    @insensitive
    def on_button_apply_system_wide_locale_clicked(self, widget):
        self.writeSystemFormats()
        return None

