1from __future__ import unicode_literals
2
3import functools
4
5from prompt_toolkit.application import Application
6from prompt_toolkit.application.current import get_app
7from prompt_toolkit.eventloop import run_in_executor
8from prompt_toolkit.key_binding.bindings.focus import (
9    focus_next,
10    focus_previous,
11)
12from prompt_toolkit.key_binding.defaults import load_key_bindings
13from prompt_toolkit.key_binding.key_bindings import (
14    KeyBindings,
15    merge_key_bindings,
16)
17from prompt_toolkit.layout import Layout
18from prompt_toolkit.layout.containers import HSplit
19from prompt_toolkit.layout.dimension import Dimension as D
20from prompt_toolkit.widgets import (
21    Box,
22    Button,
23    Dialog,
24    Label,
25    ProgressBar,
26    RadioList,
27    TextArea,
28)
29
30__all__ = [
31    'yes_no_dialog',
32    'button_dialog',
33    'input_dialog',
34    'message_dialog',
35    'radiolist_dialog',
36    'progress_dialog',
37]
38
39
40def yes_no_dialog(title='', text='', yes_text='Yes', no_text='No', style=None,
41                  async_=False):
42    """
43    Display a Yes/No dialog.
44    Return a boolean.
45    """
46    def yes_handler():
47        get_app().exit(result=True)
48
49    def no_handler():
50        get_app().exit(result=False)
51
52    dialog = Dialog(
53        title=title,
54        body=Label(text=text, dont_extend_height=True),
55        buttons=[
56            Button(text=yes_text, handler=yes_handler),
57            Button(text=no_text, handler=no_handler),
58        ], with_background=True)
59
60    return _run_dialog(dialog, style, async_=async_)
61
62
63def button_dialog(title='', text='', buttons=[], style=None,
64                  async_=False):
65    """
66    Display a dialog with button choices (given as a list of tuples).
67    Return the value associated with button.
68    """
69    def button_handler(v):
70        get_app().exit(result=v)
71
72    dialog = Dialog(
73        title=title,
74        body=Label(text=text, dont_extend_height=True),
75        buttons=[Button(text=t, handler=functools.partial(button_handler, v)) for t, v in buttons],
76        with_background=True)
77
78    return _run_dialog(dialog, style, async_=async_)
79
80
81def input_dialog(title='', text='', ok_text='OK', cancel_text='Cancel',
82                 completer=None, password=False, style=None, async_=False):
83    """
84    Display a text input box.
85    Return the given text, or None when cancelled.
86    """
87    def accept(buf):
88        get_app().layout.focus(ok_button)
89        return True  # Keep text.
90
91    def ok_handler():
92        get_app().exit(result=textfield.text)
93
94    ok_button = Button(text=ok_text, handler=ok_handler)
95    cancel_button = Button(text=cancel_text, handler=_return_none)
96
97    textfield = TextArea(
98        multiline=False,
99        password=password,
100        completer=completer,
101        accept_handler=accept)
102
103    dialog = Dialog(
104        title=title,
105        body=HSplit([
106            Label(text=text, dont_extend_height=True),
107            textfield,
108        ], padding=D(preferred=1, max=1)),
109        buttons=[ok_button, cancel_button],
110        with_background=True)
111
112    return _run_dialog(dialog, style, async_=async_)
113
114
115def message_dialog(title='', text='', ok_text='Ok', style=None, async_=False):
116    """
117    Display a simple message box and wait until the user presses enter.
118    """
119    dialog = Dialog(
120        title=title,
121        body=Label(text=text, dont_extend_height=True),
122        buttons=[
123            Button(text=ok_text, handler=_return_none),
124        ],
125        with_background=True)
126
127    return _run_dialog(dialog, style, async_=async_)
128
129
130def radiolist_dialog(title='', text='', ok_text='Ok', cancel_text='Cancel',
131                     values=None, style=None, async_=False):
132    """
133    Display a simple list of element the user can choose amongst.
134
135    Only one element can be selected at a time using Arrow keys and Enter.
136    The focus can be moved between the list and the Ok/Cancel button with tab.
137    """
138    def ok_handler():
139        get_app().exit(result=radio_list.current_value)
140
141    radio_list = RadioList(values)
142
143    dialog = Dialog(
144        title=title,
145        body=HSplit([
146            Label(text=text, dont_extend_height=True),
147            radio_list,
148        ], padding=1),
149        buttons=[
150            Button(text=ok_text, handler=ok_handler),
151            Button(text=cancel_text, handler=_return_none),
152        ],
153        with_background=True)
154
155    return _run_dialog(dialog, style, async_=async_)
156
157
158def progress_dialog(title='', text='', run_callback=None, style=None, async_=False):
159    """
160    :param run_callback: A function that receives as input a `set_percentage`
161        function and it does the work.
162    """
163    assert callable(run_callback)
164
165    progressbar = ProgressBar()
166    text_area = TextArea(
167        focusable=False,
168
169        # Prefer this text area as big as possible, to avoid having a window
170        # that keeps resizing when we add text to it.
171        height=D(preferred=10**10))
172
173    dialog = Dialog(
174        body=HSplit([
175            Box(Label(text=text)),
176            Box(text_area, padding=D.exact(1)),
177            progressbar,
178        ]),
179        title=title,
180        with_background=True)
181    app = _create_app(dialog, style)
182
183    def set_percentage(value):
184        progressbar.percentage = int(value)
185        app.invalidate()
186
187    def log_text(text):
188        text_area.buffer.insert_text(text)
189        app.invalidate()
190
191    # Run the callback in the executor. When done, set a return value for the
192    # UI, so that it quits.
193    def start():
194        try:
195            run_callback(set_percentage, log_text)
196        finally:
197            app.exit()
198
199    run_in_executor(start)
200
201    if async_:
202        return app.run_async()
203    else:
204        return app.run()
205
206
207def _run_dialog(dialog, style, async_=False):
208    " Turn the `Dialog` into an `Application` and run it. "
209    application = _create_app(dialog, style)
210    if async_:
211        return application.run_async()
212    else:
213        return application.run()
214
215
216def _create_app(dialog, style):
217    # Key bindings.
218    bindings = KeyBindings()
219    bindings.add('tab')(focus_next)
220    bindings.add('s-tab')(focus_previous)
221
222    return Application(
223        layout=Layout(dialog),
224        key_bindings=merge_key_bindings([
225            load_key_bindings(),
226            bindings,
227        ]),
228        mouse_support=True,
229        style=style,
230        full_screen=True)
231
232
233def _return_none():
234    " Button handler that returns None. "
235    get_app().exit()
236