#!/usr/bin/env python

# Copyright (C) 2007-2014 by Clement Lefebvre <root@linuxmint.com>
# Copyright (C) 2015 Martin Wimpress <code@ubuntu-mate.org>
# 
# 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 2 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, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.

import errno
import gettext
import gi
import os
import shutil
import subprocess
import string
import sys

from gi.repository import Gtk, GdkPixbuf, Gdk, GObject
from gi.repository import Gio

__VERSION__="3.4.8"

# i18n
gettext.install("mate-tweak", "/usr/share/locale")

class SidePage:
    def __init__(self, notebook_index, name, icon):
        self.notebook_index = notebook_index
        self.name = name
        self.icon = icon

class MateTweak:

    def set_string(self, schema, key, value):
        settings = Gio.Settings.new(schema)
        settings.set_string(key, value)

    def get_string(self, schema, key):
        settings = Gio.Settings.new(schema)
        return settings.get_string(key)

    def set_bool(self, schema, key, value):
        settings = Gio.Settings.new(schema)
        settings.set_boolean(key, value.get_active())

    def get_bool(self, schema, key):
        settings = Gio.Settings.new(schema)
        return settings.get_boolean(key)

    def init_checkbox(self, schema, key, name):
        source = Gio.SettingsSchemaSource.get_default()
        if source.lookup(schema, True) != None:
            widget = self.builder.get_object(name)
            value = self.get_bool(schema, key)
            widget.set_active(value)
            widget.connect("clicked", lambda x: self.set_bool(schema, key, x))

    def init_combobox(self, schema, key, name):
        source = Gio.SettingsSchemaSource.get_default()
        if source.lookup(schema, True) != None:
            widget = self.builder.get_object(name)
            conf = self.get_string(schema, key)
            index = 0
            for row in widget.get_model():
                if(conf == row[1]):
                    widget.set_active(index)
                    break
                index = index +1
            widget.connect("changed", lambda x: self.combo_fallback(schema, key, x))

    def find_on_path(self, command):
        """Is command on the executable search path?"""
        if 'PATH' not in os.environ:
            return False
        path = os.environ['PATH']
        for element in path.split(os.pathsep):
            if not element:
                continue
            filename = os.path.join(element, command)
            if os.path.isfile(filename) and os.access(filename, os.X_OK):
                return True
        return False

    def mkdir_p(self, path):
        try:
            os.makedirs(path)
        except OSError as exc: # Python >2.5
            if exc.errno == errno.EEXIST and os.path.isdir(path):
                pass
            else:
                raise

    def get_current_wm(self):
        process = subprocess.Popen(['wmctrl', '-m'], stdout=subprocess.PIPE)
        out, err = process.communicate()
        return out

    def replace_windowmanager(self, wm):
        # Use this in python < 3.3. Python >= 3.3 has subprocess.DEVNULL
        devnull = open(os.devnull, 'wb')
        if wm == 'compiz':
            # Make sure Metacity is uses the same theme as Marco. This ensures
            # gtk-window-decorator does not incorrectly change theme when
            # switching from Marco to Compiz.
            mate_theme = self.get_string('org.mate.interface', 'gtk-theme')
            self.set_string('org.gnome.desktop.wm.preferences', 'theme', mate_theme)

            # mate-panel needs to be replaced when switching to Compiz to avoid
            # rendering screw ups.
            subprocess.Popen(['killall', 'mate-panel'], stdout=devnull, stderr=devnull)
            subprocess.Popen([wm, '--replace', 'ccp'], stdout=devnull, stderr=devnull)
            subprocess.Popen(['mate-panel', '--replace'], stdout=devnull, stderr=devnull)
        else:
            subprocess.Popen([wm, '--replace'], stdout=devnull, stderr=devnull)

        devnull.close()

    def enable_dock(self, dock):
        self.mkdir_p(os.path.expanduser('~/.config/autostart/'))
        shutil.copy2('/usr/share/applications/' + dock + '.desktop', os.path.expanduser('~/.config/autostart/'))

        devnull = open(os.devnull, 'wb')
        # Docks require compositing
        current_wm = self.get_current_wm()
        if "Marco" in current_wm:
            settings = Gio.Settings.new('org.mate.Marco.general')
            settings.set_boolean('compositing-manager', True)
            subprocess.Popen(['marco', '--replace'], stdout=devnull, stderr=devnull)

        subprocess.Popen([dock], stdout=devnull, stderr=devnull)
        devnull.close()

    def disable_dock(self, dock):
        if os.path.exists(os.path.expanduser('~/.config/autostart/') + dock + '.desktop'):
            os.remove(os.path.expanduser('~/.config/autostart/') + dock + '.desktop')
        devnull = open(os.devnull, 'wb')
        subprocess.Popen(['killall', dock], stdout=devnull, stderr=devnull)
        devnull.close()

    def enable_mate_volume_applet(self):
        devnull = open(os.devnull, 'wb')
        subprocess.Popen(['mate-volume-control-applet'], stdout=devnull, stderr=devnull)
        devnull.close()

        # If the system autostart is missing create a user autostart.
        if not os.path.exists('/etc/xdg/autostart/mate-volume-control-applet.desktop'):
            self.mkdir_p(os.path.expanduser('~/.config/autostart/'))
            shutil.copy2('/usr/share/mate-tweak/mate-volume-control-applet.desktop', os.path.expanduser('~/.config/autostart/'))

    def disable_mate_volume_applet(self):
        devnull = open(os.devnull, 'wb')
        subprocess.Popen(['killall', 'mate-volume-control-applet'], stdout=devnull, stderr=devnull)

        # If a system or user autostart exists, remove it.
        if os.path.exists(os.path.expanduser('~/.config/autostart/') + 'mate-volume-control-applet.desktop'):
            os.remove(os.path.expanduser('~/.config/autostart/') + 'mate-volume-control-applet.desktop')
        if os.path.exists('/etc/xdg/autostart/mate-volume-control-applet.desktop'):
            subprocess.Popen(['pkexec', '/usr/lib/mate-tweak/disable-mate-volume-applet'], stdout=devnull, stderr=devnull)
        devnull.close()

    def replace_panel_layout(self, panel_layout):
        # Use this in python < 3.3. Python >= 3.3 has subprocess.DEVNULL
        devnull = open(os.devnull, 'wb')
        subprocess.Popen(['killall', 'mate-panel'], stdout=devnull, stderr=devnull)
        os.system('mate-panel --layout ' + panel_layout + ' --reset')
        subprocess.Popen(['mate-panel', '--replace'], stdout=devnull, stderr=devnull)
        devnull.close()

        # Indicator enabled layouts auto-load the sound-indicator.
        # Therefore, make mate-volume-control-applet is killed or
        # started accordingly.
        if 'indicators' in panel_layout:
            self.disable_mate_volume_applet()
        else:
            self.enable_mate_volume_applet()

        # Enable/disable the appropriate dock if one of the dock ready
        # panel layouts is selected.
        dock = None
        if self.find_on_path('docky'):
            dock = 'docky'
        elif self.find_on_path('plank'):
            dock = 'plank'

        if panel_layout.startswith('eleven') and dock is not None:
            if dock is 'docky':
                self.disable_dock('plank')
            elif dock is 'plank':
                self.disable_dock('docky')

            self.enable_dock(dock)
        else:
            self.disable_dock('docky')
            self.disable_dock('plank')

    def panel_layout_exists(self, panel_layout):
        if os.path.exists('/usr/share/mate-panel/layouts/' + panel_layout + '.layout'):
            return True
        return False

    def compiz_capable(self):
        if self.find_on_path('compiz') and self.find_on_path('glxinfo'):
            process = subprocess.Popen(['glxinfo'], stdout=subprocess.PIPE)
            out, err = process.communicate()
            software_rasterizer = out.count("Software Rasterizer")
            texture_from_pixmap = out.count("texture_from_pixmap")
            if software_rasterizer == 0 and texture_from_pixmap > 2:
                return True

        return False

    def additional_tweaks(self, schema, key, value):
        if schema == "org.mate.Marco.general" and key == "button-layout":
            # If the button-layout is changed in MATE reflect that change
            # for GTK3 and GNOME.
            self.set_string("org.mate.interface", "gtk-decoration-layout", value)
            self.set_string("org.gnome.desktop.wm.preferences", "button-layout", value)

        elif schema == "org.mate.session.required-components" and key == "windowmanager":
            wm = value
            # Sanity check
            if wm not in ['compiz', 'marco']:
                wm = 'marco'

            # If the window manager is being changed, replace it now!
            print('Replacing the window manager with ' + wm)
            self.replace_windowmanager(wm)

            # As we are replacing the window manager, exit MATE Tweak.
            sys.exit(0)

        elif schema == "org.mate.panel" and key == "default-layout":
            panel_layout = value
            # If the panel layout is being changed, replace it now!
            self.replace_panel_layout(panel_layout)

    def combo_fallback(self, schema, key, widget):
        act = widget.get_active()
        value = widget.get_model()[act]
        self.set_string(schema, key, value[1])

        # Process any additional changes required for the schema and key
        self.additional_tweaks(schema, key, value[1])

    # Change pages
    def side_view_nav(self, param):
        treePaths = param.get_selected_items()
        if (len(treePaths) > 0):
            treePath = treePaths[0]
            index = int("%s" % treePath) #Hack to turn treePath into an int
            target = self.sidePages[index].notebook_index
            self.builder.get_object("notebook1").set_current_page(target)

    ''' Create the UI '''
    def __init__(self):
        # load our glade ui file in
        self.builder = Gtk.Builder()

        # FIXME: this path should not be hard-coded!
        self.builder.add_from_file('/usr/lib/mate-tweak/mate-tweak.ui')

        self.window = self.builder.get_object( "main_window" )

        self.builder.get_object("main_window").connect("destroy", Gtk.main_quit)

        side_desktop_options = SidePage(0, _("Desktop"), "user-desktop")
        side_windows = SidePage(1, _("Windows"), "preferences-system-windows")
        side_interface = SidePage(2, _("Interface"), "preferences-desktop")

        # Determine the currently active window manager
        marco_mode = False
        compiz_mode = False
        current_wm = self.get_current_wm()
        if "Marco" in current_wm:
            marco_mode = True
        elif "Compiz" in current_wm:
            compiz_mode = True

        # Do not show Marco configuration options because Marco is not
        # currently running.
        if not marco_mode:
            self.builder.get_object("frame_marco1").hide()

        if marco_mode or compiz_mode:
            # Show window manager configuration
            self.sidePages = [side_desktop_options, side_interface, side_windows]
        else:
            # Do not show window manager configuration because the
            # currently running window manager is unknown.
            self.sidePages = [side_desktop_options, side_interface]
            self.builder.get_object("frame_marco2").hide()

        # create the backing store for the side nav-view.
        theme = Gtk.IconTheme.get_default()
        self.store = Gtk.ListStore(str, GdkPixbuf.Pixbuf)
        for sidePage in self.sidePages:
            img = theme.load_icon(sidePage.icon, 36, 0)
            self.store.append([sidePage.name, img])

        target = self.sidePages[0].notebook_index
        self.builder.get_object("notebook1").set_current_page(target)

        # set up the side view - navigation.
        self.builder.get_object("side_view").set_text_column(0)
        self.builder.get_object("side_view").set_pixbuf_column(1)
        self.builder.get_object("side_view").set_model(self.store)
        self.builder.get_object("side_view").select_path(Gtk.TreePath.new_first())
        self.builder.get_object("side_view").connect("selection_changed", self.side_view_nav )

        # set up larger components.
        self.builder.get_object("main_window").set_title("MATE Tweak")
        self.builder.get_object("main_window").connect("destroy", Gtk.main_quit)

        # i18n
        self.builder.get_object("label_desktop_icons").set_markup("<b>" + _("Desktop icons") + "</b>")
        self.builder.get_object("label_performance").set_markup("<b>" + _("Performance") + "</b>")
        self.builder.get_object("label_appearance").set_markup("<b>" + _("Appearance") + "</b>")
        self.builder.get_object("label_icons").set_markup("<b>" + _("Icons") + "</b>")
        self.builder.get_object("label_context_menus").set_markup("<b>" + _("Context menus") + "</b>")
        self.builder.get_object("label_toolbars").set_markup("<b>" + _("Toolbars") + "</b>")
        self.builder.get_object("label_wm").set_markup("<b>" + _("Window Manager") + "</b>")

        self.builder.get_object("caption_desktop_icons").set_markup("<small><i><span foreground=\"#555555\">" + _("Select the items you want to see on the desktop:") + "</span></i></small>")

        self.builder.get_object("checkbox_computer").set_label(_("Computer"))
        self.builder.get_object("checkbox_home").set_label(_("Home"))
        self.builder.get_object("checkbox_network").set_label(_("Network"))
        self.builder.get_object("checkbox_trash").set_label(_("Trash"))
        self.builder.get_object("checkbox_volumes").set_label(_("Mounted Volumes"))

        self.builder.get_object("checkbutton_resources").set_label(_("Don't show window content while dragging them"))
        self.builder.get_object("checkbox_compositing").set_label(_("Use compositing"))

        self.builder.get_object("label_layouts").set_text(_("Buttons layout:"))
        self.builder.get_object("label_window_manager").set_text(_("Window manager:"))

        self.builder.get_object("checkbutton_menuicon").set_label(_("Show icons on menus"))
        self.builder.get_object("checkbutton_button_icons").set_label(_("Show icons on buttons"))
        self.builder.get_object("checkbutton_im_menu").set_label(_("Show Input Methods menu in context menus"))
        self.builder.get_object("checkbutton_unicode").set_label(_("Show Unicode Control Character menu in context menus"))

        self.builder.get_object("label_tool_icons").set_text(_("Buttons labels:"))
        self.builder.get_object("label_icon_size").set_text(_("Icon size:"))

        # Desktop page
        self.init_checkbox("org.mate.caja.desktop", "computer-icon-visible", "checkbox_computer")
        self.init_checkbox("org.mate.caja.desktop", "home-icon-visible", "checkbox_home")
        self.init_checkbox("org.mate.caja.desktop", "network-icon-visible", "checkbox_network")
        self.init_checkbox("org.mate.caja.desktop", "trash-icon-visible", "checkbox_trash")
        self.init_checkbox("org.mate.caja.desktop", "volumes-visible", "checkbox_volumes")

        # Window Manager page
        self.init_checkbox("org.mate.Marco.general", "reduced-resources", "checkbutton_resources")
        self.init_checkbox("org.mate.Marco.general", "compositing-manager", "checkbox_compositing")

        # interface page
        self.init_checkbox("org.mate.interface", "menus-have-icons", "checkbutton_menuicon")
        self.init_checkbox("org.mate.interface", "show-input-method-menu","checkbutton_im_menu")
        self.init_checkbox("org.mate.interface", "show-unicode-menu", "checkbutton_unicode")
        self.init_checkbox("org.mate.interface", "buttons-have-icons", "checkbutton_button_icons")

        iconSizes = Gtk.ListStore(str, str)
        iconSizes.append([_("Small"), "small-toolbar"])
        iconSizes.append([_("Large"), "large-toolbar"])
        self.builder.get_object("combobox_icon_size").set_model(iconSizes)
        self.init_combobox("org.mate.interface", "toolbar-icons-size", "combobox_icon_size")

        # Window control button
        layouts = Gtk.ListStore(str, str)
        layouts.append([_("Traditional (Right)"), "menu:minimize,maximize,close"])
        layouts.append([_("Contemporary (Left)"), "close,minimize,maximize:"])
        self.builder.get_object("combo_wmlayout").set_model(layouts)
        self.init_combobox("org.mate.Marco.general", "button-layout", "combo_wmlayout")

        # Window manager
        wms = Gtk.ListStore(str, str)
        wms.append([_("Marco (Simple desktop effects)"), "marco"])
        if self.compiz_capable():
            wms.append([_("Compiz (Advanced GPU accelerated desktop effects)"), "compiz"])
        self.builder.get_object("combo_wm").set_model(wms)
        self.builder.get_object("combo_wm").set_tooltip_text(_("The new window manager will be activated upon selection."))
        self.init_combobox("org.mate.session.required-components", "windowmanager", "combo_wm")

        # Panel layouts
        indicators_available = False
        if os.path.exists('/usr/lib/indicators/7/libapplication.so') \
           and os.path.exists('/usr/lib/indicator-sound-gtk2/indicator-sound-service') \
           and os.path.exists('/usr/lib/mate-indicator-applet/mate-indicator-applet'):
               indicators_available = True

        mate_menu_available = False
        if os.path.exists('/usr/lib/mate-menu/mate-menu.py'):
            mate_menu_available = True

        mint_menu_available = False
        if os.path.exists('usr/lib/linuxmint/mintMenu/mintMenu.py'):
            mint_menu_available = True

        gnome_menu_available = False
        if os.path.exists('/usr/lib/gnome-main-menu/main-menu'):
            gnome_menu_available = True

        dock = False
        if os.path.exists('/usr/bin/docky') or os.path.exists('/usr/bin/plank'):
            dock = True

        panels = Gtk.ListStore(str, str)
        if self.panel_layout_exists('default'):
            panels.append([_("MATE Desktop"), "default"])

        if self.panel_layout_exists('ubuntu-mate'):
            panels.append([_("Ubuntu MATE"), "ubuntu-mate"])

        if self.panel_layout_exists('ubuntu-mate-fresh') \
            and mate_menu_available:
            panels.append([_("Ubuntu MATE with MATE Menu"), "ubuntu-mate-fresh"])

        if self.panel_layout_exists('ubuntu-mate-indicators') \
            and indicators_available:
            panels.append([_("Ubuntu MATE with Indicators"), "ubuntu-mate-indicators"])

        if self.panel_layout_exists('ubuntu-mate-indicators-fresh') \
            and indicators_available \
            and mate_menu_available:
            panels.append([_("Ubuntu MATE with Indicators and MATE Menu"), "ubuntu-mate-indicators-fresh"])

        if self.panel_layout_exists('redmond'):
            panels.append([_("Redmond"), "redmond"])

        if self.panel_layout_exists('redmond-fresh') \
            and mate_menu_available:
            panels.append([_("Redmond with MATE Menu"), "redmond-fresh"])

        if self.panel_layout_exists('redmond-indicators') \
            and indicators_available:
            panels.append([_("Redmond with Indicators"), "redmond-indicators"])

        if self.panel_layout_exists('redmond-indicators-fresh') \
            and indicators_available \
            and mate_menu_available:
            panels.append([_("Redmond with Indicators and MATE Menu"), "redmond-indicators-fresh"])

        if dock:
            if self.panel_layout_exists('eleven'):
                panels.append([_("Eleven"), "eleven"])

            if self.panel_layout_exists('eleven-fresh') \
                and mate_menu_available:
                panels.append([_("Eleven with MATE Menu"), "eleven-fresh"])

            if self.panel_layout_exists('eleven-indicators') \
                and indicators_available:
                panels.append([_("Eleven with Indicators"), "eleven-indicators"])

            if self.panel_layout_exists('eleven-indicators-fresh') \
                and indicators_available \
                and mate_menu_available:
                panels.append([_("Eleven with Indicators and MATE Menu"), "eleven-indicators-fresh"])

        if self.panel_layout_exists('linuxmint') \
            and mint_menu_available:
            panels.append([_("Linux Mint"), "linuxmint"])

        if self.panel_layout_exists('opensuse') \
            and gnome_menu_available:
            panels.append([_("openSUSE"), "opensuse"])

        self.builder.get_object("combobox_panels").set_model(panels)
        self.builder.get_object("combobox_panels").set_tooltip_text(_("The new panel layout will be activated on selection and destroy any customisations you might have made."))
        self.init_combobox("org.mate.panel", "default-layout", "combobox_panels")

        # toolbar icon styles
        iconStyles = Gtk.ListStore(str, str)
        iconStyles.append([_("Text below items"), "both"])
        iconStyles.append([_("Text beside items"), "both-horiz"])
        iconStyles.append([_("Icons only"), "icons"])
        iconStyles.append([_("Text only"), "text"])
        self.builder.get_object("combobox_toolicons").set_model(iconStyles)
        self.init_combobox("org.mate.interface", "toolbar-style", "combobox_toolicons")
        self.builder.get_object("main_window").show()


if __name__ == "__main__":
    MateTweak()
    Gtk.main()
