commit f9cf4bbc821356f7918860166d23f682ece39846 Author: Damian Johnson atagar@torproject.org Date: Sun Apr 3 12:45:31 2016 -0700
Revise sort order dialog
Rewriting this sucker. Quite a bit shorter, cleaner, and now tested. --- nyx/popups.py | 145 ++++++++++++++++++++----------------------------------- test/__init__.py | 2 +- test/popups.py | 62 ++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 93 deletions(-)
diff --git a/nyx/popups.py b/nyx/popups.py index 4a51e0c..b0cd48e 100644 --- a/nyx/popups.py +++ b/nyx/popups.py @@ -199,114 +199,75 @@ def show_counts(title, counts, fill_char = ' '): nyx.curses.key_input()
-def show_sort_dialog(title, options, old_selection, option_colors): +def show_sort_dialog(title, options, previous_order, option_colors): """ - Displays a sorting dialog of the form: + Provides sorting dialog of the form...
- Current Order: <previous selection> - New Order: <selections made> + Current Order: <previous order> + New Order: <selected options>
<option 1> <option 2> <option 3> Cancel
- Options are colored when among the "Current Order" or "New Order", but not - when an option below them. If cancel is selected or the user presses escape - then this returns None. Otherwise, the new ordering is provided. + :param str title: dialog title + :param list options: sort options to be provided + :param list previous_order: previous ordering + :param dict optoin_colors: mapping of options to their color
- Arguments: - title - title displayed for the popup window - options - ordered listing of option labels - old_selection - current ordering - option_colors - mappings of options to their color + :returns: **list** of the new sort order or **None** if dialog is canceled """
- with popup_window(9, 80) as (popup, _, _): - if popup: - new_selections = [] # new ordering - cursor_location = 0 # index of highlighted option - - selection_options = list(options) - selection_options.append('Cancel') - - while len(new_selections) < len(old_selection): - popup.win.erase() - popup.draw_box() - popup.addstr(0, 0, title, HIGHLIGHT) - - _draw_sort_selection(popup, 1, 2, 'Current Order: ', old_selection, option_colors) - _draw_sort_selection(popup, 2, 2, 'New Order: ', new_selections, option_colors) - - # presents remaining options, each row having up to four options with - # spacing of nineteen cells - - row, col = 4, 0 - - for i in range(len(selection_options)): - option_format = HIGHLIGHT if cursor_location == i else NORMAL - popup.addstr(row, col * 19 + 2, selection_options[i], option_format) - col += 1 - - if col == 4: - row, col = row + 1, 0 - - popup.win.refresh() - - key = nyx.curses.key_input() - - if key.match('left'): - cursor_location = max(0, cursor_location - 1) - elif key.match('right'): - cursor_location = min(len(selection_options) - 1, cursor_location + 1) - elif key.match('up'): - cursor_location = max(0, cursor_location - 4) - elif key.match('down'): - cursor_location = min(len(selection_options) - 1, cursor_location + 4) - elif key.is_selection(): - selection = selection_options[cursor_location] - - if selection == 'Cancel': - break - else: - new_selections.append(selection) - selection_options.remove(selection) - cursor_location = min(cursor_location, len(selection_options) - 1) - elif key.match('esc'): - break # esc - cancel + new_order = [] + cursor_index = 0 + shown_options = list(options) + ['Cancel']
- if len(new_selections) == len(old_selection): - return new_selections - else: - return None + def _draw_selection(subwindow, y, label, selection): + x = subwindow.addstr(2, y, label, BOLD)
+ for i, option in enumerate(selection): + x = subwindow.addstr(x, y, option, option_colors.get(option, WHITE), BOLD)
-def _draw_sort_selection(popup, y, x, prefix, options, option_colors): - """ - Draws a series of comma separated sort selections. The whole line is bold - and sort options also have their specified color. Example: + if i < len(selection) - 1: + x = subwindow.addstr(x, y, ', ', BOLD)
- Current Order: Man Page Entry, Option Name, Is Default - - Arguments: - popup - panel in which to draw sort selection - y - vertical location - x - horizontal location - prefix - initial string description - options - sort options to be shown - option_colors - mappings of options to their color - """ + def _render(subwindow): + subwindow.box() + subwindow.addstr(0, 0, title, HIGHLIGHT)
- popup.addstr(y, x, prefix, BOLD) - x += len(prefix) + _draw_selection(subwindow, 1, 'Current Order: ', previous_order) + _draw_selection(subwindow, 2, 'New Order: ', new_order)
- for i in range(len(options)): - sort_type = options[i] - popup.addstr(y, x, sort_type, option_colors.get(sort_type, WHITE), BOLD) - x += len(sort_type) + # presents remaining options, each row having up to four options
- # comma divider between options, if this isn't the last + for i, option in enumerate(shown_options): + attr = HIGHLIGHT if i == cursor_index else NORMAL + subwindow.addstr((i % 4) * 19 + 2, (i / 4) + 4, option, attr)
- if i < len(options) - 1: - popup.addstr(y, x, ', ', BOLD) - x += 2 + with nyx.curses.CURSES_LOCK: + while len(new_order) < len(previous_order): + nyx.curses.draw(_render, top = nyx.controller.get_controller().header_panel().get_height(), width = 80, height = 9) + key = nyx.curses.key_input() + + if key.match('left'): + cursor_index = max(0, cursor_index - 1) + elif key.match('right'): + cursor_index = min(len(shown_options) - 1, cursor_index + 1) + elif key.match('up'): + cursor_index = max(0, cursor_index - 4) + elif key.match('down'): + cursor_index = min(len(shown_options) - 1, cursor_index + 4) + elif key.is_selection(): + selection = shown_options[cursor_index] + + if selection == 'Cancel': + return None + else: + new_order.append(selection) + shown_options.remove(selection) + cursor_index = min(cursor_index, len(shown_options) - 1) + elif key.match('esc'): + return None + + return new_order
def show_menu(title, options, old_selection): diff --git a/test/__init__.py b/test/__init__.py index 0884866..705e9b1 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -51,7 +51,7 @@ def render(func, *args, **kwargs): if SHOW_RENDERED_CONTENT: time.sleep(SHOW_RENDERED_CONTENT)
- with patch('nyx.curses.key_input', return_value = Mock()): + with patch('nyx.curses.key_input', return_value = nyx.curses.KeyInput(27)): nyx.curses.start(draw_func, transparent_background = True, cursor = False)
return RenderResult(attr.get('content'), attr.get('return_value'), attr.get('runtime')) diff --git a/test/popups.py b/test/popups.py index b82c9a2..8a7e76b 100644 --- a/test/popups.py +++ b/test/popups.py @@ -2,6 +2,7 @@ Unit tests for nyx.popups. """
+import curses import unittest
import nyx.panel @@ -52,6 +53,30 @@ Client Locales-----------------------------------------------------------------+ +------------------------------------------------------------------------------+ """.strip()
+EXPECTED_SORT_DIALOG_START = """ +Config Option Ordering:--------------------------------------------------------+ +| Current Order: Man Page Entry, Name, Is Set | +| New Order: | +| | +| Name Value Value Type Category | +| Usage Summary Description Man Page Entry | +| Is Set Cancel | +| | ++------------------------------------------------------------------------------+ +""".strip() + +EXPECTED_SORT_DIALOG_END = """ +Config Option Ordering:--------------------------------------------------------+ +| Current Order: Man Page Entry, Name, Is Set | +| New Order: Name, Summary | +| | +| Value Value Type Category Usage | +| Description Man Page Entry Is Set Cancel | +| | +| | ++------------------------------------------------------------------------------+ +""".strip() +
class TestPopups(unittest.TestCase): @patch('nyx.controller.get_controller') @@ -117,3 +142,40 @@ class TestPopups(unittest.TestCase):
rendered = test.render(nyx.popups.show_counts, 'Client Locales', clients, fill_char = '*') self.assertEqual(EXPECTED_COUNTS, rendered.content) + + @patch('nyx.controller.get_controller') + def test_sort_dialog(self, get_controller_mock): + get_controller_mock().header_panel().get_height.return_value = 0 + + previous_order = ['Man Page Entry', 'Name', 'Is Set'] + options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set'] + + rendered = test.render(nyx.popups.show_sort_dialog, 'Config Option Ordering:', options, previous_order, {}) + self.assertEqual(EXPECTED_SORT_DIALOG_START, rendered.content) + self.assertEqual(None, rendered.return_value) + + @patch('nyx.controller.get_controller') + def test_sort_dialog_selecting(self, get_controller_mock): + # Use the dialog to make a selection. At the end we render two options as + # being selected (rather than three) because the act of selecing the third + # closed the popup. + + keypresses = [ + nyx.curses.KeyInput(curses.KEY_ENTER), + nyx.curses.KeyInput(curses.KEY_DOWN), + nyx.curses.KeyInput(curses.KEY_ENTER), + nyx.curses.KeyInput(curses.KEY_ENTER), + ] + + def draw_func(): + with patch('nyx.curses.key_input', side_effect = keypresses): + return nyx.popups.show_sort_dialog('Config Option Ordering:', options, previous_order, {}) + + get_controller_mock().header_panel().get_height.return_value = 0 + + previous_order = ['Man Page Entry', 'Name', 'Is Set'] + options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set'] + + rendered = test.render(draw_func) + self.assertEqual(EXPECTED_SORT_DIALOG_END, rendered.content) + self.assertEqual(['Name', 'Summary', 'Description'], rendered.return_value)