def __init__(self, parent, external, port): ''' external is a host (possibly localhost) for external FGCom instance, or None for internal (child process) ''' QWidget.__init__(self, parent) self.setupUi(self) client_address = some(external, 'localhost'), port self.settings = FgcomSettings(socket(AF_INET, SOCK_DGRAM), client_address) self.controller = Ticker(self.settings.send, parent=self) self.frequency_combo.addFrequencies([(frq, descr) for frq, descr, t in env.frequencies]) self.frequency_combo.addFrequencies(frequencies_always_proposed) if external == None: # child process self.onOff_button.setToolTip('Internal FGCom instance using local port %d' % port) ad = world_navpoint_db.findClosest(env.radarPos(), types=[Navpoint.AD]).code if env.airport_data == None else settings.location_code self.instance = InternalFgcomInstance(port, ['--airport=%s' % ad], self) self.instance.started.connect(self.processHasStarted) self.instance.finished.connect(self.processHasStopped) self.onOff_button.toggled.connect(self.switchFGCom) else: # creating box for external instance self.instance = None self.onOff_button.setToolTip('External FGCom instance on %s:%d' % client_address) self.onOff_button.setChecked(True) # keep checked (tested for RDF) self.onOff_button.setEnabled(False) self.PTT_button.setEnabled(True) self.controller.start(fgcom_controller_ticker_interval) self.PTT_button.pressed.connect(lambda: self.PTT(True)) self.PTT_button.released.connect(lambda: self.PTT(False)) self.softVolume_tickBox.clicked.connect(self.setVolume) self.frequency_combo.frequencyChanged.connect(self.setFrequency) self.updateRDF() self.RDF_tickBox.toggled.connect(self.updateRDF) self.onOff_button.toggled.connect(self.updateRDF) signals.localSettingsChanged.connect(self.updateRDF)
def __init__(self, gui): SessionManager.__init__(self, gui) self.session_type = SessionType.TEACHER self.gui = gui self.session_ticker = Ticker(self.tickSessionOnce, parent=gui) self.simulation_paused_at = None # pause time if session is paused; None otherwise self.server = QTcpServer(gui) self.student_socket = None self.server.newConnection.connect(self.studentConnects) self.aircraft_list = [] # ControlledAircraft list self.current_local_weather = None # initialised by the teaching console on sessionStarted self.noACK_traffic_count = 0
def __init__(self, gui): QObject.__init__(self) self.ticker = Ticker(self.scan, parent=gui) self.last_sweep = now() # to be updated with current time at each blip self.aircraft_list = [] # Aircraft list self.blips_invisible = { } # str -> int; number of blips for which ACFT callsign has been invisible self.soft_links = [] # (Strip, Aircraft) pairs self.known_EMG_squawkers = set() # str identifiers self.runway_occupation = {} # int -> list of ACFT identifiers if env.airport_data != None: for i in range(env.airport_data.physicalRunwayCount()): self.runway_occupation[i] = []
class WwStripExchanger: def __init__(self, gui): self.updater = SxUpdater(gui) self.update_ticker = Ticker(self.updater.start, parent=gui) self.gui = gui self.running = False def start(self): self.update_ticker.start(SX_update_interval) self.running = True def stopAndWait(self): if self.isRunning( ): # CHECK: do we need to wait for any running SXsender threads as well? self.update_ticker.stop() self.updater.wait() self.running = False self.updater.ATCs_on_last_run.clear() self.updater.current_contact_claims.clear() def isRunning(self): return self.running def connectedATCs(self): return self.updater.ATCs_on_last_run[:] def isConnected(self, atc_callsign): return any(atc.callsign == atc_callsign for atc in self.updater.ATCs_on_last_run) def claimingContact(self, callsign): return self.updater.current_contact_claims.get(callsign, None) def handOver(self, strip, atc_id): ''' returns cleanly if transfer is properly initiated (contact linked), otherwise: HandoverBlocked (handover aborted) ''' acft = strip.linkedAircraft() if acft == None: raise HandoverBlocked( 'Unlinked strips cannot be sent through the OpenRadar system.') else: SXsender(self.gui, strip, acft.identifier, handover=atc_id).start()
def __init__(self, gui): SessionManager.__init__(self, gui) self.session_type = SessionType.SOLO self.session_ticker = Ticker(self.tickSessionOnce, parent=gui) self.weather_ticker = Ticker(self.setNewWeather, parent=gui) self.spawn_timer = QTimer(gui) self.spawn_timer.setSingleShot(True) self.voice_instruction_recogniser = None self.speech_synthesiser = None self.msg_is_from_session_manager = False # set to True before sending to avoid chat msg being rejected if speech_recognition_available: try: self.voice_instruction_recogniser = InstructionRecogniser(gui) except RuntimeError as err: settings.solo_voice_instructions = False QMessageBox.critical(self.gui, 'Sphinx error', \ 'Error setting up the speech recogniser (check log): %s\nVoice instructions disabled.' % err) if speech_synthesis_available: try: self.speech_synthesiser = SpeechSynthesiser(gui) except Exception as err: settings.solo_voice_readback = False QMessageBox.critical(self.gui, 'Pyttsx error', \ 'Error setting up the speech synthesiser: %s\nPilot read-back disabled.' % err) self.controlled_traffic = [] self.uncontrolled_traffic = [] self.current_local_weather = None self.simulation_paused_at = None # start time if session is paused; None otherwise self.spawn_timer.timeout.connect( lambda: self.spawnNewControlledAircraft(isSessionStart=False)) self.playable_aircraft_types = settings.solo_aircraft_types[:] self.uncontrolled_aircraft_types = [ t for t in known_aircraft_types() if cruise_speed(t) != None ] pop_all(self.playable_aircraft_types, lambda t: t not in known_aircraft_types()) pop_all(self.playable_aircraft_types, lambda t: cruise_speed(t) == None)
class TeacherSessionManager(SessionManager): def __init__(self, gui): SessionManager.__init__(self, gui) self.session_type = SessionType.TEACHER self.gui = gui self.session_ticker = Ticker(self.tickSessionOnce, parent=gui) self.simulation_paused_at = None # pause time if session is paused; None otherwise self.server = QTcpServer(gui) self.student_socket = None self.server.newConnection.connect(self.studentConnects) self.aircraft_list = [] # ControlledAircraft list self.current_local_weather = None # initialised by the teaching console on sessionStarted self.noACK_traffic_count = 0 def start(self): self.aircraft_list.clear() self.simulation_paused_at = None self.session_ticker.start_stopOnZero(teacher_ticker_interval) self.server.listen(port=settings.teaching_service_port) print('Teaching server ready on port %d' % settings.teaching_service_port) signals.specialTool.connect(self.createNewTraffic) signals.kbdPTT.connect(self.sendPTT) signals.sessionStarted.emit() def stop(self): if self.isRunning(): self.session_ticker.stop() if self.studentConnected(): self.shutdownStudentConnection() signals.specialTool.disconnect(self.createNewTraffic) signals.kbdPTT.disconnect(self.sendPTT) self.server.close() self.aircraft_list.clear() signals.sessionEnded.emit() def studentConnected(self): return self.student_socket != None def isRunning(self): return self.session_ticker.isActive( ) or self.simulation_paused_at != None def myCallsign(self): return teacher_callsign def getAircraft(self): return self.aircraft_list[:] def getWeather(self, station): return self.current_local_weather if station == settings.primary_METAR_station else None def postRadioChatMsg(self, msg): raise ValueError( 'Public radio chat panel reserved for monitoring read-backs in teacher sessions. ' 'Use the ATC text chat system to communicate with the student.') def postAtcChatMsg(self, msg): if self.studentConnected(): if msg.isPrivate(): payload = '%s\n%s' % (msg.sender(), msg.txtOnly()) self.student.sendMessage( TeachingMsg(TeachingMsg.ATC_TEXT_CHAT, data=payload)) else: raise ValueError( 'Only private messaging is enabled in tutoring sessions.') else: raise ValueError('No student connected.') ## CONNECTION MANAGEMENT def studentConnects(self): new_connection = self.server.nextPendingConnection() if new_connection: peer_address = new_connection.peerAddress().toString() print('Contacted by %s' % peer_address) if self.studentConnected(): new_connection.disconnectFromHost() print('Client rejected. Student already connected.') else: self.student_socket = new_connection self.student_socket.disconnected.connect( self.studentDisconnects) self.student_socket.disconnected.connect( self.student_socket.deleteLater) self.student = TeachingSessionWire(self.student_socket) self.student.messageArrived.connect(self.receiveMsgFromStudent) env.ATCs.updateATC(student_callsign, None, None, None) self.noACK_traffic_count = 0 self.sendWeather() self.sendATCs() self.tickSessionOnce() if self.simulation_paused_at != None: self.student.sendMessage( TeachingMsg(TeachingMsg.SIM_PAUSED)) QMessageBox.information( self.gui, 'Student connection', 'Student accepted from %s' % peer_address) else: print('WARNING: Connection attempt failed.') def studentDisconnects(self): self.shutdownStudentConnection() QMessageBox.information(self.gui, 'Student disconnection', 'Your student has disconnected.') def shutdownStudentConnection(self): self.student_socket.disconnected.disconnect(self.studentDisconnects) env.cpdlc.endAllDataLinks() env.ATCs.removeATC(student_callsign) self.student.messageArrived.disconnect(self.receiveMsgFromStudent) self.student_socket.disconnectFromHost() self.student_socket = None ## TEACHER MANAGEMENT def instructAircraftByCallsign(self, callsign, instr): try: acft = next(acft for acft in self.aircraft_list if acft.identifier == callsign) except StopIteration: print('ERROR: Teacher aircraft not found: %s' % callsign) return try: acft.instruct([instr]) acft.readBack([instr]) except Instruction.Error as err: QMessageBox.critical(self.gui, 'Instruction error', speech_str2txt(str(err))) def createNewTraffic(self, spawn_coords, spawn_hdg): dialog = CreateTrafficDialog(spawn_coords, spawn_hdg, parent=self.gui) dialog.exec() if dialog.result() > 0: params = dialog.acftInitParams() params.XPDR_mode = new_traffic_XPDR_mode acft = ControlledAircraft(dialog.acftCallsign(), dialog.acftType(), params, None) acft.spawned = False acft.frozen = dialog.startFrozen() acft.tickOnce() self.aircraft_list.append(acft) if dialog.createStrip(): strip = Strip() strip.writeDetail(FPL.CALLSIGN, acft.identifier) strip.writeDetail(FPL.ACFT_TYPE, acft.aircraft_type) strip.writeDetail(FPL.WTC, wake_turb_cat(acft.aircraft_type)) strip.linkAircraft(acft) signals.receiveStrip.emit(strip) selection.selectAircraft(acft) def killAircraft(self, acft): if env.cpdlc.isConnected(acft.identifier): env.cpdlc.endDataLink(acft.identifier) pop_all(self.aircraft_list, lambda a: a is acft) if acft.spawned and self.studentConnected(): self.student.sendMessage( TeachingMsg(TeachingMsg.ACFT_KILLED, data=acft.identifier)) signals.aircraftKilled.emit(acft) def sendATCs(self): if self.studentConnected(): msg = TeachingMsg(TeachingMsg.SX_LIST) for atc in env.ATCs.knownATCs( lambda atc: atc.callsign != student_callsign): try: frq = env.ATCs.getATC( atc ).frequency # instance of CommFrequency class, or None except KeyError: frq = None msg.appendData(atc if frq == None else '%s\t%s' % (atc, frq)) msg.appendData('\n') self.student.sendMessage(msg) def setWeather(self, weather): # assumed at primary location and newer self.current_local_weather = weather signals.newWeather.emit(settings.primary_METAR_station, self.current_local_weather) self.sendWeather() def sendWeather(self): if self.studentConnected() and self.current_local_weather != None: self.student.sendMessage( TeachingMsg(TeachingMsg.WEATHER, data=self.current_local_weather.METAR())) def sendPTT(self, ignore_button, on_off): if selection.acft != None and selection.acft.spawned: if on_off: env.rdf.receiveSignal( selection.acft.identifier, lambda acft=selection.acft: acft.coords()) else: env.rdf.dieSignal(selection.acft.identifier) if self.studentConnected(): str_data = '%d %s' % (on_off, selection.acft.identifier) self.student.sendMessage( TeachingMsg(TeachingMsg.PTT, data=str_data)) def requestCpdlcConnection( self, callsign): # NOTE: student must confirm data link if self.studentConnected(): self.student.sendMessage( TeachingMsg(TeachingMsg.CPDLC, data=('%s\n1' % callsign))) def transferCpdlcAuthority( self, acft_callsign, atc_callsign ): # for teacher, ATC here is who to transfer *from* to student if self.studentConnected(): self.student.sendMessage(TeachingMsg(TeachingMsg.CPDLC, \ data=('%s\n%s%s' % (acft_callsign, CPDLC_transfer_cmd_prefix, atc_callsign)))) # NOTE: student must confirm data link def disconnectCpdlc(self, callsign): env.cpdlc.endDataLink(callsign) if self.studentConnected(): self.student.sendMessage( TeachingMsg(TeachingMsg.CPDLC, data=('%s\n0' % callsign))) def sendCpdlcMsg(self, callsign, msg): link = env.cpdlc.currentDataLink(callsign) if link == None: return if msg.type() == CpdlcMessage.ACK and link.msgCount() > 0: last_msg = link.lastMsg() if not last_msg.isFromMe() and last_msg.type() == CpdlcMessage.INSTR \ and yesNo_question(self.gui, 'ACK after received INSTR', last_msg.contents(), 'Execute instruction?'): try: instr = Instruction.fromEncodedStr(last_msg.contents()) self.instructAircraftByCallsign(callsign, instr) except ValueError: # raised by Instruction.fromEncodedStr if not yesNo_question(self.gui, 'CPDLC comm error', \ 'Unable to decode instruction.', 'Send ACK and perform manually?'): return # cancel sending any message except Instruction.Error as err: # raised by TeacherSessionManager.instructAircraftByCallsign if not yesNo_question(self.gui, 'CPDLC instruction error', \ 'Unable to perform instruction: %s' % err, 'Send ACK anyway?'): return # cancel sending any message else: # no problem executing instruction selection.writeStripAssignment(instr) if self.studentConnected() and link != None: link.appendMessage(msg) self.student.sendMessage( TeachingMsg( TeachingMsg.CPDLC, data=('%s\n%s%s' % (callsign, CPDLC_message_cmd_prefix, msg.text())))) def pauseSession(self): self.session_ticker.stop() self.simulation_paused_at = now() signals.sessionPaused.emit() if self.studentConnected(): self.student.sendMessage(TeachingMsg(TeachingMsg.SIM_PAUSED)) def resumeSession(self): pause_delay = now() - self.simulation_paused_at for acft in self.aircraft_list: acft.moveHistoryTimesForward(pause_delay) self.simulation_paused_at = None self.session_ticker.start_stopOnZero(teacher_ticker_interval) signals.sessionResumed.emit() if self.studentConnected(): self.student.sendMessage(TeachingMsg(TeachingMsg.SIM_RESUMED)) ## MESSAGES FROM STUDENT def receiveMsgFromStudent(self, msg): #DEBUG if msg.type != TeachingMsg.TRAFFIC: #DEBUG print('=== TEACHERS RECEIVES ===\n%s\n=== End ===' % msg.data) if msg.type == TeachingMsg.ATC_TEXT_CHAT: lines = msg.strData().split('\n') if len(lines) == 2: signals.incomingAtcTextMsg.emit( ChatMessage(student_callsign, lines[1], recipient=lines[0], private=True)) else: print( 'ERROR: Invalid format in received ATC text chat from student.' ) elif msg.type == TeachingMsg.STRIP_EXCHANGE: line_sep = msg.strData().split('\n', maxsplit=1) toATC = line_sep[0] strip = Strip.fromEncodedDetails( '' if len(line_sep) < 2 else line_sep[1]) strip.writeDetail(received_from_detail, student_callsign) if toATC != teacher_callsign: strip.writeDetail(sent_to_detail, toATC) signals.receiveStrip.emit(strip) elif msg.type == TeachingMsg.WEATHER: # requesting weather information if msg.strData() == settings.primary_METAR_station: self.sendWeather() elif msg.type == TeachingMsg.TRAFFIC: # acknowledging a traffic message if self.noACK_traffic_count > 0: self.noACK_traffic_count -= 1 else: print('ERROR: Student acknowledging unsent traffic?!') elif msg.type == TeachingMsg.CPDLC: # Msg format in 2 lines, first being ACFT callsign, second is either of the following: # - connect/disconnect: "0" or "1" # - data authority transfer: CPDLC_transfer_cmd_prefix + ATC callsign transferring to/from # - other: CPDLC_message_cmd_prefix + encoded message string try: acft_callsign, line2 = msg.strData().split('\n', maxsplit=1) if line2 == '0': # ACFT disconnected by student if env.cpdlc.isConnected(acft_callsign): env.cpdlc.endDataLink(acft_callsign) else: # student is rejecting a connection (unable CPDLC) QMessageBox.warning( self.gui, 'CPDLC connection failed', 'Student is not accepting CPDLC connections.') elif line2 == '1': # student confirming ACFT log-on env.cpdlc.beginDataLink(acft_callsign, student_callsign) elif line2.startswith( CPDLC_transfer_cmd_prefix ): # student transferring or confirming transfer atc = line2[len(CPDLC_transfer_cmd_prefix):] if env.cpdlc.isConnected( acft_callsign ): # student initiating transfer to next ATC env.cpdlc.endDataLink(acft_callsign, transferTo=atc) else: # student confirming proposed transfer env.cpdlc.beginDataLink(acft_callsign, student_callsign, transferFrom=atc) elif line2.startswith(CPDLC_message_cmd_prefix ): # student ATC sent a message encoded_msg = line2[len(CPDLC_message_cmd_prefix):] link = env.cpdlc.currentDataLink(acft_callsign) if link == None: print( 'Ignored CPDLC message sent to %s while not connected.' % acft_callsign) else: link.appendMessage( CpdlcMessage.fromText(False, encoded_msg)) else: print('Error decoding CPDLC command from student:', line2) except (IndexError, ValueError): print('Error decoding CPDLC message value from student') else: print('ERROR: Unhandled message type from student: %s' % msg.type) ## TICK def tickSessionOnce(self): pop_all(self.aircraft_list, lambda a: not env.pointInRadarRange(a.params.position)) send_traffic_this_tick = self.studentConnected( ) and self.noACK_traffic_count < max_noACK_traffic for acft in self.aircraft_list: acft.tickOnce() fgms_packet = acft.fgmsLivePositionPacket() send_packet_to_views(fgms_packet) if send_traffic_this_tick and acft.spawned: self.student.sendMessage( TeachingMsg(TeachingMsg.TRAFFIC, data=fgms_packet)) self.noACK_traffic_count += 1 ## STRIP EXCHANGE def stripDroppedOnATC(self, strip, sendto): if sendto == student_callsign: items = [teacher_callsign] + env.ATCs.knownATCs( lambda atc: atc.callsign != student_callsign) sender, ok = QInputDialog.getItem(self.gui, 'Send strip to student', 'Hand over strip from:', items, editable=False) if ok and self.studentConnected(): msg_data = sender + '\n' + strip.encodeDetails( handover_details) self.student.sendMessage( TeachingMsg(TeachingMsg.STRIP_EXCHANGE, data=msg_data)) else: raise HandoverBlocked('Cancelled by teacher.', silent=True) else: raise HandoverBlocked('Strips can only be sent to the student!') ## SNAPSHOTTING def situationSnapshot(self): return [acft.statusSnapshot() for acft in self.aircraft_list] def restoreSituation(self, situation_snapshot): while self.aircraft_list != []: self.killAircraft(self.aircraft_list[0]) for acft_snapshot in situation_snapshot: self.aircraft_list.append( ControlledAircraft.fromStatusSnapshot(acft_snapshot)) self.tickSessionOnce()
def __init__(self, gui): self.updater = SxUpdater(gui) self.update_ticker = Ticker(self.updater.start, parent=gui) self.gui = gui self.running = False
class Radar(QObject): blip = pyqtSignal() newContact = pyqtSignal(Aircraft) lostContact = pyqtSignal(Aircraft) emergencySquawk = pyqtSignal(Aircraft) runwayIncursion = pyqtSignal(int, Aircraft) pathConflict = pyqtSignal() nearMiss = pyqtSignal() def __init__(self, gui): QObject.__init__(self) self.ticker = Ticker(self.scan, parent=gui) self.last_sweep = now() # to be updated with current time at each blip self.aircraft_list = [] # Aircraft list self.blips_invisible = { } # str -> int; number of blips for which ACFT callsign has been invisible self.soft_links = [] # (Strip, Aircraft) pairs self.known_EMG_squawkers = set() # str identifiers self.runway_occupation = {} # int -> list of ACFT identifiers if env.airport_data != None: for i in range(env.airport_data.physicalRunwayCount()): self.runway_occupation[i] = [] def startSweeping(self): self.ticker.start_stopOnZero(settings.radar_sweep_interval) def stopSweeping(self): self.ticker.stop() def scan(self): visible_aircraft = { a.identifier: a for a in settings.session_manager.getAircraft() if a.isRadarVisible() } ## UPDATE AIRCRAFT LIST lost_contacts = [] for got_acft in self.aircraft_list: try: ignore = visible_aircraft.pop(got_acft.identifier) got_acft.saveRadarSnapshot() self.blips_invisible[got_acft.identifier] = 0 except KeyError: count = self.blips_invisible[got_acft.identifier] if count < settings.invisible_blips_before_contact_lost: self.blips_invisible[got_acft.identifier] = count + 1 else: lost_contacts.append(got_acft.identifier) # Remove lost aircraft for acft in pop_all(self.aircraft_list, lambda acft: acft.identifier in lost_contacts): strip = env.linkedStrip(acft) del self.blips_invisible[acft.identifier] self.known_EMG_squawkers.discard(acft.identifier) self.lostContact.emit(acft) if strip != None: signals.controlledContactLost.emit(strip, acft.coords()) # Add newly visible aircraft for new_acft in visible_aircraft.values(): new_acft.saveRadarSnapshot() self.aircraft_list.append(new_acft) self.blips_invisible[new_acft.identifier] = 0 self.newContact.emit(new_acft) ## CHECK FOR NEW EMERGENCIY SQUAWKS for acft in self.aircraft_list: if acft.xpdrCode() in XPDR_emergency_codes: if acft.identifier not in self.known_EMG_squawkers: self.known_EMG_squawkers.add(acft.identifier) self.emergencySquawk.emit(acft) else: self.known_EMG_squawkers.discard(acft.identifier) ## CHECK FOR NEW/LOST RADAR IDENTIFICATIONS if settings.traffic_identification_assistant: found_S_links = [] found_A_links = [] for strip in env.strips.listStrips( lambda s: s.linkedAircraft() == None): mode_S_found = False # Try mode S identification if strip.lookup(FPL.CALLSIGN) != None: scs = strip.lookup(FPL.CALLSIGN).upper() if env.strips.count( lambda s: s.lookup(FPL.CALLSIGN) != None and s. lookup(FPL.CALLSIGN).upper() == scs) == 1: candidates = [ acft for acft in self.aircraft_list if acft.xpdrCallsign() != None and acft.xpdrCallsign().upper() == scs ] if len(candidates) == 1: found_S_links.append((strip, candidates[0])) mode_S_found = True # Try mode A identification if not mode_S_found: ssq = strip.lookup(assigned_SQ_detail) if ssq != None and env.strips.count(lambda s: \ s.lookup(assigned_SQ_detail) == ssq and s.linkedAircraft() == None) == 1: # only one non-linked strip with this SQ candidates = [acft for acft in self.aircraft_list if not any(a is acft for s, a in found_S_links) \ and acft.xpdrCode() == ssq and env.linkedStrip(acft) == None] if len(candidates) == 1: # only one aircraft matching found_A_links.append((strip, candidates[0])) for s, a in pop_all( self.soft_links, lambda sl: not any(sl[0] is s and sl[ 1] is a for s, a in found_S_links + found_A_links)): s.writeDetail(soft_link_detail, None) for s, a, m in [(s, a, True) for s, a in found_S_links ] + [(s, a, False) for s, a in found_A_links]: if not any(sl[0] is s and sl[1] is a for sl in self.soft_links): # new found soft link if strip.lookup( received_from_detail ) != None and settings.strip_autolink_on_ident and ( m or settings.strip_autolink_include_modeC): s.linkAircraft(a) else: # strip not automatically linked; notify of a new identification self.soft_links.append((s, a)) s.writeDetail(soft_link_detail, a) signals.aircraftIdentification.emit(s, a, m) ## UPDATE POSITION/ROUTE WARNINGS conflicts = { acft.identifier: Conflict.NO_CONFLICT for acft in self.aircraft_list } traffic_for_route_checks = [] for strip in env.strips.listStrips( ): # check for position conflicts and build list of traffic to check for routes later acft = strip.linkedAircraft() if acft != None and acft.identifier in conflicts: # controlled traffic with radar contact for other in self.aircraft_list: if other is not acft and position_conflict_test( acft, other ) == Conflict.NEAR_MISS: # positive separation loss detected conflicts[acft.identifier] = conflicts[ other.identifier] = Conflict.NEAR_MISS if not bypass_route_conflict_check(strip): traffic_for_route_checks.append(acft) if settings.route_conflict_warnings: # check for route conflicts while traffic_for_route_checks != []: # progressively emptying the list acft = traffic_for_route_checks.pop() for other in traffic_for_route_checks: c = path_conflict_test(acft, other) conflicts[acft.identifier] = max( conflicts[acft.identifier], c) conflicts[other.identifier] = max( conflicts[other.identifier], c) # now update aircraft conflicts and emit signals if any are new new_near_miss = new_path_conflict = False for contact in self.aircraft_list: new_conflict = conflicts[contact.identifier] if new_conflict > contact.conflict: new_near_miss |= new_conflict == Conflict.NEAR_MISS new_path_conflict |= new_conflict in [ Conflict.DEPENDS_ON_ALT, Conflict.PATH_CONFLICT ] contact.conflict = new_conflict if new_path_conflict: self.pathConflict.emit() if new_near_miss: self.nearMiss.emit() ## UPDATE RUNWAY OCCUPATION for phrwy in self.runway_occupation: new_occ = [] if settings.monitor_runway_occupation: rwy1, rwy2 = env.airport_data.physicalRunway(phrwy) width_metres = env.airport_data.physicalRunwayData(phrwy)[0] thr1 = rwy1.threshold().toRadarCoords() thr2 = rwy2.threshold().toRadarCoords() w = m2NM * width_metres for acft in self.aircraft_list: if acft.considerOnGround(): if acft.coords().toRadarCoords().isBetween( thr1, thr2, w / 2): # ACFT is on RWY new_occ.append(acft) if not any( a is acft for a in self.runway_occupation[phrwy] ): # just entered the RWY: check if alarm must sound try: boxed_link = env.strips.findStrip( lambda strip: strip.lookup( runway_box_detail) == phrwy ).linkedAircraft() except StopIteration: # no strip boxed on this runway if rwy1.inUse() or rwy2.inUse( ): # entering a non-reserved but active RWY self.runwayIncursion.emit(phrwy, acft) else: # RWY is reserved if boxed_link == None and env.linkedStrip( acft ) == None or boxed_link is acft: # entering ACFT is the one cleared to enter, or can be if self.runway_occupation[ phrwy] != []: # some ACFT was/were already on RWY call_guilty = acft if boxed_link == None else self.runway_occupation[ phrwy][0] self.runwayIncursion.emit( phrwy, call_guilty) else: # entering ACFT is known to be different from the one cleared to enter self.runwayIncursion.emit(phrwy, acft) self.runway_occupation[phrwy] = new_occ # Finished aircraft stuff self.last_sweep = now() self.blip.emit() def runwayOccupation(self, phrwy): return self.runway_occupation[phrwy] def missedOnLastScan(self, acft_id): ''' True if ACFT is known (i.e. not already lost) but was not picked up on last radar scan. ''' try: return self.blips_invisible[acft_id] > 0 except KeyError: return False def contacts(self): ''' Returns a list of connected aircraft contacts ''' return self.aircraft_list[:] def resetContacts(self): self.aircraft_list.clear() self.blips_invisible.clear() self.soft_links.clear() self.known_EMG_squawkers.clear() for phrwy in self.runway_occupation: self.runway_occupation[phrwy].clear() def silentlyForgetContact(self, killed): for popped in pop_all( self.aircraft_list, lambda acft: acft is killed): # there should only be one del self.blips_invisible[killed.identifier] self.known_EMG_squawkers.discard(killed.identifier)
class SoloSessionManager(SessionManager): ''' VIRTUAL! Subclass and define methods: - generateAircraftAndStrip(): return (ACFT, Strip) pair of possibly None values - handoverGuard(cs, atc): return str error msg if handover not OK ''' def __init__(self, gui): SessionManager.__init__(self, gui) self.session_type = SessionType.SOLO self.session_ticker = Ticker(self.tickSessionOnce, parent=gui) self.weather_ticker = Ticker(self.setNewWeather, parent=gui) self.spawn_timer = QTimer(gui) self.spawn_timer.setSingleShot(True) self.voice_instruction_recogniser = None self.speech_synthesiser = None self.msg_is_from_session_manager = False # set to True before sending to avoid chat msg being rejected if speech_recognition_available: try: self.voice_instruction_recogniser = InstructionRecogniser(gui) except RuntimeError as err: settings.solo_voice_instructions = False QMessageBox.critical(self.gui, 'Sphinx error', \ 'Error setting up the speech recogniser (check log): %s\nVoice instructions disabled.' % err) if speech_synthesis_available: try: self.speech_synthesiser = SpeechSynthesiser(gui) except Exception as err: settings.solo_voice_readback = False QMessageBox.critical(self.gui, 'Pyttsx error', \ 'Error setting up the speech synthesiser: %s\nPilot read-back disabled.' % err) self.controlled_traffic = [] self.uncontrolled_traffic = [] self.current_local_weather = None self.simulation_paused_at = None # start time if session is paused; None otherwise self.spawn_timer.timeout.connect( lambda: self.spawnNewControlledAircraft(isSessionStart=False)) self.playable_aircraft_types = settings.solo_aircraft_types[:] self.uncontrolled_aircraft_types = [ t for t in known_aircraft_types() if cruise_speed(t) != None ] pop_all(self.playable_aircraft_types, lambda t: t not in known_aircraft_types()) pop_all(self.playable_aircraft_types, lambda t: cruise_speed(t) == None) def start(self, traffic_count): if self.playable_aircraft_types == []: QMessageBox.critical( self.gui, 'Not enough ACFT types', 'Cannot start simulation: not enough playable aircraft types.') env.ATCs.clear() return if self.voice_instruction_recogniser != None: self.voice_instruction_recogniser.startup() signals.kbdPTT.connect(self.voicePTT) if self.speech_synthesiser != None: self.speech_synthesiser.startup() signals.voiceMsg.connect(self.speech_synthesiser.radioMsg) self.controlled_traffic.clear() self.uncontrolled_traffic.clear() for i in range(traffic_count): self.spawnNewControlledAircraft(isSessionStart=True) self.adjustDistractorCount() self.simulation_paused_at = None self.setNewWeather() self.session_ticker.start_stopOnZero(solo_ticker_interval) self.startWeatherTicker() signals.voiceMsgRecognised.connect(self.handleVoiceInstrMessage) signals.soloSessionSettingsChanged.connect(self.startWeatherTicker) signals.soloSessionSettingsChanged.connect(self.adjustDistractorCount) signals.sessionStarted.emit() print('Solo simulation begins.') def stop(self): if self.isRunning(): signals.voiceMsgRecognised.disconnect(self.handleVoiceInstrMessage) signals.soloSessionSettingsChanged.disconnect( self.startWeatherTicker) signals.soloSessionSettingsChanged.disconnect( self.adjustDistractorCount) if self.voice_instruction_recogniser != None: signals.kbdPTT.disconnect(self.voicePTT) self.voice_instruction_recogniser.shutdown() self.voice_instruction_recogniser.wait() if self.speech_synthesiser != None: signals.voiceMsg.disconnect(self.speech_synthesiser.radioMsg) self.speech_synthesiser.shutdown() self.speech_synthesiser.wait() self.spawn_timer.stop() self.weather_ticker.stop() self.simulation_paused_at = None self.session_ticker.stop() self.controlled_traffic.clear() self.uncontrolled_traffic.clear() signals.sessionEnded.emit() def isRunning(self): return self.session_ticker.isActive( ) or self.simulation_paused_at != None def myCallsign(self): return settings.location_code def getAircraft(self): return self.controlled_traffic + self.uncontrolled_traffic def pauseSession(self): if self.isRunning() and self.simulation_paused_at == None: self.simulation_paused_at = now() self.session_ticker.stop() signals.sessionPaused.emit() def resumeSession(self): if self.isRunning() and self.simulation_paused_at != None: pause_delay = now() - self.simulation_paused_at for acft in self.getAircraft(): acft.moveHistoryTimesForward(pause_delay) self.session_ticker.start_stopOnZero(solo_ticker_interval) self.simulation_paused_at = None signals.sessionResumed.emit() ## WEATHER def getWeather(self, station): return self.current_local_weather if station == settings.primary_METAR_station else None def setNewWeather(self): wind_info = None if self.current_local_weather == None else self.current_local_weather.mainWind( ) if wind_info == None: w1 = 10 * randint(1, 36) w2 = randint(5, 20) if env.airport_data != None and \ not any(rwy.inUse() and abs(w1 - rwy.orientation().trueAngle()) <= 90 for rwy in env.airport_data.allRunways()): w1 += 180 else: whdg, wspd, gusts, unit = wind_info w1 = whdg.trueAngle() + 10 * randint(-1, 1) w2 = bounded(5, wspd + randint(-4, 4), 20) windstr = '%03d%02dKT' % ((w1 - 1) % 360 + 1, w2) self.current_local_weather = mkWeather(settings.primary_METAR_station, wind=windstr) signals.newWeather.emit(settings.primary_METAR_station, self.current_local_weather) def startWeatherTicker(self): self.weather_ticker.start_stopOnZero( settings.solo_weather_change_interval, immediate=False) ## COMMUNICATIONS def postRadioChatMsg(self, msg): if self.msg_is_from_session_manager: self.msg_is_from_session_manager = False else: raise ValueError('Text messages not supported in solo sessions.') def postAtcChatMsg(self, msg): raise ValueError('ATC chat not available in solo sessions.') ## TICKING AND SPAWNING def controlledAcftNeeded(self): return len(self.controlled_traffic) < settings.solo_max_aircraft_count def killAircraft(self, acft): if env.cpdlc.isConnected(acft.identifier): env.cpdlc.endDataLink(acft.identifier) if len(pop_all(self.controlled_traffic, lambda a: a is acft)) == 0: pop_all(self.uncontrolled_traffic, lambda a: a is acft) signals.aircraftKilled.emit(acft) def adjustDistractorCount(self): while len( self.uncontrolled_traffic ) > settings.solo_distracting_traffic_count: # too many uncontrolled ACFT self.killAircraft(self.uncontrolled_traffic[0]) for i in range( settings.solo_distracting_traffic_count - len(self.uncontrolled_traffic)): # uncontrolled ACFT needed self.spawnNewUncontrolledAircraft() def spawnNewUncontrolledAircraft(self): rndpos = env.radarPos().moved(Heading(randint(1, 360), True), uniform(10, .8 * settings.radar_range)) rndalt = StdPressureAlt(randint(1, 10) * 1000) if self.airbornePositionFullySeparated(rndpos, rndalt): acft_type = choice(self.uncontrolled_aircraft_types) params = SoloParams(Status(Status.AIRBORNE), rndpos, rndalt, Heading(randint(1, 360), True), cruise_speed(acft_type)) params.XPDR_code = settings.uncontrolled_VFR_XPDR_code new_acft = self.mkAiAcft(acft_type, params, goal=None) if new_acft != None: self.uncontrolled_traffic.append(new_acft) def spawnNewControlledAircraft(self, isSessionStart=False): new_acft = None attempts = 0 while new_acft == None and attempts < max_attempts_for_aircraft_spawn: new_acft, strip = self.generateAircraftAndStrip() attempts += 1 if new_acft != None and self.controlledAcftNeeded( ) and self.simulation_paused_at == None: self.controlled_traffic.append(new_acft) if settings.controller_pilot_data_link and random( ) <= settings.solo_CPDLC_balance: env.cpdlc.beginDataLink( new_acft.identifier, self.myCallsign(), transferFrom=strip.lookup(received_from_detail)) if strip != None: if isSessionStart: strip.linkAircraft(new_acft) strip.writeDetail(received_from_detail, None) signals.receiveStrip.emit(strip) if not env.cpdlc.isConnected(new_acft.identifier): new_acft.makeInitialContact( None if settings.location_radio_name == '' else settings.location_radio_name) def airbornePositionFullySeparated(self, pos, alt): try: horiz_near = [ acft for acft in self.getAircraft() if acft.params.position.distanceTo(pos) < settings.horizontal_separation ] ignore = next(acft for acft in horiz_near if abs( acft.params.altitude.diff(alt)) < settings.vertical_separation) return False except StopIteration: # No aircraft too close return True def groundPositionFullySeparated(self, pos, t): return all( ground_separated(acft, pos, t) for acft in self.getAircraft() if acft.isGroundStatus()) def tickSessionOnce(self): if self.controlledAcftNeeded() and not self.spawn_timer.isActive(): delay = randint(int(settings.solo_min_spawn_delay.total_seconds()), int(settings.solo_max_spawn_delay.total_seconds())) self.spawn_timer.start(1000 * delay) self.adjustDistractorCount() pop_all( self.controlled_traffic, lambda a: a.released or not env. pointInRadarRange(a.params.position)) pop_all(self.uncontrolled_traffic, lambda a: a.ticks_to_live == 0) for acft in self.getAircraft(): acft.tickOnce() send_packet_to_views(acft.fgmsLivePositionPacket()) def mkAiAcft(self, acft_type, params, goal): ''' goal=None for UncontrolledAircraft; otherwise ControlledAircraft returns None if something prevented fresh ACFT creation, e.g. CallsignGenerationError. ''' params.XPDR_mode = 'S' if acft_cat(acft_type) in ['jets', 'heavy' ] else 'C' airlines = known_airline_codes() if env.airport_data != None: # might be rendering in tower view, prefer ACFT with known liveries liveries_for_acft = FGFS_model_liveries.get(acft_type, {}) if len(liveries_for_acft ) > 0 and settings.solo_restrict_to_available_liveries: pop_all(airlines, lambda al: al not in liveries_for_acft) try: callsign = self.generateCallsign(acft_type, airlines) if goal == None: ms_to_live = 1000 * 60 * randint(10, 60 * 3) return UncontrolledAircraft(callsign, acft_type, params, ms_to_live // solo_ticker_interval) else: return ControlledAircraft(callsign, acft_type, params, goal) except CallsignGenerationError: return None ## DEALING WITH INSTRUCTIONS def sendCpdlcMsg(self, callsign, msg): link = env.cpdlc.currentDataLink(callsign) if link != None: link.appendMessage(msg) if msg.type( ) == CpdlcMessage.INSTR: # other message types ignored (unimplemented in solo) try: acft = next( a for a in self.controlled_traffic if a.identifier == callsign) # uncontrolled traffic is not in contact acft.instruct([Instruction.fromEncodedStr(msg.contents())]) # FUTURE ingest before instruct to allow exception raised or (delayed?) WILCO msg before actually executing except StopIteration: # ACFT not found or not connected print('WARNING: Aircraft %s not found.' % callsign) except Instruction.Error as err: # raised by ControlledAircraft.instruct link.appendMessage( CpdlcMessage(False, CpdlcMessage.REJECT, contents=str(err))) else: # instruction sent and already accepted link.appendMessage(CpdlcMessage(False, CpdlcMessage.ACK)) def transferCpdlcAuthority(self, acft_callsign, atc_callsign): try: acft = next(a for a in self.controlled_traffic if a.identifier == acft_callsign) guard = self.handoverGuard(acft, atc_callsign) if guard == None: env.cpdlc.endDataLink(acft_callsign, transferTo=atc_callsign) acft.released = True else: raise CpdlcAuthorityTransferFailed(acft_callsign, atc_callsign, guard) except StopIteration: pass def disconnectCpdlc(self, callsign): env.cpdlc.endDataLink(callsign) def instrExpectedByVoice(self, itype): return settings.solo_voice_instructions \ and itype in [Instruction.VECTOR_HDG, Instruction.VECTOR_ALT, Instruction.VECTOR_SPD, Instruction.HAND_OVER] def voicePTT(self, key, toggle): if self.voice_instruction_recogniser != None and settings.solo_voice_instructions and self.simulation_paused_at == None: if toggle: self.voice_instruction_recogniser.keyIn() else: self.voice_instruction_recogniser.keyOut() def rejectInstruction(self, msg): if settings.solo_erroneous_instruction_warning: QMessageBox.warning(self.gui, 'Erroneous/rejected instruction', msg) def instructAircraftByCallsign(self, callsign, instr): if not self.instrExpectedByVoice(instr.type): self._instructSequence([instr], callsign) def _instructSequence(self, instructions, callsign): try: acft = next(a for a in self.controlled_traffic if a.identifier == callsign) # uncontrolled traffic is not in contact self.msg_is_from_session_manager = True signals.chatInstructionSuggestion.emit( callsign, _instr_str(instructions, acft), True) try: acft.instruct(instructions) acft.readBack(instructions) if settings.solo_wilco_beeps: signals.wilco.emit() except Instruction.Error as err: acft.say('Unable. %s' % err, True) self.rejectInstruction('%s: "%s"' % (callsign, speech_str2txt(str(err)))) except StopIteration: self.msg_is_from_session_manager = True signals.chatInstructionSuggestion.emit( callsign, _instr_str(instructions, None), True) self.rejectInstruction('Nobody answering callsign %s' % callsign) def handleVoiceInstrMessage(self, radio_callsign_tokens, instructions): acft_matches = [ acft for acft in self.getAircraft() if radio_callsign_match(radio_callsign_tokens, acft.identifier) ] if acft_matches == []: callsign_to_instruct = write_radio_callsign(radio_callsign_tokens) elif len(acft_matches) == 1: callsign_to_instruct = acft_matches[0].identifier else: acft_matches[0].say('Sorry, was this for me?', True) self.rejectInstruction('Used callsign matches several: %s' % ', '.join(acft.identifier for acft in acft_matches)) return if len(acft_matches) == 1: for instr in instructions: if instr.type == Instruction.HAND_OVER: guard = self.handoverGuard(acft_matches[0], instr.arg[0]) if guard != None: acft_matches[0].say('Negative. Staying with you.', True) self.rejectInstruction('Bad/untimely handover:\n%s' % guard) return self._instructSequence(instructions, callsign_to_instruct) def stripDroppedOnATC(self, strip, atc): if not self.instrExpectedByVoice(Instruction.HAND_OVER): cs = strip.callsign(acft=True) try: acft = next(a for a in self.controlled_traffic if a.identifier == cs) guard = self.handoverGuard(acft, atc) if guard == None: transfer_selected_or_instruct(atc) else: raise HandoverBlocked(guard) except StopIteration: return
def __init__(self, launcher, parent=None): QMainWindow.__init__(self, parent) self.setupUi(self) self.central_workspace = WorkspaceWidget(self) self.setCentralWidget(self.central_workspace) self.installEventFilter(RadioKeyEventFilter(self)) self.setAttribute(Qt.WA_DeleteOnClose) self.launcher = launcher settings.controlled_tower_viewer = FlightGearTowerViewer(self) settings.session_manager = SessionManager(self) self.setWindowTitle('%s - %s (%s)' % (self.windowTitle(), env.locationName(), settings.location_code)) self.session_start_sound_lock_timer = QTimer(self) self.session_start_sound_lock_timer.setSingleShot(True) self.session_start_sound_lock_timer.timeout.connect(self.unlockSounds) ## Restore saved dock layout try: with open(dock_layout_file, 'rb') as f: # Restore saved dock arrangement self.restoreState(f.read()) except FileNotFoundError: # Fallback on default dock arrangement # left docks, top zone self.tabifyDockWidget(self.selection_info_dock, self.weather_dock) self.tabifyDockWidget(self.selection_info_dock, self.towerView_dock) self.tabifyDockWidget(self.selection_info_dock, self.navigator_dock) self.selection_info_dock.hide() # left docks, bottom zone self.tabifyDockWidget(self.instructions_dock, self.notepads_dock) self.tabifyDockWidget(self.instructions_dock, self.radio_dock) self.tabifyDockWidget(self.instructions_dock, self.FPLlist_dock) self.tabifyDockWidget(self.instructions_dock, self.CPDLC_dock) self.instructions_dock.hide() self.notepads_dock.hide() self.radio_dock.hide() self.CPDLC_dock.hide() # right docks self.rwyBoxes_dock.hide() # hiding this because bad position (user will drag 1st thing after raise) # bottom docks self.atcTextChat_dock.hide() ## Permanent tool/status bar widgets self.selectionInfo_toolbar.addWidget(SelectionInfoToolbarWidget(self)) self.METAR_statusBarLabel = QLabel() self.PTT_statusBarLabel = QLabel() self.RDF_statusBarLabel = QLabel() self.RDF_statusBarLabel.setToolTip('Current signal / last QDM') self.wind_statusBarLabel = QLabel() self.QNH_statusBarLabel = QLabel() self.QNH_statusBarLabel.setToolTip('hPa / inHg') self.clock_statusBarLabel = QLabel() self.alarmClock_statusBarButtons = [AlarmClockButton('1', self), AlarmClockButton('2', self)] self.statusbar.addWidget(self.METAR_statusBarLabel) self.statusbar.addPermanentWidget(self.PTT_statusBarLabel) self.statusbar.addPermanentWidget(self.RDF_statusBarLabel) self.statusbar.addPermanentWidget(self.wind_statusBarLabel) self.statusbar.addPermanentWidget(self.QNH_statusBarLabel) for b in self.alarmClock_statusBarButtons: self.statusbar.addPermanentWidget(b) b.alarm.connect(self.notification_pane.notifyAlarmClockTimedOut) self.statusbar.addPermanentWidget(self.clock_statusBarLabel) # Populate menus (toolbar visibility and airport viewpoints) toolbar_menu = QMenu() self.general_viewToolbar_action = self.general_toolbar.toggleViewAction() self.stripActions_viewToolbar_action = self.stripActions_toolbar.toggleViewAction() self.docks_viewToolbar_action = self.docks_toolbar.toggleViewAction() self.selectionInfo_viewToolbar_action = self.selectionInfo_toolbar.toggleViewAction() self.radarAssistance_viewToolbar_action = self.radarAssistance_toolbar.toggleViewAction() self.workspace_viewToolbar_action = self.workspace_toolbar.toggleViewAction() toolbar_menu.addAction(self.general_viewToolbar_action) toolbar_menu.addAction(self.stripActions_viewToolbar_action) toolbar_menu.addAction(self.docks_viewToolbar_action) toolbar_menu.addAction(self.selectionInfo_viewToolbar_action) toolbar_menu.addAction(self.radarAssistance_viewToolbar_action) toolbar_menu.addAction(self.workspace_viewToolbar_action) self.toolbars_view_menuAction.setMenu(toolbar_menu) if env.airport_data == None or len(env.airport_data.viewpoints) == 0: self.viewpointSelection_view_menuAction.setEnabled(False) else: viewPointSelection_menu = QMenu() viewPointSelection_actionGroup = QActionGroup(self) for vp_i, (vp_pos, vp_h, vp_name) in enumerate(env.airport_data.viewpoints): action = QAction('%s - %d ft ASFC' % (vp_name, vp_h + .5), self) action.setCheckable(True) action.triggered.connect(lambda ignore_checked, i=vp_i: self.selectIndicateViewpoint(i)) viewPointSelection_actionGroup.addAction(action) actions = viewPointSelection_actionGroup.actions() viewPointSelection_menu.addActions(actions) self.viewpointSelection_view_menuAction.setMenu(viewPointSelection_menu) try: actions[settings.selected_viewpoint].setChecked(True) except IndexError: actions[0].setChecked(True) ## Memory-persistent windows and dialogs self.solo_connect_dialog_AD = StartSoloDialog_AD(self) self.MP_connect_dialog = StartFlightGearMPdialog(self) self.start_student_session_dialog = StartStudentSessionDialog(self) self.recall_cheat_dialog = DiscardedStripsDialog(self, ShelfFilterModel(self, env.discarded_strips, False), 'Sent and deleted strips') self.shelf_dialog = DiscardedStripsDialog(self, ShelfFilterModel(self, env.discarded_strips, True), 'Strip shelf') self.environment_info_dialog = EnvironmentInfoDialog(self) self.about_dialog = AboutDialog(self) self.teaching_console = TeachingConsole(parent=self) self.unit_converter = UnitConversionWindow(parent=self) self.world_airport_navigator = WorldAirportNavigator(parent=self) self.quick_reference = QuickReference(parent=self) for w in self.teaching_console, self.unit_converter, self.world_airport_navigator, self.quick_reference: w.setWindowFlags(Qt.Window) w.installEventFilter(RadioKeyEventFilter(w)) # Make a few actions always visible self.addAction(self.newStrip_action) self.addAction(self.newLinkedStrip_action) self.addAction(self.newFPL_action) self.addAction(self.newLinkedFPL_action) self.addAction(self.startTimer1_action) self.addAction(self.forceStartTimer1_action) self.addAction(self.startTimer2_action) self.addAction(self.forceStartTimer2_action) self.addAction(self.notificationSounds_options_action) self.addAction(self.quickReference_help_action) self.addAction(self.saveDockLayout_view_action) self.addAction(self.recallWindowState_view_action) # Populate icons self.newStrip_action.setIcon(QIcon(IconFile.action_newStrip)) self.newLinkedStrip_action.setIcon(QIcon(IconFile.action_newLinkedStrip)) self.newFPL_action.setIcon(QIcon(IconFile.action_newFPL)) self.newLinkedFPL_action.setIcon(QIcon(IconFile.action_newLinkedFPL)) self.teachingConsole_view_action.setIcon(QIcon(IconFile.panel_teaching)) self.unitConversionTool_view_action.setIcon(QIcon(IconFile.panel_unitConv)) self.worldAirportNavigator_view_action.setIcon(QIcon(IconFile.panel_airportList)) self.environmentInfo_view_action.setIcon(QIcon(IconFile.panel_envInfo)) self.generalSettings_options_action.setIcon(QIcon(IconFile.action_generalSettings)) self.soloSessionSettings_system_action.setIcon(QIcon(IconFile.action_sessionSettings)) self.runwaysInUse_options_action.setIcon(QIcon(IconFile.action_runwayUse)) self.newLooseStripBay_view_action.setIcon(QIcon(IconFile.action_newLooseStripBay)) self.newRadarScreen_view_action.setIcon(QIcon(IconFile.action_newRadarScreen)) self.newStripRackPanel_view_action.setIcon(QIcon(IconFile.action_newRackPanel)) self.popOutCurrentWindow_view_action.setIcon(QIcon(IconFile.action_popOutWindow)) self.reclaimPoppedOutWindows_view_action.setIcon(QIcon(IconFile.action_reclaimWindows)) self.primaryRadar_options_action.setIcon(QIcon(IconFile.option_primaryRadar)) self.approachSpacingHints_options_action.setIcon(QIcon(IconFile.option_approachSpacingHints)) self.runwayOccupationWarnings_options_action.setIcon(QIcon(IconFile.option_runwayOccupationMonitor)) self.routeConflictWarnings_options_action.setIcon(QIcon(IconFile.option_routeConflictWarnings)) self.trafficIdentification_options_action.setIcon(QIcon(IconFile.option_identificationAssistant)) setDockAndActionIcon(IconFile.panel_ATCs, self.handovers_dockView_action, self.handover_dock) setDockAndActionIcon(IconFile.panel_atcChat, self.atcTextChat_dockView_action, self.atcTextChat_dock) setDockAndActionIcon(IconFile.panel_CPDLC, self.cpdlc_dockView_action, self.CPDLC_dock) setDockAndActionIcon(IconFile.panel_FPLs, self.FPLs_dockView_action, self.FPLlist_dock) setDockAndActionIcon(IconFile.panel_instructions, self.instructions_dockView_action, self.instructions_dock) setDockAndActionIcon(IconFile.panel_navigator, self.navpoints_dockView_action, self.navigator_dock) setDockAndActionIcon(IconFile.panel_notepads, self.notepads_dockView_action, self.notepads_dock) setDockAndActionIcon(IconFile.panel_notifications, self.notificationArea_dockView_action, self.notification_dock) setDockAndActionIcon(IconFile.panel_radios, self.fgcom_dockView_action, self.radio_dock) setDockAndActionIcon(IconFile.panel_runwayBox, self.runwayBoxes_dockView_action, self.rwyBoxes_dock) setDockAndActionIcon(IconFile.panel_selInfo, self.radarContactDetails_dockView_action, self.selection_info_dock) setDockAndActionIcon(IconFile.panel_racks, self.strips_dockView_action, self.strip_dock) setDockAndActionIcon(IconFile.panel_txtChat, self.radioTextChat_dockView_action, self.radioTextChat_dock) setDockAndActionIcon(IconFile.panel_twrView, self.towerView_dockView_action, self.towerView_dock) setDockAndActionIcon(IconFile.panel_weather, self.weather_dockView_action, self.weather_dock) # action TICKED STATES (set here before connections) self.windowedWorkspace_view_action.setChecked(settings.saved_workspace_windowed_view) self.verticalRwyBoxLayout_view_action.setChecked(settings.vertical_runway_box_layout) self.notificationSounds_options_action.setChecked(settings.notification_sounds_enabled) self.primaryRadar_options_action.setChecked(settings.primary_radar_active) self.routeConflictWarnings_options_action.setChecked(settings.route_conflict_warnings) self.trafficIdentification_options_action.setChecked(settings.traffic_identification_assistant) self.runwayOccupationWarnings_options_action.setChecked(settings.monitor_runway_occupation) self.approachSpacingHints_options_action.setChecked(settings.APP_spacing_hints) # action CONNECTIONS # non-menu actions self.newStrip_action.triggered.connect(lambda: new_strip_dialog(self, default_rack_name, linkToSelection=False)) self.newLinkedStrip_action.triggered.connect(lambda: new_strip_dialog(self, default_rack_name, linkToSelection=True)) self.newFPL_action.triggered.connect(lambda: self.FPLlist_pane.createLocalFPL(link=None)) self.newLinkedFPL_action.triggered.connect(lambda: self.FPLlist_pane.createLocalFPL(link=selection.strip)) self.startTimer1_action.triggered.connect(lambda: self.startTimer(0, False)) self.forceStartTimer1_action.triggered.connect(lambda: self.startTimer(0, True)) self.startTimer2_action.triggered.connect(lambda: self.startTimer(1, False)) self.forceStartTimer2_action.triggered.connect(lambda: self.startTimer(1, True)) # system menu self.soloSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_solo)) self.connectFlightGearMP_system_action.triggered.connect(lambda: self.startStopSession(self.start_FlightGearMP)) self.teacherSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_teaching)) self.studentSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_learning)) self.reloadAdditionalViewers_system_action.triggered.connect(self.reloadAdditionalViewers) self.reloadBgImages_system_action.triggered.connect(self.reloadBackgroundImages) self.reloadColourConfig_system_action.triggered.connect(self.reloadColourConfig) self.reloadRoutePresets_system_action.triggered.connect(self.reloadRoutePresets) self.reloadEntryExitPoints_system_action.triggered.connect(self.reloadEntryExitPoints) self.announceFgSession_system_action.triggered.connect(self.announceFgSession) self.fgcomEchoTest_system_action.triggered.connect(self.radio_pane.performEchoTest) self.extractSectorFile_system_action.triggered.connect(self.extractSectorFile) self.repositionBgImages_system_action.triggered.connect(self.repositionRadarBgImages) self.measuringLogsCoordinates_system_action.toggled.connect(self.switchMeasuringCoordsLog) self.airportGateway_system_action.triggered.connect(lambda: self.goToURL(airport_gateway_URL)) self.openStreetMap_system_action.triggered.connect(lambda: self.goToURL(mk_OSM_URL(env.radarPos()))) self.soloSessionSettings_system_action.triggered.connect(self.openSoloSessionSettings) self.locationSettings_system_action.triggered.connect(self.openLocalSettings) self.systemSettings_system_action.triggered.connect(self.openSystemSettings) self.changeLocation_system_action.triggered.connect(self.changeLocation) self.quit_system_action.triggered.connect(self.close) # view menu self.saveDockLayout_view_action.triggered.connect(self.saveDockLayout) self.recallWindowState_view_action.triggered.connect(self.recallWindowState) self.handovers_dockView_action.triggered.connect(lambda: self.raiseDock(self.handover_dock)) self.atcTextChat_dockView_action.triggered.connect(lambda: self.raiseDock(self.atcTextChat_dock)) self.cpdlc_dockView_action.triggered.connect(lambda: self.raiseDock(self.CPDLC_dock)) self.FPLs_dockView_action.triggered.connect(lambda: self.raiseDock(self.FPLlist_dock)) self.instructions_dockView_action.triggered.connect(lambda: self.raiseDock(self.instructions_dock)) self.navpoints_dockView_action.triggered.connect(lambda: self.raiseDock(self.navigator_dock)) self.notepads_dockView_action.triggered.connect(lambda: self.raiseDock(self.notepads_dock)) self.notificationArea_dockView_action.triggered.connect(lambda: self.raiseDock(self.notification_dock)) self.fgcom_dockView_action.triggered.connect(lambda: self.raiseDock(self.radio_dock)) self.runwayBoxes_dockView_action.triggered.connect(lambda: self.raiseDock(self.rwyBoxes_dock)) self.radarContactDetails_dockView_action.triggered.connect(lambda: self.raiseDock(self.selection_info_dock)) self.strips_dockView_action.triggered.connect(lambda: self.raiseDock(self.strip_dock)) self.radioTextChat_dockView_action.triggered.connect(lambda: self.raiseDock(self.radioTextChat_dock)) self.towerView_dockView_action.triggered.connect(lambda: self.raiseDock(self.towerView_dock)) self.weather_dockView_action.triggered.connect(lambda: self.raiseDock(self.weather_dock)) self.windowedWorkspace_view_action.toggled.connect(self.central_workspace.switchWindowedView) self.popOutCurrentWindow_view_action.triggered.connect(self.central_workspace.popOutCurrentWindow) self.reclaimPoppedOutWindows_view_action.triggered.connect(self.central_workspace.reclaimPoppedOutWidgets) self.newLooseStripBay_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.LOOSE_BAY)) self.newRadarScreen_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.RADAR_SCREEN)) self.newStripRackPanel_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.STRIP_PANEL)) self.verticalRwyBoxLayout_view_action.toggled.connect(self.switchVerticalRwyBoxLayout) self.towerView_view_action.triggered.connect(self.toggleTowerWindow) self.addViewer_view_action.triggered.connect(self.addView) self.listViewers_view_action.triggered.connect(self.listAdditionalViews) self.activateAdditionalViewers_view_action.toggled.connect(self.activateAdditionalViews) self.removeViewer_view_action.triggered.connect(self.removeView) self.teachingConsole_view_action.triggered.connect(self.teaching_console.show) self.unitConversionTool_view_action.triggered.connect(self.unit_converter.show) self.worldAirportNavigator_view_action.triggered.connect(self.world_airport_navigator.show) self.environmentInfo_view_action.triggered.connect(self.environment_info_dialog.exec) # options menu self.runwaysInUse_options_action.triggered.connect(self.configureRunwayUse) self.notificationSounds_options_action.toggled.connect(self.switchNotificationSounds) self.primaryRadar_options_action.toggled.connect(self.switchPrimaryRadar) self.routeConflictWarnings_options_action.toggled.connect(self.switchConflictWarnings) self.trafficIdentification_options_action.toggled.connect(self.switchTrafficIdentification) self.runwayOccupationWarnings_options_action.toggled.connect(self.switchRwyOccupationIndications) self.approachSpacingHints_options_action.toggled.connect(self.switchApproachSpacingHints) self.generalSettings_options_action.triggered.connect(self.openGeneralSettings) # cheat menu self.pauseSimulation_cheat_action.toggled.connect(self.pauseSession) self.spawnAircraft_cheat_action.triggered.connect(self.spawnAircraft) self.killSelectedAircraft_cheat_action.triggered.connect(self.killSelectedAircraft) self.popUpMsgOnRejectedInstr_cheat_action.toggled.connect(self.setRejectedInstrPopUp) self.showRecognisedVoiceStrings_cheat_action.toggled.connect(self.setShowRecognisedVoiceStrings) self.ensureClearWeather_cheat_action.toggled.connect(self.ensureClearWeather) self.ensureDayLight_cheat_action.triggered.connect(self.towerView_pane.ensureDayLight) self.changeTowerHeight_cheat_action.triggered.connect(self.changeTowerHeight) self.recallDiscardedStrip_cheat_action.triggered.connect(self.recall_cheat_dialog.exec) self.radarCheatMode_cheat_action.toggled.connect(self.setRadarCheatMode) # help menu self.quickReference_help_action.triggered.connect(self.quick_reference.show) self.videoTutorial_help_action.triggered.connect(lambda: self.goToURL(video_tutorial_URL)) self.FAQ_help_action.triggered.connect(lambda: self.goToURL(FAQ_URL)) self.about_help_action.triggered.connect(self.about_dialog.exec) ## More signal connections signals.openShelfRequest.connect(self.shelf_dialog.exec) signals.privateAtcChatRequest.connect(lambda: self.raiseDock(self.atcTextChat_dock)) signals.stripRecall.connect(recover_strip) env.radar.blip.connect(env.strips.refreshViews) env.radar.lostContact.connect(self.aircraftHasDisappeared) signals.aircraftKilled.connect(self.aircraftHasDisappeared) env.strips.rwyBoxFreed.connect(lambda box, strip: env.airport_data.physicalRunway_restartWtcTimer(box, strip.lookup(FPL.WTC))) env.rdf.signalChanged.connect(self.updateRDF) signals.statusBarMsg.connect(lambda msg: self.statusbar.showMessage(msg, status_bar_message_timeout)) signals.newWeather.connect(self.updateWeatherIfPrimary) signals.kbdPTT.connect(self.updatePTT) signals.sessionStarted.connect(self.sessionHasStarted) signals.sessionEnded.connect(self.sessionHasEnded) signals.towerViewProcessToggled.connect(self.towerView_view_action.setChecked) signals.towerViewProcessToggled.connect(self.towerView_cheat_menu.setEnabled) signals.stripInfoChanged.connect(env.strips.refreshViews) signals.fastClockTick.connect(self.updateClock) signals.fastClockTick.connect(env.cpdlc.updateAckStatuses) signals.slowClockTick.connect(strip_auto_print_check) signals.stripEditRequest.connect(lambda strip: edit_strip(self, strip)) signals.selectionChanged.connect(self.updateStripFplActions) signals.receiveStrip.connect(receive_strip) signals.handoverFailure.connect(self.recoverFailedHandover) signals.sessionPaused.connect(env.radar.stopSweeping) signals.sessionResumed.connect(env.radar.startSweeping) signals.aircraftKilled.connect(env.radar.silentlyForgetContact) signals.rackVisibilityLost.connect(self.collectClosedRacks) signals.localSettingsChanged.connect(env.rdf.clearAllSignals) signals.localSettingsChanged.connect(self.updateRDF) ## MISC GUI setup self.strip_pane.setViewRacks([default_rack_name]) # will be moved out if a rack panel's saved "visible_racks" claims it [*1] self.strip_pane.restoreState(settings.saved_strip_dock_state) # [*1] self.central_workspace.restoreWorkspaceWindows(settings.saved_workspace_windows) self.central_workspace.switchWindowedView(settings.saved_workspace_windowed_view) # keep this after restoring windows! self.subsecond_ticker = Ticker(signals.fastClockTick.emit, parent=self) self.subminute_ticker = Ticker(signals.slowClockTick.emit, parent=self) self.subsecond_ticker.start_stopOnZero(subsecond_tick_interval) self.subminute_ticker.start_stopOnZero(subminute_tick_interval) self.towerView_cheat_menu.setEnabled(False) self.solo_cheat_menu.setEnabled(False) self.updateClock() self.updateWeatherIfPrimary(settings.primary_METAR_station, None) self.updateStripFplActions() self.last_RDF_qdm = None self.updateRDF() self.updatePTT(0, False) # Disable some base airport stuff if doing CTR if env.airport_data == None: self.towerView_view_action.setEnabled(False) self.runwaysInUse_options_action.setEnabled(False) self.runwayOccupationWarnings_options_action.setEnabled(False) # Finish self.atcTextChat_pane.switchAtcChatFilter(None) # Show GUI on general chat room at start if speech_recognition_available: prepare_SR_language_files()
class MainWindow(QMainWindow, Ui_mainWindow): def __init__(self, launcher, parent=None): QMainWindow.__init__(self, parent) self.setupUi(self) self.central_workspace = WorkspaceWidget(self) self.setCentralWidget(self.central_workspace) self.installEventFilter(RadioKeyEventFilter(self)) self.setAttribute(Qt.WA_DeleteOnClose) self.launcher = launcher settings.controlled_tower_viewer = FlightGearTowerViewer(self) settings.session_manager = SessionManager(self) self.setWindowTitle('%s - %s (%s)' % (self.windowTitle(), env.locationName(), settings.location_code)) self.session_start_sound_lock_timer = QTimer(self) self.session_start_sound_lock_timer.setSingleShot(True) self.session_start_sound_lock_timer.timeout.connect(self.unlockSounds) ## Restore saved dock layout try: with open(dock_layout_file, 'rb') as f: # Restore saved dock arrangement self.restoreState(f.read()) except FileNotFoundError: # Fallback on default dock arrangement # left docks, top zone self.tabifyDockWidget(self.selection_info_dock, self.weather_dock) self.tabifyDockWidget(self.selection_info_dock, self.towerView_dock) self.tabifyDockWidget(self.selection_info_dock, self.navigator_dock) self.selection_info_dock.hide() # left docks, bottom zone self.tabifyDockWidget(self.instructions_dock, self.notepads_dock) self.tabifyDockWidget(self.instructions_dock, self.radio_dock) self.tabifyDockWidget(self.instructions_dock, self.FPLlist_dock) self.tabifyDockWidget(self.instructions_dock, self.CPDLC_dock) self.instructions_dock.hide() self.notepads_dock.hide() self.radio_dock.hide() self.CPDLC_dock.hide() # right docks self.rwyBoxes_dock.hide() # hiding this because bad position (user will drag 1st thing after raise) # bottom docks self.atcTextChat_dock.hide() ## Permanent tool/status bar widgets self.selectionInfo_toolbar.addWidget(SelectionInfoToolbarWidget(self)) self.METAR_statusBarLabel = QLabel() self.PTT_statusBarLabel = QLabel() self.RDF_statusBarLabel = QLabel() self.RDF_statusBarLabel.setToolTip('Current signal / last QDM') self.wind_statusBarLabel = QLabel() self.QNH_statusBarLabel = QLabel() self.QNH_statusBarLabel.setToolTip('hPa / inHg') self.clock_statusBarLabel = QLabel() self.alarmClock_statusBarButtons = [AlarmClockButton('1', self), AlarmClockButton('2', self)] self.statusbar.addWidget(self.METAR_statusBarLabel) self.statusbar.addPermanentWidget(self.PTT_statusBarLabel) self.statusbar.addPermanentWidget(self.RDF_statusBarLabel) self.statusbar.addPermanentWidget(self.wind_statusBarLabel) self.statusbar.addPermanentWidget(self.QNH_statusBarLabel) for b in self.alarmClock_statusBarButtons: self.statusbar.addPermanentWidget(b) b.alarm.connect(self.notification_pane.notifyAlarmClockTimedOut) self.statusbar.addPermanentWidget(self.clock_statusBarLabel) # Populate menus (toolbar visibility and airport viewpoints) toolbar_menu = QMenu() self.general_viewToolbar_action = self.general_toolbar.toggleViewAction() self.stripActions_viewToolbar_action = self.stripActions_toolbar.toggleViewAction() self.docks_viewToolbar_action = self.docks_toolbar.toggleViewAction() self.selectionInfo_viewToolbar_action = self.selectionInfo_toolbar.toggleViewAction() self.radarAssistance_viewToolbar_action = self.radarAssistance_toolbar.toggleViewAction() self.workspace_viewToolbar_action = self.workspace_toolbar.toggleViewAction() toolbar_menu.addAction(self.general_viewToolbar_action) toolbar_menu.addAction(self.stripActions_viewToolbar_action) toolbar_menu.addAction(self.docks_viewToolbar_action) toolbar_menu.addAction(self.selectionInfo_viewToolbar_action) toolbar_menu.addAction(self.radarAssistance_viewToolbar_action) toolbar_menu.addAction(self.workspace_viewToolbar_action) self.toolbars_view_menuAction.setMenu(toolbar_menu) if env.airport_data == None or len(env.airport_data.viewpoints) == 0: self.viewpointSelection_view_menuAction.setEnabled(False) else: viewPointSelection_menu = QMenu() viewPointSelection_actionGroup = QActionGroup(self) for vp_i, (vp_pos, vp_h, vp_name) in enumerate(env.airport_data.viewpoints): action = QAction('%s - %d ft ASFC' % (vp_name, vp_h + .5), self) action.setCheckable(True) action.triggered.connect(lambda ignore_checked, i=vp_i: self.selectIndicateViewpoint(i)) viewPointSelection_actionGroup.addAction(action) actions = viewPointSelection_actionGroup.actions() viewPointSelection_menu.addActions(actions) self.viewpointSelection_view_menuAction.setMenu(viewPointSelection_menu) try: actions[settings.selected_viewpoint].setChecked(True) except IndexError: actions[0].setChecked(True) ## Memory-persistent windows and dialogs self.solo_connect_dialog_AD = StartSoloDialog_AD(self) self.MP_connect_dialog = StartFlightGearMPdialog(self) self.start_student_session_dialog = StartStudentSessionDialog(self) self.recall_cheat_dialog = DiscardedStripsDialog(self, ShelfFilterModel(self, env.discarded_strips, False), 'Sent and deleted strips') self.shelf_dialog = DiscardedStripsDialog(self, ShelfFilterModel(self, env.discarded_strips, True), 'Strip shelf') self.environment_info_dialog = EnvironmentInfoDialog(self) self.about_dialog = AboutDialog(self) self.teaching_console = TeachingConsole(parent=self) self.unit_converter = UnitConversionWindow(parent=self) self.world_airport_navigator = WorldAirportNavigator(parent=self) self.quick_reference = QuickReference(parent=self) for w in self.teaching_console, self.unit_converter, self.world_airport_navigator, self.quick_reference: w.setWindowFlags(Qt.Window) w.installEventFilter(RadioKeyEventFilter(w)) # Make a few actions always visible self.addAction(self.newStrip_action) self.addAction(self.newLinkedStrip_action) self.addAction(self.newFPL_action) self.addAction(self.newLinkedFPL_action) self.addAction(self.startTimer1_action) self.addAction(self.forceStartTimer1_action) self.addAction(self.startTimer2_action) self.addAction(self.forceStartTimer2_action) self.addAction(self.notificationSounds_options_action) self.addAction(self.quickReference_help_action) self.addAction(self.saveDockLayout_view_action) self.addAction(self.recallWindowState_view_action) # Populate icons self.newStrip_action.setIcon(QIcon(IconFile.action_newStrip)) self.newLinkedStrip_action.setIcon(QIcon(IconFile.action_newLinkedStrip)) self.newFPL_action.setIcon(QIcon(IconFile.action_newFPL)) self.newLinkedFPL_action.setIcon(QIcon(IconFile.action_newLinkedFPL)) self.teachingConsole_view_action.setIcon(QIcon(IconFile.panel_teaching)) self.unitConversionTool_view_action.setIcon(QIcon(IconFile.panel_unitConv)) self.worldAirportNavigator_view_action.setIcon(QIcon(IconFile.panel_airportList)) self.environmentInfo_view_action.setIcon(QIcon(IconFile.panel_envInfo)) self.generalSettings_options_action.setIcon(QIcon(IconFile.action_generalSettings)) self.soloSessionSettings_system_action.setIcon(QIcon(IconFile.action_sessionSettings)) self.runwaysInUse_options_action.setIcon(QIcon(IconFile.action_runwayUse)) self.newLooseStripBay_view_action.setIcon(QIcon(IconFile.action_newLooseStripBay)) self.newRadarScreen_view_action.setIcon(QIcon(IconFile.action_newRadarScreen)) self.newStripRackPanel_view_action.setIcon(QIcon(IconFile.action_newRackPanel)) self.popOutCurrentWindow_view_action.setIcon(QIcon(IconFile.action_popOutWindow)) self.reclaimPoppedOutWindows_view_action.setIcon(QIcon(IconFile.action_reclaimWindows)) self.primaryRadar_options_action.setIcon(QIcon(IconFile.option_primaryRadar)) self.approachSpacingHints_options_action.setIcon(QIcon(IconFile.option_approachSpacingHints)) self.runwayOccupationWarnings_options_action.setIcon(QIcon(IconFile.option_runwayOccupationMonitor)) self.routeConflictWarnings_options_action.setIcon(QIcon(IconFile.option_routeConflictWarnings)) self.trafficIdentification_options_action.setIcon(QIcon(IconFile.option_identificationAssistant)) setDockAndActionIcon(IconFile.panel_ATCs, self.handovers_dockView_action, self.handover_dock) setDockAndActionIcon(IconFile.panel_atcChat, self.atcTextChat_dockView_action, self.atcTextChat_dock) setDockAndActionIcon(IconFile.panel_CPDLC, self.cpdlc_dockView_action, self.CPDLC_dock) setDockAndActionIcon(IconFile.panel_FPLs, self.FPLs_dockView_action, self.FPLlist_dock) setDockAndActionIcon(IconFile.panel_instructions, self.instructions_dockView_action, self.instructions_dock) setDockAndActionIcon(IconFile.panel_navigator, self.navpoints_dockView_action, self.navigator_dock) setDockAndActionIcon(IconFile.panel_notepads, self.notepads_dockView_action, self.notepads_dock) setDockAndActionIcon(IconFile.panel_notifications, self.notificationArea_dockView_action, self.notification_dock) setDockAndActionIcon(IconFile.panel_radios, self.fgcom_dockView_action, self.radio_dock) setDockAndActionIcon(IconFile.panel_runwayBox, self.runwayBoxes_dockView_action, self.rwyBoxes_dock) setDockAndActionIcon(IconFile.panel_selInfo, self.radarContactDetails_dockView_action, self.selection_info_dock) setDockAndActionIcon(IconFile.panel_racks, self.strips_dockView_action, self.strip_dock) setDockAndActionIcon(IconFile.panel_txtChat, self.radioTextChat_dockView_action, self.radioTextChat_dock) setDockAndActionIcon(IconFile.panel_twrView, self.towerView_dockView_action, self.towerView_dock) setDockAndActionIcon(IconFile.panel_weather, self.weather_dockView_action, self.weather_dock) # action TICKED STATES (set here before connections) self.windowedWorkspace_view_action.setChecked(settings.saved_workspace_windowed_view) self.verticalRwyBoxLayout_view_action.setChecked(settings.vertical_runway_box_layout) self.notificationSounds_options_action.setChecked(settings.notification_sounds_enabled) self.primaryRadar_options_action.setChecked(settings.primary_radar_active) self.routeConflictWarnings_options_action.setChecked(settings.route_conflict_warnings) self.trafficIdentification_options_action.setChecked(settings.traffic_identification_assistant) self.runwayOccupationWarnings_options_action.setChecked(settings.monitor_runway_occupation) self.approachSpacingHints_options_action.setChecked(settings.APP_spacing_hints) # action CONNECTIONS # non-menu actions self.newStrip_action.triggered.connect(lambda: new_strip_dialog(self, default_rack_name, linkToSelection=False)) self.newLinkedStrip_action.triggered.connect(lambda: new_strip_dialog(self, default_rack_name, linkToSelection=True)) self.newFPL_action.triggered.connect(lambda: self.FPLlist_pane.createLocalFPL(link=None)) self.newLinkedFPL_action.triggered.connect(lambda: self.FPLlist_pane.createLocalFPL(link=selection.strip)) self.startTimer1_action.triggered.connect(lambda: self.startTimer(0, False)) self.forceStartTimer1_action.triggered.connect(lambda: self.startTimer(0, True)) self.startTimer2_action.triggered.connect(lambda: self.startTimer(1, False)) self.forceStartTimer2_action.triggered.connect(lambda: self.startTimer(1, True)) # system menu self.soloSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_solo)) self.connectFlightGearMP_system_action.triggered.connect(lambda: self.startStopSession(self.start_FlightGearMP)) self.teacherSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_teaching)) self.studentSession_system_action.triggered.connect(lambda: self.startStopSession(self.start_learning)) self.reloadAdditionalViewers_system_action.triggered.connect(self.reloadAdditionalViewers) self.reloadBgImages_system_action.triggered.connect(self.reloadBackgroundImages) self.reloadColourConfig_system_action.triggered.connect(self.reloadColourConfig) self.reloadRoutePresets_system_action.triggered.connect(self.reloadRoutePresets) self.reloadEntryExitPoints_system_action.triggered.connect(self.reloadEntryExitPoints) self.announceFgSession_system_action.triggered.connect(self.announceFgSession) self.fgcomEchoTest_system_action.triggered.connect(self.radio_pane.performEchoTest) self.extractSectorFile_system_action.triggered.connect(self.extractSectorFile) self.repositionBgImages_system_action.triggered.connect(self.repositionRadarBgImages) self.measuringLogsCoordinates_system_action.toggled.connect(self.switchMeasuringCoordsLog) self.airportGateway_system_action.triggered.connect(lambda: self.goToURL(airport_gateway_URL)) self.openStreetMap_system_action.triggered.connect(lambda: self.goToURL(mk_OSM_URL(env.radarPos()))) self.soloSessionSettings_system_action.triggered.connect(self.openSoloSessionSettings) self.locationSettings_system_action.triggered.connect(self.openLocalSettings) self.systemSettings_system_action.triggered.connect(self.openSystemSettings) self.changeLocation_system_action.triggered.connect(self.changeLocation) self.quit_system_action.triggered.connect(self.close) # view menu self.saveDockLayout_view_action.triggered.connect(self.saveDockLayout) self.recallWindowState_view_action.triggered.connect(self.recallWindowState) self.handovers_dockView_action.triggered.connect(lambda: self.raiseDock(self.handover_dock)) self.atcTextChat_dockView_action.triggered.connect(lambda: self.raiseDock(self.atcTextChat_dock)) self.cpdlc_dockView_action.triggered.connect(lambda: self.raiseDock(self.CPDLC_dock)) self.FPLs_dockView_action.triggered.connect(lambda: self.raiseDock(self.FPLlist_dock)) self.instructions_dockView_action.triggered.connect(lambda: self.raiseDock(self.instructions_dock)) self.navpoints_dockView_action.triggered.connect(lambda: self.raiseDock(self.navigator_dock)) self.notepads_dockView_action.triggered.connect(lambda: self.raiseDock(self.notepads_dock)) self.notificationArea_dockView_action.triggered.connect(lambda: self.raiseDock(self.notification_dock)) self.fgcom_dockView_action.triggered.connect(lambda: self.raiseDock(self.radio_dock)) self.runwayBoxes_dockView_action.triggered.connect(lambda: self.raiseDock(self.rwyBoxes_dock)) self.radarContactDetails_dockView_action.triggered.connect(lambda: self.raiseDock(self.selection_info_dock)) self.strips_dockView_action.triggered.connect(lambda: self.raiseDock(self.strip_dock)) self.radioTextChat_dockView_action.triggered.connect(lambda: self.raiseDock(self.radioTextChat_dock)) self.towerView_dockView_action.triggered.connect(lambda: self.raiseDock(self.towerView_dock)) self.weather_dockView_action.triggered.connect(lambda: self.raiseDock(self.weather_dock)) self.windowedWorkspace_view_action.toggled.connect(self.central_workspace.switchWindowedView) self.popOutCurrentWindow_view_action.triggered.connect(self.central_workspace.popOutCurrentWindow) self.reclaimPoppedOutWindows_view_action.triggered.connect(self.central_workspace.reclaimPoppedOutWidgets) self.newLooseStripBay_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.LOOSE_BAY)) self.newRadarScreen_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.RADAR_SCREEN)) self.newStripRackPanel_view_action.triggered.connect(lambda: self.central_workspace.addWorkspaceWidget(WorkspaceWidget.STRIP_PANEL)) self.verticalRwyBoxLayout_view_action.toggled.connect(self.switchVerticalRwyBoxLayout) self.towerView_view_action.triggered.connect(self.toggleTowerWindow) self.addViewer_view_action.triggered.connect(self.addView) self.listViewers_view_action.triggered.connect(self.listAdditionalViews) self.activateAdditionalViewers_view_action.toggled.connect(self.activateAdditionalViews) self.removeViewer_view_action.triggered.connect(self.removeView) self.teachingConsole_view_action.triggered.connect(self.teaching_console.show) self.unitConversionTool_view_action.triggered.connect(self.unit_converter.show) self.worldAirportNavigator_view_action.triggered.connect(self.world_airport_navigator.show) self.environmentInfo_view_action.triggered.connect(self.environment_info_dialog.exec) # options menu self.runwaysInUse_options_action.triggered.connect(self.configureRunwayUse) self.notificationSounds_options_action.toggled.connect(self.switchNotificationSounds) self.primaryRadar_options_action.toggled.connect(self.switchPrimaryRadar) self.routeConflictWarnings_options_action.toggled.connect(self.switchConflictWarnings) self.trafficIdentification_options_action.toggled.connect(self.switchTrafficIdentification) self.runwayOccupationWarnings_options_action.toggled.connect(self.switchRwyOccupationIndications) self.approachSpacingHints_options_action.toggled.connect(self.switchApproachSpacingHints) self.generalSettings_options_action.triggered.connect(self.openGeneralSettings) # cheat menu self.pauseSimulation_cheat_action.toggled.connect(self.pauseSession) self.spawnAircraft_cheat_action.triggered.connect(self.spawnAircraft) self.killSelectedAircraft_cheat_action.triggered.connect(self.killSelectedAircraft) self.popUpMsgOnRejectedInstr_cheat_action.toggled.connect(self.setRejectedInstrPopUp) self.showRecognisedVoiceStrings_cheat_action.toggled.connect(self.setShowRecognisedVoiceStrings) self.ensureClearWeather_cheat_action.toggled.connect(self.ensureClearWeather) self.ensureDayLight_cheat_action.triggered.connect(self.towerView_pane.ensureDayLight) self.changeTowerHeight_cheat_action.triggered.connect(self.changeTowerHeight) self.recallDiscardedStrip_cheat_action.triggered.connect(self.recall_cheat_dialog.exec) self.radarCheatMode_cheat_action.toggled.connect(self.setRadarCheatMode) # help menu self.quickReference_help_action.triggered.connect(self.quick_reference.show) self.videoTutorial_help_action.triggered.connect(lambda: self.goToURL(video_tutorial_URL)) self.FAQ_help_action.triggered.connect(lambda: self.goToURL(FAQ_URL)) self.about_help_action.triggered.connect(self.about_dialog.exec) ## More signal connections signals.openShelfRequest.connect(self.shelf_dialog.exec) signals.privateAtcChatRequest.connect(lambda: self.raiseDock(self.atcTextChat_dock)) signals.stripRecall.connect(recover_strip) env.radar.blip.connect(env.strips.refreshViews) env.radar.lostContact.connect(self.aircraftHasDisappeared) signals.aircraftKilled.connect(self.aircraftHasDisappeared) env.strips.rwyBoxFreed.connect(lambda box, strip: env.airport_data.physicalRunway_restartWtcTimer(box, strip.lookup(FPL.WTC))) env.rdf.signalChanged.connect(self.updateRDF) signals.statusBarMsg.connect(lambda msg: self.statusbar.showMessage(msg, status_bar_message_timeout)) signals.newWeather.connect(self.updateWeatherIfPrimary) signals.kbdPTT.connect(self.updatePTT) signals.sessionStarted.connect(self.sessionHasStarted) signals.sessionEnded.connect(self.sessionHasEnded) signals.towerViewProcessToggled.connect(self.towerView_view_action.setChecked) signals.towerViewProcessToggled.connect(self.towerView_cheat_menu.setEnabled) signals.stripInfoChanged.connect(env.strips.refreshViews) signals.fastClockTick.connect(self.updateClock) signals.fastClockTick.connect(env.cpdlc.updateAckStatuses) signals.slowClockTick.connect(strip_auto_print_check) signals.stripEditRequest.connect(lambda strip: edit_strip(self, strip)) signals.selectionChanged.connect(self.updateStripFplActions) signals.receiveStrip.connect(receive_strip) signals.handoverFailure.connect(self.recoverFailedHandover) signals.sessionPaused.connect(env.radar.stopSweeping) signals.sessionResumed.connect(env.radar.startSweeping) signals.aircraftKilled.connect(env.radar.silentlyForgetContact) signals.rackVisibilityLost.connect(self.collectClosedRacks) signals.localSettingsChanged.connect(env.rdf.clearAllSignals) signals.localSettingsChanged.connect(self.updateRDF) ## MISC GUI setup self.strip_pane.setViewRacks([default_rack_name]) # will be moved out if a rack panel's saved "visible_racks" claims it [*1] self.strip_pane.restoreState(settings.saved_strip_dock_state) # [*1] self.central_workspace.restoreWorkspaceWindows(settings.saved_workspace_windows) self.central_workspace.switchWindowedView(settings.saved_workspace_windowed_view) # keep this after restoring windows! self.subsecond_ticker = Ticker(signals.fastClockTick.emit, parent=self) self.subminute_ticker = Ticker(signals.slowClockTick.emit, parent=self) self.subsecond_ticker.start_stopOnZero(subsecond_tick_interval) self.subminute_ticker.start_stopOnZero(subminute_tick_interval) self.towerView_cheat_menu.setEnabled(False) self.solo_cheat_menu.setEnabled(False) self.updateClock() self.updateWeatherIfPrimary(settings.primary_METAR_station, None) self.updateStripFplActions() self.last_RDF_qdm = None self.updateRDF() self.updatePTT(0, False) # Disable some base airport stuff if doing CTR if env.airport_data == None: self.towerView_view_action.setEnabled(False) self.runwaysInUse_options_action.setEnabled(False) self.runwayOccupationWarnings_options_action.setEnabled(False) # Finish self.atcTextChat_pane.switchAtcChatFilter(None) # Show GUI on general chat room at start if speech_recognition_available: prepare_SR_language_files() def raiseDock(self, dock): dock.show() dock.raise_() dock.widget().setFocus() dock.setStyleSheet(dock_flash_stylesheet) QTimer.singleShot(dock_flash_time, lambda: dock.setStyleSheet(None)) def startTimer(self, i, force_start): if force_start or not self.alarmClock_statusBarButtons[i].timerIsRunning(): self.alarmClock_statusBarButtons[i].setTimer() def goToURL(self, url): if not QDesktopServices.openUrl(QUrl(url)): QMessageBox.critical(self, 'Error opening web browser', \ 'Could not invoke desktop web browser.\nGo to: %s' % url) def recoverFailedHandover(self, strip, msg): recover_strip(strip) QMessageBox.critical(self, 'Handover failed', '%s\nStrip has been recovered.' % msg) # --------------------- GUI auto-update functions ---------------------- # def updateClock(self): self.clock_statusBarLabel.setText('UTC ' + timestr(seconds=True)) def unlockSounds(self): settings.session_start_sound_lock = False def updateWeatherIfPrimary(self, station, weather): # NOTE weather may be None here if station == settings.primary_METAR_station: # Update status bar info self.METAR_statusBarLabel.setText(None if weather == None else weather.METAR()) self.wind_statusBarLabel.setText('Wind ' + ('---' if weather == None else weather.readWind())) qnh = None if weather == None else weather.QNH() # NOTE qnh may still be None self.QNH_statusBarLabel.setText('QNH ' + ('%d / %.2f' % (qnh, hPa2inHg * qnh) if qnh != None else '---')) # Update tower view if weather != None and not settings.TWR_view_clear_weather_cheat: settings.controlled_tower_viewer.setWeather(weather) def updatePTT(self, button, pressed): if settings.session_manager.isRunning(): settings.transmitting_radio = pressed if settings.session_manager.session_type == SessionType.SOLO: if settings.solo_voice_instructions: self.PTT_statusBarLabel.setText('PTT' if pressed else 'VOICE') else: self.PTT_statusBarLabel.setText('MOUSE') elif pressed: self.PTT_statusBarLabel.setText('PTT') else: self.PTT_statusBarLabel.setText(' - - - ') else: settings.transmitting_radio = False self.PTT_statusBarLabel.setText('Off') def updateRDF(self): self.RDF_statusBarLabel.setVisible(settings.radio_direction_finding) if settings.radio_direction_finding: hdg = env.rdf.currentSignalRadial() if hdg == None: s1 = ' - - - ' else: self.last_RDF_qdm = hdg.opposite() s1 = hdg.read() s2 = ' - - - ' if self.last_RDF_qdm == None else self.last_RDF_qdm.read() self.RDF_statusBarLabel.setText('RDF %s / %s' % (s1, s2)) def updateSessionStartStopActions(self): running = settings.session_manager.isRunning() for gt, ma in { SessionType.SOLO: self.soloSession_system_action, SessionType.FLIGHTGEAR_MP: self.connectFlightGearMP_system_action, SessionType.STUDENT: self.studentSession_system_action, SessionType.TEACHER: self.teacherSession_system_action }.items(): if gt == settings.session_manager.session_type: ma.setEnabled(True) ma.setChecked(running) else: ma.setEnabled(not running) ma.setChecked(False) def updateStripFplActions(self): self.newLinkedStrip_action.setEnabled(selection.strip == None and not selection.acft == selection.fpl == None) self.newLinkedFPL_action.setEnabled(selection.strip != None and selection.strip.linkedFPL() == None) def sessionHasStarted(self): self.session_start_sound_lock_timer.start(session_start_sound_lock_duration) self.updateSessionStartStopActions() self.solo_cheat_menu.setEnabled(settings.session_manager.session_type == SessionType.SOLO) self.updatePTT(0, False) env.radar.startSweeping() def sessionHasEnded(self): env.radar.stopSweeping() env.radar.resetContacts() env.strips.removeAllStrips() env.FPLs.clearFPLs() env.rdf.clearAllSignals() env.cpdlc.clearHistory() self.updateSessionStartStopActions() self.pauseSimulation_cheat_action.setChecked(False) self.solo_cheat_menu.setEnabled(False) self.updatePTT(0, False) self.last_RDF_qdm = None self.updateRDF() print('Session ended.') def aircraftHasDisappeared(self, acft): strip = env.linkedStrip(acft) if strip != None: strip.linkAircraft(None) if selection.acft is acft: if strip == None: # was not linked selection.deselect() else: selection.selectStrip(strip) def collectClosedRacks(self, racks): self.strip_pane.setViewRacks(self.strip_pane.getViewRacks() + racks) # --------------------- Session start functions ---------------------- # def start_solo(self): if env.airport_data == None: # start CTR solo simulation n, ok = QInputDialog.getInt(self, 'Solo CTR session', 'Initial traffic count:', value=2, min=0, max=99) if ok: settings.session_manager = SoloSessionManager_CTR(self) settings.session_manager.start(n) else: # start AD solo simulation self.solo_connect_dialog_AD.exec() if self.solo_connect_dialog_AD.result() > 0: # not rejected settings.session_manager = SoloSessionManager_AD(self) settings.session_manager.start(self.solo_connect_dialog_AD.chosenInitialTrafficCount()) def start_FlightGearMP(self): self.MP_connect_dialog.exec() if self.MP_connect_dialog.result() > 0: # not rejected settings.session_manager = FlightGearMultiPlayerSessionManager(self, self.MP_connect_dialog.chosenCallsign()) settings.session_manager.start() def start_teaching(self): port, ok = QInputDialog.getInt(self, 'Start a teaching session', 'Service port:', value=settings.teaching_service_port, max=99999) if ok: settings.teaching_service_port = port settings.session_manager = TeacherSessionManager(self) settings.session_manager.start() def start_learning(self): self.start_student_session_dialog.exec() if self.start_student_session_dialog.result() > 0: # not rejected settings.session_manager = StudentSessionManager(self) settings.session_manager.start() # --------------------- GUI menu actions ---------------------- # ## SYSTEM MENU ## def startStopSession(self, start_func): if settings.session_manager.isRunning(): # Stop session selection.deselect() env.cpdlc.endAllDataLinks() settings.session_manager.stop() else: # Start session settings.session_start_sound_lock = True start_func() self.updateSessionStartStopActions() if not settings.session_manager.isRunning(): settings.session_start_sound_lock = False def reloadAdditionalViewers(self): print('Reload: additional viewers') settings.loadAdditionalViews() QMessageBox.information(self, 'Done reloading', 'Additional viewers reloaded. Check for console error messages.') def reloadBackgroundImages(self): print('Reload: background images') settings.radar_background_images, settings.loose_strip_bay_backgrounds = read_bg_img(settings.location_code, env.navpoints) signals.backgroundImagesReloaded.emit() QMessageBox.information(self, 'Done reloading', 'Background images reloaded. Check for console error messages.') def reloadColourConfig(self): print('Reload: colour configuration') settings.loadColourSettings() signals.colourConfigReloaded.emit() QMessageBox.information(self, 'Done reloading', 'Colour configuration reloaded. Check for console error messages.') def reloadRoutePresets(self): print('Reload: route presets') settings.route_presets = read_route_presets() QMessageBox.information(self, 'Done reloading', 'Route presets reloaded. Check for console error messages.') def reloadEntryExitPoints(self): print('Reload: entry/exit points') world_routing_db.clearEntryExitPoints() import_entry_exit_data() QMessageBox.information(self, 'Done reloading', 'Entry/exit points reloaded. Check for console error messages.') def announceFgSession(self): if settings.lenny64_account_email == '': QMessageBox.critical(self, 'Lenny64 account details missing', \ 'This feature requires a Lenny64 dashboard. Please provide one in the FlightGear system set-up tab.') else: PostLennySessionDialog(self).exec() def switchMeasuringCoordsLog(self, toggle): settings.measuring_tool_logs_coordinates = toggle def extractSectorFile(self): txt, ignore = QFileDialog.getOpenFileName(self, caption='Select sector file to extract from') if txt != '': extract_sector(txt, env.radarPos(), settings.map_range) QMessageBox.information(self, 'Done extracting', \ 'Background drawings extracted.\nSee console for summary and files created in the output directory.') def repositionRadarBgImages(self): radar_panel = self.central_workspace.getCurrentRadarPanel() if radar_panel == None: QMessageBox.critical(self, 'Image positioning error', 'This requires an active radar panel in the central workspace.') else: radar_panel.positionVisibleBgImages() def openSoloSessionSettings(self): SoloSessionSettingsDialog(self).exec() self.updatePTT(0, False) # display "MOUSE/VOICE" as appropriate def openLocalSettings(self): dialog = LocalSettingsDialog(self) dialog.exec() if dialog.result() > 0 and settings.session_manager.isRunning(): env.radar.startSweeping() def openSystemSettings(self): SystemSettingsDialog(self).exec() def changeLocation(self): if yesNo_question(self, 'Change location', 'This will close the current session.', 'Are you sure?'): self.launcher.show() self.close() ## VIEW MENU ## def saveDockLayout(self): # STYLE catch file write error with open(dock_layout_file, 'wb') as f: f.write(self.saveState()) QMessageBox.information(self, 'Save dock layout', 'Current dock layout saved.') def recallWindowState(self): try: with open(dock_layout_file, 'rb') as f: self.restoreState(f.read()) except FileNotFoundError: QMessageBox.critical(self, 'Recall dock layout', 'No saved layout to recall.') def switchVerticalRwyBoxLayout(self, toggle): settings.vertical_runway_box_layout = toggle self.rwyBox_pane.setVerticalLayout(settings.vertical_runway_box_layout) def toggleTowerWindow(self): if self.towerView_view_action.isChecked(): if env.airport_data != None and len(env.airport_data.viewpoints) == 0: QMessageBox.warning(self, 'No viewpoint', \ 'Airport data does not specify a viewpoint. ATC-pie is positioning one near a runway.\n'\ 'Update your source file if one should be available.') settings.controlled_tower_viewer.start() else: settings.controlled_tower_viewer.stop() def selectIndicateViewpoint(self, vp_index): if vp_index != settings.selected_viewpoint: settings.selected_viewpoint = vp_index settings.tower_height_cheat_offset = 0 if settings.controlled_tower_viewer.running: self.towerView_pane.updateTowerPosition() signals.indicatePoint.emit(env.viewpoint()[0]) def activateAdditionalViews(self, toggle): settings.additional_views_active = toggle def listAdditionalViews(self): if settings.additional_views == []: txt = 'No additional viewers registered.' else: lst = [' - %s on port %d' % host_port for host_port in settings.additional_views] txt = 'Additional viewers:\n%s' % '\n'.join(lst) QMessageBox.information(self, 'Additional viewers', txt) def addView(self): text, ok = QInputDialog.getText(self, 'Add an external viewer', 'Enter "host:port" to send traffic to:') if ok: split = text.rsplit(':', maxsplit=1) if len(split) == 2 and split[1].isdecimal(): viewer_address = split[0], int(split[1]) settings.additional_views.append(viewer_address) QMessageBox.information(self, 'Add an external viewer', 'Viewer added: %s on port %d.' % viewer_address) else: QMessageBox.critical(self, 'Add an external viewer', 'Bad "host:port" format.') def removeView(self): if settings.additional_views == []: QMessageBox.critical(self, 'Remove viewer', 'No additional viewers registered.') else: items = ['%d: %s on port %d' % (i, settings.additional_views[i][0], settings.additional_views[i][1]) \ for i in range(len(settings.additional_views))] item, ok = QInputDialog.getItem(self, 'Remove viewer', 'Select viewer to remove:', items, editable=False) if ok: del settings.additional_views[int(item.split(':', maxsplit=1)[0])] ## OPTIONS MENU ## def configureRunwayUse(self): RunwayUseDialog(self).exec() def switchNotificationSounds(self, toggle): settings.notification_sounds_enabled = toggle def switchPrimaryRadar(self, toggle): settings.primary_radar_active = toggle env.radar.scan() def switchConflictWarnings(self, toggle): settings.route_conflict_warnings = toggle def switchTrafficIdentification(self, toggle): settings.traffic_identification_assistant = toggle if not toggle: for strip in env.strips.listStrips(): strip.writeDetail(soft_link_detail, None) signals.stripInfoChanged.emit() def switchRwyOccupationIndications(self, toggle): settings.monitor_runway_occupation = toggle def switchApproachSpacingHints(self, toggle): settings.APP_spacing_hints = toggle signals.stripInfoChanged.emit() def openGeneralSettings(self): GeneralSettingsDialog(self).exec() ## CHEAT MENU ## def pauseSession(self, toggle): if toggle: settings.session_manager.pauseSession() else: settings.session_manager.resumeSession() def spawnAircraft(self): n, ok = QInputDialog.getInt(self, 'Spawn new aircraft', 'Try to spawn:', value=1, min=1, max=99) if ok: for i in range(n): # WARNING: session should be running settings.session_manager.spawnNewControlledAircraft() def killSelectedAircraft(self): selected = selection.acft if selected == None: QMessageBox.critical(self, 'Cheat error', 'No aircraft selected.') else: selection.deselect() settings.session_manager.killAircraft(selected) # WARNING: killAircraft method must exist env.radar.scan() def setRejectedInstrPopUp(self, toggle): settings.solo_erroneous_instruction_warning = toggle def ensureClearWeather(self, toggle): settings.TWR_view_clear_weather_cheat = toggle if toggle: weather = mkWeather(settings.location_code) # clear weather with location code as station settings.controlled_tower_viewer.setWeather(weather) else: weather = env.primaryWeather() if weather != None: settings.controlled_tower_viewer.setWeather(weather) def setShowRecognisedVoiceStrings(self, toggle): settings.show_recognised_voice_strings = toggle def changeTowerHeight(self): hoff, ok = QInputDialog.getInt(self, 'Cheat tower height', \ 'Offset in feet:', value=settings.tower_height_cheat_offset, min=0, max=1500, step=10) if ok: settings.tower_height_cheat_offset = hoff self.towerView_pane.updateTowerPosition() def setRadarCheatMode(self, toggle): settings.radar_cheat = toggle env.radar.scan() # ----------------- Internal GUI events ------------------ # def closeEvent(self, event): if settings.session_manager.isRunning(): settings.session_manager.stop() if settings.controlled_tower_viewer.running: settings.controlled_tower_viewer.stop(wait=True) if speech_recognition_available: cleanup_SR_language_files() print('Closing main window.') settings.saved_strip_racks = env.strips.rackNames() settings.saved_strip_dock_state = self.strip_pane.stateSave() settings.saved_workspace_windowed_view = self.central_workspace.windowedView() settings.saved_workspace_windows = self.central_workspace.workspaceWindowsStateSave() signals.mainWindowClosing.emit() signals.disconnect() settings.saveGeneralAndSystemSettings() settings.saveLocalSettings(env.airport_data) settings.savePresetChatMessages() env.resetEnv() settings.resetSession() EarthCoords.clearRadarPos() QMainWindow.closeEvent(self, event)
class RadioBox(QWidget, Ui_radioBox): def __init__(self, parent, external, port): ''' external is a host (possibly localhost) for external FGCom instance, or None for internal (child process) ''' QWidget.__init__(self, parent) self.setupUi(self) client_address = some(external, 'localhost'), port self.settings = FgcomSettings(socket(AF_INET, SOCK_DGRAM), client_address) self.controller = Ticker(self.settings.send, parent=self) self.frequency_combo.addFrequencies([(frq, descr) for frq, descr, t in env.frequencies]) self.frequency_combo.addFrequencies(frequencies_always_proposed) if external == None: # child process self.onOff_button.setToolTip('Internal FGCom instance using local port %d' % port) ad = world_navpoint_db.findClosest(env.radarPos(), types=[Navpoint.AD]).code if env.airport_data == None else settings.location_code self.instance = InternalFgcomInstance(port, ['--airport=%s' % ad], self) self.instance.started.connect(self.processHasStarted) self.instance.finished.connect(self.processHasStopped) self.onOff_button.toggled.connect(self.switchFGCom) else: # creating box for external instance self.instance = None self.onOff_button.setToolTip('External FGCom instance on %s:%d' % client_address) self.onOff_button.setChecked(True) # keep checked (tested for RDF) self.onOff_button.setEnabled(False) self.PTT_button.setEnabled(True) self.controller.start(fgcom_controller_ticker_interval) self.PTT_button.pressed.connect(lambda: self.PTT(True)) self.PTT_button.released.connect(lambda: self.PTT(False)) self.softVolume_tickBox.clicked.connect(self.setVolume) self.frequency_combo.frequencyChanged.connect(self.setFrequency) self.updateRDF() self.RDF_tickBox.toggled.connect(self.updateRDF) self.onOff_button.toggled.connect(self.updateRDF) signals.localSettingsChanged.connect(self.updateRDF) def isInternal(self): return self.instance != None def clientAddress(self): return self.settings.address def getReadyForRemoval(self): if self.isInternal(): self.switchFGCom(False) self.instance.waitForFinished(1000) else: self.controller.stop() try: del settings.MP_RDF_frequencies[self.settings.address[1]] except KeyError: pass def setVolume(self): if settings.FGCom_radios_muted: self.settings.vol = 0 elif self.softVolume_tickBox.isChecked(): self.settings.vol = soft_sound_level else: self.settings.vol = loud_sound_level self.settings.send() def setFrequency(self, frq): self.PTT(False) self.settings.frq = frq self.updateRDF() self.settings.send() def PTT(self, toggle): self.settings.ptt = toggle self.PTT_button.setChecked(toggle and self.PTT_button.isEnabled()) # NOTE: line below is unreliable on mouse+kbd mix, but not a serious problem. settings.transmitting_radio = toggle # accounts for direct mouse PTT press self.settings.send() def updateRDF(self): frq_select_available = settings.radio_direction_finding and settings.session_manager.session_type == SessionType.FLIGHTGEAR_MP self.RDF_tickBox.setEnabled(frq_select_available) box_key = self.settings.address[1] if frq_select_available and self.RDF_tickBox.isChecked(): settings.MP_RDF_frequencies[box_key] = self.settings.frq else: try: del settings.MP_RDF_frequencies[box_key] except KeyError: pass ## Controlling FGCom process def switchFGCom(self, on_off): if self.isInternal(): self.removeBox_button.setEnabled(not on_off) if on_off: # Start FGCom self.instance.start() else: # Stop FGCom self.PTT(False) self.instance.kill() def processHasStarted(self): self.PTT_button.setEnabled(True) self.controller.start(fgcom_controller_ticker_interval) def processHasStopped(self, exit_code, status): self.controller.stop() self.PTT_button.setEnabled(False) self.PTT_button.setChecked(False) self.onOff_button.setChecked(False)