Codebase list lightdm-gtk-greeter-settings / debian/1.2.2-2 lightdm_gtk_greeter_settings / MultiheadSetupDialog.py
debian/1.2.2-2

Tree @debian/1.2.2-2 (Download .tar.gz)

MultiheadSetupDialog.py @debian/1.2.2-2raw · history · blame

#!/usr/bin/env python3
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#   LightDM GTK Greeter Settings
#   Copyright (C) 2014 Andrew P. <pan.pav.7c5@gmail.com>
#
#   This program is free software: you can redistribute it and/or modify it
#   under the terms of the GNU General Public License version 3, as published
#   by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranties of
#   MERCHANTABILITY, SATISFACTORY QUALITY, 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/>.


from collections import defaultdict

from gi.repository import (
    Gdk,
    Gtk)

import lightdm_gtk_greeter_settings.helpers
from lightdm_gtk_greeter_settings.helpers import (
    check_path_accessibility,
    get_data_path,
    WidgetsEnum)

from gi.overrides import GLib


__all__ = ['MultiheadSetupDialog']

C_ = lambda t: lightdm_gtk_greeter_settings.helpers.C_('option|multihead', t)


class MultiheadSetupDialog(Gtk.Dialog):
    __gtype_name__ = 'MultiheadSetupDialog'

    def __new__(cls, monitors):
        builder = Gtk.Builder()
        builder.add_from_file(get_data_path('%s.ui' % cls.__gtype_name__))
        window = builder.get_object('multihead_setup_dialog')
        window.builder = builder
        window.monitors = monitors
        builder.connect_signals(window)
        window.init_window()
        return window

    class Widgets(WidgetsEnum):
        notebook = 'monitors_notebook'
        available_menu = 'available_menu'
        name = 'name_value'
        name_combo = 'name_combo'

        editor = 'editor_page'
        editor_add_button = 'editor_add_button'
        editor_add_menu_button = 'editor_add_menu_button'
        empty = 'empty_page'
        empty_add_button = 'empty_add_button'
        empty_add_menu_button = 'empty_add_menu_button'

    class PageData:
        group = None
        holder = None
        label = None
        ids = None
        # Entries
        name = None
        background = None

    builder = None
    monitors = None

    def init_window(self):
        self._widgets = self.Widgets(builder=self.builder)

        # To set size of widgets.notebook to size of widgets.editor
        self.realize()

        self._widgets.notebook.remove_page(self._widgets.notebook.page_num(self._widgets.editor))

        self._current_page = None
        self._defaults = {}
        self._page_to_data = {}

        screen = Gdk.Screen.get_default()
        self._available_monitors = [(screen.get_monitor_plug_name(i),
                                     Gtk.MenuItem(screen.get_monitor_plug_name(i)))
                                    for i in range(screen.get_n_monitors())]

        menu_header = Gtk.MenuItem(C_('Detected monitors:'))
        menu_header.set_sensitive(False)
        self._widgets.available_menu.append(menu_header)
        self._widgets.available_menu.append(Gtk.SeparatorMenuItem())
        self._widgets.available_menu.show_all()
        for name, item in self._available_monitors:
            self._widgets.available_menu.append(item)
            item.connect('activate', self.on_add_button_clicked, name)

    def run(self):
        editor_parent = self._widgets.editor.get_parent()
        if editor_parent:
            editor_parent.remove(self._widgets.editor)

        self._widgets.notebook.handler_block_by_func(self.on_switch_page)
        for page in self._widgets.notebook.get_children():
            if page != self._widgets.empty:
                self._widgets.notebook.remove_page(self._widgets.notebook.page_num(page))
        self._widgets.notebook.handler_unblock_by_func(self.on_switch_page)

        for data in self._page_to_data.values():
            for entry, ids in data.ids.items():
                for id_ in ids:
                    entry.disconnect(id_)

        self._page_to_data.clear()

        for group in self.monitors.groups:
            self._add_page(group)

        self._widgets.empty.props.visible = not self._page_to_data
        self._update_monitors_list()

        super().run()

    def _add_page(self, group):
        data = self.PageData()
        data.group = group

        data.holder = Gtk.Box()
        data.holder.show()
        data.label = Gtk.Label(group.entries['name'].value)

        close_button = Gtk.Button()
        close_button.set_focus_on_click(False)
        close_button.set_relief(Gtk.ReliefStyle.NONE)
        close_button.connect('clicked', lambda w, p: self._remove_page(p), data.holder)
        close_image = Gtk.Image.new_from_icon_name('stock_close', Gtk.IconSize.INVALID)
        close_image.set_pixel_size(16)
        close_button.add(close_image)

        label_box = Gtk.Box(Gtk.Orientation.HORIZONTAL)
        label_box.pack_start(data.label, False, False, 3)
        label_box.pack_start(close_button, False, False, 0)

        label_eventbox = Gtk.EventBox()
        label_eventbox.add(label_box)
        label_eventbox.show_all()

        data.name = group.entries['name']
        data.background = group.entries['background']

        data.ids = defaultdict(list)
        data.ids[data.name].append(data.name.changed.connect(self._on_name_changed, data))
        data.ids[data.background].append(
            data.background.changed.connect(self._on_background_changed, data))

        self._on_name_changed(data.name, data)
        self._on_background_changed(data.background, data)

        self._page_to_data[data.holder] = data

        if self._widgets.empty.get_parent():
            self._widgets.empty.hide()

        current_idx = self._widgets.notebook.get_current_page()
        current_idx = self._widgets.notebook.insert_page(data.holder, label_eventbox,
                                                         current_idx + 1)

        return current_idx

    def _remove_page(self, page):
        if page == self._widgets.editor.props.parent:
            page.remove(self._widgets.editor)

        self._widgets.notebook.remove_page(self._widgets.notebook.page_num(page))

        del self.monitors.groups[self._page_to_data[page].group]
        del self._page_to_data[page]

        self._update_monitors_list()
        if not self._page_to_data:
            self._widgets.empty.show()

    def _update_monitors_list(self):
        configs = set(group.entries['name'].value for group in self.monitors.groups)
        used_count = 0
        self._widgets.name_combo.get_model().clear()
        for name, item in self._available_monitors:
            used = name in configs
            if used:
                used_count += 1
            item.props.visible = not used
            if not used:
                self._widgets.name_combo.append_text(name)

        show_button = used_count < len(self._available_monitors)
        self._widgets.name_combo.props.button_sensitivity = (Gtk.SensitivityType.ON if show_button
                                                             else Gtk.SensitivityType.OFF)
        self._widgets.editor_add_menu_button.props.visible = show_button
        self._widgets.empty_add_menu_button.props.visible = show_button

    def _on_name_changed(self, entry, data):
        value = entry.value
        markup = None
        error = None

        if not value:
            markup = C_('<i>No name</i>')
            error = C_('The name can\'t be empty. Configuration will not be saved.')
        elif any(data.name != entry and data.name.value == value
                 for data in self._page_to_data.values()):
            error = (C_('"{name}" is already defined. Only last configuration will be saved.')
                     .format(name=value))

        if markup:
            data.label.set_markup(markup)
        else:
            data.label.set_label(value)

        data.name.error = error
        self._update_monitors_list()

    def _on_background_changed(self, entry, data):
        value = entry.value
        if not value or Gdk.RGBA().parse(value):
            entry.error = None
        else:
            entry.error = check_path_accessibility(value)

    def _focus_name_entry(self):
        self._widgets.name.grab_focus()
        self._widgets.name.set_position(0)

    def on_add_button_clicked(self, widget, name=''):
        group = self.monitors.groups.add(name)
        page_idx = self._add_page(group)

        self._widgets.empty.props.visible = not self._page_to_data
        self._widgets.notebook.set_current_page(page_idx)
        if name:
            self._update_monitors_list()

    def on_switch_page(self, notebook, page, page_idx):
        if page == self._widgets.empty:
            buttons = self._widgets.editor_add_menu_button, self._widgets.empty_add_menu_button
        else:
            buttons = self._widgets.empty_add_menu_button, self._widgets.editor_add_menu_button

            old_parent = self._widgets.editor.get_parent()
            if old_parent:
                old_parent.remove(self._widgets.editor)
            page.add(self._widgets.editor)

            data = self._page_to_data[page]
            for key, *__ in self.monitors.EntriesSetup:
                self.monitors.activate(key, data.group.entries[key])

            GLib.idle_add(self._focus_name_entry)

        buttons[0].props.popup = None
        buttons[1].props.popup = self._widgets.available_menu