Source

pygtkhelpers-main / pygtkhelpers / ui / dialogs.py

Full commit
# -*- coding: utf-8 -*-

"""
    pygtkhelpers.ui.dialogs
    ~~~~~~~~~~~~~~~~~~~~~~~~

    Dialog helpers

    largely inspired by kiwi.ui.dialogs

    :copyright: 2009-2010 by pygtkhelpers Authors
    :license: LGPL 2 or later (see README/COPYING/LICENSE)
"""

#XXX: i18n
import os

from gi.repository import Gtk, GObject
from functools import partial

image_types = {
    Gtk.MessageType.INFO: Gtk.STOCK_DIALOG_INFO,
    Gtk.MessageType.WARNING : Gtk.STOCK_DIALOG_WARNING,
    Gtk.MessageType.QUESTION : Gtk.STOCK_DIALOG_QUESTION,
    Gtk.MessageType.ERROR : Gtk.STOCK_DIALOG_ERROR,
}

button_types = {
    Gtk.ButtonsType.NONE: (),
    Gtk.ButtonsType.OK: (
        Gtk.STOCK_OK,
        Gtk.ResponseType.OK,
    ),
    Gtk.ButtonsType.CLOSE: (
        Gtk.STOCK_CLOSE,
        Gtk.ResponseType.CLOSE,
    ),
    Gtk.ButtonsType.CANCEL: (
        Gtk.STOCK_CANCEL,
        Gtk.ResponseType.CANCEL,
    ),
    Gtk.ButtonsType.YES_NO: (
        Gtk.STOCK_NO,
        Gtk.ResponseType.NO,
        Gtk.STOCK_YES,
        Gtk.ResponseType.YES,
    ),
    Gtk.ButtonsType.OK_CANCEL: (
        Gtk.STOCK_CANCEL,
        Gtk.ResponseType.CANCEL,
        Gtk.STOCK_OK,
        Gtk.ResponseType.OK,
    ),
}

def _destroy(obj):
    #XXX: util?
    obj.destroy()
    if not Gtk.main_level():
        from pygtkhelpers.utils import refresh_gui
        refresh_gui()



class AlertDialog(Gtk.Dialog):
    def __init__(self, parent, flags,
                 type=Gtk.MessageType.INFO,
                 buttons=Gtk.ButtonsType.NONE,
                 ):
        #XXX: better errors
        assert type in image_types, 'not a valid type'
        assert buttons in button_types, 'not a valid set of buttons'

        Gtk.Dialog.__init__(self, title=' ', parent=parent, flags=flags)
        self.set_border_width(5)
        self.set_resizable(False)
        self.set_skip_taskbar_hint(True)

        self.primary = Gtk.Label()
        self.secondary = Gtk.Label()
        self.details = Gtk.Label()
        self.image = Gtk.Image.new_from_stock(
                    image_types[type],
                    Gtk.IconSize.DIALOG
                    )
        self.image.set_alignment(0.0, 0.5)
        self.primary.set_use_markup(True)

        for label in (self.primary, self.secondary, self.details):
            label.set_line_wrap(True)
            label.set_selectable(True)
            label.set_alignment(0.0, 0.5)

        hbox = Gtk.HBox(homogeneous=False, spacing=12)
        hbox.set_border_width(5)
        hbox.pack_start(self.image, False, False, 0)

        self.label_vbox = vbox = Gtk.VBox(homogeneous=False, spacing=0)
        hbox.pack_start(vbox, False, False, 0)
        vbox.pack_start(self.primary, False, False, 0)
        vbox.pack_start(self.secondary, False, False, 0)

        self.expander = Gtk.Expander.new_with_mnemonic(
                'show more _details'
                )
        self.expander.set_spacing(5)
        self.expander.add(self.details)
        vbox.pack_start(self.expander, False, False, 0)
	self.vbox = self.get_child()
        self.vbox.pack_start(hbox, False, False, 0)
        hbox.show_all()
        self.expander.hide()
        self._buttons = button_types[buttons]
        self.add_buttons(*self._buttons)


    def set_primary(self, text):
        #XXX: escape
        self.primary.set_markup(
                '<span weight="bold" size="larger">%s</span>'%(text,)
                )
    def set_secondary(self, text):
        self.set_secondary.set_markup(text)

    def set_details(self, text):
        self.details.set_markup(text)
        self.expander.show()

    def set_details_widget(self, widget):
        self.expander.remove(self.details)
        self.details = widget
        self.expander.add(widget)
        self.expander.show()


def _message_dialog(type, short,
                    long=None,
                    parent=None,
                    buttons=Gtk.ButtonsType.OK,
                    default=None, #XXX: kiwi had -1 there, why?
                    _before_run=None): # for unittests

    if buttons in button_types:
        dialog_buttons = buttons
        buttons = []
    else:
        assert buttons is None or isinstance(buttons, tuple)
        dialog_buttons = Gtk.ButtonsType.NONE

    assert parent is None or isinstance(parent, Gtk.Window)


    dialog = AlertDialog(
                parent=parent,
                flags=Gtk.DialogFlags.MODAL,
                type=type,
                buttons = dialog_buttons,
                )
    dialog.set_primary(short)

    if long:
        #XXX: test all cases
        if isinstance(long, Gtk.Widget):
            dialog.set_details_widget(long)
        elif isinstance(long, basestring):
            dialog.set_details(long)
        else:
            raise TypeError('long must be a string or a Widget, not %r'%long)

    if default is not None:
        dialog.set_default_response(default)
    if parent:
        dialog.set_transient_for(parent)
        dialog.set_modal(True)

    if _before_run is not None:
        _before_run(dialog)

    response = dialog.run()
    _destroy(dialog)
    return response


def simple(type, short, long=None,
           parent=None, buttons=Gtk.ButtonsType.OK, default=None, **kw):
    """A simple dialog

    :param type: The type of dialog
    :param short: The short description
    :param long: The long description
    :param parent: The parent Window to make this dialog transient to
    :param buttons: A buttons enum
    :param default: A default response
    """
    if buttons == Gtk.ButtonsType.OK:
        default = Gtk.ResponseType.OK
    return _message_dialog(type, short, long,
                         parent=parent, buttons=buttons,
                         default=default, **kw)


#: Show an error dialog, see :func:`~pygtkhelpers.ui.dialogs.simple` parameters
error = partial(simple, Gtk.MessageType.ERROR)


#: Show an info dialog, see :func:`~pygtkhelpers.ui.dialogs.simple` parameters
info = partial(simple, Gtk.MessageType.INFO)


#: Show a warning dialog, see :func:`~pygtkhelpers.ui.dialogs.simple` parameters
warning = partial(simple, Gtk.MessageType.WARNING)


#:  A yes/no question dialog, see :func:`~pygtkhelpers.ui.dialogs.simple` parameters
yesno = partial(simple, Gtk.MessageType.WARNING,
                default=Gtk.ResponseType.YES,
                buttons=Gtk.ButtonsType.YES_NO,)



def open_filechooser(
    title, parent=None, patterns=None,
    folder=None, filter=None, _before_run=None,
    action=None):
    """An open dialog.

    :param parent: window or None
    :param patterns: file match patterns
    :param folder: initial folder
    :param filter: file filter

    Use of filter and patterns at the same time is invalid.
    """

    assert not (patterns and filter)
    assert action is not None
    filechooser = Gtk.FileChooserDialog(title,
                                        parent,
                                        action,
                                        (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                                         Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
    if patterns or filter:
        if not filter:
            filter = Gtk.FileFilter()
            for pattern in patterns:
                filter.add_pattern(pattern)
        filechooser.set_filter(filter)
    filechooser.set_default_response(Gtk.ResponseType.OK)

    if folder:
        filechooser.set_current_folder(folder)

    try:
        if _before_run is not None:
            _before_run(filechooser)
        response = filechooser.run()
        print Gtk.ResponseType(response)
        if response not in (Gtk.ResponseType.OK, Gtk.ResponseType.NONE):
            return

        path = filechooser.get_filename()
        print path
        if path and os.access(path, os.R_OK):
            return path

    finally:
        _destroy(filechooser)


open = partial(open_filechooser,
               title='Open',
               action=Gtk.FileChooserAction.OPEN)

select_folder = partial(open_filechooser,
                        title='Select folder',
                        action=Gtk.FileChooserAction.SELECT_FOLDER)

def ask_overwrite(filename, parent=None, **kw):
    submsg1 = 'A file named "%s" already exists' % os.path.abspath(filename)
    submsg2 = 'Do you wish to replace it with the current one?'
    text = ('<span weight="bold" size="larger">%s</span>\n'
            '\n%s\n' % (submsg1, submsg2))
    result = messagedialog(Gtk.MessageType.ERROR, text, parent=parent,
                           buttons=((Gtk.STOCK_CANCEL,
                                     Gtk.ResponseType.CANCEL),
                                    (_("Replace"),
                                     Gtk.ResponseType.YES)),
                                    **kw)
    return result == Gtk.ResponseType.YES



def save(title='Save', parent=None, current_name='', folder=None,
        _before_run=None, _before_overwrite=None):
    """Displays a save dialog."""
    filechooser = Gtk.FileChooserDialog(title, parent,
                                        Gtk.FileChooserAction.SAVE,
                                        (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                                         Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
    if current_name:
        filechooser.set_current_name(current_name)
    filechooser.set_default_response(Gtk.ResponseType.OK)

    if folder:
        filechooser.set_current_folder(folder)

    path = None
    while True:
        if _before_run:
            _before_run(filechooser)
            _before_run = None #XXX: find better implications
        response = filechooser.run()
        if response != Gtk.ResponseType.OK:
            path = None
            break

        path = filechooser.get_filename()
        if not os.path.exists(path):
            break

        if ask_overwrite(path, parent, _before_run=_before_overwrite):
            break
        _before_overwrite = None #XXX: same
    _destroy(filechooser)
    return path



def input(title, value=None, label=None, parent=None, _before_run=None):
    d = Gtk.Dialog(
        title=title,
        buttons=button_types[Gtk.ButtonsType.OK_CANCEL]
    )

    e = Gtk.Entry()
    if value:
        e.set_text(value)

    if label is None:
        e.show()
        d.vbox.pack_start(e, True, True, 0)
    else:
        hbox = Gtk.HBox()
        hbox.set_spacing(6)
        hbox.set_border_width(6)
        hbox.add(Gtk.Label(label=label))
        hbox.add(e)
        hbox.show_all()
        d.vbox.add(hbox)

    if parent:
        d.set_transient_for(parent)

    if _before_run:
        _before_run(d)
    r = d.run()
    res = e.get_text()
    d.hide()
    d.destroy()
    if r == Gtk.ResponseType.OK:
        return res