#!/usr/bin/env python #**************************************************************************** # planview.py, provides a flight plan view window # # FlyWay, a VFR/IFR Route Planner for Pilots # Copyright (C) 2002, Douglas W. Bell # # This is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License, Version 2. This program is # distributed in the hope that it will be useful, but WITTHOUT ANY WARRANTY. #***************************************************************************** from route import RouteLeg from plandata import PlanData from units import Time from tmpcontrol import TmpEdit, SpinBoxEx from flydialogs import TimeDlg from qt import * if qVersion()[0] >= '3': from tmptextedit import TmpTextEdit else: from tmpmultiedit import TmpTextEdit import flymainwin from xpm import * import sys class PlanView(QScrollView): """Flight plan window""" colPos = (0, 23, 46, 92, 126, 172, 218, 276, 366, 424, 482, 540) rowPos = (0, 17, 29, 41, 53, 65, 77, 150, 162, 174, 210, 222, 234, \ 270, 282, 305) vertLines = ((0, 0, 15), (1, 2, 5), (2, 1, 5), (2, 11, 13), (3, 10, 13), \ (4, 1, 5), (4, 7, 10), (4, 13, 15), (5, 8, 10), (6, 1, 5), \ (6, 7, 13), (7, 1, 5), (8, 1, 5), (9, 2, 5), (10, 1, 5), \ (10, 10, 13), (11, 0, 15)) horizLines = ((0, 0, 11), (1, 0, 11), (2, 0, 2), (2, 8, 10), (3, 0, 2), \ (4, 0, 2), (5, 0, 11), (7, 0, 11), (8, 4, 6), (10, 0, 11), \ (11, 0, 3), (13, 0, 11), (15, 0, 11)) viewMargin = 10 def __init__(self, parent=None, name=None): QScrollView.__init__(self, parent, name) self.viewport().setBackgroundMode(QWidget.PaletteBase) self.viewport().setFocusPolicy(QWidget.StrongFocus) if sys.platform == 'win32': self.setCaption('Flight Plan (PyQt)') else: self.setCaption('Flight Plan') self.setIcon(QPixmap(fly_xpm)) self.resizeContents(PlanView.colPos[-1] + 2 * PlanView.viewMargin, \ PlanView.rowPos[-1] + 2 * PlanView.viewMargin) self.normFont = self.font() self.smFont = QFont('Helvetica', 6) type1 = BoolEntry(self.rect(0, 2, 1, 3), PlanData.type1, 1, self) self.entries = [type1] self.selectedEntry = type1 type2 = BoolEntry(self.rect(0, 3, 1, 4), PlanData.type2, 2, self) self.entries.append(type2) type3 = BoolEntry(self.rect(0, 4, 1, 5), PlanData.type3, 3, self) self.entries.append(type3) self.connect(type1, PYSIGNAL('checked'), type2.uncheck) self.connect(type2, PYSIGNAL('checked'), type3.uncheck) self.connect(type3, PYSIGNAL('checked'), type1.uncheck) self.entries.append(LineEntry(self.rect(2, 3, 4, 5), \ PlanData.craftID, self)) self.entries.append(LineEntry(self.rect(4, 3, 6, 5), \ PlanData.equip, self)) self.entries.append(SpeedEntry(self.rect(6, 3, 7, 5), None, self)) self.entries.append(DepartEntry(self.rect(8, 3, 9, 5), None, self)) alt = IntEntry(self.rect(10, 3, 11, 5), PlanData.altitude, self) alt.step = 500 self.entries.append(alt) self.entries.append(MultiEntry(self.rect(0, 6, 11, 7), \ PlanData.route1, 4, self)) self.entries.append(MultiEntry(self.rect(6, 8, 11, 10), \ PlanData.remark1, 3, self)) self.entries.append(IntEntry(self.rect(0, 12, 2, 13), \ PlanData.fuelHr, self)) fuelMin = IntEntry(self.rect(2, 12, 3, 13), PlanData.fuelMin, self) fuelMin.step = 5 fuelMin.leadZeros = 2 self.entries.append(fuelMin) self.entries.append(LineEntry(self.rect(3, 12, 6, 13), \ PlanData.alternate, self)) self.entries.append(MultiEntry(self.rect(6, 11, 10, 13), \ PlanData.pilot1, 3, self)) self.entries.append(IntEntry(self.rect(10, 12, 11, 13), \ PlanData.numAboard, self)) self.entries.append(LineEntry(self.rect(0, 14, 4, 15), \ PlanData.color, self)) self.updatePlan() def rect(self, x1, y1, x2, y2): """Return QRect for given position""" return QRect(QPoint(PlanView.colPos[x1], PlanView.rowPos[y1]), \ QPoint(PlanView.colPos[x2], PlanView.rowPos[y2])) def updatePlan(self): """Update plan contents and repaint""" for entry in self.entries: entry.updateData() self.viewport().update() def updateMenus(self): """Signal main window to update menu status""" self.emit(PYSIGNAL('menuChg'), ()) def updateRoute(self): """Signal to update route for airspeed or depart time""" self.emit(PYSIGNAL('paramChg'), ()) def paintPlan(self, painter): """Paint flight plan""" for coord in PlanView.vertLines: painter.drawLine(PlanView.colPos[coord[0]], \ PlanView.rowPos[coord[1]], \ PlanView.colPos[coord[0]], \ PlanView.rowPos[coord[2]]) for coord in PlanView.horizLines: painter.drawLine(PlanView.colPos[coord[1]], \ PlanView.rowPos[coord[0]], \ PlanView.colPos[coord[2]], \ PlanView.rowPos[coord[0]]) painter.setFont(self.smFont) painter.drawText(self.rect(1, 2, 2, 3), Qt.AlignCenter, 'VFR') painter.drawText(self.rect(1, 3, 2, 4), Qt.AlignCenter, 'IFR') painter.drawText(self.rect(1, 4, 2, 5), Qt.AlignCenter, 'DVFR') painter.save() painter.translate(0, 2) # adjust title vertical positions painter.drawText(self.rect(0, 1, 2, 2), Qt.AlignHCenter, '1. TYPE') painter.drawText(self.rect(8, 1, 10, 2), Qt.AlignHCenter, \ '6. DEPARTURE TIME') painter.drawText(self.rect(8, 2, 9, 3), Qt.AlignHCenter, \ 'PROPOSED (Z)') painter.drawText(self.rect(9, 2, 10, 3), Qt.AlignHCenter, \ 'ACTUAL (Z)') painter.drawText(self.rect(4, 7, 6, 8), Qt.AlignHCenter, \ '10. EST. TIME ENROUTE') painter.drawText(self.rect(4, 8, 5, 9), Qt.AlignHCenter, 'HOURS') painter.drawText(self.rect(5, 8, 6, 9), Qt.AlignHCenter, 'MINUTES') painter.drawText(self.rect(0, 10, 3, 11), Qt.AlignHCenter, \ '12. FUEL ON BOARD') painter.drawText(self.rect(0, 11, 2, 12), Qt.AlignHCenter, 'HOURS') painter.drawText(self.rect(2, 11, 3, 12), Qt.AlignHCenter, 'MINUTES') painter.translate(2, 0) # adjust title horizontal positions painter.drawText(self.rect(2, 1, 4, 3), 0, \ '2. AIRCRAFT\n IDENTIFICATION') painter.drawText(self.rect(4, 1, 6, 3), 0, \ '3. AIRCRAFT TYPE/\n SPECIAL EQUIPMENT') painter.drawText(self.rect(6, 1, 7, 3), 0, '4. TRUE\n AIRSPEED') painter.drawText(self.rect(7, 1, 8, 3), 0, '5. DEPARTURE POINT') painter.drawText(self.rect(10, 1, 11, 3), 0, \ '7. CRUISING\n ALTITUDE') painter.drawText(self.rect(0, 5, 11, 6), 0, '8. ROUTE OF FLIGHT') painter.drawText(self.rect(0, 7, 4, 9), 0, \ '9. DESTINATION (Name of airport\n and city)') painter.drawText(self.rect(6, 7, 11, 9), 0, '11. REMARKS') painter.drawText(self.rect(3, 10, 6, 12), 0, \ '13. ALTERNATE AIRPORT(S)') painter.drawText(self.rect(6, 10, 10, 12), 0, \ '14. PILOT\'S NAME, ADDRESS & PHONE & AIRCRAFT HOME BASE') painter.drawText(self.rect(10, 10, 11, 12), 0, '15. NUMBER\n ABOARD') painter.drawText(self.rect(0, 13, 4, 14), 0, '16. COLOR OF AIRCRAFT') painter.restore() painter.setFont(self.normFont) painter.drawText(self.rect(0, 0, 11, 1), Qt.AlignCenter, 'FLIGHT PLAN') if flymainwin.FlyMainWin.route.wpList: # depart & destination id painter.drawText(self.rect(7, 3, 8, 5), Qt.AlignCenter, \ flymainwin.FlyMainWin.route.wpList[0].ident()) painter.drawText(self.rect(0, 9, 4, 10), Qt.AlignCenter, \ flymainwin.FlyMainWin.route.wpList[-1].ident()) if flymainwin.FlyMainWin.route.legList: # time enroute ete = flymainwin.FlyMainWin.route.legList[-1].totalTime.hourMinute() painter.drawText(self.rect(4, 9, 5, 10), Qt.AlignCenter, `ete[0]`) painter.drawText(self.rect(5, 9, 6, 10), Qt.AlignCenter, \ '%02d' % ete[1]) for entry in self.entries: entry.drawText(painter) def drawContents(self, painter, clipX=None, clipY=None, \ clipW=None, clipH=None): """Paint view""" if clipX == None: # none is for wrong overridden function return QFrame.drawContents(self, painter) pal = self.palette() painter.setPen(pal.color(QPalette.Active, QColorGroup.Text)) painter.save() painter.translate(PlanView.viewMargin, PlanView.viewMargin) self.paintPlan(painter) self.selectedEntry.drawSelected(painter) painter.restore() def contentsMousePressEvent(self, event): """Mouse entry selection control""" position = QPoint(event.pos().x() - PlanView.viewMargin, \ event.pos().y() - PlanView.viewMargin) for entry in self.entries: if entry.rect.contains(position): if self.selectedEntry != entry: self.selectedEntry = entry self.viewport().update() return def contentsMouseDoubleClickEvent(self, event): """Mouse entry edit control""" position = QPoint(event.pos().x() - PlanView.viewMargin, \ event.pos().y() - PlanView.viewMargin) for entry in self.entries: if entry.rect.contains(position): entry.editEntry() return def keyPressEvent(self, event): """Key bindings""" if event.key() == Qt.Key_Up or event.key() == Qt.Key_Left: i = self.entries.index(self.selectedEntry) - 1 if i < 0: i = len(self.entries) - 1 self.selectedEntry = self.entries[i] self.viewport().update() elif event.key() == Qt.Key_Down or event.key() == Qt.Key_Right: i = self.entries.index(self.selectedEntry) + 1 if i >= len(self.entries): i = 0 self.selectedEntry = self.entries[i] self.viewport().update() elif event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: self.selectedEntry.editEntry() else: QWidget.keyPressEvent(self, event) def closeEvent(self, event): """Signal that view is closing""" self.emit(PYSIGNAL('viewClosed'), ()) event.accept() class PlanEntry(QObject): """Draw and edit entries in flight plan""" def __init__(self, rect, dataTag=None, parent=None, name=None): QObject.__init__(self, parent, name) self.rect = rect self.dataTag = dataTag self.dataStr = '' self.ctrl = None def updateData(self): """Refresh data from PlanData""" if self.dataTag != None: self.dataStr = flymainwin.FlyMainWin.route.fltPlan.\ data[self.dataTag] def saveData(self): """Store data to PlanData""" if self.dataTag != None: flymainwin.FlyMainWin.route.fltPlan.\ data[self.dataTag] = self.dataStr def drawText(self, painter): """Draw text in rect""" painter.drawText(self.rect, Qt.AlignCenter, self.dataStr) def drawSelected(self, painter): """Draw bold border for selected item""" oldPen = painter.pen() painter.setPen(QPen(oldPen.color(), 3)) painter.drawRect(self.rect) painter.setPen(oldPen) def editEntry(self): """Called for user edit of items""" pass def ctrlRect(self): """Return adjusted rect for ctrl""" rect = QRect(self.rect) rect.moveTopLeft(self.parent().contentsToViewport(rect.topLeft())) rect.moveBy(PlanView.viewMargin, PlanView.viewMargin) return rect class BoolEntry(PlanEntry): """Draw and edit entries for types in flight plan""" def __init__(self, rect, dataTag=None, boolNum=0, parent=None, name=None): PlanEntry.__init__(self, rect, dataTag, parent, name) self.boolNum = boolNum self.updateData() def editEntry(self): """Called for user edit of items""" if not self.dataStr: self.dataStr = 'X' self.saveData() self.emit(PYSIGNAL('checked'), (self.boolNum,)) self.parent().viewport().update() flymainwin.FlyMainWin.docModified = 1 self.parent().updateMenus() def uncheck(self, boolNum): """Unset item in response to other change""" if boolNum != self.boolNum: self.dataStr = '' self.saveData() self.emit(PYSIGNAL('checked'), (boolNum,)) self.parent().viewport().update() class LineEntry(PlanEntry): """Draw and edit entries for single text lines in flight plan""" def __init__(self, rect, dataTag=None, parent=None, name=None): PlanEntry.__init__(self, rect, dataTag, parent, name) self.updateData() def editEntry(self): """Called for user edit of line items""" self.ctrl = TmpEdit(self.dataStr, self.parent().viewport()) self.ctrl.setGeometry(self.ctrlRect()) self.ctrl.show() self.ctrl.setFocus() self.connect(self.ctrl, PYSIGNAL('editDone'), self.editDone) def editDone(self): """Called by ctrl after return or focus change""" text = str(self.ctrl.text()) self.ctrl.close(1) self.ctrl = None if text != self.dataStr: self.dataStr = text self.saveData() self.parent().viewport().update() flymainwin.FlyMainWin.docModified = 1 self.parent().updateMenus() class MultiEntry(PlanEntry): """Draw and edit entries for multiple text lines in flight plan""" def __init__(self, rect, dataTag=None, numLines=4, parent=None, name=None): PlanEntry.__init__(self, rect, dataTag, parent, name) self.numLines = numLines self.updateData() def updateData(self): """Refresh data from PlanData""" if self.dataTag != None: self.dataStr = '\n'.join([flymainwin.FlyMainWin.route.\ fltPlan.data[num] \ for num in range(self.dataTag, \ self.dataTag \ + self.numLines)]) self.dataStr = self.dataStr.rstrip() def saveData(self): """Store data to PlanData""" if self.dataTag != None: list = self.dataStr.split('\n', self.numLines) list.extend([''] * self.numLines) for num in range(self.numLines): flymainwin.FlyMainWin.route.fltPlan.\ data[num + self.dataTag] = list[num] def drawText(self, painter): """Draw text in rect""" painter.save() painter.translate(2, 2) # adjust position painter.drawText(self.rect, Qt.AlignTop | Qt.AlignLeft \ | Qt.WordBreak, self.dataStr) painter.restore() def editEntry(self): """Called for user edit of lines""" self.ctrl = TmpTextEdit(self.dataStr, self.parent().viewport()) self.ctrl.setMaxLines(self.numLines) self.ctrl.setGeometry(self.ctrlRect()) self.ctrl.show() self.ctrl.setFocus() self.connect(self.ctrl, PYSIGNAL('editDone'), self.editDone) def editDone(self): """Called by ctrl after return or focus change""" text = str(self.ctrl.text()).rstrip() self.ctrl.close(1) self.ctrl = None if text != self.dataStr: self.dataStr = text self.saveData() self.parent().viewport().update() flymainwin.FlyMainWin.docModified = 1 self.parent().updateMenus() class IntEntry(PlanEntry): """Draw and edit entries for integers in flight plan""" def __init__(self, rect, dataTag=None, parent=None, name=None): PlanEntry.__init__(self, rect, dataTag, parent, name) self.min = 0 self.max = 99 self.step = 1 self.leadZeros = 0 if self.dataTag != None: self.min = flymainwin.FlyMainWin.route.fltPlan.\ intMinList[self.dataTag] self.max = flymainwin.FlyMainWin.route.fltPlan.\ intMaxList[self.dataTag] self.updateData() def drawText(self, painter): """Draw int text in rect""" if self.leadZeros == 2: painter.drawText(self.rect, Qt.AlignCenter, \ '%02d' % int(self.dataStr)) else: painter.drawText(self.rect, Qt.AlignCenter, self.dataStr) def editEntry(self): """Called for user edit of int items""" self.ctrl = SpinBoxEx(self.min, self.max, self.step, \ self.leadZeros, self.parent().viewport()) self.ctrl.setValue(int(self.dataStr)) self.ctrl.origValue = int(self.dataStr) self.ctrl.setGeometry(self.ctrlRect()) self.ctrl.show() self.ctrl.setFocus() self.connect(self.ctrl, PYSIGNAL('editDone'), self.editDone) def editDone(self): """Called by ctrl after return or focus change""" text = str(self.ctrl.text()) self.ctrl.close(1) self.ctrl = None if text != self.dataStr: self.dataStr = text self.saveData() self.parent().viewport().update() flymainwin.FlyMainWin.docModified = 1 self.parent().updateMenus() class SpeedEntry(IntEntry): """Draw and edit the entry for airspeed in flight plan""" def __init__(self, rect, dataTag=None, parent=None, name=None): IntEntry.__init__(self, rect, dataTag, parent, name) self.step = 5 self.leadZeros = 0 def updateData(self): """Refresh data from route leg""" self.min = 1 self.max = RouteLeg.maxAirspeed self.dataStr = `flymainwin.FlyMainWin.option.\ intData('Airspeed', self.min, self.max)` def editDone(self): """Called by ctrl after return or focus change""" text = str(self.ctrl.text()) self.ctrl.close(1) self.ctrl = None if text != self.dataStr: self.dataStr = text self.parent().viewport().update() flymainwin.FlyMainWin.option.changeData('Airspeed', text, 1) flymainwin.FlyMainWin.option.writeChanges() self.parent().updateRoute() class DepartEntry(PlanEntry): """Draw and edit entry for depart time in flight plan""" def __init__(self, rect, dataTag=None, parent=None, name=None): PlanEntry.__init__(self, rect, dataTag, parent, name) self.updateData() def updateData(self): """Refresh data from route leg""" option = flymainwin.FlyMainWin.option depart = Time(option.intData('DepartHour', 0, 23) \ + option.intData('DepartMinute', 0, 59) / 60.0) if option.boolData('LocalClock'): depart.toGMT() self.dataStr = depart.timeStr() def editEntry(self): """Called for user edit of departure""" dlg = TimeDlg(self.parent().viewport(), None, 1) if dlg.exec_loop() == QDialog.Accepted: self.updateData() self.parent().viewport().update() self.parent().updateRoute()