import os import re import sys import time import urllib.error import urllib.parse import urllib.request from datetime import datetime from functools import wraps from platform import python_version from twisted.internet import task from syncplay import utils, constants, version, revision, release_number from syncplay.messages import getMessage from syncplay.ui.consoleUI import ConsoleUI from syncplay.utils import resourcespath from syncplay.utils import isLinux, isWindows, isMacOS from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL from syncplay.vendor import Qt from syncplay.vendor.Qt import QtWidgets, QtGui, __binding__, __binding_version__, __qt_version__, IsPySide, IsPySide2 from syncplay.vendor.Qt.QtCore import Qt, QSettings, QSize, QPoint, QUrl, QLine, QDateTime if IsPySide2: from PySide2.QtCore import QStandardPaths if isMacOS() and IsPySide: from Foundation import NSURL from Cocoa import NSString, NSUTF8StringEncoding lastCheckedForUpdates = None class ConsoleInGUI(ConsoleUI): def showMessage(self, message, noTimestamp=False): self._syncplayClient.ui.showMessage(message, True) def showDebugMessage(self, message): self._syncplayClient.ui.showDebugMessage(message) def showErrorMessage(self, message, criticalerror=False): self._syncplayClient.ui.showErrorMessage(message, criticalerror) def updateRoomName(self, room=""): self._syncplayClient.ui.updateRoomName(room) def getUserlist(self): self._syncplayClient.showUserList(self) class UserlistItemDelegate(QtWidgets.QStyledItemDelegate): def __init__(self): QtWidgets.QStyledItemDelegate.__init__(self) def sizeHint(self, option, index): size = QtWidgets.QStyledItemDelegate.sizeHint(self, option, index) if (index.column() == constants.USERLIST_GUI_USERNAME_COLUMN): size.setWidth(size.width() + constants.USERLIST_GUI_USERNAME_OFFSET) return size def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): column = indexQModelIndex.column() midY = int((optionQStyleOptionViewItem.rect.y() + optionQStyleOptionViewItem.rect.bottomLeft().y()) / 2) if column == constants.USERLIST_GUI_USERNAME_COLUMN: currentQAbstractItemModel = indexQModelIndex.model() itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), constants.USERLIST_GUI_USERNAME_COLUMN, indexQModelIndex.parent()) controlIconQPixmap = QtGui.QPixmap(resourcespath + "user_key.png") tickIconQPixmap = QtGui.QPixmap(resourcespath + "tick.png") crossIconQPixmap = QtGui.QPixmap(resourcespath + "cross.png") roomController = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) userReady = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_READY_ROLE) if roomController and not controlIconQPixmap.isNull(): itemQPainter.drawPixmap( optionQStyleOptionViewItem.rect.x()+6, midY-8, controlIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) if userReady and not tickIconQPixmap.isNull(): itemQPainter.drawPixmap( (optionQStyleOptionViewItem.rect.x()-10), midY - 8, tickIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) elif userReady == False and not crossIconQPixmap.isNull(): itemQPainter.drawPixmap( (optionQStyleOptionViewItem.rect.x()-10), midY - 8, crossIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) isUserRow = indexQModelIndex.parent() != indexQModelIndex.parent().parent() if isUserRow: optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+constants.USERLIST_GUI_USERNAME_OFFSET) if column == constants.USERLIST_GUI_FILENAME_COLUMN: currentQAbstractItemModel = indexQModelIndex.model() itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), constants.USERLIST_GUI_FILENAME_COLUMN, indexQModelIndex.parent()) fileSwitchRole = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.FILEITEM_SWITCH_ROLE) if fileSwitchRole == constants.FILEITEM_SWITCH_FILE_SWITCH: fileSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "film_go.png") itemQPainter.drawPixmap( (optionQStyleOptionViewItem.rect.x()), midY - 8, fileSwitchIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16) elif fileSwitchRole == constants.FILEITEM_SWITCH_STREAM_SWITCH: streamSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "world_go.png") itemQPainter.drawPixmap( (optionQStyleOptionViewItem.rect.x()), midY - 8, streamSwitchIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16) QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) class AboutDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(AboutDialog, self).__init__(parent) if isMacOS(): self.setWindowTitle("") self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint) else: self.setWindowTitle(getMessage("about-dialog-title")) if isWindows(): self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setWindowIcon(QtGui.QPixmap(resourcespath + 'syncplay.png')) nameLabel = QtWidgets.QLabel("
Syncplay
") nameLabel.setFont(QtGui.QFont("Helvetica", 18)) linkLabel = QtWidgets.QLabel("
syncplay.pl
") linkLabel.setOpenExternalLinks(True) versionExtString = version + revision versionLabel = QtWidgets.QLabel( "

" + getMessage("about-dialog-release").format(versionExtString, release_number) + "
Python " + python_version() + " - " + __binding__ + " " + __binding_version__ + " - Qt " + __qt_version__ + "

") licenseLabel = QtWidgets.QLabel( "

Copyright © 2012–2019 Syncplay

" + getMessage("about-dialog-license-text") + "

") aboutIconPixmap = QtGui.QPixmap(resourcespath + "syncplay.png") aboutIconLabel = QtWidgets.QLabel() aboutIconLabel.setPixmap(aboutIconPixmap.scaled(65, 65, Qt.KeepAspectRatio)) aboutLayout = QtWidgets.QGridLayout() aboutLayout.addWidget(aboutIconLabel, 0, 0, 3, 4, Qt.AlignHCenter) aboutLayout.addWidget(nameLabel, 3, 0, 1, 4) aboutLayout.addWidget(linkLabel, 4, 0, 1, 4) aboutLayout.addWidget(versionLabel, 5, 0, 1, 4) aboutLayout.addWidget(licenseLabel, 6, 0, 1, 4) licenseButton = QtWidgets.QPushButton(getMessage("about-dialog-license-button")) licenseButton.setAutoDefault(False) licenseButton.clicked.connect(self.openLicense) aboutLayout.addWidget(licenseButton, 7, 0, 1, 2) dependenciesButton = QtWidgets.QPushButton(getMessage("about-dialog-dependencies")) dependenciesButton.setAutoDefault(False) dependenciesButton.clicked.connect(self.openDependencies) aboutLayout.addWidget(dependenciesButton, 7, 2, 1, 2) aboutLayout.setVerticalSpacing(10) aboutLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.setSizeGripEnabled(False) self.setLayout(aboutLayout) def openLicense(self): if isWindows(): QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + "license.rtf")) else: QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + "license.rtf")) def openDependencies(self): if isWindows(): QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + "third-party-notices.rtf")) else: QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + "third-party-notices.rtf")) class CertificateDialog(QtWidgets.QDialog): def __init__(self, tlsData, parent=None): super(CertificateDialog, self).__init__(parent) if isMacOS(): self.setWindowTitle("") self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint) else: self.setWindowTitle(getMessage("tls-information-title")) if isWindows(): self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setWindowIcon(QtGui.QPixmap(resourcespath + 'syncplay.png')) statusLabel = QtWidgets.QLabel(getMessage("tls-dialog-status-label").format(tlsData["subject"])) descLabel = QtWidgets.QLabel(getMessage("tls-dialog-desc-label").format(tlsData["subject"])) connDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-connection-label").format(tlsData["protocolVersion"], tlsData["cipher"])) certDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-certificate-label").format(tlsData["issuer"], tlsData["expires"])) if isMacOS(): statusLabel.setFont(QtGui.QFont("Helvetica", 12)) descLabel.setFont(QtGui.QFont("Helvetica", 12)) connDataLabel.setFont(QtGui.QFont("Helvetica", 12)) certDataLabel.setFont(QtGui.QFont("Helvetica", 12)) lockIconPixmap = QtGui.QPixmap(resourcespath + "lock_green_dialog.png") lockIconLabel = QtWidgets.QLabel() lockIconLabel.setPixmap(lockIconPixmap.scaled(64, 64, Qt.KeepAspectRatio)) certLayout = QtWidgets.QGridLayout() certLayout.addWidget(lockIconLabel, 1, 0, 3, 1, Qt.AlignLeft | Qt.AlignTop) certLayout.addWidget(statusLabel, 0, 1, 1, 3) certLayout.addWidget(descLabel, 1, 1, 1, 3) certLayout.addWidget(connDataLabel, 2, 1, 1, 3) certLayout.addWidget(certDataLabel, 3, 1, 1, 3) closeButton = QtWidgets.QPushButton("Close") closeButton.setFixedWidth(100) closeButton.setAutoDefault(False) closeButton.clicked.connect(self.closeDialog) certLayout.addWidget(closeButton, 4, 3, 1, 1) certLayout.setVerticalSpacing(10) certLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.setSizeGripEnabled(False) self.setLayout(certLayout) def closeDialog(self): self.close() class MainWindow(QtWidgets.QMainWindow): insertPosition = None playlistState = [] updatingPlaylist = False playlistIndex = None sslInformation = "N/A" sslMode = False def setPlaylistInsertPosition(self, newPosition): if not self.playlist.isEnabled(): return if MainWindow.insertPosition != newPosition: MainWindow.insertPosition = newPosition self.playlist.forceUpdate() class PlaylistItemDelegate(QtWidgets.QStyledItemDelegate): def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): itemQPainter.save() currentQAbstractItemModel = indexQModelIndex.model() currentlyPlayingFile = currentQAbstractItemModel.data(indexQModelIndex, Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE) if currentlyPlayingFile: currentlyplayingIconQPixmap = QtGui.QPixmap(resourcespath + "bullet_right_grey.png") midY = int((optionQStyleOptionViewItem.rect.y() + optionQStyleOptionViewItem.rect.bottomLeft().y()) / 2) itemQPainter.drawPixmap( (optionQStyleOptionViewItem.rect.x()+4), midY-8, currentlyplayingIconQPixmap.scaled(6, 16, Qt.KeepAspectRatio)) optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+10) QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) lineAbove = False lineBelow = False if MainWindow.insertPosition == 0 and indexQModelIndex.row() == 0: lineAbove = True elif MainWindow.insertPosition and indexQModelIndex.row() == MainWindow.insertPosition-1: lineBelow = True if lineAbove: line = QLine(optionQStyleOptionViewItem.rect.topLeft(), optionQStyleOptionViewItem.rect.topRight()) itemQPainter.drawLine(line) elif lineBelow: line = QLine(optionQStyleOptionViewItem.rect.bottomLeft(), optionQStyleOptionViewItem.rect.bottomRight()) itemQPainter.drawLine(line) itemQPainter.restore() class PlaylistGroupBox(QtWidgets.QGroupBox): def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() window = self.parent().parent().parent().parent().parent() if urls and urls[0].scheme() == 'file': event.acceptProposedAction() window.setPlaylistInsertPosition(window.playlist.count()) else: super(MainWindow.PlaylistGroupBox, self).dragEnterEvent(event) def dragLeaveEvent(self, event): window = self.parent().parent().parent().parent().parent() window.setPlaylistInsertPosition(None) def dropEvent(self, event): window = self.parent().parent().parent().parent().parent() if not window.playlist.isEnabled(): return window.setPlaylistInsertPosition(None) if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': indexRow = window.playlist.count() if window.clearedPlaylistNote else 0 for url in urls[::-1]: if isMacOS() and IsPySide: macURL = NSString.alloc().initWithString_(str(url.toString())) pathString = macURL.stringByAddingPercentEscapesUsingEncoding_(NSUTF8StringEncoding) dropfilepath = os.path.abspath(NSURL.URLWithString_(pathString).filePathURL().path()) else: dropfilepath = os.path.abspath(str(url.toLocalFile())) if os.path.isfile(dropfilepath): window.addFileToPlaylist(dropfilepath, indexRow) elif os.path.isdir(dropfilepath): window.addFolderToPlaylist(dropfilepath) else: super(MainWindow.PlaylistWidget, self).dropEvent(event) class PlaylistWidget(QtWidgets.QListWidget): selfWindow = None playlistIndexFilename = None def setPlaylistIndexFilename(self, filename): if filename != self.playlistIndexFilename: self.playlistIndexFilename = filename self.updatePlaylistIndexIcon() def updatePlaylistIndexIcon(self): for item in range(self.count()): itemFilename = self.item(item).text() isPlayingFilename = itemFilename == self.playlistIndexFilename self.item(item).setData(Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE, isPlayingFilename) fileIsAvailable = self.selfWindow.isFileAvailable(itemFilename) fileIsUntrusted = self.selfWindow.isItemUntrusted(itemFilename) if fileIsUntrusted: self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_UNTRUSTEDITEM_COLOR))) elif fileIsAvailable: self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(QtGui.QPalette.ColorRole(QtGui.QPalette.Text)))) else: self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) self.selfWindow._syncplayClient.fileSwitch.setFilenameWatchlist(self.selfWindow.newWatchlist) self.forceUpdate() def setWindow(self, window): self.selfWindow = window def dragLeaveEvent(self, event): window = self.parent().parent().parent().parent().parent().parent() window.setPlaylistInsertPosition(None) def forceUpdate(self): root = self.rootIndex() self.dataChanged(root, root) def keyPressEvent(self, event): if event.key() == Qt.Key_Delete: self.remove_selected_items() else: super(MainWindow.PlaylistWidget, self).keyPressEvent(event) def updatePlaylist(self, newPlaylist): for index in range(self.count()): self.takeItem(0) uniquePlaylist = [] for item in newPlaylist: if item not in uniquePlaylist: uniquePlaylist.append(item) self.insertItems(0, uniquePlaylist) self.updatePlaylistIndexIcon() def remove_selected_items(self): for item in self.selectedItems(): self.takeItem(self.row(item)) def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': event.acceptProposedAction() else: super(MainWindow.PlaylistWidget, self).dragEnterEvent(event) def dragMoveEvent(self, event): data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': event.acceptProposedAction() indexRow = self.indexAt(event.pos()).row() window = self.parent().parent().parent().parent().parent().parent() if indexRow == -1 or not window.clearedPlaylistNote: indexRow = window.playlist.count() window.setPlaylistInsertPosition(indexRow) else: super(MainWindow.PlaylistWidget, self).dragMoveEvent(event) def dropEvent(self, event): window = self.parent().parent().parent().parent().parent().parent() if not window.playlist.isEnabled(): return window.setPlaylistInsertPosition(None) if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': indexRow = self.indexAt(event.pos()).row() if not window.clearedPlaylistNote: indexRow = 0 if indexRow == -1: indexRow = window.playlist.count() for url in urls[::-1]: if isMacOS() and IsPySide: macURL = NSString.alloc().initWithString_(str(url.toString())) pathString = macURL.stringByAddingPercentEscapesUsingEncoding_(NSUTF8StringEncoding) dropfilepath = os.path.abspath(NSURL.URLWithString_(pathString).filePathURL().path()) else: dropfilepath = os.path.abspath(str(url.toLocalFile())) if os.path.isfile(dropfilepath): window.addFileToPlaylist(dropfilepath, indexRow) elif os.path.isdir(dropfilepath): window.addFolderToPlaylist(dropfilepath) else: super(MainWindow.PlaylistWidget, self).dropEvent(event) class topSplitter(QtWidgets.QSplitter): def createHandle(self): return self.topSplitterHandle(self.orientation(), self) class topSplitterHandle(QtWidgets.QSplitterHandle): def mouseReleaseEvent(self, event): QtWidgets.QSplitterHandle.mouseReleaseEvent(self, event) self.parent().parent().parent().updateListGeometry() def mouseMoveEvent(self, event): QtWidgets.QSplitterHandle.mouseMoveEvent(self, event) self.parent().parent().parent().updateListGeometry() def needsClient(f): # @NoSelf @wraps(f) def wrapper(self, *args, **kwds): if not self._syncplayClient: self.showDebugMessage("Tried to use client before it was ready!") return return f(self, *args, **kwds) return wrapper def addClient(self, client): self._syncplayClient = client if self.console: self.console.addClient(client) self.roomInput.setText(self._syncplayClient.getRoom()) self.config = self._syncplayClient.getConfig() try: self.playlistGroup.blockSignals(True) self.playlistGroup.setChecked(self.config['sharedPlaylistEnabled']) self.playlistGroup.blockSignals(False) self._syncplayClient.fileSwitch.setMediaDirectories(self.config["mediaSearchDirectories"]) if not self.config["mediaSearchDirectories"]: self._syncplayClient.ui.showErrorMessage(getMessage("no-media-directories-error")) self.updateReadyState(self.config['readyAtStart']) autoplayInitialState = self.config['autoplayInitialState'] if autoplayInitialState is not None: self.autoplayPushButton.blockSignals(True) self.autoplayPushButton.setChecked(autoplayInitialState) self.autoplayPushButton.blockSignals(False) if self.config['autoplayMinUsers'] > 1: self.autoplayThresholdSpinbox.blockSignals(True) self.autoplayThresholdSpinbox.setValue(self.config['autoplayMinUsers']) self.autoplayThresholdSpinbox.blockSignals(False) self.changeAutoplayState() self.changeAutoplayThreshold() self.updateAutoPlayIcon() except: self.showErrorMessage("Failed to load some settings.") self.automaticUpdateCheck() def promptFor(self, prompt=">", message=""): # TODO: Prompt user return None def setFeatures(self, featureList): if not featureList["readiness"]: self.readyPushButton.setEnabled(False) if not featureList["chat"]: self.chatFrame.setEnabled(False) self.chatInput.setReadOnly(True) if not featureList["sharedPlaylists"]: self.playlistGroup.setEnabled(False) self.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH) self.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH) def setSSLMode(self, sslMode, sslInformation): self.sslMode = sslMode self.sslInformation = sslInformation self.sslButton.setVisible(sslMode) def getSSLInformation(self): return self.sslInformation def showMessage(self, message, noTimestamp=False): message = str(message) username = None messageWithUsername = re.match(constants.MESSAGE_WITH_USERNAME_REGEX, message, re.UNICODE) if messageWithUsername: username = messageWithUsername.group("username") message = messageWithUsername.group("message") message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") if username: message = constants.STYLE_USER_MESSAGE.format(constants.STYLE_USERNAME, username, message) message = message.replace("\n", "
") if noTimestamp: self.newMessage("{}
".format(message)) else: self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
") @needsClient def getFileSwitchState(self, filename): if filename: if filename == getMessage("nofile-note"): return constants.FILEITEM_SWITCH_NO_SWITCH if self._syncplayClient.userlist.currentUser.file and utils.sameFilename(filename, self._syncplayClient.userlist.currentUser.file['name']): return constants.FILEITEM_SWITCH_NO_SWITCH if isURL(filename): return constants.FILEITEM_SWITCH_STREAM_SWITCH elif filename not in self.newWatchlist: if self._syncplayClient.fileSwitch.findFilepath(filename): return constants.FILEITEM_SWITCH_FILE_SWITCH else: self.newWatchlist.extend([filename]) return constants.FILEITEM_SWITCH_NO_SWITCH @needsClient def isItemUntrusted(self, filename): return isURL(filename) and not self._syncplayClient.isURITrusted(filename) @needsClient def isFileAvailable(self, filename): if filename: if filename == getMessage("nofile-note"): return None if isURL(filename): return True elif filename not in self.newWatchlist: if self._syncplayClient.fileSwitch.findFilepath(filename): return True else: self.newWatchlist.extend([filename]) return False @needsClient def showUserList(self, currentUser, rooms): self._usertreebuffer = QtGui.QStandardItemModel() self._usertreebuffer.setHorizontalHeaderLabels( ( getMessage("roomuser-heading-label"), getMessage("size-heading-label"), getMessage("duration-heading-label"), getMessage("filename-heading-label") )) usertreeRoot = self._usertreebuffer.invisibleRootItem() if ( self._syncplayClient.userlist.currentUser.file and self._syncplayClient.userlist.currentUser.file and os.path.isfile(self._syncplayClient.userlist.currentUser.file["path"]) ): self._syncplayClient.fileSwitch.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"])) for room in rooms: self.newWatchlist = [] roomitem = QtGui.QStandardItem(room) font = QtGui.QFont() font.setItalic(True) if room == currentUser.room: font.setWeight(QtGui.QFont.Bold) roomitem.setFont(font) roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable) usertreeRoot.appendRow(roomitem) isControlledRoom = RoomPasswordProvider.isControlledRoom(room) if isControlledRoom: if room == currentUser.room and currentUser.isController(): roomitem.setIcon(QtGui.QPixmap(resourcespath + 'lock_open.png')) else: roomitem.setIcon(QtGui.QPixmap(resourcespath + 'lock.png')) else: roomitem.setIcon(QtGui.QPixmap(resourcespath + 'chevrons_right.png')) for user in rooms[room]: useritem = QtGui.QStandardItem(user.username) isController = user.isController() sameRoom = room == currentUser.room if sameRoom: isReadyWithFile = user.isReadyWithFile() else: isReadyWithFile = None useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) useritem.setData(isReadyWithFile, Qt.UserRole + constants.USERITEM_READY_ROLE) if user.file: filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'])) filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration']))) filename = user.file['name'] if isURL(filename): filename = urllib.parse.unquote(filename) filenameitem = QtGui.QStandardItem(filename) fileSwitchState = self.getFileSwitchState(user.file['name']) if room == currentUser.room else None if fileSwitchState != constants.FILEITEM_SWITCH_NO_SWITCH: filenameTooltip = getMessage("switch-to-file-tooltip").format(filename) else: filenameTooltip = filename filenameitem.setToolTip(filenameTooltip) filenameitem.setData(fileSwitchState, Qt.UserRole + constants.FILEITEM_SWITCH_ROLE) if currentUser.file: sameName = sameFilename(user.file['name'], currentUser.file['name']) sameSize = sameFilesize(user.file['size'], currentUser.file['size']) sameDuration = sameFileduration(user.file['duration'], currentUser.file['duration']) underlinefont = QtGui.QFont() underlinefont.setUnderline(True) if sameRoom: if not sameName: filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) filenameitem.setFont(underlinefont) if not sameSize: if formatSize(user.file['size']) == formatSize(currentUser.file['size']): filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'], precise=True)) filesizeitem.setFont(underlinefont) filesizeitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) if not sameDuration: filedurationitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) filedurationitem.setFont(underlinefont) else: filenameitem = QtGui.QStandardItem(getMessage("nofile-note")) filedurationitem = QtGui.QStandardItem("") filesizeitem = QtGui.QStandardItem("") if room == currentUser.room: filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR))) font = QtGui.QFont() if currentUser.username == user.username: font.setWeight(QtGui.QFont.Bold) self.updateReadyState(currentUser.isReadyWithFile()) if isControlledRoom and not isController: useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) useritem.setFont(font) useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable) filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable) filedurationitem.setFlags(filedurationitem.flags() & ~Qt.ItemIsEditable) roomitem.appendRow((useritem, filesizeitem, filedurationitem, filenameitem)) self.listTreeModel = self._usertreebuffer self.listTreeView.setModel(self.listTreeModel) self.listTreeView.setItemDelegate(UserlistItemDelegate()) self.listTreeView.setItemsExpandable(False) self.listTreeView.setRootIsDecorated(False) self.listTreeView.expandAll() self.updateListGeometry() self._syncplayClient.fileSwitch.setFilenameWatchlist(self.newWatchlist) @needsClient def undoPlaylistChange(self): self._syncplayClient.playlist.undoPlaylistChange() @needsClient def shuffleRemainingPlaylist(self): self._syncplayClient.playlist.shuffleRemainingPlaylist() @needsClient def shuffleEntirePlaylist(self): self._syncplayClient.playlist.shuffleEntirePlaylist() @needsClient def openPlaylistMenu(self, position): indexes = self.playlist.selectedIndexes() if len(indexes) > 0: item = self.playlist.selectedIndexes()[0] else: item = None menu = QtWidgets.QMenu() if item: firstFile = item.sibling(item.row(), 0).data() pathFound = self._syncplayClient.fileSwitch.findFilepath(firstFile) if not isURL(firstFile) else None if self._syncplayClient.userlist.currentUser.file is None or firstFile != self._syncplayClient.userlist.currentUser.file["name"]: if isURL(firstFile): menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openstreamurl-menu-label"), lambda: self.openFile(firstFile, resetPosition=True)) elif pathFound: menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openmedia-menu-label"), lambda: self.openFile(pathFound, resetPosition=True)) if pathFound: menu.addAction(QtGui.QPixmap(resourcespath + "folder_film.png"), getMessage('open-containing-folder'), lambda: utils.open_system_file_browser(pathFound)) if self._syncplayClient.isUntrustedTrustableURI(firstFile): domain = utils.getDomainFromURL(firstFile) menu.addAction(QtGui.QPixmap(resourcespath + "shield_add.png"), getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain)) menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), getMessage("removefromplaylist-menu-label"), lambda: self.deleteSelectedPlaylistItems()) menu.addSeparator() menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), getMessage("shuffleremainingplaylist-menu-label"), lambda: self.shuffleRemainingPlaylist()) menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), getMessage("shuffleentireplaylist-menu-label"), lambda: self.shuffleEntirePlaylist()) menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), getMessage("undoplaylist-menu-label"), lambda: self.undoPlaylistChange()) menu.addAction(QtGui.QPixmap(resourcespath + "film_edit.png"), getMessage("editplaylist-menu-label"), lambda: self.openEditPlaylistDialog()) menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), getMessage("addfilestoplaylist-menu-label"), lambda: self.OpenAddFilesToPlaylistDialog()) menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), getMessage("addurlstoplaylist-menu-label"), lambda: self.OpenAddURIsToPlaylistDialog()) menu.addSeparator() menu.addAction(QtGui.QPixmap(resourcespath + "film_folder_edit.png"), getMessage("setmediadirectories-menu-label"), lambda: self.openSetMediaDirectoriesDialog()) menu.addAction(QtGui.QPixmap(resourcespath + "shield_edit.png"), getMessage("settrusteddomains-menu-label"), lambda: self.openSetTrustedDomainsDialog()) menu.exec_(self.playlist.viewport().mapToGlobal(position)) def openRoomMenu(self, position): # TODO: Deselect items after right click indexes = self.listTreeView.selectedIndexes() if len(indexes) > 0: item = self.listTreeView.selectedIndexes()[0] else: return menu = QtWidgets.QMenu() username = item.sibling(item.row(), 0).data() if username == self._syncplayClient.userlist.currentUser.username: shortUsername = getMessage("item-is-yours-indicator") elif len(username) < 15: shortUsername = getMessage("item-is-others-indicator").format(username) else: shortUsername = "{}...".format(getMessage("item-is-others-indicator").format(username[0:12])) # TODO: Enforce username limits in client and server filename = item.sibling(item.row(), 3).data() while item.parent().row() != -1: item = item.parent() roomToJoin = item.sibling(item.row(), 0).data() if roomToJoin != self._syncplayClient.getRoom(): menu.addAction(getMessage("joinroom-menu-label").format(roomToJoin), lambda: self.joinRoom(roomToJoin)) elif username and filename and filename != getMessage("nofile-note"): if self.config['sharedPlaylistEnabled'] and not self.isItemInPlaylist(filename): if isURL(filename): menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), getMessage("addusersstreamstoplaylist-menu-label").format(shortUsername), lambda: self.addStreamToPlaylist(filename)) else: menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), getMessage("addusersfiletoplaylist-menu-label").format(shortUsername), lambda: self.addStreamToPlaylist(filename)) if self._syncplayClient.userlist.currentUser.file is None or filename != self._syncplayClient.userlist.currentUser.file["name"]: if isURL(filename): menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openusersstream-menu-label").format(shortUsername), lambda: self.openFile(filename)) else: pathFound = self._syncplayClient.fileSwitch.findFilepath(filename) if pathFound: menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openusersfile-menu-label").format(shortUsername), lambda: self.openFile(pathFound)) if self._syncplayClient.isUntrustedTrustableURI(filename): domain = utils.getDomainFromURL(filename) menu.addAction(QtGui.QPixmap(resourcespath + "shield_add.png"), getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain)) if not isURL(filename) and filename != getMessage("nofile-note"): path = self._syncplayClient.fileSwitch.findFilepath(filename) if path: menu.addAction(QtGui.QPixmap(resourcespath + "folder_film.png"), getMessage('open-containing-folder'), lambda: utils.open_system_file_browser(path)) else: return menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) def updateListGeometry(self): try: roomtocheck = 0 while self.listTreeModel.item(roomtocheck): self.listTreeView.setFirstColumnSpanned(roomtocheck, self.listTreeView.rootIndex(), True) roomtocheck += 1 self.listTreeView.header().setStretchLastSection(False) if IsPySide2: self.listTreeView.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) self.listTreeView.header().setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) self.listTreeView.header().setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) self.listTreeView.header().setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) if IsPySide: self.listTreeView.header().setResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) self.listTreeView.header().setResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) self.listTreeView.header().setResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) self.listTreeView.header().setResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) NarrowTabsWidth = self.listTreeView.header().sectionSize(0)+self.listTreeView.header().sectionSize(1)+self.listTreeView.header().sectionSize(2) if self.listTreeView.header().width() < (NarrowTabsWidth+self.listTreeView.header().sectionSize(3)): self.listTreeView.header().resizeSection(3, self.listTreeView.header().width()-NarrowTabsWidth) else: if IsPySide2: self.listTreeView.header().setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch) if IsPySide: self.listTreeView.header().setResizeMode(3, QtWidgets.QHeaderView.Stretch) self.listTreeView.expandAll() except: pass def updateReadyState(self, newState): oldState = self.readyPushButton.isChecked() if newState != oldState and newState is not None: self.readyPushButton.blockSignals(True) self.readyPushButton.setChecked(newState) self.readyPushButton.blockSignals(False) self.updateReadyIcon() @needsClient def playlistItemClicked(self, item): # TODO: Integrate into client.py code filename = item.data() if self._isTryingToChangeToCurrentFile(filename): return if isURL(filename): self._syncplayClient._player.openFile(filename, resetPosition=True) else: pathFound = self._syncplayClient.fileSwitch.findFilepath(filename, highPriority=True) if pathFound: self._syncplayClient._player.openFile(pathFound, resetPosition=True) else: self._syncplayClient.ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) def _isTryingToChangeToCurrentFile(self, filename): if self._syncplayClient.userlist.currentUser.file and filename == self._syncplayClient.userlist.currentUser.file["name"]: self.showDebugMessage("File change request ignored (Syncplay should not be asked to change to current filename)") return True else: return False def roomClicked(self, item): username = item.sibling(item.row(), 0).data() filename = item.sibling(item.row(), 3).data() while item.parent().row() != -1: item = item.parent() roomToJoin = item.sibling(item.row(), 0).data() if roomToJoin != self._syncplayClient.getRoom(): self.joinRoom(item.sibling(item.row(), 0).data()) elif username and filename and username != self._syncplayClient.userlist.currentUser.username: if self._isTryingToChangeToCurrentFile(filename): return if isURL(filename): self._syncplayClient._player.openFile(filename) else: pathFound = self._syncplayClient.fileSwitch.findFilepath(filename, highPriority=True) if pathFound: self._syncplayClient._player.openFile(pathFound) else: self._syncplayClient.fileSwitch.updateInfo() self.showErrorMessage(getMessage("switch-file-not-found-error").format(filename)) @needsClient def userListChange(self): self._syncplayClient.showUserList() def fileSwitchFoundFiles(self): self._syncplayClient.showUserList() self.playlist.updatePlaylistIndexIcon() def updateRoomName(self, room=""): self.roomInput.setText(room) def showDebugMessage(self, message): print(message) def showErrorMessage(self, message, criticalerror=False): message = str(message) if criticalerror: QtWidgets.QMessageBox.critical(self, "Syncplay", message) message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") message = message.replace("<a href="https://syncplay.pl/trouble">", '').replace("</a>", "") message = message.replace("\n", "
") message = "".format(constants.STYLE_ERRORNOTIFICATION) + message + "" self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "
") @needsClient def joinRoom(self, room=None): if room is None: 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.roomInput.setText(room) if room != self._syncplayClient.getRoom(): self._syncplayClient.setRoom(room, resetAutoplay=True) self._syncplayClient.sendRoom() def seekPositionDialog(self): seekTime, ok = QtWidgets.QInputDialog.getText( self, getMessage("seektime-menu-label"), getMessage("seektime-msgbox-label"), QtWidgets.QLineEdit.Normal, "0:00") if ok and seekTime != '': self.seekPosition(seekTime) def seekFromButton(self): self.seekPosition(self.seekInput.text()) @needsClient def seekPosition(self, seekTime): s = re.match(constants.UI_SEEK_REGEX, seekTime) 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) else: self.showErrorMessage(getMessage("invalid-seek-value")) @needsClient def undoSeek(self): tmp_pos = self._syncplayClient.getPlayerPosition() self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek) self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos @needsClient def togglePause(self): self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused()) @needsClient def play(self): self._syncplayClient.setPaused(False) @needsClient def pause(self): self._syncplayClient.setPaused(True) @needsClient def exitSyncplay(self): self._syncplayClient.stop() def closeEvent(self, event): self.exitSyncplay() self.saveSettings() def loadMediaBrowseSettings(self): settings = QSettings("Syncplay", "MediaBrowseDialog") settings.beginGroup("MediaBrowseDialog") self.mediadirectory = settings.value("mediadir", "") settings.endGroup() def saveMediaBrowseSettings(self): settings = QSettings("Syncplay", "MediaBrowseDialog") settings.beginGroup("MediaBrowseDialog") settings.setValue("mediadir", self.mediadirectory) settings.endGroup() def getInitialMediaDirectory(self, includeUserSpecifiedDirectories=True): if IsPySide: if self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]) and includeUserSpecifiedDirectories: defaultdirectory = self.config["mediaSearchDirectories"][0] elif includeUserSpecifiedDirectories and os.path.isdir(self.mediadirectory): defaultdirectory = self.mediadirectory elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation)): defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation) elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation)): defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation) else: defaultdirectory = "" elif IsPySide2: if self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]) and includeUserSpecifiedDirectories: defaultdirectory = self.config["mediaSearchDirectories"][0] elif includeUserSpecifiedDirectories and os.path.isdir(self.mediadirectory): defaultdirectory = self.mediadirectory elif os.path.isdir(QStandardPaths.standardLocations(QStandardPaths.MoviesLocation)[0]): defaultdirectory = QStandardPaths.standardLocations(QStandardPaths.MoviesLocation)[0] elif os.path.isdir(QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0]): defaultdirectory = QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0] else: defaultdirectory = "" return defaultdirectory @needsClient def browseMediapath(self): if self._syncplayClient._player.customOpenDialog == True: self._syncplayClient._player.openCustomOpenDialog() return self.loadMediaBrowseSettings() if isMacOS() and IsPySide: options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.DontUseNativeDialog) else: options = QtWidgets.QFileDialog.Options() self.mediadirectory = "" currentdirectory = os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]) if self._syncplayClient.userlist.currentUser.file else None if currentdirectory and os.path.isdir(currentdirectory): defaultdirectory = currentdirectory else: defaultdirectory = self.getInitialMediaDirectory() browserfilter = "All files (*)" fileName, filtr = QtWidgets.QFileDialog.getOpenFileName( self, getMessage("browseformedia-label"), defaultdirectory, browserfilter, "", options) if fileName: if isWindows(): fileName = fileName.replace("/", "\\") self.mediadirectory = os.path.dirname(fileName) self._syncplayClient.fileSwitch.setCurrentDirectory(self.mediadirectory) self.saveMediaBrowseSettings() self._syncplayClient._player.openFile(fileName) @needsClient def OpenAddFilesToPlaylistDialog(self): if self._syncplayClient._player.customOpenDialog == True: self._syncplayClient._player.openCustomOpenDialog() return self.loadMediaBrowseSettings() if isMacOS() and IsPySide: options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.DontUseNativeDialog) else: options = QtWidgets.QFileDialog.Options() self.mediadirectory = "" currentdirectory = os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]) if self._syncplayClient.userlist.currentUser.file else None if currentdirectory and os.path.isdir(currentdirectory): defaultdirectory = currentdirectory else: defaultdirectory = self.getInitialMediaDirectory() browserfilter = "All files (*)" fileNames, filtr = QtWidgets.QFileDialog.getOpenFileNames( self, getMessage("browseformedia-label"), defaultdirectory, browserfilter, "", options) self.updatingPlaylist = True if fileNames: for fileName in fileNames: if isWindows(): fileName = fileName.replace("/", "\\") self.mediadirectory = os.path.dirname(fileName) self._syncplayClient.fileSwitch.setCurrentDirectory(self.mediadirectory) self.saveMediaBrowseSettings() self.addFileToPlaylist(fileName) self.updatingPlaylist = False self.playlist.updatePlaylist(self.getPlaylistState()) @needsClient def OpenAddURIsToPlaylistDialog(self): URIsDialog = QtWidgets.QDialog() URIsDialog.setWindowTitle(getMessage("adduris-msgbox-label")) URIsLayout = QtWidgets.QGridLayout() URIsLabel = QtWidgets.QLabel(getMessage("adduris-msgbox-label")) URIsLayout.addWidget(URIsLabel, 0, 0, 1, 1) URIsTextbox = QtWidgets.QPlainTextEdit() URIsTextbox.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) URIsLayout.addWidget(URIsTextbox, 1, 0, 1, 1) URIsButtonBox = QtWidgets.QDialogButtonBox() URIsButtonBox.setOrientation(Qt.Horizontal) URIsButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) URIsButtonBox.accepted.connect(URIsDialog.accept) URIsButtonBox.rejected.connect(URIsDialog.reject) URIsLayout.addWidget(URIsButtonBox, 2, 0, 1, 1) URIsDialog.setLayout(URIsLayout) URIsDialog.setModal(True) URIsDialog.show() result = URIsDialog.exec_() if result == QtWidgets.QDialog.Accepted: URIsToAdd = utils.convertMultilineStringToList(URIsTextbox.toPlainText()) self.updatingPlaylist = True for URI in URIsToAdd: URI = URI.rstrip() URI = urllib.parse.unquote(URI) if URI != "": self.addStreamToPlaylist(URI) self.updatingPlaylist = False @needsClient def openEditPlaylistDialog(self): oldPlaylist = utils.getListAsMultilineString(self.getPlaylistState()) editPlaylistDialog = QtWidgets.QDialog() editPlaylistDialog.setWindowTitle(getMessage("editplaylist-msgbox-label")) editPlaylistLayout = QtWidgets.QGridLayout() editPlaylistLabel = QtWidgets.QLabel(getMessage("editplaylist-msgbox-label")) editPlaylistLayout.addWidget(editPlaylistLabel, 0, 0, 1, 1) editPlaylistTextbox = QtWidgets.QPlainTextEdit(oldPlaylist) editPlaylistTextbox.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) editPlaylistLayout.addWidget(editPlaylistTextbox, 1, 0, 1, 1) editPlaylistButtonBox = QtWidgets.QDialogButtonBox() editPlaylistButtonBox.setOrientation(Qt.Horizontal) editPlaylistButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) editPlaylistButtonBox.accepted.connect(editPlaylistDialog.accept) editPlaylistButtonBox.rejected.connect(editPlaylistDialog.reject) editPlaylistLayout.addWidget(editPlaylistButtonBox, 2, 0, 1, 1) editPlaylistDialog.setLayout(editPlaylistLayout) editPlaylistDialog.setModal(True) editPlaylistDialog.setMinimumWidth(600) editPlaylistDialog.setMinimumHeight(500) editPlaylistDialog.show() result = editPlaylistDialog.exec_() if result == QtWidgets.QDialog.Accepted: newPlaylist = utils.convertMultilineStringToList(editPlaylistTextbox.toPlainText()) if newPlaylist != self.playlistState and self._syncplayClient and not self.updatingPlaylist: self.setPlaylist(newPlaylist) self._syncplayClient.playlist.changePlaylist(newPlaylist) self._syncplayClient.fileSwitch.updateInfo() @needsClient def openSetMediaDirectoriesDialog(self): MediaDirectoriesDialog = QtWidgets.QDialog() MediaDirectoriesDialog.setWindowTitle(getMessage("syncplay-mediasearchdirectories-title")) # TODO: Move to messages_*.py MediaDirectoriesLayout = QtWidgets.QGridLayout() MediaDirectoriesLabel = QtWidgets.QLabel(getMessage("syncplay-mediasearchdirectories-label")) MediaDirectoriesLayout.addWidget(MediaDirectoriesLabel, 0, 0, 1, 2) MediaDirectoriesTextbox = QtWidgets.QPlainTextEdit() MediaDirectoriesTextbox.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) MediaDirectoriesTextbox.setPlainText(utils.getListAsMultilineString(self.config["mediaSearchDirectories"])) MediaDirectoriesLayout.addWidget(MediaDirectoriesTextbox, 1, 0, 1, 1) MediaDirectoriesButtonBox = QtWidgets.QDialogButtonBox() MediaDirectoriesButtonBox.setOrientation(Qt.Horizontal) MediaDirectoriesButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) MediaDirectoriesButtonBox.accepted.connect(MediaDirectoriesDialog.accept) MediaDirectoriesButtonBox.rejected.connect(MediaDirectoriesDialog.reject) MediaDirectoriesLayout.addWidget(MediaDirectoriesButtonBox, 2, 0, 1, 1) MediaDirectoriesAddFolderButton = QtWidgets.QPushButton(getMessage("addfolder-label")) MediaDirectoriesAddFolderButton.pressed.connect(lambda: self.openAddMediaDirectoryDialog(MediaDirectoriesTextbox, MediaDirectoriesDialog)) MediaDirectoriesLayout.addWidget(MediaDirectoriesAddFolderButton, 1, 1, 1, 1, Qt.AlignTop) MediaDirectoriesDialog.setLayout(MediaDirectoriesLayout) MediaDirectoriesDialog.setModal(True) MediaDirectoriesDialog.show() result = MediaDirectoriesDialog.exec_() if result == QtWidgets.QDialog.Accepted: newMediaDirectories = utils.convertMultilineStringToList(MediaDirectoriesTextbox.toPlainText()) self._syncplayClient.fileSwitch.changeMediaDirectories(newMediaDirectories) @needsClient def openSetTrustedDomainsDialog(self): TrustedDomainsDialog = QtWidgets.QDialog() TrustedDomainsDialog.setWindowTitle(getMessage("syncplay-trusteddomains-title")) TrustedDomainsLayout = QtWidgets.QGridLayout() TrustedDomainsLabel = QtWidgets.QLabel(getMessage("trusteddomains-msgbox-label")) TrustedDomainsLayout.addWidget(TrustedDomainsLabel, 0, 0, 1, 1) TrustedDomainsTextbox = QtWidgets.QPlainTextEdit() TrustedDomainsTextbox.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) TrustedDomainsTextbox.setPlainText(utils.getListAsMultilineString(self.config["trustedDomains"])) TrustedDomainsLayout.addWidget(TrustedDomainsTextbox, 1, 0, 1, 1) TrustedDomainsButtonBox = QtWidgets.QDialogButtonBox() TrustedDomainsButtonBox.setOrientation(Qt.Horizontal) TrustedDomainsButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) TrustedDomainsButtonBox.accepted.connect(TrustedDomainsDialog.accept) TrustedDomainsButtonBox.rejected.connect(TrustedDomainsDialog.reject) TrustedDomainsLayout.addWidget(TrustedDomainsButtonBox, 2, 0, 1, 1) TrustedDomainsDialog.setLayout(TrustedDomainsLayout) TrustedDomainsDialog.setModal(True) TrustedDomainsDialog.show() result = TrustedDomainsDialog.exec_() if result == QtWidgets.QDialog.Accepted: newTrustedDomains = utils.convertMultilineStringToList(TrustedDomainsTextbox.toPlainText()) self._syncplayClient.setTrustedDomains(newTrustedDomains) @needsClient def addTrustedDomain(self, newDomain): trustedDomains = self.config["trustedDomains"][:] if newDomain: trustedDomains.append(newDomain) self._syncplayClient.setTrustedDomains(trustedDomains) @needsClient def openAddMediaDirectoryDialog(self, MediaDirectoriesTextbox, MediaDirectoriesDialog): if isMacOS() and IsPySide: options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) else: options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.ShowDirsOnly) folderName = str(QtWidgets.QFileDialog.getExistingDirectory( self, None, self.getInitialMediaDirectory(includeUserSpecifiedDirectories=False), options)) if folderName: existingMediaDirs = MediaDirectoriesTextbox.toPlainText() if existingMediaDirs == "": newMediaDirList = folderName else: newMediaDirList = existingMediaDirs + "\n" + folderName MediaDirectoriesTextbox.setPlainText(newMediaDirList) MediaDirectoriesDialog.raise_() MediaDirectoriesDialog.activateWindow() @needsClient def promptForStreamURL(self): streamURL, ok = QtWidgets.QInputDialog.getText( self, getMessage("promptforstreamurl-msgbox-label"), getMessage("promptforstreamurlinfo-msgbox-label"), QtWidgets.QLineEdit.Normal, "") if ok and streamURL != '': self._syncplayClient._player.openFile(streamURL) @needsClient def createControlledRoom(self): controlroom, ok = QtWidgets.QInputDialog.getText( self, getMessage("createcontrolledroom-msgbox-label"), getMessage("controlledroominfo-msgbox-label"), QtWidgets.QLineEdit.Normal, utils.stripRoomName(self._syncplayClient.getRoom())) if ok and controlroom != '': self._syncplayClient.createControlledRoom(controlroom) @needsClient def identifyAsController(self): msgboxtitle = getMessage("identifyascontroller-msgbox-label") msgboxtext = getMessage("identifyinfo-msgbox-label") controlpassword, ok = QtWidgets.QInputDialog.getText(self, msgboxtitle, msgboxtext, QtWidgets.QLineEdit.Normal, "") if ok and controlpassword != '': self._syncplayClient.identifyAsController(controlpassword) def _extractSign(self, m): if m: if m == "-": return -1 else: return 1 else: return None @needsClient def setOffset(self): newoffset, ok = QtWidgets.QInputDialog.getText( self, getMessage("setoffset-msgbox-label"), getMessage("offsetinfo-msgbox-label"), QtWidgets.QLineEdit.Normal, "") if ok and newoffset != '': o = re.match(constants.UI_OFFSET_REGEX, "o " + 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.showErrorMessage(getMessage("invalid-offset-value")) def openUserGuide(self): if isLinux(): self.QtGui.QDesktopServices.openUrl(QUrl("https://syncplay.pl/guide/linux/")) elif isWindows(): self.QtGui.QDesktopServices.openUrl(QUrl("https://syncplay.pl/guide/windows/")) else: self.QtGui.QDesktopServices.openUrl(QUrl("https://syncplay.pl/guide/")) def drop(self): self.close() def getPlaylistState(self): playlistItems = [] for playlistItem in range(self.playlist.count()): playlistItemText = self.playlist.item(playlistItem).text() if playlistItemText != getMessage("playlist-instruction-item-message"): playlistItems.append(playlistItemText) return playlistItems def playlistChangeCheck(self): if self.updatingPlaylist: return newPlaylist = self.getPlaylistState() if newPlaylist != self.playlistState and self._syncplayClient and not self.updatingPlaylist: self.playlistState = newPlaylist self._syncplayClient.playlist.changePlaylist(newPlaylist) self._syncplayClient.fileSwitch.updateInfo() def executeCommand(self, command): self.showMessage("/{}".format(command)) self.console.executeCommand(command) def sendChatMessage(self): chatText = self.chatInput.text() self.chatInput.setText("") if chatText != "": if chatText[:1] == "/" and chatText != "/": command = chatText[1:] if command and command[:1] == "/": chatText = chatText[1:] else: self.executeCommand(command) return self._syncplayClient.sendChat(chatText) def addTopLayout(self, window): window.topSplit = self.topSplitter(Qt.Horizontal, self) window.outputLayout = QtWidgets.QVBoxLayout() window.outputbox = QtWidgets.QTextBrowser() window.outputbox.setReadOnly(True) window.outputbox.setTextInteractionFlags(window.outputbox.textInteractionFlags() | Qt.TextSelectableByKeyboard) window.outputbox.setOpenExternalLinks(True) window.outputbox.unsetCursor() window.outputbox.moveCursor(QtGui.QTextCursor.End) window.outputbox.insertHtml(constants.STYLE_CONTACT_INFO.format(getMessage("contact-label"))) window.outputbox.moveCursor(QtGui.QTextCursor.End) window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) window.outputlabel = QtWidgets.QLabel(getMessage("notifications-heading-label")) window.outputlabel.setMinimumHeight(27) window.chatInput = QtWidgets.QLineEdit() window.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH) window.chatInput.returnPressed.connect(self.sendChatMessage) window.chatButton = QtWidgets.QPushButton( QtGui.QPixmap(resourcespath + 'email_go.png'), getMessage("sendmessage-label")) window.chatButton.pressed.connect(self.sendChatMessage) window.chatLayout = QtWidgets.QHBoxLayout() window.chatFrame = QtWidgets.QFrame() window.chatFrame.setLayout(self.chatLayout) window.chatFrame.setContentsMargins(0, 0, 0, 0) window.chatFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) window.chatLayout.setContentsMargins(0, 0, 0, 0) self.chatButton.setToolTip(getMessage("sendmessage-tooltip")) window.chatLayout.addWidget(window.chatInput) window.chatLayout.addWidget(window.chatButton) window.chatFrame.setMaximumHeight(window.chatFrame.sizeHint().height()) window.outputFrame = QtWidgets.QFrame() window.outputFrame.setLineWidth(0) window.outputFrame.setMidLineWidth(0) window.outputLayout.setContentsMargins(0, 0, 0, 0) window.outputLayout.addWidget(window.outputlabel) window.outputLayout.addWidget(window.outputbox) window.outputLayout.addWidget(window.chatFrame) window.outputFrame.setLayout(window.outputLayout) window.listLayout = QtWidgets.QVBoxLayout() window.listTreeModel = QtGui.QStandardItemModel() window.listTreeView = QtWidgets.QTreeView() window.listTreeView.setModel(window.listTreeModel) window.listTreeView.setIndentation(21) window.listTreeView.doubleClicked.connect(self.roomClicked) self.listTreeView.setContextMenuPolicy(Qt.CustomContextMenu) self.listTreeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.listTreeView.customContextMenuRequested.connect(self.openRoomMenu) window.listlabel = QtWidgets.QLabel(getMessage("userlist-heading-label")) window.listlabel.setMinimumHeight(27) if isMacOS: window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'lock_green.png').scaled(14, 14),"") window.sslButton.setVisible(False) window.sslButton.setFixedHeight(21) window.sslButton.setFixedWidth(21) window.sslButton.setMinimumSize(21, 21) window.sslButton.setStyleSheet("QPushButton:!hover{border: 1px solid gray;} QPushButton:hover{border:2px solid black;}") else: window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'lock_green.png'),"") window.sslButton.setVisible(False) window.sslButton.setFixedHeight(27) window.sslButton.setFixedWidth(27) window.sslButton.pressed.connect(self.openSSLDetails) window.sslButton.setToolTip(getMessage("sslconnection-tooltip")) window.listFrame = QtWidgets.QFrame() window.listFrame.setLineWidth(0) window.listFrame.setMidLineWidth(0) window.listFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) window.listLayout.setContentsMargins(0, 0, 0, 0) window.userlistLayout = QtWidgets.QGridLayout() window.userlistFrame = QtWidgets.QFrame() window.userlistFrame.setLineWidth(0) window.userlistFrame.setMidLineWidth(0) window.userlistFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) window.userlistLayout.setContentsMargins(0, 0, 0, 0) window.userlistFrame.setLayout(window.userlistLayout) window.userlistLayout.addWidget(window.listlabel, 0, 0, Qt.AlignLeft) window.userlistLayout.addWidget(window.sslButton, 0, 2, Qt.AlignRight) window.userlistLayout.addWidget(window.listTreeView, 1, 0, 1, 3) window.listSplit = QtWidgets.QSplitter(Qt.Vertical, self) window.listSplit.addWidget(window.userlistFrame) window.listLayout.addWidget(window.listSplit) window.roomInput = QtWidgets.QLineEdit() window.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH) window.roomInput.returnPressed.connect(self.joinRoom) window.roomButton = QtWidgets.QPushButton( QtGui.QPixmap(resourcespath + 'door_in.png'), getMessage("joinroom-label")) window.roomButton.pressed.connect(self.joinRoom) window.roomLayout = QtWidgets.QHBoxLayout() window.roomFrame = QtWidgets.QFrame() window.roomFrame.setLayout(self.roomLayout) window.roomFrame.setContentsMargins(0, 0, 0, 0) window.roomFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) window.roomLayout.setContentsMargins(0, 0, 0, 0) self.roomButton.setToolTip(getMessage("joinroom-tooltip")) window.roomLayout.addWidget(window.roomInput) window.roomLayout.addWidget(window.roomButton) window.roomFrame.setMaximumHeight(window.roomFrame.sizeHint().height()) window.listLayout.addWidget(window.roomFrame, Qt.AlignRight) window.listFrame.setLayout(window.listLayout) window.topSplit.addWidget(window.outputFrame) window.topSplit.addWidget(window.listFrame) window.topSplit.setStretchFactor(0, 4) window.topSplit.setStretchFactor(1, 5) window.mainLayout.addWidget(window.topSplit) window.topSplit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) def addBottomLayout(self, window): window.bottomLayout = QtWidgets.QHBoxLayout() window.bottomFrame = QtWidgets.QFrame() window.bottomFrame.setLayout(window.bottomLayout) window.bottomLayout.setContentsMargins(0, 0, 0, 0) self.addPlaybackLayout(window) window.playlistGroup = self.PlaylistGroupBox(getMessage("sharedplaylistenabled-label")) window.playlistGroup.setCheckable(True) window.playlistGroup.toggled.connect(self.changePlaylistEnabledState) window.playlistLayout = QtWidgets.QHBoxLayout() window.playlistGroup.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) window.playlistGroup.setAcceptDrops(True) window.playlist = self.PlaylistWidget() window.playlist.setWindow(window) window.playlist.setItemDelegate(self.PlaylistItemDelegate()) window.playlist.setDragEnabled(True) window.playlist.setAcceptDrops(True) window.playlist.setDropIndicatorShown(True) window.playlist.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) window.playlist.setDefaultDropAction(Qt.MoveAction) window.playlist.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) window.playlist.doubleClicked.connect(self.playlistItemClicked) window.playlist.setContextMenuPolicy(Qt.CustomContextMenu) window.playlist.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) window.playlist.customContextMenuRequested.connect(self.openPlaylistMenu) self.playlistUpdateTimer = task.LoopingCall(self.playlistChangeCheck) self.playlistUpdateTimer.start(0.1, True) noteFont = QtGui.QFont() noteFont.setItalic(True) playlistItem = QtWidgets.QListWidgetItem(getMessage("playlist-instruction-item-message")) playlistItem.setFont(noteFont) window.playlist.addItem(playlistItem) playlistItem.setFont(noteFont) window.playlist.addItem(playlistItem) window.playlistLayout.addWidget(window.playlist) window.playlistLayout.setAlignment(Qt.AlignTop) window.playlistGroup.setLayout(window.playlistLayout) window.listSplit.addWidget(window.playlistGroup) window.readyPushButton = QtWidgets.QPushButton() readyFont = QtGui.QFont() readyFont.setWeight(QtGui.QFont.Bold) window.readyPushButton.setText(getMessage("ready-guipushbuttonlabel")) window.readyPushButton.setCheckable(True) window.readyPushButton.setAutoExclusive(False) window.readyPushButton.toggled.connect(self.changeReadyState) window.readyPushButton.setFont(readyFont) window.readyPushButton.setStyleSheet(constants.STYLE_READY_PUSHBUTTON) window.readyPushButton.setToolTip(getMessage("ready-tooltip")) window.listLayout.addWidget(window.readyPushButton, Qt.AlignRight) window.autoplayLayout = QtWidgets.QHBoxLayout() window.autoplayFrame = QtWidgets.QFrame() window.autoplayFrame.setVisible(False) window.autoplayLayout.setContentsMargins(0, 0, 0, 0) window.autoplayFrame.setLayout(window.autoplayLayout) window.autoplayPushButton = QtWidgets.QPushButton() autoPlayFont = QtGui.QFont() autoPlayFont.setWeight(QtGui.QFont.Bold) window.autoplayPushButton.setText(getMessage("autoplay-guipushbuttonlabel")) window.autoplayPushButton.setCheckable(True) window.autoplayPushButton.setAutoExclusive(False) window.autoplayPushButton.toggled.connect(self.changeAutoplayState) window.autoplayPushButton.setFont(autoPlayFont) window.autoplayPushButton.setStyleSheet(constants.STYLE_AUTO_PLAY_PUSHBUTTON) window.autoplayPushButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) window.autoplayPushButton.setToolTip(getMessage("autoplay-tooltip")) window.autoplayLabel = QtWidgets.QLabel(getMessage("autoplay-minimum-label")) window.autoplayLabel.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) window.autoplayLabel.setMaximumWidth(window.autoplayLabel.minimumSizeHint().width()) window.autoplayLabel.setToolTip(getMessage("autoplay-tooltip")) window.autoplayThresholdSpinbox = QtWidgets.QSpinBox() window.autoplayThresholdSpinbox.setMaximumWidth(window.autoplayThresholdSpinbox.minimumSizeHint().width()) window.autoplayThresholdSpinbox.setMinimum(2) window.autoplayThresholdSpinbox.setMaximum(99) window.autoplayThresholdSpinbox.setToolTip(getMessage("autoplay-tooltip")) window.autoplayThresholdSpinbox.valueChanged.connect(self.changeAutoplayThreshold) window.autoplayLayout.addWidget(window.autoplayPushButton, Qt.AlignRight) window.autoplayLayout.addWidget(window.autoplayLabel, Qt.AlignRight) window.autoplayLayout.addWidget(window.autoplayThresholdSpinbox, Qt.AlignRight) window.listLayout.addWidget(window.autoplayFrame, Qt.AlignLeft) window.autoplayFrame.setMaximumHeight(window.autoplayFrame.sizeHint().height()) window.mainLayout.addWidget(window.bottomFrame, Qt.AlignLeft) window.bottomFrame.setMaximumHeight(window.bottomFrame.minimumSizeHint().height()) def addPlaybackLayout(self, window): window.playbackFrame = QtWidgets.QFrame() window.playbackFrame.setVisible(False) window.playbackFrame.setContentsMargins(0, 0, 0, 0) window.playbackLayout = QtWidgets.QHBoxLayout() window.playbackLayout.setAlignment(Qt.AlignLeft) window.playbackLayout.setContentsMargins(0, 0, 0, 0) window.playbackFrame.setLayout(window.playbackLayout) window.seekInput = QtWidgets.QLineEdit() window.seekInput.returnPressed.connect(self.seekFromButton) window.seekButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'clock_go.png'), "") window.seekButton.setToolTip(getMessage("seektime-menu-label")) window.seekButton.pressed.connect(self.seekFromButton) window.seekInput.setText("0:00") window.seekInput.setFixedWidth(60) window.playbackLayout.addWidget(window.seekInput) window.playbackLayout.addWidget(window.seekButton) window.unseekButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'arrow_undo.png'), "") window.unseekButton.setToolTip(getMessage("undoseek-menu-label")) window.unseekButton.pressed.connect(self.undoSeek) window.miscLayout = QtWidgets.QHBoxLayout() window.playbackLayout.addWidget(window.unseekButton) window.playButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'control_play_blue.png'), "") window.playButton.setToolTip(getMessage("play-menu-label")) window.playButton.pressed.connect(self.play) window.playbackLayout.addWidget(window.playButton) window.pauseButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'control_pause_blue.png'), "") window.pauseButton.setToolTip(getMessage("pause-menu-label")) window.pauseButton.pressed.connect(self.pause) window.playbackLayout.addWidget(window.pauseButton) window.playbackFrame.setMaximumHeight(window.playbackFrame.sizeHint().height()) window.playbackFrame.setMaximumWidth(window.playbackFrame.sizeHint().width()) window.outputLayout.addWidget(window.playbackFrame) def addMenubar(self, window): window.menuBar = QtWidgets.QMenuBar() # File menu window.fileMenu = QtWidgets.QMenu(getMessage("file-menu-label"), self) window.openAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'folder_explore.png'), getMessage("openmedia-menu-label")) window.openAction.triggered.connect(self.browseMediapath) window.openAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'world_explore.png'), getMessage("openstreamurl-menu-label")) window.openAction.triggered.connect(self.promptForStreamURL) window.openAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'film_folder_edit.png'), getMessage("setmediadirectories-menu-label")) window.openAction.triggered.connect(self.openSetMediaDirectoriesDialog) window.exitAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'cross.png'), getMessage("exit-menu-label")) window.exitAction.triggered.connect(self.exitSyncplay) window.menuBar.addMenu(window.fileMenu) # Playback menu window.playbackMenu = QtWidgets.QMenu(getMessage("playback-menu-label"), self) window.playAction = window.playbackMenu.addAction( QtGui.QPixmap(resourcespath + 'control_play_blue.png'), getMessage("play-menu-label")) window.playAction.triggered.connect(self.play) window.pauseAction = window.playbackMenu.addAction( QtGui.QPixmap(resourcespath + 'control_pause_blue.png'), getMessage("pause-menu-label")) window.pauseAction.triggered.connect(self.pause) window.seekAction = window.playbackMenu.addAction( QtGui.QPixmap(resourcespath + 'clock_go.png'), getMessage("seektime-menu-label")) window.seekAction.triggered.connect(self.seekPositionDialog) window.unseekAction = window.playbackMenu.addAction( QtGui.QPixmap(resourcespath + 'arrow_undo.png'), getMessage("undoseek-menu-label")) window.unseekAction.triggered.connect(self.undoSeek) window.menuBar.addMenu(window.playbackMenu) # Advanced menu window.advancedMenu = QtWidgets.QMenu(getMessage("advanced-menu-label"), self) window.setoffsetAction = window.advancedMenu.addAction( QtGui.QPixmap(resourcespath + 'timeline_marker.png'), getMessage("setoffset-menu-label")) window.setoffsetAction.triggered.connect(self.setOffset) window.setTrustedDomainsAction = window.advancedMenu.addAction( QtGui.QPixmap(resourcespath + 'shield_edit.png'), getMessage("settrusteddomains-menu-label")) window.setTrustedDomainsAction.triggered.connect(self.openSetTrustedDomainsDialog) window.createcontrolledroomAction = window.advancedMenu.addAction( QtGui.QPixmap(resourcespath + 'page_white_key.png'), getMessage("createcontrolledroom-menu-label")) window.createcontrolledroomAction.triggered.connect(self.createControlledRoom) window.identifyascontroller = window.advancedMenu.addAction(QtGui.QPixmap(resourcespath + 'key_go.png'), getMessage("identifyascontroller-menu-label")) window.identifyascontroller.triggered.connect(self.identifyAsController) window.menuBar.addMenu(window.advancedMenu) # Window menu window.windowMenu = QtWidgets.QMenu(getMessage("window-menu-label"), self) window.playbackAction = window.windowMenu.addAction(getMessage("playbackbuttons-menu-label")) window.playbackAction.setCheckable(True) window.playbackAction.triggered.connect(self.updatePlaybackFrameVisibility) window.autoplayAction = window.windowMenu.addAction(getMessage("autoplay-menu-label")) window.autoplayAction.setCheckable(True) window.autoplayAction.triggered.connect(self.updateAutoplayVisibility) window.menuBar.addMenu(window.windowMenu) # Help menu window.helpMenu = QtWidgets.QMenu(getMessage("help-menu-label"), self) window.userguideAction = window.helpMenu.addAction( QtGui.QPixmap(resourcespath + 'help.png'), getMessage("userguide-menu-label")) window.userguideAction.triggered.connect(self.openUserGuide) window.updateAction = window.helpMenu.addAction( QtGui.QPixmap(resourcespath + 'application_get.png'), getMessage("update-menu-label")) window.updateAction.triggered.connect(self.userCheckForUpdates) if not isMacOS(): window.helpMenu.addSeparator() window.about = window.helpMenu.addAction( QtGui.QPixmap(resourcespath + 'syncplay.png'), getMessage("about-menu-label")) else: window.about = window.helpMenu.addAction("&About") window.about.triggered.connect(self.openAbout) window.menuBar.addMenu(window.helpMenu) if not isMacOS(): window.mainLayout.setMenuBar(window.menuBar) @needsClient def openSSLDetails(self): sslDetailsBox = CertificateDialog(self.getSSLInformation()) sslDetailsBox.exec_() self.sslButton.setDown(False) def openAbout(self): aboutMsgBox = AboutDialog() aboutMsgBox.exec_() def addMainFrame(self, window): window.mainFrame = QtWidgets.QFrame() window.mainFrame.setLineWidth(0) window.mainFrame.setMidLineWidth(0) window.mainFrame.setContentsMargins(0, 0, 0, 0) window.mainFrame.setLayout(window.mainLayout) window.setCentralWidget(window.mainFrame) 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 updatePlaybackFrameVisibility(self): self.playbackFrame.setVisible(self.playbackAction.isChecked()) def updateAutoplayVisibility(self): self.autoplayFrame.setVisible(self.autoplayAction.isChecked()) def changeReadyState(self): self.updateReadyIcon() if self._syncplayClient: self._syncplayClient.changeReadyState(self.readyPushButton.isChecked()) else: self.showDebugMessage("Tried to change ready state too soon.") def changePlaylistEnabledState(self): self._syncplayClient.changePlaylistEnabledState(self.playlistGroup.isChecked()) @needsClient def changeAutoplayThreshold(self, source=None): self._syncplayClient.changeAutoPlayThrehsold(self.autoplayThresholdSpinbox.value()) def updateAutoPlayState(self, newState): oldState = self.autoplayPushButton.isChecked() if newState != oldState and newState is not None: self.autoplayPushButton.blockSignals(True) self.autoplayPushButton.setChecked(newState) self.autoplayPushButton.blockSignals(False) self.updateAutoPlayIcon() @needsClient def changeAutoplayState(self, source=None): self.updateAutoPlayIcon() if self._syncplayClient: self._syncplayClient.changeAutoplayState(self.autoplayPushButton.isChecked()) else: self.showDebugMessage("Tried to set AutoplayState too soon") def updateReadyIcon(self): ready = self.readyPushButton.isChecked() if ready: self.readyPushButton.setIcon(QtGui.QPixmap(resourcespath + 'tick_checkbox.png')) else: self.readyPushButton.setIcon(QtGui.QPixmap(resourcespath + 'empty_checkbox.png')) def updateAutoPlayIcon(self): ready = self.autoplayPushButton.isChecked() if ready: self.autoplayPushButton.setIcon(QtGui.QPixmap(resourcespath + 'tick_checkbox.png')) else: self.autoplayPushButton.setIcon(QtGui.QPixmap(resourcespath + 'empty_checkbox.png')) def automaticUpdateCheck(self): currentDateTimeValue = QDateTime.currentDateTime() if not self.config['checkForUpdatesAutomatically']: return try: if self.config['lastCheckedForUpdates']: configLastChecked = datetime.strptime(self.config["lastCheckedForUpdates"], "%Y-%m-%d %H:%M:%S.%f") if self.lastCheckedForUpdates is None or configLastChecked > self.lastCheckedForUpdates.toPython(): self.lastCheckedForUpdates = QDateTime.fromString(self.config["lastCheckedForUpdates"], 'yyyy-MM-dd HH-mm-ss') if self.lastCheckedForUpdates is None: self.checkForUpdates() else: timeDelta = currentDateTimeValue.toPython() - self.lastCheckedForUpdates.toPython() if timeDelta.total_seconds() > constants.AUTOMATIC_UPDATE_CHECK_FREQUENCY: self.checkForUpdates() except: self.showDebugMessage("Automatic check for updates failed. An update check was manually trigggered.") self.checkForUpdates() def userCheckForUpdates(self): self.checkForUpdates(userInitiated=True) @needsClient def checkForUpdates(self, userInitiated=False): self.lastCheckedForUpdates = QDateTime.currentDateTime() updateStatus, updateMessage, updateURL, self.publicServerList = self._syncplayClient.checkForUpdate(userInitiated) if updateMessage is None: if updateStatus == "uptodate": updateMessage = getMessage("syncplay-uptodate-notification") elif updateStatus == "updateavailale": updateMessage = getMessage("syncplay-updateavailable-notification") else: import syncplay updateMessage = getMessage("update-check-failed-notification").format(syncplay.version) if userInitiated == True: updateURL = constants.SYNCPLAY_DOWNLOAD_URL if updateURL is not None: reply = QtWidgets.QMessageBox.question( self, "Syncplay", updateMessage, QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) if reply == QtWidgets.QMessageBox.Yes: self.QtGui.QDesktopServices.openUrl(QUrl(updateURL)) elif userInitiated: QtWidgets.QMessageBox.information(self, "Syncplay", updateMessage) else: self.showMessage(updateMessage) def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': event.acceptProposedAction() def dropEvent(self, event): rewindFile = False if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted rewindFile = True data = event.mimeData() urls = data.urls() if urls and urls[0].scheme() == 'file': url = event.mimeData().urls()[0] if isMacOS() and IsPySide: macURL = NSString.alloc().initWithString_(str(url.toString())) pathString = macURL.stringByAddingPercentEscapesUsingEncoding_(NSUTF8StringEncoding) dropfilepath = os.path.abspath(NSURL.URLWithString_(pathString).filePathURL().path()) else: dropfilepath = os.path.abspath(str(url.toLocalFile())) if rewindFile == False: self._syncplayClient._player.openFile(dropfilepath) else: self._syncplayClient.setPosition(0) self._syncplayClient._player.openFile(dropfilepath, resetPosition=True) self._syncplayClient.setPosition(0) def setPlaylist(self, newPlaylist, newIndexFilename=None): if self.updatingPlaylist: self.ui.showDebugMessage("Trying to set playlist while it is already being updated") if newPlaylist == self.playlistState: if newIndexFilename: self.playlist.setPlaylistIndexFilename(newIndexFilename) self.updatingPlaylist = False return self.updatingPlaylist = True if newPlaylist and len(newPlaylist) > 0: self.clearedPlaylistNote = True self.playlistState = newPlaylist self.playlist.updatePlaylist(newPlaylist) if newIndexFilename: self.playlist.setPlaylistIndexFilename(newIndexFilename) self.updatingPlaylist = False self._syncplayClient.fileSwitch.updateInfo() def setPlaylistIndexFilename(self, filename): self.playlist.setPlaylistIndexFilename(filename) def addFileToPlaylist(self, filePath, index=-1): if os.path.isfile(filePath): self.removePlaylistNote() filename = os.path.basename(filePath) if self.noPlaylistDuplicates(filename): if self.playlist == -1 or index == -1: self.playlist.addItem(filename) else: self.playlist.insertItem(index, filename) self._syncplayClient.fileSwitch.notifyUserIfFileNotInMediaDirectory(filename, filePath) elif isURL(filePath): self.removePlaylistNote() if self.noPlaylistDuplicates(filePath): if self.playlist == -1 or index == -1: self.playlist.addItem(filePath) else: self.playlist.insertItem(index, filePath) def openFile(self, filePath, resetPosition=False): self._syncplayClient._player.openFile(filePath, resetPosition) def noPlaylistDuplicates(self, filename): if self.isItemInPlaylist(filename): self.showErrorMessage(getMessage("cannot-add-duplicate-error").format(filename)) return False else: return True def isItemInPlaylist(self, filename): for playlistindex in range(self.playlist.count()): if self.playlist.item(playlistindex).text() == filename: return True return False def addStreamToPlaylist(self, streamURI): self.removePlaylistNote() if self.noPlaylistDuplicates(streamURI): self.playlist.addItem(streamURI) def removePlaylistNote(self): if not self.clearedPlaylistNote: for index in range(self.playlist.count()): self.playlist.takeItem(0) self.clearedPlaylistNote = True def addFolderToPlaylist(self, folderPath): self.showErrorMessage("You tried to add the folder '{}' to the playlist. Syncplay only currently supports adding files to the playlist.".format(folderPath)) # TODO: Implement "add folder to playlist" def deleteSelectedPlaylistItems(self): self.playlist.remove_selected_items() def saveSettings(self): settings = QSettings("Syncplay", "MainWindow") settings.beginGroup("MainWindow") settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.setValue("showPlaybackButtons", self.playbackAction.isChecked()) settings.setValue("showAutoPlayButton", self.autoplayAction.isChecked()) settings.setValue("autoplayChecked", self.autoplayPushButton.isChecked()) settings.setValue("autoplayMinUsers", self.autoplayThresholdSpinbox.value()) settings.endGroup() settings = QSettings("Syncplay", "Interface") settings.beginGroup("Update") settings.setValue("lastCheckedQt", self.lastCheckedForUpdates) settings.endGroup() settings.beginGroup("PublicServerList") if self.publicServerList: settings.setValue("publicServers", self.publicServerList) settings.endGroup() def loadSettings(self): settings = QSettings("Syncplay", "MainWindow") settings.beginGroup("MainWindow") self.resize(settings.value("size", QSize(700, 500))) self.move(settings.value("pos", QPoint(200, 200))) if settings.value("showPlaybackButtons", "false") == "true": self.playbackAction.setChecked(True) self.updatePlaybackFrameVisibility() if settings.value("showAutoPlayButton", "false") == "true": self.autoplayAction.setChecked(True) self.updateAutoplayVisibility() if settings.value("autoplayChecked", "false") == "true": self.updateAutoPlayState(True) self.autoplayPushButton.setChecked(True) self.autoplayThresholdSpinbox.blockSignals(True) self.autoplayThresholdSpinbox.setValue(int(settings.value("autoplayMinUsers", 2))) self.autoplayThresholdSpinbox.blockSignals(False) settings.endGroup() settings = QSettings("Syncplay", "Interface") settings.beginGroup("Update") self.lastCheckedForUpdates = settings.value("lastCheckedQt", None) settings.endGroup() settings.beginGroup("PublicServerList") self.publicServerList = settings.value("publicServers", None) def __init__(self): super(MainWindow, self).__init__() self.console = ConsoleInGUI() self.console.setDaemon(True) self.newWatchlist = [] self.publicServerList = [] self.lastCheckedForUpdates = None self._syncplayClient = None self.folderSearchEnabled = True self.QtGui = QtGui if isMacOS(): self.setWindowFlags(self.windowFlags()) else: self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) self.setWindowTitle("Syncplay v" + version) self.mainLayout = QtWidgets.QVBoxLayout() self.addTopLayout(self) self.addBottomLayout(self) self.addMenubar(self) self.addMainFrame(self) self.loadSettings() self.setWindowIcon(QtGui.QPixmap(resourcespath + "syncplay.png")) self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.show() self.setAcceptDrops(True) self.clearedPlaylistNote = False