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