Python PyQt4 creating pdf from qlayout - python

I am developing an app using PyQt4. And I would like to have an option to print the main widget to a pdf document. I have a custom qlayout for the main widget and I want to create a pdf document with that qlayout. I read a lot about pyqt qprinter, but I'm not sure that's what I want.
Could anyone suggest how I could create a pdf with a qlayout full of qwidgets?

Use QPixmap.grabWidget to render the widget to a pixmap, then paint that on a QPrinter which can then convert it to a pdf:
import sys
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.text = QtGui.QTextEdit(self)
self.text.setText(open(__file__).read())
self.edit = QtGui.QLineEdit(self)
self.edit.setText('/tmp/test.pdf')
self.buttonSave = QtGui.QPushButton('Save', self)
self.buttonSave.clicked.connect(self.handleSave)
layout = QtGui.QGridLayout(self)
layout.addWidget(self.text, 0, 0, 1, 2)
layout.addWidget(self.edit, 1, 0, 1, 1)
layout.addWidget(self.buttonSave, 1, 1, 1, 1)
def handleSave(self):
printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
printer.setPageSize(QtGui.QPrinter.A6)
printer.setColorMode(QtGui.QPrinter.Color)
printer.setOutputFormat(QtGui.QPrinter.PdfFormat)
printer.setOutputFileName(self.edit.text())
pixmap = QtGui.QPixmap.grabWidget(self).scaled(
printer.pageRect(QtGui.QPrinter.DevicePixel).size().toSize(),
QtCore.Qt.KeepAspectRatio)
painter = QtGui.QPainter(printer)
painter.drawPixmap(0, 0, pixmap)
painter.end()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 640, 640)
window.show()
sys.exit(app.exec_())
EDIT:
If the QPainter part won't work for some reason on your setup, you could try the alternative save method below:
def handleSave(self):
printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
printer.setPageSize(QtGui.QPrinter.A9)
printer.setColorMode(QtGui.QPrinter.Color)
printer.setOutputFormat(QtGui.QPrinter.PdfFormat)
printer.setOutputFileName(self.edit.text())
self.render(printer)
Or another alternative would be to use a QTextDocument:
def handleSave(self):
printer = QtGui.QPrinter()
printer.setPageSize(QtGui.QPrinter.A5)
printer.setResolution(200)
printer.setColorMode(QtGui.QPrinter.Color)
printer.setOutputFormat(QtGui.QPrinter.PdfFormat)
printer.setOutputFileName(self.edit.text())
size = printer.pageRect(QtGui.QPrinter.DevicePixel).size()
pixmap = QtGui.QPixmap.grabWidget(self).scaled(
size.toSize(), QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation)
data = QtCore.QByteArray()
buffer = QtCore.QBuffer(data)
pixmap.save(buffer, 'PNG')
document = QtGui.QTextDocument()
document.setPageSize(size)
document.setHtml('<img src="data:image/png;base64,%s"/>' %
bytes(data.toBase64()).decode('ascii'))
document.print_(printer)

Related

Pyqt QSplitter not visualizing correctly

I'm trying to incorporate a QSplitter. The code works perfectly from a functionality standpoint, but the QSplitter itself doesn't appear correctly under the default PyQt style... possibly because it is itself embedded within a vertical splitter. This is confusing for the user.
If you uncomment out the line (and thus change the default PyQt style), the QSplitter visualizes correctly only when hovered over... however, I also don't want this other style.
Can anyone provide any guidance on this matter?
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout(self)
L_layout = QGridLayout()
R_layout = QGridLayout()
L_widgets = QWidget()
L_widgets.setLayout(L_layout)
R_widgets = QWidget()
R_widgets.setLayout(R_layout)
topleft = QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
btn1 = QPushButton('btn1')
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
textedit = QTextEdit()
L_layout.addWidget(topleft, 0, 0, 1, 1)
L_layout.addWidget(btn1, 1, 0, 1, 1)
R_layout.addWidget(textedit)
splitter1 = QSplitter(Qt.Horizontal,frameShape=QFrame.StyledPanel,frameShadow=QFrame.Plain)
splitter1.addWidget(L_widgets)
splitter1.addWidget(R_widgets)
splitter1.setStretchFactor(1,1)
splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
hbox.addWidget(splitter2)
self.setLayout(hbox)
#QApplication.setStyle(QStyleFactory.create('Cleanlooks'))
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QSplitter demo')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
EDIT: This is apparently a known macOS bug. When viewed on Linux, the bar of splitter1 has the same look as splitter2. I'll leave this topic open in case anyone else knows of a suitable workaround for mac.
Because the QPushButton has default minimum size, when you want to move splitter to left,
the button has reached its minimum size. So you can not move left anymore, otherwise the left will will collapse.
So if you want the left showing as you want, you can set the minimum size off button widget.
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Example(QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QHBoxLayout(self)
L_layout = QGridLayout()
R_layout = QGridLayout()
L_widgets = QWidget()
L_widgets.setLayout(L_layout)
R_widgets = QWidget()
R_widgets.setLayout(R_layout)
topleft = QFrame()
topleft.setFrameShape(QFrame.StyledPanel)
btn1 = QPushButton('btn1')
btn1.setMinimumWidth(1) # For example : set the minimum width to 1, then you can move left until the btn1 width is 1
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
textedit = QTextEdit()
L_layout.addWidget(topleft, 0, 0, 1, 1)
L_layout.addWidget(btn1, 1, 0, 1, 1)
R_layout.addWidget(textedit)
splitter1 = QSplitter(Qt.Horizontal,frameShape=QFrame.StyledPanel,frameShadow=QFrame.Plain)
splitter1.addWidget(L_widgets)
splitter1.addWidget(R_widgets)
splitter1.setStretchFactor(1,1)
splitter2 = QSplitter(Qt.Vertical)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
hbox.addWidget(splitter2)
self.setLayout(hbox)
#QApplication.setStyle(QStyleFactory.create('Cleanlooks'))
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('QSplitter demo')
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

How to save and restore current state and values using PyQt4

I am trying to figure out a way to save the current state and all values in the gui such as the text in QLineEdit and QEditText widgets.
I found this code which I have been trying to use and it seems that I can get it to save everything okay when I exit the GUI but when I open it, all it seems to restore is the window dimensions if I had moved them previously.
I can see in the ini file that everything gets saved including any text in the 2 widgets but I cant get the text to restore when I open the GUI. Does anyone know how I can get the text values to restore as well?
Here is what I am currently working with.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
def restore(settings):
finfo = QFileInfo(settings.fileName())
if finfo.exists() and finfo.isFile():
for w in qApp.allWidgets():
mo = w.metaObject()
if w.objectName() != "":
for i in range(mo.propertyCount()):
name = mo.property(i).name()
val = settings.value("{}/{}".format(w.objectName(), name), w.property(name))
w.setProperty(name, val)
def save(settings):
for w in qApp.allWidgets():
mo = w.metaObject()
if w.objectName() != "":
for i in range(mo.propertyCount()):
name = mo.property(i).name()
settings.setValue("{}/{}".format(w.objectName(), name), w.property(name))
class MainWindow(QWidget):
settings = QSettings("gui.ini", QSettings.IniFormat)
def __init__(self):
super(MainWindow, self).__init__()
self.setObjectName("MainWindow")
restore(self.settings)
self.layout = QGridLayout()
self.text_Box = QTextEdit(self)
self.text_Box.setObjectName("text_Box")
self.layout.addWidget(self.text_Box, 2, 0, 1, 1)
self.quit_Button = QPushButton(self)
self.quit_Button.setMaximumSize(30, 30)
self.quit_Button.setObjectName("quit_Button")
self.layout.addWidget(self.quit_Button, 3, 0, 1, 1)
self.line_Edit = QLineEdit(self)
self.line_Edit.setObjectName("line_Edit")
self.layout.addWidget(self.line_Edit, 1, 0, 1, 1)
self.quit_Button.clicked.connect(self.exitGUI)
self.setLayout(self.layout)
def closeEvent(self, event):
save(self.settings)
QWidget.closeEvent(self, event)
def exitGUI(self):
self.close()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
mainwindow = MainWindow()
mainwindow.show()
sys.exit(app.exec_())

How to add a tab to QTabWidget using the button on the form?

I decided to write a visual form for my script.
The idea is to have a button that will add new tabs to QTabWidget. It does not work and I can not find a good example. I use PyQt5. Here's a piece of what I've tried:
import sys
from PyQt5.QtGui import QIcon
from PyQt5 import QtCore, QtWidgets
class mainForm(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.runUi()
def runUi(self):
self.resize(250, 150)
self.move(300, 300)
self.setWindowTitle('Let\'s Rock!')
self.setWindowIcon(QIcon('icon.png'))
self.setMaximumSize(QtCore.QSize(560, 522))
self.setMinimumSize(QtCore.QSize(560, 522))
groupBoxGD = QtWidgets.QGroupBox('Соединение с ГД', self)
groupBoxGD.setGeometry(QtCore.QRect(10, 10, 541, 151))
hrLWGDLink = QtWidgets.QWidget(groupBoxGD)
hrLWGDLink.setGeometry(QtCore.QRect(10, 10, 521, 31))
hrLGD = QtWidgets.QHBoxLayout(hrLWGDLink)
hrLGD.setContentsMargins(0, 0, 0, 0)
btnAddTab = QtWidgets.QPushButton(hrLWGDLink)
btnAddTab.setText('Add tab')
hrLGD.addWidget(btnAddTab)
tabWidget = QtWidgets.QTabWidget(groupBoxGD)
tabWidget.setGeometry(QtCore.QRect(10, 170, 541, 351))
btnAddTab.clicked.connect(self.addProjectTab)
self.show()
def addProjectTab(self):
tab = QtWidgets.QWidget()
#how add tab at this line?
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ui = mainForm()
sys.exit(app.exec_())
You have to use the addTab() function, but to do so from another class the QTabWidget object must be a member of the class. Also I made some changes in the design because the button was on the QTabWidget, covering the tabs.
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class mainForm(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.runUi()
def runUi(self):
self.resize(250, 150)
self.move(300, 300)
self.setWindowTitle('Let\'s Rock!')
self.setWindowIcon(QtGui.QIcon('icon.png'))
self.setMaximumSize(QtCore.QSize(560, 522))
self.setMinimumSize(QtCore.QSize(560, 522))
layout = QtWidgets.QVBoxLayout(self)
groupBoxGD = QtWidgets.QGroupBox('Соединение с ГД', self)
layout2 = QtWidgets.QVBoxLayout(groupBoxGD)
hrLWGDLink = QtWidgets.QWidget(groupBoxGD)
hrLGD = QtWidgets.QVBoxLayout(hrLWGDLink)
hrLGD.setContentsMargins(0, 0, 0, 0)
btnAddTab = QtWidgets.QPushButton(hrLWGDLink)
btnAddTab.setText('Add tab')
hrLGD.addWidget(btnAddTab)
self.tabWidget = QtWidgets.QTabWidget(hrLWGDLink)
hrLGD.addWidget(self.tabWidget)
layout2.addWidget(hrLWGDLink)
layout.addWidget(groupBoxGD)
btnAddTab.clicked.connect(self.addProjectTab)
def addProjectTab(self):
tab = QtWidgets.QWidget()
self.tabWidget.addTab(tab, "tab")
app = QtWidgets.QApplication(sys.argv)
w = mainForm()
w.show()
sys.exit(app.exec_())
Screenshot:

How to deal with layouts in PyQt4?

I want to display welcome label in middle of frame, how can I do that? It seems like layout problem as I googled but I haven't got final solution.
Here is the code:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
palette = QPalette()
palette.setBrush(QPalette.Background, QBrush(QPixmap("Login page.jpg")))
self.setPalette(palette)
self.setWindowTitle("Login Frame")
self.setWindowIcon(QIcon('logo.png'))
self.setGeometry(50, 50, 500, 300)
self.setFixedSize(500, 300)
self.addWidgets()
def addWidgets(self):
self.lblWelcome = QLabel("Welcome to Railway e-Ticketing System", self)
self.lblWelcome.move(100,30)
wcFont = QFont("Open Sans", 25)
self.lblWelcome.setFont(wcFont)
self.lblUid = QLabel("User ID:", self)
self.lblUid.move(100,80)
font = QFont("Open Sans", 10)
self.lneUid = QLineEdit(self)
self.lneUid.setFont(font)
self.lneUid.setFixedHeight(25)
self.lneUid.setFixedWidth(200)
self.lneUid.move(225, 80)
self.lblPass = QLabel("Password:", self)
self.lblPass.move(100, 130)
self.lnePass = QLineEdit(self)
self.lnePass.setEchoMode(QLineEdit.Password)
self.lnePass.setFixedHeight(25)
self.lnePass.setFixedWidth(200)
self.lnePass.move(225, 130)
self.lblInvalid = QLabel("",self)
self.lblInvalid.move(100, 180)
self.btnLogin = QPushButton("Login",self)
#btnLogin.resize(btnLogin.sizeHint())
self.btnLogin.move(175, 230)
self.btnLogin.clicked.connect(self.authenticate)
#self.authenticate()
self.btnReg = QPushButton("Register", self)
self.btnReg.move(300, 230)
#btnReg.clicked.connect(register)
self.show()
def authenticate(self):
uid = self.lneUid.text()
upass = self.lnePass.text()
if(len(uid.strip()) == 0 or len(upass.strip()) == 0):
palette = QPalette()
palette.setColor(QPalette.Foreground, Qt.darkRed)
self.lblInvalid.setPalette(palette)
self.lblInvalid.setText("*Invalid credentials .")
else:
self.lblInvalid.setText("")
def main():
app = QApplication(sys.argv)
LoginWin = Window()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
And here is the output:
You are using a QMainWindow which already has a layout with a central widget, a toolbar, a menu bar etc. The right way to use it is to define a central Widget, and put all your label and buttons in it. You didn't, so your label is not displayed properly.
But for your login frame, you clearly don't need all of this. You just need a QWidget:
import sys
from PyQt4 import QtCore,QtGui
class LoginFrame(QtGui.QWidget):
def __init__(self):
super(LoginFrame, self).__init__()
...
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
win = LoginFrame()
win.show()
sys.exit(app.exec_())
Your code should work with a QWidget, but I would still advise reading about box layout. Right now, you're using absolute positioning, which means you have to manually place your widget at a precise position, and you can't resize your window.
A box layout would be more flexible, and practical. For example you can use QFormLayout for the userID and password.
More about layouts on the ZetCode tutorial

How to use QPrinter and QPrintPreviewDialog

I want to preview, and then print, a report through a printer using PyQt. I tried the following code :
printer = QtGui.QPrinter()
doc = QtGui.QTextDocument("testing")
dialog = QtGui.QPrintDialog(printer)
dialog.setModal(True)
dialog.setWindowTitle("printerrr")
pdialog = QtGui.QPrintPreviewDialog(printer)
pdialog.setWindowFlags(QtCore.Qt.Window)
pdialog.exec_()
How I can preview my report then print it?
Basic demo of Qt's print dialogs:
PyQt4
import sys, os
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle('Document Printer')
self.editor = QtGui.QTextEdit(self)
self.editor.textChanged.connect(self.handleTextChanged)
self.buttonOpen = QtGui.QPushButton('Open', self)
self.buttonOpen.clicked.connect(self.handleOpen)
self.buttonPrint = QtGui.QPushButton('Print', self)
self.buttonPrint.clicked.connect(self.handlePrint)
self.buttonPreview = QtGui.QPushButton('Preview', self)
self.buttonPreview.clicked.connect(self.handlePreview)
layout = QtGui.QGridLayout(self)
layout.addWidget(self.editor, 0, 0, 1, 3)
layout.addWidget(self.buttonOpen, 1, 0)
layout.addWidget(self.buttonPrint, 1, 1)
layout.addWidget(self.buttonPreview, 1, 2)
self.handleTextChanged()
def handleOpen(self):
path = QtGui.QFileDialog.getOpenFileName(
self, 'Open file', '',
'HTML files (*.html);;Text files (*.txt)')
if path:
file = QtCore.QFile(path)
if file.open(QtCore.QIODevice.ReadOnly):
stream = QtCore.QTextStream(file)
text = stream.readAll()
info = QtCore.QFileInfo(path)
if info.completeSuffix() == 'html':
self.editor.setHtml(text)
else:
self.editor.setPlainText(text)
file.close()
def handlePrint(self):
dialog = QtGui.QPrintDialog()
if dialog.exec_() == QtGui.QDialog.Accepted:
self.editor.document().print_(dialog.printer())
def handlePreview(self):
dialog = QtGui.QPrintPreviewDialog()
dialog.paintRequested.connect(self.editor.print_)
dialog.exec_()
def handleTextChanged(self):
enable = not self.editor.document().isEmpty()
self.buttonPrint.setEnabled(enable)
self.buttonPreview.setEnabled(enable)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
PyQt5
import sys, os
from PyQt5 import QtCore, QtWidgets, QtPrintSupport
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.setWindowTitle('Document Printer')
self.editor = QtWidgets.QTextEdit(self)
self.editor.textChanged.connect(self.handleTextChanged)
self.buttonOpen = QtWidgets.QPushButton('Open', self)
self.buttonOpen.clicked.connect(self.handleOpen)
self.buttonPrint = QtWidgets.QPushButton('Print', self)
self.buttonPrint.clicked.connect(self.handlePrint)
self.buttonPreview = QtWidgets.QPushButton('Preview', self)
self.buttonPreview.clicked.connect(self.handlePreview)
layout = QtWidgets.QGridLayout(self)
layout.addWidget(self.editor, 0, 0, 1, 3)
layout.addWidget(self.buttonOpen, 1, 0)
layout.addWidget(self.buttonPrint, 1, 1)
layout.addWidget(self.buttonPreview, 1, 2)
self.handleTextChanged()
def handleOpen(self):
path = QtWidgets.QFileDialog.getOpenFileName(
self, 'Open file', '',
'HTML files (*.html);;Text files (*.txt)')[0]
if path:
file = QtCore.QFile(path)
if file.open(QtCore.QIODevice.ReadOnly):
stream = QtCore.QTextStream(file)
text = stream.readAll()
info = QtCore.QFileInfo(path)
if info.completeSuffix() == 'html':
self.editor.setHtml(text)
else:
self.editor.setPlainText(text)
file.close()
def handlePrint(self):
dialog = QtPrintSupport.QPrintDialog()
if dialog.exec_() == QtWidgets.QDialog.Accepted:
self.editor.document().print_(dialog.printer())
def handlePreview(self):
dialog = QtPrintSupport.QPrintPreviewDialog()
dialog.paintRequested.connect(self.editor.print_)
dialog.exec_()
def handleTextChanged(self):
enable = not self.editor.document().isEmpty()
self.buttonPrint.setEnabled(enable)
self.buttonPreview.setEnabled(enable)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
EDIT
To print-preview a graphics view, use its render method:
def handlePreview(self):
# dialog = QtPrintSupport.QPrintPreviewDialog() # PyQt5
dialog = QtGui.QPrintPreviewDialog()
dialog.paintRequested.connect(self.handlePaintRequest)
dialog.exec_()
def handlePaintRequest(self, printer):
self.view.render(QtGui.QPainter(printer))
A PyQt5 update to the example by ekhumoro providing print preview and print for a Chart:
from PyQt5 import QtChart, QtCore, QtGui, QtPrintSupport, QtWidgets
import sys
import random
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle(self.tr('Chart Printing'))
self.chart = QtChart.QChart()
self.chart_view = QtChart.QChartView(self.chart)
self.chart_view.setRenderHint(QtGui.QPainter.Antialiasing)
self.buttonPreview = QtWidgets.QPushButton('Preview', self)
self.buttonPreview.clicked.connect(self.handle_preview)
self.buttonPrint = QtWidgets.QPushButton('Print', self)
self.buttonPrint.clicked.connect(self.handle_print)
layout = QtWidgets.QGridLayout(self)
layout.addWidget(self.chart_view, 0, 0, 1, 2)
layout.addWidget(self.buttonPreview, 1, 0)
layout.addWidget(self.buttonPrint, 1, 1)
self.create_chart()
def create_chart(self):
self.chart.setTitle("Chart Print Preview and Print Example")
for i in range(5):
series = QtChart.QLineSeries()
series.setName("Line {}".format(i + 1))
series.append(0, 0)
for i in range(1, 10):
series.append(i, random.randint(1, 9))
series.append(10, 10)
self.chart.addSeries(series)
self.chart.createDefaultAxes()
def handle_print(self):
printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
dialog = QtPrintSupport.QPrintDialog(printer, self)
if dialog.exec_() == QtPrintSupport.QPrintDialog.Accepted:
self.handle_paint_request(printer)
def handle_preview(self):
dialog = QtPrintSupport.QPrintPreviewDialog()
dialog.paintRequested.connect(self.handle_paint_request)
dialog.exec_()
def handle_paint_request(self, printer):
painter = QtGui.QPainter(printer)
painter.setViewport(self.chart_view.rect())
painter.setWindow(self.chart_view.rect())
self.chart_view.render(painter)
painter.end()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Edit: For the above demo code to run on Windows, then it requires:
> pip install PyQt5
> pip install pyqt5-tools
> pip install PyQtChart

Categories

Resources