diff --git a/resources/arrow_undo.png b/resources/arrow_undo.png
new file mode 100644
index 0000000..6972c5e
Binary files /dev/null and b/resources/arrow_undo.png differ
diff --git a/resources/clock_go.png b/resources/clock_go.png
new file mode 100644
index 0000000..a1a24d3
Binary files /dev/null and b/resources/clock_go.png differ
diff --git a/resources/control_pause_blue.png b/resources/control_pause_blue.png
new file mode 100644
index 0000000..ec61099
Binary files /dev/null and b/resources/control_pause_blue.png differ
diff --git a/resources/cross.png b/resources/cross.png
new file mode 100644
index 0000000..1514d51
Binary files /dev/null and b/resources/cross.png differ
diff --git a/resources/door_in.png b/resources/door_in.png
new file mode 100644
index 0000000..41676a0
Binary files /dev/null and b/resources/door_in.png differ
diff --git a/resources/table_refresh.png b/resources/table_refresh.png
new file mode 100644
index 0000000..ab92010
Binary files /dev/null and b/resources/table_refresh.png differ
diff --git a/resources/timeline_marker.png b/resources/timeline_marker.png
new file mode 100644
index 0000000..a3fbddf
Binary files /dev/null and b/resources/timeline_marker.png differ
diff --git a/syncplay/client.py b/syncplay/client.py
index 9a35b48..abd1b2a 100644
--- a/syncplay/client.py
+++ b/syncplay/client.py
@@ -317,6 +317,9 @@ class SyncplayClient(object):
if(self._protocol and self._protocol.logged):
self._protocol.sendList()
+ def showUserList(self):
+ self.userlist.showUserList()
+
def getPassword(self):
return self._serverPassword
@@ -504,7 +507,6 @@ class SyncplayUserlist(object):
self.ui.showMessage(message)
def addUser(self, username, room, file_, position = 0, noMessage = False):
- self._roomUsersChanged = True
if(username == self.currentUser.username):
self.currentUser.lastPosition = position
return
@@ -512,14 +514,15 @@ class SyncplayUserlist(object):
self._users[username] = user
if(not noMessage):
self.__showUserChangeMessage(username, room, file_)
+ self.userListChange()
def removeUser(self, username):
- self._roomUsersChanged = True
if(self._users.has_key(username)):
self._users.pop(username)
message = getMessage("en", "left-notification").format(username)
self.ui.showMessage(message)
-
+ self.userListChange()
+
def __displayModUserMessage(self, username, room, file_, user):
if (file_ and not user.isFileSame(file_)):
self.__showUserChangeMessage(username, room, file_)
@@ -527,7 +530,6 @@ class SyncplayUserlist(object):
self.__showUserChangeMessage(username, room, None)
def modUser(self, username, room, file_):
- self._roomUsersChanged = True
if(self._users.has_key(username)):
user = self._users[username]
self.__displayModUserMessage(username, room, file_, user)
@@ -537,7 +539,8 @@ class SyncplayUserlist(object):
self.__showUserChangeMessage(username, room, file_)
else:
self.addUser(username, room, file_)
-
+ self.userListChange()
+
def __addUserWithFileToList(self, rooms, user):
currentPosition = utils.formatTime(user.lastPosition)
file_key = '\'{}\' ({}/{})'.format(user.file['name'], currentPosition, utils.formatTime(user.file['duration']))
@@ -575,27 +578,28 @@ class SyncplayUserlist(object):
return message
def __displayFileWatchersInRoomList(self, key, users):
- self.ui.showMessage(getMessage("en", "file-played-by-notification").format(key), True, True)
+ self.ui.showListMessage(getMessage("en", "file-played-by-notification").format(key))
for user in sorted(users.itervalues()):
message = "<"+user.username+">"
if(self.currentUser.username == user.username):
message = "*" + message + "*"
message = self.__addDifferentFileMessageIfNecessary(user, message)
- self.ui.showMessage("\t" + message, True, True)
+ self.ui.showListMessage("\t" + message)
def __displayPeopleInRoomWithNoFile(self, noFileList):
if (noFileList):
- self.ui.showMessage(getMessage("en", "notplaying-notification"), True, True)
+ self.ui.showListMessage(getMessage("en", "notplaying-notification"))
for user in sorted(noFileList.itervalues()):
- self.ui.showMessage("\t<" + user.username + ">", True, True)
+ self.ui.showListMessage("\t<" + user.username + ">")
def __displayListOfPeople(self, rooms):
for roomName in sorted(rooms.iterkeys()):
- self.ui.showMessage(getMessage("en", "userlist-room-notification").format(roomName), True, False)
+ self.ui.showListMessage(getMessage("en", "userlist-room-notification").format(roomName))
noFileList = rooms[roomName].pop("__noFile__") if (rooms[roomName].has_key("__noFile__")) else None
for key in sorted(rooms[roomName].iterkeys()):
self.__displayFileWatchersInRoomList(key, rooms[roomName][key])
self.__displayPeopleInRoomWithNoFile(noFileList)
+ self.ui.markEndOfUserlist()
def areAllFilesInRoomSame(self):
for user in self._users.itervalues():
@@ -609,6 +613,10 @@ class SyncplayUserlist(object):
return False
return True
+ def userListChange(self):
+ self._roomUsersChanged = True
+ self.ui.userListChange()
+
def roomStateConfirmed(self):
self._roomUsersChanged = False
@@ -632,6 +640,9 @@ class UiManager(object):
if(not noPlayer): self.showOSDMessage(message)
self.__ui.showMessage(message, noTimestamp)
+ def showListMessage(self, message):
+ self.__ui.showListMessage(message)
+
def showOSDMessage(self, message, duration = constants.OSD_DURATION):
if(self._client._player):
self._client._player.displayMessage(message, duration * 1000)
@@ -641,5 +652,11 @@ class UiManager(object):
def promptFor(self, prompt):
return self.__ui.promptFor(prompt)
+
+ def userListChange(self):
+ self.__ui.userListChange()
+ def markEndOfUserlist(self):
+ self.__ui.markEndOfUserlist()
+
diff --git a/syncplay/clientManager.py b/syncplay/clientManager.py
index 043a5f6..4bb1c1b 100644
--- a/syncplay/clientManager.py
+++ b/syncplay/clientManager.py
@@ -1,4 +1,3 @@
-from syncplay.client import SyncplayClient
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
from syncplay import ui
from syncplay.messages import getMessage
@@ -6,6 +5,7 @@ from syncplay.messages import getMessage
class SyncplayClientManager(object):
def run(self):
config = ConfigurationGetter().getConfiguration()
+ from syncplay.client import SyncplayClient #Imported later, so the proper reactor is installed
interface = ui.getUi(graphical=not config["noGui"])
syncplayClient = SyncplayClient(config["playerClass"], interface, config)
if(syncplayClient):
diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py
index 36cb091..9d9310b 100644
--- a/syncplay/players/mpc.py
+++ b/syncplay/players/mpc.py
@@ -307,10 +307,12 @@ class MPCHCAPIPlayer(BasePlayer):
speedSupported = False
def __init__(self, client):
+ from twisted.internet import reactor
+ self.reactor = reactor
self.__client = client
self._mpcApi = MpcHcApi()
self._mpcApi.callbacks.onUpdateFilename = lambda _: self.__makePing()
- self._mpcApi.callbacks.onMpcClosed = lambda _: self.__client.stop(False)
+ self._mpcApi.callbacks.onMpcClosed = lambda _: self.reactor.callFromThread(self.__client.stop, (False),)
self._mpcApi.callbacks.onFileStateChange = lambda _: self.__lockAsking()
self._mpcApi.callbacks.onUpdatePlaystate = lambda _: self.__unlockAsking()
self._mpcApi.callbacks.onGetCurrentPosition = lambda _: self.__onGetPosition()
@@ -355,7 +357,7 @@ class MPCHCAPIPlayer(BasePlayer):
self._mpcApi.askForVersion()
if(not self.__versionUpdate.wait(0.1) or not self._mpcApi.version):
self.__mpcError(getMessage("en", "mpc-version-insufficient-error").format(constants.MPC_MIN_VER))
- self.__client.stop(True)
+ self.reactor.callFromThread(self.__client.stop, (True),)
def __testMpcReady(self):
if(not self.__preventAsking.wait(10)):
@@ -365,12 +367,12 @@ class MPCHCAPIPlayer(BasePlayer):
try:
self.__testMpcReady()
self._mpcApi.callbacks.onUpdateFilename = lambda _: self.__handleUpdatedFilename()
- self.__client.initPlayer(self)
+ self.reactor.callFromThread(self.__client.initPlayer, (self))
self.__handleUpdatedFilename()
self.askForStatus()
except Exception, err:
self.__client.ui.showErrorMessage(err.message)
- self.__client.stop()
+ self.reactor.callFromThread(self.__client.stop)
def initPlayer(self, filePath):
self.__dropIfNotSufficientVersion()
@@ -448,11 +450,12 @@ class MPCHCAPIPlayer(BasePlayer):
def __handleUpdatedFilename(self):
with self.__fileUpdate:
self.__setUpStateForNewlyOpenedFile()
- self.__client.updateFile(self._mpcApi.filePlaying, self._mpcApi.fileDuration, self._mpcApi.filePath)
+ args = (self._mpcApi.filePlaying, self._mpcApi.fileDuration, self._mpcApi.filePath)
+ self.reactor.callFromThread(self.__client.updateFile, *args)
def __mpcError(self, err=""):
self.__client.ui.showErrorMessage(err)
- self.__client.stop()
+ self.reactor.callFromThread(self.__client.stop)
def sendCustomCommand(self, cmd, val):
self._mpcApi.sendRawCommand(cmd, val)
diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py
index 7ee31b7..249f472 100644
--- a/syncplay/ui/ConfigurationGetter.py
+++ b/syncplay/ui/ConfigurationGetter.py
@@ -8,6 +8,8 @@ from syncplay.players.playerFactory import PlayerFactory
import codecs
try:
from syncplay.ui.GuiConfiguration import GuiConfiguration
+ from PySide import QtGui #@UnresolvedImport
+ from PySide.QtCore import Qt, QCoreApplication
except ImportError:
GuiConfiguration = None
@@ -205,8 +207,9 @@ class ConfigurationGetter(object):
except InvalidConfigValue:
pass
try:
- for key, value in self._promptForMissingArguments().items():
- self._config[key] = value
+ if(self._config['noGui'] == False):
+ for key, value in self._promptForMissingArguments().items():
+ self._config[key] = value
except:
sys.exit()
@@ -240,5 +243,10 @@ class ConfigurationGetter(object):
self._saveConfig(iniPath)
if(self._config['file']):
self._loadRelativeConfiguration()
+ if(not self._config['noGui']):
+ from syncplay.vendor import qt4reactor
+ if QCoreApplication.instance() is None:
+ self.app = QtGui.QApplication(sys.argv)
+ qt4reactor.install()
return self._config
diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py
index 84dfcd4..fdfd4e8 100644
--- a/syncplay/ui/GuiConfiguration.py
+++ b/syncplay/ui/GuiConfiguration.py
@@ -1,5 +1,5 @@
from PySide import QtCore, QtGui
-from PySide.QtCore import QSettings, Qt
+from PySide.QtCore import QSettings, Qt, QCoreApplication
from PySide.QtGui import QApplication, QLineEdit, QCursor, QLabel, QCheckBox, QDesktopServices, QIcon
import os
@@ -14,10 +14,8 @@ class GuiConfiguration:
def run(self):
- try:
+ if QCoreApplication.instance() is None:
self.app = QtGui.QApplication(sys.argv)
- except:
- pass
dialog = ConfigDialog(self.config, self._availablePlayerPaths, self.error)
dialog.exec_()
diff --git a/syncplay/ui/__init__.py b/syncplay/ui/__init__.py
index bca0a02..0848c4f 100644
--- a/syncplay/ui/__init__.py
+++ b/syncplay/ui/__init__.py
@@ -1,11 +1,11 @@
-from syncplay.ui.gui import GraphicalUI
+from syncplay.ui.gui import MainDialog as GraphicalUI
from syncplay.ui.consoleUI import ConsoleUI
def getUi(graphical=True):
- if(False): #graphical): #TODO: Add graphical ui
+ if(graphical): #TODO: Add graphical ui
ui = GraphicalUI()
else:
ui = ConsoleUI()
- ui.setDaemon(True)
- ui.start()
+ ui.setDaemon(True)
+ ui.start()
return ui
diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py
index 33bdf7d..82761de 100644
--- a/syncplay/ui/consoleUI.py
+++ b/syncplay/ui/consoleUI.py
@@ -40,6 +40,15 @@ class ConsoleUI(threading.Thread):
self.promptMode.wait()
return self.PromptResult
+ def showListMessage(self, message):
+ self.showMessage(message, True)
+
+ def markEndOfUserlist(self):
+ pass
+
+ def userListChange(self):
+ pass
+
def showMessage(self, message, noTimestamp=False):
message = message.encode(sys.stdout.encoding, 'replace')
if(noTimestamp):
diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py
index 0ae7f4d..cedec7a 100644
--- a/syncplay/ui/gui.py
+++ b/syncplay/ui/gui.py
@@ -1,9 +1,264 @@
-'''
-Created on 05-07-2012
+from PySide import QtGui #@UnresolvedImport
+from PySide.QtCore import Qt #@UnresolvedImport
+from syncplay import utils, constants
+import sys
+import time
+import re
-@author: Uriziel
-'''
+class MainDialog(QtGui.QDialog):
+ def addClient(self, client):
+ self._syncplayClient = client
+
+ def promptFor(self, prompt=">", message=""):
+ #TODO: Prompt user
+ return None
-class GraphicalUI(object):
+ def showMessage(self, message, noTimestamp=False):
+ message = message.encode(sys.stdout.encoding, 'replace')
+ message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">")
+ message = message.replace("\n", "
")
+ if(noTimestamp):
+ self.newMessage(message + "
")
+ else:
+ self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
")
+
+ def showListMessage(self, message):
+ message = message.encode(sys.stdout.encoding, 'replace')
+ message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">")
+ message = message.replace("\t", " "*4)
+ self._listBuffer += message + "
"
+
+ def markEndOfUserlist(self):
+ self.resetList()
+ self.newListItem(self._listBuffer)
+ self._listBuffer = "";
+
+ def userListChange(self):
+ self._syncplayClient.showUserList()
+
+ def showDebugMessage(self, message):
+ print(message)
+
+ def showErrorMessage(self, message):
+ print("ERROR:\t" + message)
+
+ def joinRoom(self):
+ room = self.roomInput.text()
+ if room == "":
+ if self._syncplayClient.userlist.currentUser.file:
+ room = self._syncplayClient.userlist.currentUser.file["name"]
+ else:
+ room = self._syncplayClient.defaultRoom
+ self._syncplayClient.setRoom(room)
+ self._syncplayClient.sendRoom()
+
+ def seekPosition(self):
+ s = re.match(constants.UI_SEEK_REGEX, self.seekInput.text())
+ if(s):
+ sign = self._extractSign(s.group('sign'))
+ t = utils.parseTime(s.group('time'))
+ if(t is None):
+ return
+ if(sign):
+ t = self._syncplayClient.getGlobalPosition() + sign * t
+ self._syncplayClient.setPosition(t)
+ self.seekInput.setText("")
+ else:
+ self.showMessage("Invalid seek value", True)
+
+ def showList(self):
+ self._syncplayClient.getUserList() #TODO: remove?
+
+ def undoSeek(self):
+ tmp_pos = self._syncplayClient.getPlayerPosition()
+ self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
+ self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
+
+ def togglePause(self):
+ self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
+
+ def exitSyncplay(self):
+ self._syncplayClient.stop()
+
+ def closeEvent(self, event):
+ self.exitSyncplay()
+ event.ignore()
+
+ def _extractSign(self, m):
+ if(m):
+ if(m == "-"):
+ return -1
+ else:
+ return 1
+ else:
+ return None
+
+ def setOffset(self):
+ newoffset, ok = QtGui.QInputDialog.getText(self,"Set Offset",
+ "Offset (+/-):", QtGui.QLineEdit.Normal,
+ "")
+ if ok and newoffset != '':
+ o = re.match(constants.UI_OFFSET_REGEX, newoffset)
+ if(o):
+ sign = self._extractSign(o.group('sign'))
+ t = utils.parseTime(o.group('time'))
+ if(t is None):
+ return
+ if (o.group('sign') == "/"):
+ t = self._syncplayClient.getPlayerPosition() - t
+ elif(sign):
+ t = self._syncplayClient.getUserOffset() + sign * t
+ self._syncplayClient.setUserOffset(t)
+ else:
+ self.showMessage("Invalid offset value", True)
+
+ def openUserGuide(self):
+ self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/")
+
+ def addTopLayout(self, dialog):
+ dialog.topSplit = QtGui.QSplitter(Qt.Horizontal)
+
+ dialog.outputLayout = QtGui.QVBoxLayout()
+ dialog.outputbox = QtGui.QTextEdit()
+ dialog.outputbox.setReadOnly(True)
+ dialog.outputlabel = QtGui.QLabel("Notifications")
+ dialog.outputFrame = QtGui.QFrame()
+ dialog.outputFrame.setLineWidth(0)
+ dialog.outputFrame.setMidLineWidth(0)
+ dialog.outputLayout.setContentsMargins(0,0,0,0)
+ dialog.outputLayout.addWidget(dialog.outputlabel)
+ dialog.outputLayout.addWidget(dialog.outputbox)
+ dialog.outputFrame.setLayout(dialog.outputLayout)
+
+ dialog.listLayout = QtGui.QVBoxLayout()
+ dialog.listbox = QtGui.QTextEdit()
+ dialog.listbox.setReadOnly(True)
+ dialog.listlabel = QtGui.QLabel("List of who is playing what")
+ dialog.listFrame = QtGui.QFrame()
+ dialog.listFrame.setLineWidth(0)
+ dialog.listFrame.setMidLineWidth(0)
+ dialog.listLayout.setContentsMargins(0,0,0,0)
+ dialog.listLayout.addWidget(dialog.listlabel)
+ dialog.listLayout.addWidget(dialog.listbox)
+ dialog.listFrame.setLayout(dialog.listLayout)
+
+ dialog.topSplit.addWidget(dialog.outputFrame)
+ dialog.topSplit.addWidget(dialog.listFrame)
+ dialog.topSplit.setStretchFactor(0,3)
+ dialog.topSplit.setStretchFactor(1,2)
+ dialog.mainLayout.addWidget(dialog.topSplit)
+ dialog.topSplit.setSizePolicy(QtGui.QSizePolicy.Preferred,QtGui.QSizePolicy.Expanding)
+
+ def addBottomLayout(self, dialog):
+ dialog.bottomLayout = QtGui.QHBoxLayout()
+
+ dialog.addRoomBox(MainDialog)
+ dialog.addSeekBox(MainDialog)
+ dialog.addMiscBox(MainDialog)
+
+ dialog.bottomLayout.addWidget(dialog.roomGroup, Qt.AlignLeft)
+ dialog.bottomLayout.addWidget(dialog.seekGroup, Qt.AlignLeft)
+ dialog.bottomLayout.addWidget(dialog.miscGroup, Qt.AlignLeft)
+
+ dialog.mainLayout.addLayout(dialog.bottomLayout, Qt.AlignLeft)
+
+ def addRoomBox(self, dialog):
+ dialog.roomGroup = QtGui.QGroupBox("Room")
+
+ dialog.roomInput = QtGui.QLineEdit()
+ dialog.roomButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'door_in.png'), "Join room")
+ dialog.roomButton.pressed.connect(self.joinRoom)
+ dialog.roomLayout = QtGui.QHBoxLayout()
+ dialog.roomInput.setMaximumWidth(150)
+
+ dialog.roomLayout.addWidget(dialog.roomInput)
+ dialog.roomLayout.addWidget(dialog.roomButton)
+
+ dialog.roomGroup.setLayout(dialog.roomLayout)
+ dialog.roomGroup.setFixedSize(dialog.roomGroup.sizeHint())
+
+ def addSeekBox(self, dialog):
+ dialog.seekGroup = QtGui.QGroupBox("Seek")
+
+ dialog.seekInput = QtGui.QLineEdit()
+ dialog.seekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'clock_go.png'),"Seek to position")
+ dialog.seekButton.pressed.connect(self.seekPosition)
+
+ dialog.seekLayout = QtGui.QHBoxLayout()
+ dialog.seekInput.setMaximumWidth(100)
+
+ dialog.seekLayout.addWidget(dialog.seekInput)
+ dialog.seekLayout.addWidget(dialog.seekButton)
+
+ dialog.seekGroup.setLayout(dialog.seekLayout)
+ dialog.seekGroup.setFixedSize(dialog.seekGroup.sizeHint())
+
+ def addMiscBox(self, dialog):
+ dialog.miscGroup = QtGui.QGroupBox("Other Commands")
+
+ dialog.unseekButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'arrow_undo.png'),"Undo last seek")
+ dialog.unseekButton.pressed.connect(self.undoSeek)
+ dialog.pauseButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'control_pause_blue.png'),"Toggle pause")
+ dialog.pauseButton.pressed.connect(self.togglePause)
+ dialog.showListButton = QtGui.QPushButton(QtGui.QIcon(self.resourcespath + 'table_refresh.png'),"Update list")
+ dialog.showListButton.pressed.connect(self.showList)
+
+ dialog.miscLayout = QtGui.QHBoxLayout()
+ dialog.miscLayout.addWidget(dialog.unseekButton)
+ dialog.miscLayout.addWidget(dialog.pauseButton)
+ dialog.miscLayout.addWidget(dialog.showListButton)
+
+ dialog.miscGroup.setLayout(dialog.miscLayout)
+ dialog.miscGroup.setFixedSize(dialog.miscGroup.sizeHint())
+
+
+ def addMenubar(self, dialog):
+ dialog.menuBar = QtGui.QMenuBar()
+
+ dialog.fileMenu = QtGui.QMenu("&File", self)
+ dialog.exitAction = dialog.fileMenu.addAction(QtGui.QIcon(self.resourcespath + 'cross.png'), "E&xit")
+ dialog.exitAction.triggered.connect(self.exitSyncplay)
+ dialog.menuBar.addMenu(dialog.fileMenu)
+
+ dialog.advancedMenu = QtGui.QMenu("&Advanced", self)
+ dialog.setoffsetAction = dialog.advancedMenu.addAction(QtGui.QIcon(self.resourcespath + 'timeline_marker.png'),"Set &Offset")
+ dialog.setoffsetAction.triggered.connect(self.setOffset)
+ dialog.menuBar.addMenu(dialog.advancedMenu)
+
+ dialog.helpMenu = QtGui.QMenu("&Help", self)
+ dialog.userguideAction = dialog.helpMenu.addAction(QtGui.QIcon(self.resourcespath + 'help.png'), "Open User &Guide")
+ dialog.userguideAction.triggered.connect(self.openUserGuide)
+
+ dialog.menuBar.addMenu(dialog.helpMenu)
+ dialog.mainLayout.setMenuBar(dialog.menuBar)
+
+ def newMessage(self, message):
+ self.outputbox.moveCursor(QtGui.QTextCursor.End)
+ self.outputbox.insertHtml(message)
+ self.outputbox.moveCursor(QtGui.QTextCursor.End)
+
+ def resetList(self):
+ self.listbox.setText("")
+
+ def newListItem(self, item):
+ self.listbox.moveCursor(QtGui.QTextCursor.End)
+ self.listbox.insertHtml(item)
+ self.listbox.moveCursor(QtGui.QTextCursor.End)
+
def __init__(self):
- pass
+ super(MainDialog, self).__init__()
+ self.QtGui = QtGui
+ self._listBuffer = ""
+ if sys.platform.startswith('linux'):
+ self.resourcespath = utils.findWorkingDir() + "/resources/"
+ else:
+ self.resourcespath = utils.findWorkingDir() + "\\resources\\"
+ self.setWindowTitle("Syncplay - Main Window")
+ self.mainLayout = QtGui.QVBoxLayout()
+ self.addTopLayout(self)
+ self.addBottomLayout(self)
+ self.addMenubar(self)
+ self.setLayout(self.mainLayout)
+ self.resize(700,500)
+ self.setWindowIcon(QtGui.QIcon(self.resourcespath + "syncplay.png"))
+ self.show()
diff --git a/syncplay/vendor/__init__.py b/syncplay/vendor/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/syncplay/vendor/qt4reactor.py b/syncplay/vendor/qt4reactor.py
new file mode 100644
index 0000000..a337099
--- /dev/null
+++ b/syncplay/vendor/qt4reactor.py
@@ -0,0 +1,359 @@
+# Copyright (c) 2001-2011 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+This module provides support for Twisted to be driven by the Qt mainloop.
+
+In order to use this support, simply do the following::
+ | app = QApplication(sys.argv) # your code to init Qt
+ | import qt4reactor
+ | qt4reactor.install()
+
+alternatively:
+
+ | from twisted.application import reactors
+ | reactors.installReactor('qt4')
+
+Then use twisted.internet APIs as usual. The other methods here are not
+intended to be called directly.
+
+If you don't instantiate a QApplication or QCoreApplication prior to
+installing the reactor, a QCoreApplication will be constructed
+by the reactor. QCoreApplication does not require a GUI so trial testing
+can occur normally.
+
+Twisted can be initialized after QApplication.exec_() with a call to
+reactor.runReturn(). calling reactor.stop() will unhook twisted but
+leave your Qt application running
+
+API Stability: stable
+
+Maintainer: U{Glenn H Tarbox, PhD}
+
+Previous maintainer: U{Itamar Shtull-Trauring}
+Original port to QT4: U{Gabe Rudy}
+Subsequent port by therve
+"""
+
+import sys
+import time
+from zope.interface import implements
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.python import log, runtime
+from twisted.internet import posixbase
+from twisted.python.runtime import platformType, platform
+
+try:
+ from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
+ from PyQt4.QtCore import QEventLoop
+except ImportError:
+ from PySide.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
+ from PySide.QtCore import QEventLoop
+
+
+class TwistedSocketNotifier(QObject):
+ """
+ Connection between an fd event and reader/writer callbacks.
+ """
+
+ def __init__(self, parent, reactor, watcher, socketType):
+ QObject.__init__(self, parent)
+ self.reactor = reactor
+ self.watcher = watcher
+ fd = watcher.fileno()
+ self.notifier = QSocketNotifier(fd, socketType, parent)
+ self.notifier.setEnabled(True)
+ if socketType == QSocketNotifier.Read:
+ self.fn = self.read
+ else:
+ self.fn = self.write
+ QObject.connect(self.notifier, SIGNAL("activated(int)"), self.fn)
+
+
+ def shutdown(self):
+ self.notifier.setEnabled(False)
+ self.disconnect(self.notifier, SIGNAL("activated(int)"), self.fn)
+ self.fn = self.watcher = None
+ self.notifier.deleteLater()
+ self.deleteLater()
+
+
+ def read(self, fd):
+ if not self.watcher:
+ return
+ w = self.watcher
+ # doRead can cause self.shutdown to be called so keep a reference to self.watcher
+ def _read():
+ #Don't call me again, until the data has been read
+ self.notifier.setEnabled(False)
+ why = None
+ try:
+ why = w.doRead()
+ inRead = True
+ except:
+ inRead = False
+ log.err()
+ why = sys.exc_info()[1]
+ if why:
+ self.reactor._disconnectSelectable(w, why, inRead)
+ elif self.watcher:
+ self.notifier.setEnabled(True) # Re enable notification following sucessfull read
+ self.reactor._iterate(fromqt=True)
+ log.callWithLogger(w, _read)
+
+ def write(self, sock):
+ if not self.watcher:
+ return
+ w = self.watcher
+ def _write():
+ why = None
+ self.notifier.setEnabled(False)
+
+ try:
+ why = w.doWrite()
+ except:
+ log.err()
+ why = sys.exc_info()[1]
+ if why:
+ self.reactor._disconnectSelectable(w, why, False)
+ elif self.watcher:
+ self.notifier.setEnabled(True)
+ self.reactor._iterate(fromqt=True)
+ log.callWithLogger(w, _write)
+
+
+
+class QtReactor(posixbase.PosixReactorBase):
+ implements(IReactorFDSet)
+
+ def __init__(self):
+ self._reads = {}
+ self._writes = {}
+ self._notifiers = {}
+ self._timer = QTimer()
+ self._timer.setSingleShot(True)
+ QObject.connect(self._timer, SIGNAL("timeout()"), self.iterate)
+
+ if QCoreApplication.instance() is None:
+ # Application Object has not been started yet
+ self.qApp=QCoreApplication([])
+ self._ownApp=True
+ else:
+ self.qApp = QCoreApplication.instance()
+ self._ownApp=False
+ self._blockApp = None
+ posixbase.PosixReactorBase.__init__(self)
+
+
+ def _add(self, xer, primary, type):
+ """
+ Private method for adding a descriptor from the event loop.
+
+ It takes care of adding it if new or modifying it if already added
+ for another state (read -> read/write for example).
+ """
+ if xer not in primary:
+ primary[xer] = TwistedSocketNotifier(None, self, xer, type)
+
+
+ def addReader(self, reader):
+ """
+ Add a FileDescriptor for notification of data available to read.
+ """
+ self._add(reader, self._reads, QSocketNotifier.Read)
+
+
+ def addWriter(self, writer):
+ """
+ Add a FileDescriptor for notification of data available to write.
+ """
+ self._add(writer, self._writes, QSocketNotifier.Write)
+
+
+ def _remove(self, xer, primary):
+ """
+ Private method for removing a descriptor from the event loop.
+
+ It does the inverse job of _add, and also add a check in case of the fd
+ has gone away.
+ """
+ if xer in primary:
+ notifier = primary.pop(xer)
+ notifier.shutdown()
+
+
+ def removeReader(self, reader):
+ """
+ Remove a Selectable for notification of data available to read.
+ """
+ self._remove(reader, self._reads)
+
+
+ def removeWriter(self, writer):
+ """
+ Remove a Selectable for notification of data available to write.
+ """
+ self._remove(writer, self._writes)
+
+
+ def removeAll(self):
+ """
+ Remove all selectables, and return a list of them.
+ """
+ rv = self._removeAll(self._reads, self._writes)
+ return rv
+
+
+ def getReaders(self):
+ return self._reads.keys()
+
+
+ def getWriters(self):
+ return self._writes.keys()
+
+
+ def callLater(self,howlong, *args, **kargs):
+ rval = super(QtReactor,self).callLater(howlong, *args, **kargs)
+ self.reactorInvocation()
+ return rval
+
+
+ def reactorInvocation(self):
+ self._timer.stop()
+ self._timer.setInterval(0)
+ self._timer.start()
+
+
+ def _iterate(self, delay=None, fromqt=False):
+ """See twisted.internet.interfaces.IReactorCore.iterate.
+ """
+ self.runUntilCurrent()
+ self.doIteration(delay, fromqt)
+
+ iterate = _iterate
+
+ def doIteration(self, delay=None, fromqt=False):
+ 'This method is called by a Qt timer or by network activity on a file descriptor'
+
+ if not self.running and self._blockApp:
+ self._blockApp.quit()
+ self._timer.stop()
+ delay = max(delay, 1)
+ if not fromqt:
+ self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000)
+ if self.timeout() is None:
+ timeout = 0.1
+ elif self.timeout() == 0:
+ timeout = 0
+ else:
+ timeout = self.timeout()
+ self._timer.setInterval(timeout * 1000)
+ self._timer.start()
+
+
+ def runReturn(self, installSignalHandlers=True):
+ self.startRunning(installSignalHandlers=installSignalHandlers)
+ self.reactorInvocation()
+
+
+ def run(self, installSignalHandlers=True):
+ if self._ownApp:
+ self._blockApp = self.qApp
+ else:
+ self._blockApp = QEventLoop()
+ self.runReturn()
+ self._blockApp.exec_()
+
+
+class QtEventReactor(QtReactor):
+ def __init__(self, *args, **kwargs):
+ self._events = {}
+ super(QtEventReactor, self).__init__()
+
+
+ def addEvent(self, event, fd, action):
+ """
+ Add a new win32 event to the event loop.
+ """
+ self._events[event] = (fd, action)
+
+
+ def removeEvent(self, event):
+ """
+ Remove an event.
+ """
+ if event in self._events:
+ del self._events[event]
+
+
+ def doEvents(self):
+ handles = self._events.keys()
+ if len(handles) > 0:
+ val = None
+ while val != WAIT_TIMEOUT:
+ val = MsgWaitForMultipleObjects(handles, 0, 0, QS_ALLINPUT | QS_ALLEVENTS)
+ if val >= WAIT_OBJECT_0 and val < WAIT_OBJECT_0 + len(handles):
+ event_id = handles[val - WAIT_OBJECT_0]
+ if event_id in self._events:
+ fd, action = self._events[event_id]
+ log.callWithLogger(fd, self._runAction, action, fd)
+ elif val == WAIT_TIMEOUT:
+ pass
+ else:
+ #print 'Got an unexpected return of %r' % val
+ return
+
+
+ def _runAction(self, action, fd):
+ try:
+ closed = getattr(fd, action)()
+ except:
+ closed = sys.exc_info()[1]
+ log.deferr()
+
+ if closed:
+ self._disconnectSelectable(fd, closed, action == 'doRead')
+
+
+ def timeout(self):
+ t = super(QtEventReactor, self).timeout()
+ return min(t, 0.01)
+
+
+ def iterate(self, delay=None):
+ """See twisted.internet.interfaces.IReactorCore.iterate.
+ """
+ self.runUntilCurrent()
+ self.doEvents()
+ self.doIteration(delay)
+
+
+def posixinstall():
+ """
+ Install the Qt reactor.
+ """
+ p = QtReactor()
+ from twisted.internet.main import installReactor
+ installReactor(p)
+
+
+def win32install():
+ """
+ Install the Qt reactor.
+ """
+ p = QtEventReactor()
+ from twisted.internet.main import installReactor
+ installReactor(p)
+
+
+if runtime.platform.getType() == 'win32':
+ from win32event import CreateEvent, MsgWaitForMultipleObjects
+ from win32event import WAIT_OBJECT_0, WAIT_TIMEOUT, QS_ALLINPUT, QS_ALLEVENTS
+ install = win32install
+else:
+ install = posixinstall
+
+
+__all__ = ["install"]
+