diff --git a/.gitignore b/.gitignore index 7e07ab4..844b308 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,10 @@ .idea/workspace.xml .vs/* *.pyproj +*.exe +src/dist/* +src/build/* +*.log +avbin64.dll +avbin32.dll +avbin.dll diff --git a/README.md b/README.md index db79e05..4de6d70 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,16 @@ Visual intel chat analysis, planning and notification application for [EVE Online](http://www.eveonline.com). Gathers status through in-game intelligence channels on all known hostiles and presents all the data on a [dotlan](http://evemaps.dotlan.net/map/Cache#npc24) generated regional map. The map is annotated in real-time as players report intel in monitored chat channels. -Vintel is written with Python 2.7, using PyQt4 for the application presentation layer, BeautifulSoup4 for SVG parsing, and Pyglet for audio playback. +Vintel is written with Python 3.6, using PyQt5 for the application presentation layer, BeautifulSoup4 for SVG parsing, and Pyglet+pyttsx3 for audio playback. ### News -_The current release version of Vintel [can be found here](https://github.com/Xanthos-Eve/vintel/releases). Both Mac and Windows distributions are now available for download with this release._ +_The current release version of Vintel [can be found here](https://github.com/bperian/vintel/releases). Windows distributions are now available for download with this release._ -Keep up on the latest at the [wiki](https://github.com/Xanthos-Eve/vintel/wiki) or visit our [issues](https://github.com/Xanthos-Eve/vintel/issues) page to see what bugs and features are in the queue. +Keep up on the latest at the [wiki](https://github.com/bperian/vintel/wiki) or visit our [issues](https://github.com/bperian/vintel/issues) page to see what bugs and features are in the queue. ## Screenshot -![](https://github.com/Xanthos-Eve/vintel/blob/master/src/docs/screenshot.png) +![](https://github.com/bperian/vintel/blob/master/src/docs/screenshot.png) ## Features @@ -55,47 +55,29 @@ To use this feature: click on a pilot in the local pilot list and then type the ## Running Vintel from Source -To run or build from the source you need the following packages installed on your machine. Most, if not all, can be installed from the command line using package management software such as "pip". Mac and Linux both come with pip installed, Windows users may need to install [cygwin](https://www.cygwin.com) to get pip. Of course all the requirements also have downoad links. - -The packages required are: -- Python 2.7.x -https://www.python.org/downloads/ -Vintel is not compatible with Python 3! -- PyQt4x -http://www.riverbankcomputing.com/software/pyqt/download -Please use the PyQt Binary Package for Py2.7 -Vintel is not compatible with PyQt5! -- BeautifulSoup 4 -https://pypi.python.org/pypi/beautifulsoup4 -- Pyglet 1.2.4 (for python 2.7) -https://bitbucket.org/pyglet/pyglet/wiki/Download -pyglet is used to play the sound – If it is not available the sound option will be disabled. -- Requests 2 -https://pypi.python.org/pypi/requests -- Six for python 3 compatibility https://pypi.python.org/pypi/six +- The packages requirements are defined in `setup.py` file +- Pyglet may require https://github.com/AVbin/AVbin installed; this can be easily confirmed by checking the `vintel.log` file for errors regarding `avbin` not loaded. + ## Building the Vintel Standalone Package - - The standalone is created using pyinstaller. All media files and the .spec-file with the configuration for pyinstaller are included in the source repo. Pyinstaller can be found here: https://github.com/pyinstaller/pyinstaller/wiki. - - Edit the .spec file to match your src path in the "a = Analysis" section and execute "pyinstaller vintel.spec vintel.py". If everything went correctly you should get a dist folder that contains the standalone executable. + - The standalone is created using pyinstaller. All media files and the setup.py file with the configuration for pyinstaller are included in the source repo. + pyinstaller can be installed through pip like this `pip install pyinstaller` + - execute "pyinstaller vintel.spec vintel.py". If everything went correctly you should get a dist folder that contains the standalone executable + required files. ## FAQ -**License?** +**License** Vintel is licensed under the [GPLv3](http://www.gnu.org/licenses/gpl-3.0.html). -**Vintel does not play sounds - is there a remedy for this?** - -The most likely cause of this is that pyglet is not installed. - **A litte bit to big for such a little tool.** -The .exe ships with the complete environment and needed libs. You could save some space using the the source code instead. +- The .exe ships with the complete environment and needed libs. You could save some space using the the source code instead. **What platforms are supported?** -Vintel runs on Mac (OS X), Windows and Linux. Mac and Windows standalone packages are provided with each release. Linux users are advised to install all the requirements listed above then download and run from source. +Vintel runs on Mac (OS X), Windows and Linux. Windows standalone packages are provided with each release. Linux and Mac users are advised to install all the requirements listed above then download and run from source. **What file system permissions does Vintel need?** @@ -110,31 +92,23 @@ Vintel looks for a new version at startup and loads dynamic infomation (i.e., ju **Vintel does not find my chatlogs or is not showing changes to chat when it should. What can I do?** -Vintel looks for your chat logs in ~\EVE\logs\chatlogs and ~\DOCUMENTS\EVE\logs\chatlogs. Logging must be enabled in the EVE client options. You can set this path on your own by giving it to Vintel at startup. For this you have to start it on the command line and call the program with the path to the logs. +Vintel looks for your chat logs in ~\EVE\logs\chatlogs and ~\DOCUMENTS\EVE\logs\chatlogs , game logs in ~\EVE\logs\gamelogs and ~\DOCUMENTS\EVE\logs\gamelogs +Logging must be enabled in the EVE client options. You can set this path on your own by giving it to Vintel at startup. For this you have to start it on the command line and call the program with the path to the logs. Examples: -`win> vintel-1.0.exe "d:\strange\path\EVE\logs\chatlogs"` +`win> vintel.exe "d:\strange\path\EVE\logs\chatlogs" "d:\strange\path\EVE\logs\gamelogs"` – or – -`linux and mac> python vintel.py "/home/user/myverypecialpath/EVE/logs/chatlogs"` - -**Vintel does not start! What can I do?** +`linux and mac> python vintel.py "/home/user/myverypecialpath/EVE/logs/chatlogs" "/home/user/myverypecialpath/EVE/logs/gamelogs"` -Please try to delete Vintel's Cache. It is located in the EVE-directory where the chatlogs are in. If your chatlogs are in \Documents\EVE\logs\chatlogs Vintel writes the cachte to \Documents\EVE\vintel **Vintel takes many seconds to start up; what are some of the causes and what can I do about it?** Vintel asks the operating system to notifiy when a change has been made to the ChatLogs directory - this will happen when a new log is created or an existing one is updated. In response to this notification, Vintel examines all of the files in the directory to analysze the changes. If you have a lot of chat logs this can make Vintel slow to scan for file changes. Try perodically moving all the chatlogs out of the ChatLogs directory (zip them up and save them somewhere else if you think you may need them some day). -**Vintel complains about missing dll files on Windows at app launch, is there a workaround for this?** - -Yes there is! There is a bit of a mix up going on with the latest pyinstaller and the Microsoft developer dlls. Here is a link to help illuminate the issue https://github.com/pyinstaller/pyinstaller/issues/1974 - -You can visit Microsoft's web site to download the developer dlls https://www.microsoft.com/en-in/download/details.aspx?id=5555. -You can also read a more technical treatment of the issue here http://www.tomshardware.com/answers/id-2417960/msvcr100-dll-32bit-64bit.html **How can I resolve the "empty certificate data" error?** @@ -142,12 +116,13 @@ Do not use the standalone EXE, install the environment and use the sourcecode di **Vintel is misbehaving and I dont know why - how can I easily help diagnose problems with Vintel** -Vintel writes its own set of logs to the \Documents\EVE\vintel\vintel directory. A new log is created as the old one fills up to its maximum size setting. Each entry inside the log file is time-stamped. These logs are emitted in real-time so you can watch the changes to the file as you use the app. +Vintel writes its own set of logs to the \Documents\EVE\vintel\vintel directory and in the application directory vintel.log . A new log is created as the old one fills up to its maximum size setting. Each entry inside the log file is time-stamped. These logs are emitted in real-time so you can watch the changes to the file as you use the app. **I love Vintel - how can I help?** -If you are technically inclined and have a solid grasp of Python, [contact the project maintainer via email](mailto:xanthos.eve@gmail.com) to see how you can best help out. Alternatively you can find something you want to change and create a pull request to have your changes reviewed and potentially added to the codebase. There have been several great contributions made this way! +If you are technically inclined and have a solid grasp of Python, [contact the project maintainer via email](mailto:bperian@faxtorial.com) to see how you can best help out. Alternatively you can find something you want to change and create a pull request to have your changes reviewed and potentially added to the codebase. There have been several great contributions made this way! **I'm not a coder, how can I help?** -Your feedback is needed! Use the program for a while, then come back [here and create issues](https://github.com/Xanthos-Eve/vintel/issues). Record anything you think about Vintel - bugs, frustrations, and ideas to make it better. +Your feedback is needed! Use the program for a while, then come back [here and create issues](https://github.com/bperian/vintel/issues). Record anything you think about Vintel - bugs, frustrations, and ideas to make it better. +ISK donations to [Blitz Arkaral](https://zkillboard.com/character/95517727/) are of course welcome. \ No newline at end of file diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 0000000..33a81b4 --- /dev/null +++ b/src/setup.py @@ -0,0 +1,40 @@ +from setuptools import find_packages, setup, Executable + + + + +import sys +base = None + +if sys.platform == "win32": + base = "Win32GUI" + +import os.path +PYTHON_INSTALL_DIR = os.path.dirname(os.path.dirname(os.__file__)) + + + +executables = [Executable("vintel.py", base=base,icon="icon.ico")] + +packages = ["appdirs","packaging.version","packaging.specifiers","packaging","pyglet","pyqt5","pyttsx3"] + +package_data = {'ui' : ['vi/ui/*']} + +options = { + 'build_exe': { + 'packages':packages, + 'optimize':2, + + }, +} + +setup( + name = "VINTEL", + options = options, + version = "1.2.4", + description = 'Intel chat analyzer', + executables = executables, + include_package_data=True, + package_data = {'' : ['*.ui','*.png','*.svg','*.wav']}, + +) \ No newline at end of file diff --git a/src/tools/addmessage.py b/src/tools/addmessage.py deleted file mode 100644 index 58f9f10..0000000 --- a/src/tools/addmessage.py +++ /dev/null @@ -1,24 +0,0 @@ -import sys -import six -if six.PY2: - from io import open - -PATH = "/home/sparrow/EVE/logs/Chatlogs/TheCitadel_20401229_065150.txt" - -def main(): - line = "" - with open(PATH, "r", encoding="utf-16") as f: - content = f.read() - lines = content.split("\n") - line = lines[-2].strip() - line = line[:line.find(">")+1] - line = line + " " + sys.argv[1] + "\n" - with open(PATH, "a") as f: - f.write(line.encode("utf-16")) - - - -if __name__ == "__main__": - main() - - diff --git a/src/vi/PanningWebView.py b/src/vi/PanningWebView.py index cba19e0..fc4cafa 100644 --- a/src/vi/PanningWebView.py +++ b/src/vi/PanningWebView.py @@ -1,12 +1,22 @@ - -from PyQt4.QtWebKit import QWebView -from PyQt4.QtGui import * -from PyQt4 import QtCore -from PyQt4.QtCore import QPoint -from PyQt4.QtCore import QString -from PyQt4.QtCore import QEvent - -class PanningWebView(QWebView): +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage +from PyQt5.QtCore import QPoint,pyqtSignal, QUrl, QEvent + +class WebEnginePage(QWebEnginePage): + mapLinkClicked = pyqtSignal(QUrl) + def acceptNavigationRequest(self, url, type, isMainFrame): + if type == QWebEnginePage.NavigationTypeLinkClicked: + self.mapLinkClicked.emit(url) + return False + return True + def javaScriptConsoleMessage(self, level, msg, line, source): + # supress Javascript warnings and uncaught ReferenceError exceptions for JS functions + pass + +class PanningWebView(QWebEngineView): + + def setPage(self, newPage): + super(PanningWebView, self).setPage(newPage) + self.widget = self.page() def __init__(self, parent=None): super(PanningWebView, self).__init__() @@ -17,7 +27,7 @@ def __init__(self, parent=None): self.offset = 0 self.handIsClosed = False self.clickedInScrollBar = False - + self.setPage(WebEnginePage(self)) def mousePressEvent(self, mouseEvent): pos = mouseEvent.pos() @@ -27,7 +37,7 @@ def mousePressEvent(self, mouseEvent): else: if self.ignored.count(mouseEvent): self.ignored.remove(mouseEvent) - return QWebView.mousePressEvent(self, mouseEvent) + return QWebEngineView.mousePressEvent(self, mouseEvent) if not self.pressed and not self.scrolling and mouseEvent.modifiers() == QtCore.Qt.NoModifier: if mouseEvent.buttons() == QtCore.Qt.LeftButton: @@ -43,7 +53,7 @@ def mousePressEvent(self, mouseEvent): self.offset = QPoint(xTuple[0], yTuple[0]) return - return QWebView.mousePressEvent(self, mouseEvent) + return QWebEngineView.mousePressEvent(self, mouseEvent) def mouseReleaseEvent(self, mouseEvent): @@ -52,7 +62,7 @@ def mouseReleaseEvent(self, mouseEvent): else: if self.ignored.count(mouseEvent): self.ignored.remove(mouseEvent) - return QWebView.mousePressEvent(self, mouseEvent) + return QWebEngineView.mousePressEvent(self, mouseEvent) if self.scrolling: self.pressed = False @@ -74,7 +84,7 @@ def mouseReleaseEvent(self, mouseEvent): QApplication.postEvent(self, event1) QApplication.postEvent(self, event2) return - return QWebView.mouseReleaseEvent(self, mouseEvent) + return QWebEngineView.mouseReleaseEvent(self, mouseEvent) def mouseMoveEvent(self, mouseEvent): @@ -94,7 +104,7 @@ def mouseMoveEvent(self, mouseEvent): self.pressed = False self.scrolling = True return - return QWebView.mouseMoveEvent(self, mouseEvent) + return QWebEngineView.mouseMoveEvent(self, mouseEvent) def pointInScroller(self, position, orientation): diff --git a/src/vi/amazon_s3.py b/src/vi/amazon_s3.py index 560c84d..55daa34 100644 --- a/src/vi/amazon_s3.py +++ b/src/vi/amazon_s3.py @@ -21,8 +21,8 @@ import requests import logging -from PyQt4 import Qt -from PyQt4.QtCore import QThread, SIGNAL +from PyQt5 import Qt +from PyQt5.QtCore import QThread, pyqtSignal from vi import version from vi.cache.cache import Cache from distutils.version import LooseVersion, StrictVersion @@ -62,6 +62,9 @@ def getNewestVersion(): class NotifyNewVersionThread(QThread): + + newer_version = pyqtSignal() + def __init__(self): QThread.__init__(self) self.alerted = False diff --git a/src/vi/cache/cache.py b/src/vi/cache/cache.py index 3d0837b..96f0b31 100644 --- a/src/vi/cache/cache.py +++ b/src/vi/cache/cache.py @@ -21,6 +21,8 @@ import threading import time import six +import PyQt5 + if six.PY2: def to_blob(x): return buffer(str(x)) diff --git a/src/vi/chatparser/chatparser.py b/src/vi/chatparser/chatparser.py index 8d7ac21..7c8cdd0 100644 --- a/src/vi/chatparser/chatparser.py +++ b/src/vi/chatparser/chatparser.py @@ -21,12 +21,14 @@ import os import time import six +import logging + if six.PY2: from io import open from bs4 import BeautifulSoup from vi import states -from PyQt4.QtGui import QMessageBox +from PyQt5.QtWidgets import QMessageBox from .parser_functions import parseStatus @@ -40,7 +42,7 @@ class ChatParser(object): """ ChatParser will analyze every new line that was found inside the Chatlogs. """ - def __init__(self, path, rooms, systems): + def __init__(self, path, rooms, systems, logging): """ path = the path with the logs rooms = the rooms to parse""" self.path = path # the path with the chatlog @@ -50,6 +52,7 @@ def __init__(self, path, rooms, systems): self.knownMessages = [] # message we allready analyzed self.locations = {} # informations about the location of a char self.ignoredPaths = [] + self.logging = logging self._collectInitFileData(path) def _collectInitFileData(self, path): @@ -58,50 +61,57 @@ def _collectInitFileData(self, path): for filename in os.listdir(path): fullPath = os.path.join(path, filename) fileTime = os.path.getmtime(fullPath) + encoding = 'utf-16-le' + if len(filename)<20: + encoding = 'utf-8' if currentTime - fileTime < maxDiff: - self.addFile(fullPath) + self.addFile(fullPath,encoding) - def addFile(self, path): + def addFile(self, path, encod): lines = None content = "" filename = os.path.basename(path) roomname = filename[:-20] try: - with open(path, "r", encoding='utf-16-le') as f: + with open(path, "r", encoding=encod, errors='ignore') as f: content = f.read() except Exception as e: self.ignoredPaths.append(path) - QMessageBox.warning(None, "Read a log file failed!", "File: {0} - problem: {1}".format(path, six.text_type(e)), "OK") + QMessageBox.warning(None, "Read a log file failed!", "File: {0} - problem: {1}".format(path, six.text_type(e)), QMessageBox.Ok) return None - + charname = None + try: + charname = self.fileData[path]["charname"] + except: + pass lines = content.split("\n") - if path not in self.fileData or (roomname in LOCAL_NAMES and "charname" not in self.fileData.get(path, [])): + if path not in self.fileData or "charname" not in self.fileData.get(path, []): self.fileData[path] = {} - if roomname in LOCAL_NAMES: - charname = None - sessionStart = None - # for local-chats we need more infos - for line in lines: - if "Listener:" in line: - charname = line[line.find(":") + 1:].strip() - elif "Session started:" in line: - sessionStr = line[line.find(":") + 1:].strip() - sessionStart = datetime.datetime.strptime(sessionStr, "%Y.%m.%d %H:%M:%S") - if charname and sessionStart: - self.fileData[path]["charname"] = charname - self.fileData[path]["sessionstart"] = sessionStart - break + sessionStart = None + # for local-chats we need more infos + for line in lines: + if "listener:" in line.lower(): + charname = line[line.find(":") + 1:].strip() + elif "session started:" in line.lower(): + sessionStr = line[line.find(":") + 1:].strip() + sessionStart = datetime.datetime.strptime(sessionStr, "%Y.%m.%d %H:%M:%S") + if charname and sessionStart: + self.fileData[path]["charname"] = charname + self.fileData[path]["sessionstart"] = sessionStart + break self.fileData[path]["lines"] = len(lines) return lines def _lineToMessage(self, line, roomname): # finding the timestamp + line = line.decode('utf-8') timeStart = line.find("[") + 1 timeEnds = line.find("]") timeStr = line[timeStart:timeEnds].strip() try: timestamp = datetime.datetime.strptime(timeStr, "%Y.%m.%d %H:%M:%S") - except ValueError: + except ValueError as e: + logging.critical(e) return None # finding the username of the poster userEnds = line.find(">") @@ -168,15 +178,22 @@ def _parseLocal(self, path, line): self.locations[charname] = {"system": "?", "timestamp": datetime.datetime(1970, 1, 1, 0, 0, 0, 0)} # Finding the timestamp - timeStart = line.find("[") + 1 - timeEnds = line.find("]") + timeStart = line.decode().find("[") + 1 + timeEnds = line.decode().find("]") timeStr = line[timeStart:timeEnds].strip() - timestamp = datetime.datetime.strptime(timeStr, "%Y.%m.%d %H:%M:%S") - + timestamp = None + try: + timestamp = datetime.datetime.strptime(timeStr.decode(), "%Y.%m.%d %H:%M:%S") + except Exception as e: + logging.critical(e) + return # Finding the username of the poster - userEnds = line.find(">") + userEnds = line.decode().find(">") + if userEnds == -1: + userEnds = len(line)-1 username = line[timeEnds + 1:userEnds].strip() + status = states.IGNORE # Finding the pure message text = line[userEnds + 1:].strip() # text will the text to work an if username in ("EVE-System", "EVE System"): @@ -191,6 +208,18 @@ def _parseLocal(self, path, line): self.locations[charname]["system"] = system self.locations[charname]["timestamp"] = timestamp message = Message("", "", timestamp, charname, [system, ], "", "", status) + + # Solving new game logs user location where case + if message == [] and status != states.LOCATION: + text = line[timeEnds:].decode().strip().replace("*", "").lower() + if "(none)" in text and "jumping" in text and "to" in text: + system = text.split("to")[1].strip().upper() + status = states.LOCATION + if self.locations[charname]["timestamp"] is None or timestamp > self.locations[charname]["timestamp"]: + self.locations[charname]["system"] = system + self.locations[charname]["timestamp"] = timestamp + message = Message("", "", timestamp, charname, [system, ], "", "", status) + return message def fileModified(self, path): @@ -200,26 +229,52 @@ def fileModified(self, path): # Checking if we must do anything with the changed file. # We only need those which name is in the rooms-list # EvE names the file like room_20140913_200737.txt, so we don't need - # the last 20 chars + # the last 20 chars. if file name is under 20 char it's most likely a game log filename = os.path.basename(path) - roomname = filename[:-20] - if path not in self.fileData: - # seems eve created a new file. New Files have 12 lines header - self.fileData[path] = {"lines": 13} - oldLength = self.fileData[path]["lines"] - lines = self.addFile(path) - if path in self.ignoredPaths: - return [] - for line in lines[oldLength - 1:]: - line = line.strip() - if len(line) > 2: - message = None - if roomname in LOCAL_NAMES: + + if len(filename) > 20: + roomname = str(filename[:-20]) + if roomname.find('[') > -1: + roomname = roomname[0:roomname.find('[')-1] + + if path not in self.fileData: + # seems eve created a new file. New Files have 12 lines header + self.fileData[path] = {"lines": 13} + oldLength = self.fileData[path]["lines"] + lines = self.addFile(path,'utf-16-le') + + if path in self.ignoredPaths: + return [] + + for line in lines[oldLength - 1:]: + line = line.strip() + line = line.encode('utf-8', 'ignore') + if len(line) > 2: + message = None + if roomname == "Local": + message = self._parseLocal(path, line) + else: + message = self._lineToMessage(line, roomname) + if message: + messages.append(message) + else: + # Game log parsing + if path not in self.fileData: + self.fileData[path] = { "lines": 6} # Game logs have 6 lines header so we'll skip that + oldLength = self.fileData[path]["lines"] + lines = self.addFile(path,'utf-8') + + if path in self.ignoredPaths: + return [] + for line in lines[oldLength - 1:]: + line = line.strip() + line = line.encode('ascii','ignore') + if len(line) > 2: + message = None + message = self._parseLocal(path, line) - else: - message = self._lineToMessage(line, roomname) - if message: - messages.append(message) + if message: + messages.append(message) return messages diff --git a/src/vi/dotlan.py b/src/vi/dotlan.py index 182b7bd..15c1d85 100644 --- a/src/vi/dotlan.py +++ b/src/vi/dotlan.py @@ -88,7 +88,7 @@ def __init__(self, region, svgFile=None): "temporary problem (like dotlan is not reachable), or " \ "everythig went to hell. Sorry. This makes no sense " \ "without the map.\n\nRemember the site for possible " \ - "updates: https://github.com/Xanthos-Eve/vintel".format(type(e), six.text_type(e)) + "updates: https://github.com/bperian/vintel".format(type(e), six.text_type(e)) raise DotlanException(t) # Create soup from the svg self.soup = BeautifulSoup(svg, 'html.parser') @@ -119,6 +119,15 @@ def _extractSystemsFromSoup(self, soup): for element in symbol.select(".sys"): name = element.select("text")[0].text.strip().upper() mapCoordinates = {} + sov = {} + sovData = element.select("text")[1].text.strip().upper() + try: + sov["name"] = sovData[0:(sovData.index('(')-1)] + sov["sov_level"] = sovData[sovData.index('(')+1:len(sovData)-1] + except: + # If system SOV is NPC index isn't set + sov["name"] = sovData + for keyname in ("x", "y", "width", "height"): mapCoordinates[keyname] = float(uses[symbolId][keyname]) mapCoordinates["center_x"] = (mapCoordinates["x"] + (mapCoordinates["width"] / 2)) @@ -127,8 +136,9 @@ def _extractSystemsFromSoup(self, soup): transform = uses[symbolId]["transform"] except KeyError: transform = "translate(0,0)" - systems[name] = System(name, element, self.soup, mapCoordinates, transform, systemId) + systems[name] = System(name, element, self.soup, mapCoordinates, transform, systemId, sov) return systems + def _prepareSvg(self, soup, systems): svg = soup.select("svg")[0] @@ -267,19 +277,8 @@ def changeJumpbridgesVisibility(self): for line in self.soup.select(".jumpbridge"): line["visibility"] = value self._jumpMapsVisible = newStatus - # self.debugWriteSoup() return newStatus - def debugWriteSoup(self): - svgData = self.soup.prettify("utf-8") - try: - with open("/Users/mark/Desktop/output.svg", "wb") as svgFile: - svgFile.write(svgData) - svgFile.close() - except Exception as e: - logging.error(e) - - class System(object): """ A System on the Map @@ -291,7 +290,7 @@ class System(object): UNKNOWN_COLOR = "#FFFFFF" CLEAR_COLOR = "#59FF6C" - def __init__(self, name, svgElement, mapSoup, mapCoordinates, transform, systemId): + def __init__(self, name, svgElement, mapSoup, mapCoordinates, transform, systemId, sovData): self.status = states.UNKNOWN self.name = name self.svgElement = svgElement @@ -301,6 +300,8 @@ def __init__(self, name, svgElement, mapSoup, mapCoordinates, transform, systemI self.secondLine = svgElement.select("text")[1] self.lastAlarmTime = 0 self.messages = [] + self.sovData = sovData + self.sovShown = False self.setStatus(states.UNKNOWN) self.__locatedCharacters = [] self.backgroundColor = "#FFFFFF" @@ -440,8 +441,15 @@ def setStatus(self, newStatus): self.secondLine["style"] = "fill: #000000;" elif newStatus == states.UNKNOWN: self.setBackgroundColor(self.UNKNOWN_COLOR) - # second line in the rects is reserved for the clock - self.secondLine.string = "?" + # second line in the rects is reserved for the clock and SOV + if self.sovShown is False: + self.SovShown = True + self.secondLine.string = self.sovData["name"] + try: + self.secondLine.string += " (" + self.sovData["sov_level"] + ")" + except KeyError: + pass # System is NPC owned + self.secondLine["style"] = "fill: #000000;" if newStatus not in (states.NOT_CHANGE, states.REQUEST): # unknown not affect system status self.status = newStatus @@ -473,15 +481,26 @@ def update(self): seconds = int(diff - minutes * 60) string = "{m:02d}:{s:02d}".format(m=minutes, s=seconds) if self.status == states.CLEAR: + secondsUntilWhite = 10 * 60 calcValue = int(diff / (secondsUntilWhite / 255.0)) if calcValue > 255: calcValue = 255 self.secondLine["style"] = "fill: #008100;" - string = "clr: {m:02d}:{s:02d}".format(m=minutes, s=seconds) - self.setBackgroundColor("rgb({r},{g},{b})".format(r=calcValue, g=255, b=calcValue)) - self.secondLine.string = string - + if minutes > 30 and self.sovShown is False: + self.SovShown = True + self.secondLine.string = self.sovData["name"] + try: + self.secondLine.string += " (" + self.sovData["sov_level"] + ")" + except KeyError: + pass # System is NPC owned + else: + self.sovShown = False + string = "clr: {m:02d}:{s:02d}".format(m=minutes, s=seconds) + self.setBackgroundColor("rgb({r},{g},{b})".format(r=calcValue, g=255, b=calcValue)) + self.secondLine.string = string + + def convertRegionName(name): """ @@ -505,11 +524,3 @@ def convertRegionName(name): nextUpper = False converted.append(char) return u"".join(converted) - - -# this is for testing: -if __name__ == "__main__": - map = Map("Providence", "Providence.svg") - s = map.systems["I7S-1S"] - s.setStatus(states.ALARM) - logging.error(map.svg) diff --git a/src/vi/filewatcher.py b/src/vi/filewatcher.py index 75032a1..6c6eabc 100644 --- a/src/vi/filewatcher.py +++ b/src/vi/filewatcher.py @@ -22,8 +22,8 @@ import time import logging -from PyQt4 import QtCore -from PyQt4.QtCore import SIGNAL +from PyQt5 import QtCore +from PyQt5.QtCore import pyqtSignal, QObject, QModelIndex """ There is a problem with the QFIleWatcher on Windows and the log @@ -40,6 +40,9 @@ DEFAULT_MAX_AGE = 60 * 60 * 24 class FileWatcher(QtCore.QThread): + + data_changed = pyqtSignal(str) + def __init__(self, path, maxAge=DEFAULT_MAX_AGE): QtCore.QThread.__init__(self) self.path = path @@ -52,7 +55,7 @@ def __init__(self, path, maxAge=DEFAULT_MAX_AGE): self.paused = True self.active = True - + def directoryChanged(self): self.updateWatchedFiles() @@ -69,7 +72,7 @@ def run(self): if not stat.S_ISREG(pathStat.st_mode): continue if modified < pathStat.st_size: - self.emit(SIGNAL("file_change"), path) + self.data_changed.emit(path) self.files[path] = pathStat.st_size @@ -92,4 +95,5 @@ def updateWatchedFiles(self): if self.maxAge and ((now - pathStat.st_mtime) > self.maxAge): continue filesInDir[fullPath] = self.files.get(fullPath, 0) + self.files = filesInDir diff --git a/src/vi/resources.py b/src/vi/resources.py deleted file mode 100644 index fa94cba..0000000 --- a/src/vi/resources.py +++ /dev/null @@ -1,33 +0,0 @@ -########################################################################### -# Vintel - Visual Intel Chat Analyzer # -# Copyright (C) 2014-15 Sebastian Meyer (sparrow.242.de+eve@gmail.com ) # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 3 of the License, or # -# (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see . # -########################################################################### - -import os -import sys - - -def resourcePath(relativePath): - """ Get absolute path to resource, works for dev and for PyInstaller - """ - if getattr(sys, 'frozen', False): - # PyInstaller creates a temp folder and stores path in _MEIPASS - basePath = sys._MEIPASS - else: - basePath = os.path.abspath(".") - returnpath = os.path.join(basePath, relativePath) - return returnpath diff --git a/src/vi/soundmanager.py b/src/vi/soundmanager.py index ff8bbfd..2011406 100644 --- a/src/vi/soundmanager.py +++ b/src/vi/soundmanager.py @@ -8,7 +8,7 @@ # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# but WITHOUT ANY WARRANTYf without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # @@ -24,10 +24,13 @@ import requests import time import six +import logging +import pyttsx3 +from win32com.server.util import wrap from collections import namedtuple -from PyQt4.QtCore import QThread -from .resources import resourcePath +from PyQt5.QtCore import QThread +from pkg_resources import resource_filename from six.moves import queue import logging @@ -35,6 +38,7 @@ global gPygletAvailable + try: import pyglet from pyglet import media @@ -68,9 +72,9 @@ def platformSupportsAudio(self): return self.platformSupportsSpeech() or gPygletAvailable def platformSupportsSpeech(self): - if self._soundThread.isDarwin: + if self._soundThread.isDarwin or self._soundThread.usePyTTS: return True - return False + return True def setUseSpokenNotifications(self, newValue): if newValue is not None: @@ -82,14 +86,18 @@ def setSoundVolume(self, newValue): self.soundVolume = max(0, min(100, newValue)) self._soundThread.setVolume(self.soundVolume) - def playSound(self, name="alarm", message="", abbreviatedMessage=""): + def playSound(self, name="alarm", message="Incoming enemies", abbreviatedMessage="red alert"): """ Schedules the work, which is picked up by SoundThread.run() """ + if name is False: + name = "alarm" + message = "this is a test" + abbreviatedMessage = "test" if self.soundAvailable and self.soundActive: if self.useSpokenNotifications: audioFile = None else: - audioFile = resourcePath("vi/ui/res/{0}".format(self.SOUNDS[name])) + audioFile = resource_filename(__name__,"ui/res/{0}".format(self.SOUNDS[name])) self._soundThread.queue.put((audioFile, message, abbreviatedMessage)) def quit(self): @@ -100,12 +108,9 @@ def quit(self): # Inner class handle audio playback without blocking the UI # - class SoundThread(QThread): + class SoundThread(QThread): + usePyTTS = True queue = None - useGoogleTTS = False - useVoiceRss = False - VOICE_RSS_API_KEY = '896a7f61ec5e478cba856a78babab79c' - GOOGLE_TTS_API_KEY = '' isDarwin = sys.platform.startswith("darwin") volume = 25 @@ -116,8 +121,11 @@ def __init__(self): if gPygletAvailable: self.player = media.Player() else: - self.player = None + self.player = None + if self.usePyTTS: + self.speech_engine = pyttsx3.init() self.active = True + def setVolume(self, volume): @@ -148,10 +156,11 @@ def quit(self): def speak(self, message): - if self.useGoogleTTS: - self.audioExtractToMp3(inputText=message) # experimental - elif self.useVoiceRss: - self.playTTS(message) # experimental + if self.usePyTTS: + self.speech_engine.say(message) # experimental + self.speech_engine.setProperty('volume',self.volume/100.0) + self.speech_engine.setProperty('rate',130) + self.speech_engine.runAndWait() elif self.isDarwin: self.darwinSpeak(message) else: @@ -184,54 +193,6 @@ def darwinSpeak(self, message): except Exception as e: logging.error("SoundThread.darwinSpeak exception: %s", e) - # - # Experimental text-to-speech stuff below - # - - # VoiceRss - - def playTTS(self, inputText=''): - try: - mp3url = 'http://api.voicerss.org/?c=WAV&key={self.VOICE_RSS_API_KEY}&src={inputText}&hl=en-us'.format( - **locals()) - self.playAudioFile(requests.get(mp3url, stream=True).raw) - time.sleep(.5) - except requests.exceptions.RequestException as e: - logging.error('playTTS error: %s', str(e)) - - # google_tts - - def audioExtractToMp3(self, inputText='', args=None): - # This accepts : - # a dict, - # an audio_args named tuple - # or arg parse object - audioArgs = namedtuple('audio_args', ['language', 'output']) - if args is None: - args = audioArgs(language='en', output=open('output.mp3', 'w')) - if type(args) is dict: - args = audioArgs(language=args.get('language', 'en'), output=open(args.get('output', 'output.mp3'), 'w')) - # Process inputText into chunks - # Google TTS only accepts up to (and including) 100 characters long texts. - # Split the text in segments of maximum 100 characters long. - combinedText = self.splitText(inputText) - - # Download chunks and write them to the output file - for idx, val in enumerate(combinedText): - mp3url = "http://translate.google.com/translate_tts?tl=%s&q=%s&total=%s&idx=%s&ie=UTF-8&client=t&key=%s" % ( - args.language, requests.utils.quote(val), len(combinedText), idx, self.GOOGLE_TTS_API_KEY) - headers = {"Host": "translate.google.com", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1)"} - sys.stdout.write('.') - sys.stdout.flush() - if len(val) > 0: - try: - args.timeout.write(requests.get(mp3url, headers=headers).content) - time.sleep(.5) - except requests.exceptions.RequestException as e: - logging.error('audioExtractToMp3 error: %s', e) - args.output.close() - return args.output.name - def splitText(self, inputText, maxLength=100): """ Try to split between sentences to avoid interruptions mid-sentence. diff --git a/src/vi/threads.py b/src/vi/threads.py index f42c9e9..5b9247e 100755 --- a/src/vi/threads.py +++ b/src/vi/threads.py @@ -22,16 +22,20 @@ import six from six.moves import queue -from PyQt4.QtCore import QThread, SIGNAL, QTimer +from PyQt5 import QtWidgets +from PyQt5.QtCore import QThread, pyqtSignal, QTimer from vi import evegate from vi import koschecker from vi.cache.cache import Cache -from vi.resources import resourcePath + +from vi.ui import ChatEntryWidget +from pkg_resources import resource_filename STATISTICS_UPDATE_INTERVAL_MSECS = 1 * 60 * 1000 class AvatarFindThread(QThread): + avatar_update = pyqtSignal(QtWidgets.QWidget, bytes) def __init__(self): QThread.__init__(self) self.queue = queue.Queue() @@ -64,7 +68,7 @@ def run(self): logging.debug("AvatarFindThread getting avatar for %s" % charname) avatar = None if charname == "VINTEL": - with open(resourcePath("vi/ui/res/logo_small.png"), "rb") as f: + with open(resource_filename(__name__,"ui/res/logo_small.png"), "rb") as f: avatar = f.read() if not avatar: avatar = cache.getAvatar(charname) @@ -80,7 +84,7 @@ def run(self): cache.putAvatar(charname, avatar) if avatar: logging.debug("AvatarFindThread emit avatar_update for %s" % charname) - self.emit(SIGNAL("avatar_update"), chatEntry, avatar) + self.avatar_update.emit( chatEntry, avatar) except Exception as e: logging.error("Error in AvatarFindThread : %s", e) @@ -93,6 +97,8 @@ def quit(self): class KOSCheckerThread(QThread): + kos_result = pyqtSignal() + def __init__(self): QThread.__init__(self) self.queue = queue.Queue() @@ -151,6 +157,7 @@ def quit(self): class MapStatisticsThread(QThread): + statisticDataUpdate = pyqtSignal(dict) def __init__(self): QThread.__init__(self) self.queue = queue.Queue(maxsize=1) @@ -166,7 +173,7 @@ def requestStatistics(self): def run(self): self.refreshTimer = QTimer() - self.connect(self.refreshTimer, SIGNAL("timeout()"), self.requestStatistics) + self.refreshTimer.timeout.connect(self.requestStatistics) while True: # Block waiting for requestStatistics() to enqueue a token self.queue.get() @@ -183,7 +190,7 @@ def run(self): requestData = {"result": "error", "text": six.text_type(e)} self.lastStatisticsUpdate = time.time() self.refreshTimer.start(self.pollRate) - self.emit(SIGNAL("statistic_data_update"), requestData) + self.statisticDataUpdate.emit(requestData) logging.debug("MapStatisticsThread emitted statistic_data_update") diff --git a/src/vi/ui/ChatEntryWidget.py b/src/vi/ui/ChatEntryWidget.py new file mode 100644 index 0000000..f437847 --- /dev/null +++ b/src/vi/ui/ChatEntryWidget.py @@ -0,0 +1,62 @@ +import datetime +import sys +import six +from PyQt5 import QtWidgets, QtGui, uic +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtGui import QImage,QPixmap +from pkg_resources import resource_stream, resource_filename + +class ChatEntryWidget(QtWidgets.QWidget): + mark_system = pyqtSignal(str) + TEXT_SIZE = 11 + SHOW_AVATAR = True + questionMarkPixmap = None + + def __init__(self, message): + QtWidgets.QWidget.__init__(self) + if not self.questionMarkPixmap: + self.questionMarkPixmap = QtGui.QPixmap(resource_filename(__name__,"res/qmark.png")).scaledToHeight(32) + uic.loadUi(resource_stream(__name__,"ChatEntry.ui"), self) + self.avatarLabel.setPixmap(self.questionMarkPixmap) + self.message = message + self.updateText() + self.textLabel.linkActivated.connect(self.linkClicked) + if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"): + ChatEntryWidget.TEXT_SIZE = 8 + self.changeFontSize(self.TEXT_SIZE) + if not ChatEntryWidget.SHOW_AVATAR: + self.avatarLabel.setVisible(False) + + + def linkClicked(self, link): + link = six.text_type(link) + function, parameter = link.split("/", 1) + if function == "mark_system": + self.mark_system.emit(parameter) + elif function == "link": + webbrowser.open(parameter) + + + def updateText(self): + time = datetime.datetime.strftime(self.message.timestamp, "%H:%M:%S") + text = u"{time} - {user} - {room}
{text}".format(user=self.message.user, + room=self.message.room, + time=time, + text=self.message.message) + self.textLabel.setText(text) + + + def updateAvatar(self, avatarData): + image = QImage.fromData(avatarData) + pixmap = QPixmap.fromImage(image) + if pixmap.isNull(): + return False + scaledAvatar = pixmap.scaled(32, 32) + self.avatarLabel.setPixmap(scaledAvatar) + return True + + + def changeFontSize(self, newSize): + font = self.textLabel.font() + font.setPointSize(newSize) + self.textLabel.setFont(font) diff --git a/src/vi/ui/Info.ui b/src/vi/ui/Info.ui index 6864006..6b69751 100644 --- a/src/vi/ui/Info.ui +++ b/src/vi/ui/Info.ui @@ -750,7 +750,7 @@ Public License instead of this License. But first, please read - <html><head/><body><p align="center"><span style=" font-weight:600;">Vintel</span> - Visual Intel Chat Analyzer<br/><br/><br/>Dev Team: <a href="mailto:xanthos.eve@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">Xanthos</span></a>, <a href="mailto:lightlazer@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">Parrion Hukondo</span></a>, <a href="mailto:cberg@3wsolutions.se"><span style=" text-decoration: underline; color:#0000ff;">chriz</span></a><br/><br/>This software is based on original work by Sebastian Meyer <br/>and is open source licensed under GPL v3.<br/>Find current sources and documentation at<br/><a href="https://github.com/XanthosX/vintel"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/xanthos-eve/vintel</span></a></p><p align="center">Donations of ISK are appreciated.</p><p align="center">NRDS!</p></body></html> + <html><head/><body><p align="center"><span style=" font-weight:600;">Vintel</span> - Visual Intel Chat Analyzer<br/><br/><br/> Dev Team: <ul style="text-align:left;" > <li> <a href="https://github.com/bperian">Blitz Arkaral<a> </li> <li> <a href="mailto:xanthos.eve@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">Xanthos</span></a> </li> <li> <a href="mailto:lightlazer@gmail.com"><span style=" text-decoration: underline; color:#0000ff;">Parrion Hukondo</span></a> </li> <li> <a href="mailto:cberg@3wsolutions.se"><span style=" text-decoration: underline; color:#0000ff;">chriz</span></a> </li> </ul> <br/><br/>This software is based on original work by Sebastian Meyer <br/>and is open source licensed under GPL v3.<br/>Find current sources and documentation at<br/><a href="https://github.com/bperian/vintel"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/xanthos-eve/vintel</span></a></p><p align="center">Donations of ISK are appreciated.</p><p align="center"></p></body></html> Qt::AlignHCenter|Qt::AlignTop diff --git a/src/vi/ui/MainWindow.ui b/src/vi/ui/MainWindow.ui index d3a9ccd..944c7a4 100644 --- a/src/vi/ui/MainWindow.ui +++ b/src/vi/ui/MainWindow.ui @@ -384,7 +384,7 @@ - + @@ -533,7 +533,7 @@ Sound Setup... - + Jumpbridge Data... diff --git a/src/vi/ui/res/mapdata/Branch.svg b/src/vi/ui/res/mapdata/Branch.svg new file mode 100644 index 0000000..e3bcb42 --- /dev/null +++ b/src/vi/ui/res/mapdata/Branch.svg @@ -0,0 +1,1321 @@ + + + + + + + + + + + +Branch + + + © by Wollari & CCP + +XXXXXYYYYY (Z) + +X= System +Y= Alliance +Z= Sov. Lvl += Outpost += Cq Stat. += NPC Stat. += Icebelt + += Refinery += Factory += Research += Offices += Refining += Industry += Research += Cloning += Contested + + + + + + + + XW-JHT + DARK. (5) + + + + + + 313I-B + DARK. (3) + + + + + + + + + Q-4DEC + DARK. (3) + + + + + + + + + 2B7A-3 + DARK. (5) + + + + + + C-VGYO + DARK. (5) + + + + + + K-8SQS + DARK. (5) + + + + + + + + + + + + 1IX-C0 + DARK. (5) + + + + + + + + + Y-1918 + DARK. (5) + + + + + + + + + + O94U-A + DARK. (5) + + + + + + + + + W-4FA9 + DARK. (5) + + + + + + + + + 6-O5GY + DARK. (3) + + + + + + + + + J9-5MQ + DARK. (3) + + + + + + + + + NEH-CS + DARK. (5) + + + + + + C-4ZOS + DARK. (5) + + + + + + + + + + 3F-JZF + DARK. (3) + + + + + + PUWL-4 + DARK. (5) + + + + + + + D4R-H7 + DARK. (3) + + + + + + + + + + + + 4DTQ-K + DARK. (3) + + + + + + + + + B8O-KJ + DARK. (3) + + + + + + 5LJ-MD + DARK. (3) + + + + + + + + + + 52G-NZ + DARK. (3) + + + + + + + + + + 5-0WB9 + DARK. (5) + + + + + + + + + EQI2-2 + DARK. (3) + + + + + + + + + UB-UQZ + DARK. (3) + + + + + + + + + XM-4L0 + DARK. (3) + + + + + + QCWA-Z + DARK. (3) + + + + + + + + + KV-8SN + DARK. (3) + + + + + + + + + M-HU4V + DARK. (5) + + + + + + 9F-7PZ + DARK. (5) + + + + + + + + + C-LP3N + DARK. (5) + + + + + + + + + 1G-MJE + DARK. (5) + + + + + + WO-AIJ + DARK. (5) + + + + + + X7R-JW + DARK. (5) + + + + + + YG-82V + DARK. (3) + + + + + + + + + Z-K495 + DARK. (3) + + + + + + + + + QXQ-BA + DARK. (5) + + + + + + + B-GC1T + DARK. (5) + + + + + + + + + T-Q2DD + DARK. (5) + + + + + + + + + LRWD-B + DARK. (5) + + + + + + LXWN-W + -000- (5) + + + + + + + + + RO90-H + DARK. (3) + + + + + + + + + MA-VDX + DARK. (5) + + + + + + + + + 8-4GQM + DARK. (5) + + + + + + + O-JPKH + SB-SQ (5) + + + + + + + + + F-9F6Q + DARK. (5) + + + + + + + + + KJ-QWL + DARK. (5) + + + + + + 9-B1DS + DARK. (5) + + + + + + + + + CH9L-K + DARK. (5) + + + + + + + + + HB7R-F + SB-SQ (4) + + + + + + + + + + JTAU-5 + DARK. (5) + + + + + + + + + 0P9Z-I + SB-SQ (3) + + + + + + KMQ4-V + DARK. (5) + + + + + + SVB-RE + DARK. (5) + + + + + + + 3KNA-N + DARK. (5) + + + + + + + + + ME-4IU + DARK. (5) + + + + + + + + + + I-7JR4 + DARK. (5) + + + + + + + + + S-B7IT + DARK. (5) + + + + + + BKG-Q2 + SB-SQ (3) + + + + + + + + + + + Q-FEEJ + SB-SQ (3) + + + + + + + + + + AH-B84 + SB-SQ (5) + + + + + + + + + + 5-P1Y2 + DARK. (5) + + + + + + + + + + BU-IU4 + DARK. (5) + + + + + + + + + QYZM-W + DARK. (5) + + + + + + + + + JRZ-B9 + DARK. (5) + + + + + + X4UV-Z + DARK. (5) + + + + + + 4-48K1 + SB-SQ (5) + + + + + + NTV0-1 + SB-SQ (5) + + + + + + + + + C-HCGU + SB-SQ (3) + + + + + + + + + J52-BH + KOS (5) + + + + + + + + + + XW-2XP + SB-SQ (3) + + + + + + + + + V8W-QS + DARK. (5) + + + + + + J7YR-1 + SB-SQ (5) + + + + + + + + + + PKG4-7 + SB-SQ (5) + + + + + + EWN-2U + SB-SQ (5) + + + + + + I-7RIS + -000- (5) + + + + + + C-LBQS + KOS (5) + + + + + + + + + DCI7-7 + SB-SQ (5) + + + + + + + OJ-A8M + DARK. (5) + + + + + + P7Z-R3 + -000- (5) + + + + + + A-G1FM + -000- (5) + + + + + + + + + CS-ZGD + -000- (5) + + + + + + + + + CX-1XF + KOS (5) + + + + + + KL3O-J + KOS (5) + + + + + + + BWI1-9 + KOS (5) + + + + + + + + + ZIU-EP + -000- (5) + + + + + + UQ9-3C + SB-SQ (5) + + + + + + + + + VL3I-M + SB-SQ (5) + + + + + + + + + 3-TD6L + KOS (5) + + + + + + + + + R4O-I6 + KOS (5) + + + + + + + 4-BE0M + -000- (5) + + + + + + + + + 3-N3OO + -000- (5) + + + + + + KMC-WI + SB-SQ (5) + + + + + + + + + NLPB-0 + KOS (5) + + + + + + + + + Q-NJZ4 + KOS (5) + + + + + + UJY-HE + Deklein + + + + + + + + + 92D-OI + Venal + + + + + + ZXA-V6 + Tenal + + + + + + Q1U-IU + Tenal + + + + + + 2-3Q2G + Tenal + + + + + + H1-ESN + Tenal + + + + + + 9IPC-E + Venal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/vi/ui/res/mapdata/Branch_files/dotSvg.js b/src/vi/ui/res/mapdata/Branch_files/dotSvg.js new file mode 100644 index 0000000..eace579 --- /dev/null +++ b/src/vi/ui/res/mapdata/Branch_files/dotSvg.js @@ -0,0 +1 @@ +var SVG="http://www.w3.org/2000/svg";var XLINK="http://www.w3.org/1999/xlink";(function(){var A=Object.prototype.toString,z="Null",p="Undefined",u="Boolean",f="Number",s="String",D="Object",w="[object Boolean]",g="[object Number]",k="[object String]",i="[object Array]";function j(F){switch(F){case null:return z;case (void 0):return p}var E=typeof F;switch(E){case"boolean":return u;case"number":return f;case"string":return s}return D}function C(E){try{if(d(E)){return"undefined"}if(E===null){return"null"}return E.inspect?E.inspect():String(E)}catch(F){if(F instanceof RangeError){return"..."}throw F}}function x(E,G){for(var F in G){E[F]=G[F]}return E}function r(E){if(j(E)!==D){throw new TypeError()}var F=[];for(var G in E){if(E.hasOwnProperty(G)){F.push(G)}}return F}function e(E){var F=[];for(var G in E){F.push(E[G])}return F}function y(E){return x({},E)}function t(E){return !!(E&&E.nodeType==1)}function n(E){return A.call(E)===i}var c=(typeof Array.isArray=="function")&&Array.isArray([])&&!Array.isArray({});if(c){n=Array.isArray}function b(E){return typeof E==="function"}function o(E){return A.call(E)===k}function q(E){return A.call(E)===g}function d(E){return typeof E==="undefined"}function h(F){for(var E in this){if(typeof this[E]!="function"){F(this[E],E)}}}function v(){return this.map()}function a(E){if(Object.isFunction(this.indexOf)){if(this.indexOf(E)!=-1){return true}}var F=false;this.each(function(G){if(G==E){F=true;throw {}}});return F}function l(G,F){var E=[];this.each(function(I,H){if(G.call(F,I,H)){E.push(I)}});return E}function B(G,F){G=G||function(H){return H};var E=[];this.each(function(I,H){E.push(G.call(F,I,H))});return E}x(Object,{inspect:C,extend:x,keys:Object.keys||r,values:e,clone:y,isElement:t,isArray:n,isFunction:b,isString:o,isNumber:q,isUndefined:d,toArray:v});Object.extend(Object.prototype,{each:h,map:B,select:l,include:a})})();function hashLength(b){var c=0;for(var a in b){if(typeof b[a]!="function"){c++}}return c}(function(){function a(b){return this.indexOf(b)>-1}Object.extend(String.prototype,{include:a})})();$break={};(function(){var e=Array.prototype.forEach;function g(l){for(var k=0,n=this.length;k1){for(var a=0,d=[],c=arguments.length;a-1){this.zMax=6500}$(window).on("contextmenu",function(d){SVGWorld.nullEvent(d)});this.enableControls();$("connections").insert(dotSvg.create("path",{"class":"j",id:"j",d:WDB.path.j}));$("connections").insert(dotSvg.create("path",{"class":"jc",id:"jc",d:WDB.path.jc}));$("connections").insert(dotSvg.create("path",{"class":"jr",id:"jr",d:WDB.path.jr}));$("sysdots").insert(dotSvg.create("path",{"class":"sd",id:"sd",d:WDB.path.sd}));this.update()},setLimit:function(){this.limitXR=((this.docW*(this.zoom/100))-this.viewW<0)?((this.docW*(this.zoom/100))-this.viewW)/2:(this.docW*(this.zoom/100))-this.viewW;this.limitXL=(this.limitXR<0)?this.limitXR:0;this.limitY=((this.docH*(this.zoom/100))-this.viewH<0)?0:(this.docH*(this.zoom/100))-this.viewH},update:function(){if(isNaN(this.x)){this.x=0}else{if(this.xthis.limitXR){this.x=this.limitXR}}}if(isNaN(this.y)){this.y=0}else{if(this.y<0){this.y=0}else{if(this.y>this.limitY){this.y=this.limitY}}}this.zoom=isNaN(this.zoom)?this.zMin:(this.zoom<=this.zMin)?this.zMin:(this.zoom>=this.zMax)?this.zMax:this.zoom;this.vb_x=(Math.round((this.x/(this.zoom/100))*100)/100);this.vb_y=(Math.round((this.y/(this.zoom/100))*100)/100);this.vb_w=(Math.round((this.docW/(this.zoom/100))*100)/100);this.vb_h=(Math.round((this.docH/(this.zoom/100))*100)/100);this.transform="translate("+(this.vb_x*(this.zoom/100)*-1)+","+(this.vb_y*(this.zoom/100)*-1)+")";this.scale="scale("+(this.zoom/100)+")";$("map").attr("transform",this.transform+" "+this.scale);$("mapover").attr("transform",this.transform+" "+this.scale);$("regionnames").attr("transform",this.transform);if(this.oldZoom!=this.zoom){$("controls").attr("title","Current Zoom: "+this.zoom);this.lj=(Math.round((1/(this.zoom/100))*1000)/1000)+"";this.ls=this.lj*((this.zoom>300)?3:(this.zoom>200)?2.5:(this.zoom>100)?2:1.5);$("sd").attr("style","stroke-width: "+SVGWorld.ls);$("j").attr("style","stroke-width: "+SVGWorld.lj);$("jc").attr("style","stroke-width: "+SVGWorld.lj);$("jr").attr("style","stroke-width: "+SVGWorld.lj);$$(".jx").each(function(b){$(b).attr("style","stroke-width: "+(SVGWorld.lj*7))});$$(".jb").each(function(b){$(b).attr("style","stroke-width: "+(SVGWorld.lj*2))});this.oldZoom=this.zoom}if(!this.waitSysUpdate){this.moveSysNew=0;this.moveSysUpdate=0;this.moveSysRemove=0;this.route_alt=$A(this.route_alt);this.route_on=$A(this.route_on);this.route_wp=$A(this.route_wp);this.update_regiontext();this.update_constellation();this.update_sys();this.update_systext();if(this.zoom<400&&this.route_alt.length){for(var a=0;a=400&&this.zoom<1100&&this.route_on.length){for(var a=0;a0){for(var a=0;a0){this.zoomIn(a,b)}if(b<0){this.zoomOut(a,b)}if(a.preventDefault){a.preventDefault()}a.returnValue=false},moveStart:function(a){if(a.button>0){return}this.drag=true;this.oldX=a.pageX;this.oldY=a.pageY},moveDo:function(a){if(!this.drag){return false}this.x=this.x-parseInt(a.pageX-this.oldX);this.y=this.y-parseInt(a.pageY-this.oldY);this.oldX=a.pageX;this.oldY=a.pageY;if(!this.waitSysUpdate){this.waitSysUpdate=true;$("systems").attr("style","display: none");$("sysover").attr("style","display: none");$("systemnames").attr("style","display: none");$("systextover").attr("style","display: none");$("constellations").attr("style","display: none")}this.update()},moveStop:function(a){if(!this.drag){return}this.drag=false;this.waitSysUpdate=false;this.update()},setZoom:function(b,a,c){b=isNaN(b)?this.zMin:(b<=this.zMin)?this.zMin:(b>=this.zMax)?this.zMax:b;if(this.zoom==b&&typeof(a)=="undefined"&&typeof(c)=="undefined"){return}this.zoom=b;this.setLimit();if(typeof(a)!="undefined"&&typeof(c)!="undefined"){this.setPos(a,c)}this.hide_sys();this.hide_systext();this.hide_constellation();this.hide_regiontext();this.update()},setPos:function(a,b){if(typeof(a)!="undefined"&&typeof(b)!="undefined"){this.x=Math.round(a*100)/100;this.y=Math.round(b*100)/100}this.update()},zoomIn:function(b,e){this.zoomDiff=(Math.floor(this.zoom/100)*100)/2;var a=Math.floor((this.zoom+this.zoomDiff)/this.zoomDiff)*this.zoomDiff;var d=(b&&b.pageX)?b.pageX:this.viewW/2;var c=(b&&b.pageY)?b.pageY:this.viewH/2;a=(a<=this.zMin)?this.zMin:(a>=this.zMax)?this.zMax:a;this.x=Math.round(((this.x+d)/(this.zoom/100))*(a/100))-d;this.y=Math.round(((this.y+c)/(this.zoom/100))*(a/100))-c;this.setZoom(a)},zoomOut:function(b,e){this.zoomDiff=(Math.floor((this.zoom-this.zoomDiff)/100)*100)/2;var a=Math.floor((this.zoom-this.zoomDiff)/this.zoomDiff)*this.zoomDiff;var d=(b&&b.pageX)?b.pageX:this.viewW/2;var c=(b&&b.pageY)?b.pageY:this.viewH/2;a=(a<=this.zMin)?this.zMin:(a>=this.zMax)?this.zMax:a;this.x=Math.round(((this.x+d)/(this.zoom/100))*(a/100))-d;this.y=Math.round(((this.y+c)/(this.zoom/100))*(a/100))-c;this.setZoom(a)},update_sys:function(){this.hide_sys();if(this.zoom>=400){for(id in WDB.system){var a=WDB.system[id];if(a.x>=this.vb_x&&a.x<=(this.vb_x+this.vb_w)&&a.y>=this.vb_y&&a.y<=(this.vb_y+this.vb_h)){this.drawSystem(id)}else{this.removeSystem(id)}}}else{$("systems").clear();$("sysover").clear()}$("systems").attr("style","");$("sysover").attr("style","")},hide_sys:function(){this.sysOnMap=0;$("systems").attr("style","display: none");$("sysover").attr("style","display: none")},update_systext:function(){this.hide_systext();if(this.zoom>=1100){for(id in WDB.system){if(typeof(WDB.system[id])!="function"){var a=WDB.system[id];if(a.x>=this.vb_x&&a.x<=(this.vb_x+this.vb_w)&&a.y>=this.vb_y&&a.y<=(this.vb_y+this.vb_h)){this.drawSystemName(id)}else{this.removeSystemName(id)}}}}else{$("systemnames").clear();$("systextover").clear()}$("systemnames").attr("style","");$("systextover").attr("style","")},hide_systext:function(){$("systemnames").attr("style","display: none");$("systextover").attr("style","display: none")},update_constellation:function(){this.hide_constellation();if(this.zoom>=600&&this.zoom<1100){for(id in WDB.constellation){if(typeof(WDB.constellation[id])!="function"){var a=WDB.constellation[id];if(a.x>=this.vb_x&&a.x<=(this.vb_x+this.vb_w)&&a.y>=this.vb_y&&a.y<=(this.vb_y+this.vb_h)){this.drawConstellationName(id)}else{this.removeConstallationName(id)}}}}else{$("constellations").clear()}$("constellations").attr("style","")},hide_constellation:function(){$("constellations").attr("style","display: none")},update_regiontext:function(){var a=dotSvg.helper.getNodes($("regionnames"));if(!a.length){this.hide_regiontext();for(id in WDB.region){if(typeof(WDB.region[id])!="function"){this.drawRegionName(id)}}}$("regionnames").attr("style","")},hide_regiontext:function(){$("regionnames").attr("style","display: none")},getSystemUrl:function(c){var b=WDB.system[c];if(!b){return""}var a=WDB.region[b.r];return"/map/"+a.n.replace(this.rxp,"_")+"/"+b.n.replace(this.rxp,"_")},getConstellationUrl:function(c){var a=WDB.constellation[c];if(!a){return""}var b=WDB.region[a.r];return"/map/"+b.n.replace(this.rxp,"_")+"/"+a.n.replace(this.rxp,"_")},getRegionUrl:function(b){var a=WDB.region[b];if(!a){return""}return"/map/"+a.n.replace(this.rxp,"_")},system_over:function(a){this.drawSystemName(a,true)},system_out:function(d){var c=this.route_wp.include(parseInt(d)+30000000)||this.route_wp.include(d);var a=this.route_alt.include(parseInt(d)+30000000)||this.route_alt.include(d);var b=this.route_on.include(parseInt(d)+30000000)||this.route_on.include(d);if(!c&&!b&&this.zoom<1100){if($("t"+d)){$("t"+d).remove()}}if(!c){if($("th"+d)){$("th"+d).remove()}}},click:function(h,f){var d=WDB.system[h];var b=WDB.region[d.r];var a=this.route_alt.include(parseInt(h)+30000000)||this.route_alt.include(h);var c="";if(parent){if(a&&this.route_alt_cur&&WDB.system[(parseInt(this.route_alt_cur)-30000000)]){var g=WDB.system[(parseInt(this.route_alt_cur)-30000000)];parent.routeJump.altSelect(g.n,d.n)}else{parent.location.href=this.getSystemUrl(h)}}},setFocus:function(b){this.resetFocus();if(b){b=$A(b);for(var a=0;athis.smax_x||this.smax_x<0)?c.x:this.smax_x;this.smin_y=(c.ythis.smax_y||this.smax_y<0)?c.y:this.smax_y}}}this.moveFocus()},resetFocus:function(){this.smin_y=-1;this.smax_y=-1;this.smin_x=-1;this.smax_x=-1},moveFocus:function(){if(this.smin_y>-1&&this.smax_y>-1&&this.smin_x>-1&&this.smax_x>-1){var h=this.smin_y;var n=this.smax_y;var j=this.smin_x;var a=this.smax_x;var d=(a-j)+this.margin[3]+this.margin[1];var k=(n-h)+this.margin[0]+this.margin[2];var b=this.docW/this.docH;var c=d/k;if(cthis.docH/k)?this.docW/d:this.docH/k)*100);var i=j-this.margin[3];var g=h-this.margin[0];this.setZoom(l,i*(l/100),g*(l/100));this.focusOn=false}},nullEvent:function(a){if(a.preventDefault){a.preventDefault()}if(a.stopPropagation){a.stopPropagation()}return false},closeContextMenu:function(a){window.parent.MapUniverseUtil.contextMenu("close");$(window).unbind("click",SVGWorld.closeContextMenu)},openContextMenu:function(d,b,c,a){if(b==3&&c<10000000){c+=10000000}else{if(b==4&&c<20000000){c+=20000000}else{if(b==5&&c<30000000){c+=30000000}}}if(!window.parent||!window.parent.MapUniverseUtil){return}window.parent.MapUniverseUtil.contextMenu(a,b,c,d.pageX,d.pageY);$(window).on("click",SVGWorld.closeContextMenu);return SVGWorld.nullEvent(d)},drawSystem:function(b){var b=(b>=30000000)?(parseInt(b)-30000000):parseInt(b);var i=WDB.system[b];var h=Math.round((i.x-this.vb_x)*(this.zoom/100));var g=Math.round((i.y-this.vb_y)*(this.zoom/100));if(h<-30||h>this.docW+30||g<-30||g>this.docH+30){this.removeSystem(b);this.moveSysRemove++;return}var d=this.route_wp.include(parseInt(b)+30000000)||this.route_wp.include(b);var j=this.route_alt.include(parseInt(b)+30000000)||this.route_alt.include(b);var e=this.route_on.include(parseInt(b)+30000000)||this.route_on.include(b);var f=i.c+((d)?"w":(e)?"r":(j)?"a":"");var a=(d)?"sysover":"systems";if($("s"+b)&&$("s"+b).attr("xlink:href")=="#"+f){$("s"+b).attr({x:h-6,y:g-6});this.moveSysUpdate++;this.sysOnMap++;return}else{this.removeSystem(b)}this.moveSysNew++;$(a).insert($(dotSvg.create("use",{"xlink:href":"#"+f,id:"s"+b,x:h-6,y:g-6,title:i.n})).on("mouseup",function(c){if(c.button==0){SVGWorld.click(b)}}).on("mouseover",function(){SVGWorld.system_over(b)}).on("mouseout",function(){SVGWorld.system_out(b)}).on("contextmenu",function(c){SVGWorld.openContextMenu(c,5,b,SVGWorld.getSystemUrl(b))}));this.sysOnMap++},removeSystem:function(a){if($("s"+a)){$("s"+a).remove()}},drawSystemName:function(b,e){var b=(b>=30000000)?(parseInt(b)-30000000):parseInt(b);var e=e||false;var l=WDB.system[b];var k=Math.round((l.x-this.vb_x)*(this.zoom/100));var j=Math.round((l.y-this.vb_y)*(this.zoom/100));if(k<-50||k>this.docW+50||j<-50||j>this.docH+50){this.removeSystemName(b);return}var d=this.route_wp.include(parseInt(b)+30000000)||this.route_wp.include(b);var n=this.route_alt.include(parseInt(b)+30000000)||this.route_alt.include(b);var h=this.route_on.include(parseInt(b)+30000000)||this.route_on.include(b);var g=10;var i="t"+((d)?"w":(h)?"r":"");var a=(d)?"systextover":"systemnames";var e=d||e||false;if(($("th"+b)&&e||!$("th"+b)&&!e)&&$("t"+b)&&$("t"+b).attr("class")==i){if(d||e){$("th"+b).attr({x:k+g,y:j})}$("t"+b).attr({x:k+g,y:j});return}else{this.removeSystemName(b)}if(e){$(a).insert($(dotSvg.create("text",{id:"th"+b,x:k+g,y:j,"class":i+"h"})).svgText(l.n))}$(a).insert($(dotSvg.create("text",{id:"t"+b,x:k+g,y:j,"class":i})).svgText(l.n).on("mouseup",function(c){if(c.button==0){SVGWorld.click(b)}}).on("contextmenu",function(c){SVGWorld.openContextMenu(c,5,b,SVGWorld.getSystemUrl(b))}))},removeSystemName:function(a){if($("th"+a)){$("th"+a).remove()}if($("t"+a)){$("t"+a).remove()}},drawConstellationName:function(j){var j=(j>=20000000)?(parseInt(j)-20000000):parseInt(j);var d=true;var b=WDB.constellation[j];var a=Math.round((b.x-this.vb_x)*(this.zoom/100));var i=Math.round((b.y-this.vb_y)*(this.zoom/100));if(a<-50||a>this.docW+50||i<-50||i>this.docH+50){this.removeConstallationName(j);return}var e=10;var h="c";var g="constellations";if($("ch"+j)&&$("c"+j)&&$("c"+j).attr("class")==h){$("ch"+j).attr({x:a+e,y:i});$("c"+j).attr({x:a+e,y:i});return}else{this.removeConstallationName(j)}if(d){$(g).insert($(dotSvg.create("text",{id:"ch"+j,x:a+e,y:i,"class":h+"h"})).svgText(b.n))}$(g).insert($(dotSvg.create("text",{id:"c"+j,x:a+e,y:i,"class":h})).svgText(b.n).on("mouseup",function(c){if(c.button==0){parent.location.href=SVGWorld.getConstellationUrl(j)}}).on("contextmenu",function(c){SVGWorld.openContextMenu(c,4,j,SVGWorld.getConstellationUrl(j))}))},removeConstallationName:function(a){if($("ch"+a)){$("ch"+a).remove()}if($("c"+a)){$("c"+a).remove()}},drawRegionName:function(h){var d=WDB.region[h];var g="r"+((this.zoom>200)?"r":"");var b=true;var a=Math.round((d.x*(this.zoom/100)));var f=Math.round((d.y*(this.zoom/100)));var e="regionnames";if($("rh"+h)&&$("r"+h)&&$("r"+h).attr("class")==g){$("rh"+h).attr({x:a,y:f});$("r"+h).attr({x:a,y:f});return}else{this.removeRegionName(h)}if(b){$(e).insert($(dotSvg.create("text",{id:"rh"+h,x:a,y:f,"class":g+"h"})).svgText(d.n))}$(e).insert($(dotSvg.create("text",{id:"r"+h,x:a,y:f,"class":g})).svgText(d.n).on("mouseup",function(c){if(c.button==0){parent.location.href=SVGWorld.getRegionUrl(h)}}).on("contextmenu",function(c){SVGWorld.openContextMenu(c,3,h,SVGWorld.getRegionUrl(h))}))},removeRegionName:function(a){if($("rh"+a)){$("rh"+a).remove()}if($("r"+a)){$("r"+a).remove()}},drawRoute:function(a){this.hideRoute();if(a){this.route=a}if($A(this.route.j)&&$A(this.route.j).length){$A(this.route.j).each(function(c){var b=c.split("-");var e=WDB.system[(parseInt(b[0])-30000000)];var d=WDB.system[(parseInt(b[1])-30000000)];if(e&&d){$("routes").insert(dotSvg.create("line",{x1:Math.round(e.x*100)/100,y1:Math.round(e.y*100)/100,x2:Math.round(d.x*100)/100,y2:Math.round(d.y*100)/100,"class":"jx",style:"stroke-width: "+(SVGWorld.lj*7)+"px"}))}})}if($A(this.route.jb)&&$A(this.route.jb).length){$A(this.route.jb).each(function(f){var s=f.split("-");var p=WDB.system[(parseInt(s[0])-30000000)];var q=WDB.system[(parseInt(s[1])-30000000)];if(p&&q){var b=Math.round(p.x*100)/100;var r=Math.round(p.y*100)/100;var l=Math.round(q.x*100)/100;var i=Math.round(q.y*100)/100;var g=Math.sqrt((b-l)*(b-l)+(r-i)*(r-i));var k=g/5;var e=k/g;var d=(i-r)*e;var c=(l-b)*e;var o=(b+(l-b)/2)+d;var n=(r+(i-r)/2)-c;$("jumps").insert(dotSvg.create("path",{d:"M"+b+","+r+" S"+o+","+n+" "+l+","+i,"class":"jb "+s[2],style:"stroke-width: "+(SVGWorld.lj*3)+"px"}))}})}this.route_on=$A(this.route.sys);this.route_wp=$A(this.route.wp);this.route_alt=$A(this.route.alt);if(this.focusOn=="route"&&this.route.sys.length>0){this.setFocus(this.route.sys)}else{this.setFocus([].concat(this.route.wp).concat(this.route.alt))}this.update()},hideRoute:function(){this.route_alt=[];this.route_on=[];this.route_wp=[];$("routes").clear();$("jumps").clear()},drawJumpMidpoints:function(a){this.hideJumpMidpoints();this.route_alt=$A(a.sys);this.route_alt_cur=a.current;if(this.focusOn=="jumpmid"){var b=a.sys;b.push(a.next);b.push(a.prev);b.push(a.current);this.setFocus(b)}this.update()},hideJumpMidpoints:function(){this.route_alt=[]},drawJumpRange:function(a){this.hideJumpRange();this.route_alt=$A(a.sys);this.route_wp=$A(a.wp);if(this.focusOn=="jumprange"){this.setFocus(a.sys)}this.update()},hideJumpRange:function(){this.hideRoute()},drawHighlight:function(b,a){this.focusOn=a?"search":false;SVGWorld.route_alt=$A(b);SVGWorld.setFocus($A(b))}};var SVGMap={doc:false,x:0,y:0,oldX:0,oldY:0,docH:0,docW:0,viewH:0,viewW:0,drag:false,limitX:0,limitXR:0,limitXL:0,vb_x:0,vb_y:0,vb_w:0,vb_h:0,transform:"",scale:"",adminMode:false,limitY:0,realLimitX:0,realLimitY:0,oldZoom:100,zoom:100,zMin:100,zMax:400,wheel:false,MapUtil:false,init:function(f){this.doc=f.target.ownerDocument;this.docW=parseInt($("svgdoc").attr("width"));this.docH=parseInt($("svgdoc").attr("height"));this.viewH=this.docH;this.viewW=this.docW;if(window&&window.parent&&window.parent.MapUtil){this.MapUtil=true}if(this.MapUtil){window.parent.MapUtil.ready();if(!this.adminMode){dim=window.parent.MapUtil.getSize();this.resize(dim.width,dim.height)}var b=function(i){if(i.preventDefault){i.preventDefault()}if(i.stopPropagation){i.stopPropagation()}return false},e=function(i){window.parent.MapUtil.contextMenu("close");$(window).unbind("click",e)},g=function(n,j){var k=$(j).attr("class").match(/link\-(\d+)\-(\d+)/),i=$(j).attr("xlink:href");window.parent.MapUtil.contextMenu(i,k[1],k[2],n.pageX,n.pageY);$(window).on("click",e);return b(n)},a=function(j,i){if(j.button!==0){return b(j)}if($(i).attr("xlink:href")){window.parent.location=$(i).attr("xlink:href")}},c=null,h=function(j,i){c=null;if(j.button!==1){return}c=$(i).attr("xlink:href")},d=function(j,i){if(!c||j.button!==1){return}if(c==$(i).attr("xlink:href")){window.open(c)}c=null;return b(j)};window.oncontextmenu=b;$$(".sys").each(function(i){var j=$(i).attr("class").match(/link\-(\d+)\-(\d+)/);$("sys"+j[2]).on("click",function(k){return a(k,i)}).on("contextmenu",function(k){return g(k,i)}).on("mousedown",function(k){return h(k,i)}).on("mouseup",function(k){return d(k,i)})})}this.update()},enableControls:function(){dotSvg.observe(window,"mousedown",function(a){SVGMap.moveStart(a)});dotSvg.observe(window,"mousemove",function(a){SVGMap.moveDo(a)});dotSvg.observe(window,"mouseup",function(a){SVGMap.moveStop(a)});dotSvg.observe(window,"keydown",function(a){SVGMap.keyboard(a)});document.onselectstart=function(){return false};document.ondragstart=function(){return false};dotSvg.observe("zoom_in","click",function(){SVGMap.zoomIn()});dotSvg.observe("zoom_out","click",function(){SVGMap.zoomOut()});dotSvg.observe("wheel_on","click",function(){SVGMap.enableWheel(true)});dotSvg.observe("wheel_off","click",function(){SVGMap.disableWheel(true)});$("controls").attr("style","")},enableWheel:function(a){if(!this.wheel){dotSvg.observe(document,"mousewheel",function(b){SVGMap.mousewheel(b)});dotSvg.observe(document,"DOMMouseScroll",function(b){SVGMap.mousewheel(b)})}$("wheel_on").attr("style","cursor: pointer; display:none;");$("wheel_off").attr("style","cursor: pointer; ");this.wheel=true},disableWheel:function(a){if(this.wheel){dotSvg.stopObserving(document,"mousewheel");dotSvg.stopObserving(document,"DOMMouseScroll")}$("wheel_on").attr("style","cursor: pointer; ");$("wheel_off").attr("style","cursor: pointer; display:none");this.wheel=false},keyboard:function(a){a=a||window.event;if(a.keyCode==40){this.y+=20;this.update()}else{if(a.keyCode==38){this.y-=20;this.update()}else{if(a.keyCode==39){this.x+=20;this.update()}else{if(a.keyCode==37){this.x-=20;this.update()}else{if(a.keyCode==187||a.keyCode==107){this.zoomIn()}else{if(a.keyCode==189||a.keyCode==109){this.zoomOut()}}}}}}},mousewheel:function(a){var b=0;a=a||window.event;if(!a){a=window.event}if(a.wheelDelta){b=a.wheelDelta/120;if(window.opera&&window.opera.version&&window.opera.version()<10){b=-b}}else{if(a.detail){b=-a.detail/3}}if(b>0){this.zoomIn(a,b)}if(b<0){this.zoomOut(a,b)}if(a.preventDefault){a.preventDefault()}a.returnValue=false},resize:function(a,b){if(this.adminMode){return}this.viewW=(a<0)?0:a;this.viewH=(b<0)?0:b;this.zMin=((this.viewH/this.docH)>(this.viewW/this.docW))?(this.viewW/this.docW)*100:(this.viewH/this.docH)*100;this.zoom=(this.zMin>this.zoom)?this.zMin:this.zoom;this.setLimit();this.update()},setLimit:function(){this.limitXR=((this.docW*(this.zoom/100))-this.viewW<0)?((this.docW*(this.zoom/100))-this.viewW)/2:(this.docW*(this.zoom/100))-this.viewW;this.limitXL=(this.limitXR<0)?this.limitXR:0;this.limitY=((this.docH*(this.zoom/100))-this.viewH<0)?0:(this.docH*(this.zoom/100))-this.viewH},update:function(){if(isNaN(this.x)){this.x=0}else{if(this.xthis.limitXR){this.x=this.limitXR}}}if(isNaN(this.y)){this.y=0}else{if(this.y<0){this.y=0}else{if(this.y>this.limitY){this.y=this.limitY}}}this.zoom=isNaN(this.zoom)?this.zMin:(this.zoom<=this.zMin)?this.zMin:(this.zoom>=this.zMax)?this.zMax:this.zoom;this.vb_x=(Math.round((this.x/(this.zoom/100))*100)/100);this.vb_y=(Math.round((this.y/(this.zoom/100))*100)/100);this.vb_w=(Math.round((this.docW/(this.zoom/100))*100)/100);this.vb_h=(Math.round((this.docH/(this.zoom/100))*100)/100);this.transform="translate("+(this.vb_x*(this.zoom/100)*-1)+","+(this.vb_y*(this.zoom/100)*-1)+")";this.scale="scale("+(this.zoom/100)+")";$("map").attr("transform",this.transform+" "+this.scale);if(this.oldZoom!=this.zoom){var a=(Math.round((1/(this.zoom/100))*10)/10)+"";$$(".j").each(function(b){b.attr("style","stroke-width: "+a)});$$(".jc").each(function(b){b.attr("style","stroke-width: "+a)});$$(".jr").each(function(b){b.attr("style","stroke-width: "+a)});this.oldZoom=this.zoom}},debug:function(a){$("debug").firstChild.data=a},green:function(a){if(window&&window.parent&&window.parent.document&&window.parent.document.getElementById("header_sub")){window.parent.document.getElementById("header_sub").innerHTML=a}},moveStart:function(a){if(a.button>0){return}this.drag=true;this.oldX=a.pageX;this.oldY=a.pageY},moveDo:function(a){if(!this.drag){return false}this.x=this.x-parseInt(a.pageX-this.oldX);this.y=this.y-parseInt(a.pageY-this.oldY);this.oldX=a.pageX;this.oldY=a.pageY;this.update()},moveStop:function(a){this.drag=false},setZoom:function(b,a,c){b=(b<=this.zMin)?this.zMin:(b>=this.zMax)?this.zMax:b;if(this.zoom==b&&typeof(a)=="undefined"&&typeof(c)=="undefined"){return}this.zoom=b;this.setLimit();if(typeof(a)!="undefined"&&typeof(c)!="undefined"){this.setPos(a,c)}else{this.update()}},setPos:function(a,b){if(typeof(a)!="undefined"&&typeof(b)!="undefined"){this.x=Math.round((a/this.docW)*this.limitX);this.y=Math.round((b/this.docH)*this.limitY)}this.update()},zoomIn:function(b,e){var a=Math.floor((this.zoom+25)/25)*25;var d=(b&&b.pageX)?b.pageX:this.viewW/2;var c=(b&&b.pageY)?b.pageY:this.viewH/2;a=(a<=this.zMin)?this.zMin:(a>=this.zMax)?this.zMax:a;this.x=Math.round(((this.x+d)/(this.zoom/100))*(a/100))-d;this.y=Math.round(((this.y+c)/(this.zoom/100))*(a/100))-c;this.setZoom(a)},zoomOut:function(b,e){var a=Math.floor((this.zoom-25)/25)*25;var d=(b&&b.pageX)?b.pageX:this.viewW/2;var c=(b&&b.pageY)?b.pageY:this.viewH/2;a=(a<=this.zMin)?this.zMin:(a>=this.zMax)?this.zMax:a;this.x=Math.round(((this.x+d)/(this.zoom/100))*(a/100))-d;this.y=Math.round(((this.y+c)/(this.zoom/100))*(a/100))-c;this.setZoom(a)},clearStandings:function(){$("standings").clear()},getStandingImage:function(a){if(a>5){return"/images/icons/standing_plus_10.png"}else{if(a>0){return"/images/icons/standing_plus_5.png"}else{if(a<-5){return"/images/icons/standing_minus_10.png"}else{if(a<0){return"/images/icons/standing_minus_5.png"}else{return"/images/pixel.gif"}}}}},addStanding:function(g,d){var e=$("sys"+g);if(!e){return}if(!d){$("standings").clear()}var b=parseInt(e.attr("x")),f=parseInt(e.attr("y"));var c=dotSvg.create("a",{id:"standa"+g,"xlink:title":d>0?"+"+d:d,});$(c).insert(dotSvg.create("image",{"xlink:href":this.getStandingImage(d),id:"stand"+g,height:7,width:7,x:b,y:f,"class":"scl"}));$("standings").insert(c)},clearNotes:function(){$("notes").clear()},addNote:function(e){var c=$("sys"+e);if(!c){return}var a=parseInt(c.attr("x")),d=parseInt(c.attr("y"))+18;var b=dotSvg.create("image",{"xlink:href":"/images/icons/note_mini.png",id:"note"+e,height:11,width:10,x:a,y:d,style:"cursor: pointer","class":"note"}).on("mouseover",function(f){if(window.parent&&window.parent.dotNotes){window.parent.dotNotes.over(e,f,true)}}).on("mouseout",function(f){if(window.parent&&window.parent.dotNotes){window.parent.dotNotes.out(e,f)}}).on("click",function(f){if(window.parent&&window.parent.dotNotes){window.parent.dotNotes.click(e,f)}});$("notes").insert(b)},clearCampaigns:function(){$("notes").clear()},addCampaign:function(g,e){var d=$("sys"+g);if(!d){return}var a=parseInt(d.attr("x"))-13.5,f=parseInt(d.attr("y"))-8.5;var b=dotSvg.create("use",{"xlink:href":e?"#defCampaignActive":"#defCampaign",id:"cam"+g,x:a,y:f});$("campaigns").insert(b);var c=$("rect"+g);if(!c){return}c.attr("class",c.attr("class")+" sc")}}; \ No newline at end of file diff --git a/src/vi/ui/systemtray.py b/src/vi/ui/systemtray.py index 5379f2f..0b21edc 100644 --- a/src/vi/ui/systemtray.py +++ b/src/vi/ui/systemtray.py @@ -20,39 +20,39 @@ import time from six.moves import range -from PyQt4 import QtGui, QtCore, Qt -from PyQt4.QtGui import QAction, QActionGroup -from PyQt4.QtGui import QIcon, QSystemTrayIcon +from PyQt5 import QtGui, QtCore, Qt, QtWidgets +from PyQt5.QtWidgets import QAction, QActionGroup, QSystemTrayIcon +from PyQt5.QtGui import QIcon +from pkg_resources import resource_filename -from vi.resources import resourcePath from vi import states from vi.soundmanager import SoundManager -from PyQt4.QtCore import SIGNAL +from PyQt5.QtCore import pyqtSignal -class TrayContextMenu(QtGui.QMenu): +class TrayContextMenu(QtWidgets.QMenu): instances = set() def __init__(self, trayIcon): """ trayIcon = the object with the methods to call """ - QtGui.QMenu.__init__(self) + QtWidgets.QMenu.__init__(self) TrayContextMenu.instances.add(self) self.trayIcon = trayIcon self._buildMenu() def _buildMenu(self): - self.framelessCheck = QtGui.QAction("Frameless Window", self, checkable=True) - self.connect(self.framelessCheck, SIGNAL("triggered()"), self.trayIcon.changeFrameless) + self.framelessCheck = QtWidgets.QAction("Frameless Window", self, checkable=True) + self.framelessCheck.triggered.connect(self.trayIcon.changeFrameless) self.addAction(self.framelessCheck) self.addSeparator() - self.requestCheck = QtGui.QAction("Show status request notifications", self, checkable=True) + self.requestCheck = QtWidgets.QAction("Show status request notifications", self, checkable=True) self.requestCheck.setChecked(True) self.addAction(self.requestCheck) - self.connect(self.requestCheck, SIGNAL("triggered()"), self.trayIcon.switchRequest) - self.alarmCheck = QtGui.QAction("Show alarm notifications", self, checkable=True) + self.requestCheck.triggered.connect(self.trayIcon.switchRequest) + self.alarmCheck = QtWidgets.QAction("Show alarm notifications", self, checkable=True) self.alarmCheck.setChecked(True) - self.connect(self.alarmCheck, SIGNAL("triggered()"), self.trayIcon.switchAlarm) + self.alarmCheck.triggered.connect(self.trayIcon.switchAlarm) self.addAction(self.alarmCheck) distanceMenu = self.addMenu("Alarm Distance") self.distanceGroup = QActionGroup(self) @@ -61,13 +61,13 @@ def _buildMenu(self): if i == 0: action.setChecked(True) action.alarmDistance = i - self.connect(action, SIGNAL("triggered()"), self.changeAlarmDistance) + action.triggered.connect(self.changeAlarmDistance) self.distanceGroup.addAction(action) distanceMenu.addAction(action) self.addMenu(distanceMenu) self.addSeparator() self.quitAction = QAction("Quit", self) - self.connect(self.quitAction, SIGNAL("triggered()"), self.trayIcon.quit) + self.quitAction.triggered.connect(self.trayIcon.quit) self.addAction(self.quitAction) def changeAlarmDistance(self): @@ -77,12 +77,14 @@ def changeAlarmDistance(self): self.trayIcon.changeAlarmDistance() -class TrayIcon(QtGui.QSystemTrayIcon): +class TrayIcon(QtWidgets.QSystemTrayIcon): + alarm_distance = pyqtSignal(int) + quit_sig = pyqtSignal() # Min seconds between two notifications MIN_WAIT_NOTIFICATION = 15 def __init__(self, app): - self.icon = QIcon(resourcePath("vi/ui/res/logo_small.png")) + self.icon = QIcon(resource_filename(__name__,"res/logo_small.png")) QSystemTrayIcon.__init__(self, self.icon, app) self.setToolTip("Your Vintel-Information-Service! :)") self.lastNotifications = {} @@ -93,17 +95,17 @@ def __init__(self, app): def changeAlarmDistance(self): distance = self.alarmDistance - self.emit(SIGNAL("alarm_distance"), distance) + self.alarm_distance.emit(distance) def changeFrameless(self): - self.emit(SIGNAL("change_frameless")) + self.emit(pyqtSignal("change_frameless")) @property def distanceGroup(self): return self.contextMenu().distanceGroup def quit(self): - self.emit(SIGNAL("quit")) + self.emit(pyqtSignal("quit")) def switchAlarm(self): newValue = not self.showAlarm diff --git a/src/vi/ui/viui.py b/src/vi/ui/viui.py index a253f1e..0c15c82 100644 --- a/src/vi/ui/viui.py +++ b/src/vi/ui/viui.py @@ -22,27 +22,28 @@ import time import six import requests -import webbrowser -import vi.version + +import vi.PanningWebView +import vi.version +from vi.ui.ChatEntryWidget import ChatEntryWidget +from pkg_resources import resource_string, resource_stream, resource_filename import logging -from PyQt4.QtGui import * -from PyQt4 import QtGui, uic, QtCore -from PyQt4.QtCore import QPoint, SIGNAL -from PyQt4.QtGui import QImage, QPixmap, QMessageBox -from PyQt4.QtWebKit import QWebPage +from PyQt5 import uic, QtCore, QtWidgets +from PyQt5.QtCore import QPoint, pyqtSignal, QTimer +from PyQt5.QtWidgets import QMessageBox, QAction, QActionGroup, QStyleOption, QStyle +from PyQt5.QtGui import QPainter, QIcon, QPixmap + from vi import amazon_s3, evegate from vi import dotlan, filewatcher from vi import states from vi.cache.cache import Cache -from vi.resources import resourcePath from vi.soundmanager import SoundManager from vi.threads import AvatarFindThread, KOSCheckerThread, MapStatisticsThread from vi.ui.systemtray import TrayContextMenu from vi.chatparser import ChatParser -from PyQt4.QtGui import QAction -from PyQt4.QtGui import QMessageBox + # Timer intervals MESSAGE_EXPIRY_SECS = 20 * 60 @@ -50,31 +51,34 @@ CLIPBOARD_CHECK_INTERVAL_MSECS = 4 * 1000 -class MainWindow(QtGui.QMainWindow): +class MainWindow(QtWidgets.QMainWindow): + chat_message_added = pyqtSignal(object) + avatar_loaded = pyqtSignal(str,bytes) - def __init__(self, pathToLogs, trayIcon, backGroundColor): + def __init__(self, pathToLogs, pathToGameLogs, trayIcon, backGroundColor, logging): - QtGui.QMainWindow.__init__(self) + QtWidgets.QMainWindow.__init__(self) self.cache = Cache() if backGroundColor: self.setStyleSheet("QWidget { background-color: %s; }" % backGroundColor) - uic.loadUi(resourcePath('vi/ui/MainWindow.ui'), self) + uic.loadUi(resource_stream(__name__, '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.taskbarIconQuiescent = QIcon(resource_filename(__name__,'res/logo_small.png')) + self.taskbarIconWorking = QIcon(resource_filename(__name__,'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.pathToGameLogs = pathToGameLogs + self.mapTimer = QTimer(self) + self.mapTimer.timeout.connect(self.updateMapView) + self.clipboardTimer = QTimer(self) self.oldClipboardContent = "" self.trayIcon = trayIcon self.trayIcon.activated.connect(self.systemTrayActivated) - self.clipboard = QtGui.QApplication.clipboard() + self.clipboard = QtWidgets.QApplication.clipboard() self.clipboard.clear(mode=self.clipboard.Clipboard) self.alarmDistance = 0 self.lastStatisticsUpdate = 0 @@ -83,7 +87,8 @@ def __init__(self, pathToLogs, trayIcon, backGroundColor): self.scanIntelForKosRequestsEnabled = True self.initialMapPosition = None self.mapPositionsDict = {} - + self.deferedScrollPosition = None + self.logging = logging # Load user's toon names self.knownPlayerNames = self.cache.getFromCache("known_player_names") if self.knownPlayerNames: @@ -91,7 +96,7 @@ def __init__(self, pathToLogs, trayIcon, backGroundColor): 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." - QMessageBox.warning(None, "Known Characters not Found", diagText, "Ok") + QMessageBox.warning(None, "Known Characters not Found", diagText, QMessageBox.Ok) # Set up user's intel rooms roomnames = self.cache.getFromCache("room_names") @@ -115,7 +120,7 @@ def __init__(self, pathToLogs, trayIcon, backGroundColor): if i == 100: action.setChecked(True) action.opacity = i / 100.0 - self.connect(action, SIGNAL("triggered()"), self.changeOpacity) + action.triggered.connect(self.changeOpacity) self.opacityGroup.addAction(action) self.menuTransparency.addAction(action) @@ -155,59 +160,82 @@ def recallCachedSettings(self): 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.mapView.page().scrollRequested.connect(self.mapPositionChanged) - + self.clipboard.changed.connect(self.clipboardChanged) + + self.autoScanIntelAction.triggered.connect(self.changeAutoScanIntel) + self.kosClipboardActiveAction.triggered.connect(self.changeKosCheckClipboard) + self.zoomInButton.clicked.connect(self.zoomMapIn) + self.zoomOutButton.clicked.connect(self.zoomMapOut) + self.statisticsButton.clicked.connect(self.changeStatisticsVisibility) + self.jumpbridgesButton.clicked.connect(self.changeJumpbridgesVisibility) + self.chatLargeButton.clicked.connect(self.chatLarger) + self.chatSmallButton.clicked.connect(self.chatSmaller) + self.infoAction.triggered.connect(self.showInfo) + self.showChatAvatarsAction.triggered.connect(self.changeShowAvatars) + self.alwaysOnTopAction.triggered.connect(self.changeAlwaysOnTop) + self.chooseChatRoomsAction.triggered.connect(self.showChatroomChooser) + self.catchRegionAction.triggered.connect(lambda : self.handleRegionMenuItemSelected(self.catchRegionAction)) + self.providenceRegionAction.triggered.connect( lambda : self.handleRegionMenuItemSelected(self.providenceRegionAction)) + self.queriousRegionAction.triggered.connect(lambda : self.handleRegionMenuItemSelected(self.queriousRegionAction)) + self.providenceCatchRegionAction.triggered.connect(lambda : self.handleRegionMenuItemSelected(self.providenceCatchRegionAction)) + self.providenceCatchCompactRegionAction.triggered.connect(lambda : self.handleRegionMenuItemSelected(self.providenceCatchCompactRegionAction)) + self.chooseRegionAction.triggered.connect(self.showRegionChooser) + self.showChatAction.triggered.connect(self.changeChatVisibility) + self.soundSetupAction.triggered.connect(self.showSoundSetup) + self.activateSoundAction.triggered.connect(self.changeSound) + self.useSpokenNotificationsAction.triggered.connect(self.changeUseSpokenNotifications) + self.trayIcon.alarm_distance.connect(self.changeAlarmDistance) + self.framelessWindowAction.triggered.connect(self.changeFrameless) + #self.trayIcon.change_frameless.connect(self.changeFrameless) + self.frameButton.clicked.connect(self.changeFrameless) + self.quitAction.triggered.connect(self.close) + self.trayIcon.quit_sig.connect(self.close) + self.jumpBridgeDataAction.triggered.connect(self.showJumpBridgeChooser) + #self.mapView.page().scrollRequested.connect(self.mapPositionChanged) + self.mapView.page().mapLinkClicked.connect( self.mapLinkClicked) + self.mapView.loadFinished.connect(self.handleLoadFinished) + pass + + def handleLoadFinished(self): + self.loaded = True + if self.deferedScrollPosition: + self.mapView.page().runJavaScript('window.scrollTo({}, {});'.format(self.deferedScrollPosition.x(), self.deferedScrollPosition.y())) + self.deferedScrollPosition = None + + def setMapScrollPosition(self, position): + if self.loaded: + self.mapView.page().runJavaScript('window.scrollTo({}, {});'.format(position.x(), position.y())) + else: + self.deferedScrollPosition = position + + def getMapScrollPosition(self): + return self.mapView.page().scrollPosition() def setupThreads(self): # Set up threads and their connections self.avatarFindThread = AvatarFindThread() - self.connect(self.avatarFindThread, SIGNAL("avatar_update"), self.updateAvatarOnChatEntry) + self.avatarFindThread.avatar_update.connect(self.updateAvatarOnChatEntry) self.avatarFindThread.start() self.kosRequestThread = KOSCheckerThread() - self.connect(self.kosRequestThread, SIGNAL("kos_result"), self.showKosResult) + self.kosRequestThread.kos_result.connect(self.showKosResult) self.kosRequestThread.start() self.filewatcherThread = filewatcher.FileWatcher(self.pathToLogs) - self.connect(self.filewatcherThread, SIGNAL("file_change"), self.logFileChanged) + 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.connect(self.versionCheckThread, SIGNAL("newer_version"), self.notifyNewerVersion) + self.versionCheckThread.newer_version.connect(self.notifyNewerVersion) self.versionCheckThread.start() self.statisticsThread = MapStatisticsThread() - self.connect(self.statisticsThread, SIGNAL("statistic_data_update"), self.updateStatisticsOnMap) + self.statisticsThread.statisticDataUpdate.connect(self.updateStatisticsOnMap) self.statisticsThread.start() # statisticsThread is blocked until first call of requestStatistics @@ -215,23 +243,23 @@ def setupThreads(self): def setupMap(self, initialize=False): self.mapTimer.stop() self.filewatcherThread.paused = True - + self.fileWatcherForGameLogsThread.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: + with open(resource_filename(__name__,"res/mapdata/{0}.svg".format(regionName))) as svgFile: svg = svgFile.read() except Exception as e: - pass + logging.critical(e) 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") + QMessageBox.critical(None, "Error getting map", six.text_type(e), QMessageBox.Ok) sys.exit(1) if self.dotlan.outdatedCacheError: @@ -239,7 +267,7 @@ def setupMap(self, initialize=False): 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") + QMessageBox.warning(None, "Using map from cache", diagText, QMessageBox.Ok) # Load the jumpbridges @@ -247,8 +275,8 @@ def setupMap(self, initialize=False): 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) - + self.chatparser = ChatParser(self.pathToLogs, self.roomnames, self.systems, self.logging) + # Menus - only once if initialize: logging.critical("Initializing contextual menus") @@ -260,9 +288,6 @@ def mapContextMenuEvent(event): 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) @@ -286,6 +311,7 @@ def mapContextMenuEvent(event): self.mapTimer.start(MAP_UPDATE_INTERVAL_MSECS) # Allow the file watcher to run now that all else is set up self.filewatcherThread.paused = False + self.fileWatcherForGameLogsThread.paused = False logging.critical("Map setup complete") @@ -305,13 +331,13 @@ def startClipboardTimer(self): 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.timeout.connect(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.timeout.connect(self.clipboardChanged) self.clipboardTimer.stop() @@ -325,9 +351,9 @@ def closeEvent(self, event): 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())), + settings = ((None, "restoreGeometry", self.saveGeometry()), (None, "restoreState", self.saveState()), + ("splitter", "restoreGeometry", self.splitter.saveGeometry()), + ("splitter", "restoreState", self.splitter.saveState()), ("mapView", "setZoomFactor", self.mapView.zoomFactor()), (None, "changeChatFontSize", ChatEntryWidget.TEXT_SIZE), (None, "changeOpacity", self.opacityGroup.checkedAction().opacity), @@ -351,6 +377,8 @@ def closeEvent(self, event): self.avatarFindThread.wait() self.filewatcherThread.quit() self.filewatcherThread.wait() + self.fileWatcherForGameLogsThread.quit() + self.fileWatcherForGameLogsThread.wait() self.kosRequestThread.quit() self.kosRequestThread.wait() self.versionCheckThread.quit() @@ -413,7 +441,7 @@ def changeSound(self, newValue=None, disable=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") + QMessageBox.Ok) else: if newValue is None: newValue = self.activateSoundAction.isChecked() @@ -517,13 +545,17 @@ def clipboardChanged(self, mode=0): 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() - + try: + system = self.systems[str(systemName)] + sc = SystemChat(self, SystemChat.SYSTEM, system, self.chatEntries, self.knownPlayerNames) + self.chat_message_added.connect(sc.addChatEntry) + self.avatar_loaded.connect(sc.newAvatarAvailable) + sc.signal_location_set.connect(self.setLocation) + sc.show() + except Exception as e: + # We didn't click a system url + logging.critical("mapLinkClicked->Error::{0}".format(e)) + pass def markSystemOnMap(self, systemname): self.systems[six.text_type(systemname)].mark() @@ -539,18 +571,20 @@ def setLocation(self, char, newSystem): def setMapContent(self, content): + self.loaded = False 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) + scrollPosition = self.getMapScrollPosition() + else: + scrollPosition = self.initialMapPosition + self.mapView.setHtml(content) # 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(): + scrollPosition = self.mapView.page().scrollPosition() + self.scrollPosition = self.setMapScrollPosition(scrollPosition) + scrollPosition = self.getMapScrollPosition() + + if scrollPosition and (scrollPosition.x() or scrollPosition.y()): self.initialMapPosition = None @@ -578,14 +612,14 @@ def mapPositionChanged(self, dx, dy, rectToScroll): def showChatroomChooser(self): chooser = ChatroomsChooser(self) - chooser.connect(chooser, SIGNAL("rooms_changed"), self.changedRoomnames) + chooser.roomsChanged.connect(self.changedRoomnames) chooser.show() - def showJumbridgeChooser(self): + def showJumpBridgeChooser(self): url = self.cache.getFromCache("jumpbridge_url") - chooser = JumpbridgeChooser(self, url) - chooser.connect(chooser, SIGNAL("set_jumpbridge_url"), self.setJumpbridges) + chooser = JumpBridgeChooser(self, url) + chooser.setJumpbridgeURL.connect(self.setJumpbridges) chooser.show() @@ -609,7 +643,7 @@ def setJumpbridges(self, url): 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") + QMessageBox.warning(None, "Loading jumpbridges failed!", "Error: {0}".format(six.text_type(e)), QMessageBox.Ok) def handleRegionMenuItemSelected(self, menuAction=None): @@ -621,7 +655,7 @@ def handleRegionMenuItemSelected(self, menuAction=None): self.chooseRegionAction.setChecked(False) if menuAction: menuAction.setChecked(True) - regionName = six.text_type(menuAction.property("regionName").toString()) + regionName = six.text_type(menuAction.property("regionName")) regionName = dotlan.convertRegionName(regionName) Cache().putIntoCache("region_name", regionName, 60 * 60 * 24 * 365) self.setupMap() @@ -635,7 +669,7 @@ def handleRegionChosen(): self.chooseRegionAction.setChecked(False) chooser = RegionChooser(self) - self.connect(chooser, SIGNAL("new_region_chosen"), handleRegionChosen) + chooser.newRegionChosen.connect( handleRegionChosen) chooser.show() @@ -644,14 +678,14 @@ def addMessageToIntelChat(self, message): if (self.chatListWidget.verticalScrollBar().value() == self.chatListWidget.verticalScrollBar().maximum()): scrollToBottom = True chatEntryWidget = ChatEntryWidget(message) - listWidgetItem = QtGui.QListWidgetItem(self.chatListWidget) + listWidgetItem = QtWidgets.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) + chatEntryWidget.mark_system.connect(self.markSystemOnMap) + self.chat_message_added.emit(chatEntryWidget) self.pruneMessages() if scrollToBottom: self.chatListWidget.scrollToBottom() @@ -706,26 +740,26 @@ def changedRoomnames(self, newRoomnames): def showInfo(self): - infoDialog = QtGui.QDialog(self) - uic.loadUi(resourcePath("vi/ui/Info.ui"), infoDialog) + infoDialog = QtWidgets.QDialog(self) + uic.loadUi(resource_stream(__name__,"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.logoLabel.setPixmap(QPixmap(resource_filename(__name__,"res/logo.png"))) + infoDialog.closeButton.clicked.connect(infoDialog.accept) infoDialog.show() def showSoundSetup(self): - dialog = QtGui.QDialog(self) - uic.loadUi(resourcePath("vi/ui/SoundSetup.ui"), dialog) + dialog = QtWidgets.QDialog(self) + uic.loadUi(resource_stream(__name__,"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.volumeSlider.valueChanged.connect(SoundManager().setSoundVolume) + dialog.testSoundButton.clicked.connect(SoundManager().playSound) + dialog.closeButton.clicked.connect(dialog.accept) dialog.show() def systemTrayActivated(self, reason): - if reason == QtGui.QSystemTrayIcon.Trigger: + if reason == QtWidgets.QSystemTrayIcon.Trigger: if self.isMinimized(): self.showNormal() self.activateWindow() @@ -740,7 +774,7 @@ def updateAvatarOnChatEntry(self, chatEntry, avatarData): if not updated: self.avatarFindThread.addChatEntry(chatEntry, clearCache=True) else: - self.emit(SIGNAL("avatar_loaded"), chatEntry.message.user, avatarData) + self.avatar_loaded.emit(chatEntry.message.user, avatarData) def updateStatisticsOnMap(self, data): @@ -754,7 +788,7 @@ def updateStatisticsOnMap(self, data): logging.error("updateStatisticsOnMap, error: %s" % text) - def updateMapView(self): + def updateMapView(self): logging.debug("Updating map start") self.setMapContent(self.dotlan.svg) logging.debug("Updating map complete") @@ -804,13 +838,15 @@ def logFileChanged(self, path): self.setMapContent(self.dotlan.svg) -class ChatroomsChooser(QtGui.QDialog): +class ChatroomsChooser(QtWidgets.QDialog): + roomsChanged = pyqtSignal(list) + def __init__(self, parent): - QtGui.QDialog.__init__(self, parent) - uic.loadUi(resourcePath("vi/ui/ChatroomsChooser.ui"), self) - self.connect(self.defaultButton, SIGNAL("clicked()"), self.setDefaults) - self.connect(self.cancelButton, SIGNAL("clicked()"), self.accept) - self.connect(self.saveButton, SIGNAL("clicked()"), self.saveClicked) + QtWidgets.QDialog.__init__(self, parent) + uic.loadUi(resource_stream(__name__,"ChatroomsChooser.ui"), self) + self.defaultButton.clicked.connect(self.setDefaults) + self.cancelButton.clicked.connect( self.accept) + self.saveButton.clicked.connect(self.saveClicked) cache = Cache() roomnames = cache.getFromCache("room_names") if not roomnames: @@ -822,19 +858,22 @@ def saveClicked(self): text = six.text_type(self.roomnamesField.toPlainText()) rooms = [six.text_type(name.strip()) for name in text.split(",")] self.accept() - self.emit(SIGNAL("rooms_changed"), rooms) + self.roomsChanged.emit(rooms) def setDefaults(self): self.roomnamesField.setPlainText(u"TheCitadel,North Provi Intel,North Catch Intel,North Querious Intel") -class RegionChooser(QtGui.QDialog): +class RegionChooser(QtWidgets.QDialog): + + newRegionChosen = pyqtSignal() + def __init__(self, parent): - QtGui.QDialog.__init__(self, parent) - uic.loadUi(resourcePath("vi/ui/RegionChooser.ui"), self) - self.connect(self.cancelButton, SIGNAL("clicked()"), self.accept) - self.connect(self.saveButton, SIGNAL("clicked()"), self.saveClicked) + QtWidgets.QDialog.__init__(self, parent) + uic.loadUi(resource_stream(__name__,"RegionChooser.ui"), self) + self.cancelButton.clicked.connect(self.accept) + self.saveButton.clicked.connect(self.saveClicked) cache = Cache() regionName = cache.getFromCache("region_name") if not regionName: @@ -844,6 +883,7 @@ def __init__(self, parent): def saveClicked(self): text = six.text_type(self.regionNameField.toPlainText()) + text = text.strip() text = dotlan.convertRegionName(text) self.regionNameField.setPlainText(text) correct = False @@ -854,7 +894,7 @@ def saveClicked(self): correct = False # Fallback -> ships vintel with this map? try: - with open(resourcePath("vi/ui/res/mapdata/{0}.svg".format(text))) as _: + with open(resource_filename(__name__,"res/mapdata/{0}.svg".format(text))) as _: correct = True except Exception as e: logging.error(e) @@ -870,15 +910,16 @@ def saveClicked(self): if correct: Cache().putIntoCache("region_name", text, 60 * 60 * 24 * 365) self.accept() - self.emit(SIGNAL("new_region_chosen")) + self.newRegionChosen.emit() -class SystemChat(QtGui.QDialog): +class SystemChat(QtWidgets.QDialog): SYSTEM = 0 + signal_location_set = pyqtSignal(str,str) def __init__(self, parent, chatType, selector, chatEntries, knownPlayerNames): - QtGui.QDialog.__init__(self, parent) - uic.loadUi(resourcePath("vi/ui/SystemChat.ui"), self) + QtWidgets.QDialog.__init__(self, parent) + uic.loadUi(resource_stream(__name__,"SystemChat.ui"), self) self.parent = parent self.chatType = 0 self.selector = selector @@ -892,10 +933,10 @@ def __init__(self, parent, chatType, selector, chatEntries, knownPlayerNames): for name in knownPlayerNames: self.playerNamesBox.addItem(name) self.setWindowTitle("Chat for {0}".format(titleName)) - self.connect(self.closeButton, SIGNAL("clicked()"), self.closeDialog) - self.connect(self.alarmButton, SIGNAL("clicked()"), self.setSystemAlarm) - self.connect(self.clearButton, SIGNAL("clicked()"), self.setSystemClear) - self.connect(self.locationButton, SIGNAL("clicked()"), self.locationSet) + self.closeButton.clicked.connect(self.closeDialog) + self.alarmButton.clicked.connect(self.setSystemAlarm) + self.clearButton.clicked.connect(self.setSystemClear) + self.locationButton.clicked.connect(self.locationSet) def _addMessageToChat(self, message, avatarPixmap): @@ -904,12 +945,12 @@ def _addMessageToChat(self, message, avatarPixmap): scrollToBottom = True entry = ChatEntryWidget(message) entry.avatarLabel.setPixmap(avatarPixmap) - listWidgetItem = QtGui.QListWidgetItem(self.chat) + listWidgetItem = QtWidgets.QListWidgetItem(self.chat) listWidgetItem.setSizeHint(entry.sizeHint()) self.chat.addItem(listWidgetItem) self.chat.setItemWidget(listWidgetItem, entry) self.chatEntries.append(entry) - self.connect(entry, SIGNAL("mark_system"), self.parent.markSystemOnMap) + entry.mark_system.connect(self.parent.markSystemOnMap) if scrollToBottom: self.chat.scrollToBottom() @@ -924,7 +965,7 @@ def addChatEntry(self, entry): def locationSet(self): char = six.text_type(self.playerNamesBox.currentText()) - self.emit(SIGNAL("location_set"), char, self.system.name) + self.signal_location_set.emit(char, self.system.name) def newAvatarAvailable(self, charname, avatarData): @@ -947,70 +988,19 @@ def closeDialog(self): self.accept() -class ChatEntryWidget(QtGui.QWidget): - TEXT_SIZE = 11 - SHOW_AVATAR = True - questionMarkPixmap = None - - def __init__(self, message): - QtGui.QWidget.__init__(self) - if not self.questionMarkPixmap: - self.questionMarkPixmap = QtGui.QPixmap(resourcePath("vi/ui/res/qmark.png")).scaledToHeight(32) - uic.loadUi(resourcePath("vi/ui/ChatEntry.ui"), self) - self.avatarLabel.setPixmap(self.questionMarkPixmap) - self.message = message - self.updateText() - self.connect(self.textLabel, SIGNAL("linkActivated(QString)"), self.linkClicked) - if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"): - ChatEntryWidget.TEXT_SIZE = 8 - self.changeFontSize(self.TEXT_SIZE) - if not ChatEntryWidget.SHOW_AVATAR: - self.avatarLabel.setVisible(False) - - - def linkClicked(self, link): - link = six.text_type(link) - function, parameter = link.split("/", 1) - if function == "mark_system": - self.emit(SIGNAL("mark_system"), parameter) - elif function == "link": - webbrowser.open(parameter) - - - def updateText(self): - time = datetime.datetime.strftime(self.message.timestamp, "%H:%M:%S") - text = u"{time} - {user} - {room}
{text}".format(user=self.message.user, - room=self.message.room, - time=time, - text=self.message.message) - self.textLabel.setText(text) - - - def updateAvatar(self, avatarData): - image = QImage.fromData(avatarData) - pixmap = QPixmap.fromImage(image) - if pixmap.isNull(): - return False - scaledAvatar = pixmap.scaled(32, 32) - self.avatarLabel.setPixmap(scaledAvatar) - return True - - def changeFontSize(self, newSize): - font = self.textLabel.font() - font.setPointSize(newSize) - self.textLabel.setFont(font) +class JumpBridgeChooser(QtWidgets.QDialog): + setJumpbridgeURL = pyqtSignal(str) -class JumpbridgeChooser(QtGui.QDialog): def __init__(self, parent, url): - QtGui.QDialog.__init__(self, parent) - uic.loadUi(resourcePath("vi/ui/JumpbridgeChooser.ui"), self) - self.connect(self.saveButton, SIGNAL("clicked()"), self.savePath) - self.connect(self.cancelButton, SIGNAL("clicked()"), self.accept) + QtWidgets.QDialog.__init__(self, parent) + uic.loadUi(resource_stream(__name__,"JumpbridgeChooser.ui"), self) + self.saveButton.clicked.connect(self.savePath) + self.cancelButton.clicked.connect( self.accept) self.urlField.setText(url) # loading format explanation from textfile - with open(resourcePath("docs/jumpbridgeformat.txt")) as f: + with open(resource_filename(__name__,"../../docs/jumpbridgeformat.txt")) as f: self.formatInfoField.setPlainText(f.read()) @@ -1019,7 +1009,7 @@ def savePath(self): url = six.text_type(self.urlField.text()) if url != "": requests.get(url).text - self.emit(SIGNAL("set_jumpbridge_url"), url) + self.setJumpbridgeURL.emit(url) self.accept() except Exception as e: - QMessageBox.critical(None, "Finding Jumpbridgedata failed", "Error: {0}".format(six.text_type(e)), "OK") + QMessageBox.critical(None, "Finding Jumpbridgedata failed", "Error: {0}".format(six.text_type(e)),QMessageBox.Ok) diff --git a/src/vintel.py b/src/vintel.py index 2ebbc95..12088c8 100644 --- a/src/vintel.py +++ b/src/vintel.py @@ -24,16 +24,16 @@ import traceback from logging.handlers import RotatingFileHandler -from logging import StreamHandler +from logging import FileHandler -from PyQt4 import QtGui from vi import version from vi.ui import viui, systemtray from vi.cache import cache -from vi.resources import resourcePath +from pkg_resources import resource_filename from vi.cache.cache import Cache -from PyQt4.QtGui import QApplication, QMessageBox - +from PyQt5.QtWidgets import QApplication, QMessageBox +from PyQt5 import QtGui, QtWidgets +import PyQt5 def exceptHook(exceptionType, exceptionValue, tracebackObject): """ @@ -55,11 +55,18 @@ class Application(QApplication): def __init__(self, args): super(Application, self).__init__(args) - + # Set up paths chatLogDirectory = "" + gameLogDirectory = "" + + if sys.platform != "win32" and len(sys.argv) <=2: + print("Usage: python vintel.py ") + sys.exit(1) if len(sys.argv) > 1: chatLogDirectory = sys.argv[1] + if len(sys.argv) > 2: + gameLogDirectory = sys.argv[2] if not os.path.exists(chatLogDirectory): if sys.platform.startswith("darwin"): @@ -77,8 +84,29 @@ def __init__(self, args): chatLogDirectory = os.path.join(documentsPath, "EVE", "logs", "Chatlogs") if not os.path.exists(chatLogDirectory): # None of the paths for logs exist, bailing out - QMessageBox.critical(None, "No path to Logs", "No logs found at: " + chatLogDirectory, "Quit") + QMessageBox.critical(None, "No path to Logs", "No logs found at: " + chatLogDirectory, QMessageBox.Ok) sys.exit(1) + + + if not os.path.exists(gameLogDirectory): + if sys.platform.startswith("darwin"): + gameLogDirectory = os.path.join(os.path.expanduser("~"), "Documents", "EVE", "logs", "Gamelogs") + if not os.path.exists(gameLogDirectory): + gameLogDirectory = os.path.join(os.path.expanduser("~"), "Library", "Application Support", "Eve Online", + "p_drive", "User", "My Documents", "EVE", "logs", "Gamelogs") + elif sys.platform.startswith("linux"): + gameLogDirectory = os.path.join(os.path.expanduser("~"), "EVE", "logs", "Gamelogs") + elif sys.platform.startswith("win32") or sys.platform.startswith("cygwin"): + import ctypes.wintypes + buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + ctypes.windll.shell32.SHGetFolderPathW(0, 5, 0, 0, buf) + documentsPath = buf.value + gameLogDirectory = os.path.join(documentsPath, "EVE", "logs", "Gamelogs") + if not os.path.exists(gameLogDirectory): + # None of the paths for logs exist, bailing out + QMessageBox.critical(None, "No path to Logs", "No logs found at: " + gameLogDirectory, QMessageBox.Ok) + sys.exit(1) + # Setting local directory for cache and logging vintelDirectory = os.path.join(os.path.dirname(os.path.dirname(chatLogDirectory)), "vintel") @@ -90,7 +118,7 @@ def __init__(self, args): if not os.path.exists(vintelLogDirectory): os.mkdir(vintelLogDirectory) - splash = QtGui.QSplashScreen(QtGui.QPixmap(resourcePath("vi/ui/res/logo.png"))) + splash = QtWidgets.QSplashScreen(QtGui.QPixmap(resource_filename(__name__,"vi/ui/res/logo.png"))) vintelCache = Cache() logLevel = vintelCache.getFromCache("logging_level") @@ -113,9 +141,9 @@ def __init__(self, args): fileHandler.setFormatter(formatter) rootLogger.addHandler(fileHandler) - consoleHandler = StreamHandler() - consoleHandler.setFormatter(formatter) - rootLogger.addHandler(consoleHandler) + fileHandler = RotatingFileHandler(maxBytes=(1048576*5), backupCount=7, filename="vintel.log", mode='a') + fileHandler.setFormatter(formatter) + rootLogger.addHandler(fileHandler) logging.critical("") logging.critical("------------------- Vintel %s starting up -------------------", version.VERSION) @@ -126,7 +154,8 @@ def __init__(self, args): trayIcon = systemtray.TrayIcon(self) trayIcon.show() - self.mainWindow = viui.MainWindow(chatLogDirectory, trayIcon, backGroundColor) + + self.mainWindow = viui.MainWindow(chatLogDirectory, gameLogDirectory, trayIcon, backGroundColor, logging) self.mainWindow.show() self.mainWindow.raise_() splash.finish(self.mainWindow) diff --git a/src/vintel.spec b/src/vintel.spec index 6bf561b..2d17cd8 100644 --- a/src/vintel.spec +++ b/src/vintel.spec @@ -1,56 +1,47 @@ # -*- mode: python -*- -import sys -app_name = 'vintel' +import os +import ntpath +import PyQt5 + block_cipher = None + a = Analysis(['vintel.py'], - pathex=['z:\\mark\\code\\vintel\\src' if sys.platform == 'win32' else '/Users/mark/code/vintel/src'], - binaries=None, - datas=None, - hiddenimports=[], + pathex=['E:\\work\\Repositories\\vintel\\src',os.path.join(ntpath.dirname(PyQt5.__file__), 'Qt', 'bin')], + binaries=[('avbin.dll','.'),], + datas=[ + ('vi/ui/*.ui','vi/ui'), + ('vi/ui/res/*','vi/ui/res'), + ('docs/*','docs') + ], + hiddenimports=[ + 'pyttsx3.drivers', + 'pyttsx3.drivers.dummy', + 'pyttsx3.drivers.espeak', + 'pyttsx3.drivers.nsss', + 'pyttsx3.drivers.sapi5', + ], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher) - -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) - -a.datas += [('vi/ui/MainWindow.ui', 'vi/ui/MainWindow.ui', 'DATA'), - ('vi/ui/SystemChat.ui', 'vi/ui/SystemChat.ui', 'DATA'), - ('vi/ui/ChatEntry.ui', 'vi/ui/ChatEntry.ui', 'DATA'), - ('vi/ui/Info.ui', 'vi/ui/Info.ui', 'DATA'), - ('vi/ui/ChatroomsChooser.ui', 'vi/ui/ChatroomsChooser.ui', 'DATA'), - ('vi/ui/RegionChooser.ui', 'vi/ui/RegionChooser.ui', 'DATA'), - ('vi/ui/SoundSetup.ui', 'vi/ui/SoundSetup.ui', 'DATA'), - ('vi/ui/JumpbridgeChooser.ui', 'vi/ui/JumpbridgeChooser.ui', 'DATA'), - ('vi/ui/res/qmark.png', 'vi/ui/res/qmark.png', 'DATA'), - ('vi/ui/res/logo.png', 'vi/ui/res/logo.png', 'DATA'), - ('vi/ui/res/logo_small.png', 'vi/ui/res/logo_small.png', 'DATA'), - ('vi/ui/res/logo_small_green.png', 'vi/ui/res/logo_small_green.png', 'DATA'), - ('vi/ui/res/178028__zimbot__bosun-whistle-sttos-recreated.wav', 'vi/ui/res/178028__zimbot__bosun-whistle-sttos-recreated.wav', 'DATA'), - ('vi/ui/res/178031__zimbot__transporterstartbeep0-sttos-recreated.wav', 'vi/ui/res/178031__zimbot__transporterstartbeep0-sttos-recreated.wav', 'DATA'), - ('vi/ui/res/178032__zimbot__redalert-klaxon-sttos-recreated.wav', 'vi/ui/res/178032__zimbot__redalert-klaxon-sttos-recreated.wav', 'DATA'), - ('vi/ui/res/mapdata/Providencecatch.svg', 'vi/ui/res/mapdata/Providencecatch.svg', 'DATA'), - ('docs/jumpbridgeformat.txt', 'docs/jumpbridgeformat.txt', 'DATA'), - ] - +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) exe = EXE(pyz, a.scripts, - a.binaries, - a.zipfiles, - a.datas, - name=os.path.join('dist', app_name + ('.exe' if sys.platform == 'win32' else '')), + exclude_binaries=True, + name='vintel', debug=False, strip=False, - icon='icon.ico', - console=False, - cipher=block_cipher) - -# Build a .app if on OS X -if sys.platform == 'darwin': - app = BUNDLE(exe, - name=app_name + '.app', - icon='icon.ico') + upx=True, + console=False ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='vintel')