def setupThreads(self): # Set up threads and their connections self.avatarFindThread = AvatarFindThread() self.connect(self.avatarFindThread, SIGNAL("avatar_update"), self.updateAvatarOnChatEntry) self.avatarFindThread.start() self.kosRequestThread = KOSCheckerThread() self.connect(self.kosRequestThread, SIGNAL("kos_result"), self.showKosResult) self.kosRequestThread.start() self.filewatcherThread = filewatcher.FileWatcher(self.pathToLogs) self.connect(self.filewatcherThread, SIGNAL("file_change"), self.logFileChanged) self.filewatcherThread.start() self.versionCheckThread = amazon_s3.NotifyNewVersionThread() self.versionCheckThread.connect(self.versionCheckThread, SIGNAL("newer_version"), self.notifyNewerVersion) self.versionCheckThread.start() self.statisticsThread = MapStatisticsThread() self.connect(self.statisticsThread, SIGNAL("statistic_data_update"), self.updateStatisticsOnMap) self.statisticsThread.start() # statisticsThread is blocked until first call of requestStatistics self.voiceThread = VoiceOverThread() self.voiceThread.start()
def setupThreads(self): # Set up threads and their connections self.avatarFindThread = AvatarFindThread() self.avatarFindThread.avatar_update.connect( self.updateAvatarOnChatEntry) self.avatarFindThread.start() self.kosRequestThread = KOSCheckerThread() self.kosRequestThread.kos_result.connect(self.showKosResult) self.kosRequestThread.start() self.filewatcherThread = filewatcher.FileWatcher(self.pathToLogs) self.filewatcherThread.data_changed.connect(self.logFileChanged) self.filewatcherThread.start() self.fileWatcherForGameLogsThread = filewatcher.FileWatcher( self.pathToGameLogs) self.fileWatcherForGameLogsThread.data_changed.connect( self.logFileChanged) self.fileWatcherForGameLogsThread.start() self.versionCheckThread = amazon_s3.NotifyNewVersionThread() self.versionCheckThread.newer_version.connect(self.notifyNewerVersion) self.versionCheckThread.start() self.statisticsThread = MapStatisticsThread() self.statisticsThread.statisticDataUpdate.connect( self.updateStatisticsOnMap) self.statisticsThread.start()
def setupThreads(self): # Set up threads and their connections self.avatarFindThread = AvatarFindThread() self.connect(self.avatarFindThread, QtCore.SIGNAL("avatar_update"), self.updateAvatarOnChatEntry) self.avatarFindThread.start() self.kosRequestThread = KOSCheckerThread() self.connect(self.kosRequestThread, Qt.SIGNAL("kos_result"), self.showKosResult) self.kosRequestThread.start() self.filewatcherThread = filewatcher.FileWatcher(self.pathToLogs) self.connect(self.filewatcherThread, QtCore.SIGNAL("file_change"), self.logFileChanged) self.filewatcherThread.start() self.versionCheckThread = amazon_s3.NotifyNewVersionThread() self.versionCheckThread.connect(self.versionCheckThread, Qt.SIGNAL("newer_version"), self.notifyNewerVersion) self.versionCheckThread.start() self.statisticsThread = MapStatisticsThread() self.connect(self.statisticsThread, Qt.SIGNAL("statistic_data_update"), self.updateStatisticsOnMap) self.statisticsThread.start()
def __init__(self, pathToLogs, trayIcon): QtGui.QMainWindow.__init__(self) uic.loadUi(resourcePath('vi/ui/MainWindow.ui'), self) self.setWindowTitle("Vintel " + VERSION) self.taskbarIconQuiescent = QtGui.QIcon(resourcePath("vi/ui/res/logo_small.png")) self.taskbarIconWorking = QtGui.QIcon(resourcePath("vi/ui/res/logo_small_green.png")) self.setWindowIcon(self.taskbarIconQuiescent) self.pathToLogs = pathToLogs self.trayIcon = trayIcon self.cache = Cache() # Load my toon names self.knownPlayerNames = self.cache.getFromCache("known_player_names") if self.knownPlayerNames: self.knownPlayerNames = set(self.knownPlayerNames.split(",")) else: self.knownPlayerNames = set() # Set up my intel rooms roomnames = self.cache.getFromCache("room_names") if roomnames: roomnames = roomnames.split(",") else: roomnames = (u"TheCitadel", u"North Provi Intel", u"North Catch Intel") self.cache.putIntoCache("room_names", u",".join(roomnames), 60 * 60 * 24 * 365 * 5) self.roomnames = roomnames # Disable the sound UI if sound is not available if not SoundManager().soundAvailable: self.changeSound(disable=True) else: self.changeSound() # Initialize state self.initMapPosition = None self.oldClipboardContent = "" self.alarmDistance = 0 self.lastStatisticsUpdate = 0 self.alreadyShowedSoundWarning = False self.chatEntries = [] self.frameButton.setVisible(False) self.trayIcon.activated.connect(self.systemTrayActivated) self.clipboard = QtGui.QApplication.clipboard() self.clipboard.clear(mode=self.clipboard.Clipboard) # Fill in opacity values and connections self.opacityGroup = QtGui.QActionGroup(self.menu) for i in (100, 80, 60, 40, 20): action = QtGui.QAction("Opacity {0}%".format(i), None, checkable=True) if i == 100: action.setChecked(True) action.opacity = i / 100.0 self.connect(action, QtCore.SIGNAL("triggered()"), self.changeOpacity) self.opacityGroup.addAction(action) self.menuTransparency.addAction(action) # Wire up UI connections self.connect(self.clipboard, Qt.SIGNAL("changed(QClipboard::Mode)"), self.clipboardChanged) self.connect(self.zoomInButton, Qt.SIGNAL("clicked()"), self.zoomMapIn) self.connect(self.zoomOutButton, Qt.SIGNAL("clicked()"), self.zoomMapOut) self.connect(self.statisticsButton, Qt.SIGNAL("clicked()"), self.changeStatisticsVisibility) self.connect(self.jumpbridgesButton, Qt.SIGNAL("clicked()"), self.changeJumpbridgesVisibility) self.connect(self.chatLargeButton, Qt.SIGNAL("clicked()"), self.chatLarger) self.connect(self.chatSmallButton, Qt.SIGNAL("clicked()"), self.chatSmaller) self.connect(self.infoAction, Qt.SIGNAL("triggered()"), self.showInfo) self.connect(self.showChatAvatarsAction, Qt.SIGNAL("triggered()"), self.changeShowAvatars) self.connect(self.alwaysOnTopAction, Qt.SIGNAL("triggered()"), self.changeAlwaysOnTop) self.connect(self.chooseChatRoomsAction, Qt.SIGNAL("triggered()"), self.showChatroomChooser) self.connect(self.catchRegionAction, Qt.SIGNAL("triggered()"), lambda item=self.catchRegionAction: self.handleRegionMenuItemSelected(item)) self.connect(self.providenceRegionAction, Qt.SIGNAL("triggered()"), lambda item=self.providenceRegionAction: self.handleRegionMenuItemSelected(item)) self.connect(self.providenceCatchRegionAction, Qt.SIGNAL("triggered()"), lambda item=self.providenceCatchRegionAction: self.handleRegionMenuItemSelected(item)) self.connect(self.chooseRegionAction, Qt.SIGNAL("triggered()"), self.showRegionChooser) self.connect(self.showChatAction, Qt.SIGNAL("triggered()"), self.changeChatVisibility) self.connect(self.soundSetupAction, Qt.SIGNAL("triggered()"), self.showSoundSetup) self.connect(self.activateSoundAction, Qt.SIGNAL("triggered()"), self.changeSound) self.connect(self.useSpokenNotificationsAction, Qt.SIGNAL("triggered()"), self.changeUseSpokenNotifications) self.connect(self.floatingOverviewAction, Qt.SIGNAL("triggered()"), self.showFloatingOverview) self.connect(self.trayIcon, Qt.SIGNAL("alarm_distance"), self.changeAlarmDistance) self.connect(self.framelessWindowAction, Qt.SIGNAL("triggered()"), self.changeFrameless) self.connect(self.trayIcon, Qt.SIGNAL("change_frameless"), self.changeFrameless) self.connect(self.frameButton, Qt.SIGNAL("clicked()"), self.changeFrameless) self.connect(self.quitAction, Qt.SIGNAL("triggered()"), self.close) self.connect(self.trayIcon, Qt.SIGNAL("quit"), self.close) self.connect(self.jumpbridgeDataAction, Qt.SIGNAL("triggered()"), self.showJumbridgeChooser) # Create a timer to refresh the map, then load up the map, either from cache or dotlan self.mapTimer = QtCore.QTimer(self) self.connect(self.mapTimer, QtCore.SIGNAL("timeout()"), self.updateMapView) self.setupMap(True) # Recall cached user settings try: self.cache.recallAndApplySettings(self, "settings") except Exception as e: print str(e) self.trayIcon.showMessage("Settings error", "Something went wrong loading saved state:\n {0}".format(str(e)), 1) # Set up threads and their connections self.avatarFindThread = AvatarFindThread() self.connect(self.avatarFindThread, QtCore.SIGNAL("avatar_update"), self.updateAvatarOnChatEntry) self.avatarFindThread.start() self.kosRequestThread = KOSCheckerThread() self.connect(self.kosRequestThread, Qt.SIGNAL("kos_result"), self.showKosResult) self.kosRequestThread.start() self.filewatcherThread = filewatcher.FileWatcher(self.pathToLogs, 60 * 60 * 24) self.connect(self.filewatcherThread, QtCore.SIGNAL("file_change"), self.logFileChanged) self.filewatcherThread.start() self.versionCheckThread = amazon_s3.NotifyNewVersionThread() self.versionCheckThread.connect(self.versionCheckThread, Qt.SIGNAL("newer_version"), self.notifyNewerVersion) self.versionCheckThread.start() if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"): font = self.statisticsButton.font() font.setPointSize(8) self.statisticsButton.setFont(font) self.jumpbridgesButton.setFont(font)
class MainWindow(QtGui.QMainWindow): def __init__(self, pathToLogs, trayIcon): QtGui.QMainWindow.__init__(self) uic.loadUi(resourcePath('vi/ui/MainWindow.ui'), self) self.setWindowTitle("Vintel " + VERSION) self.taskbarIconQuiescent = QtGui.QIcon(resourcePath("vi/ui/res/logo_small.png")) self.taskbarIconWorking = QtGui.QIcon(resourcePath("vi/ui/res/logo_small_green.png")) self.setWindowIcon(self.taskbarIconQuiescent) self.pathToLogs = pathToLogs self.trayIcon = trayIcon self.cache = Cache() # Load my toon names self.knownPlayerNames = self.cache.getFromCache("known_player_names") if self.knownPlayerNames: self.knownPlayerNames = set(self.knownPlayerNames.split(",")) else: self.knownPlayerNames = set() # Set up my intel rooms roomnames = self.cache.getFromCache("room_names") if roomnames: roomnames = roomnames.split(",") else: roomnames = (u"TheCitadel", u"North Provi Intel", u"North Catch Intel") self.cache.putIntoCache("room_names", u",".join(roomnames), 60 * 60 * 24 * 365 * 5) self.roomnames = roomnames # Disable the sound UI if sound is not available if not SoundManager().soundAvailable: self.changeSound(disable=True) else: self.changeSound() # Initialize state self.initMapPosition = None self.oldClipboardContent = "" self.alarmDistance = 0 self.lastStatisticsUpdate = 0 self.alreadyShowedSoundWarning = False self.chatEntries = [] self.frameButton.setVisible(False) self.trayIcon.activated.connect(self.systemTrayActivated) self.clipboard = QtGui.QApplication.clipboard() self.clipboard.clear(mode=self.clipboard.Clipboard) # Fill in opacity values and connections self.opacityGroup = QtGui.QActionGroup(self.menu) for i in (100, 80, 60, 40, 20): action = QtGui.QAction("Opacity {0}%".format(i), None, checkable=True) if i == 100: action.setChecked(True) action.opacity = i / 100.0 self.connect(action, QtCore.SIGNAL("triggered()"), self.changeOpacity) self.opacityGroup.addAction(action) self.menuTransparency.addAction(action) # Wire up UI connections self.connect(self.clipboard, Qt.SIGNAL("changed(QClipboard::Mode)"), self.clipboardChanged) self.connect(self.zoomInButton, Qt.SIGNAL("clicked()"), self.zoomMapIn) self.connect(self.zoomOutButton, Qt.SIGNAL("clicked()"), self.zoomMapOut) self.connect(self.statisticsButton, Qt.SIGNAL("clicked()"), self.changeStatisticsVisibility) self.connect(self.jumpbridgesButton, Qt.SIGNAL("clicked()"), self.changeJumpbridgesVisibility) self.connect(self.chatLargeButton, Qt.SIGNAL("clicked()"), self.chatLarger) self.connect(self.chatSmallButton, Qt.SIGNAL("clicked()"), self.chatSmaller) self.connect(self.infoAction, Qt.SIGNAL("triggered()"), self.showInfo) self.connect(self.showChatAvatarsAction, Qt.SIGNAL("triggered()"), self.changeShowAvatars) self.connect(self.alwaysOnTopAction, Qt.SIGNAL("triggered()"), self.changeAlwaysOnTop) self.connect(self.chooseChatRoomsAction, Qt.SIGNAL("triggered()"), self.showChatroomChooser) self.connect(self.catchRegionAction, Qt.SIGNAL("triggered()"), lambda item=self.catchRegionAction: self.handleRegionMenuItemSelected(item)) self.connect(self.providenceRegionAction, Qt.SIGNAL("triggered()"), lambda item=self.providenceRegionAction: self.handleRegionMenuItemSelected(item)) self.connect(self.providenceCatchRegionAction, Qt.SIGNAL("triggered()"), lambda item=self.providenceCatchRegionAction: self.handleRegionMenuItemSelected(item)) self.connect(self.chooseRegionAction, Qt.SIGNAL("triggered()"), self.showRegionChooser) self.connect(self.showChatAction, Qt.SIGNAL("triggered()"), self.changeChatVisibility) self.connect(self.soundSetupAction, Qt.SIGNAL("triggered()"), self.showSoundSetup) self.connect(self.activateSoundAction, Qt.SIGNAL("triggered()"), self.changeSound) self.connect(self.useSpokenNotificationsAction, Qt.SIGNAL("triggered()"), self.changeUseSpokenNotifications) self.connect(self.floatingOverviewAction, Qt.SIGNAL("triggered()"), self.showFloatingOverview) self.connect(self.trayIcon, Qt.SIGNAL("alarm_distance"), self.changeAlarmDistance) self.connect(self.framelessWindowAction, Qt.SIGNAL("triggered()"), self.changeFrameless) self.connect(self.trayIcon, Qt.SIGNAL("change_frameless"), self.changeFrameless) self.connect(self.frameButton, Qt.SIGNAL("clicked()"), self.changeFrameless) self.connect(self.quitAction, Qt.SIGNAL("triggered()"), self.close) self.connect(self.trayIcon, Qt.SIGNAL("quit"), self.close) self.connect(self.jumpbridgeDataAction, Qt.SIGNAL("triggered()"), self.showJumbridgeChooser) # Create a timer to refresh the map, then load up the map, either from cache or dotlan self.mapTimer = QtCore.QTimer(self) self.connect(self.mapTimer, QtCore.SIGNAL("timeout()"), self.updateMapView) self.setupMap(True) # Recall cached user settings try: self.cache.recallAndApplySettings(self, "settings") except Exception as e: print str(e) self.trayIcon.showMessage("Settings error", "Something went wrong loading saved state:\n {0}".format(str(e)), 1) # Set up threads and their connections self.avatarFindThread = AvatarFindThread() self.connect(self.avatarFindThread, QtCore.SIGNAL("avatar_update"), self.updateAvatarOnChatEntry) self.avatarFindThread.start() self.kosRequestThread = KOSCheckerThread() self.connect(self.kosRequestThread, Qt.SIGNAL("kos_result"), self.showKosResult) self.kosRequestThread.start() self.filewatcherThread = filewatcher.FileWatcher(self.pathToLogs, 60 * 60 * 24) self.connect(self.filewatcherThread, QtCore.SIGNAL("file_change"), self.logFileChanged) self.filewatcherThread.start() self.versionCheckThread = amazon_s3.NotifyNewVersionThread() self.versionCheckThread.connect(self.versionCheckThread, Qt.SIGNAL("newer_version"), self.notifyNewerVersion) self.versionCheckThread.start() if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"): font = self.statisticsButton.font() font.setPointSize(8) self.statisticsButton.setFont(font) self.jumpbridgesButton.setFont(font) def setupMap(self, initialize=False): self.mapTimer.stop() regionName = self.cache.getFromCache("region_name") if not regionName: regionName = "Providence" svg = None try: with open(resourcePath("vi/ui/res/mapdata/{0}.svg".format(regionName))) as svgFile: svg = svgFile.read() except Exception as e: pass try: self.dotlan = dotlan.Map(regionName, svg) except dotlan.DotlanException as e: QtGui.QMessageBox.critical(None, "Error getting map", unicode(e), "Quit") sys.exit(1) if self.dotlan.outdatedCacheError: e = self.dotlan.outdatedCacheError diagText = "Something went wrong getting map data. Proceeding with older cached data. " \ "Check for a newer version and inform the maintainer.\n\nError: {0} {1}".format(type(e), unicode(e)) print str(e) QtGui.QMessageBox.warning(None, "Using map from cache", diagText, "Ok") # Load the jumpbridges self.setJumpbridges(self.cache.getFromCache("jumpbridge_url")) self.initMapPosition = None # We read this after first rendering self.systems = self.dotlan.systems self.chatparser = chatparser.ChatParser(self.pathToLogs, self.roomnames, self.systems) # Menus - only once if initialize: # Add a contextual menu to the mapView def mapContextMenuEvent(event): self.mapView.contextMenu.exec_(self.mapToGlobal(QPoint(event.x(), event.y()))) self.setMapContent(self.dotlan.svg) self.mapView.contextMenu = TrayContextMenu(self.trayIcon) self.mapView.contextMenuEvent = mapContextMenuEvent self.mapView.connect(self.mapView, Qt.SIGNAL("linkClicked(const QUrl&)"), self.mapLinkClicked) # Also set up our app menus regionName = self.cache.getFromCache("region_name") if not regionName: self.providenceRegionAction.setChecked(True) elif regionName.startswith("Providencecatch"): self.providenceCatchRegionAction.setChecked(True) elif regionName.startswith("Catch"): self.catchRegionAction.setChecked(True) elif regionName.startswith("Providence"): self.providenceRegionAction.setChecked(True) else: self.chooseRegionAction.setChecked(True) self.updateMapView(force=True) self.mapTimer.start(MAP_UPDATE_INTERVAL_IN_MSECS) self.jumpbridgesButton.setChecked(False) self.statisticsButton.setChecked(False) def closeEvent(self, event): """ Persisting things to the cache before closing the window """ # Known playernames if self.knownPlayerNames: value = ",".join(self.knownPlayerNames) self.cache.putIntoCache("known_player_names", value, 60 * 60 * 24 * 365) # Program state to cache (to read it on next startup) settings = ((None, "restoreGeometry", str(self.saveGeometry())), (None, "restoreState", str(self.saveState())), ("splitter", "restoreGeometry", str(self.splitter.saveGeometry())), ("splitter", "restoreState", str(self.splitter.saveState())), ("mapView", "setZoomFactor", self.mapView.zoomFactor()), (None, "changeChatFontSize", ChatEntryWidget.TEXT_SIZE), (None, "changeOpacity", self.opacityGroup.checkedAction().opacity), (None, "changeAlwaysOnTop", self.alwaysOnTopAction.isChecked()), (None, "changeShowAvatars", self.showChatAvatarsAction.isChecked()), (None, "changeAlarmDistance", self.alarmDistance), (None, "changeSound", self.activateSoundAction.isChecked()), (None, "changeChatVisibility", self.showChatAction.isChecked()), (None, "setInitMapPosition", (self.mapView.page().mainFrame().scrollPosition().x(), self.mapView.page().mainFrame().scrollPosition().y())), (None, "setSoundVolume", SoundManager().soundVolume), (None, "changeFrameless", self.framelessWindowAction.isChecked()), (None, "changeUseSpokenNotifications", self.useSpokenNotificationsAction.isChecked()), (None, "changeClipboard", self.kosClipboardActiveAction.isChecked()), (None, "changeFloatingOverview", self.floatingOverviewAction.isChecked()), (None, "changeAlreadyShowedSoundWarning", self.alreadyShowedSoundWarning)) self.cache.putIntoCache("settings", str(settings), 60 * 60 * 24 * 365) # Stop the threads try: self.filewatcherThread.quit() self.kosRequestThread.quit() SoundManager().quit() self.versionCheckThread.quit() except Exception: pass event.accept() def notifyNewerVersion(self, newestVersion): self.trayIcon.showMessage("Newer Version", ("An update is available for Vintel.\nhttps://github.com/Xanthos-Eve/vintel"), 1) def changeFloatingOverview(self, newValue=None): pass def changeAlreadyShowedSoundWarning(self, newValue): self.alreadyShowedSoundWarning = newValue def changeChatVisibility(self, newValue=None): if newValue is None: newValue = self.showChatAction.isChecked() self.showChatAction.setVisible(newValue) self.chatbox.setVisible(newValue) def changeClipboard(self, newValue=None): if newValue is None: newValue = self.kosClipboardActiveAction.isChecked() self.kosClipboardActiveAction.setChecked(newValue) def changeUseSpokenNotifications(self, newValue=None): if SoundManager().platformSupportsSpeech(): if newValue is None: newValue = self.useSpokenNotificationsAction.isChecked() self.useSpokenNotificationsAction.setChecked(newValue) SoundManager().setUseSpokenNotifications(newValue) else: self.useSpokenNotificationsAction.setChecked(False) self.useSpokenNotificationsAction.setEnabled(False) def changeOpacity(self, newValue=None): if newValue is not None: for action in self.opacityGroup.actions(): if action.opacity == newValue: action.setChecked(True) action = self.opacityGroup.checkedAction() self.setWindowOpacity(action.opacity) def changeSound(self, newValue=None, disable=False): if disable: self.activateSoundAction.setChecked(False) self.activateSoundAction.setEnabled(False) self.soundSetupAction.setEnabled(False) self.soundButton.setEnabled(False) if not self.alreadyShowedSoundWarning: self.alreadyShowedSoundWarning = True QtGui.QMessageBox.warning(None, "Sound disabled", "The lib 'pyglet' which is used to play sounds cannot be found, ""so the soundsystem is disabled.\nIf you want sound, please install the 'pyglet' library. This warning will not be shown again.", "OK") else: if newValue is None: newValue = self.activateSoundAction.isChecked() self.activateSoundAction.setChecked(newValue) SoundManager().soundActive = newValue def changeAlwaysOnTop(self, newValue=None): if newValue is None: newValue = self.alwaysOnTopAction.isChecked() self.hide() self.alwaysOnTopAction.setChecked(newValue) if newValue: self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) else: self.setWindowFlags(self.windowFlags() & (~QtCore.Qt.WindowStaysOnTopHint)) self.show() def changeFrameless(self, newValue=None): if newValue is None: newValue = not self.frameButton.isVisible() self.hide() if newValue: self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.changeAlwaysOnTop(True) else: self.setWindowFlags(self.windowFlags() & (~QtCore.Qt.FramelessWindowHint)) self.menubar.setVisible(not newValue) self.frameButton.setVisible(newValue) self.framelessWindowAction.setChecked(newValue) for cm in TrayContextMenu.instances: cm.framelessCheck.setChecked(newValue) self.show() def changeShowAvatars(self, newValue=None): if newValue is None: newValue = self.showChatAvatarsAction.isChecked() self.showChatAvatarsAction.setChecked(newValue) ChatEntryWidget.SHOW_AVATAR = newValue for entry in self.chatEntries: entry.avatarLabel.setVisible(newValue) def changeChatFontSize(self, newSize): if newSize: for entry in self.chatEntries: entry.changeFontSize(newSize) ChatEntryWidget.TEXT_SIZE = newSize def chatSmaller(self): newSize = ChatEntryWidget.TEXT_SIZE - 1 self.changeChatFontSize(newSize) def chatLarger(self): newSize = ChatEntryWidget.TEXT_SIZE + 1 self.changeChatFontSize(newSize) def changeAlarmDistance(self, distance): self.alarmDistance = distance for cm in TrayContextMenu.instances: for action in cm.distanceGroup.actions(): if action.alarmDistance == distance: action.setChecked(True) self.trayIcon.alarmDistance = distance def changeJumpbridgesVisibility(self): newValue = self.dotlan.changeJumpbridgesVisibility() self.jumpbridgesButton.setChecked(newValue) self.updateMapView() def changeStatisticsVisibility(self): newValue = self.dotlan.changeStatisticsVisibility() self.statisticsButton.setChecked(newValue) self.updateMapView() def clipboardChanged(self, mode): if not (mode == 0 and self.kosClipboardActiveAction.isChecked() and self.clipboard.mimeData().hasText()): return content = unicode(self.clipboard.text()) contentTuple = tuple(content) # Limit redundant kos checks if contentTuple != self.oldClipboardContent: parts = tuple(content.split("\n")) for part in parts: # Make sure user is in the content (this is a check of the local system in Eve) if part in self.knownPlayerNames: self.trayIcon.setIcon(self.taskbarIconWorking) self.kosRequestThread.addRequest(parts, "clipboard", True) break self.oldClipboardContent = contentTuple def mapLinkClicked(self, url): systemName = unicode(url.path().split("/")[-1]).upper() system = self.systems[str(systemName)] sc = SystemChat(self, SystemChat.SYSTEM, system, self.chatEntries, self.knownPlayerNames) sc.connect(self, Qt.SIGNAL("chat_message_added"), sc.addChatEntry) sc.connect(self, Qt.SIGNAL("avatar_loaded"), sc.newAvatarAvailable) sc.connect(sc, Qt.SIGNAL("location_set"), self.setLocation) sc.show() def markSystemOnMap(self, systemname): self.systems[unicode(systemname)].mark() self.updateMapView() def setLocation(self, char, newSystem): for system in self.systems.values(): system.removeLocatedCharacter(char) if not newSystem == "?" and newSystem in self.systems: self.systems[newSystem].addLocatedCharacter(char) self.setMapContent(self.dotlan.svg) def setMapContent(self, content): if self.initMapPosition is None: scrollposition = self.mapView.page().mainFrame().scrollPosition() else: scrollposition = self.initMapPosition self.initMapPosition = None self.mapView.setContent(content) self.mapView.page().mainFrame().setScrollPosition(scrollposition) self.mapView.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) def setInitMapPosition(self, xy): self.initMapPosition = QPoint(xy[0], xy[1]) def showChatroomChooser(self): chooser = ChatroomsChooser(self) chooser.connect(chooser, Qt.SIGNAL("rooms_changed"), self.changedRoomnames) chooser.show() def showJumbridgeChooser(self): url = self.cache.getFromCache("jumpbridge_url") chooser = JumpbridgeChooser(self, url) chooser.connect(chooser, Qt.SIGNAL("set_jumpbridge_url"), self.setJumpbridges) chooser.show() def setSoundVolume(self, value): SoundManager().setSoundVolume(value) def setJumpbridges(self, url): if url is None: url = "" try: data = [] if url != "": content = urllib2.urlopen(url).read() for line in content.split("\n"): parts = line.strip().split() if len(parts) == 3: data.append(parts) else: data = amazon_s3.getJumpbridgeData(self.dotlan.region.lower()) self.dotlan.setJumpbridges(data) self.cache.putIntoCache("jumpbridge_url", url, 60 * 60 * 24 * 365 * 8) except Exception as e: QtGui.QMessageBox.warning(None, "Loading jumpbridges failed!", "Error: {0}".format(unicode(e)), "OK") def handleRegionMenuItemSelected(self, menuAction=None): self.catchRegionAction.setChecked(False) self.providenceRegionAction.setChecked(False) self.providenceCatchRegionAction.setChecked(False) self.chooseRegionAction.setChecked(False) if menuAction: menuAction.setChecked(True) regionName = unicode(menuAction.property("regionName").toString()) regionName = dotlan.convertRegionName(regionName) Cache().putIntoCache("region_name", regionName, 60 * 60 * 24 * 365) self.setupMap() def showRegionChooser(self): def handleRegionChosen(): self.handleRegionMenuItemSelected(None) self.chooseRegionAction.setChecked(True) self.setupMap() self.chooseRegionAction.setChecked(False) chooser = RegionChooser(self) self.connect(chooser, Qt.SIGNAL("new_region_chosen"), handleRegionChosen) chooser.show() def addMessageToIntelChat(self, message): scrollToBottom = False if (self.chatListWidget.verticalScrollBar().value() == self.chatListWidget.verticalScrollBar().maximum()): scrollToBottom = True chatEntryWidget = ChatEntryWidget(message) listWidgetItem = QtGui.QListWidgetItem(self.chatListWidget) listWidgetItem.setSizeHint(chatEntryWidget.sizeHint()) self.chatListWidget.addItem(listWidgetItem) self.chatListWidget.setItemWidget(listWidgetItem, chatEntryWidget) self.avatarFindThread.addChatEntry(chatEntryWidget) self.chatEntries.append(chatEntryWidget) self.connect(chatEntryWidget, Qt.SIGNAL("mark_system"), self.markSystemOnMap) self.emit(Qt.SIGNAL("chat_message_added"), chatEntryWidget) self.pruneMessages() if scrollToBottom: self.chatListWidget.scrollToBottom() def pruneMessages(self): try: now = time.mktime(evegate.currentEveTime().timetuple()) for row in range(self.chatListWidget.count()): chatListWidgetItem = self.chatListWidget.item(0) chatEntryWidget = self.chatListWidget.itemWidget(chatListWidgetItem) message = chatEntryWidget.message if now - time.mktime(message.timestamp.timetuple()) > MESSAGE_EXPIRY_IN_SECONDS: self.chatEntries.remove(chatEntryWidget) self.chatListWidget.takeItem(0) for widgetInMessage in message.widgets: widgetInMessage.removeItemWidget(chatListWidgetItem) else: break except Exception as e: print e def showKosResult(self, state, text, requestType, hasKos): try: if hasKos: SoundManager().playSound("kos", text) if state == "ok": if requestType == "xxx": # a xxx request out of the chat self.trayIcon.showMessage("Player KOS-Check", text, 1) elif requestType == "clipboard": # request from clipboard-change if len(text) <= 0: text = "None KOS" self.trayIcon.showMessage("Your KOS-Check", text, 1) text = text.replace("\n\n", "<br>") message = chatparser.chatparser.Message("Vintel KOS-Check", text, evegate.currentEveTime(), "VINTEL", [], states.NOT_CHANGE, text.upper(), text) self.addMessageToIntelChat(message) elif state == "error": self.trayIcon.showMessage("KOS Failure", text, 3) except Exception: pass self.trayIcon.setIcon(self.taskbarIconQuiescent) def changedRoomnames(self, newRoomnames): self.cache.putIntoCache("room_names", u",".join(newRoomnames), 60 * 60 * 24 * 365 * 5) self.chatparser.rooms = newRoomnames def showInfo(self): infoDialog = QtGui.QDialog(self) uic.loadUi(resourcePath("vi/ui/Info.ui"), infoDialog) infoDialog.versionLabel.setText(u"Version: {0}".format(VERSION)) infoDialog.logoLabel.setPixmap(QtGui.QPixmap(resourcePath("vi/ui/res/logo.png"))) infoDialog.connect(infoDialog.closeButton, Qt.SIGNAL("clicked()"), infoDialog.accept) infoDialog.show() def showFloatingOverview(self): pass def showSoundSetup(self): dialog = QtGui.QDialog(self) uic.loadUi(resourcePath("vi/ui/SoundSetup.ui"), dialog) dialog.volumeSlider.setValue(SoundManager().soundVolume) dialog.connect(dialog.volumeSlider, Qt.SIGNAL("valueChanged(int)"), SoundManager().setSoundVolume) dialog.connect(dialog.testSoundButton, Qt.SIGNAL("clicked()"), SoundManager().playSound) dialog.connect(dialog.closeButton, Qt.SIGNAL("clicked()"), dialog.accept) dialog.show() def systemTrayActivated(self, reason): if reason == QtGui.QSystemTrayIcon.Trigger: if self.isMinimized(): self.showNormal() self.activateWindow() elif not self.isActiveWindow(): self.activateWindow() else: self.showMinimized() def updateAvatarOnChatEntry(self, chatEntry, avatarData): updated = chatEntry.updateAvatar(avatarData) if not updated: self.avatarFindThread.addChatEntry(chatEntry, clearCache=True) else: self.emit(Qt.SIGNAL("avatar_loaded"), chatEntry.message.user, avatarData) def updateMapView(self, force=False): def updateStatisticsOnMap(data): if data["result"] == "ok": self.dotlan.addSystemStatistics(data["statistics"]) elif data["result"] == "error": text = data["text"] self.trayIcon.showMessage("Loading statstics failed", text, 3) if force or self.lastStatisticsUpdate < time.time() - STATISTICS_UPDATE_INTERVAL_IN_MSECS: self.lastStatisticsUpdate = time.time() statisticsThread = MapStatisticsThread() self.connect(statisticsThread, Qt.SIGNAL("statistic_data_update"), updateStatisticsOnMap) statisticsThread.start() self.setMapContent(self.dotlan.svg) def zoomMapIn(self): self.mapView.setZoomFactor(self.mapView.zoomFactor() + 0.1) def zoomMapOut(self): self.mapView.setZoomFactor(self.mapView.zoomFactor() - 0.1) def logFileChanged(self, path): messages = self.chatparser.fileModified(path) for message in messages: # If players location has changed if message.status == states.LOCATION: self.knownPlayerNames.add(message.user) self.setLocation(message.user, message.systems[0]) # SOUND_TEST special elif message.status == states.SOUND_TEST and message.user in self.knownPlayerNames: words = message.message.split() if len(words) > 1: SoundManager().playSound(words[1]) # KOS request elif message.status == states.KOS_STATUS_REQUEST: text = message.message[4:] text = text.replace(" ", ",") parts = (name.strip() for name in text.split(",")) self.trayIcon.setIcon(self.taskbarIconWorking) self.kosRequestThread.addRequest(parts, "xxx", False) # Otherwise consider it a 'normal' chat message elif (message.user not in ("EVE-System", "EVE System") and message.status != states.IGNORE): self.addMessageToIntelChat(message) # For each system that was mentioned in the message, check for alarm distance to the current system # and alarm if within alarm distance. if message.systems: for system in message.systems: systemname = system.name self.dotlan.systems[systemname].setStatus(message.status) if message.status in (states.REQUEST, states.ALARM) and message.user not in self.knownPlayerNames: alarmDistance = self.alarmDistance if message.status == states.ALARM else 0 for nSystem, data in system.getNeighbours(alarmDistance).items(): distance = data["distance"] chars = nSystem.getLocatedCharacters() if len(chars) > 0 and message.user not in chars: self.trayIcon.showNotification(message, system.name, ", ".join(chars), distance) self.setMapContent(self.dotlan.svg)
class MainWindow(QtGui.QMainWindow): def __init__(self, pathToLogs, trayIcon, backGroundColor): QtGui.QMainWindow.__init__(self) self.cache = Cache() if backGroundColor: self.setStyleSheet("QWidget { background-color: %s; }" % backGroundColor) uic.loadUi(resourcePath('vi/ui/MainWindow.ui'), self) self.setWindowTitle("Vintel " + vi.version.VERSION + "{dev}".format(dev="-SNAPSHOT" if vi.version.SNAPSHOT else "")) self.taskbarIconQuiescent = QtGui.QIcon(resourcePath("vi/ui/res/logo_small.png")) self.taskbarIconWorking = QtGui.QIcon(resourcePath("vi/ui/res/logo_small_green.png")) self.setWindowIcon(self.taskbarIconQuiescent) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.pathToLogs = pathToLogs self.mapTimer = QtCore.QTimer(self) self.connect(self.mapTimer, QtCore.SIGNAL("timeout()"), self.updateMapView) self.clipboardTimer = QtCore.QTimer(self) self.oldClipboardContent = "" self.trayIcon = trayIcon self.trayIcon.activated.connect(self.systemTrayActivated) self.clipboard = QtGui.QApplication.clipboard() self.clipboard.clear(mode=self.clipboard.Clipboard) self.alarmDistance = 0 self.lastStatisticsUpdate = 0 self.chatEntries = [] self.frameButton.setVisible(False) self.scanIntelForKosRequestsEnabled = True self.initialMapPosition = None self.mapPositionsDict = {} # Load user's toon names self.knownPlayerNames = self.cache.getFromCache("known_player_names") if self.knownPlayerNames: self.knownPlayerNames = set(self.knownPlayerNames.split(",")) else: self.knownPlayerNames = set() diagText = "Vintel scans EVE system logs and remembers your characters as they change systems.\n\nSome features (clipboard KOS checking, alarms, etc.) may not work until your character(s) have been registered. Change systems, with each character you want to monitor, while Vintel is running to remedy this." QtGui.QMessageBox.warning(None, "Known Characters not Found", diagText, "Ok") # Set up user's intel rooms roomnames = self.cache.getFromCache("room_names") if roomnames: roomnames = roomnames.split(",") else: roomnames = (u"TheCitadel", u"North Provi Intel", u"North Catch Intel", "North Querious Intel") self.cache.putIntoCache("room_names", u",".join(roomnames), 60 * 60 * 24 * 365 * 5) self.roomnames = roomnames # Disable the sound UI if sound is not available if not SoundManager().soundAvailable: self.changeSound(disable=True) else: self.changeSound() # Set up Transparency menu - fill in opacity values and make connections self.opacityGroup = QtGui.QActionGroup(self.menu) for i in (100, 80, 60, 40, 20): action = QtGui.QAction("Opacity {0}%".format(i), None, checkable=True) if i == 100: action.setChecked(True) action.opacity = i / 100.0 self.connect(action, QtCore.SIGNAL("triggered()"), self.changeOpacity) self.opacityGroup.addAction(action) self.menuTransparency.addAction(action) # # Platform specific UI resizing - we size items in the resource files to look correct on the mac, # then resize other platforms as needed # if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"): font = self.statisticsButton.font() font.setPointSize(8) self.statisticsButton.setFont(font) self.jumpbridgesButton.setFont(font) elif sys.platform.startswith("linux"): pass self.wireUpUIConnections() self.recallCachedSettings() self.setupThreads() self.setupMap(True) def paintEvent(self, event): opt = QStyleOption() opt.initFrom(self) painter = QPainter(self) self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self) def recallCachedSettings(self): try: self.cache.recallAndApplySettings(self, "settings") except Exception as e: logging.error(e) # todo: add a button to delete the cache / DB self.trayIcon.showMessage("Settings error", "Something went wrong loading saved state:\n {0}".format(str(e)), 1) def wireUpUIConnections(self): # Wire up general UI connections self.connect(self.clipboard, Qt.SIGNAL("changed(QClipboard::Mode)"), self.clipboardChanged) self.connect(self.autoScanIntelAction, Qt.SIGNAL("triggered()"), self.changeAutoScanIntel) self.connect(self.kosClipboardActiveAction, Qt.SIGNAL("triggered()"), self.changeKosCheckClipboard) self.connect(self.zoomInButton, Qt.SIGNAL("clicked()"), self.zoomMapIn) self.connect(self.zoomOutButton, Qt.SIGNAL("clicked()"), self.zoomMapOut) self.connect(self.statisticsButton, Qt.SIGNAL("clicked()"), self.changeStatisticsVisibility) self.connect(self.jumpbridgesButton, Qt.SIGNAL("clicked()"), self.changeJumpbridgesVisibility) self.connect(self.chatLargeButton, Qt.SIGNAL("clicked()"), self.chatLarger) self.connect(self.chatSmallButton, Qt.SIGNAL("clicked()"), self.chatSmaller) self.connect(self.infoAction, Qt.SIGNAL("triggered()"), self.showInfo) self.connect(self.showChatAvatarsAction, Qt.SIGNAL("triggered()"), self.changeShowAvatars) self.connect(self.alwaysOnTopAction, Qt.SIGNAL("triggered()"), self.changeAlwaysOnTop) self.connect(self.chooseChatRoomsAction, Qt.SIGNAL("triggered()"), self.showChatroomChooser) self.connect(self.catchRegionAction, Qt.SIGNAL("triggered()"), lambda : self.handleRegionMenuItemSelected(self.catchRegionAction)) self.connect(self.providenceRegionAction, Qt.SIGNAL("triggered()"), lambda : self.handleRegionMenuItemSelected(self.providenceRegionAction)) self.connect(self.queriousRegionAction, Qt.SIGNAL("triggered()"), lambda : self.handleRegionMenuItemSelected(self.queriousRegionAction)) self.connect(self.providenceCatchRegionAction, Qt.SIGNAL("triggered()"), lambda : self.handleRegionMenuItemSelected(self.providenceCatchRegionAction)) self.connect(self.providenceCatchCompactRegionAction, Qt.SIGNAL("triggered()"), lambda : self.handleRegionMenuItemSelected(self.providenceCatchCompactRegionAction)) self.connect(self.chooseRegionAction, Qt.SIGNAL("triggered()"), self.showRegionChooser) self.connect(self.showChatAction, Qt.SIGNAL("triggered()"), self.changeChatVisibility) self.connect(self.soundSetupAction, Qt.SIGNAL("triggered()"), self.showSoundSetup) self.connect(self.activateSoundAction, Qt.SIGNAL("triggered()"), self.changeSound) self.connect(self.useSpokenNotificationsAction, Qt.SIGNAL("triggered()"), self.changeUseSpokenNotifications) self.connect(self.trayIcon, Qt.SIGNAL("alarm_distance"), self.changeAlarmDistance) self.connect(self.framelessWindowAction, Qt.SIGNAL("triggered()"), self.changeFrameless) self.connect(self.trayIcon, Qt.SIGNAL("change_frameless"), self.changeFrameless) self.connect(self.frameButton, Qt.SIGNAL("clicked()"), self.changeFrameless) self.connect(self.quitAction, Qt.SIGNAL("triggered()"), self.close) self.connect(self.trayIcon, Qt.SIGNAL("quit"), self.close) self.connect(self.jumpbridgeDataAction, Qt.SIGNAL("triggered()"), self.showJumbridgeChooser) self.mapView.page().scrollRequested.connect(self.mapPositionChanged) def setupThreads(self): # Set up threads and their connections self.avatarFindThread = AvatarFindThread() self.connect(self.avatarFindThread, QtCore.SIGNAL("avatar_update"), self.updateAvatarOnChatEntry) self.avatarFindThread.start() self.kosRequestThread = KOSCheckerThread() self.connect(self.kosRequestThread, Qt.SIGNAL("kos_result"), self.showKosResult) self.kosRequestThread.start() self.filewatcherThread = filewatcher.FileWatcher(self.pathToLogs) self.connect(self.filewatcherThread, QtCore.SIGNAL("file_change"), self.logFileChanged) self.filewatcherThread.start() self.versionCheckThread = amazon_s3.NotifyNewVersionThread() self.versionCheckThread.connect(self.versionCheckThread, Qt.SIGNAL("newer_version"), self.notifyNewerVersion) self.versionCheckThread.start() self.statisticsThread = MapStatisticsThread() self.connect(self.statisticsThread, Qt.SIGNAL("statistic_data_update"), self.updateStatisticsOnMap) self.statisticsThread.start() # statisticsThread is blocked until first call of requestStatistics def setupMap(self, initialize=False): self.mapTimer.stop() self.filewatcherThread.paused = True logging.info("Finding map file") regionName = self.cache.getFromCache("region_name") if not regionName: regionName = "Providence" svg = None try: with open(resourcePath("vi/ui/res/mapdata/{0}.svg".format(regionName))) as svgFile: svg = svgFile.read() except Exception as e: pass try: self.dotlan = dotlan.Map(regionName, svg) except dotlan.DotlanException as e: logging.error(e) QtGui.QMessageBox.critical(None, "Error getting map", six.text_type(e), "Quit") sys.exit(1) if self.dotlan.outdatedCacheError: e = self.dotlan.outdatedCacheError diagText = "Something went wrong getting map data. Proceeding with older cached data. " \ "Check for a newer version and inform the maintainer.\n\nError: {0} {1}".format(type(e), six.text_type(e)) logging.warn(diagText) QtGui.QMessageBox.warning(None, "Using map from cache", diagText, "Ok") # Load the jumpbridges logging.critical("Load jump bridges") self.setJumpbridges(self.cache.getFromCache("jumpbridge_url")) self.systems = self.dotlan.systems logging.critical("Creating chat parser") self.chatparser = chatparser.ChatParser(self.pathToLogs, self.roomnames, self.systems) # Menus - only once if initialize: logging.critical("Initializing contextual menus") # Add a contextual menu to the mapView def mapContextMenuEvent(event): #if QApplication.activeWindow() or QApplication.focusWidget(): self.mapView.contextMenu.exec_(self.mapToGlobal(QPoint(event.x(), event.y()))) self.mapView.contextMenuEvent = mapContextMenuEvent self.mapView.contextMenu = self.trayIcon.contextMenu() # Clicking links self.mapView.connect(self.mapView, Qt.SIGNAL("linkClicked(const QUrl&)"), self.mapLinkClicked) # Also set up our app menus if not regionName: self.providenceCatchRegionAction.setChecked(True) elif regionName.startswith("Providencecatch"): self.providenceCatchRegionAction.setChecked(True) elif regionName.startswith("Catch"): self.catchRegionAction.setChecked(True) elif regionName.startswith("Providence"): self.providenceRegionAction.setChecked(True) elif regionName.startswith("Querious"): self.queriousRegionAction.setChecked(True) else: self.chooseRegionAction.setChecked(True) self.jumpbridgesButton.setChecked(False) self.statisticsButton.setChecked(False) # Update the new map view, then clear old statistics from the map and request new logging.critical("Updating the map") self.updateMapView() self.setInitialMapPositionForRegion(regionName) self.mapTimer.start(MAP_UPDATE_INTERVAL_MSECS) # Allow the file watcher to run now that all else is set up self.filewatcherThread.paused = False logging.critical("Map setup complete") # def eventFilter(self, obj, event): # if event.type() == QtCore.QEvent.WindowDeactivate: # self.enableContextMenu(False) # return True # elif event.type() == QtCore.QEvent.WindowActivate: # self.enableContextMenu(True) # return True # return False def startClipboardTimer(self): """ Start a timer to check the keyboard for changes and kos check them, first initializing the content so we dont kos check from random content """ self.oldClipboardContent = tuple(six.text_type(self.clipboard.text())) self.connect(self.clipboardTimer, QtCore.SIGNAL("timeout()"), self.clipboardChanged) self.clipboardTimer.start(CLIPBOARD_CHECK_INTERVAL_MSECS) def stopClipboardTimer(self): if self.clipboardTimer: self.disconnect(self.clipboardTimer, QtCore.SIGNAL("timeout()"), self.clipboardChanged) self.clipboardTimer.stop() def closeEvent(self, event): """ Persisting things to the cache before closing the window """ # Known playernames if self.knownPlayerNames: value = ",".join(self.knownPlayerNames) self.cache.putIntoCache("known_player_names", value, 60 * 60 * 24 * 30) # Program state to cache (to read it on next startup) settings = ((None, "restoreGeometry", str(self.saveGeometry())), (None, "restoreState", str(self.saveState())), ("splitter", "restoreGeometry", str(self.splitter.saveGeometry())), ("splitter", "restoreState", str(self.splitter.saveState())), ("mapView", "setZoomFactor", self.mapView.zoomFactor()), (None, "changeChatFontSize", ChatEntryWidget.TEXT_SIZE), (None, "changeOpacity", self.opacityGroup.checkedAction().opacity), (None, "changeAlwaysOnTop", self.alwaysOnTopAction.isChecked()), (None, "changeShowAvatars", self.showChatAvatarsAction.isChecked()), (None, "changeAlarmDistance", self.alarmDistance), (None, "changeSound", self.activateSoundAction.isChecked()), (None, "changeChatVisibility", self.showChatAction.isChecked()), (None, "loadInitialMapPositions", self.mapPositionsDict), (None, "setSoundVolume", SoundManager().soundVolume), (None, "changeFrameless", self.framelessWindowAction.isChecked()), (None, "changeUseSpokenNotifications", self.useSpokenNotificationsAction.isChecked()), (None, "changeKosCheckClipboard", self.kosClipboardActiveAction.isChecked()), (None, "changeAutoScanIntel", self.scanIntelForKosRequestsEnabled)) self.cache.putIntoCache("settings", str(settings), 60 * 60 * 24 * 30) # Stop the threads try: SoundManager().quit() self.avatarFindThread.quit() self.avatarFindThread.wait() self.filewatcherThread.quit() self.filewatcherThread.wait() self.kosRequestThread.quit() self.kosRequestThread.wait() self.versionCheckThread.quit() self.versionCheckThread.wait() self.statisticsThread.quit() self.statisticsThread.wait() except Exception: pass self.trayIcon.hide() event.accept() def notifyNewerVersion(self, newestVersion): self.trayIcon.showMessage("Newer Version", ("An update is available for Vintel.\nhttps://github.com/Xanthos-Eve/vintel"), 1) def changeChatVisibility(self, newValue=None): if newValue is None: newValue = self.showChatAction.isChecked() self.showChatAction.setChecked(newValue) self.chatbox.setVisible(newValue) def changeKosCheckClipboard(self, newValue=None): if newValue is None: newValue = self.kosClipboardActiveAction.isChecked() self.kosClipboardActiveAction.setChecked(newValue) if newValue: self.startClipboardTimer() else: self.stopClipboardTimer() def changeAutoScanIntel(self, newValue=None): if newValue is None: newValue = self.autoScanIntelAction.isChecked() self.autoScanIntelAction.setChecked(newValue) self.scanIntelForKosRequestsEnabled = newValue def changeUseSpokenNotifications(self, newValue=None): if SoundManager().platformSupportsSpeech(): if newValue is None: newValue = self.useSpokenNotificationsAction.isChecked() self.useSpokenNotificationsAction.setChecked(newValue) SoundManager().setUseSpokenNotifications(newValue) else: self.useSpokenNotificationsAction.setChecked(False) self.useSpokenNotificationsAction.setEnabled(False) def changeOpacity(self, newValue=None): if newValue is not None: for action in self.opacityGroup.actions(): if action.opacity == newValue: action.setChecked(True) action = self.opacityGroup.checkedAction() self.setWindowOpacity(action.opacity) def changeSound(self, newValue=None, disable=False): if disable: self.activateSoundAction.setChecked(False) self.activateSoundAction.setEnabled(False) self.soundSetupAction.setEnabled(False) #self.soundButton.setEnabled(False) QtGui.QMessageBox.warning(None, "Sound disabled", "The lib 'pyglet' which is used to play sounds cannot be found, ""so the soundsystem is disabled.\nIf you want sound, please install the 'pyglet' library. This warning will not be shown again.", "OK") else: if newValue is None: newValue = self.activateSoundAction.isChecked() self.activateSoundAction.setChecked(newValue) SoundManager().soundActive = newValue def changeAlwaysOnTop(self, newValue=None): if newValue is None: newValue = self.alwaysOnTopAction.isChecked() self.hide() self.alwaysOnTopAction.setChecked(newValue) if newValue: self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) else: self.setWindowFlags(self.windowFlags() & (~QtCore.Qt.WindowStaysOnTopHint)) self.show() def changeFrameless(self, newValue=None): if newValue is None: newValue = not self.frameButton.isVisible() self.hide() if newValue: self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.changeAlwaysOnTop(True) else: self.setWindowFlags(self.windowFlags() & (~QtCore.Qt.FramelessWindowHint)) self.menubar.setVisible(not newValue) self.frameButton.setVisible(newValue) self.framelessWindowAction.setChecked(newValue) for cm in TrayContextMenu.instances: cm.framelessCheck.setChecked(newValue) self.show() def changeShowAvatars(self, newValue=None): if newValue is None: newValue = self.showChatAvatarsAction.isChecked() self.showChatAvatarsAction.setChecked(newValue) ChatEntryWidget.SHOW_AVATAR = newValue for entry in self.chatEntries: entry.avatarLabel.setVisible(newValue) def changeChatFontSize(self, newSize): if newSize: for entry in self.chatEntries: entry.changeFontSize(newSize) ChatEntryWidget.TEXT_SIZE = newSize def chatSmaller(self): newSize = ChatEntryWidget.TEXT_SIZE - 1 self.changeChatFontSize(newSize) def chatLarger(self): newSize = ChatEntryWidget.TEXT_SIZE + 1 self.changeChatFontSize(newSize) def changeAlarmDistance(self, distance): self.alarmDistance = distance for cm in TrayContextMenu.instances: for action in cm.distanceGroup.actions(): if action.alarmDistance == distance: action.setChecked(True) self.trayIcon.alarmDistance = distance def changeJumpbridgesVisibility(self): newValue = self.dotlan.changeJumpbridgesVisibility() self.jumpbridgesButton.setChecked(newValue) self.updateMapView() def changeStatisticsVisibility(self): newValue = self.dotlan.changeStatisticsVisibility() self.statisticsButton.setChecked(newValue) self.updateMapView() if newValue: self.statisticsThread.requestStatistics() def clipboardChanged(self, mode=0): if not (mode == 0 and self.kosClipboardActiveAction.isChecked() and self.clipboard.mimeData().hasText()): return content = six.text_type(self.clipboard.text()) contentTuple = tuple(content) # Limit redundant kos checks if contentTuple != self.oldClipboardContent: parts = tuple(content.split("\n")) knownPlayers = self.knownPlayerNames for part in parts: # Make sure user is in the content (this is a check of the local system in Eve). # also, special case for when you have no knonwnPlayers (initial use) if not knownPlayers or part in knownPlayers: self.trayIcon.setIcon(self.taskbarIconWorking) self.kosRequestThread.addRequest(parts, "clipboard", True) break self.oldClipboardContent = contentTuple def mapLinkClicked(self, url): systemName = six.text_type(url.path().split("/")[-1]).upper() system = self.systems[str(systemName)] sc = SystemChat(self, SystemChat.SYSTEM, system, self.chatEntries, self.knownPlayerNames) sc.connect(self, Qt.SIGNAL("chat_message_added"), sc.addChatEntry) sc.connect(self, Qt.SIGNAL("avatar_loaded"), sc.newAvatarAvailable) sc.connect(sc, Qt.SIGNAL("location_set"), self.setLocation) sc.show() def markSystemOnMap(self, systemname): self.systems[six.text_type(systemname)].mark() self.updateMapView() def setLocation(self, char, newSystem): for system in self.systems.values(): system.removeLocatedCharacter(char) if not newSystem == "?" and newSystem in self.systems: self.systems[newSystem].addLocatedCharacter(char) self.setMapContent(self.dotlan.svg) def setMapContent(self, content): if self.initialMapPosition is None: scrollPosition = self.mapView.page().mainFrame().scrollPosition() else: scrollPosition = self.initialMapPosition self.mapView.setContent(content) self.mapView.page().mainFrame().setScrollPosition(scrollPosition) self.mapView.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) # Make sure we have positioned the window before we nil the initial position; # even though we set it, it may not take effect until the map is fully loaded scrollPosition = self.mapView.page().mainFrame().scrollPosition() if scrollPosition.x() or scrollPosition.y(): self.initialMapPosition = None def loadInitialMapPositions(self, newDictionary): self.mapPositionsDict = newDictionary def setInitialMapPositionForRegion(self, regionName): try: if not regionName: regionName = self.cache.getFromCache("region_name") if regionName: xy = self.mapPositionsDict[regionName] self.initialMapPosition = QPoint(xy[0], xy[1]) except Exception: pass def mapPositionChanged(self, dx, dy, rectToScroll): regionName = self.cache.getFromCache("region_name") if regionName: scrollPosition = self.mapView.page().mainFrame().scrollPosition() self.mapPositionsDict[regionName] = (scrollPosition.x(), scrollPosition.y()) def showChatroomChooser(self): chooser = ChatroomsChooser(self) chooser.connect(chooser, Qt.SIGNAL("rooms_changed"), self.changedRoomnames) chooser.show() def showJumbridgeChooser(self): url = self.cache.getFromCache("jumpbridge_url") chooser = JumpbridgeChooser(self, url) chooser.connect(chooser, Qt.SIGNAL("set_jumpbridge_url"), self.setJumpbridges) chooser.show() def setSoundVolume(self, value): SoundManager().setSoundVolume(value) def setJumpbridges(self, url): if url is None: url = "" try: data = [] if url != "": resp = requests.get(url) for line in resp.iter_lines(decode_unicode=True): parts = line.strip().split() if len(parts) == 3: data.append(parts) else: data = amazon_s3.getJumpbridgeData(self.dotlan.region.lower()) self.dotlan.setJumpbridges(data) self.cache.putIntoCache("jumpbridge_url", url, 60 * 60 * 24 * 365 * 8) except Exception as e: QtGui.QMessageBox.warning(None, "Loading jumpbridges failed!", "Error: {0}".format(six.text_type(e)), "OK") def handleRegionMenuItemSelected(self, menuAction=None): self.catchRegionAction.setChecked(False) self.providenceRegionAction.setChecked(False) self.queriousRegionAction.setChecked(False) self.providenceCatchRegionAction.setChecked(False) self.providenceCatchCompactRegionAction.setChecked(False) self.chooseRegionAction.setChecked(False) if menuAction: menuAction.setChecked(True) regionName = six.text_type(menuAction.property("regionName").toString()) regionName = dotlan.convertRegionName(regionName) Cache().putIntoCache("region_name", regionName, 60 * 60 * 24 * 365) self.setupMap() def showRegionChooser(self): def handleRegionChosen(): self.handleRegionMenuItemSelected(None) self.chooseRegionAction.setChecked(True) self.setupMap() self.chooseRegionAction.setChecked(False) chooser = RegionChooser(self) self.connect(chooser, Qt.SIGNAL("new_region_chosen"), handleRegionChosen) chooser.show() def addMessageToIntelChat(self, message): scrollToBottom = False if (self.chatListWidget.verticalScrollBar().value() == self.chatListWidget.verticalScrollBar().maximum()): scrollToBottom = True chatEntryWidget = ChatEntryWidget(message) listWidgetItem = QtGui.QListWidgetItem(self.chatListWidget) listWidgetItem.setSizeHint(chatEntryWidget.sizeHint()) self.chatListWidget.addItem(listWidgetItem) self.chatListWidget.setItemWidget(listWidgetItem, chatEntryWidget) self.avatarFindThread.addChatEntry(chatEntryWidget) self.chatEntries.append(chatEntryWidget) self.connect(chatEntryWidget, Qt.SIGNAL("mark_system"), self.markSystemOnMap) self.emit(Qt.SIGNAL("chat_message_added"), chatEntryWidget) self.pruneMessages() if scrollToBottom: self.chatListWidget.scrollToBottom() def pruneMessages(self): try: now = time.mktime(evegate.currentEveTime().timetuple()) for row in range(self.chatListWidget.count()): chatListWidgetItem = self.chatListWidget.item(0) chatEntryWidget = self.chatListWidget.itemWidget(chatListWidgetItem) message = chatEntryWidget.message if now - time.mktime(message.timestamp.timetuple()) > MESSAGE_EXPIRY_SECS: self.chatEntries.remove(chatEntryWidget) self.chatListWidget.takeItem(0) for widgetInMessage in message.widgets: widgetInMessage.removeItemWidget(chatListWidgetItem) else: break except Exception as e: logging.error(e) def showKosResult(self, state, text, requestType, hasKos): if not self.scanIntelForKosRequestsEnabled: return try: if hasKos: SoundManager().playSound("kos", text) if state == "ok": if requestType == "xxx": # An xxx request out of the chat self.trayIcon.showMessage("Player KOS-Check", text, 1) elif requestType == "clipboard": # request from clipboard-change if len(text) <= 0: text = "None KOS" self.trayIcon.showMessage("Your KOS-Check", text, 1) text = text.replace("\n\n", "<br>") message = chatparser.chatparser.Message("Vintel KOS-Check", text, evegate.currentEveTime(), "VINTEL", [], states.NOT_CHANGE, text.upper(), text) self.addMessageToIntelChat(message) elif state == "error": self.trayIcon.showMessage("KOS Failure", text, 3) except Exception: pass self.trayIcon.setIcon(self.taskbarIconQuiescent) def changedRoomnames(self, newRoomnames): self.cache.putIntoCache("room_names", u",".join(newRoomnames), 60 * 60 * 24 * 365 * 5) self.chatparser.rooms = newRoomnames def showInfo(self): infoDialog = QtGui.QDialog(self) uic.loadUi(resourcePath("vi/ui/Info.ui"), infoDialog) infoDialog.versionLabel.setText(u"Version: {0}".format(vi.version.VERSION)) infoDialog.logoLabel.setPixmap(QtGui.QPixmap(resourcePath("vi/ui/res/logo.png"))) infoDialog.connect(infoDialog.closeButton, Qt.SIGNAL("clicked()"), infoDialog.accept) infoDialog.show() def showSoundSetup(self): dialog = QtGui.QDialog(self) uic.loadUi(resourcePath("vi/ui/SoundSetup.ui"), dialog) dialog.volumeSlider.setValue(SoundManager().soundVolume) dialog.connect(dialog.volumeSlider, Qt.SIGNAL("valueChanged(int)"), SoundManager().setSoundVolume) dialog.connect(dialog.testSoundButton, Qt.SIGNAL("clicked()"), SoundManager().playSound) dialog.connect(dialog.closeButton, Qt.SIGNAL("clicked()"), dialog.accept) dialog.show() def systemTrayActivated(self, reason): if reason == QtGui.QSystemTrayIcon.Trigger: if self.isMinimized(): self.showNormal() self.activateWindow() elif not self.isActiveWindow(): self.activateWindow() else: self.showMinimized() def updateAvatarOnChatEntry(self, chatEntry, avatarData): updated = chatEntry.updateAvatar(avatarData) if not updated: self.avatarFindThread.addChatEntry(chatEntry, clearCache=True) else: self.emit(Qt.SIGNAL("avatar_loaded"), chatEntry.message.user, avatarData) def updateStatisticsOnMap(self, data): if not self.statisticsButton.isChecked(): return if data["result"] == "ok": self.dotlan.addSystemStatistics(data["statistics"]) elif data["result"] == "error": text = data["text"] self.trayIcon.showMessage("Loading statstics failed", text, 3) logging.error("updateStatisticsOnMap, error: %s" % text) def updateMapView(self): logging.debug("Updating map start") self.setMapContent(self.dotlan.svg) logging.debug("Updating map complete") def zoomMapIn(self): self.mapView.setZoomFactor(self.mapView.zoomFactor() + 0.1) def zoomMapOut(self): self.mapView.setZoomFactor(self.mapView.zoomFactor() - 0.1) def logFileChanged(self, path): messages = self.chatparser.fileModified(path) for message in messages: # If players location has changed if message.status == states.LOCATION: self.knownPlayerNames.add(message.user) self.setLocation(message.user, message.systems[0]) elif message.status == states.KOS_STATUS_REQUEST: # Do not accept KOS requests from monitored intel channels # as we don't want to encourage the use of xxx in those channels. if not message.room in self.roomnames: text = message.message[4:] text = text.replace(" ", ",") parts = (name.strip() for name in text.split(",")) self.trayIcon.setIcon(self.taskbarIconWorking) self.kosRequestThread.addRequest(parts, "xxx", False) # Otherwise consider it a 'normal' chat message elif message.user not in ("EVE-System", "EVE System") and message.status != states.IGNORE: self.addMessageToIntelChat(message) # For each system that was mentioned in the message, check for alarm distance to the current system # and alarm if within alarm distance. if message.systems: for system in message.systems: systemname = system.name self.dotlan.systems[systemname].setStatus(message.status) if message.status in (states.REQUEST, states.ALARM) and message.user not in self.knownPlayerNames: alarmDistance = self.alarmDistance if message.status == states.ALARM else 0 for nSystem, data in system.getNeighbours(alarmDistance).items(): distance = data["distance"] chars = nSystem.getLocatedCharacters() if len(chars) > 0 and message.user not in chars: self.trayIcon.showNotification(message, system.name, ", ".join(chars), distance) self.setMapContent(self.dotlan.svg)
class MainWindow(QtGui.QMainWindow): def __init__(self, pathToLogs, trayIcon, backGroundColor): QtGui.QMainWindow.__init__(self) self.cache = Cache() if backGroundColor: self.setStyleSheet("QWidget { background-color: %s; }" % backGroundColor) uic.loadUi(resourcePath('vi/ui/MainWindow.ui'), self) self.setWindowTitle( "Spyglass " + vi.version.VERSION + "{dev}".format(dev="-SNAPSHOT" if vi.version.SNAPSHOT else "")) self.taskbarIconQuiescent = QtGui.QIcon(resourcePath("vi/ui/res/logo_small.png")) self.taskbarIconWorking = QtGui.QIcon(resourcePath("vi/ui/res/logo_small_green.png")) self.setWindowIcon(self.taskbarIconQuiescent) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.pathToLogs = pathToLogs self.mapTimer = QtCore.QTimer(self) self.connect(self.mapTimer, SIGNAL("timeout()"), self.updateMapView) self.clipboardTimer = QtCore.QTimer(self) self.oldClipboardContent = "" self.trayIcon = trayIcon self.trayIcon.activated.connect(self.systemTrayActivated) self.clipboard = QtGui.QApplication.clipboard() self.clipboard.clear(mode=self.clipboard.Clipboard) self.alarmDistance = 0 self.lastStatisticsUpdate = 0 self.chatEntries = [] self.frameButton.setVisible(False) self.scanIntelForKosRequestsEnabled = True self.initialMapPosition = None self.mapPositionsDict = {} self.autoRescanIntelEnabled = self.cache.getFromCache("changeAutoRescanIntel") # Load user's toon names self.knownPlayerNames = self.cache.getFromCache("known_player_names") if self.knownPlayerNames: self.knownPlayerNames = set(self.knownPlayerNames.split(",")) else: self.knownPlayerNames = set() diagText = "Spyglass scans EVE system logs and remembers your characters as they change systems.\n\nSome features (clipboard KOS checking, alarms, etc.) may not work until your character(s) have been registered. Change systems, with each character you want to monitor, while Spyglass is running to remedy this." QMessageBox.warning(None, "Known Characters not Found", diagText, "Ok") # Set up user's intel rooms roomnames = self.cache.getFromCache("room_names") if roomnames: roomnames = roomnames.split(",") else: roomnames = (u"TheCitadel", u"North Provi Intel", u"4THINTEL") self.cache.putIntoCache("room_names", u",".join(roomnames), 60 * 60 * 24 * 365 * 5) self.roomnames = roomnames # Disable the sound UI if sound is not available if not SoundManager().soundAvailable: self.changeSound(disable=True) else: self.changeSound() # Set up Transparency menu - fill in opacity values and make connections self.opacityGroup = QActionGroup(self.menu) for i in (100, 80, 60, 40, 20): action = QAction("Opacity {0}%".format(i), None, checkable=True) if i == 100: action.setChecked(True) action.opacity = i / 100.0 self.connect(action, SIGNAL("triggered()"), self.changeOpacity) self.opacityGroup.addAction(action) self.menuTransparency.addAction(action) # Set up Theme menu - fill in list of themes and add connections self.themeGroup = QActionGroup(self.menu) styles = Styles() for theme in styles.getStyles(): action = QAction(theme, None, checkable=True) action.theme = theme if action.theme == "default": action.setChecked(True) logging.info("Adding theme {}".format(theme)) self.connect(action, SIGNAL("triggered()"), self.changeTheme) self.themeGroup.addAction(action) self.menuTheme.addAction(action) styles = None # # Platform specific UI resizing - we size items in the resource files to look correct on the mac, # then resize other platforms as needed # if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"): font = self.statisticsButton.font() font.setPointSize(8) self.statisticsButton.setFont(font) self.jumpbridgesButton.setFont(font) elif sys.platform.startswith("linux"): pass self.wireUpUIConnections() self.recallCachedSettings() self.setupThreads() self.setupMap(True) initialTheme = self.cache.getFromCache("theme") if initialTheme: self.changeTheme(initialTheme) def paintEvent(self, event): opt = QStyleOption() opt.initFrom(self) painter = QPainter(self) self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self) def recallCachedSettings(self): try: self.cache.recallAndApplySettings(self, "settings") except Exception as e: logging.error(e) # todo: add a button to delete the cache / DB self.trayIcon.showMessage("Settings error", "Something went wrong loading saved state:\n {0}".format(str(e)), 1) def wireUpUIConnections(self): # Wire up general UI connections self.connect(self.clipboard, SIGNAL("changed(QClipboard::Mode)"), self.clipboardChanged) self.connect(self.autoScanIntelAction, SIGNAL("triggered()"), self.changeAutoScanIntel) self.connect(self.kosClipboardActiveAction, SIGNAL("triggered()"), self.changeKosCheckClipboard) self.connect(self.zoomInButton, SIGNAL("clicked()"), self.zoomMapIn) self.connect(self.zoomOutButton, SIGNAL("clicked()"), self.zoomMapOut) self.connect(self.statisticsButton, SIGNAL("clicked()"), self.changeStatisticsVisibility) self.connect(self.jumpbridgesButton, SIGNAL("clicked()"), self.changeJumpbridgesVisibility) self.connect(self.chatLargeButton, SIGNAL("clicked()"), self.chatLarger) self.connect(self.chatSmallButton, SIGNAL("clicked()"), self.chatSmaller) self.connect(self.infoAction, SIGNAL("triggered()"), self.showInfo) self.connect(self.showChatAvatarsAction, SIGNAL("triggered()"), self.changeShowAvatars) self.connect(self.alwaysOnTopAction, SIGNAL("triggered()"), self.changeAlwaysOnTop) self.connect(self.chooseChatRoomsAction, SIGNAL("triggered()"), self.showChatroomChooser) self.connect(self.catchRegionAction, SIGNAL("triggered()"), lambda: self.handleRegionMenuItemSelected(self.catchRegionAction)) self.connect(self.providenceRegionAction, SIGNAL("triggered()"), lambda: self.handleRegionMenuItemSelected(self.providenceRegionAction)) self.connect(self.queriousRegionAction, SIGNAL("triggered()"), lambda: self.handleRegionMenuItemSelected(self.queriousRegionAction)) self.connect(self.providenceCatchRegionAction, SIGNAL("triggered()"), lambda: self.handleRegionMenuItemSelected(self.providenceCatchRegionAction)) self.connect(self.providenceCatchCompactRegionAction, SIGNAL("triggered()"), lambda: self.handleRegionMenuItemSelected(self.providenceCatchCompactRegionAction)) self.connect(self.chooseRegionAction, SIGNAL("triggered()"), self.showRegionChooser) self.connect(self.showChatAction, SIGNAL("triggered()"), self.changeChatVisibility) self.connect(self.soundSetupAction, SIGNAL("triggered()"), self.showSoundSetup) self.connect(self.activateSoundAction, SIGNAL("triggered()"), self.changeSound) self.connect(self.useSpokenNotificationsAction, SIGNAL("triggered()"), self.changeUseSpokenNotifications) self.connect(self.trayIcon, SIGNAL("alarm_distance"), self.changeAlarmDistance) self.connect(self.framelessWindowAction, SIGNAL("triggered()"), self.changeFrameless) self.connect(self.trayIcon, SIGNAL("change_frameless"), self.changeFrameless) self.connect(self.frameButton, SIGNAL("clicked()"), self.changeFrameless) self.connect(self.quitAction, SIGNAL("triggered()"), self.close) self.connect(self.trayIcon, SIGNAL("quit"), self.close) self.connect(self.jumpbridgeDataAction, SIGNAL("triggered()"), self.showJumbridgeChooser) self.connect(self.rescanNowAction, SIGNAL("triggered()"), self.rescanIntel) self.connect(self.clearIntelAction, SIGNAL("triggered()"), self.clearIntelChat) self.connect(self.autoRescanAction, SIGNAL("triggered()"), self.changeAutoRescanIntel) self.mapView.page().scrollRequested.connect(self.mapPositionChanged) def setupThreads(self): # Set up threads and their connections self.avatarFindThread = AvatarFindThread() self.connect(self.avatarFindThread, SIGNAL("avatar_update"), self.updateAvatarOnChatEntry) self.avatarFindThread.start() self.kosRequestThread = KOSCheckerThread() self.connect(self.kosRequestThread, SIGNAL("kos_result"), self.showKosResult) self.kosRequestThread.start() self.filewatcherThread = filewatcher.FileWatcher(self.pathToLogs) self.connect(self.filewatcherThread, SIGNAL("file_change"), self.logFileChanged) self.filewatcherThread.start() self.versionCheckThread = amazon_s3.NotifyNewVersionThread() self.versionCheckThread.connect(self.versionCheckThread, SIGNAL("newer_version"), self.notifyNewerVersion) self.versionCheckThread.start() self.statisticsThread = MapStatisticsThread() self.connect(self.statisticsThread, SIGNAL("statistic_data_update"), self.updateStatisticsOnMap) self.statisticsThread.start() # statisticsThread is blocked until first call of requestStatistics self.voiceThread = VoiceOverThread() self.voiceThread.start() def setupMap(self, initialize=False): self.mapTimer.stop() self.filewatcherThread.paused = True logging.info("Finding map file") regionName = self.cache.getFromCache("region_name") if not regionName: regionName = "Providence" svg = None try: with open(resourcePath("vi/ui/res/mapdata/{0}.svg".format(regionName))) as svgFile: svg = svgFile.read() except Exception as e: pass try: self.dotlan = dotlan.Map(regionName, svg) except dotlan.DotlanException as e: logging.error(e) QMessageBox.critical(None, "Error getting map", six.text_type(e), "Quit") sys.exit(1) if self.dotlan.outdatedCacheError: e = self.dotlan.outdatedCacheError diagText = "Something went wrong getting map data. Proceeding with older cached data. " \ "Check for a newer version and inform the maintainer.\n\nError: {0} {1}".format(type(e), six.text_type(e)) logging.warn(diagText) QMessageBox.warning(None, "Using map from cache", diagText, "Ok") # Load the jumpbridges logging.critical("Load jump bridges") self.setJumpbridges(self.cache.getFromCache("jumpbridge_url")) self.systems = self.dotlan.systems logging.critical("Creating chat parser") self.chatparser = ChatParser(self.pathToLogs, self.roomnames, self.systems) # Menus - only once if initialize: logging.critical("Initializing contextual menus") # Add a contextual menu to the mapView def mapContextMenuEvent(event): # if QApplication.activeWindow() or QApplication.focusWidget(): self.mapView.contextMenu.exec_(self.mapToGlobal(QPoint(event.x(), event.y()))) self.mapView.contextMenuEvent = mapContextMenuEvent self.mapView.contextMenu = self.trayIcon.contextMenu() # Clicking links self.mapView.connect(self.mapView, SIGNAL("linkClicked(const QUrl&)"), self.mapLinkClicked) # Also set up our app menus if not regionName: self.providenceCatchRegionAction.setChecked(True) elif regionName.startswith("Providencecatch"): self.providenceCatchRegionAction.setChecked(True) elif regionName.startswith("Catch"): self.catchRegionAction.setChecked(True) elif regionName.startswith("Providence"): self.providenceRegionAction.setChecked(True) elif regionName.startswith("Querious"): self.queriousRegionAction.setChecked(True) else: self.chooseRegionAction.setChecked(True) self.jumpbridgesButton.setChecked(False) self.statisticsButton.setChecked(False) # Update the new map view, then clear old statistics from the map and request new logging.critical("Updating the map") self.updateMapView() self.setInitialMapPositionForRegion(regionName) self.mapTimer.start(MAP_UPDATE_INTERVAL_MSECS) # Allow the file watcher to run now that all else is set up self.filewatcherThread.paused = False logging.critical("Map setup complete") # def eventFilter(self, obj, event): # if event.type() == QtCore.QEvent.WindowDeactivate: # self.enableContextMenu(False) # return True # elif event.type() == QtCore.QEvent.WindowActivate: # self.enableContextMenu(True) # return True # return False def rescanIntel(self): logging.info("Intel ReScan begun") self.clearIntelChat() now = datetime.datetime.now() for file in os.listdir(self.pathToLogs): if file.endswith(".txt"): filePath = self.pathToLogs + six.text_type(os.sep) + file roomname = file[:-20] mtime = datetime.datetime.fromtimestamp(os.path.getmtime(filePath)) delta = (now - mtime) if delta.total_seconds() < (60 * 20) and delta.total_seconds() > 0: if roomname in self.roomnames: logging.info("Reading log {}".format(roomname)) self.logFileChanged(filePath, True) def startClipboardTimer(self): """ Start a timer to check the keyboard for changes and kos check them, first initializing the content so we dont kos check from random content """ self.oldClipboardContent = tuple(six.text_type(self.clipboard.text())) self.connect(self.clipboardTimer, SIGNAL("timeout()"), self.clipboardChanged) self.clipboardTimer.start(CLIPBOARD_CHECK_INTERVAL_MSECS) def stopClipboardTimer(self): if self.clipboardTimer: self.disconnect(self.clipboardTimer, SIGNAL("timeout()"), self.clipboardChanged) self.clipboardTimer.stop() def closeEvent(self, event): """ Persisting things to the cache before closing the window """ # Known playernames if self.knownPlayerNames: value = ",".join(self.knownPlayerNames) self.cache.putIntoCache("known_player_names", value, 60 * 60 * 24 * 30) # Program state to cache (to read it on next startup) settings = ((None, "restoreGeometry", str(self.saveGeometry())), (None, "restoreState", str(self.saveState())), ("splitter", "restoreGeometry", str(self.splitter.saveGeometry())), ("splitter", "restoreState", str(self.splitter.saveState())), ("mapView", "setZoomFactor", self.mapView.zoomFactor()), (None, "changeChatFontSize", ChatEntryWidget.TEXT_SIZE), (None, "changeOpacity", self.opacityGroup.checkedAction().opacity), (None, "changeAlwaysOnTop", self.alwaysOnTopAction.isChecked()), (None, "changeShowAvatars", self.showChatAvatarsAction.isChecked()), (None, "changeAlarmDistance", self.alarmDistance), (None, "changeSound", self.activateSoundAction.isChecked()), (None, "changeChatVisibility", self.showChatAction.isChecked()), (None, "loadInitialMapPositions", self.mapPositionsDict), (None, "setSoundVolume", SoundManager().soundVolume), (None, "changeFrameless", self.framelessWindowAction.isChecked()), (None, "changeUseSpokenNotifications", self.useSpokenNotificationsAction.isChecked()), (None, "changeKosCheckClipboard", self.kosClipboardActiveAction.isChecked()), (None, "changeAutoScanIntel", self.scanIntelForKosRequestsEnabled), (None, "changeAutoRescanIntel", self.autoRescanIntelEnabled)) self.cache.putIntoCache("settings", str(settings), 60 * 60 * 24 * 30) # Stop the threads try: SoundManager().quit() self.avatarFindThread.quit() self.avatarFindThread.wait() self.filewatcherThread.quit() self.filewatcherThread.wait() self.kosRequestThread.quit() self.kosRequestThread.wait() self.versionCheckThread.quit() self.versionCheckThread.wait() self.statisticsThread.quit() self.statisticsThread.wait() self.voiceThread.join() except Exception: pass self.trayIcon.hide() event.accept() def notifyNewerVersion(self, newestVersion): self.trayIcon.showMessage("Newer Version", ("An update is available for Spyglass.\n www.crypta.tech"), 1) def changeChatVisibility(self, newValue=None): if newValue is None: newValue = self.showChatAction.isChecked() self.showChatAction.setChecked(newValue) self.chatbox.setVisible(newValue) def changeKosCheckClipboard(self, newValue=None): if newValue is None: newValue = self.kosClipboardActiveAction.isChecked() self.kosClipboardActiveAction.setChecked(newValue) if newValue: self.startClipboardTimer() else: self.stopClipboardTimer() def changeAutoScanIntel(self, newValue=None): if newValue is None: newValue = self.autoScanIntelAction.isChecked() self.autoScanIntelAction.setChecked(newValue) self.autoRescanIntelEnabled = newValue def changeAutoRescanIntel(self, newValue=None): if newValue is None: newValue = self.autoRescanAction.isChecked() self.autoRescanAction.setChecked(newValue) self.autoRescanIntelEnabled = newValue def changeUseSpokenNotifications(self, newValue=None): if SoundManager().platformSupportsSpeech(): if newValue is None: newValue = self.useSpokenNotificationsAction.isChecked() self.useSpokenNotificationsAction.setChecked(newValue) SoundManager().setUseSpokenNotifications(newValue) else: self.useSpokenNotificationsAction.setChecked(False) self.useSpokenNotificationsAction.setEnabled(False) def changeOpacity(self, newValue=None): if newValue is not None: for action in self.opacityGroup.actions(): if action.opacity == newValue: action.setChecked(True) action = self.opacityGroup.checkedAction() self.setWindowOpacity(action.opacity) def changeTheme(self, newTheme=None): if newTheme is not None: for action in self.themeGroup.actions(): if action.theme == newTheme: action.setChecked(True) action = self.themeGroup.checkedAction() styles = Styles() styles.setStyle(action.theme) theme = styles.getStyle() self.setStyleSheet(theme) logging.critical("Setting new theme: {}".format(action.theme)) self.cache.putIntoCache("theme", action.theme, 60 * 60 * 24 * 365) self.setupMap() self.clearIntelChat() if self.autoRescanIntelEnabled: self.rescanIntel() def changeSound(self, newValue=None, disable=False): if disable: self.activateSoundAction.setChecked(False) self.activateSoundAction.setEnabled(False) self.soundSetupAction.setEnabled(False) # self.soundButton.setEnabled(False) QMessageBox.warning(None, "Sound disabled", "The lib 'pyglet' which is used to play sounds cannot be found, ""so the soundsystem is disabled.\nIf you want sound, please install the 'pyglet' library. This warning will not be shown again.", "OK") else: if newValue is None: newValue = self.activateSoundAction.isChecked() self.activateSoundAction.setChecked(newValue) SoundManager().soundActive = newValue def changeAlwaysOnTop(self, newValue=None): if newValue is None: newValue = self.alwaysOnTopAction.isChecked() self.hide() self.alwaysOnTopAction.setChecked(newValue) if newValue: self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) else: self.setWindowFlags(self.windowFlags() & (~QtCore.Qt.WindowStaysOnTopHint)) self.show() def changeFrameless(self, newValue=None): if newValue is None: newValue = not self.frameButton.isVisible() self.hide() if newValue: self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.changeAlwaysOnTop(True) else: self.setWindowFlags(self.windowFlags() & (~QtCore.Qt.FramelessWindowHint)) self.menubar.setVisible(not newValue) self.frameButton.setVisible(newValue) self.framelessWindowAction.setChecked(newValue) for cm in TrayContextMenu.instances: cm.framelessCheck.setChecked(newValue) self.show() def changeShowAvatars(self, newValue=None): if newValue is None: newValue = self.showChatAvatarsAction.isChecked() self.showChatAvatarsAction.setChecked(newValue) ChatEntryWidget.SHOW_AVATAR = newValue for entry in self.chatEntries: entry.avatarLabel.setVisible(newValue) def changeChatFontSize(self, newSize): if newSize: for entry in self.chatEntries: entry.changeFontSize(newSize) ChatEntryWidget.TEXT_SIZE = newSize def chatSmaller(self): newSize = ChatEntryWidget.TEXT_SIZE - 1 self.changeChatFontSize(newSize) def chatLarger(self): newSize = ChatEntryWidget.TEXT_SIZE + 1 self.changeChatFontSize(newSize) def changeAlarmDistance(self, distance): self.alarmDistance = distance for cm in TrayContextMenu.instances: for action in cm.distanceGroup.actions(): if action.alarmDistance == distance: action.setChecked(True) self.trayIcon.alarmDistance = distance def changeJumpbridgesVisibility(self): newValue = self.dotlan.changeJumpbridgesVisibility() self.jumpbridgesButton.setChecked(newValue) self.updateMapView() def changeStatisticsVisibility(self): newValue = self.dotlan.changeStatisticsVisibility() self.statisticsButton.setChecked(newValue) self.updateMapView() if newValue: self.statisticsThread.requestStatistics() def clipboardChanged(self, mode=0): if not (mode == 0 and self.kosClipboardActiveAction.isChecked() and self.clipboard.mimeData().hasText()): return content = six.text_type(self.clipboard.text()) contentTuple = tuple(content) # Limit redundant kos checks if contentTuple != self.oldClipboardContent: parts = tuple(content.split("\n")) knownPlayers = self.knownPlayerNames for part in parts: # Make sure user is in the content (this is a check of the local system in Eve). # also, special case for when you have no knonwnPlayers (initial use) if not knownPlayers or part in knownPlayers: self.trayIcon.setIcon(self.taskbarIconWorking) self.kosRequestThread.addRequest(parts, "clipboard", True) break self.oldClipboardContent = contentTuple def mapLinkClicked(self, url): systemName = six.text_type(url.path().split("/")[-1]).upper() system = self.systems[str(systemName)] sc = SystemChat(self, SystemChat.SYSTEM, system, self.chatEntries, self.knownPlayerNames) sc.connect(self, SIGNAL("chat_message_added"), sc.addChatEntry) sc.connect(self, SIGNAL("avatar_loaded"), sc.newAvatarAvailable) sc.connect(sc, SIGNAL("location_set"), self.setLocation) sc.show() def markSystemOnMap(self, systemname, timeA=time.time()): self.systems[six.text_type(systemname)].mark(timeA) self.updateMapView() def setLocation(self, char, newSystem): for system in self.systems.values(): system.removeLocatedCharacter(char) if not newSystem == "?" and newSystem in self.systems: self.systems[newSystem].addLocatedCharacter(char) self.setMapContent(self.dotlan.svg) def setMapContent(self, content): if self.initialMapPosition is None: scrollPosition = self.mapView.page().mainFrame().scrollPosition() else: scrollPosition = self.initialMapPosition self.mapView.setContent(content) self.mapView.page().mainFrame().setScrollPosition(scrollPosition) self.mapView.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) # Make sure we have positioned the window before we nil the initial position; # even though we set it, it may not take effect until the map is fully loaded scrollPosition = self.mapView.page().mainFrame().scrollPosition() if scrollPosition.x() or scrollPosition.y(): self.initialMapPosition = None def loadInitialMapPositions(self, newDictionary): self.mapPositionsDict = newDictionary def setInitialMapPositionForRegion(self, regionName): try: if not regionName: regionName = self.cache.getFromCache("region_name") if regionName: xy = self.mapPositionsDict[regionName] self.initialMapPosition = QPoint(xy[0], xy[1]) except Exception: pass def mapPositionChanged(self, dx, dy, rectToScroll): regionName = self.cache.getFromCache("region_name") if regionName: scrollPosition = self.mapView.page().mainFrame().scrollPosition() self.mapPositionsDict[regionName] = (scrollPosition.x(), scrollPosition.y()) def showChatroomChooser(self): chooser = ChatroomsChooser(self) chooser.connect(chooser, SIGNAL("rooms_changed"), self.changedRoomnames) chooser.show() def showJumbridgeChooser(self): url = self.cache.getFromCache("jumpbridge_url") chooser = JumpbridgeChooser(self, url) chooser.connect(chooser, SIGNAL("set_jumpbridge_url"), self.setJumpbridges) chooser.show() def setSoundVolume(self, value): SoundManager().setSoundVolume(value) def setJumpbridges(self, url): if url is None: url = "" try: data = [] if url != "": if url.startswith("http://") or url.startswith("https://"): resp = requests.get(url) for line in resp.iter_lines(decode_unicode=True): parts = line.strip().split() if len(parts) == 3: data.append(parts) else: content = None with open(url, 'r') as f: content = f.readlines() for line in content: parts = line.strip().split() if len(parts) == 3: data.append(parts) else: data = amazon_s3.getJumpbridgeData(self.dotlan.region.lower()) self.dotlan.setJumpbridges(data) self.cache.putIntoCache("jumpbridge_url", url, 60 * 60 * 24 * 365 * 8) except Exception as e: QMessageBox.warning(None, "Loading jumpbridges failed!", "Error: {0}".format(six.text_type(e)), "OK") def handleRegionMenuItemSelected(self, menuAction=None): self.catchRegionAction.setChecked(False) self.providenceRegionAction.setChecked(False) self.queriousRegionAction.setChecked(False) self.providenceCatchRegionAction.setChecked(False) self.providenceCatchCompactRegionAction.setChecked(False) self.chooseRegionAction.setChecked(False) if menuAction: menuAction.setChecked(True) regionName = six.text_type(menuAction.property("regionName").toString()) regionName = dotlan.convertRegionName(regionName) Cache().putIntoCache("region_name", regionName, 60 * 60 * 24 * 365) self.setupMap() def showRegionChooser(self): def handleRegionChosen(): self.handleRegionMenuItemSelected(None) self.chooseRegionAction.setChecked(True) self.setupMap() self.chooseRegionAction.setChecked(False) chooser = RegionChooser(self) self.connect(chooser, SIGNAL("new_region_chosen"), handleRegionChosen) chooser.show() def addMessageToIntelChat(self, message, timeA=time.time()): scrollToBottom = False if (self.chatListWidget.verticalScrollBar().value() == self.chatListWidget.verticalScrollBar().maximum()): scrollToBottom = True chatEntryWidget = ChatEntryWidget(message) listWidgetItem = QtGui.QListWidgetItem(self.chatListWidget) listWidgetItem.setSizeHint(chatEntryWidget.sizeHint()) self.chatListWidget.addItem(listWidgetItem) self.chatListWidget.setItemWidget(listWidgetItem, chatEntryWidget) self.avatarFindThread.addChatEntry(chatEntryWidget) self.chatEntries.append(chatEntryWidget) self.connect(chatEntryWidget, SIGNAL("mark_system"), self.markSystemOnMap) self.emit(SIGNAL("chat_message_added"), chatEntryWidget, timeA) self.pruneMessages() if scrollToBottom: self.chatListWidget.scrollToBottom() def clearIntelChat(self): logging.info("Clearing Intel") self.setupMap() try: for row in range(self.chatListWidget.count()): item = self.chatListWidget.item(0) entry = self.chatListWidget.itemWidget(item) message = entry.message self.chatEntries.remove(entry) self.chatListWidget.takeItem(0) for widgetInMessage in message.widgets: widgetInMessage.removeItemWidget(item) except Exception as e: logging.error(e) def pruneMessages(self): try: now = time.mktime(evegate.currentEveTime().timetuple()) for row in range(self.chatListWidget.count()): chatListWidgetItem = self.chatListWidget.item(0) chatEntryWidget = self.chatListWidget.itemWidget(chatListWidgetItem) message = chatEntryWidget.message if now - time.mktime(message.timestamp.timetuple()) > MESSAGE_EXPIRY_SECS: self.chatEntries.remove(chatEntryWidget) self.chatListWidget.takeItem(0) for widgetInMessage in message.widgets: widgetInMessage.removeItemWidget(chatListWidgetItem) else: break except Exception as e: logging.error(e) def showKosResult(self, state, text, requestType, hasKos): if not self.scanIntelForKosRequestsEnabled: return try: if hasKos: SoundManager().playSound("kos", text) if state == "ok": if requestType == "xxx": # An xxx request out of the chat self.trayIcon.showMessage("Player KOS-Check", text, 1) elif requestType == "clipboard": # request from clipboard-change if len(text) <= 0: text = "None KOS" self.trayIcon.showMessage("Your KOS-Check", text, 1) text = text.replace("\n\n", "<br>") message = chatparser.chatparser.Message("Spyglass KOS-Check", text, evegate.currentEveTime(), "Spyglass", [], states.NOT_CHANGE, text.upper(), text) self.addMessageToIntelChat(message) elif state == "error": self.trayIcon.showMessage("KOS Failure", text, 3) except Exception: pass self.trayIcon.setIcon(self.taskbarIconQuiescent) def changedRoomnames(self, newRoomnames): self.cache.putIntoCache("room_names", u",".join(newRoomnames), 60 * 60 * 24 * 365 * 5) self.chatparser.rooms = newRoomnames def showInfo(self): infoDialog = QtGui.QDialog(self) uic.loadUi(resourcePath("vi/ui/Info.ui"), infoDialog) infoDialog.versionLabel.setText(u"Version: {0}".format(vi.version.VERSION)) infoDialog.logoLabel.setPixmap(QtGui.QPixmap(resourcePath("vi/ui/res/logo.png"))) infoDialog.connect(infoDialog.closeButton, SIGNAL("clicked()"), infoDialog.accept) infoDialog.show() def showSoundSetup(self): dialog = QtGui.QDialog(self) uic.loadUi(resourcePath("vi/ui/SoundSetup.ui"), dialog) dialog.volumeSlider.setValue(SoundManager().soundVolume) dialog.connect(dialog.volumeSlider, SIGNAL("valueChanged(int)"), SoundManager().setSoundVolume) dialog.connect(dialog.testSoundButton, SIGNAL("clicked()"), SoundManager().playSound) dialog.connect(dialog.closeButton, SIGNAL("clicked()"), dialog.accept) dialog.show() def systemTrayActivated(self, reason): if reason == QtGui.QSystemTrayIcon.Trigger: if self.isMinimized(): self.showNormal() self.activateWindow() elif not self.isActiveWindow(): self.activateWindow() else: self.showMinimized() def updateAvatarOnChatEntry(self, chatEntry, avatarData): updated = chatEntry.updateAvatar(avatarData) if not updated: self.avatarFindThread.addChatEntry(chatEntry, clearCache=True) else: self.emit(SIGNAL("avatar_loaded"), chatEntry.message.user, avatarData) def updateStatisticsOnMap(self, data): if not self.statisticsButton.isChecked(): return if data["result"] == "ok": self.dotlan.addSystemStatistics(data["statistics"]) elif data["result"] == "error": text = data["text"] self.trayIcon.showMessage("Loading statstics failed", text, 3) logging.error("updateStatisticsOnMap, error: %s" % text) def updateMapView(self): logging.debug("Updating map start") self.setMapContent(self.dotlan.svg) logging.debug("Updating map complete") def zoomMapIn(self): self.mapView.setZoomFactor(self.mapView.zoomFactor() + 0.1) def zoomMapOut(self): self.mapView.setZoomFactor(self.mapView.zoomFactor() - 0.1) def logFileChanged(self, path, rescan=False): messages = self.chatparser.fileModified(path, rescan) for message in messages: # If players location has changed if message.status == states.LOCATION: self.knownPlayerNames.add(message.user) self.setLocation(message.user, message.systems[0]) elif message.status == states.KOS_STATUS_REQUEST: # Do not accept KOS requests from any but monitored intel channels # as we don't want to encourage the use of xxx in those channels. if not message.room in self.roomnames: text = message.message[4:] text = text.replace(" ", ",") parts = (name.strip() for name in text.split(",")) self.trayIcon.setIcon(self.taskbarIconWorking) self.kosRequestThread.addRequest(parts, "xxx", False) # Otherwise consider it a 'normal' chat message elif message.user not in ("EVE-System", "EVE System") and message.status != states.IGNORE: self.addMessageToIntelChat(message, message.timestamp) # For each system that was mentioned in the message, check for alarm distance to the current system # and alarm if within alarm distance. systemList = self.dotlan.systems if message.systems: for system in message.systems: systemname = system.name systemList[systemname].setStatus(message.status) if message.status in ( states.REQUEST, states.ALARM) and message.user not in self.knownPlayerNames: alarmDistance = self.alarmDistance if message.status == states.ALARM else 0 for nSystem, data in system.getNeighbours(alarmDistance).items(): distance = data["distance"] chars = nSystem.getLocatedCharacters() if len(chars) > 0 and message.user not in chars: self.trayIcon.showNotification(message, system.name, ", ".join(chars), distance) self.setMapContent(self.dotlan.svg)