Merge branch 'shinyGui'
Conflicts: syncplay/ui/GuiConfiguration.py
BIN
resources/arrow_undo.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
resources/clock_go.png
Normal file
|
After Width: | Height: | Size: 959 B |
BIN
resources/control_pause_blue.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
resources/cross.png
Normal file
|
After Width: | Height: | Size: 655 B |
BIN
resources/door_in.png
Normal file
|
After Width: | Height: | Size: 693 B |
BIN
resources/table_refresh.png
Normal file
|
After Width: | Height: | Size: 795 B |
BIN
resources/timeline_marker.png
Normal file
|
After Width: | Height: | Size: 327 B |
@ -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()
|
||||
|
||||
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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_()
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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", "<br />")
|
||||
if(noTimestamp):
|
||||
self.newMessage(message + "<br />")
|
||||
else:
|
||||
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
|
||||
|
||||
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 + "<br />"
|
||||
|
||||
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()
|
||||
|
||||
0
syncplay/vendor/__init__.py
vendored
Normal file
359
syncplay/vendor/qt4reactor.py
vendored
Normal file
@ -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<mailto:glenn@tarbox.org>}
|
||||
|
||||
Previous maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>}
|
||||
Original port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>}
|
||||
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"]
|
||||
|
||||