Skip to content

Commit

Permalink
Merge branch 'release/0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
pmbarrett314 committed Mar 4, 2016
2 parents d13d51f + 8fa6487 commit c76fc00
Show file tree
Hide file tree
Showing 13 changed files with 73 additions and 31 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/unittests.txt
/run.cmd
/tmp/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ env:
- TOX_ENV=pypy
- TOX_ENV=pypy3
- TOX_ENV=docs
- TOX_ENV=pep8

matrix:
include:
Expand All @@ -31,6 +32,7 @@ matrix:
- python: "nightly"
- env: TOX_ENV=py26
- env: TOX_ENV=py36
- env: TOX_ENV=pep8
fast_finish: true


4 changes: 3 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ curses-menu

A simple Python menu-based GUI system on the terminal using curses.
Perfect for those times when you need a GUI, but don’t want the overhead
or learning curve of a full-fledged GUI framework.
or learning curve of a full-fledged GUI framework. However, it's also
flexible enough to do cool stuff like on-the-fly changing of menus and is extensible to
a large variety of uses.

http://curses-menu.readthedocs.org/en/latest/

Expand Down
28 changes: 22 additions & 6 deletions cursesmenu/curses_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class CursesMenu(object):
is currently active (E.G. when switching between menus)
"""
currently_active_menu = None
stdscr = None

def __init__(self, title=None, subtitle=None, show_exit_option=True):
"""
Expand Down Expand Up @@ -95,6 +96,11 @@ def append_item(self, item):
self.items.append(item)
if did_remove:
self.add_exit()
if self.screen:
max_row, max_cols = self.screen.getmaxyx()
if max_row < 6 + len(self.items):
self.screen.resize(6 + len(self.items), max_cols)
self.draw()

def add_exit(self):
"""
Expand Down Expand Up @@ -126,7 +132,7 @@ def _wrap_start(self):
if self.parent is None:
curses.wrapper(self._main_loop)
else:
self._main_loop(curses.initscr())
self._main_loop(None)
CursesMenu.currently_active_menu = None
self.clear_screen()
clear_terminal()
Expand Down Expand Up @@ -174,9 +180,12 @@ def show(self, show_exit_option=None):
self.join()

def _main_loop(self, scr):
self.screen = scr
if scr is not None:
CursesMenu.stdscr = scr
self.screen = curses.newpad(len(self.items) + 6, CursesMenu.stdscr.getmaxyx()[1])
self._set_up_colors()
curses.curs_set(0)
CursesMenu.stdscr.refresh()
self.draw()
CursesMenu.currently_active_menu = self
self._running.set()
Expand All @@ -187,8 +196,6 @@ def draw(self):
"""
Redraws the menu and refreshes the screen. Should be called whenever something changes that needs to be redrawn.
"""
if self.screen.getmaxyx()[0] < 5 + len(self.items):
raise Exception("There are too many items to fit in your terminal")

self.screen.border(0)
if self.title is not None:
Expand All @@ -202,7 +209,16 @@ def draw(self):
else:
text_style = self.normal
self.screen.addstr(5 + index, 4, item.show(index), text_style)
self.screen.refresh()

screen_rows, screen_cols = CursesMenu.stdscr.getmaxyx()
top_row = 0
if 6 + len(self.items) > screen_rows:
if screen_rows + self.current_option < 6 + len(self.items):
top_row = self.current_option
else:
top_row = 6 + len(self.items) - screen_rows

self.screen.refresh(top_row, 0, 0, 0, screen_rows - 1, screen_cols - 1)

def is_running(self):
"""
Expand Down Expand Up @@ -253,7 +269,7 @@ def get_input(self):
:return: the ordinal value of a single character
:rtype: int
"""
return self.screen.getch()
return CursesMenu.stdscr.getch()

def process_user_input(self):
"""
Expand Down
3 changes: 3 additions & 0 deletions cursesmenu/selection_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ def get_selection(cls, strings, title="Select an option", subtitle=None, exit_op
menu.show()
menu.join()
return menu.selected_option

def append_string(self, string):
self.append_item(SelectionItem(string))
2 changes: 1 addition & 1 deletion cursesmenu/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.4.1"
__version__ = "0.5.0"
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ snowballstemmer==1.2.1
Sphinx==1.3.5
sphinx-rtd-theme==0.1.9
tox==2.3.1
virtualenv==14.0.3
virtualenv==14.0.6
6 changes: 5 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
[wheel]
universal = 1
universal = 1
[pytest]
pep8maxlinelength = 120
pep8ignore =
docs/conf.py E402
13 changes: 9 additions & 4 deletions test/base_test_case.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import sys
import curses
from threading import Thread

if sys.version_info < (2, 7):
import unittest2 as unittest
Expand All @@ -9,12 +11,14 @@
from unittest.mock import Mock, patch
except ImportError:
from mock import Mock, patch
import curses
from threading import Thread


class ThreadedReturnGetter(Thread):
def __init__(self, function, args=list(), kwargs=dict()):
def __init__(self, function, args=None, kwargs=None):
if args is None:
args = []
if kwargs is None:
kwargs = {}
self.return_value = None
self.function = function
try:
Expand All @@ -31,8 +35,9 @@ def get_return_value(self, *args, **kwargs):
class BaseTestCase(unittest.TestCase):
def setUp(self):
self.mock_curses = Mock(spec=curses)
self.mock_window = Mock(spec=['keypad', 'addstr', 'border', 'getch', 'refresh', 'clear'])
self.mock_window = Mock(spec=['keypad', 'addstr', 'border', 'getch', 'refresh', 'clear', 'getmaxyx'])
self.mock_window.getch.return_value = ord('a')
self.mock_window.getmaxyx.return_value = (999999999, 999999999)
self.mock_curses.initscr.return_value = self.mock_window
self.mock_curses.wrapper.side_effect = lambda x: x(self.mock_window)

Expand Down
8 changes: 4 additions & 4 deletions test/test_curses_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def setUp(self):
self.menu.append_item(self.item1)
self.menu.append_item(self.item2)
self.menu.start()
self.menu.wait_for_start(10)
self.menu.wait_for_start(timeout=10)

def tearDown(self):
super(TestSampleMenu, self).tearDown()
self.menu.exit()
self.menu.join()
self.menu.join(timeout=10)

def test_go_down(self):
self.menu.go_down()
Expand Down Expand Up @@ -59,12 +59,12 @@ def test_select(self):
self.menu.select()
self.assertEqual(self.menu.selected_option, 2)
self.assertIs(self.menu.selected_item, self.menu.exit_item)
self.menu.join(timeout=5)
self.menu.join(timeout=10)
self.assertFalse(self.menu.is_alive())

def test_exit(self):
self.menu.exit()
self.menu.join(timeout=5)
self.menu.join(timeout=10)
self.assertFalse(self.menu.is_alive())


Expand Down
6 changes: 3 additions & 3 deletions test/test_selection_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_get_selection(self):
while not menu:
continue
menu = menu[0]
menu.wait_for_start()
menu.wait_for_start(timeout=10)
menu.go_down()
menu.select()
print(menu.returned_value)
Expand All @@ -45,13 +45,13 @@ def test_current_menu(self):
while not menu:
continue
menu = menu[0]
menu.wait_for_start(10)
menu.wait_for_start(timeout=10)
self.assertIsInstance(CursesMenu.currently_active_menu, SelectionMenu)
self.assertIs(CursesMenu.currently_active_menu, menu)

selection_menu = SelectionMenu(strings=["a", "b", "c"], title="Select a letter")
selection_menu.start()
selection_menu.wait_for_start(10)
selection_menu.wait_for_start(timeout=10)
self.assertIs(CursesMenu.currently_active_menu, selection_menu)

def test_init(self):
Expand Down
6 changes: 3 additions & 3 deletions test/test_submenu_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ def test_action(self):
root_menu.append_item(submenu_item_2)

root_menu.start()
root_menu.wait_for_start(10)
root_menu.wait_for_start(timeout=10)
self.assertIs(CursesMenu.currently_active_menu, root_menu)
submenu_item_1.action()
submenu1.wait_for_start(10)
submenu1.wait_for_start(timeout=10)
self.assertIs(CursesMenu.currently_active_menu, submenu1)
CursesMenu.currently_active_menu.exit()
submenu1.join(timeout=10)
self.assertIs(CursesMenu.currently_active_menu, root_menu)
submenu_item_2.action()
submenu2.wait_for_start(10)
submenu2.wait_for_start(timeout=10)
self.assertIs(CursesMenu.currently_active_menu, submenu2)
23 changes: 16 additions & 7 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ envlist =
py34
py35
docs
pep8

[testenv]
deps=
pytest
pytest-timeout
sitepackages=True
setenv=
PYTHONWARNINGS=all
Expand All @@ -18,7 +20,7 @@ deps=
mock
unittest2
commands=
py.test
py.test --timeout=30

[testenv:py27]
deps=
Expand All @@ -29,33 +31,40 @@ commands=

[testenv:py33]
commands=
py.test
py.test --timeout=30

[testenv:py34]
commands=
py.test
py.test --timeout=30

[testenv:py35]
commands=
py.test
py.test --timeout=30

[testenv:py36]
commands=
py.test
py.test --timeout=30

[testenv:pypy]
deps=
{[testenv]deps}
mock
commands=
py.test
py.test --timeout=30

[testenv:pypy3]
deps=
{[testenv]deps}
mock
commands=
py.test
py.test --timeout=30

[testenv:pep8]
deps=
{[testenv]deps}
pytest-pep8
commands=
py.test --timeout=30 --pep8

[testenv:docs]
basepython=python
Expand Down

0 comments on commit c76fc00

Please sign in to comment.