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