From 12fc04326a781227e31e21a391cabd22fa50e67b Mon Sep 17 00:00:00 2001 From: Alberto Sottile Date: Mon, 13 May 2019 12:38:05 +0200 Subject: [PATCH] macOS: add localized Edit menu with dictation support and emoji picker Qt on macOS automatically adds these entries to the menubar of GUI apps, providing that such apps have an Edit menu at their startup. Hence, this commit contains the following changes (macOS only): - create the menubar in the first dialog shown by the app (GuiConfiguration) - create an Edit menu, populate it with Cut/Copy/Paste/Select all actions - connect system-wide shortcuts to these new actions - pass the menubar and the Edit menu to the MainWindow through config and through an added optional argument in getUI and GraphicalUI - populate the menubar created before and not a new menubar in MainWindow - provide localized strings for the entries in the Edit menu - add xx.lproj folders in Syncplay.app/Contents/Resources/ to allow automatic localization of the entries added by the OS Known issues: - automatically added entries will always be in the OS language - the Edit menu might retain the previous language after a language change in the app settings. Reboot the app solves the issue. - the automatically added entries might disappear if the app language does not match the OS language --- syncplay/clientManager.py | 2 +- syncplay/messages_de.py | 6 ++++++ syncplay/messages_en.py | 6 ++++++ syncplay/messages_es.py | 6 ++++++ syncplay/messages_it.py | 6 ++++++ syncplay/messages_ru.py | 7 +++++++ syncplay/ui/GuiConfiguration.py | 32 +++++++++++++++++++++++++++++++ syncplay/ui/__init__.py | 4 ++-- syncplay/ui/gui.py | 34 +++++++++++++++++++++++---------- travis/macos-deploy.sh | 4 ++++ 10 files changed, 94 insertions(+), 13 deletions(-) diff --git a/syncplay/clientManager.py b/syncplay/clientManager.py index bdc3412..c222133 100755 --- a/syncplay/clientManager.py +++ b/syncplay/clientManager.py @@ -8,7 +8,7 @@ class SyncplayClientManager(object): def run(self): config = ConfigurationGetter().getConfiguration() from syncplay.client import SyncplayClient # Imported later, so the proper reactor is installed - interface = ui.getUi(graphical=not config["noGui"]) + interface = ui.getUi(graphical=not config["noGui"], passedBar=config['menuBar']) syncplayClient = SyncplayClient(config["playerClass"], interface, config) if syncplayClient: interface.addClient(syncplayClient) diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index cf43fa4..070b7ca 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -304,6 +304,12 @@ de = { "settrusteddomains-menu-label": "Set &trusted domains", # TODO: Translate "addtrusteddomain-menu-label": "Add {} as trusted domain", # Domain # TODO: Translate + "edit-menu-label": "&Bearbeiten", + "cut-menu-label": "Aus&schneiden", + "copy-menu-label": "&Kopieren", + "paste-menu-label": "&Einsetzen", + "selectall-menu-label": "&Alles auswälhen", + "playback-menu-label": "&Wiedergabe", "help-menu-label": "&Hilfe", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index 7beee10..1f02846 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -306,6 +306,12 @@ en = { "settrusteddomains-menu-label": "Set &trusted domains", "addtrusteddomain-menu-label": "Add {} as trusted domain", # Domain + "edit-menu-label": "&Edit", + "cut-menu-label": "Cu&t", + "copy-menu-label": "&Copy", + "paste-menu-label": "&Paste", + "selectall-menu-label": "&Select All", + "playback-menu-label": "&Playback", "help-menu-label": "&Help", diff --git a/syncplay/messages_es.py b/syncplay/messages_es.py index aa6f3d5..0ebd4cd 100644 --- a/syncplay/messages_es.py +++ b/syncplay/messages_es.py @@ -306,6 +306,12 @@ es = { "settrusteddomains-menu-label": "Es&tablecer dominios de confianza", "addtrusteddomain-menu-label": "Agregar {} como dominio de confianza", # Domain + "edit-menu-label": "&Edición", + "cut-menu-label": "Cor&tar", + "copy-menu-label": "&Copiar", + "paste-menu-label": "&Pegar", + "selectall-menu-label": "&Seleccionar todo", + "playback-menu-label": "Re&producción", "help-menu-label": "A&yuda", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 753f6ed..43809a9 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -306,6 +306,12 @@ it = { "settrusteddomains-menu-label": "Imposta &domini fidati", "addtrusteddomain-menu-label": "Aggiungi {} come dominio fidato", # Domain + "edit-menu-label": "&Modifica", + "cut-menu-label": "&Taglia", + "copy-menu-label": "&Copia", + "paste-menu-label": "&Incolla", + "selectall-menu-label": "&Seleziona tutto", + "playback-menu-label": "&Riproduzione", "help-menu-label": "&Aiuto", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index cf7c155..2d9be9c 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -307,6 +307,13 @@ ru = { "identifyascontroller-menu-label": "&Войти как оператор комнаты", "settrusteddomains-menu-label": "Доверенные &сайты", + # Edit menu - TODO: check - these should match the values of macOS menubar + "edit-menu-label": "&Правка", + "cut-menu-label": "Bы&резать", + "copy-menu-label": "&Скопировать", + "paste-menu-label": "&Bставить", + "selectall-menu-label": "Bыбра&ть все", + "playback-menu-label": "&Управление", "help-menu-label": "&Помощь", diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index f10859c..d1ce71f 100755 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -1294,6 +1294,30 @@ class ConfigDialog(QtWidgets.QDialog): self.serverpassTextbox.setReadOnly(False) self.serverpassTextbox.setText(self.storedPassword) + def createMenubar(self): + self.menuBar = QtWidgets.QMenuBar() + + # Edit menu + self.editMenu = QtWidgets.QMenu(getMessage("edit-menu-label"), self) + + self.cutAction = self.editMenu.addAction(getMessage("cut-menu-label")) + self.cutAction.setShortcuts(QtGui.QKeySequence.Cut) + + self.copyAction = self.editMenu.addAction(getMessage("copy-menu-label")) + self.copyAction.setShortcuts(QtGui.QKeySequence.Copy) + + self.pasteAction = self.editMenu.addAction(getMessage("paste-menu-label")) + self.pasteAction.setShortcuts(QtGui.QKeySequence.Paste) + + self.selectAction = self.editMenu.addAction(getMessage("selectall-menu-label")) + self.selectAction.setShortcuts(QtGui.QKeySequence.SelectAll) + + self.editMenu.addSeparator() + + self.menuBar.addMenu(self.editMenu) + + self.mainLayout.setMenuBar(self.menuBar) + def __init__(self, config, playerpaths, error, defaultConfig): self.config = config self.defaultConfig = defaultConfig @@ -1345,6 +1369,14 @@ class ConfigDialog(QtWidgets.QDialog): self.addMiscTab() self.tabList() + if isMacOS(): + self.createMenubar() + self.config['menuBar'] = dict() + self.config['menuBar']['bar'] = self.menuBar + self.config['menuBar']['editMenu'] = self.editMenu + else: + self.config['menuBar'] = None + self.mainLayout.addWidget(self.stackedFrame, 0, 1) self.addBottomLayout() self.updatePasswordVisibilty() diff --git a/syncplay/ui/__init__.py b/syncplay/ui/__init__.py index c61a305..f883ef5 100755 --- a/syncplay/ui/__init__.py +++ b/syncplay/ui/__init__.py @@ -12,9 +12,9 @@ except ImportError: from syncplay.ui.consoleUI import ConsoleUI -def getUi(graphical=True): +def getUi(graphical=True, passedBar=None): if graphical: - ui = GraphicalUI() + ui = GraphicalUI(passedBar=passedBar) else: ui = ConsoleUI() ui.setDaemon(True) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 7683420..5f8b17c 100755 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1511,9 +1511,15 @@ class MainWindow(QtWidgets.QMainWindow): window.playbackFrame.setMaximumWidth(window.playbackFrame.sizeHint().width()) window.outputLayout.addWidget(window.playbackFrame) - def addMenubar(self, window): - window.menuBar = QtWidgets.QMenuBar() + def loadMenubar(self, window, passedBar): + if passedBar is not None: + window.menuBar = passedBar['bar'] + window.editMenu = passedBar['editMenu'] + else: + window.menuBar = QtWidgets.QMenuBar() + window.editMenu = None + def populateMenubar(self, window): # File menu window.fileMenu = QtWidgets.QMenu(getMessage("file-menu-label"), self) @@ -1527,10 +1533,17 @@ class MainWindow(QtWidgets.QMainWindow): getMessage("setmediadirectories-menu-label")) window.openAction.triggered.connect(self.openSetMediaDirectoriesDialog) - window.exitAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'cross.png'), - getMessage("exit-menu-label")) + window.exitAction = window.fileMenu.addAction(getMessage("exit-menu-label")) + if isMacOS(): + window.exitAction.setMenuRole(QtWidgets.QAction.QuitRole) + else: + window.exitAction.setIcon(QtGui.QPixmap(resourcespath + 'cross.png')) window.exitAction.triggered.connect(self.exitSyncplay) - window.menuBar.addMenu(window.fileMenu) + + if(window.editMenu is not None): + window.menuBar.insertMenu(window.editMenu.menuAction(), window.fileMenu) + else: + window.menuBar.addMenu(window.fileMenu) # Playback menu @@ -1607,11 +1620,11 @@ class MainWindow(QtWidgets.QMainWindow): getMessage("about-menu-label")) else: window.about = window.helpMenu.addAction("&About") + window.about.setMenuRole(QtWidgets.QAction.AboutRole) window.about.triggered.connect(self.openAbout) window.menuBar.addMenu(window.helpMenu) - if not isMacOS(): - window.mainLayout.setMenuBar(window.menuBar) + window.mainLayout.setMenuBar(window.menuBar) @needsClient def openSSLDetails(self): @@ -1887,7 +1900,7 @@ class MainWindow(QtWidgets.QMainWindow): settings.beginGroup("PublicServerList") self.publicServerList = settings.value("publicServers", None) - def __init__(self): + def __init__(self, passedBar=None): super(MainWindow, self).__init__() self.console = ConsoleInGUI() self.console.setDaemon(True) @@ -1905,11 +1918,12 @@ class MainWindow(QtWidgets.QMainWindow): self.mainLayout = QtWidgets.QVBoxLayout() self.addTopLayout(self) self.addBottomLayout(self) - self.addMenubar(self) + self.loadMenubar(self, passedBar) + self.populateMenubar(self) self.addMainFrame(self) self.loadSettings() self.setWindowIcon(QtGui.QPixmap(resourcespath + "syncplay.png")) - self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.AA_DontUseNativeMenuBar & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) + self.setWindowFlags(self.windowFlags() & Qt.WindowCloseButtonHint & Qt.WindowMinimizeButtonHint & ~Qt.WindowContextHelpButtonHint) self.show() self.setAcceptDrops(True) self.clearedPlaylistNote = False diff --git a/travis/macos-deploy.sh b/travis/macos-deploy.sh index 08cb90a..3fb21ab 100755 --- a/travis/macos-deploy.sh +++ b/travis/macos-deploy.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +mkdir dist/Syncplay.app/Contents/Resources/German.lproj +mkdir dist/Syncplay.app/Contents/Resources/Italian.lproj +mkdir dist/Syncplay.app/Contents/Resources/ru.lproj +mkdir dist/Syncplay.app/Contents/Resources/Spanish.lproj pip3 install dmgbuild mv syncplay/resources/macOS_readme.pdf syncplay/resources/.macOS_readme.pdf dmgbuild -s appdmg.py "Syncplay" dist_bintray/Syncplay_${VER}.dmg