I want to use a QTranslator to be able to use English text labels and still have the software showing German labels.
Unfortunately my app does not translate, except when I specify the context.
The following static function instanciates a QApplication and adds the desired translators.
The first print translates 'Apple2' correctly to 'Apfel2'. The context in Qt Linguist also has the context 'app'.
The second print does not translate though. tr() calls in classes (defined in the same python file) don't translate either.
def load_application():
app = QApplication()
qt_translator = QTranslator()
qt_translator.load('qt_' + QLocale.system().name(), QLibraryInfo.location(QLibraryInfo.TranslationsPath))
app.installTranslator(qt_translator)
app_translator = QTranslator()
r = app_translator.load('i18n/' + QLocale.system().name())
app.installTranslator(app_translator)
print(app.translate('app', 'Apple2'))
print(app.tr('Apple'))
return app
EDIT:
The part for the static function was correct. The context for the application was QApplication. This did not help with the QMainWindow subclass though. I updated the code accordingly. The context generated by pyside-lupdate for the class is MainWindow:
view
class MainWindow(QMainWindow):
add_model_widget = None
def __init__(self):
QMainWindow.__init__(self)
# Create menu bar
menu_bar = QMenuBar(self)
m_file = QMenu(self.tr('File'), menu_bar)
a_add_model = QAction(QIcon('add.png'), self.tr('Add Jewel'), self)
m_file.addAction(a_add_model)
menu_bar.addMenu(m_file)
self.setMenuBar(menu_bar)
def load_application():
app = QApplication()
app_translator = QTranslator()
app_translator.load(QLocale.system().name(), 'i18n')
app.installTranslator(app_translator)
return app
controller
def initiate():
model.initiate_mongodb()
app = view.load_application()
main_window = view.MainWindow()
main_window.show()
sys.exit(app.exec_())
Solution:
The solution to my problem was that the QTranslator didn't have any parent. QTranslator(app) solved my problem.
This seems to happen because, unlike Qt, PySide/PyQt determines the context at runtime.
In your example, the context will (I think) resolve to QApplication at runtime, whereas the pyside/pyqt lupdate tools will hardcode it as app. The tools only do static analysis of the source code, and so I suppose they are not smart enough to figure out what the correct class should be.
The example code should work if you do something like this, though:
class App(QtGui.QApplication):
def __init__(self):
super(App, self).__init__()
message = self.tr('Apple')
...
app = App()
...
print(app.tr('Apple'))
(Obviously you will need to update the translation files first).
EDIT:
Here's a simplified demo that works for me:
test.py:
import sys, os
from PySide import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
menu = self.menuBar().addMenu(self.tr('File'))
menu.addAction(self.tr('Hello World'))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
translator = QtCore.QTranslator(app)
translator.load('i18n/tr_de', os.path.dirname(__file__))
app.installTranslator(translator)
window = MainWindow()
window.show()
sys.exit(app.exec_())
i18n/tr.pro:
SOURCES = ../test.py
TRANSLATIONS = tr_de.ts
i18n/tr_de.ts:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="1.1" language="de_DE">
<context>
<name>MainWindow</name>
<message>
<location filename="../test.py" line="7"/>
<source>File</source>
<translation>Datei</translation>
</message>
<message>
<location filename="../test.py" line="8"/>
<source>Hello World</source>
<translation>Hallo Welt</translation>
</message>
</context>
</TS>
command output:
$ pyside-lupdate -verbose -noobsolete i18n/tr.pro
Updating 'tr_de.ts'...
Found 2 source texts (2 new and 0 already existing)
$ lrelease-qt4 i18n/tr.pro
Updating './i18n/tr_de.qm'...
Generated 2 translation(s) (2 finished and 0 unfinished)
If you got here like me, wanting to understand how you should do translation using PySide.
Use translate(), not tr()
The annoying part about using tr() is that you have to manually edit the generated .ts files every time you edit your source with the method he proposes, this editing takes a lot of time especially when you update your .ts file, because it puts all .tr() calls in the "Unknown context" context, you have to manually move all the translations back to the correct contexts again, this gets out of hand really quickly.
Instead, there's a very simple solution.
I read the official pyqt4-lupdate documentation, and it says
The PyQt4 behaviour is unsatisfactory and may be changed in the
future. It is recommended that QCoreApplication.translate() be used in
preference to tr() (and trUtf8()). This is guaranteed to work with
current and future versions of PyQt4 and makes it much easier to share
message files between Python and C++ code. Below is the alternative
implementation of A that uses QCoreApplication.translate():
from QtCore import QCoreApplication
translate = QCoreApplication.translate
class A(QtCore.QObject):
def hello(self):
return translate("A", "Hello")
Where "A" is the hard-coded context, which pyside-lupdate will find. A lot better!
Related
I purchased this book called Building Mapping Applications with QGIS and I am trying to work through one of the exercises. There is one script that I try to run that crashes python, generating the error message "python.exe has stopped working".
import sys
import os
from qgis.core import *
from qgis.gui import *
from PyQt4.QtGui import *
from PyQt4.QtCore import Qt
#############################################################################
class MapViewer(QMainWindow):
def __init__(self, shapefile):
QMainWindow.__init__(self)
self.setWindowTitle("Map Viewer")
canvas = QgsMapCanvas()
canvas.useImageToRender(False)
canvas.setCanvasColor(Qt.white)
canvas.show()
layer = QgsVectorLayer(shapefile, "layer1", "ogr")
if not layer.isValid():
raise IOError("Invalid shapefile")
QgsMapLayerRegistry.instance().addMapLayer(layer)
canvas.setExtent(layer.extent())
canvas.setLayerSet([QgsMapCanvasLayer(layer)])
layout = QVBoxLayout()
layout.addWidget(canvas)
contents = QWidget()
contents.setLayout(layout)
self.setCentralWidget(contents)
#############################################################################
def main():
""" Our main program.
"""
QgsApplication.setPrefixPath(os.environ['QGIS_PREFIX'], True)
QgsApplication.initQgis()
app = QApplication(sys.argv)
viewer = MapViewer("C:/folder/shapefile.shp")
viewer.show()
app.exec_()
QgsApplication.exitQgis()
#############################################################################
if __name__ == "__main__":
main()
I don't know a whole lot about Python with QGIS so I'm not too sure what is causing python to crash. I am positive that all of the modules are importing correctly because if I define my paths and then import the modules in the script using the OSGeo4W Shell, there are no error messages.
This is how my paths are defined:
SET OSGEO4W_ROOT=C:\OSGeo4W64
SET QGIS_PREFIX=%OSGEO4W_ROOT%\apps\qgis
SET PATH=%PATH%;%QGIS_PREFIX%\bin
SET PYTHONPATH=%QGIS_PREFIX%\python;%PYTHONPATH%
Given all of this, I think there has to be something wrong in the script. However, when I check the script using http://pep8online.com/ there are no errors that I can fix that will result in python not crashing.
Note that I have tried I have tried SET PATH=%QGIS_PREFIX%\bin;%PATH% instead of SET PATH=%PATH%;%QGIS_PREFIX%\bin with no success.
I was fortunate enough to get in touch with the author of the book so I will share his response here:
I suspect I may know what the problem is...after looking at this
reader's problems in more depth, I've discovered that something has
changed in newer versions of QGIS, and the example code no longer
works as it is written. In technical terms, it seems that you now
need to instantiate the QApplication object before making the call to
QgsApplication.initQgis() -- the example program in the book
instantiates the QApplication object after calling
QgsApplication.initQgis(), which causes the program to crash. To fix
this, change the main() function to look like the following:
def main():
""" Our main program.
"""
app = QApplication(sys.argv)
QgsApplication.setPrefixPath(os.environ['QGIS_PREFIX'],True)
QgsApplication.initQgis()
viewer = MapViewer("C:/folder/shapefile.shp")
viewer.show()
app.exec_()
QgsApplication.exitQgis()
As you can see, I've moved the "app = QApplication(sys.argv)" line to the top.
Important Note: Make sure that forward slashes are used in viewer = MapViewer("C:/folder/shapefile.shp") - using a backslash will result in an error message stating that the shapefile is invalid.
I also thought it would be worth mentioning that none of the above fixes (comments on the question) were necessary. So, the script will work if the paths are defined as follows:
SET OSGEO4W_ROOT=C:\OSGeo4W64
SET QGIS_PREFIX=%OSGEO4W_ROOT%\apps\qgis
SET PATH=%PATH%;%QGIS_PREFIX%\bin
SET PYTHONPATH=%QGIS_PREFIX%\python;%PYTHONPATH%
Then, the entire script looks like this:
import sys
import os
from qgis.core import *
from qgis.gui import *
from PyQt4.QtGui import *
from PyQt4.QtCore import Qt
#############################################################################
class MapViewer(QMainWindow):
def __init__(self, shapefile):
QMainWindow.__init__(self)
self.setWindowTitle("Map Viewer")
canvas = QgsMapCanvas()
canvas.useImageToRender(False)
canvas.setCanvasColor(Qt.white)
canvas.show()
layer = QgsVectorLayer(shapefile, "layer1", "ogr")
if not layer.isValid():
raise IOError("Invalid shapefile")
QgsMapLayerRegistry.instance().addMapLayer(layer)
canvas.setExtent(layer.extent())
canvas.setLayerSet([QgsMapCanvasLayer(layer)])
layout = QVBoxLayout()
layout.addWidget(canvas)
contents = QWidget()
contents.setLayout(layout)
self.setCentralWidget(contents)
#############################################################################
def main():
""" Our main program.
"""
app = QApplication(sys.argv)
QgsApplication.setPrefixPath(os.environ['QGIS_PREFIX'],True)
QgsApplication.initQgis()
viewer = MapViewer("C:/folder/shapefile.shp")
viewer.show()
app.exec_()
QgsApplication.exitQgis()
#############################################################################
if __name__ == "__main__":
main()
Execute it in the OSGEO4W Shell using the following command:
python "C:\script.py"
Lastly, note that at the time of this writing, the script works properly and launches a viewer showing the shapefile referenced, but returns a few errors in the shell that do not seem to be problematic:
ERROR: Opening of authentication db FAILED
ERROR: Unable to establish authentication database connection
ERROR: Auth db could not be created and opened
QSqlDatabasePrivate::database: unable to open database: "unable to open database file Error opening database"
ERROR: Opening of authentication db FAILED
Much thanks to the author Erik Westra for providing me with this solution.
One thing that seems suspect is that you're creating a gui element without giving it a parent - QgsMapCanvas() - and then trying to manually show() it before adding it to a layout. You should never have to call show() on subwidgets, and all subwidgets should be parented to the main widget (or one of its other subwidgets).
Also, you should store persistent references to the python objects; otherwise, it's possible the underlying C++ object with get garbage collected and cause your program to crash. You do this by assigning your widgets and layouts to an attribute on self
Ex.
self.layout = QVBoxLayout(...
self.layer = ...
You should be adding the canvas like this, you should not need to call .show()
self.canvas = QgsMapCanvas(self)
layout.addWidget(self.canvas)
I have created a simple test program in Qt Designer that allows you to select a folder and display its contents on a window. It looks like this:
I have successfully converted the .ui file to .py without fail. Next, here is my code to run the program, aptly named main.py:
from PyQt4 import QtGui
import sys
import design
import os
class ExampleApp(QtGui.QMainWindow, design.Ui_MainWindow):
def _init_(self):
super(self._class_, self)._init_()
self.setupUI(self)
self.btnBrowse.clicked.connect(self.browse_folder)
def browse_folder(self):
self.listWidget.clear()
directory = QtGui.QFileDialog.getExistingDirectory(self,"Pick a Folder")
if directory:
for file_name in os.listdir(directory):
self.listWidget.addItem(file_name)
def main():
app = QtGui.QApplication(sys.argv)
form = ExampleApp()
form.show()
app.exec_()
if __name__ == '__main__':
main()
In my command prompt, I run the following code:
python main.py
It proceeds to load for a second or two, and then I get this:
Is there something that I am doing wrong? Why isn't my program showing up the way it should be? Any help is appreciated!
These lines are wrong:
def _init_(self):
super(self._class_, self)._init_()
Instead you want something like:
def __init__(self, parent=None):
super(ExampleApp, self).__init__(parent)
Note the double underscores, the different super argument, and passing parent to the super class. I can't test this right now, but it should be much closer to working.
By naming your __init__ method incorrectly it never would've been called. That explains why you get a window but not the one you designed.
I want to do some things with the PyQt4 framework. So I decided to do some browser like thing. Here is the code. Its just simple:
import sys
from PyQt4 import QtGui, QtCore, QtWebKit
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(250, 150)
self.setWindowTitle('Testbrowser')
exit = QtGui.QAction(QtGui.QIcon('icons/exit.png'), 'Exit', self)
exit.setShortcut('Ctrl+Q')
exit.setStatusTip('Exit application')
self.connect(exit, QtCore.SIGNAL('triggered()'), QtCore.SLOT('close()'))
self.statusBar()
menubar = self.menuBar()
datei = menubar.addMenu('&Datei')
datei.addAction(exit)
tools = menubar.addMenu('&Tools')
app = QtGui.QApplication(sys.argv)
main = MainWindow()
web = QWebView()
web.load(QUrl("http://google.de"))
main.show()
sys.exit(app.exec_())
I think I understood some of the things here. But what I do not understand is, how can I work with new modules here? The MainWindow class inherits from the QtGui.QMainWindow class, thats ok. But what now? Should I create a whole new class which inherits from QWebView?? Kind of like :
class newclass(QWebView.QtWebKit):
def __init__(self):
QWebView.QtWebkit.__init__(self)
ect
Or how can I do this without a new class? Or how do I do this in general? I saw a webpage on which the author made a simple browser too. But he imported it in a new programm and made an object out of it and then did some stuff. Do I have to do this, or is there a simpler way? How is this all done in PyQt4?
Greets
some reference
http://pyqt.sourceforge.net/Docs/PyQt4/qwebview.html
Accessing elements of a module follows a dot notation convention in Python -- e.g. to access QWebView, you should use
web = QtWebkit.QWebView()
the URL would be
QtCore.QUrl("http://google.de")
If you want to have all of the names available to you without dot notation, you have to import everything:
from PyQt4.QtWebkit import *
I am trying to intercept paste() for a specific edit box. After much reading and head scratching I decided to try the big hammer and monkey patch. This didn't work for me either. Anyone know why?
import sys
from PyQt4 import QtGui
def myPaste():
print("paste") # Never gets here
if __name__ == "__main__":
# QtGui.QLineEdit.paste = myPaste # Try #1
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
window.setWindowTitle("monkey")
centralWidget = QtGui.QWidget(window)
edit = QtGui.QLineEdit(centralWidget)
# QtGui.QLineEdit.paste = myPaste # Try #2
edit.paste = myPaste # Try #3
window.setCentralWidget(centralWidget)
window.show()
app.exec_()
Based on feedback..i was able to use the event filter suggestion to solve my problem. Updated example code follows...
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
window.setWindowTitle("monkey")
centralWidget = QtGui.QWidget(window)
edit = QtGui.QLineEdit(centralWidget)
window.setCentralWidget(centralWidget)
def eventFilter(obj, e):
if isinstance(obj, QtGui.QLineEdit):
if (e.type() == QtCore.QEvent.KeyPress):
if (e.matches(QtGui.QKeySequence.Paste)):
obj.paste()
t=str(obj.text()).title() # Special handling here...uppercase each word for example
obj.setText(t)
return True
return False
else:
return QtGui.QMainWindow.eventFilter(obj, e)
window.eventFilter = eventFilter
edit.installEventFilter(window)
window.show()
app.exec_()
The reason you can't "monkey-patch" QLineEdit.paste() is because it's not a virtual function. The important thing about virtual functions is that when they are overridden, the reimplemented function will get called internally by Qt; whereas non-virtual overrides will only get called by Python code. So, since QLinedit.paste() isn't virtual, you will instead have to intercept all the events that would normally result in it being called internally by Qt.
That will mean reimplementing QLineEdit.keyPressEvent, so that you can trap the shortcuts for the default key bindings; and also QLineEdit.contextMenuEvent, so that you can modify the default context menu. And, depending on what you're trying to do, you might also need to override the default drag-and-drop handling. (If you prefer not to use a subclass, an event-filter can be used to monitor all the relevant events).
The QClipboard class provides access to the system clipboard, which will allow you to intercept the text before it is pasted. Every application has a single clipboard object, which can be accessed via QApplication.clipboard() or qApp.clipboard().
In order to do what you want you can subclass QLineEdit and create a method that provides the custom paste functionality that you want (paste method isn't virtual so if it is overriden it won't be called from Qt code). In addition you will need an event filter to intercept the shortcut for CTRL+V. Probably you will have to filter the middle mouse button too which is also used to paste the clipboard content. From the event filter you can call your replacement of paste method.
You can use the following code as starting point:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class myEditor(QLineEdit):
def __init__(self, parent=None):
super(myEditor, self).__init__(parent)
def myPaste(self):
self.insert("custom text pasted! ")
class myWindow(QMainWindow):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.customEditor = myEditor(self)
self.setCentralWidget(self.customEditor)
self.customEditor.installEventFilter(self)
def eventFilter(self, obj, e):
if (obj == self.customEditor):
if (e.type() == QEvent.KeyPress):
if (e.matches(QKeySequence.Paste)):
self.customEditor.myPaste()
return True
return False
else:
return QMainWindow.eventFilter(obj, e)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = myWindow()
window.show()
app.exec_()
The event filter here only takes care of the keyboard shortcut for pasting. As I said you need to consider also other sources of the paste operation.
I'm writing a cross platform app in PyQt4. For a particular feature, I would like to access the QTextHtmlImporter class of Qt4. There is no direct python adapter class available in PyQt4. The class is part of the src/gui/text/qtextdocumentfragment_p.h file. Is there any way I can access that in Python?
I would like to modify QTextDocument.setHtml(), which code is:
void QTextDocument::setHtml(const QString &html) {
Q_D(QTextDocument); setUndoRedoEnabled(false);
d->clear();
QTextHtmlImporter(this, html).import();
setUndoRedoEnabled(true);
}
to
void QTextDocument::setHtml(const QString &html) {
Q_D(QTextDocument);
QTextHtmlImporter(this, html).import();
}
Basically setting the HTML without clearing the history. I planned to do this by using a derived class of PyQt4's QTextDocument overriding the setHtml function. Is there any other way to do this?
QTextHtmlImporter isn't even part of the Qt4 API, so the short answer is: no, there's no way to access it in PyQt4.
You could, of course, attempt to port the code to PyQt4, but I'm guessing that would be a non-trivial task.
The question is: why do you think you need to do this?
Why can't you use QTextCursor.insertHtml or QTextDocumentFragment.fromHtml?
EDIT
Here's an example of how to set the html in a text document without clearing the undo history:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.edit = QtGui.QTextEdit(self)
self.undo = QtGui.QPushButton('Undo')
self.redo = QtGui.QPushButton('Redo')
self.insert = QtGui.QPushButton('Set Html')
layout.addWidget(self.edit)
layout.addWidget(self.undo)
layout.addWidget(self.redo)
layout.addWidget(self.insert)
self.undo.clicked.connect(self.edit.undo)
self.redo.clicked.connect(self.edit.redo)
self.insert.clicked.connect(self.handleInsert)
self.edit.append('One')
self.edit.append('Two')
self.edit.append('Three')
def handleInsert(self):
cursor = QtGui.QTextCursor(self.edit.document())
cursor.select(QtGui.QTextCursor.Document)
cursor.insertHtml("""<p>Some <b>HTML</b> text</p>""")
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())