1 2import os 3import re 4import sys 5import time 6import urllib.error 7import urllib.parse 8import urllib.request 9from datetime import datetime 10from functools import wraps 11from platform import python_version 12 13from twisted.internet import task 14 15from syncplay import utils, constants, version, revision, release_number 16from syncplay.messages import getMessage 17from syncplay.ui.consoleUI import ConsoleUI 18from syncplay.utils import resourcespath 19from syncplay.utils import isLinux, isWindows, isMacOS 20from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize, isURL 21from syncplay.vendor import Qt 22from syncplay.vendor.Qt import QtCore, QtWidgets, QtGui, __binding__, __binding_version__, __qt_version__, IsPySide, IsPySide2 23from syncplay.vendor.Qt.QtCore import Qt, QSettings, QSize, QPoint, QUrl, QLine, QDateTime 24if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'): 25 QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) 26if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'): 27 QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) 28if IsPySide2: 29 from PySide2.QtCore import QStandardPaths 30if isMacOS() and IsPySide: 31 from Foundation import NSURL 32 from Cocoa import NSString, NSUTF8StringEncoding 33lastCheckedForUpdates = None 34from syncplay.vendor import darkdetect 35if isMacOS(): 36 isDarkMode = darkdetect.isDark() 37else: 38 isDarkMode = None 39 40 41class ConsoleInGUI(ConsoleUI): 42 def showMessage(self, message, noTimestamp=False): 43 self._syncplayClient.ui.showMessage(message, True) 44 45 def showDebugMessage(self, message): 46 self._syncplayClient.ui.showDebugMessage(message) 47 48 def showErrorMessage(self, message, criticalerror=False): 49 self._syncplayClient.ui.showErrorMessage(message, criticalerror) 50 51 def updateRoomName(self, room=""): 52 self._syncplayClient.ui.updateRoomName(room) 53 54 def getUserlist(self): 55 self._syncplayClient.showUserList(self) 56 57 58class UserlistItemDelegate(QtWidgets.QStyledItemDelegate): 59 def __init__(self, view=None): 60 self.view = view 61 QtWidgets.QStyledItemDelegate.__init__(self) 62 63 def sizeHint(self, option, index): 64 size = QtWidgets.QStyledItemDelegate.sizeHint(self, option, index) 65 if (index.column() == constants.USERLIST_GUI_USERNAME_COLUMN): 66 size.setWidth(size.width() + constants.USERLIST_GUI_USERNAME_OFFSET) 67 return size 68 69 def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): 70 column = indexQModelIndex.column() 71 midY = int((optionQStyleOptionViewItem.rect.y() + optionQStyleOptionViewItem.rect.bottomLeft().y()) / 2) 72 if column == constants.USERLIST_GUI_USERNAME_COLUMN: 73 currentQAbstractItemModel = indexQModelIndex.model() 74 itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), constants.USERLIST_GUI_USERNAME_COLUMN, indexQModelIndex.parent()) 75 controlIconQPixmap = QtGui.QPixmap(resourcespath + "user_key.png") 76 tickIconQPixmap = QtGui.QPixmap(resourcespath + "tick.png") 77 crossIconQPixmap = QtGui.QPixmap(resourcespath + "cross.png") 78 roomController = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) 79 userReady = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.USERITEM_READY_ROLE) 80 isUserRow = indexQModelIndex.parent() != indexQModelIndex.parent().parent() 81 bkgColor = self.view.palette().color(QtGui.QPalette.Base) 82 if isUserRow and (isMacOS() or isLinux()): 83 blankRect = QtCore.QRect(0, optionQStyleOptionViewItem.rect.y(), optionQStyleOptionViewItem.rect.width(), optionQStyleOptionViewItem.rect.height()) 84 itemQPainter.fillRect(blankRect, bkgColor) 85 86 if roomController and not controlIconQPixmap.isNull(): 87 itemQPainter.drawPixmap( 88 optionQStyleOptionViewItem.rect.x()+6, 89 midY-8, 90 controlIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) 91 92 if userReady and not tickIconQPixmap.isNull(): 93 itemQPainter.drawPixmap( 94 (optionQStyleOptionViewItem.rect.x()-10), 95 midY - 8, 96 tickIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) 97 98 elif userReady == False and not crossIconQPixmap.isNull(): 99 itemQPainter.drawPixmap( 100 (optionQStyleOptionViewItem.rect.x()-10), 101 midY - 8, 102 crossIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) 103 if isUserRow: 104 optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+constants.USERLIST_GUI_USERNAME_OFFSET) 105 if column == constants.USERLIST_GUI_FILENAME_COLUMN: 106 currentQAbstractItemModel = indexQModelIndex.model() 107 itemQModelIndex = currentQAbstractItemModel.index(indexQModelIndex.row(), constants.USERLIST_GUI_FILENAME_COLUMN, indexQModelIndex.parent()) 108 fileSwitchRole = currentQAbstractItemModel.data(itemQModelIndex, Qt.UserRole + constants.FILEITEM_SWITCH_ROLE) 109 if fileSwitchRole == constants.FILEITEM_SWITCH_FILE_SWITCH: 110 fileSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "film_go.png") 111 itemQPainter.drawPixmap( 112 (optionQStyleOptionViewItem.rect.x()), 113 midY - 8, 114 fileSwitchIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) 115 optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16) 116 117 elif fileSwitchRole == constants.FILEITEM_SWITCH_STREAM_SWITCH: 118 streamSwitchIconQPixmap = QtGui.QPixmap(resourcespath + "world_go.png") 119 itemQPainter.drawPixmap( 120 (optionQStyleOptionViewItem.rect.x()), 121 midY - 8, 122 streamSwitchIconQPixmap.scaled(16, 16, Qt.KeepAspectRatio)) 123 optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+16) 124 QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) 125 126 127class AboutDialog(QtWidgets.QDialog): 128 def __init__(self, parent=None): 129 super(AboutDialog, self).__init__(parent) 130 if isMacOS(): 131 self.setWindowTitle("") 132 self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint) 133 else: 134 self.setWindowTitle(getMessage("about-dialog-title")) 135 if isWindows(): 136 self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) 137 self.setWindowIcon(QtGui.QPixmap(resourcespath + 'syncplay.png')) 138 nameLabel = QtWidgets.QLabel("<center><strong>Syncplay</strong></center>") 139 nameLabel.setFont(QtGui.QFont("Helvetica", 18)) 140 linkLabel = QtWidgets.QLabel() 141 if isDarkMode: 142 linkLabel.setText(("<center><a href=\"https://syncplay.pl\" style=\"{}\">syncplay.pl</a></center>").format(constants.STYLE_DARK_ABOUT_LINK_COLOR)) 143 else: 144 linkLabel.setText("<center><a href=\"https://syncplay.pl\">syncplay.pl</a></center>") 145 linkLabel.setOpenExternalLinks(True) 146 versionExtString = version + revision 147 versionLabel = QtWidgets.QLabel( 148 "<p><center>" + getMessage("about-dialog-release").format(versionExtString, release_number) + 149 "<br />Python " + python_version() + " - " + __binding__ + " " + __binding_version__ + 150 " - Qt " + __qt_version__ + "</center></p>") 151 licenseLabel = QtWidgets.QLabel( 152 "<center><p>Copyright © 2012–2019 Syncplay</p><p>" + 153 getMessage("about-dialog-license-text") + "</p></center>") 154 aboutIcon = QtGui.QIcon() 155 aboutIcon.addFile(resourcespath + "syncplayAbout.png") 156 aboutIconLabel = QtWidgets.QLabel() 157 aboutIconLabel.setPixmap(aboutIcon.pixmap(64, 64)) 158 aboutLayout = QtWidgets.QGridLayout() 159 aboutLayout.addWidget(aboutIconLabel, 0, 0, 3, 4, Qt.AlignHCenter) 160 aboutLayout.addWidget(nameLabel, 3, 0, 1, 4) 161 aboutLayout.addWidget(linkLabel, 4, 0, 1, 4) 162 aboutLayout.addWidget(versionLabel, 5, 0, 1, 4) 163 aboutLayout.addWidget(licenseLabel, 6, 0, 1, 4) 164 licenseButton = QtWidgets.QPushButton(getMessage("about-dialog-license-button")) 165 licenseButton.setAutoDefault(False) 166 licenseButton.clicked.connect(self.openLicense) 167 aboutLayout.addWidget(licenseButton, 7, 0, 1, 2) 168 dependenciesButton = QtWidgets.QPushButton(getMessage("about-dialog-dependencies")) 169 dependenciesButton.setAutoDefault(False) 170 dependenciesButton.clicked.connect(self.openDependencies) 171 aboutLayout.addWidget(dependenciesButton, 7, 2, 1, 2) 172 aboutLayout.setVerticalSpacing(10) 173 aboutLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) 174 self.setSizeGripEnabled(False) 175 self.setLayout(aboutLayout) 176 177 def openLicense(self): 178 if isWindows(): 179 QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + "license.rtf")) 180 else: 181 QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + "license.rtf")) 182 183 def openDependencies(self): 184 if isWindows(): 185 QtGui.QDesktopServices.openUrl(QUrl("file:///" + resourcespath + "third-party-notices.rtf")) 186 else: 187 QtGui.QDesktopServices.openUrl(QUrl("file://" + resourcespath + "third-party-notices.rtf")) 188 189 190class CertificateDialog(QtWidgets.QDialog): 191 def __init__(self, tlsData, parent=None): 192 super(CertificateDialog, self).__init__(parent) 193 if isMacOS(): 194 self.setWindowTitle("") 195 self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint | Qt.CustomizeWindowHint) 196 else: 197 self.setWindowTitle(getMessage("tls-information-title")) 198 if isWindows(): 199 self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) 200 self.setWindowIcon(QtGui.QPixmap(resourcespath + 'syncplay.png')) 201 statusLabel = QtWidgets.QLabel(getMessage("tls-dialog-status-label").format(tlsData["subject"])) 202 descLabel = QtWidgets.QLabel(getMessage("tls-dialog-desc-label").format(tlsData["subject"])) 203 connDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-connection-label").format(tlsData["protocolVersion"], tlsData["cipher"])) 204 certDataLabel = QtWidgets.QLabel(getMessage("tls-dialog-certificate-label").format(tlsData["issuer"], tlsData["expires"])) 205 if isMacOS(): 206 statusLabel.setFont(QtGui.QFont("Helvetica", 12)) 207 descLabel.setFont(QtGui.QFont("Helvetica", 12)) 208 connDataLabel.setFont(QtGui.QFont("Helvetica", 12)) 209 certDataLabel.setFont(QtGui.QFont("Helvetica", 12)) 210 lockIcon = QtGui.QIcon() 211 lockIcon.addFile(resourcespath + "lock_green_dialog.png") 212 lockIconLabel = QtWidgets.QLabel() 213 lockIconLabel.setPixmap(lockIcon.pixmap(64, 64)) 214 certLayout = QtWidgets.QGridLayout() 215 certLayout.addWidget(lockIconLabel, 1, 0, 3, 1, Qt.AlignLeft | Qt.AlignTop) 216 certLayout.addWidget(statusLabel, 0, 1, 1, 3) 217 certLayout.addWidget(descLabel, 1, 1, 1, 3) 218 certLayout.addWidget(connDataLabel, 2, 1, 1, 3) 219 certLayout.addWidget(certDataLabel, 3, 1, 1, 3) 220 closeButton = QtWidgets.QPushButton("Close") 221 closeButton.setFixedWidth(100) 222 closeButton.setAutoDefault(False) 223 closeButton.clicked.connect(self.closeDialog) 224 certLayout.addWidget(closeButton, 4, 3, 1, 1) 225 certLayout.setVerticalSpacing(10) 226 certLayout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) 227 self.setSizeGripEnabled(False) 228 self.setLayout(certLayout) 229 230 def closeDialog(self): 231 self.close() 232 233 234class MainWindow(QtWidgets.QMainWindow): 235 insertPosition = None 236 playlistState = [] 237 updatingPlaylist = False 238 playlistIndex = None 239 sslInformation = "N/A" 240 sslMode = False 241 242 def setPlaylistInsertPosition(self, newPosition): 243 if not self.playlist.isEnabled(): 244 return 245 if MainWindow.insertPosition != newPosition: 246 MainWindow.insertPosition = newPosition 247 self.playlist.forceUpdate() 248 249 class PlaylistItemDelegate(QtWidgets.QStyledItemDelegate): 250 def paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex): 251 itemQPainter.save() 252 currentQAbstractItemModel = indexQModelIndex.model() 253 currentlyPlayingFile = currentQAbstractItemModel.data(indexQModelIndex, Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE) 254 if currentlyPlayingFile: 255 currentlyplayingIconQPixmap = QtGui.QPixmap(resourcespath + "bullet_right_grey.png") 256 midY = int((optionQStyleOptionViewItem.rect.y() + optionQStyleOptionViewItem.rect.bottomLeft().y()) / 2) 257 itemQPainter.drawPixmap( 258 (optionQStyleOptionViewItem.rect.x()+4), 259 midY-8, 260 currentlyplayingIconQPixmap.scaled(6, 16, Qt.KeepAspectRatio)) 261 optionQStyleOptionViewItem.rect.setX(optionQStyleOptionViewItem.rect.x()+10) 262 263 QtWidgets.QStyledItemDelegate.paint(self, itemQPainter, optionQStyleOptionViewItem, indexQModelIndex) 264 265 lineAbove = False 266 lineBelow = False 267 if MainWindow.insertPosition == 0 and indexQModelIndex.row() == 0: 268 lineAbove = True 269 elif MainWindow.insertPosition and indexQModelIndex.row() == MainWindow.insertPosition-1: 270 lineBelow = True 271 if lineAbove: 272 line = QLine(optionQStyleOptionViewItem.rect.topLeft(), optionQStyleOptionViewItem.rect.topRight()) 273 itemQPainter.drawLine(line) 274 elif lineBelow: 275 line = QLine(optionQStyleOptionViewItem.rect.bottomLeft(), optionQStyleOptionViewItem.rect.bottomRight()) 276 itemQPainter.drawLine(line) 277 itemQPainter.restore() 278 279 class PlaylistGroupBox(QtWidgets.QGroupBox): 280 281 def dragEnterEvent(self, event): 282 data = event.mimeData() 283 urls = data.urls() 284 window = self.parent().parent().parent().parent().parent() 285 if urls and urls[0].scheme() == 'file': 286 event.acceptProposedAction() 287 window.setPlaylistInsertPosition(window.playlist.count()) 288 else: 289 super(MainWindow.PlaylistGroupBox, self).dragEnterEvent(event) 290 291 def dragLeaveEvent(self, event): 292 window = self.parent().parent().parent().parent().parent() 293 window.setPlaylistInsertPosition(None) 294 295 def dropEvent(self, event): 296 window = self.parent().parent().parent().parent().parent() 297 if not window.playlist.isEnabled(): 298 return 299 window.setPlaylistInsertPosition(None) 300 if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: 301 QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted 302 data = event.mimeData() 303 urls = data.urls() 304 305 if urls and urls[0].scheme() == 'file': 306 indexRow = window.playlist.count() if window.clearedPlaylistNote else 0 307 308 for url in urls[::-1]: 309 if isMacOS() and IsPySide: 310 macURL = NSString.alloc().initWithString_(str(url.toString())) 311 pathString = macURL.stringByAddingPercentEscapesUsingEncoding_(NSUTF8StringEncoding) 312 dropfilepath = os.path.abspath(NSURL.URLWithString_(pathString).filePathURL().path()) 313 else: 314 dropfilepath = os.path.abspath(str(url.toLocalFile())) 315 if os.path.isfile(dropfilepath): 316 window.addFileToPlaylist(dropfilepath, indexRow) 317 elif os.path.isdir(dropfilepath): 318 window.addFolderToPlaylist(dropfilepath) 319 else: 320 super(MainWindow.PlaylistWidget, self).dropEvent(event) 321 322 class PlaylistWidget(QtWidgets.QListWidget): 323 selfWindow = None 324 playlistIndexFilename = None 325 326 def setPlaylistIndexFilename(self, filename): 327 if filename != self.playlistIndexFilename: 328 self.playlistIndexFilename = filename 329 self.updatePlaylistIndexIcon() 330 331 def updatePlaylistIndexIcon(self): 332 for item in range(self.count()): 333 itemFilename = self.item(item).text() 334 isPlayingFilename = itemFilename == self.playlistIndexFilename 335 self.item(item).setData(Qt.UserRole + constants.PLAYLISTITEM_CURRENTLYPLAYING_ROLE, isPlayingFilename) 336 fileIsAvailable = self.selfWindow.isFileAvailable(itemFilename) 337 fileIsUntrusted = self.selfWindow.isItemUntrusted(itemFilename) 338 if fileIsUntrusted: 339 if isDarkMode: 340 self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DARK_UNTRUSTEDITEM_COLOR))) 341 else: 342 self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_UNTRUSTEDITEM_COLOR))) 343 elif fileIsAvailable: 344 self.item(item).setForeground(QtGui.QBrush(self.selfWindow.palette().color(QtGui.QPalette.Text))) 345 else: 346 if isDarkMode: 347 self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DARK_DIFFERENTITEM_COLOR))) 348 else: 349 self.item(item).setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DIFFERENTITEM_COLOR))) 350 self.selfWindow._syncplayClient.fileSwitch.setFilenameWatchlist(self.selfWindow.newWatchlist) 351 self.forceUpdate() 352 353 def setWindow(self, window): 354 self.selfWindow = window 355 356 def dragLeaveEvent(self, event): 357 window = self.parent().parent().parent().parent().parent().parent() 358 window.setPlaylistInsertPosition(None) 359 360 def forceUpdate(self): 361 root = self.rootIndex() 362 self.dataChanged(root, root) 363 364 def keyPressEvent(self, event): 365 if event.key() == Qt.Key_Delete: 366 self.remove_selected_items() 367 else: 368 super(MainWindow.PlaylistWidget, self).keyPressEvent(event) 369 370 def updatePlaylist(self, newPlaylist): 371 for index in range(self.count()): 372 self.takeItem(0) 373 uniquePlaylist = [] 374 for item in newPlaylist: 375 if item not in uniquePlaylist: 376 uniquePlaylist.append(item) 377 self.insertItems(0, uniquePlaylist) 378 self.updatePlaylistIndexIcon() 379 380 def remove_selected_items(self): 381 for item in self.selectedItems(): 382 self.takeItem(self.row(item)) 383 384 def dragEnterEvent(self, event): 385 data = event.mimeData() 386 urls = data.urls() 387 if urls and urls[0].scheme() == 'file': 388 event.acceptProposedAction() 389 else: 390 super(MainWindow.PlaylistWidget, self).dragEnterEvent(event) 391 392 def dragMoveEvent(self, event): 393 data = event.mimeData() 394 urls = data.urls() 395 if urls and urls[0].scheme() == 'file': 396 event.acceptProposedAction() 397 indexRow = self.indexAt(event.pos()).row() 398 window = self.parent().parent().parent().parent().parent().parent() 399 if indexRow == -1 or not window.clearedPlaylistNote: 400 indexRow = window.playlist.count() 401 window.setPlaylistInsertPosition(indexRow) 402 else: 403 super(MainWindow.PlaylistWidget, self).dragMoveEvent(event) 404 405 def dropEvent(self, event): 406 window = self.parent().parent().parent().parent().parent().parent() 407 if not window.playlist.isEnabled(): 408 return 409 window.setPlaylistInsertPosition(None) 410 if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: 411 QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted 412 data = event.mimeData() 413 urls = data.urls() 414 415 if urls and urls[0].scheme() == 'file': 416 indexRow = self.indexAt(event.pos()).row() 417 if not window.clearedPlaylistNote: 418 indexRow = 0 419 if indexRow == -1: 420 indexRow = window.playlist.count() 421 for url in urls[::-1]: 422 if isMacOS() and IsPySide: 423 macURL = NSString.alloc().initWithString_(str(url.toString())) 424 pathString = macURL.stringByAddingPercentEscapesUsingEncoding_(NSUTF8StringEncoding) 425 dropfilepath = os.path.abspath(NSURL.URLWithString_(pathString).filePathURL().path()) 426 else: 427 dropfilepath = os.path.abspath(str(url.toLocalFile())) 428 if os.path.isfile(dropfilepath): 429 window.addFileToPlaylist(dropfilepath, indexRow) 430 elif os.path.isdir(dropfilepath): 431 window.addFolderToPlaylist(dropfilepath) 432 else: 433 super(MainWindow.PlaylistWidget, self).dropEvent(event) 434 435 class topSplitter(QtWidgets.QSplitter): 436 def createHandle(self): 437 return self.topSplitterHandle(self.orientation(), self) 438 439 class topSplitterHandle(QtWidgets.QSplitterHandle): 440 def mouseReleaseEvent(self, event): 441 QtWidgets.QSplitterHandle.mouseReleaseEvent(self, event) 442 self.parent().parent().parent().updateListGeometry() 443 444 def mouseMoveEvent(self, event): 445 QtWidgets.QSplitterHandle.mouseMoveEvent(self, event) 446 self.parent().parent().parent().updateListGeometry() 447 448 def needsClient(f): # @NoSelf 449 @wraps(f) 450 def wrapper(self, *args, **kwds): 451 if not self._syncplayClient: 452 self.showDebugMessage("Tried to use client before it was ready!") 453 return 454 return f(self, *args, **kwds) 455 return wrapper 456 457 def addClient(self, client): 458 self._syncplayClient = client 459 if self.console: 460 self.console.addClient(client) 461 self.roomInput.setText(self._syncplayClient.getRoom()) 462 self.config = self._syncplayClient.getConfig() 463 try: 464 self.playlistGroup.blockSignals(True) 465 self.playlistGroup.setChecked(self.config['sharedPlaylistEnabled']) 466 self.playlistGroup.blockSignals(False) 467 self._syncplayClient.fileSwitch.setMediaDirectories(self.config["mediaSearchDirectories"]) 468 if not self.config["mediaSearchDirectories"]: 469 self._syncplayClient.ui.showErrorMessage(getMessage("no-media-directories-error")) 470 self.updateReadyState(self.config['readyAtStart']) 471 autoplayInitialState = self.config['autoplayInitialState'] 472 if autoplayInitialState is not None: 473 self.autoplayPushButton.blockSignals(True) 474 self.autoplayPushButton.setChecked(autoplayInitialState) 475 self.autoplayPushButton.blockSignals(False) 476 if self.config['autoplayMinUsers'] > 1: 477 self.autoplayThresholdSpinbox.blockSignals(True) 478 self.autoplayThresholdSpinbox.setValue(self.config['autoplayMinUsers']) 479 self.autoplayThresholdSpinbox.blockSignals(False) 480 self.changeAutoplayState() 481 self.changeAutoplayThreshold() 482 self.updateAutoPlayIcon() 483 except: 484 self.showErrorMessage("Failed to load some settings.") 485 self.automaticUpdateCheck() 486 487 def promptFor(self, prompt=">", message=""): 488 # TODO: Prompt user 489 return None 490 491 def setFeatures(self, featureList): 492 if not featureList["readiness"]: 493 self.readyPushButton.setEnabled(False) 494 if not featureList["chat"]: 495 self.chatFrame.setEnabled(False) 496 self.chatInput.setReadOnly(True) 497 if not featureList["sharedPlaylists"]: 498 self.playlistGroup.setEnabled(False) 499 self.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH) 500 self.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH) 501 502 def setSSLMode(self, sslMode, sslInformation): 503 self.sslMode = sslMode 504 self.sslInformation = sslInformation 505 self.sslButton.setVisible(sslMode) 506 507 def getSSLInformation(self): 508 return self.sslInformation 509 510 def showMessage(self, message, noTimestamp=False): 511 message = str(message) 512 username = None 513 messageWithUsername = re.match(constants.MESSAGE_WITH_USERNAME_REGEX, message, re.UNICODE) 514 if messageWithUsername: 515 username = messageWithUsername.group("username") 516 message = messageWithUsername.group("message") 517 message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") 518 if username: 519 message = constants.STYLE_USER_MESSAGE.format(constants.STYLE_USERNAME, username, message) 520 message = message.replace("\n", "<br />") 521 if noTimestamp: 522 self.newMessage("{}<br />".format(message)) 523 else: 524 self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />") 525 526 @needsClient 527 def getFileSwitchState(self, filename): 528 if filename: 529 if filename == getMessage("nofile-note"): 530 return constants.FILEITEM_SWITCH_NO_SWITCH 531 if self._syncplayClient.userlist.currentUser.file and utils.sameFilename(filename, self._syncplayClient.userlist.currentUser.file['name']): 532 return constants.FILEITEM_SWITCH_NO_SWITCH 533 if isURL(filename): 534 return constants.FILEITEM_SWITCH_STREAM_SWITCH 535 elif filename not in self.newWatchlist: 536 if self._syncplayClient.fileSwitch.findFilepath(filename): 537 return constants.FILEITEM_SWITCH_FILE_SWITCH 538 else: 539 self.newWatchlist.extend([filename]) 540 return constants.FILEITEM_SWITCH_NO_SWITCH 541 542 @needsClient 543 def isItemUntrusted(self, filename): 544 return isURL(filename) and not self._syncplayClient.isURITrusted(filename) 545 546 @needsClient 547 def isFileAvailable(self, filename): 548 if filename: 549 if filename == getMessage("nofile-note"): 550 return None 551 if isURL(filename): 552 return True 553 elif filename not in self.newWatchlist: 554 if self._syncplayClient.fileSwitch.findFilepath(filename): 555 return True 556 else: 557 self.newWatchlist.extend([filename]) 558 return False 559 560 @needsClient 561 def showUserList(self, currentUser, rooms): 562 self._usertreebuffer = QtGui.QStandardItemModel() 563 self._usertreebuffer.setHorizontalHeaderLabels( 564 ( 565 getMessage("roomuser-heading-label"), getMessage("size-heading-label"), 566 getMessage("duration-heading-label"), getMessage("filename-heading-label") 567 )) 568 usertreeRoot = self._usertreebuffer.invisibleRootItem() 569 if ( 570 self._syncplayClient.userlist.currentUser.file and 571 self._syncplayClient.userlist.currentUser.file and 572 os.path.isfile(self._syncplayClient.userlist.currentUser.file["path"]) 573 ): 574 self._syncplayClient.fileSwitch.setCurrentDirectory(os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"])) 575 576 for room in rooms: 577 self.newWatchlist = [] 578 roomitem = QtGui.QStandardItem(room) 579 font = QtGui.QFont() 580 font.setItalic(True) 581 if room == currentUser.room: 582 font.setWeight(QtGui.QFont.Bold) 583 roomitem.setFont(font) 584 roomitem.setFlags(roomitem.flags() & ~Qt.ItemIsEditable) 585 usertreeRoot.appendRow(roomitem) 586 isControlledRoom = RoomPasswordProvider.isControlledRoom(room) 587 588 if isControlledRoom: 589 if room == currentUser.room and currentUser.isController(): 590 roomitem.setIcon(QtGui.QPixmap(resourcespath + 'lock_open.png')) 591 else: 592 roomitem.setIcon(QtGui.QPixmap(resourcespath + 'lock.png')) 593 else: 594 roomitem.setIcon(QtGui.QPixmap(resourcespath + 'chevrons_right.png')) 595 596 for user in rooms[room]: 597 useritem = QtGui.QStandardItem(user.username) 598 isController = user.isController() 599 sameRoom = room == currentUser.room 600 if sameRoom: 601 isReadyWithFile = user.isReadyWithFile() 602 else: 603 isReadyWithFile = None 604 useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) 605 useritem.setData(isReadyWithFile, Qt.UserRole + constants.USERITEM_READY_ROLE) 606 if user.file: 607 filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'])) 608 filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration']))) 609 filename = user.file['name'] 610 if isURL(filename): 611 filename = urllib.parse.unquote(filename) 612 filenameitem = QtGui.QStandardItem(filename) 613 fileSwitchState = self.getFileSwitchState(user.file['name']) if room == currentUser.room else None 614 if fileSwitchState != constants.FILEITEM_SWITCH_NO_SWITCH: 615 filenameTooltip = getMessage("switch-to-file-tooltip").format(filename) 616 else: 617 filenameTooltip = filename 618 filenameitem.setToolTip(filenameTooltip) 619 filenameitem.setData(fileSwitchState, Qt.UserRole + constants.FILEITEM_SWITCH_ROLE) 620 if currentUser.file: 621 sameName = sameFilename(user.file['name'], currentUser.file['name']) 622 sameSize = sameFilesize(user.file['size'], currentUser.file['size']) 623 sameDuration = sameFileduration(user.file['duration'], currentUser.file['duration']) 624 underlinefont = QtGui.QFont() 625 underlinefont.setUnderline(True) 626 differentItemColor = constants.STYLE_DARK_DIFFERENTITEM_COLOR if isDarkMode else constants.STYLE_DIFFERENTITEM_COLOR 627 if sameRoom: 628 if not sameName: 629 filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(differentItemColor))) 630 filenameitem.setFont(underlinefont) 631 if not sameSize: 632 if formatSize(user.file['size']) == formatSize(currentUser.file['size']): 633 filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'], precise=True)) 634 filesizeitem.setFont(underlinefont) 635 filesizeitem.setForeground(QtGui.QBrush(QtGui.QColor(differentItemColor))) 636 if not sameDuration: 637 filedurationitem.setForeground(QtGui.QBrush(QtGui.QColor(differentItemColor))) 638 filedurationitem.setFont(underlinefont) 639 else: 640 filenameitem = QtGui.QStandardItem(getMessage("nofile-note")) 641 filedurationitem = QtGui.QStandardItem("") 642 filesizeitem = QtGui.QStandardItem("") 643 if room == currentUser.room: 644 if isDarkMode: 645 filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_DARK_NOFILEITEM_COLOR))) 646 else: 647 filenameitem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOFILEITEM_COLOR))) 648 font = QtGui.QFont() 649 if currentUser.username == user.username: 650 font.setWeight(QtGui.QFont.Bold) 651 self.updateReadyState(currentUser.isReadyWithFile()) 652 if isControlledRoom and not isController: 653 useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) 654 useritem.setFont(font) 655 useritem.setFlags(useritem.flags() & ~Qt.ItemIsEditable) 656 filenameitem.setFlags(filenameitem.flags() & ~Qt.ItemIsEditable) 657 filesizeitem.setFlags(filesizeitem.flags() & ~Qt.ItemIsEditable) 658 filedurationitem.setFlags(filedurationitem.flags() & ~Qt.ItemIsEditable) 659 roomitem.appendRow((useritem, filesizeitem, filedurationitem, filenameitem)) 660 self.listTreeModel = self._usertreebuffer 661 self.listTreeView.setModel(self.listTreeModel) 662 self.listTreeView.setItemDelegate(UserlistItemDelegate(view=self.listTreeView)) 663 self.listTreeView.setItemsExpandable(False) 664 self.listTreeView.setRootIsDecorated(False) 665 self.listTreeView.expandAll() 666 self.updateListGeometry() 667 self._syncplayClient.fileSwitch.setFilenameWatchlist(self.newWatchlist) 668 669 @needsClient 670 def undoPlaylistChange(self): 671 self._syncplayClient.playlist.undoPlaylistChange() 672 673 @needsClient 674 def shuffleRemainingPlaylist(self): 675 self._syncplayClient.playlist.shuffleRemainingPlaylist() 676 677 @needsClient 678 def shuffleEntirePlaylist(self): 679 self._syncplayClient.playlist.shuffleEntirePlaylist() 680 681 @needsClient 682 def openPlaylistMenu(self, position): 683 indexes = self.playlist.selectedIndexes() 684 if len(indexes) > 0: 685 item = self.playlist.selectedIndexes()[0] 686 else: 687 item = None 688 menu = QtWidgets.QMenu() 689 690 if item: 691 firstFile = item.sibling(item.row(), 0).data() 692 pathFound = self._syncplayClient.fileSwitch.findFilepath(firstFile) if not isURL(firstFile) else None 693 if self._syncplayClient.userlist.currentUser.file is None or firstFile != self._syncplayClient.userlist.currentUser.file["name"]: 694 if isURL(firstFile): 695 menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openstreamurl-menu-label"), lambda: self.openFile(firstFile, resetPosition=True)) 696 elif pathFound: 697 menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openmedia-menu-label"), lambda: self.openFile(pathFound, resetPosition=True)) 698 if pathFound: 699 menu.addAction(QtGui.QPixmap(resourcespath + "folder_film.png"), 700 getMessage('open-containing-folder'), 701 lambda: utils.open_system_file_browser(pathFound)) 702 if self._syncplayClient.isUntrustedTrustableURI(firstFile): 703 domain = utils.getDomainFromURL(firstFile) 704 menu.addAction(QtGui.QPixmap(resourcespath + "shield_add.png"), getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain)) 705 menu.addAction(QtGui.QPixmap(resourcespath + "delete.png"), getMessage("removefromplaylist-menu-label"), lambda: self.deleteSelectedPlaylistItems()) 706 menu.addSeparator() 707 menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), getMessage("shuffleremainingplaylist-menu-label"), lambda: self.shuffleRemainingPlaylist()) 708 menu.addAction(QtGui.QPixmap(resourcespath + "arrow_switch.png"), getMessage("shuffleentireplaylist-menu-label"), lambda: self.shuffleEntirePlaylist()) 709 menu.addAction(QtGui.QPixmap(resourcespath + "arrow_undo.png"), getMessage("undoplaylist-menu-label"), lambda: self.undoPlaylistChange()) 710 menu.addAction(QtGui.QPixmap(resourcespath + "film_edit.png"), getMessage("editplaylist-menu-label"), lambda: self.openEditPlaylistDialog()) 711 menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), getMessage("addfilestoplaylist-menu-label"), lambda: self.OpenAddFilesToPlaylistDialog()) 712 menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), getMessage("addurlstoplaylist-menu-label"), lambda: self.OpenAddURIsToPlaylistDialog()) 713 menu.addSeparator() 714 menu.addAction(QtGui.QPixmap(resourcespath + "film_folder_edit.png"), getMessage("setmediadirectories-menu-label"), lambda: self.openSetMediaDirectoriesDialog()) 715 menu.addAction(QtGui.QPixmap(resourcespath + "shield_edit.png"), getMessage("settrusteddomains-menu-label"), lambda: self.openSetTrustedDomainsDialog()) 716 menu.exec_(self.playlist.viewport().mapToGlobal(position)) 717 718 def openRoomMenu(self, position): 719 # TODO: Deselect items after right click 720 indexes = self.listTreeView.selectedIndexes() 721 if len(indexes) > 0: 722 item = self.listTreeView.selectedIndexes()[0] 723 else: 724 return 725 726 menu = QtWidgets.QMenu() 727 username = item.sibling(item.row(), 0).data() 728 729 if len(username) < 15: 730 shortUsername = username 731 else: 732 shortUsername = "{}...".format(username[0:12]) 733 734 if username == self._syncplayClient.userlist.currentUser.username: 735 addUsersFileToPlaylistLabelText = getMessage("addyourfiletoplaylist-menu-label") 736 addUsersStreamToPlaylistLabelText = getMessage("addyourstreamstoplaylist-menu-label") 737 else: 738 addUsersFileToPlaylistLabelText = getMessage("addotherusersfiletoplaylist-menu-label").format(shortUsername) 739 addUsersStreamToPlaylistLabelText = getMessage("addotherusersstreamstoplaylist-menu-label").format(shortUsername) 740 741 filename = item.sibling(item.row(), 3).data() 742 while item.parent().row() != -1: 743 item = item.parent() 744 roomToJoin = item.sibling(item.row(), 0).data() 745 if roomToJoin != self._syncplayClient.getRoom(): 746 menu.addAction(getMessage("joinroom-menu-label").format(roomToJoin), lambda: self.joinRoom(roomToJoin)) 747 elif username and filename and filename != getMessage("nofile-note"): 748 if self.config['sharedPlaylistEnabled'] and not self.isItemInPlaylist(filename): 749 if isURL(filename): 750 menu.addAction(QtGui.QPixmap(resourcespath + "world_add.png"), addUsersStreamToPlaylistLabelText, lambda: self.addStreamToPlaylist(filename)) 751 else: 752 menu.addAction(QtGui.QPixmap(resourcespath + "film_add.png"), addUsersFileToPlaylistLabelText, lambda: self.addStreamToPlaylist(filename)) 753 754 if self._syncplayClient.userlist.currentUser.file is None or filename != self._syncplayClient.userlist.currentUser.file["name"]: 755 if isURL(filename): 756 menu.addAction(QtGui.QPixmap(resourcespath + "world_go.png"), getMessage("openusersstream-menu-label").format(shortUsername), lambda: self.openFile(filename)) 757 else: 758 pathFound = self._syncplayClient.fileSwitch.findFilepath(filename) 759 if pathFound: 760 menu.addAction(QtGui.QPixmap(resourcespath + "film_go.png"), getMessage("openusersfile-menu-label").format(shortUsername), lambda: self.openFile(pathFound)) 761 if self._syncplayClient.isUntrustedTrustableURI(filename): 762 domain = utils.getDomainFromURL(filename) 763 menu.addAction(QtGui.QPixmap(resourcespath + "shield_add.png"), getMessage("addtrusteddomain-menu-label").format(domain), lambda: self.addTrustedDomain(domain)) 764 765 if not isURL(filename) and filename != getMessage("nofile-note"): 766 path = self._syncplayClient.fileSwitch.findFilepath(filename) 767 if path: 768 menu.addAction(QtGui.QPixmap(resourcespath + "folder_film.png"), getMessage('open-containing-folder'), lambda: utils.open_system_file_browser(path)) 769 else: 770 return 771 menu.exec_(self.listTreeView.viewport().mapToGlobal(position)) 772 773 def updateListGeometry(self): 774 try: 775 roomtocheck = 0 776 while self.listTreeModel.item(roomtocheck): 777 self.listTreeView.setFirstColumnSpanned(roomtocheck, self.listTreeView.rootIndex(), True) 778 roomtocheck += 1 779 self.listTreeView.header().setStretchLastSection(False) 780 if IsPySide2: 781 self.listTreeView.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) 782 self.listTreeView.header().setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) 783 self.listTreeView.header().setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) 784 self.listTreeView.header().setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) 785 if IsPySide: 786 self.listTreeView.header().setResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) 787 self.listTreeView.header().setResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) 788 self.listTreeView.header().setResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) 789 self.listTreeView.header().setResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) 790 NarrowTabsWidth = self.listTreeView.header().sectionSize(0)+self.listTreeView.header().sectionSize(1)+self.listTreeView.header().sectionSize(2) 791 if self.listTreeView.header().width() < (NarrowTabsWidth+self.listTreeView.header().sectionSize(3)): 792 self.listTreeView.header().resizeSection(3, self.listTreeView.header().width()-NarrowTabsWidth) 793 else: 794 if IsPySide2: 795 self.listTreeView.header().setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch) 796 if IsPySide: 797 self.listTreeView.header().setResizeMode(3, QtWidgets.QHeaderView.Stretch) 798 self.listTreeView.expandAll() 799 except: 800 pass 801 802 def updateReadyState(self, newState): 803 oldState = self.readyPushButton.isChecked() 804 if newState != oldState and newState is not None: 805 self.readyPushButton.blockSignals(True) 806 self.readyPushButton.setChecked(newState) 807 self.readyPushButton.blockSignals(False) 808 self.updateReadyIcon() 809 810 @needsClient 811 def playlistItemClicked(self, item): 812 # TODO: Integrate into client.py code 813 filename = item.data() 814 if self._isTryingToChangeToCurrentFile(filename): 815 return 816 if isURL(filename): 817 self._syncplayClient._player.openFile(filename, resetPosition=True) 818 else: 819 pathFound = self._syncplayClient.fileSwitch.findFilepath(filename, highPriority=True) 820 if pathFound: 821 self._syncplayClient._player.openFile(pathFound, resetPosition=True) 822 else: 823 self._syncplayClient.ui.showErrorMessage(getMessage("cannot-find-file-for-playlist-switch-error").format(filename)) 824 825 def _isTryingToChangeToCurrentFile(self, filename): 826 if self._syncplayClient.userlist.currentUser.file and filename == self._syncplayClient.userlist.currentUser.file["name"]: 827 self.showDebugMessage("File change request ignored (Syncplay should not be asked to change to current filename)") 828 return True 829 else: 830 return False 831 832 def roomClicked(self, item): 833 username = item.sibling(item.row(), 0).data() 834 filename = item.sibling(item.row(), 3).data() 835 while item.parent().row() != -1: 836 item = item.parent() 837 roomToJoin = item.sibling(item.row(), 0).data() 838 if roomToJoin != self._syncplayClient.getRoom(): 839 self.joinRoom(item.sibling(item.row(), 0).data()) 840 elif username and filename and username != self._syncplayClient.userlist.currentUser.username: 841 if self._isTryingToChangeToCurrentFile(filename): 842 return 843 if isURL(filename): 844 self._syncplayClient._player.openFile(filename) 845 else: 846 pathFound = self._syncplayClient.fileSwitch.findFilepath(filename, highPriority=True) 847 if pathFound: 848 self._syncplayClient._player.openFile(pathFound) 849 else: 850 self._syncplayClient.fileSwitch.updateInfo() 851 self.showErrorMessage(getMessage("switch-file-not-found-error").format(filename)) 852 853 @needsClient 854 def userListChange(self): 855 self._syncplayClient.showUserList() 856 857 def fileSwitchFoundFiles(self): 858 self._syncplayClient.showUserList() 859 self.playlist.updatePlaylistIndexIcon() 860 861 def updateRoomName(self, room=""): 862 self.roomInput.setText(room) 863 864 def showDebugMessage(self, message): 865 print(message) 866 867 def showErrorMessage(self, message, criticalerror=False): 868 message = str(message) 869 if criticalerror: 870 QtWidgets.QMessageBox.critical(self, "Syncplay", message) 871 message = message.replace("&", "&").replace('"', """).replace("<", "<").replace(">", ">") 872 message = message.replace("<a href="https://syncplay.pl/trouble">", '<a href="https://syncplay.pl/trouble">').replace("</a>", "</a>") 873 message = message.replace("\n", "<br />") 874 if isDarkMode: 875 message = "<span style=\"{}\">".format(constants.STYLE_DARK_ERRORNOTIFICATION) + message + "</span>" 876 else: 877 message = "<span style=\"{}\">".format(constants.STYLE_ERRORNOTIFICATION) + message + "</span>" 878 self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />") 879 880 @needsClient 881 def joinRoom(self, room=None): 882 if room is None: 883 room = self.roomInput.text() 884 if room == "": 885 if self._syncplayClient.userlist.currentUser.file: 886 room = self._syncplayClient.userlist.currentUser.file["name"] 887 else: 888 room = self._syncplayClient.defaultRoom 889 self.roomInput.setText(room) 890 if room != self._syncplayClient.getRoom(): 891 self._syncplayClient.setRoom(room, resetAutoplay=True) 892 self._syncplayClient.sendRoom() 893 894 def seekPositionDialog(self): 895 seekTime, ok = QtWidgets.QInputDialog.getText( 896 self, getMessage("seektime-menu-label"), 897 getMessage("seektime-msgbox-label"), QtWidgets.QLineEdit.Normal, 898 "0:00") 899 if ok and seekTime != '': 900 self.seekPosition(seekTime) 901 902 def seekFromButton(self): 903 self.seekPosition(self.seekInput.text()) 904 905 @needsClient 906 def seekPosition(self, seekTime): 907 s = re.match(constants.UI_SEEK_REGEX, seekTime) 908 if s: 909 sign = self._extractSign(s.group('sign')) 910 t = utils.parseTime(s.group('time')) 911 if t is None: 912 return 913 if sign: 914 t = self._syncplayClient.getGlobalPosition() + sign * t 915 self._syncplayClient.setPosition(t) 916 else: 917 self.showErrorMessage(getMessage("invalid-seek-value")) 918 919 @needsClient 920 def undoSeek(self): 921 tmp_pos = self._syncplayClient.getPlayerPosition() 922 self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek) 923 self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos 924 925 @needsClient 926 def togglePause(self): 927 self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused()) 928 929 @needsClient 930 def play(self): 931 self._syncplayClient.setPaused(False) 932 933 @needsClient 934 def pause(self): 935 self._syncplayClient.setPaused(True) 936 937 @needsClient 938 def exitSyncplay(self): 939 self._syncplayClient.stop() 940 941 def closeEvent(self, event): 942 self.exitSyncplay() 943 self.saveSettings() 944 945 def loadMediaBrowseSettings(self): 946 settings = QSettings("Syncplay", "MediaBrowseDialog") 947 settings.beginGroup("MediaBrowseDialog") 948 self.mediadirectory = settings.value("mediadir", "") 949 settings.endGroup() 950 951 def saveMediaBrowseSettings(self): 952 settings = QSettings("Syncplay", "MediaBrowseDialog") 953 settings.beginGroup("MediaBrowseDialog") 954 settings.setValue("mediadir", self.mediadirectory) 955 settings.endGroup() 956 957 def getInitialMediaDirectory(self, includeUserSpecifiedDirectories=True): 958 if IsPySide: 959 if self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]) and includeUserSpecifiedDirectories: 960 defaultdirectory = self.config["mediaSearchDirectories"][0] 961 elif includeUserSpecifiedDirectories and os.path.isdir(self.mediadirectory): 962 defaultdirectory = self.mediadirectory 963 elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation)): 964 defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.MoviesLocation) 965 elif os.path.isdir(QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation)): 966 defaultdirectory = QtGui.QDesktopServices.storageLocation(QtGui.QDesktopServices.HomeLocation) 967 else: 968 defaultdirectory = "" 969 elif IsPySide2: 970 if self.config["mediaSearchDirectories"] and os.path.isdir(self.config["mediaSearchDirectories"][0]) and includeUserSpecifiedDirectories: 971 defaultdirectory = self.config["mediaSearchDirectories"][0] 972 elif includeUserSpecifiedDirectories and os.path.isdir(self.mediadirectory): 973 defaultdirectory = self.mediadirectory 974 elif os.path.isdir(QStandardPaths.standardLocations(QStandardPaths.MoviesLocation)[0]): 975 defaultdirectory = QStandardPaths.standardLocations(QStandardPaths.MoviesLocation)[0] 976 elif os.path.isdir(QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0]): 977 defaultdirectory = QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0] 978 else: 979 defaultdirectory = "" 980 return defaultdirectory 981 982 @needsClient 983 def browseMediapath(self): 984 if self._syncplayClient._player.customOpenDialog == True: 985 self._syncplayClient._player.openCustomOpenDialog() 986 return 987 988 self.loadMediaBrowseSettings() 989 if isMacOS() and IsPySide: 990 options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.DontUseNativeDialog) 991 else: 992 options = QtWidgets.QFileDialog.Options() 993 self.mediadirectory = "" 994 currentdirectory = os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]) if self._syncplayClient.userlist.currentUser.file else None 995 if currentdirectory and os.path.isdir(currentdirectory): 996 defaultdirectory = currentdirectory 997 else: 998 defaultdirectory = self.getInitialMediaDirectory() 999 browserfilter = "All files (*)" 1000 fileName, filtr = QtWidgets.QFileDialog.getOpenFileName( 1001 self, getMessage("browseformedia-label"), defaultdirectory, 1002 browserfilter, "", options) 1003 if fileName: 1004 if isWindows(): 1005 fileName = fileName.replace("/", "\\") 1006 self.mediadirectory = os.path.dirname(fileName) 1007 self._syncplayClient.fileSwitch.setCurrentDirectory(self.mediadirectory) 1008 self.saveMediaBrowseSettings() 1009 self._syncplayClient._player.openFile(fileName) 1010 1011 @needsClient 1012 def OpenAddFilesToPlaylistDialog(self): 1013 if self._syncplayClient._player.customOpenDialog == True: 1014 self._syncplayClient._player.openCustomOpenDialog() 1015 return 1016 1017 self.loadMediaBrowseSettings() 1018 if isMacOS() and IsPySide: 1019 options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.DontUseNativeDialog) 1020 else: 1021 options = QtWidgets.QFileDialog.Options() 1022 self.mediadirectory = "" 1023 currentdirectory = os.path.dirname(self._syncplayClient.userlist.currentUser.file["path"]) if self._syncplayClient.userlist.currentUser.file else None 1024 if currentdirectory and os.path.isdir(currentdirectory): 1025 defaultdirectory = currentdirectory 1026 else: 1027 defaultdirectory = self.getInitialMediaDirectory() 1028 browserfilter = "All files (*)" 1029 fileNames, filtr = QtWidgets.QFileDialog.getOpenFileNames( 1030 self, getMessage("browseformedia-label"), defaultdirectory, 1031 browserfilter, "", options) 1032 self.updatingPlaylist = True 1033 if fileNames: 1034 for fileName in fileNames: 1035 if isWindows(): 1036 fileName = fileName.replace("/", "\\") 1037 self.mediadirectory = os.path.dirname(fileName) 1038 self._syncplayClient.fileSwitch.setCurrentDirectory(self.mediadirectory) 1039 self.saveMediaBrowseSettings() 1040 self.addFileToPlaylist(fileName) 1041 self.updatingPlaylist = False 1042 self.playlist.updatePlaylist(self.getPlaylistState()) 1043 1044 @needsClient 1045 def OpenAddURIsToPlaylistDialog(self): 1046 URIsDialog = QtWidgets.QDialog() 1047 URIsDialog.setWindowTitle(getMessage("adduris-msgbox-label")) 1048 URIsLayout = QtWidgets.QGridLayout() 1049 URIsLabel = QtWidgets.QLabel(getMessage("adduris-msgbox-label")) 1050 URIsLayout.addWidget(URIsLabel, 0, 0, 1, 1) 1051 URIsTextbox = QtWidgets.QPlainTextEdit() 1052 URIsTextbox.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) 1053 URIsLayout.addWidget(URIsTextbox, 1, 0, 1, 1) 1054 URIsButtonBox = QtWidgets.QDialogButtonBox() 1055 URIsButtonBox.setOrientation(Qt.Horizontal) 1056 URIsButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) 1057 URIsButtonBox.accepted.connect(URIsDialog.accept) 1058 URIsButtonBox.rejected.connect(URIsDialog.reject) 1059 URIsLayout.addWidget(URIsButtonBox, 2, 0, 1, 1) 1060 URIsDialog.setLayout(URIsLayout) 1061 URIsDialog.setModal(True) 1062 URIsDialog.show() 1063 result = URIsDialog.exec_() 1064 if result == QtWidgets.QDialog.Accepted: 1065 URIsToAdd = utils.convertMultilineStringToList(URIsTextbox.toPlainText()) 1066 self.updatingPlaylist = True 1067 for URI in URIsToAdd: 1068 URI = URI.rstrip() 1069 URI = urllib.parse.unquote(URI) 1070 if URI != "": 1071 self.addStreamToPlaylist(URI) 1072 self.updatingPlaylist = False 1073 1074 @needsClient 1075 def openEditPlaylistDialog(self): 1076 oldPlaylist = utils.getListAsMultilineString(self.getPlaylistState()) 1077 editPlaylistDialog = QtWidgets.QDialog() 1078 editPlaylistDialog.setWindowTitle(getMessage("editplaylist-msgbox-label")) 1079 editPlaylistLayout = QtWidgets.QGridLayout() 1080 editPlaylistLabel = QtWidgets.QLabel(getMessage("editplaylist-msgbox-label")) 1081 editPlaylistLayout.addWidget(editPlaylistLabel, 0, 0, 1, 1) 1082 editPlaylistTextbox = QtWidgets.QPlainTextEdit(oldPlaylist) 1083 editPlaylistTextbox.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) 1084 editPlaylistLayout.addWidget(editPlaylistTextbox, 1, 0, 1, 1) 1085 editPlaylistButtonBox = QtWidgets.QDialogButtonBox() 1086 editPlaylistButtonBox.setOrientation(Qt.Horizontal) 1087 editPlaylistButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) 1088 editPlaylistButtonBox.accepted.connect(editPlaylistDialog.accept) 1089 editPlaylistButtonBox.rejected.connect(editPlaylistDialog.reject) 1090 editPlaylistLayout.addWidget(editPlaylistButtonBox, 2, 0, 1, 1) 1091 editPlaylistDialog.setLayout(editPlaylistLayout) 1092 editPlaylistDialog.setModal(True) 1093 editPlaylistDialog.setMinimumWidth(600) 1094 editPlaylistDialog.setMinimumHeight(500) 1095 editPlaylistDialog.show() 1096 result = editPlaylistDialog.exec_() 1097 if result == QtWidgets.QDialog.Accepted: 1098 newPlaylist = utils.convertMultilineStringToList(editPlaylistTextbox.toPlainText()) 1099 if newPlaylist != self.playlistState and self._syncplayClient and not self.updatingPlaylist: 1100 self.setPlaylist(newPlaylist) 1101 self._syncplayClient.playlist.changePlaylist(newPlaylist) 1102 self._syncplayClient.fileSwitch.updateInfo() 1103 1104 @needsClient 1105 def openSetMediaDirectoriesDialog(self): 1106 MediaDirectoriesDialog = QtWidgets.QDialog() 1107 MediaDirectoriesDialog.setWindowTitle(getMessage("syncplay-mediasearchdirectories-title")) # TODO: Move to messages_*.py 1108 MediaDirectoriesLayout = QtWidgets.QGridLayout() 1109 MediaDirectoriesLabel = QtWidgets.QLabel(getMessage("syncplay-mediasearchdirectories-label")) 1110 MediaDirectoriesLayout.addWidget(MediaDirectoriesLabel, 0, 0, 1, 2) 1111 MediaDirectoriesTextbox = QtWidgets.QPlainTextEdit() 1112 MediaDirectoriesTextbox.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) 1113 MediaDirectoriesTextbox.setPlainText(utils.getListAsMultilineString(self.config["mediaSearchDirectories"])) 1114 MediaDirectoriesLayout.addWidget(MediaDirectoriesTextbox, 1, 0, 1, 1) 1115 MediaDirectoriesButtonBox = QtWidgets.QDialogButtonBox() 1116 MediaDirectoriesButtonBox.setOrientation(Qt.Horizontal) 1117 MediaDirectoriesButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) 1118 MediaDirectoriesButtonBox.accepted.connect(MediaDirectoriesDialog.accept) 1119 MediaDirectoriesButtonBox.rejected.connect(MediaDirectoriesDialog.reject) 1120 MediaDirectoriesLayout.addWidget(MediaDirectoriesButtonBox, 2, 0, 1, 1) 1121 MediaDirectoriesAddFolderButton = QtWidgets.QPushButton(getMessage("addfolder-label")) 1122 MediaDirectoriesAddFolderButton.pressed.connect(lambda: self.openAddMediaDirectoryDialog(MediaDirectoriesTextbox, MediaDirectoriesDialog)) 1123 MediaDirectoriesLayout.addWidget(MediaDirectoriesAddFolderButton, 1, 1, 1, 1, Qt.AlignTop) 1124 MediaDirectoriesDialog.setLayout(MediaDirectoriesLayout) 1125 MediaDirectoriesDialog.setModal(True) 1126 MediaDirectoriesDialog.show() 1127 result = MediaDirectoriesDialog.exec_() 1128 if result == QtWidgets.QDialog.Accepted: 1129 newMediaDirectories = utils.convertMultilineStringToList(MediaDirectoriesTextbox.toPlainText()) 1130 self._syncplayClient.fileSwitch.changeMediaDirectories(newMediaDirectories) 1131 1132 @needsClient 1133 def openSetTrustedDomainsDialog(self): 1134 TrustedDomainsDialog = QtWidgets.QDialog() 1135 TrustedDomainsDialog.setWindowTitle(getMessage("syncplay-trusteddomains-title")) 1136 TrustedDomainsLayout = QtWidgets.QGridLayout() 1137 TrustedDomainsLabel = QtWidgets.QLabel(getMessage("trusteddomains-msgbox-label")) 1138 TrustedDomainsLayout.addWidget(TrustedDomainsLabel, 0, 0, 1, 1) 1139 TrustedDomainsTextbox = QtWidgets.QPlainTextEdit() 1140 TrustedDomainsTextbox.setLineWrapMode(QtWidgets.QPlainTextEdit.NoWrap) 1141 TrustedDomainsTextbox.setPlainText(utils.getListAsMultilineString(self.config["trustedDomains"])) 1142 TrustedDomainsLayout.addWidget(TrustedDomainsTextbox, 1, 0, 1, 1) 1143 TrustedDomainsButtonBox = QtWidgets.QDialogButtonBox() 1144 TrustedDomainsButtonBox.setOrientation(Qt.Horizontal) 1145 TrustedDomainsButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) 1146 TrustedDomainsButtonBox.accepted.connect(TrustedDomainsDialog.accept) 1147 TrustedDomainsButtonBox.rejected.connect(TrustedDomainsDialog.reject) 1148 TrustedDomainsLayout.addWidget(TrustedDomainsButtonBox, 2, 0, 1, 1) 1149 TrustedDomainsDialog.setLayout(TrustedDomainsLayout) 1150 TrustedDomainsDialog.setModal(True) 1151 TrustedDomainsDialog.show() 1152 result = TrustedDomainsDialog.exec_() 1153 if result == QtWidgets.QDialog.Accepted: 1154 newTrustedDomains = utils.convertMultilineStringToList(TrustedDomainsTextbox.toPlainText()) 1155 self._syncplayClient.setTrustedDomains(newTrustedDomains) 1156 1157 @needsClient 1158 def addTrustedDomain(self, newDomain): 1159 trustedDomains = self.config["trustedDomains"][:] 1160 if newDomain: 1161 trustedDomains.append(newDomain) 1162 self._syncplayClient.setTrustedDomains(trustedDomains) 1163 1164 @needsClient 1165 def openAddMediaDirectoryDialog(self, MediaDirectoriesTextbox, MediaDirectoriesDialog): 1166 if isMacOS() and IsPySide: 1167 options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) 1168 else: 1169 options = QtWidgets.QFileDialog.Options(QtWidgets.QFileDialog.ShowDirsOnly) 1170 folderName = str(QtWidgets.QFileDialog.getExistingDirectory( 1171 self, None, self.getInitialMediaDirectory(includeUserSpecifiedDirectories=False), options)) 1172 1173 if folderName: 1174 existingMediaDirs = MediaDirectoriesTextbox.toPlainText() 1175 if existingMediaDirs == "": 1176 newMediaDirList = folderName 1177 else: 1178 newMediaDirList = existingMediaDirs + "\n" + folderName 1179 MediaDirectoriesTextbox.setPlainText(newMediaDirList) 1180 MediaDirectoriesDialog.raise_() 1181 MediaDirectoriesDialog.activateWindow() 1182 1183 @needsClient 1184 def promptForStreamURL(self): 1185 streamURL, ok = QtWidgets.QInputDialog.getText( 1186 self, getMessage("promptforstreamurl-msgbox-label"), 1187 getMessage("promptforstreamurlinfo-msgbox-label"), QtWidgets.QLineEdit.Normal, "") 1188 if ok and streamURL != '': 1189 self._syncplayClient._player.openFile(streamURL) 1190 1191 @needsClient 1192 def createControlledRoom(self): 1193 controlroom, ok = QtWidgets.QInputDialog.getText( 1194 self, getMessage("createcontrolledroom-msgbox-label"), 1195 getMessage("controlledroominfo-msgbox-label"), QtWidgets.QLineEdit.Normal, 1196 utils.stripRoomName(self._syncplayClient.getRoom())) 1197 if ok and controlroom != '': 1198 self._syncplayClient.createControlledRoom(controlroom) 1199 1200 @needsClient 1201 def identifyAsController(self): 1202 msgboxtitle = getMessage("identifyascontroller-msgbox-label") 1203 msgboxtext = getMessage("identifyinfo-msgbox-label") 1204 controlpassword, ok = QtWidgets.QInputDialog.getText(self, msgboxtitle, msgboxtext, QtWidgets.QLineEdit.Normal, "") 1205 if ok and controlpassword != '': 1206 self._syncplayClient.identifyAsController(controlpassword) 1207 1208 def _extractSign(self, m): 1209 if m: 1210 if m == "-": 1211 return -1 1212 else: 1213 return 1 1214 else: 1215 return None 1216 1217 @needsClient 1218 def setOffset(self): 1219 newoffset, ok = QtWidgets.QInputDialog.getText( 1220 self, getMessage("setoffset-msgbox-label"), 1221 getMessage("offsetinfo-msgbox-label"), QtWidgets.QLineEdit.Normal, "") 1222 if ok and newoffset != '': 1223 o = re.match(constants.UI_OFFSET_REGEX, "o " + newoffset) 1224 if o: 1225 sign = self._extractSign(o.group('sign')) 1226 t = utils.parseTime(o.group('time')) 1227 if t is None: 1228 return 1229 if o.group('sign') == "/": 1230 t = self._syncplayClient.getPlayerPosition() - t 1231 elif sign: 1232 t = self._syncplayClient.getUserOffset() + sign * t 1233 self._syncplayClient.setUserOffset(t) 1234 else: 1235 self.showErrorMessage(getMessage("invalid-offset-value")) 1236 1237 def openUserGuide(self): 1238 if isLinux(): 1239 self.QtGui.QDesktopServices.openUrl(QUrl("https://syncplay.pl/guide/linux/")) 1240 elif isWindows(): 1241 self.QtGui.QDesktopServices.openUrl(QUrl("https://syncplay.pl/guide/windows/")) 1242 else: 1243 self.QtGui.QDesktopServices.openUrl(QUrl("https://syncplay.pl/guide/")) 1244 1245 def drop(self): 1246 self.close() 1247 1248 def getPlaylistState(self): 1249 playlistItems = [] 1250 for playlistItem in range(self.playlist.count()): 1251 playlistItemText = self.playlist.item(playlistItem).text() 1252 if playlistItemText != getMessage("playlist-instruction-item-message"): 1253 playlistItems.append(playlistItemText) 1254 return playlistItems 1255 1256 def playlistChangeCheck(self): 1257 if self.updatingPlaylist: 1258 return 1259 newPlaylist = self.getPlaylistState() 1260 if newPlaylist != self.playlistState and self._syncplayClient and not self.updatingPlaylist: 1261 self.playlistState = newPlaylist 1262 self._syncplayClient.playlist.changePlaylist(newPlaylist) 1263 self._syncplayClient.fileSwitch.updateInfo() 1264 1265 def executeCommand(self, command): 1266 self.showMessage("/{}".format(command)) 1267 self.console.executeCommand(command) 1268 1269 def sendChatMessage(self): 1270 chatText = self.chatInput.text() 1271 self.chatInput.setText("") 1272 if chatText != "": 1273 if chatText[:1] == "/" and chatText != "/": 1274 command = chatText[1:] 1275 if command and command[:1] == "/": 1276 chatText = chatText[1:] 1277 else: 1278 self.executeCommand(command) 1279 return 1280 self._syncplayClient.sendChat(chatText) 1281 1282 def addTopLayout(self, window): 1283 window.topSplit = self.topSplitter(Qt.Horizontal, self) 1284 1285 window.outputLayout = QtWidgets.QVBoxLayout() 1286 window.outputbox = QtWidgets.QTextBrowser() 1287 if isDarkMode: window.outputbox.document().setDefaultStyleSheet(constants.STYLE_DARK_LINKS_COLOR); 1288 window.outputbox.setReadOnly(True) 1289 window.outputbox.setTextInteractionFlags(window.outputbox.textInteractionFlags() | Qt.TextSelectableByKeyboard) 1290 window.outputbox.setOpenExternalLinks(True) 1291 window.outputbox.unsetCursor() 1292 window.outputbox.moveCursor(QtGui.QTextCursor.End) 1293 window.outputbox.insertHtml(constants.STYLE_CONTACT_INFO.format(getMessage("contact-label"))) 1294 window.outputbox.moveCursor(QtGui.QTextCursor.End) 1295 window.outputbox.setCursorWidth(0) 1296 if not isMacOS(): window.outputbox.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 1297 1298 window.outputlabel = QtWidgets.QLabel(getMessage("notifications-heading-label")) 1299 window.outputlabel.setMinimumHeight(27) 1300 window.chatInput = QtWidgets.QLineEdit() 1301 window.chatInput.setMaxLength(constants.MAX_CHAT_MESSAGE_LENGTH) 1302 window.chatInput.returnPressed.connect(self.sendChatMessage) 1303 window.chatButton = QtWidgets.QPushButton( 1304 QtGui.QPixmap(resourcespath + 'email_go.png'), 1305 getMessage("sendmessage-label")) 1306 window.chatButton.pressed.connect(self.sendChatMessage) 1307 window.chatLayout = QtWidgets.QHBoxLayout() 1308 window.chatFrame = QtWidgets.QFrame() 1309 window.chatFrame.setLayout(self.chatLayout) 1310 window.chatFrame.setContentsMargins(0, 0, 0, 0) 1311 window.chatFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) 1312 window.chatLayout.setContentsMargins(0, 0, 0, 0) 1313 self.chatButton.setToolTip(getMessage("sendmessage-tooltip")) 1314 window.chatLayout.addWidget(window.chatInput) 1315 window.chatLayout.addWidget(window.chatButton) 1316 window.chatFrame.setMaximumHeight(window.chatFrame.sizeHint().height()) 1317 window.outputFrame = QtWidgets.QFrame() 1318 window.outputFrame.setLineWidth(0) 1319 window.outputFrame.setMidLineWidth(0) 1320 if isMacOS(): window.outputLayout.setSpacing(8) 1321 window.outputLayout.setContentsMargins(0, 0, 0, 0) 1322 window.outputLayout.addWidget(window.outputlabel) 1323 window.outputLayout.addWidget(window.outputbox) 1324 window.outputLayout.addWidget(window.chatFrame) 1325 window.outputFrame.setLayout(window.outputLayout) 1326 1327 window.listLayout = QtWidgets.QVBoxLayout() 1328 window.listTreeModel = QtGui.QStandardItemModel() 1329 window.listTreeView = QtWidgets.QTreeView() 1330 window.listTreeView.setModel(window.listTreeModel) 1331 window.listTreeView.setIndentation(21) 1332 window.listTreeView.doubleClicked.connect(self.roomClicked) 1333 self.listTreeView.setContextMenuPolicy(Qt.CustomContextMenu) 1334 self.listTreeView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 1335 self.listTreeView.customContextMenuRequested.connect(self.openRoomMenu) 1336 window.listlabel = QtWidgets.QLabel(getMessage("userlist-heading-label")) 1337 if isMacOS: 1338 window.listlabel.setMinimumHeight(21) 1339 window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'lock_green.png').scaled(14, 14),"") 1340 window.sslButton.setVisible(False) 1341 window.sslButton.setFixedHeight(21) 1342 window.sslButton.setFixedWidth(21) 1343 window.sslButton.setMinimumSize(21, 21) 1344 window.sslButton.setStyleSheet("QPushButton:!hover{border: 1px solid gray;} QPushButton:hover{border:2px solid black;}") 1345 else: 1346 window.listlabel.setMinimumHeight(27) 1347 window.sslButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'lock_green.png'),"") 1348 window.sslButton.setVisible(False) 1349 window.sslButton.setFixedHeight(27) 1350 window.sslButton.setFixedWidth(27) 1351 window.sslButton.pressed.connect(self.openSSLDetails) 1352 window.sslButton.setToolTip(getMessage("sslconnection-tooltip")) 1353 window.listFrame = QtWidgets.QFrame() 1354 window.listFrame.setLineWidth(0) 1355 window.listFrame.setMidLineWidth(0) 1356 window.listFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 1357 window.listLayout.setContentsMargins(0, 0, 0, 0) 1358 if isMacOS(): window.listLayout.setSpacing(8) 1359 1360 window.userlistLayout = QtWidgets.QGridLayout() 1361 window.userlistFrame = QtWidgets.QFrame() 1362 window.userlistFrame.setLineWidth(0) 1363 window.userlistFrame.setMidLineWidth(0) 1364 window.userlistFrame.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 1365 window.userlistLayout.setContentsMargins(0, 0, 0, 0) 1366 window.userlistFrame.setLayout(window.userlistLayout) 1367 window.userlistLayout.addWidget(window.listlabel, 0, 0, Qt.AlignLeft) 1368 window.userlistLayout.addWidget(window.sslButton, 0, 2, Qt.AlignRight) 1369 window.userlistLayout.addWidget(window.listTreeView, 1, 0, 1, 3) 1370 if isMacOS(): window.userlistLayout.setContentsMargins(3, 0, 3, 0) 1371 1372 window.listSplit = QtWidgets.QSplitter(Qt.Vertical, self) 1373 window.listSplit.addWidget(window.userlistFrame) 1374 window.listLayout.addWidget(window.listSplit) 1375 1376 window.roomInput = QtWidgets.QLineEdit() 1377 window.roomInput.setMaxLength(constants.MAX_ROOM_NAME_LENGTH) 1378 window.roomInput.returnPressed.connect(self.joinRoom) 1379 window.roomButton = QtWidgets.QPushButton( 1380 QtGui.QPixmap(resourcespath + 'door_in.png'), 1381 getMessage("joinroom-label")) 1382 window.roomButton.pressed.connect(self.joinRoom) 1383 window.roomLayout = QtWidgets.QHBoxLayout() 1384 window.roomFrame = QtWidgets.QFrame() 1385 window.roomFrame.setLayout(self.roomLayout) 1386 window.roomFrame.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) 1387 if isMacOS(): 1388 window.roomLayout.setSpacing(8) 1389 window.roomLayout.setContentsMargins(3, 0, 0, 0) 1390 else: 1391 window.roomFrame.setContentsMargins(0, 0, 0, 0) 1392 window.roomLayout.setContentsMargins(0, 0, 0, 0) 1393 self.roomButton.setToolTip(getMessage("joinroom-tooltip")) 1394 window.roomLayout.addWidget(window.roomInput) 1395 window.roomLayout.addWidget(window.roomButton) 1396 window.roomFrame.setMaximumHeight(window.roomFrame.sizeHint().height()) 1397 window.listLayout.addWidget(window.roomFrame, Qt.AlignRight) 1398 1399 window.listFrame.setLayout(window.listLayout) 1400 if isMacOS(): window.listFrame.setMinimumHeight(window.outputFrame.height()) 1401 1402 window.topSplit.addWidget(window.outputFrame) 1403 window.topSplit.addWidget(window.listFrame) 1404 window.topSplit.setStretchFactor(0, 4) 1405 window.topSplit.setStretchFactor(1, 5) 1406 window.mainLayout.addWidget(window.topSplit) 1407 window.topSplit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) 1408 1409 def addBottomLayout(self, window): 1410 window.bottomLayout = QtWidgets.QHBoxLayout() 1411 window.bottomFrame = QtWidgets.QFrame() 1412 window.bottomFrame.setLayout(window.bottomLayout) 1413 window.bottomLayout.setContentsMargins(0, 0, 0, 0) 1414 if isMacOS(): window.bottomLayout.setSpacing(0) 1415 1416 self.addPlaybackLayout(window) 1417 1418 window.playlistGroup = self.PlaylistGroupBox(getMessage("sharedplaylistenabled-label")) 1419 window.playlistGroup.setCheckable(True) 1420 window.playlistGroup.toggled.connect(self.changePlaylistEnabledState) 1421 window.playlistLayout = QtWidgets.QHBoxLayout() 1422 window.playlistGroup.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 1423 window.playlistGroup.setAcceptDrops(True) 1424 window.playlist = self.PlaylistWidget() 1425 window.playlist.setWindow(window) 1426 window.playlist.setItemDelegate(self.PlaylistItemDelegate()) 1427 window.playlist.setDragEnabled(True) 1428 window.playlist.setAcceptDrops(True) 1429 window.playlist.setDropIndicatorShown(True) 1430 window.playlist.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) 1431 window.playlist.setDefaultDropAction(Qt.MoveAction) 1432 window.playlist.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) 1433 window.playlist.doubleClicked.connect(self.playlistItemClicked) 1434 window.playlist.setContextMenuPolicy(Qt.CustomContextMenu) 1435 window.playlist.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 1436 window.playlist.customContextMenuRequested.connect(self.openPlaylistMenu) 1437 self.playlistUpdateTimer = task.LoopingCall(self.playlistChangeCheck) 1438 self.playlistUpdateTimer.start(0.1, True) 1439 noteFont = QtGui.QFont() 1440 noteFont.setItalic(True) 1441 playlistItem = QtWidgets.QListWidgetItem(getMessage("playlist-instruction-item-message")) 1442 playlistItem.setFont(noteFont) 1443 window.playlist.addItem(playlistItem) 1444 window.playlistLayout.addWidget(window.playlist) 1445 window.playlistLayout.setAlignment(Qt.AlignTop) 1446 window.playlistGroup.setLayout(window.playlistLayout) 1447 window.listSplit.addWidget(window.playlistGroup) 1448 1449 window.readyPushButton = QtWidgets.QPushButton() 1450 readyFont = QtGui.QFont() 1451 readyFont.setWeight(QtGui.QFont.Bold) 1452 window.readyPushButton.setText(getMessage("ready-guipushbuttonlabel")) 1453 window.readyPushButton.setCheckable(True) 1454 window.readyPushButton.setAutoExclusive(False) 1455 window.readyPushButton.toggled.connect(self.changeReadyState) 1456 window.readyPushButton.setFont(readyFont) 1457 window.readyPushButton.setStyleSheet(constants.STYLE_READY_PUSHBUTTON) 1458 window.readyPushButton.setToolTip(getMessage("ready-tooltip")) 1459 window.listLayout.addWidget(window.readyPushButton, Qt.AlignRight) 1460 if isMacOS(): window.listLayout.setContentsMargins(0, 0, 0, 10) 1461 1462 window.autoplayLayout = QtWidgets.QHBoxLayout() 1463 window.autoplayFrame = QtWidgets.QFrame() 1464 window.autoplayFrame.setVisible(False) 1465 1466 window.autoplayFrame.setLayout(window.autoplayLayout) 1467 window.autoplayPushButton = QtWidgets.QPushButton() 1468 autoPlayFont = QtGui.QFont() 1469 autoPlayFont.setWeight(QtGui.QFont.Bold) 1470 window.autoplayPushButton.setText(getMessage("autoplay-guipushbuttonlabel")) 1471 window.autoplayPushButton.setCheckable(True) 1472 window.autoplayPushButton.setAutoExclusive(False) 1473 window.autoplayPushButton.toggled.connect(self.changeAutoplayState) 1474 window.autoplayPushButton.setFont(autoPlayFont) 1475 if isMacOS(): 1476 window.autoplayFrame.setMinimumWidth(window.listFrame.sizeHint().width()) 1477 window.autoplayLayout.setSpacing(15) 1478 window.autoplayLayout.setContentsMargins(0, 8, 3, 3) 1479 window.autoplayPushButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) 1480 else: 1481 window.autoplayLayout.setContentsMargins(0, 0, 0, 0) 1482 window.autoplayPushButton.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 1483 window.autoplayPushButton.setStyleSheet(constants.STYLE_AUTO_PLAY_PUSHBUTTON) 1484 window.autoplayPushButton.setToolTip(getMessage("autoplay-tooltip")) 1485 window.autoplayLabel = QtWidgets.QLabel(getMessage("autoplay-minimum-label")) 1486 window.autoplayLabel.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) 1487 window.autoplayLabel.setMaximumWidth(window.autoplayLabel.minimumSizeHint().width()) 1488 window.autoplayLabel.setToolTip(getMessage("autoplay-tooltip")) 1489 window.autoplayThresholdSpinbox = QtWidgets.QSpinBox() 1490 window.autoplayThresholdSpinbox.setMaximumWidth(window.autoplayThresholdSpinbox.minimumSizeHint().width()) 1491 window.autoplayThresholdSpinbox.setMinimum(2) 1492 window.autoplayThresholdSpinbox.setMaximum(99) 1493 window.autoplayThresholdSpinbox.setToolTip(getMessage("autoplay-tooltip")) 1494 window.autoplayThresholdSpinbox.valueChanged.connect(self.changeAutoplayThreshold) 1495 window.autoplayLayout.addWidget(window.autoplayPushButton, Qt.AlignRight) 1496 window.autoplayLayout.addWidget(window.autoplayLabel, Qt.AlignRight) 1497 window.autoplayLayout.addWidget(window.autoplayThresholdSpinbox, Qt.AlignRight) 1498 1499 window.listLayout.addWidget(window.autoplayFrame, Qt.AlignLeft) 1500 window.autoplayFrame.setMaximumHeight(window.autoplayFrame.sizeHint().height()) 1501 window.mainLayout.addWidget(window.bottomFrame, Qt.AlignLeft) 1502 window.bottomFrame.setMaximumHeight(window.bottomFrame.minimumSizeHint().height()) 1503 1504 def addPlaybackLayout(self, window): 1505 window.playbackFrame = QtWidgets.QFrame() 1506 window.playbackFrame.setVisible(False) 1507 window.playbackFrame.setContentsMargins(0, 0, 0, 0) 1508 window.playbackLayout = QtWidgets.QHBoxLayout() 1509 window.playbackLayout.setAlignment(Qt.AlignLeft) 1510 window.playbackLayout.setContentsMargins(0, 0, 0, 0) 1511 window.playbackFrame.setLayout(window.playbackLayout) 1512 window.seekInput = QtWidgets.QLineEdit() 1513 window.seekInput.returnPressed.connect(self.seekFromButton) 1514 window.seekButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'clock_go.png'), "") 1515 window.seekButton.setToolTip(getMessage("seektime-menu-label")) 1516 window.seekButton.pressed.connect(self.seekFromButton) 1517 window.seekInput.setText("0:00") 1518 window.seekInput.setFixedWidth(60) 1519 window.playbackLayout.addWidget(window.seekInput) 1520 window.playbackLayout.addWidget(window.seekButton) 1521 window.unseekButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'arrow_undo.png'), "") 1522 window.unseekButton.setToolTip(getMessage("undoseek-menu-label")) 1523 window.unseekButton.pressed.connect(self.undoSeek) 1524 1525 window.miscLayout = QtWidgets.QHBoxLayout() 1526 window.playbackLayout.addWidget(window.unseekButton) 1527 window.playButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'control_play_blue.png'), "") 1528 window.playButton.setToolTip(getMessage("play-menu-label")) 1529 window.playButton.pressed.connect(self.play) 1530 window.playbackLayout.addWidget(window.playButton) 1531 window.pauseButton = QtWidgets.QPushButton(QtGui.QPixmap(resourcespath + 'control_pause_blue.png'), "") 1532 window.pauseButton.setToolTip(getMessage("pause-menu-label")) 1533 window.pauseButton.pressed.connect(self.pause) 1534 window.playbackLayout.addWidget(window.pauseButton) 1535 window.playbackFrame.setMaximumHeight(window.playbackFrame.sizeHint().height()) 1536 window.playbackFrame.setMaximumWidth(window.playbackFrame.sizeHint().width()) 1537 window.outputLayout.addWidget(window.playbackFrame) 1538 1539 def loadMenubar(self, window, passedBar): 1540 if passedBar is not None: 1541 window.menuBar = passedBar['bar'] 1542 window.editMenu = passedBar['editMenu'] 1543 else: 1544 window.menuBar = QtWidgets.QMenuBar() 1545 window.editMenu = None 1546 1547 def populateMenubar(self, window): 1548 # File menu 1549 1550 window.fileMenu = QtWidgets.QMenu(getMessage("file-menu-label"), self) 1551 window.openAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'folder_explore.png'), 1552 getMessage("openmedia-menu-label")) 1553 window.openAction.triggered.connect(self.browseMediapath) 1554 window.openAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'world_explore.png'), 1555 getMessage("openstreamurl-menu-label")) 1556 window.openAction.triggered.connect(self.promptForStreamURL) 1557 window.openAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'film_folder_edit.png'), 1558 getMessage("setmediadirectories-menu-label")) 1559 window.openAction.triggered.connect(self.openSetMediaDirectoriesDialog) 1560 1561 window.exitAction = window.fileMenu.addAction(getMessage("exit-menu-label")) 1562 if isMacOS(): 1563 window.exitAction.setMenuRole(QtWidgets.QAction.QuitRole) 1564 else: 1565 window.exitAction.setIcon(QtGui.QPixmap(resourcespath + 'cross.png')) 1566 window.exitAction.triggered.connect(self.exitSyncplay) 1567 1568 if(window.editMenu is not None): 1569 window.menuBar.insertMenu(window.editMenu.menuAction(), window.fileMenu) 1570 else: 1571 window.menuBar.addMenu(window.fileMenu) 1572 1573 # Playback menu 1574 1575 window.playbackMenu = QtWidgets.QMenu(getMessage("playback-menu-label"), self) 1576 window.playAction = window.playbackMenu.addAction( 1577 QtGui.QPixmap(resourcespath + 'control_play_blue.png'), 1578 getMessage("play-menu-label")) 1579 window.playAction.triggered.connect(self.play) 1580 window.pauseAction = window.playbackMenu.addAction( 1581 QtGui.QPixmap(resourcespath + 'control_pause_blue.png'), 1582 getMessage("pause-menu-label")) 1583 window.pauseAction.triggered.connect(self.pause) 1584 window.seekAction = window.playbackMenu.addAction( 1585 QtGui.QPixmap(resourcespath + 'clock_go.png'), 1586 getMessage("seektime-menu-label")) 1587 window.seekAction.triggered.connect(self.seekPositionDialog) 1588 window.unseekAction = window.playbackMenu.addAction( 1589 QtGui.QPixmap(resourcespath + 'arrow_undo.png'), 1590 getMessage("undoseek-menu-label")) 1591 window.unseekAction.triggered.connect(self.undoSeek) 1592 1593 window.menuBar.addMenu(window.playbackMenu) 1594 1595 # Advanced menu 1596 1597 window.advancedMenu = QtWidgets.QMenu(getMessage("advanced-menu-label"), self) 1598 window.setoffsetAction = window.advancedMenu.addAction( 1599 QtGui.QPixmap(resourcespath + 'timeline_marker.png'), 1600 getMessage("setoffset-menu-label")) 1601 window.setoffsetAction.triggered.connect(self.setOffset) 1602 window.setTrustedDomainsAction = window.advancedMenu.addAction( 1603 QtGui.QPixmap(resourcespath + 'shield_edit.png'), 1604 getMessage("settrusteddomains-menu-label")) 1605 window.setTrustedDomainsAction.triggered.connect(self.openSetTrustedDomainsDialog) 1606 window.createcontrolledroomAction = window.advancedMenu.addAction( 1607 QtGui.QPixmap(resourcespath + 'page_white_key.png'), getMessage("createcontrolledroom-menu-label")) 1608 window.createcontrolledroomAction.triggered.connect(self.createControlledRoom) 1609 window.identifyascontroller = window.advancedMenu.addAction(QtGui.QPixmap(resourcespath + 'key_go.png'), 1610 getMessage("identifyascontroller-menu-label")) 1611 window.identifyascontroller.triggered.connect(self.identifyAsController) 1612 1613 window.menuBar.addMenu(window.advancedMenu) 1614 1615 # Window menu 1616 1617 window.windowMenu = QtWidgets.QMenu(getMessage("window-menu-label"), self) 1618 1619 window.playbackAction = window.windowMenu.addAction(getMessage("playbackbuttons-menu-label")) 1620 window.playbackAction.setCheckable(True) 1621 window.playbackAction.triggered.connect(self.updatePlaybackFrameVisibility) 1622 1623 window.autoplayAction = window.windowMenu.addAction(getMessage("autoplay-menu-label")) 1624 window.autoplayAction.setCheckable(True) 1625 window.autoplayAction.triggered.connect(self.updateAutoplayVisibility) 1626 window.menuBar.addMenu(window.windowMenu) 1627 1628 # Help menu 1629 1630 window.helpMenu = QtWidgets.QMenu(getMessage("help-menu-label"), self) 1631 1632 window.userguideAction = window.helpMenu.addAction( 1633 QtGui.QPixmap(resourcespath + 'help.png'), 1634 getMessage("userguide-menu-label")) 1635 window.userguideAction.triggered.connect(self.openUserGuide) 1636 window.updateAction = window.helpMenu.addAction( 1637 QtGui.QPixmap(resourcespath + 'application_get.png'), 1638 getMessage("update-menu-label")) 1639 window.updateAction.triggered.connect(self.userCheckForUpdates) 1640 1641 if not isMacOS(): 1642 window.helpMenu.addSeparator() 1643 window.about = window.helpMenu.addAction( 1644 QtGui.QPixmap(resourcespath + 'syncplay.png'), 1645 getMessage("about-menu-label")) 1646 else: 1647 window.about = window.helpMenu.addAction("&About") 1648 window.about.setMenuRole(QtWidgets.QAction.AboutRole) 1649 window.about.triggered.connect(self.openAbout) 1650 1651 window.menuBar.addMenu(window.helpMenu) 1652 window.mainLayout.setMenuBar(window.menuBar) 1653 1654 @needsClient 1655 def openSSLDetails(self): 1656 sslDetailsBox = CertificateDialog(self.getSSLInformation()) 1657 sslDetailsBox.exec_() 1658 self.sslButton.setDown(False) 1659 1660 def openAbout(self): 1661 aboutMsgBox = AboutDialog() 1662 aboutMsgBox.exec_() 1663 1664 def addMainFrame(self, window): 1665 window.mainFrame = QtWidgets.QFrame() 1666 window.mainFrame.setLineWidth(0) 1667 window.mainFrame.setMidLineWidth(0) 1668 window.mainFrame.setContentsMargins(0, 0, 0, 0) 1669 window.mainFrame.setLayout(window.mainLayout) 1670 1671 window.setCentralWidget(window.mainFrame) 1672 1673 def newMessage(self, message): 1674 self.outputbox.moveCursor(QtGui.QTextCursor.End) 1675 self.outputbox.insertHtml(message) 1676 self.outputbox.moveCursor(QtGui.QTextCursor.End) 1677 1678 def resetList(self): 1679 self.listbox.setText("") 1680 1681 def newListItem(self, item): 1682 self.listbox.moveCursor(QtGui.QTextCursor.End) 1683 self.listbox.insertHtml(item) 1684 self.listbox.moveCursor(QtGui.QTextCursor.End) 1685 1686 def updatePlaybackFrameVisibility(self): 1687 self.playbackFrame.setVisible(self.playbackAction.isChecked()) 1688 1689 def updateAutoplayVisibility(self): 1690 self.autoplayFrame.setVisible(self.autoplayAction.isChecked()) 1691 1692 def changeReadyState(self): 1693 self.updateReadyIcon() 1694 if self._syncplayClient: 1695 self._syncplayClient.changeReadyState(self.readyPushButton.isChecked()) 1696 else: 1697 self.showDebugMessage("Tried to change ready state too soon.") 1698 1699 def changePlaylistEnabledState(self): 1700 self._syncplayClient.changePlaylistEnabledState(self.playlistGroup.isChecked()) 1701 1702 @needsClient 1703 def changeAutoplayThreshold(self, source=None): 1704 self._syncplayClient.changeAutoPlayThrehsold(self.autoplayThresholdSpinbox.value()) 1705 1706 def updateAutoPlayState(self, newState): 1707 oldState = self.autoplayPushButton.isChecked() 1708 if newState != oldState and newState is not None: 1709 self.autoplayPushButton.blockSignals(True) 1710 self.autoplayPushButton.setChecked(newState) 1711 self.autoplayPushButton.blockSignals(False) 1712 self.updateAutoPlayIcon() 1713 1714 @needsClient 1715 def changeAutoplayState(self, source=None): 1716 self.updateAutoPlayIcon() 1717 if self._syncplayClient: 1718 self._syncplayClient.changeAutoplayState(self.autoplayPushButton.isChecked()) 1719 else: 1720 self.showDebugMessage("Tried to set AutoplayState too soon") 1721 1722 def updateReadyIcon(self): 1723 ready = self.readyPushButton.isChecked() 1724 if ready: 1725 self.readyPushButton.setIcon(QtGui.QPixmap(resourcespath + 'tick_checkbox.png')) 1726 else: 1727 self.readyPushButton.setIcon(QtGui.QPixmap(resourcespath + 'empty_checkbox.png')) 1728 1729 def updateAutoPlayIcon(self): 1730 ready = self.autoplayPushButton.isChecked() 1731 if ready: 1732 self.autoplayPushButton.setIcon(QtGui.QPixmap(resourcespath + 'tick_checkbox.png')) 1733 else: 1734 self.autoplayPushButton.setIcon(QtGui.QPixmap(resourcespath + 'empty_checkbox.png')) 1735 1736 def automaticUpdateCheck(self): 1737 currentDateTimeValue = QDateTime.currentDateTime() 1738 if not self.config['checkForUpdatesAutomatically']: 1739 return 1740 try: 1741 if self.config['lastCheckedForUpdates']: 1742 configLastChecked = datetime.strptime(self.config["lastCheckedForUpdates"], "%Y-%m-%d %H:%M:%S.%f") 1743 if self.lastCheckedForUpdates is None or configLastChecked > self.lastCheckedForUpdates.toPython(): 1744 self.lastCheckedForUpdates = QDateTime.fromString(self.config["lastCheckedForUpdates"], 'yyyy-MM-dd HH-mm-ss') 1745 if self.lastCheckedForUpdates is None: 1746 self.checkForUpdates() 1747 else: 1748 timeDelta = currentDateTimeValue.toPython() - self.lastCheckedForUpdates.toPython() 1749 if timeDelta.total_seconds() > constants.AUTOMATIC_UPDATE_CHECK_FREQUENCY: 1750 self.checkForUpdates() 1751 except: 1752 self.showDebugMessage("Automatic check for updates failed. An update check was manually trigggered.") 1753 self.checkForUpdates() 1754 1755 def userCheckForUpdates(self): 1756 self.checkForUpdates(userInitiated=True) 1757 1758 @needsClient 1759 def checkForUpdates(self, userInitiated=False): 1760 self.lastCheckedForUpdates = QDateTime.currentDateTime() 1761 updateStatus, updateMessage, updateURL, self.publicServerList = self._syncplayClient.checkForUpdate(userInitiated) 1762 1763 if updateMessage is None: 1764 if updateStatus == "uptodate": 1765 updateMessage = getMessage("syncplay-uptodate-notification") 1766 elif updateStatus == "updateavailale": 1767 updateMessage = getMessage("syncplay-updateavailable-notification") 1768 else: 1769 import syncplay 1770 updateMessage = getMessage("update-check-failed-notification").format(syncplay.version) 1771 if userInitiated == True: 1772 updateURL = constants.SYNCPLAY_DOWNLOAD_URL 1773 if updateURL is not None: 1774 reply = QtWidgets.QMessageBox.question( 1775 self, "Syncplay", 1776 updateMessage, QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No) 1777 if reply == QtWidgets.QMessageBox.Yes: 1778 self.QtGui.QDesktopServices.openUrl(QUrl(updateURL)) 1779 elif userInitiated: 1780 QtWidgets.QMessageBox.information(self, "Syncplay", updateMessage) 1781 else: 1782 self.showMessage(updateMessage) 1783 1784 def dragEnterEvent(self, event): 1785 data = event.mimeData() 1786 urls = data.urls() 1787 if urls and urls[0].scheme() == 'file': 1788 event.acceptProposedAction() 1789 1790 def dropEvent(self, event): 1791 rewindFile = False 1792 if QtGui.QDropEvent.proposedAction(event) == Qt.MoveAction: 1793 QtGui.QDropEvent.setDropAction(event, Qt.CopyAction) # Avoids file being deleted 1794 rewindFile = True 1795 data = event.mimeData() 1796 urls = data.urls() 1797 if urls and urls[0].scheme() == 'file': 1798 url = event.mimeData().urls()[0] 1799 if isMacOS() and IsPySide: 1800 macURL = NSString.alloc().initWithString_(str(url.toString())) 1801 pathString = macURL.stringByAddingPercentEscapesUsingEncoding_(NSUTF8StringEncoding) 1802 dropfilepath = os.path.abspath(NSURL.URLWithString_(pathString).filePathURL().path()) 1803 else: 1804 dropfilepath = os.path.abspath(str(url.toLocalFile())) 1805 if rewindFile == False: 1806 self._syncplayClient._player.openFile(dropfilepath) 1807 else: 1808 self._syncplayClient.setPosition(0) 1809 self._syncplayClient._player.openFile(dropfilepath, resetPosition=True) 1810 self._syncplayClient.setPosition(0) 1811 1812 def setPlaylist(self, newPlaylist, newIndexFilename=None): 1813 if self.updatingPlaylist: 1814 self.ui.showDebugMessage("Trying to set playlist while it is already being updated") 1815 if newPlaylist == self.playlistState: 1816 if newIndexFilename: 1817 self.playlist.setPlaylistIndexFilename(newIndexFilename) 1818 self.updatingPlaylist = False 1819 return 1820 self.updatingPlaylist = True 1821 if newPlaylist and len(newPlaylist) > 0: 1822 self.clearedPlaylistNote = True 1823 self.playlistState = newPlaylist 1824 self.playlist.updatePlaylist(newPlaylist) 1825 if newIndexFilename: 1826 self.playlist.setPlaylistIndexFilename(newIndexFilename) 1827 self.updatingPlaylist = False 1828 self._syncplayClient.fileSwitch.updateInfo() 1829 1830 def setPlaylistIndexFilename(self, filename): 1831 self.playlist.setPlaylistIndexFilename(filename) 1832 1833 def addFileToPlaylist(self, filePath, index=-1): 1834 if os.path.isfile(filePath): 1835 self.removePlaylistNote() 1836 filename = os.path.basename(filePath) 1837 if self.noPlaylistDuplicates(filename): 1838 if self.playlist == -1 or index == -1: 1839 self.playlist.addItem(filename) 1840 else: 1841 self.playlist.insertItem(index, filename) 1842 self._syncplayClient.fileSwitch.notifyUserIfFileNotInMediaDirectory(filename, filePath) 1843 elif isURL(filePath): 1844 self.removePlaylistNote() 1845 if self.noPlaylistDuplicates(filePath): 1846 if self.playlist == -1 or index == -1: 1847 self.playlist.addItem(filePath) 1848 else: 1849 self.playlist.insertItem(index, filePath) 1850 1851 def openFile(self, filePath, resetPosition=False): 1852 self._syncplayClient._player.openFile(filePath, resetPosition) 1853 1854 def noPlaylistDuplicates(self, filename): 1855 if self.isItemInPlaylist(filename): 1856 self.showErrorMessage(getMessage("cannot-add-duplicate-error").format(filename)) 1857 return False 1858 else: 1859 return True 1860 1861 def isItemInPlaylist(self, filename): 1862 for playlistindex in range(self.playlist.count()): 1863 if self.playlist.item(playlistindex).text() == filename: 1864 return True 1865 return False 1866 1867 def addStreamToPlaylist(self, streamURI): 1868 self.removePlaylistNote() 1869 if self.noPlaylistDuplicates(streamURI): 1870 self.playlist.addItem(streamURI) 1871 1872 def removePlaylistNote(self): 1873 if not self.clearedPlaylistNote: 1874 for index in range(self.playlist.count()): 1875 self.playlist.takeItem(0) 1876 self.clearedPlaylistNote = True 1877 1878 def addFolderToPlaylist(self, folderPath): 1879 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" 1880 1881 def deleteSelectedPlaylistItems(self): 1882 self.playlist.remove_selected_items() 1883 1884 def saveSettings(self): 1885 settings = QSettings("Syncplay", "MainWindow") 1886 settings.beginGroup("MainWindow") 1887 settings.setValue("size", self.size()) 1888 settings.setValue("pos", self.pos()) 1889 settings.setValue("showPlaybackButtons", self.playbackAction.isChecked()) 1890 settings.setValue("showAutoPlayButton", self.autoplayAction.isChecked()) 1891 settings.setValue("autoplayChecked", self.autoplayPushButton.isChecked()) 1892 settings.setValue("autoplayMinUsers", self.autoplayThresholdSpinbox.value()) 1893 settings.endGroup() 1894 settings = QSettings("Syncplay", "Interface") 1895 settings.beginGroup("Update") 1896 settings.setValue("lastCheckedQt", self.lastCheckedForUpdates) 1897 settings.endGroup() 1898 settings.beginGroup("PublicServerList") 1899 if self.publicServerList: 1900 settings.setValue("publicServers", self.publicServerList) 1901 settings.endGroup() 1902 1903 def loadSettings(self): 1904 settings = QSettings("Syncplay", "MainWindow") 1905 settings.beginGroup("MainWindow") 1906 self.resize(settings.value("size", QSize(700, 500))) 1907 self.move(settings.value("pos", QPoint(200, 200))) 1908 if settings.value("showPlaybackButtons", "false") == "true": 1909 self.playbackAction.setChecked(True) 1910 self.updatePlaybackFrameVisibility() 1911 if settings.value("showAutoPlayButton", "false") == "true": 1912 self.autoplayAction.setChecked(True) 1913 self.updateAutoplayVisibility() 1914 if settings.value("autoplayChecked", "false") == "true": 1915 self.updateAutoPlayState(True) 1916 self.autoplayPushButton.setChecked(True) 1917 self.autoplayThresholdSpinbox.blockSignals(True) 1918 self.autoplayThresholdSpinbox.setValue(int(settings.value("autoplayMinUsers", 2))) 1919 self.autoplayThresholdSpinbox.blockSignals(False) 1920 settings.endGroup() 1921 settings = QSettings("Syncplay", "Interface") 1922 settings.beginGroup("Update") 1923 self.lastCheckedForUpdates = settings.value("lastCheckedQt", None) 1924 settings.endGroup() 1925 settings.beginGroup("PublicServerList") 1926 self.publicServerList = settings.value("publicServers", None) 1927 1928 def __init__(self, passedBar=None): 1929 super(MainWindow, self).__init__() 1930 self.console = ConsoleInGUI() 1931 self.console.setDaemon(True) 1932 self.newWatchlist = [] 1933 self.publicServerList = [] 1934 self.lastCheckedForUpdates = None 1935 self._syncplayClient = None 1936 self.folderSearchEnabled = True 1937 self.QtGui = QtGui 1938 if isMacOS(): 1939 self.setWindowFlags(self.windowFlags()) 1940 else: 1941 self.setWindowFlags(self.windowFlags() & Qt.AA_DontUseNativeMenuBar) 1942 self.setWindowTitle("Syncplay v" + version + revision) 1943 self.mainLayout = QtWidgets.QVBoxLayout() 1944 self.addTopLayout(self) 1945 self.addBottomLayout(self) 1946 self.loadMenubar(self, passedBar) 1947 self.populateMenubar(self) 1948 self.addMainFrame(self) 1949 self.loadSettings() 1950 self.setWindowIcon(QtGui.QPixmap(resourcespath + "syncplay.png")) 1951 self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) 1952 self.show() 1953 self.setAcceptDrops(True) 1954 self.clearedPlaylistNote = False 1955