Implement 'auto-start in GUI' feature
[munter.git] / munter / gui.py
1 # -*- coding: utf-8 -*-
2
3
4 """
5 GUI implementation
6 """
7
8 import wx
9 from . import munter
10 from . import __progname__ as progname
11 from . import __version__ as version
12
13 class MainFrame(wx.Frame):
14 """
15 The main wx.Frame
16 """
17 program_name = ""
18
19 def __init__(self, *args, **kw):
20 """add to the init process for the wx.Frame"""
21 super(MainFrame, self).__init__(*args, **kw)
22
23 # initial setup
24 self.SetTitle(progname)
25 self.SetSize(600, 400)
26
27 self.props = self.init_props()
28 self.pnl = wx.Panel(self)
29
30 # wx item vars
31 self.wx_items = dict()
32 self.wx_items['StaticText'] = dict()
33 self.wx_items['TextEntry'] = dict()
34 self.wx_items['RadioButton'] = dict()
35 self.wx_items['ComboBox'] = dict()
36
37 # setup the GUI itself
38 self.create_wx_items()
39 self.establish_wx_bindings()
40 self.do_wx_layout()
41 self.update_mtc()
42
43 # init routines
44 def init_props(self):
45 """centralized place to initialize props var"""
46 props = dict()
47 props['distance'] = 0
48 props['elevation'] = 0
49 props['fitness'] = 'average'
50 props['units'] = 'imperial'
51 props['travel_mode'] = 'uphill'
52 return props
53
54 def create_wx_items(self):
55 """create wxPython items"""
56 # title bar / program name
57 self.program_name = wx.StaticText(self.pnl, label=progname)
58 font = self.program_name.GetFont()
59 font.PointSize += 10
60 font = font.Bold()
61
62 self.program_name.SetFont(font)
63
64 # text entry fields
65 self.wx_items['StaticText']['distance'] = wx.StaticText(self.pnl,
66 label="Distance: ",
67 style=wx.ALIGN_RIGHT)
68 self.wx_items['TextEntry']['distance'] = wx.TextCtrl(self.pnl,
69 value="0",
70 size=(140, -1))
71
72 self.wx_items['StaticText']['elevation'] = wx.StaticText(self.pnl,
73 label="Elevation: ",
74 style=wx.ALIGN_RIGHT)
75 self.wx_items['TextEntry']['elevation'] = wx.TextCtrl(self.pnl,
76 value="0",
77 size=(140, -1))
78
79 self.wx_items['StaticText']['fitness'] = wx.StaticText(self.pnl,
80 label="Fitness: ",
81 style=wx.ALIGN_RIGHT)
82 rb_fitness_choices = ['slow', 'average', 'fast']
83 rb_fitness_default = 'average'
84 self.wx_items['RadioButton']['fitness'] = wx.ComboBox(self.pnl,
85 choices=rb_fitness_choices,
86 value=rb_fitness_default,
87 style=wx.CB_READONLY)
88
89 self.wx_items['StaticText']['travel_mode'] = wx.StaticText(self.pnl,
90 label="Travel Mode: ",
91 style=wx.ALIGN_RIGHT)
92 rb_travel_mode_choices = ['uphill', 'flat', 'downhill', 'bushwhacking']
93 rb_travel_mode_default = 'uphill'
94 self.wx_items['RadioButton']['travel_mode'] = wx.ComboBox(self.pnl,
95 choices=rb_travel_mode_choices,
96 value=rb_travel_mode_default,
97 style=wx.CB_READONLY)
98
99 self.wx_items['StaticText']['units'] = wx.StaticText(self.pnl,
100 label="Units: ",
101 style=wx.ALIGN_RIGHT)
102 rb_units_choices = ['imperial', 'metric']
103 rb_units_default = 'imperial'
104
105 self.wx_items['RadioButton']['units'] = []
106 for choice in rb_units_choices:
107 label = choice
108 style = wx.RB_GROUP if choice == rb_units_default else 0
109 self.wx_items['RadioButton']['units'].append(wx.RadioButton(self.pnl,
110 label=label,
111 style=style))
112
113 # static text
114 self.wx_items['StaticText']['mtc'] = wx.StaticText(self.pnl,
115 label="",
116 style=wx.ALIGN_CENTRE_HORIZONTAL)
117
118 st_mtc_font = self.program_name.GetFont()
119 st_mtc_font.PointSize += 10
120 self.wx_items['StaticText']['mtc'].SetFont(st_mtc_font)
121
122 # buttons
123 self.b_reset = wx.Button(self.pnl, wx.NewId(), '&Reset', (-1, -1),
124 wx.DefaultSize)
125
126 def establish_wx_bindings(self):
127 """establish wxPython bindings"""
128 self.pnl.Bind(wx.EVT_TEXT, self.update_distance, self.wx_items['TextEntry']['distance'])
129 self.pnl.Bind(wx.EVT_TEXT, self.update_elevation, self.wx_items['TextEntry']['elevation'])
130 self.wx_items['RadioButton']['fitness'].Bind(wx.EVT_COMBOBOX, self.update_fitness)
131 self.wx_items['RadioButton']['travel_mode'].Bind(wx.EVT_COMBOBOX, self.update_travel_mode)
132 self.b_reset.Bind(wx.EVT_BUTTON, self.reset)
133
134 for rb_unit in self.wx_items['RadioButton']['units']:
135 rb_unit.Bind(wx.EVT_RADIOBUTTON, self.update_units)
136
137 def do_wx_layout(self):
138 """layout the wxPython items"""
139 border = 5
140 width = 100
141
142 sizer = wx.BoxSizer(wx.HORIZONTAL)
143 sizer.Add(self.program_name, wx.SizerFlags().Border(wx.TOP|wx.LEFT, 0))
144
145 static_line = wx.StaticLine(self.pnl, wx.NewId(), style=wx.LI_HORIZONTAL)
146
147 hsizer_distance = wx.BoxSizer(wx.HORIZONTAL)
148 hsizer_distance.Add(self.wx_items['StaticText']['distance'], 0, wx.RIGHT, border)
149 hsizer_distance.Add(self.wx_items['TextEntry']['distance'], 1, wx.GROW, border)
150 hsizer_distance.SetItemMinSize(self.wx_items['StaticText']['distance'], (width, -1))
151
152 hsizer_elevation = wx.BoxSizer(wx.HORIZONTAL)
153 hsizer_elevation.Add(self.wx_items['StaticText']['elevation'], 0, wx.RIGHT, border)
154 hsizer_elevation.Add(self.wx_items['TextEntry']['elevation'], 1, wx.GROW, border)
155 hsizer_elevation.SetItemMinSize(self.wx_items['StaticText']['elevation'], (width, -1))
156
157 hsizer_fitness = wx.BoxSizer(wx.HORIZONTAL)
158 hsizer_fitness.Add(self.wx_items['StaticText']['fitness'], 0, wx.RIGHT, border)
159 hsizer_fitness.Add(self.wx_items['RadioButton']['fitness'], 1, wx.GROW, border)
160 hsizer_fitness.SetItemMinSize(self.wx_items['StaticText']['fitness'], (width, -1))
161
162 hsizer_travel_mode = wx.BoxSizer(wx.HORIZONTAL)
163 hsizer_travel_mode.Add(self.wx_items['StaticText']['travel_mode'], 0, wx.RIGHT, border)
164 hsizer_travel_mode.Add(self.wx_items['RadioButton']['travel_mode'], 1, wx.GROW, border)
165 hsizer_travel_mode.SetItemMinSize(self.wx_items['StaticText']['travel_mode'], (width, -1))
166
167 hsizer_units = wx.BoxSizer(wx.HORIZONTAL)
168 hsizer_units.Add(self.wx_items['StaticText']['units'], 0, wx.RIGHT, border)
169 for rb_unit in range(len(self.wx_items['RadioButton']['units'])):
170 hsizer_units.Add(self.wx_items['RadioButton']['units'][rb_unit],
171 rb_unit + 1,
172 wx.GROW,
173 border)
174 hsizer_units.SetItemMinSize(self.wx_items['StaticText']['units'], (width, -1))
175
176 hsizer_mtc = wx.BoxSizer(wx.HORIZONTAL)
177 hsizer_mtc.Add(self.wx_items['StaticText']['mtc'], 1, wx.GROW, border)
178 hsizer_mtc.SetItemMinSize(self.wx_items['StaticText']['mtc'], (width, -1))
179
180 hsizer5 = wx.BoxSizer(wx.HORIZONTAL)
181 hsizer5.Add(self.b_reset, 0)
182
183 vsizer1 = wx.BoxSizer(wx.VERTICAL)
184 vsizer1.Add(sizer, 0, wx.EXPAND | wx.ALL, border*border)
185 vsizer1.Add(hsizer_distance, 0, wx.EXPAND | wx.ALL, border)
186 vsizer1.Add(hsizer_elevation, 0, wx.EXPAND | wx.ALL, border)
187 vsizer1.Add(hsizer_fitness, 0, wx.EXPAND | wx.ALL, border)
188 vsizer1.Add(hsizer_travel_mode, 0, wx.EXPAND | wx.ALL, border)
189 vsizer1.Add(hsizer_units, 0, wx.EXPAND | wx.ALL, border)
190 vsizer1.Add(hsizer_mtc, 0, wx.EXPAND | wx.ALL, border)
191 vsizer1.Add(static_line, 0, wx.GROW | wx.ALL, border)
192 vsizer1.Add(hsizer5, 0, wx.ALIGN_RIGHT | wx.ALL, border)
193
194 self.pnl.SetSizerAndFit(vsizer1)
195 self.pnl.SetClientSize(vsizer1.GetSize())
196
197 # event handlers
198 def update_distance(self, event):
199 """updates distance prop"""
200 value = event.GetEventObject().GetValue()
201 if value:
202 try:
203 new_val = float(value)
204 self.props['distance'] = new_val
205 except ValueError:
206 # reset GUI to last-accepted val
207 self.wx_items['TextEntry']['distance'].SetValue(str(self.props['distance']))
208 self.update_mtc()
209
210 def update_elevation(self, event):
211 """updates elevation prop"""
212 value = event.GetEventObject().GetValue()
213 if value:
214 try:
215 new_val = int(value)
216 self.props['elevation'] = new_val
217 except ValueError:
218 # reset GUI to last-accepted val
219 self.wx_items['TextEntry']['elevation'].SetValue(str(self.props['elevation']))
220 self.update_mtc()
221
222 def update_fitness(self, event):
223 """updates fitness prop"""
224 value = event.GetEventObject().GetValue()
225 if value:
226 self.props['fitness'] = value
227 self.update_mtc()
228
229 def update_travel_mode(self, event):
230 """updates travel_mode prop"""
231 value = event.GetEventObject().GetValue()
232 if value:
233 self.props['travel_mode'] = value
234 self.update_mtc()
235
236 def update_units(self, event):
237 """updates units prop"""
238 value = event.GetEventObject().GetLabel()
239 if value:
240 self.props['units'] = value
241 self.update_mtc()
242
243 def reset(self, event):
244 """resets all values"""
245 event.GetTimestamp()
246 self.props = self.init_props()
247 self.wx_items['TextEntry']['distance'].SetValue(str(self.props['distance']))
248 self.wx_items['TextEntry']['elevation'].SetValue(str(self.props['elevation']))
249 self.wx_items['RadioButton']['fitness'].SetValue(str(self.props['fitness']))
250 self.wx_items['RadioButton']['travel_mode'].SetValue(str(self.props['travel_mode']))
251 # leave units as the user selected
252 self.update_mtc()
253
254 # other routines
255 def update_mtc(self):
256 """updates mtc, the final result the user wants"""
257 if (self.props['distance'] is None) or (self.props['elevation'] is None):
258 return
259
260 est = munter.time_calc(self.props['distance'],
261 self.props['elevation'],
262 self.props['fitness'],
263 self.props['travel_mode'],
264 self.props['units'])
265
266 hours = int(est['time'])
267 minutes = int((est['time'] - hours) * 60)
268 self.wx_items['StaticText']['mtc'].SetLabel("{human_time}".format(
269 human_time="{hours} hours {minutes} minutes".format(
270 hours=hours,
271 minutes=minutes)))
272
273 self.pnl.Layout()
274
275 def startup():
276 """kick off the GUI"""
277 app = wx.App()
278 frm = MainFrame(None)
279 frm.Show()
280
281 app.MainLoop()