Skip to content
This repository has been archived by the owner on Aug 6, 2021. It is now read-only.

Commit

Permalink
DatetimeSpinner: add spinning functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
cs142ta committed Jul 24, 2020
1 parent 499cdaa commit fc449e5
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 10 deletions.
158 changes: 151 additions & 7 deletions zygrader/ui/components.py
Expand Up @@ -170,22 +170,26 @@ def selected(self):
class DatetimeSpinner(Popup):
FORMAT_STR = "%b %d, %Y at %H:%M:%S"
FIELDS = [
{'name': 'month', 'x_offset': 0, 'str_len': 3, 'formatter': '%b'},
{'name': 'day', 'x_offset': 4, 'str_len': 2, 'formatter': '%d'},
{'name': 'year', 'x_offset': 8, 'str_len': 4, 'formatter': '%Y'},
{'name': 'hour', 'x_offset': 16, 'str_len': 2, 'formatter': '%H'},
{'name': 'minute', 'x_offset': 19, 'str_len': 2, 'formatter': '%M'},
{'name': 'second', 'x_offset': 22, 'str_len': 2, 'formatter': '%S'}
{'name': 'month', 'x_offset': 0, 'unit': None, 'formatter': '%b'},
{'name': 'day', 'x_offset': 4, 'unit': datetime.timedelta(days=1), 'formatter': '%d'},
{'name': 'year', 'x_offset': 8, 'unit': None, 'formatter': '%Y'},
{'name': 'hour', 'x_offset': 16, 'unit': datetime.timedelta(hours=1), 'formatter': '%H'},
{'name': 'minute', 'x_offset': 19, 'unit': datetime.timedelta(minutes=1), 'formatter': '%M'},
{'name': 'second', 'x_offset': 22, 'unit': datetime.timedelta(seconds=1), 'formatter': '%S'}
]
STR_LEN = 24

def __init__(self, height, width, title, time):
def __init__(self, height, width, title, time, quickpicks):
super().__init__(height, width, title, [], Popup.ALIGN_CENTER)
if time is None:
time = datetime.datetime.now()
self.time = time
self.field_index = 3

if quickpicks:
quickpicks = sorted(quickpicks)
self.quickpicks = quickpicks

def draw(self):
self.message = [self.time.strftime(DatetimeSpinner.FORMAT_STR)]
super().draw_text()
Expand All @@ -200,6 +204,146 @@ def draw(self):

self.window.noutrefresh()

def next_field(self):
self.field_index = (self.field_index + 1) % len(DatetimeSpinner.FIELDS)

def previous_field(self):
self.field_index = (self.field_index - 1) % len(DatetimeSpinner.FIELDS)

def first_field(self):
self.field_index = 0

def last_field(self):
self.field_index = len(DatetimeSpinner.FIELDS) - 1

def increment_field(self):
field = DatetimeSpinner.FIELDS[self.field_index]
if field['name'] == 'minute' and self.quickpicks:
self._increment_quickpick()
else:
self._increment_field()

def decrement_field(self):
field = DatetimeSpinner.FIELDS[self.field_index]
if field['name'] == 'minute' and self.quickpicks:
self._decrement_quickpick()
else:
self._decrement_field()

def alt_increment_field(self):
self._increment_field()

def alt_decrement_field(self):
self._decrement_field()

def _increment_field(self):
field = DatetimeSpinner.FIELDS[self.field_index]
if field['unit']:
self.time = self.time + field['unit']
else:
if field['name'] == 'month':
new_month = (self.time.month % 12) + 1 #month is in 1..12, this incs 12->1
self.time = self.time.replace(month=new_month)
elif field['name'] == 'year':
new_year = min(max(self.time.year + 1, datetime.MINYEAR), datetime.MAXYEAR)
self.time = self.time.replace(year=new_year)

def _decrement_field(self):
field = DatetimeSpinner.FIELDS[self.field_index]
if field['unit']:
self.time = self.time - field['unit']
else:
if field['name'] == 'month':
new_month = self.time.month - 1
if new_month == 0:
new_month = 12
self.time = self.time.replace(month=new_month)
elif field['name'] == 'year':
new_year = min(max(self.time.year - 1, datetime.MINYEAR), datetime.MAXYEAR)
self.time = self.time.replace(year=new_year)

def _increment_quickpick(self):
new_minute, new_second = self.quickpicks[0]
for minute, second in self.quickpicks:
if minute > self.time.minute:
new_minute, new_second = minute, second
break

self.time = self.time.replace(minute=new_minute, second=new_second)

def _decrement_quickpick(self):
new_minute, new_second = self.quickpicks[-1]
for minute, second in self.quickpicks[::-1]:
if minute < self.time.minute:
new_minute, new_second = minute, second
break

self.time = self.time.replace(minute=new_minute, second=new_second)

def set_field(self, val: str):
try:
new_val = int(val)
field_name = DatetimeSpinner.FIELDS[self.field_index]['name']

if field_name == 'month':
self.time = self.time.replace(month=new_val)
elif field_name == 'day':
self.time = self.time.replace(day=new_val)
elif field_name == 'year':
self.time = self.time.replace(year=new_val)
elif field_name == 'hour':
self.time = self.time.replace(hour=new_val)
elif field_name == 'minute':
self.time = self.time.replace(minute=new_val)
elif field_name == 'second':
self.time = self.time.replace(second=new_val)

return True
except ValueError:
return self._set_month(val)

MONTH_STR_PATH = {
'j': (1, False, {
'a': (1, True, 'nuary'),
'u': (6, False, {
'n': (6, True, 'e'),
'l': (7, True, 'y')
})
}),
'f': (2, True, 'ebruary'),
'm': (3, False, {
'a': (3, False, {
'r': (3, True, 'ch'),
'y': (5, True, '')
})
}),
'a': (4, False, {
'p': (4, True, 'ril'),
'u': (8, True, 'gust')
}),
's': (9, True, 'eptember'),
'o': (10, True, 'ctober'),
'n': (11, True, 'ovember'),
'd': (12, True, 'ecember')
}
def _set_month(self, new_month: str):
position = DatetimeSpinner.MONTH_STR_PATH
guess = None
for char in new_month:
if char not in position:
return

guess, surety, position = position[char]
if surety:
break

if guess:
self.time = self.time.replace(month=guess)
return True

return False


class FilteredList(Component):

class ListLine():
Expand Down
22 changes: 19 additions & 3 deletions zygrader/ui/window.py
Expand Up @@ -491,19 +491,35 @@ def create_options_popup(self, title, message, options, align=components.Popup.A
if not use_dict:
return popup.selected()

def create_datetime_spinner(self, title, time=None):
def create_datetime_spinner(self, title, time=None, quickpicks=None):
"""Create a popup with a datetime spinner to select a datetime.
time is the initial time to present
quickpicks is an optional list of (minute, second) pairs.
If provided, spinning the minute field will spin through the quickpicks
"""

popup = components.DatetimeSpinner(self.rows, self.cols, title, time)
popup = components.DatetimeSpinner(self.rows, self.cols, title, time, quickpicks)

self.component_init(popup)

while True:
event = self.consume_event()

if event.type == Event.ENTER:
if event.type == Event.LEFT:
popup.previous_field()
elif event.type == Event.RIGHT:
popup.next_field()
elif event.type == Event.HOME:
popup.first_field()
elif event.type == Event.END:
popup.last_field()
elif event.type == Event.UP:
popup.increment_field()
elif event.type == Event.DOWN:
popup.decrement_field()
elif event.type == Event.ESC and self.use_esc_back:
break
elif event.type == Event.ENTER:
break

self.draw()
Expand Down

0 comments on commit fc449e5

Please sign in to comment.