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 &copy; 2012&ndash;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("&", "&amp;").replace('"', "&quot;").replace("<", "&lt;").replace(">", "&gt;")
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("&", "&amp;").replace('"', "&quot;").replace("<", "&lt;").replace(">", "&gt;")
872        message = message.replace("&lt;a href=&quot;https://syncplay.pl/trouble&quot;&gt;", '<a href="https://syncplay.pl/trouble">').replace("&lt;/a&gt;", "</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