Add a Quit dialog

Remove the contextlib pipe close feature -- file descriptors are
closed on program exit anyway.  Letting that happen normally
actually makes exiting with CTRL-C nicer (fewer race conditions).

Map "CTRL-Q" to the quit command, but pop up a yes/no dialog.

Refactor the MessageDialog into a ButtonDialog base class that
can serve MessageDialog and YesNoDialog.

Add global help text that is prepended to each screen's help text
to deal with the growing number of global commands.

Change-Id: I455344cb20fb19032a3964d602fc886e19f256e5
This commit is contained in:
James E. Blair 2014-05-02 19:38:00 -07:00
parent 5500eca644
commit 68c51ffdb7
6 changed files with 68 additions and 26 deletions

View File

@ -13,7 +13,6 @@
# under the License. # under the License.
import argparse import argparse
import contextlib
import logging import logging
import os import os
import sys import sys
@ -123,6 +122,7 @@ class App(object):
self.loop = urwid.MainLoop(screen, palette=palette, self.loop = urwid.MainLoop(screen, palette=palette,
unhandled_input=self.unhandledInput) unhandled_input=self.unhandledInput)
self.sync_pipe = self.loop.watch_pipe(self.refresh) self.sync_pipe = self.loop.watch_pipe(self.refresh)
self.loop.screen.tty_signal_keys(start='undefined', stop='undefined')
#self.loop.screen.set_terminal_properties(colors=88) #self.loop.screen.set_terminal_properties(colors=88)
if not disable_sync: if not disable_sync:
self.sync_thread = threading.Thread(target=self.sync.run, args=(self.sync_pipe,)) self.sync_thread = threading.Thread(target=self.sync.run, args=(self.sync_pipe,))
@ -134,15 +134,23 @@ class App(object):
def run(self): def run(self):
self.loop.run() self.loop.run()
def close(self): def _quit(self, widget=None):
os.close(self.sync_pipe) raise urwid.ExitMainLoop()
def quit(self):
dialog = mywid.YesNoDialog(u'Quit',
u'Are you sure you want to quit?')
urwid.connect_signal(dialog, 'no', self.backScreen)
urwid.connect_signal(dialog, 'yes', self._quit)
self.popup(dialog)
def changeScreen(self, widget): def changeScreen(self, widget):
self.status.update(title=widget.title) self.status.update(title=widget.title)
self.screens.append(self.loop.widget) self.screens.append(self.loop.widget)
self.loop.widget = widget self.loop.widget = widget
def backScreen(self): def backScreen(self, widget=None):
if not self.screens: if not self.screens:
return return
widget = self.screens.pop() widget = self.screens.pop()
@ -173,15 +181,15 @@ class App(object):
lines = self.loop.widget.help.split('\n') lines = self.loop.widget.help.split('\n')
urwid.connect_signal(dialog, 'close', urwid.connect_signal(dialog, 'close',
lambda button: self.backScreen()) lambda button: self.backScreen())
self.popup(dialog, min_width=76, min_height=len(lines)+2) self.popup(dialog, min_width=76, min_height=len(lines)+4)
def unhandledInput(self, key): def unhandledInput(self, key):
if key == 'esc': if key == 'esc':
self.backScreen() self.backScreen()
elif key == 'f1': elif key == 'f1':
self.help() self.help()
elif key in ('q', 'Q'): elif key == 'ctrl q':
raise urwid.ExitMainLoop() self.quit()
def getRepo(self, project_name): def getRepo(self, project_name):
local_path = os.path.join(self.config.git_root, project_name) local_path = os.path.join(self.config.git_root, project_name)
@ -201,5 +209,4 @@ if __name__ == '__main__':
help='the server to use (as specified in config file)') help='the server to use (as specified in config file)')
args = parser.parse_args() args = parser.parse_args()
g = App(args.server, args.debug, args.no_sync) g = App(args.server, args.debug, args.no_sync)
with contextlib.closing(g): g.run()
g.run()

View File

@ -14,6 +14,14 @@
import urwid import urwid
GLOBAL_HELP = """\
Global Keys
===========
<F1> Help
<ESC> Back to previous screen
<CTRL-Q> Quit Gertty
"""
class TextButton(urwid.Button): class TextButton(urwid.Button):
def selectable(self): def selectable(self):
return True return True
@ -44,18 +52,36 @@ class Table(urwid.WidgetWrap):
for i, widget in enumerate(cells): for i, widget in enumerate(cells):
self._w.contents[i][0].contents.append((widget, ('pack', None))) self._w.contents[i][0].contents.append((widget, ('pack', None)))
class MessageDialog(urwid.WidgetWrap): class ButtonDialog(urwid.WidgetWrap):
signals = ['close'] def __init__(self, title, message, buttons=[]):
def __init__(self, title, message): button_widgets = []
ok_button = FixedButton(u'OK') for button in buttons:
urwid.connect_signal(ok_button, 'click', button_widgets.append(('pack', button))
lambda button:self._emit('close')) button_columns = urwid.Columns(button_widgets, dividechars=2)
buttons = urwid.Columns([('pack', ok_button)],
dividechars=2)
rows = [] rows = []
rows.append(urwid.Text(message)) rows.append(urwid.Text(message))
rows.append(urwid.Divider()) rows.append(urwid.Divider())
rows.append(buttons) rows.append(button_columns)
pile = urwid.Pile(rows) pile = urwid.Pile(rows)
fill = urwid.Filler(pile, valign='top') fill = urwid.Filler(pile, valign='top')
super(MessageDialog, self).__init__(urwid.LineBox(fill, title)) super(ButtonDialog, self).__init__(urwid.LineBox(fill, title))
class MessageDialog(ButtonDialog):
signals = ['close']
def __init__(self, title, message):
ok_button = FixedButton('OK')
urwid.connect_signal(ok_button, 'click',
lambda button:self._emit('close'))
super(MessageDialog, self).__init__(title, message, buttons=[ok_button])
class YesNoDialog(ButtonDialog):
signals = ['yes', 'no']
def __init__(self, title, message):
yes_button = FixedButton('Yes')
no_button = FixedButton('No')
urwid.connect_signal(yes_button, 'click',
lambda button:self._emit('yes'))
urwid.connect_signal(no_button, 'click',
lambda button:self._emit('no'))
super(YesNoDialog, self).__init__(title, message, buttons=[yes_button,
no_button])

View File

@ -238,9 +238,10 @@ class ChangeMessageBox(urwid.Text):
self.set_text(text) self.set_text(text)
class ChangeView(urwid.WidgetWrap): class ChangeView(urwid.WidgetWrap):
help = """ help = mywid.GLOBAL_HELP + """
This Screen
===========
<r> Toggle the reviewed flag for the current change. <r> Toggle the reviewed flag for the current change.
<ESC> Go back to the previous screen.
""" """
def __init__(self, app, change_key): def __init__(self, app, change_key):

View File

@ -14,6 +14,7 @@
import urwid import urwid
import mywid
import view.change import view.change
class ChangeRow(urwid.Button): class ChangeRow(urwid.Button):
@ -64,10 +65,11 @@ class ChangeListHeader(urwid.WidgetWrap):
self._w.contents.append((urwid.Text(' %s' % category[0]), self._w.options('given', 3))) self._w.contents.append((urwid.Text(' %s' % category[0]), self._w.options('given', 3)))
class ChangeListView(urwid.WidgetWrap): class ChangeListView(urwid.WidgetWrap):
help = """ help = mywid.GLOBAL_HELP + """
This Screen
===========
<l> Toggle whether only unreviewed or all changes are displayed. <l> Toggle whether only unreviewed or all changes are displayed.
<r> Toggle the reviewed flag for the currently selected change. <r> Toggle the reviewed flag for the currently selected change.
<ESC> Go back to the previous screen.
""" """
def __init__(self, app, project_key): def __init__(self, app, project_key):

View File

@ -17,6 +17,8 @@ import logging
import urwid import urwid
import mywid
class LineContext(object): class LineContext(object):
def __init__(self, old_revision_key, new_revision_key, def __init__(self, old_revision_key, new_revision_key,
old_revision_num, new_revision_num, old_revision_num, new_revision_num,
@ -101,9 +103,10 @@ class DiffLine(urwid.Button):
self._w = urwid.AttrMap(col, None, focus_map=map) self._w = urwid.AttrMap(col, None, focus_map=map)
class DiffView(urwid.WidgetWrap): class DiffView(urwid.WidgetWrap):
help = """ help = mywid.GLOBAL_HELP + """
This Screen
===========
<Enter> Add an inline comment. <Enter> Add an inline comment.
<ESC> Go back to the previous screen.
""" """
def __init__(self, app, new_revision_key): def __init__(self, app, new_revision_key):

View File

@ -14,6 +14,7 @@
import urwid import urwid
import mywid
import sync import sync
import view.change_list import view.change_list
@ -56,7 +57,9 @@ class ProjectRow(urwid.Button):
self.reviewed_changes.set_text(str(len(project.reviewed_changes))) self.reviewed_changes.set_text(str(len(project.reviewed_changes)))
class ProjectListView(urwid.WidgetWrap): class ProjectListView(urwid.WidgetWrap):
help = """ help = mywid.GLOBAL_HELP + """
This Screen
===========
<l> Toggle whether only subscribed projects or all projects are listed. <l> Toggle whether only subscribed projects or all projects are listed.
<s> Toggle the subscription flag for the currently selected project. <s> Toggle the subscription flag for the currently selected project.
""" """