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
This commit is contained in:
Alberto Sottile 2019-05-13 12:38:05 +02:00
parent b3545a35bb
commit 12fc04326a
10 changed files with 94 additions and 13 deletions

View File

@ -8,7 +8,7 @@ class SyncplayClientManager(object):
def run(self): def run(self):
config = ConfigurationGetter().getConfiguration() config = ConfigurationGetter().getConfiguration()
from syncplay.client import SyncplayClient # Imported later, so the proper reactor is installed 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) syncplayClient = SyncplayClient(config["playerClass"], interface, config)
if syncplayClient: if syncplayClient:
interface.addClient(syncplayClient) interface.addClient(syncplayClient)

View File

@ -304,6 +304,12 @@ de = {
"settrusteddomains-menu-label": "Set &trusted domains", # TODO: Translate "settrusteddomains-menu-label": "Set &trusted domains", # TODO: Translate
"addtrusteddomain-menu-label": "Add {} as trusted domain", # Domain # 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", "playback-menu-label": "&Wiedergabe",
"help-menu-label": "&Hilfe", "help-menu-label": "&Hilfe",

View File

@ -306,6 +306,12 @@ en = {
"settrusteddomains-menu-label": "Set &trusted domains", "settrusteddomains-menu-label": "Set &trusted domains",
"addtrusteddomain-menu-label": "Add {} as trusted domain", # Domain "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", "playback-menu-label": "&Playback",
"help-menu-label": "&Help", "help-menu-label": "&Help",

View File

@ -306,6 +306,12 @@ es = {
"settrusteddomains-menu-label": "Es&tablecer dominios de confianza", "settrusteddomains-menu-label": "Es&tablecer dominios de confianza",
"addtrusteddomain-menu-label": "Agregar {} como dominio de confianza", # Domain "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", "playback-menu-label": "Re&producción",
"help-menu-label": "A&yuda", "help-menu-label": "A&yuda",

View File

@ -306,6 +306,12 @@ it = {
"settrusteddomains-menu-label": "Imposta &domini fidati", "settrusteddomains-menu-label": "Imposta &domini fidati",
"addtrusteddomain-menu-label": "Aggiungi {} come dominio fidato", # Domain "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", "playback-menu-label": "&Riproduzione",
"help-menu-label": "&Aiuto", "help-menu-label": "&Aiuto",

View File

@ -307,6 +307,13 @@ ru = {
"identifyascontroller-menu-label": "&Войти как оператор комнаты", "identifyascontroller-menu-label": "&Войти как оператор комнаты",
"settrusteddomains-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": "бра&ть все",
"playback-menu-label": "&Управление", "playback-menu-label": "&Управление",
"help-menu-label": "&Помощь", "help-menu-label": "&Помощь",

View File

@ -1294,6 +1294,30 @@ class ConfigDialog(QtWidgets.QDialog):
self.serverpassTextbox.setReadOnly(False) self.serverpassTextbox.setReadOnly(False)
self.serverpassTextbox.setText(self.storedPassword) 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): def __init__(self, config, playerpaths, error, defaultConfig):
self.config = config self.config = config
self.defaultConfig = defaultConfig self.defaultConfig = defaultConfig
@ -1345,6 +1369,14 @@ class ConfigDialog(QtWidgets.QDialog):
self.addMiscTab() self.addMiscTab()
self.tabList() 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.mainLayout.addWidget(self.stackedFrame, 0, 1)
self.addBottomLayout() self.addBottomLayout()
self.updatePasswordVisibilty() self.updatePasswordVisibilty()

View File

@ -12,9 +12,9 @@ except ImportError:
from syncplay.ui.consoleUI import ConsoleUI from syncplay.ui.consoleUI import ConsoleUI
def getUi(graphical=True): def getUi(graphical=True, passedBar=None):
if graphical: if graphical:
ui = GraphicalUI() ui = GraphicalUI(passedBar=passedBar)
else: else:
ui = ConsoleUI() ui = ConsoleUI()
ui.setDaemon(True) ui.setDaemon(True)

View File

@ -1511,9 +1511,15 @@ class MainWindow(QtWidgets.QMainWindow):
window.playbackFrame.setMaximumWidth(window.playbackFrame.sizeHint().width()) window.playbackFrame.setMaximumWidth(window.playbackFrame.sizeHint().width())
window.outputLayout.addWidget(window.playbackFrame) window.outputLayout.addWidget(window.playbackFrame)
def addMenubar(self, window): def loadMenubar(self, window, passedBar):
window.menuBar = QtWidgets.QMenuBar() 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 # File menu
window.fileMenu = QtWidgets.QMenu(getMessage("file-menu-label"), self) window.fileMenu = QtWidgets.QMenu(getMessage("file-menu-label"), self)
@ -1527,10 +1533,17 @@ class MainWindow(QtWidgets.QMainWindow):
getMessage("setmediadirectories-menu-label")) getMessage("setmediadirectories-menu-label"))
window.openAction.triggered.connect(self.openSetMediaDirectoriesDialog) window.openAction.triggered.connect(self.openSetMediaDirectoriesDialog)
window.exitAction = window.fileMenu.addAction(QtGui.QPixmap(resourcespath + 'cross.png'), window.exitAction = window.fileMenu.addAction(getMessage("exit-menu-label"))
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.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 # Playback menu
@ -1607,11 +1620,11 @@ class MainWindow(QtWidgets.QMainWindow):
getMessage("about-menu-label")) getMessage("about-menu-label"))
else: else:
window.about = window.helpMenu.addAction("&About") window.about = window.helpMenu.addAction("&About")
window.about.setMenuRole(QtWidgets.QAction.AboutRole)
window.about.triggered.connect(self.openAbout) window.about.triggered.connect(self.openAbout)
window.menuBar.addMenu(window.helpMenu) window.menuBar.addMenu(window.helpMenu)
if not isMacOS(): window.mainLayout.setMenuBar(window.menuBar)
window.mainLayout.setMenuBar(window.menuBar)
@needsClient @needsClient
def openSSLDetails(self): def openSSLDetails(self):
@ -1887,7 +1900,7 @@ class MainWindow(QtWidgets.QMainWindow):
settings.beginGroup("PublicServerList") settings.beginGroup("PublicServerList")
self.publicServerList = settings.value("publicServers", None) self.publicServerList = settings.value("publicServers", None)
def __init__(self): def __init__(self, passedBar=None):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
self.console = ConsoleInGUI() self.console = ConsoleInGUI()
self.console.setDaemon(True) self.console.setDaemon(True)
@ -1905,11 +1918,12 @@ class MainWindow(QtWidgets.QMainWindow):
self.mainLayout = QtWidgets.QVBoxLayout() self.mainLayout = QtWidgets.QVBoxLayout()
self.addTopLayout(self) self.addTopLayout(self)
self.addBottomLayout(self) self.addBottomLayout(self)
self.addMenubar(self) self.loadMenubar(self, passedBar)
self.populateMenubar(self)
self.addMainFrame(self) self.addMainFrame(self)
self.loadSettings() self.loadSettings()
self.setWindowIcon(QtGui.QPixmap(resourcespath + "syncplay.png")) 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.show()
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.clearedPlaylistNote = False self.clearedPlaylistNote = False

View File

@ -1,5 +1,9 @@
#!/usr/bin/env bash #!/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 pip3 install dmgbuild
mv syncplay/resources/macOS_readme.pdf syncplay/resources/.macOS_readme.pdf mv syncplay/resources/macOS_readme.pdf syncplay/resources/.macOS_readme.pdf
dmgbuild -s appdmg.py "Syncplay" dist_bintray/Syncplay_${VER}.dmg dmgbuild -s appdmg.py "Syncplay" dist_bintray/Syncplay_${VER}.dmg