summaryrefslogtreecommitdiff
path: root/presentty/console.py
diff options
context:
space:
mode:
Diffstat (limited to 'presentty/console.py')
-rw-r--r--presentty/console.py292
1 files changed, 292 insertions, 0 deletions
diff --git a/presentty/console.py b/presentty/console.py
new file mode 100644
index 0000000..d29b864
--- /dev/null
+++ b/presentty/console.py
@@ -0,0 +1,292 @@
1# Copyright (C) 2015 James E. Blair <corvus@gnu.org>
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import argparse
17import sys
18import datetime
19import time
20
21import urwid
22
23import palette
24import client
25import slide
26import rst
27
28PALETTE = [
29 ('reversed', 'standout', ''),
30 ('status', 'light red', ''),
31]
32
33class Row(urwid.Button):
34 def __init__(self, index, title, console):
35 super(Row, self).__init__('', on_press=console.jump, user_data=index)
36 col = urwid.Columns([
37 ('fixed', 3, urwid.Text('%-2i' % (index+1))),
38 urwid.Text(title),
39 ])
40 self._w = urwid.AttrMap(col, None, focus_map='reversed')
41
42 def selectable(self):
43 return True
44
45class Footer(urwid.WidgetWrap):
46 def __init__(self):
47 super(Footer, self).__init__(urwid.Columns([]))
48 self.position = urwid.Text(u'')
49 self.timer = urwid.Text(u'')
50 self._w.contents.append((self.position, ('pack', None, False)))
51 self._w.contents.append((urwid.Text(u''), ('weight', 1, False)))
52 self._w.contents.append((self.timer, ('pack', None, False)))
53
54class Screen(urwid.WidgetWrap):
55 def __init__(self, console):
56 super(Screen, self).__init__(urwid.Pile([]))
57 self.console = console
58 self.program = []
59 self.current = -1
60 self.progressive_state = 0
61 self.blank_slide = slide.UrwidSlide(
62 u'', None, urwid.Text(u''), None)
63 self.timer = 45*60
64 self.size = (80, 25)
65 self.timer_end = None
66 self.listbox = urwid.ListBox(urwid.SimpleFocusListWalker([]))
67 self.footer = Footer()
68 footer = urwid.AttrMap(self.footer, 'status')
69 self.left = urwid.Pile([])
70 self.left.contents.append((self.listbox, ('weight', 1)))
71 self.left.set_focus(0)
72
73 self.right = urwid.Pile([])
74 self.setPreviews()
75
76 self.main = urwid.Columns([])
77 self.main.contents.append((self.left, ('weight', 1, False)))
78 self.main.contents.append((self.right, ('given', self.size[0]+2, False)))
79 self.main.set_focus(0)
80
81 self._w.contents.append((self.main, ('weight', 1)))
82 self._w.contents.append((footer, ('pack', 1)))
83 self._w.set_focus(0)
84
85 def setPreviews(self):
86 current_slide = next_slide = self.blank_slide
87 if 0 <= self.current < len(self.program):
88 current_slide = self.program[self.current]
89 if 0 <= self.current+1 < len(self.program):
90 next_slide = self.program[self.current+1]
91 current_slide.setProgressive(self.progressive_state)
92 current_box = urwid.LineBox(current_slide, "Current")
93 next_box = urwid.LineBox(next_slide, "Next")
94 if current_slide.handout:
95 notes_box = urwid.LineBox(current_slide.handout, "Notes")
96 else:
97 notes_box = None
98 self.right.contents[:] = []
99 self.left.contents[:] = self.left.contents[:1]
100 cols, rows = self.size
101 self.right.contents.append((current_box, ('given', rows+2)))
102 self.right.contents.append((next_box, ('given', rows+2)))
103 self.right.contents.append((urwid.Filler(urwid.Text(u'')), ('weight', 1)))
104 if notes_box:
105 self.left.contents.append((notes_box, ('pack', None)))
106
107 def setProgram(self, program):
108 self.program = program
109 self.listbox.body[:] = []
110 for i, s in enumerate(program):
111 self.listbox.body.append(Row(i, s.title, self.console))
112
113 def setSize(self, size):
114 self.size = size
115 cols, rows = size
116 self.right.contents[0] = (self.right.contents[0][0], ('given', rows+2))
117 self.right.contents[1] = (self.right.contents[1][0], ('given', rows+2))
118 self.main.contents[1] = (self.main.contents[1][0], ('given', cols+2, False))
119
120 # Implement this method from the urwid screen interface for the ScreenHinter
121 def get_cols_rows(self):
122 return self.size
123
124 def setCurrent(self, state):
125 index, progressive_state = state
126 changed = False
127 if index != self.current:
128 self.current = index
129 self.listbox.set_focus(index)
130 self.listbox._invalidate()
131 self.footer.position.set_text('%i / %i' % (index+1, len(self.program)))
132 changed = True
133 if progressive_state != self.progressive_state:
134 self.progressive_state = progressive_state
135 changed = True
136 if changed:
137 self.setPreviews()
138 self.footer.timer.set_text(self.getTime())
139
140 def getTime(self):
141 now = time.time()
142 if self.timer_end:
143 return str(datetime.timedelta(seconds=(int(self.timer_end-now))))
144 else:
145 return str(datetime.timedelta(seconds=int(self.timer)))
146
147 def setTimer(self, secs):
148 self.timer = secs
149 if self.timer_end:
150 now = time.time()
151 self.timer_end = now + self.timer
152
153 def startStopTimer(self):
154 now = time.time()
155 if self.timer_end:
156 remain = max(self.timer_end - int(now), 0)
157 self.timer = remain
158 self.timer_end = None
159 else:
160 self.timer_end = now + self.timer
161
162 def keypress(self, size, key):
163 if key in (' ', 'x'):
164 self.startStopTimer()
165 elif key == 'page up':
166 self.console.prev()
167 elif key == 'page down':
168 self.console.next()
169 elif key == 'right':
170 self.console.next()
171 elif key == 'left':
172 self.console.prev()
173 elif key == 't':
174 self.console.timerDialog()
175 else:
176 return super(Screen, self).keypress(size, key)
177 return None
178
179class FixedButton(urwid.Button):
180 def sizing(self):
181 return frozenset([urwid.FIXED])
182
183 def pack(self, size, focus=False):
184 return (len(self.get_label())+4, 1)
185
186class TimerDialog(urwid.WidgetWrap):
187 signals = ['set', 'cancel']
188 def __init__(self):
189 set_button = FixedButton('Set')
190 cancel_button = FixedButton('Cancel')
191 urwid.connect_signal(set_button, 'click',
192 lambda button:self._emit('set'))
193 urwid.connect_signal(cancel_button, 'click',
194 lambda button:self._emit('cancel'))
195 button_widgets = [('pack', set_button),
196 ('pack', cancel_button)]
197 button_columns = urwid.Columns(button_widgets, dividechars=2)
198 rows = []
199 self.entry = urwid.Edit('Timer: ', edit_text='45:00')
200 rows.append(self.entry)
201 rows.append(urwid.Divider())
202 rows.append(button_columns)
203 pile = urwid.Pile(rows)
204 fill = urwid.Filler(pile, valign='top')
205 super(TimerDialog, self).__init__(urwid.LineBox(fill, 'Timer'))
206
207 def keypress(self, size, key):
208 r = super(TimerDialog, self).keypress(size, key)
209 if r == 'enter':
210 self._emit('set')
211 return None
212 elif r == 'esc':
213 self._emit('cancel')
214 return None
215 return r
216
217class Console(object):
218 poll_interval = 0.5
219
220 def __init__(self, program):
221 self.screen = Screen(self)
222 self.loop = urwid.MainLoop(self.screen, palette=PALETTE)
223 self.client = client.Client()
224 self.screen.setProgram(program)
225 self.update()
226 self.loop.set_alarm_in(self.poll_interval, self.updateCallback)
227
228 def run(self):
229 self.loop.run()
230
231 def jump(self, widget, index):
232 self.screen.setCurrent(self.client.jump(index))
233
234 def next(self):
235 self.screen.setCurrent(self.client.next())
236
237 def prev(self):
238 self.screen.setCurrent(self.client.prev())
239
240 def updateCallback(self, loop=None, data=None):
241 self.update()
242 self.loop.set_alarm_in(self.poll_interval, self.updateCallback)
243
244 def update(self):
245 self.screen.setSize(self.client.size())
246 self.screen.setCurrent(self.client.current())
247
248 def timerDialog(self):
249 dialog = TimerDialog()
250 overlay = urwid.Overlay(dialog, self.loop.widget,
251 'center', 30,
252 'middle', 6)
253 self.loop.widget = overlay
254 urwid.connect_signal(dialog, 'cancel', self.cancelDialog)
255 urwid.connect_signal(dialog, 'set', self.setTimer)
256
257 def cancelDialog(self, widget):
258 self.loop.widget = self.screen
259
260 def setTimer(self, widget):
261 parts = widget.entry.edit_text.split(':')
262 secs = 0
263 if len(parts):
264 secs += int(parts.pop())
265 if len(parts):
266 secs += int(parts.pop())*60
267 if len(parts):
268 secs += int(parts.pop())*60*60
269 self.screen.setTimer(secs)
270 self.loop.widget = self.screen
271
272
273def main():
274 parser = argparse.ArgumentParser(
275 description='Console-based presentation system')
276 parser.add_argument('--light', dest='light',
277 default=False,
278 action='store_true',
279 help='use a black on white palette')
280 parser.add_argument('file',
281 help='presentation file (RST)')
282 args = parser.parse_args()
283 if args.light:
284 plt = palette.LIGHT_PALETTE
285 else:
286 plt = palette.DARK_PALETTE
287 hinter = slide.ScreenHinter()
288 parser = rst.PresentationParser(plt, hinter)
289 program = parser.parse(open(args.file).read())
290 c = Console(program)
291 hinter.setScreen(c.screen)
292 c.run()