1
2
3 """
4 Created on 17.06.2016
5
6 @author: johanneskinzig
7 @version: 0.3
8
9 """
10
11 from Tkinter import *
12 import ttk
13 from tkFileDialog import askopenfilename, asksaveasfilename
14 from DroneDataConversion import DroneDataConversion
15 import DataStoreController
16 import FileStorageController
17 import os
18 import Plotlib
19 import PlotGPSTrack
20
22 - def __init__(self, master):
23 """Setup Tkinter and all necessary properties. Generate instances of classes."""
24
25
26
27
28
29
30 self.FileStorage = FileStorageController.FileStorageController("FlightReportManager", "JoKi", "0.1_alpha")
31 print("Data directory: " + self.FileStorage.getDataDirectory())
32
33
34 self.DataStore = DataStoreController.DataStoreController(self.FileStorage.getDBLocation())
35
36
37
38
39
40 master.title("FlightReportManager Bebop")
41 master.resizable(width=False, height=False)
42 master.grid_columnconfigure(0, weight=1)
43 master.grid_rowconfigure(0, weight=1)
44
45
46
47
48
49
50
51
52 self.initialize_widgets(master)
53
54
55 self.generateTreeViewContextMenu(master)
56
57
58 self.updateDataInView()
59
61 """Holds all the information necessary to place the widgets in the frame. Including callbacks and methods"""
62 tabbar = ttk.Notebook(master)
63 flight_tab = ttk.Frame(tabbar)
64 import_tab = ttk.Frame(tabbar)
65 settings_tab = ttk.Frame(tabbar)
66 tabbar.add(flight_tab, text='Flights')
67 tabbar.add(import_tab, text='Import')
68 tabbar.add(settings_tab, text='Settings')
69 tabbar.grid(row=0, column=0, sticky=(N+E+W+S))
70
71
72
73
74
75
76
77
78 columns = ("event_name", "city_nearby", "pilot_location", "datetime", "total_distance", "max_altitude", "avg_speed", "flight_duration", "controller_type", "Drone_type", "battery_usage")
79 self.flights_treeView = ttk.Treeview(flight_tab, columns=columns, selectmode='browse', height=15, padding=(0,0,0,0))
80 self.flights_treeView.grid(row=1, column=0, rowspan = 5, columnspan = 4, sticky=N+S+W)
81
82
83
84
85 vsb_treeview = ttk.Scrollbar(flight_tab, orient="vertical", command = self.flights_treeView.yview)
86 hsb_treeview = ttk.Scrollbar(flight_tab, orient="horizontal", command = self.flights_treeView.xview)
87
88
89 self.flights_treeView.configure(yscrollcommand=vsb_treeview.set, xscrollcommand=hsb_treeview.set)
90 vsb_treeview.grid(row=1, column=4, rowspan = 5, sticky=(N+S))
91 hsb_treeview.grid(row=6, column=0, columnspan = 4, sticky=(E+W))
92
93
94 self.flights_treeView.heading("#0", text="FlightID")
95 self.flights_treeView.column("#0", minwidth=50, width=50)
96
97 self.flights_treeView.heading("event_name", text="Event Name")
98 self.flights_treeView.heading("city_nearby", text="City nearby")
99 self.flights_treeView.heading("pilot_location", text="Pilot Location")
100 self.flights_treeView.heading("datetime", text="Time")
101 self.flights_treeView.heading("total_distance", text="Distance (m)")
102 self.flights_treeView.heading("max_altitude", text="max Alt. (m)")
103 self.flights_treeView.heading("avg_speed", text="avg. Speed (m/s)")
104 self.flights_treeView.heading("flight_duration", text="Duration (min)")
105 self.flights_treeView.heading("controller_type", text="Controller Type")
106 self.flights_treeView.heading("Drone_type", text="Drone Type")
107 self.flights_treeView.heading("battery_usage", text="Battery Usage (%)")
108
109 self.flights_treeView.column("event_name", minwidth=90, width=100)
110 self.flights_treeView.column("city_nearby", minwidth=80, width=110)
111 self.flights_treeView.column("pilot_location", minwidth=160, width=170, anchor=CENTER)
112 self.flights_treeView.column("datetime", minwidth=120, width=165)
113 self.flights_treeView.column("total_distance", minwidth=70, width=70, anchor=CENTER)
114 self.flights_treeView.column("max_altitude", minwidth=70, width=70, anchor=CENTER)
115 self.flights_treeView.column("avg_speed", minwidth=95, width=95)
116 self.flights_treeView.column("flight_duration", minwidth=80, width=85)
117 self.flights_treeView.column("controller_type", minwidth=105, width=105, anchor=CENTER)
118 self.flights_treeView.column("Drone_type", minwidth=80, width=80)
119 self.flights_treeView.column("battery_usage", minwidth=95, width=95)
120
121
122
123
124
125 ttk.Button(flight_tab, text="Export Track (gpx)", command=self.exportAsGPXInvocation, width = 14).grid(row=0, column = 0)
126 ttk.Button(flight_tab, text="Export Track (kml)", command=self.action, width = 14).grid(row=0, column = 1)
127 ttk.Button(flight_tab, text="Export Flight (csv)", command=self.exportAsCSVInvocation, width = 14).grid(row=0, column = 2)
128 ttk.Button(flight_tab, text="Edit Flight", command=lambda: self.editRowWindowInvocation(master), width = 14).grid(row=0, column = 3)
129
130
131
132
133 ttk.Button(flight_tab, text="Plot Track", command=self.plotTrackInvocation, width = 14).grid(row = 7, column = 0)
134 ttk.Button(flight_tab, text="Plot Altitude", command=self.plotAltitudeInvocation, width = 14).grid(row = 7, column = 1)
135 ttk.Button(flight_tab, text="Plot Speed", command=self.plotSpeedInovaction, width = 14).grid(row=7, column = 2)
136 ttk.Button(flight_tab, text="Plot Battery", command=self.plotBatteryUsageInvocation, width = 14).grid(row = 7, column = 3)
137
138
139
140
141
142
143
144
145 ttk.Label(import_tab, text="Event Name: ").grid(column = 0, row = 0)
146 self.event_name_entry = ttk.Entry(import_tab, width=30)
147 self.event_name_entry.grid(column = 1, row= 0)
148
149
150
151
152 ttk.Label(import_tab, text="PUD/JSON File location: ").grid(column=0, row = 1, sticky = W)
153 self.file_location_entry = ttk.Entry(import_tab, width=30)
154 self.file_location_entry.grid(column = 1, row = 1)
155 ttk.Button(import_tab, text = "Choose...", command=self.openFileChooser, width = 7).grid(column = 2, row = 1, sticky = E)
156
157
158
159
160 ttk.Label(import_tab, text = "Import Log: ").grid(column = 0, row = 2, sticky = W)
161 ttk.Button(import_tab, text = "Import", command = self.importInvocation, width = 7).grid(column = 2, row = 2, sticky = E)
162 self.log_textfield = Text(import_tab, height=15)
163 self.log_textfield.grid(column = 0, row = 3, columnspan = 3, sticky=EW)
164
165
166
167
168 vsb_log = ttk.Scrollbar(import_tab, orient="vertical", command = self.log_textfield.yview)
169 hsb_log = ttk.Scrollbar(import_tab, orient="horizontal", command = self.log_textfield.xview)
170
171
172 self.log_textfield.configure(yscrollcommand=vsb_log.set, xscrollcommand=hsb_log.set)
173 vsb_log.grid(row=3, column=4, rowspan = 1, sticky=N+S)
174 hsb_log.grid(row=4, column=0, columnspan = 3, sticky=E+W)
175
176
178 """Generate Menu which opens when right-clicking on treeview entry"""
179 self.tv_cont_men = Menu(master, tearoff=0)
180
181 self.tv_cont_men.add_command(label="Export Track (gpx)", command=self.exportAsGPXInvocation)
182 self.tv_cont_men.add_command(label="Export Track (csv)", command=self.exportAsCSVInvocation)
183 self.tv_cont_men.add_command(label="Export Takeoff Waypoint (gpx)", command=self.exportTakeoffGpxWptInvocation)
184 self.tv_cont_men.add_command(label="Export Takeoff Waypoint (csv)", command=self.exportTakeoffCsvWptInvocation)
185 self.tv_cont_men.add_separator()
186 self.tv_cont_men.add_command(label="Edit", command=lambda: self.editRowWindowInvocation(master))
187 self.tv_cont_men.add_command(label="Delete", command=self.deleteRowInvocation)
188 self.tv_cont_men.add_separator()
189
190
191 self.flights_treeView.bind("<Button-2>", self.showTreeViewContextMenu)
192
194 ''' Show context menu and store selected row
195 '''
196 try:
197
198 self.tv_cont_men.selection = self.flights_treeView.item(self.flights_treeView.focus())
199 self.tv_cont_men.post(event.x_root, event.y_root)
200 finally:
201
202 self.tv_cont_men.grab_release()
203
204
205 - def editRowWindowInvocation(self, master):
206 """Generate second Tkinter window, directly generate a ttk-Frame to have similar styles for all windows"""
207 def editRowInvocation(window, flight_id, newName):
208 """Actively edit the selected row (treeview) in DB"""
209
210 self.DataStore.updatRowNameInTable(flight_id, str(newName.get()))
211
212 window.destroy()
213 self.updateDataInView()
214
215 def editRowUpdateCityInvocation(window, flight_id, newCity):
216 """Update city nearby, in case geolocation service was unavailable"""
217
218 self.DataStore.updateRowCityInTable(flight_id, newCity)
219
220 window.destroy()
221 self.updateDataInView()
222
223
224
225
226 editNameWindowTL = Toplevel(master)
227 editNameWindowTL.resizable(width=False, height=False)
228 editNameWindowTL.title("Edit Flight")
229 editNameWindowTL.minsize(60,30)
230 editNameWindowFr = ttk.Frame(editNameWindowTL, padding="3 3 12 12")
231 editNameWindowFr.grid(column=0, row=0, sticky=(N+E+W+S))
232 ttk.Label(editNameWindowFr, text="New Name:").grid(column=0, row=0)
233 newName = ttk.Entry(editNameWindowFr)
234 newName.grid(column=1, row = 0, columnspan = 3, sticky=(N+E+W+S))
235
236
237
238
239
240
241 flight_id = int(self.flights_treeView.item(self.flights_treeView.focus())['text'])
242 print("Selected Row --> Flight_ID: " + str(flight_id))
243
244 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem())
245
246
247
248
249 ttk.Button(editNameWindowFr, text="Exit", command=editNameWindowTL.destroy).grid(column=1, row=1)
250 ttk.Button(editNameWindowFr, text="Rename", command=lambda: editRowInvocation(editNameWindowTL, flight_id, newName)).grid(column=2, row=1)
251 ttk.Button(editNameWindowFr, text="Refresh City", command=lambda: editRowUpdateCityInvocation(editNameWindowTL, flight_id, str(flightManager.get_nearest_city()))).grid(column=3, row=1)
252
254 """Delte pud file from directory"""
255 self.FileStorage.deleteFile(self.getFilenameForSelectedItem())
256
258 """Delete selected row from table and delete according files"""
259
260 self.deletePudFileInvocation()
261
262 self.DataStore.deleteRowFromTable(int(self.flights_treeView.item(self.flights_treeView.focus())['text']))
263 self.updateDataInView()
264
266 """Update data in GUI"""
267
268
269
270
271
272 self.flights_treeView.delete(*self.flights_treeView.get_children())
273
274 data = self.DataStore.readDataFromTable()
275 for data_set in data:
276
277
278
279
280
281 self.flights_treeView.insert("", 0, str(data_set[0]), text=str(data_set[0]), values=(data_set[1], data_set[2], data_set[3], data_set[4], round(data_set[5], 2), data_set[6], round(data_set[7], 2), round(data_set[8], 2), data_set[9], data_set[10], data_set[11]))
282
284 """Get the user input for the "event_name_entry" and "file_location_entry" text entry field - Store data inside DB"""
285 event_name_value = self.event_name_entry.get()
286 file_location_value = self.file_location_entry.get()
287
288
289
290 print("EVENT NAME USER INPUT: " + event_name_value)
291 print("PUD/JSON FILE LOCATION: " + file_location_value)
292
293
294
295 pud_path, pud_name = os.path.split(file_location_value)
296 self.raw_file_name = pud_name
297
298 file_location_destination_value = self.FileStorage.getPudfileDataDirectory() + pud_name
299 print("PUD/JSON FILE DESTINATION: " + file_location_destination_value)
300
301 self.FileStorage.copyFileTo(file_location_value, file_location_destination_value)
302
303
304
305 flightManager = DroneDataConversion.BebopFlightDataManager(file_location_destination_value)
306
307 flightManager.display_diagnostic_information_debug()
308
309 self.DataStore.insertIntoTable(event_name_value, flightManager.diagnostic_information_raw())
310
311 self.log_textfield.delete('1.0', END)
312
313 self.log_textfield.insert(END, ("\n".join(flightManager.display_diagnostic_information_debug())))
314
315 self.updateDataInView()
316
317 - def openFileChooser(self):
318 """Open tkaskopenfilename and return filename/absolute filepath of user's selection"""
319
320 file_options = {}
321 file_options['defaultextension'] = '.json'
322 file_options['filetypes'] = [('JSON Files', '.json'), ('PUD Files', '.pud')]
323 file_options['title'] = "Choose Bebop's json/pud File..."
324
325
326 filepath_absolute = askopenfilename(**file_options)
327
328 print("FILENAME: " + filepath_absolute)
329
330 self.file_location_entry.delete(0, END)
331 self.file_location_entry.insert(END, filepath_absolute)
332
334 """Makes a request to the DB and returns the location/filename for the selected item"""
335
336 flight_id = int(self.flights_treeView.item(self.flights_treeView.focus())['text'])
337
338
339 row = self.DataStore.getRowFromID(flight_id)
340 print("PUD/JSON File for parsing: " + str(row[12]))
341 file_location = self.FileStorage.getPudfileDataDirectory() + str(row[12])
342 return str(file_location)
343
345 """Makes a request to the DB and returns the event name for the selected item"""
346
347 flight_id = int(self.flights_treeView.item(self.flights_treeView.focus())['text'])
348
349
350 row = self.DataStore.getRowFromID(flight_id)
351 print("Eventname: " + str(row[1]))
352 event_name = str(row[1])
353 return event_name
354
355 - def saveToFileDialog(self, filetype):
356 """Open Tkinter save file dialog and return path to file"""
357 filetype = str(filetype)
358 event_name = self.getEventnameForSelectedItem()
359
360 file_options = {}
361 if filetype == "gpx":
362 file_options['defaultextension'] = '.gpx'
363 file_options['title'] = "Export GPX to ..."
364 file_options['initialfile'] = event_name + str(file_options['defaultextension'])
365 elif filetype == "csv":
366 file_options['defaultextension'] = '.csv'
367 file_options['title'] = "Export CSV to ..."
368 file_options['initialfile'] = event_name + str(file_options['defaultextension'])
369
370 filepath = asksaveasfilename(**file_options)
371 return filepath
372
374 """Export track as GPX file invocation"""
375
376 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem())
377 filelocation = self.saveToFileDialog("gpx")
378
379 flightManager.export_as_gpx(filelocation, gpx_track_name=str(os.path.basename(os.path.splitext(filelocation)[0])))
380 print("Done")
381
383 """Export takeoff location as gpx waypoint invocation"""
384 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem())
385 filelocation = self.saveToFileDialog("gpx")
386
387 flightManager.takeoff_location_as_gpx(filelocation, waypoint_name=str(os.path.basename(os.path.splitext(filelocation)[0])))
388 print("Done")
389
390
392 """Export track as kml file invocation"""
393 pass
394
395
397 """Export takeoff location as kml waypoint invocation"""
398 pass
399
400
402 """Export track as csv file invocation"""
403
404 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem())
405
406 filelocation = self.saveToFileDialog("csv")
407
408 flightManager.export_as_csv(filelocation)
409 print("Done")
410
412 """Export the takeoff location as csv waypoint"""
413
414 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem())
415
416 filelocation = self.saveToFileDialog("csv")
417
418 flightManager.takeoff_location_as_csv(filelocation, waypoint_name=str(os.path.basename(os.path.splitext(filelocation)[0])))
419 print("Done")
420
422 """Invocation of battery plot"""
423
424 plot_flight = Plotlib.Plotlib(self.getFilenameForSelectedItem())
425 plot_flight.plotBatteryUsage()
426
428 """Invocation of altitude plot"""
429
430 plot_flight = Plotlib.Plotlib(self.getFilenameForSelectedItem())
431 plot_flight.plotAltitude()
432
434 """Invocation of speed Plot"""
435
436 plot_flight = Plotlib.Plotlib(self.getFilenameForSelectedItem())
437 plot_flight.plotSpeed()
438
440 """Plot the flight track - experimental only!"""
441
442 plot_flight = PlotGPSTrack.PlotGPSTrack(self.getFilenameForSelectedItem())
443 plot_flight.plotTrackOnMap_MP()
444
446 print "Not yet implemented"
447
448 if __name__ == '__main__':
449 root = Tk()
450 root.columnconfigure(0, weight=2)
451 root.rowconfigure(0, weight=2)
452 app=MainView(root)
453 root.mainloop()
454