diff --git a/LICENSE b/LICENSE
index 0ca00d4..aaf6302 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License
-Copyright (c) 2015-2020 maicss github.com/maicss
+Copyright (c) 2015-2021 maicss https://github.com/maicss
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 120f02e..619f855 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,30 @@
-# 前言
+# 项目介绍
+翻译自 zetcode 的 PyQt 教程。
-PyQt5中文教程,翻译自 [zetcode](http://zetcode.com/gui/pyqt5/),GitBook 预览地址:[https://maicss.gitbook.io/pyqt5-chinese-tutoral/](https://maicss.gitbook.io/pyqt5-chinese-tutoral/)
+GitBook 预览地址:[https://maicss.gitbook.io/pyqt-chinese-tutoral/](https://maicss.gitbook.io/pyqt-chinese-tutoral/)。
-这个教程比较好的地方是,能讲解每一段代码的含义。
+开始阅读 [PyQt5](./pyqt5/index.md) 教程。
-虽然PyQt的函数命名已经非常语义化了,但是对于新手来说,有这一步还是更好的。
+开始阅读 [PyQt6](./pyqt6/index.md) 教程。
-所以我选择了翻译这篇教程,希望能给刚入门的你带来帮助。
+## 前言
+
+这个教程比较好的地方是,能讲解每一段代码的含义。虽然PyQt的函数命名已经非常语义化了,但是对于新手来说,有这一步还是更好的。希望这篇能给刚入门的你带来帮助。
翻译的水平有限\(如有错误,请指出\),而且有些地方是自己的理解,也加入了自己的提示在里面(并没有标注出来),所以并不完全等于原文。
-我尽量让翻译不带英语腔,做到即使一个完全不懂编程的人来看,虽然不知道说的啥,但是最起码语句通顺,不会读着别扭。也算是对老师的一点敬意吧~~
+我尽量让翻译不带英语腔,做到即使一个完全不懂编程的人来看,虽然不知道说的啥,但是最起码语句通顺,不会读着别扭。
-## 翻译吐槽:
+添加了一些原文没有带的图片。所以有的图片风格跟原文的不一样。
-* label 这个词好难翻译,有时候就是个占位符的意思,说是文字说明吧,有专门的词 caption,但是像 checkbox 的名称这种的,不是文字说明又是啥...,但是居然还用 label 说图标这种事情,也是醉了。
-* 源文档更新了,但是没有更新日志,只能一段段的比对……长了记性,创建了一个源文档的文件,下次直接 copy 的时候用 VCS 对比就可以了。
-* 更新了一些图片,主要是原来没有的。因为手头没有Windows,而且源文档的图片也是不是Windows10,都不是一个风格的,凑合着看吧……
-
-## 更新:
-
-### 2017-8
+## 更新日志:
+### 2017-08
+* [原文地址](https://zetcode.com/gui/pyqt5/)
* 菜单和工具栏 【新增】 右键菜单 子菜单 勾选菜单
* 事件和信号 【新增】 事件对象
* 绘图 【新增】 贝塞尔曲线
+### 2021-12
+
+* 新增 PyQt6 教程,原文地址:[Python PyQt6](https://zetcode.com/pyqt6/)
diff --git a/original/hello_world.md b/original/pyqt5/hello_world.md
similarity index 100%
rename from original/hello_world.md
rename to original/pyqt5/hello_world.md
diff --git a/original/事件和信号.md b/original/pyqt5/事件和信号.md
similarity index 100%
rename from original/事件和信号.md
rename to original/pyqt5/事件和信号.md
diff --git a/original/俄罗斯方块游戏.md b/original/pyqt5/俄罗斯方块游戏.md
similarity index 100%
rename from original/俄罗斯方块游戏.md
rename to original/pyqt5/俄罗斯方块游戏.md
diff --git a/original/对话框.md b/original/pyqt5/对话框.md
similarity index 100%
rename from original/对话框.md
rename to original/pyqt5/对话框.md
diff --git a/original/布局管理.md b/original/pyqt5/布局管理.md
similarity index 100%
rename from original/布局管理.md
rename to original/pyqt5/布局管理.md
diff --git a/original/拖拽.md b/original/pyqt5/拖拽.md
similarity index 100%
rename from original/拖拽.md
rename to original/pyqt5/拖拽.md
diff --git a/original/控件1.md b/original/pyqt5/控件1.md
similarity index 100%
rename from original/控件1.md
rename to original/pyqt5/控件1.md
diff --git a/original/控件2.md b/original/pyqt5/控件2.md
similarity index 100%
rename from original/控件2.md
rename to original/pyqt5/控件2.md
diff --git a/original/绘图.md b/original/pyqt5/绘图.md
similarity index 100%
rename from original/绘图.md
rename to original/pyqt5/绘图.md
diff --git a/original/自定义组件.md b/original/pyqt5/自定义组件.md
similarity index 100%
rename from original/自定义组件.md
rename to original/pyqt5/自定义组件.md
diff --git a/original/菜单和工具栏.md b/original/pyqt5/菜单和工具栏.md
similarity index 100%
rename from original/菜单和工具栏.md
rename to original/pyqt5/菜单和工具栏.md
diff --git a/original/pyqt6/customwidgets.md b/original/pyqt6/customwidgets.md
new file mode 100644
index 0000000..b3a3129
--- /dev/null
+++ b/original/pyqt6/customwidgets.md
@@ -0,0 +1,224 @@
+# Custom widgets in PyQt6
+*last modified May 17, 2021*
+
+PyQt6 has a rich set of widgets. However, no toolkit can provide all widgets that programmers might need in their applications. Toolkits usually provide only the most common widgets like buttons, text widgets, or sliders. If there is a need for a more specialised widget, we must create it ourselves.
+
+Custom widgets are created by using the drawing tools provided by the toolkit. There are two basic possibilities: a programmer can modify or enhance an existing widget or he can create a custom widget from scratch.
+
+## PyQt6 burning widget
+This is a widget that we can see in Nero, K3B, or other CD/DVD burning software.
+
+``` python
+# file: burning_widget.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create a custom widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QSlider, QApplication,
+ QHBoxLayout, QVBoxLayout)
+from PyQt6.QtCore import QObject, Qt, pyqtSignal
+from PyQt6.QtGui import QPainter, QFont, QColor, QPen
+import sys
+
+
+class Communicate(QObject):
+ updateBW = pyqtSignal(int)
+
+
+class BurningWidget(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setMinimumSize(1, 30)
+ self.value = 75
+ self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]
+
+
+ def setValue(self, value):
+
+ self.value = value
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawWidget(qp)
+ qp.end()
+
+
+ def drawWidget(self, qp):
+
+ MAX_CAPACITY = 700
+ OVER_CAPACITY = 750
+
+ font = QFont('Serif', 7, QFont.Weight.Light)
+ qp.setFont(font)
+
+ size = self.size()
+ w = size.width()
+ h = size.height()
+
+ step = int(round(w / 10))
+
+ till = int(((w / OVER_CAPACITY) * self.value))
+ full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
+
+ if self.value >= MAX_CAPACITY:
+
+ qp.setPen(QColor(255, 255, 255))
+ qp.setBrush(QColor(255, 255, 184))
+ qp.drawRect(0, 0, full, h)
+ qp.setPen(QColor(255, 175, 175))
+ qp.setBrush(QColor(255, 175, 175))
+ qp.drawRect(full, 0, till - full, h)
+
+ else:
+
+ qp.setPen(QColor(255, 255, 255))
+ qp.setBrush(QColor(255, 255, 184))
+ qp.drawRect(0, 0, till, h)
+
+ pen = QPen(QColor(20, 20, 20), 1,
+ Qt.PenStyle.SolidLine)
+
+ qp.setPen(pen)
+ qp.setBrush(Qt.BrushStyle.NoBrush)
+ qp.drawRect(0, 0, w - 1, h - 1)
+
+ j = 0
+
+ for i in range(step, 10 * step, step):
+
+ qp.drawLine(i, 0, i, 5)
+ metrics = qp.fontMetrics()
+ fw = metrics.horizontalAdvance(str(self.num[j]))
+
+ x, y = int(i - fw/2), int(h / 2)
+ qp.drawText(x, y, str(self.num[j]))
+ j = j + 1
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ OVER_CAPACITY = 750
+
+ sld = QSlider(Qt.Orientations.Horizontal, self)
+ sld.setFocusPolicy(Qt.FocusPolicy.NoFocus)
+ sld.setRange(1, OVER_CAPACITY)
+ sld.setValue(75)
+ sld.setGeometry(30, 40, 150, 30)
+
+ self.c = Communicate()
+ self.wid = BurningWidget()
+ self.c.updateBW[int].connect(self.wid.setValue)
+
+ sld.valueChanged[int].connect(self.changeValue)
+ hbox = QHBoxLayout()
+ hbox.addWidget(self.wid)
+ vbox = QVBoxLayout()
+ vbox.addStretch(1)
+ vbox.addLayout(hbox)
+ self.setLayout(vbox)
+
+ self.setGeometry(300, 300, 390, 210)
+ self.setWindowTitle('Burning widget')
+ self.show()
+
+
+ def changeValue(self, value):
+
+ self.c.updateBW.emit(value)
+ self.wid.repaint()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we have a `QSlider` and a custom widget. The slider controls the custom widget. This widget shows graphically the total capacity of a medium and the free space available to us. The minimum value of our custom widget is 1, the maximum is OVER_CAPACITY. If we reach value MAX_CAPACITY, we begin drawing in red colour. This normally indicates overburning.
+
+The burning widget is placed at the bottom of the window. This is achieved using one `QHBoxLayout` and one `QVBoxLayout`.
+
+```python
+class BurningWidget(QWidget):
+
+ def __init__(self):
+ super().__init__()
+```
+The burning widget it based on the `QWidget` widget.
+
+```python
+self.setMinimumSize(1, 30)
+```
+We change the minimum size (height) of the widget. The default value is a bit small for us.
+```python
+font = QFont('Serif', 7, QFont.Weight.Light)
+qp.setFont(font)
+```
+We use a smaller font than the default one. This better suits our needs.
+
+```python
+size = self.size()
+w = size.width()
+h = size.height()
+
+step = int(round(w / 10))
+
+
+till = int(((w / OVER_CAPACITY) * self.value))
+full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
+```
+We draw the widget dynamically. The greater is the window, the greater is the burning widget and vice versa. That is why we must calculate the size of the widget onto which we draw the custom widget. The till parameter determines the total size to be drawn. This value comes from the slider widget. It is a proportion of the whole area. The full parameter determines the point where we begin to draw in red colour.
+
+The actual drawing consists of three steps. We draw the yellow or the red and yellow rectangle. Then we draw the vertical lines which divide the widget into several parts. Finally, we draw the numbers which indicate the capacity of the medium.
+
+``` python
+metrics = qp.fontMetrics()
+fw = metrics.horizontalAdvance(str(self.num[j]))
+
+x, y = int(i - fw/2), int(h / 2)
+qp.drawText(x, y, str(self.num[j]))
+```
+We use font metrics to draw the text. We must know the width of the text in order to center it around the vertical line.
+
+``` python
+def changeValue(self, value):
+
+ self.c.updateBW.emit(value)
+ self.wid.repaint()
+```
+When we move the slider, the changeValue method is called. Inside the method, we send a custom updateBW signal with a parameter. The parameter is the current value of the slider. The value is later used to calculate the capacity of the Burning widget to be drawn. The custom widget is then repainted.
+
+![The burning widget](./images/burning.png)
+
+Figure: The burning widget
+
+In this part of the PyQt6 tutorial, we created a custom widget.
\ No newline at end of file
diff --git a/original/pyqt6/datetime.md b/original/pyqt6/datetime.md
new file mode 100644
index 0000000..29d0b2d
--- /dev/null
+++ b/original/pyqt6/datetime.md
@@ -0,0 +1,361 @@
+# PyQt6 date and time
+*last modified April 23, 2021*
+
+This part of the PyQt6 tutorial shows how to work with date and time in PyQt6.
+
+## QDate, QTime, QDateTime
+
+PyQt6 has `QDate`, `QDateTime`, `QTime` classes to work with date and time. The `QDate` is a class for working with a calendar date in the Gregorian calendar. It has methods for determining the date, comparing, or manipulating dates. The `QTime` class works with a clock time. It provides methods for comparing time, determining the time and various other time manipulating methods. The `QDateTime` is a class that combines both `QDate` and `QTime` objects into one object.
+
+## PyQt current date and time
+
+PyQt6 has `currentDate`, `currentTime` and `currentDateTime` methods for determining current date and time.
+
+``` python
+# file: current_date_time.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate, QTime, QDateTime, Qt
+
+now = QDate.currentDate()
+
+print(now.toString(Qt.DateFormat.ISODate))
+print(now.toString(Qt.DateFormat.RFC2822Date))
+
+datetime = QDateTime.currentDateTime()
+
+print(datetime.toString())
+
+time = QTime.currentTime()
+print(time.toString(Qt.DateFormat.ISODate))
+```
+
+The example prints the current date, date and time, and time in various formats.
+
+``` python
+now = QDate.currentDate()
+```
+
+The `currentDate` method returns the current date.
+
+``` python
+print(now.toString(Qt.DateFormat.ISODate))
+print(now.toString(Qt.DateFormat.RFC2822Date))
+```
+The date is printed in two different formats by passing the values Qt.DateFormat.ISODate and `Qt.DateFormat.RFC2822Date` to the toString method.
+
+```python
+datetime = QDateTime.currentDateTime()
+```
+
+The `currentDateTime` returns the current date and time.
+
+``` python
+time = QTime.currentTime()
+```
+
+Finally, the `currentTime` method returns the current time.
+
+``` sh
+$ ./current_date_time.py
+2021-04-23
+23 Apr 2021
+Fri Apr 23 11:41:39 2021
+11:41:39
+```
+## PyQt6 UTC time
+Our planet is a sphere; it revolves round its axis. The Earth rotates towards the east, so the Sun rises at different times in different locations. The Earth rotates once in about 24 hours. Therefore, the world was divided into 24 time zones. In each time zone, there is a different local time. This local time is often further modified by the daylight saving.
+
+There is a pragmatic need for one global time. One global time helps to avoid confusion about time zones and daylight saving time. The UTC (Universal Coordinated time) was chosen to be the primary time standard. UTC is used in aviation, weather forecasts, flight plans, air traffic control clearances, and maps. Unlike local time, UTC does not change with a change of seasons.
+``` python
+# file: utc_local.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDateTime, Qt
+
+now = QDateTime.currentDateTime()
+
+print('Local datetime: ', now.toString(Qt.DateFormat.ISODate))
+print('Universal datetime: ', now.toUTC().toString(Qt.DateFormat.ISODate))
+
+print(f'The offset from UTC is: {now.offsetFromUtc()} seconds')
+The example determines the current universal and local date and time.
+
+print('Local datetime: ', now.toString(Qt.DateFormat.ISODate))
+```
+The `currentDateTime` method returns the current date and time expressed as local time. We can use the `toLocalTime` to convert a universal time into a local time.
+
+``` python
+print('Universal datetime: ', now.toUTC().toString(Qt.DateFormat.ISODate))
+```
+
+We get the universal time with the `toUTC` method from the date time object.
+
+``` python
+print(f'The offset from UTC is: {now.offsetFromUtc()} seconds')
+```
+
+The `offsetFromUtc` gives the difference between universal time and local time in seconds.
+
+``` sh
+$ ./utc_local.py
+Local datetime: 2021-04-23T11:44:15
+Universal datetime: 2021-04-23T09:44:15Z
+The offset from UTC is: 7200 seconds
+```
+## PyQt6 number of days
+The number of days in a particular month is returned by the `daysInMonth` method and the number of days in a year by the `daysInYear` method.
+
+``` python
+# file: n_of_days.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate
+
+now = QDate.currentDate()
+
+d = QDate(1945, 5, 7)
+
+print(f'Days in month: {d.daysInMonth()}')
+print(f'Days in year: {d.daysInYear()}')
+```
+The example prints the number of days in a month and year for the chosen date.
+``` sh
+$ ./n_of_days.py
+Days in month: 31
+Days in year: 365
+```
+
+## PyQt6 difference in days
+The `daysTo` method returns the number of days from a date to another date.
+
+``` python
+# file: xmas.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate, Qt
+
+now = QDate.currentDate()
+y = now.year()
+
+print(f'today is {now.toString(Qt.DateFormat.ISODate)}')
+
+xmas1 = QDate(y-1, 12, 25)
+xmas2 = QDate(y, 12, 25)
+
+dayspassed = xmas1.daysTo(now)
+print(f'{dayspassed} days have passed since last XMas')
+
+nofdays = now.daysTo(xmas2)
+print(f'There are {nofdays} days until next XMas')
+```
+The example calculates the number of days passed from the last XMas and the number of days until the next XMas.
+
+``` sh
+$ ./xmas.py
+today is 2021-04-23
+119 days have passed since last XMas
+There are 246 days until next XMas
+```
+## PyQt6 datetime arithmetic
+We often need to add or subtract days, seconds, or years to a datetime value.
+``` python
+# file: arithmetic.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDateTime, Qt
+
+now = QDateTime.currentDateTime()
+
+print(f'Today: {now.toString(Qt.DateFormat.ISODate)}')
+print(f'Adding 12 days: {now.addDays(12).toString(Qt.DateFormat.ISODate)}')
+print(f'Subtracting 22 days: {now.addDays(-22).toString(Qt.DateFormat.ISODate)}')
+
+print(f'Adding 50 seconds: {now.addSecs(50).toString(Qt.DateFormat.ISODate)}')
+print(f'Adding 3 months: {now.addMonths(3).toString(Qt.DateFormat.ISODate)}')
+print(f'Adding 12 years: {now.addYears(12).toString(Qt.DateFormat.ISODate)}')
+```
+The example determines the current datetime and add or subtract days, seconds, months, and years.
+
+``` sh
+$ ./arithmetic.py
+Today: 2021-04-23T12:06:10
+Adding 12 days: 2021-05-05T12:06:10
+Subtracting 22 days: 2021-04-01T12:06:10
+Adding 50 seconds: 2021-04-23T12:07:00
+Adding 3 months: 2021-07-23T12:06:10
+Adding 12 years: 2033-04-23T12:06:10
+```
+## PyQt6 daylight saving time
+Daylight saving time (DST) is the practice of advancing clocks during summer months so that evening daylight lasts longer. The time is adjusted forward one hour in the beginning of spring and adjusted backward in the autumn to standard time.
+
+``` python
+# file: daylight_saving.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDateTime, QTimeZone, Qt
+
+now = QDateTime.currentDateTime()
+
+print(f'Time zone: {now.timeZoneAbbreviation()}')
+
+if now.isDaylightTime():
+ print('The current date falls into DST time')
+else:
+ print('The current date does not fall into DST time')
+```
+The example checks if the datetime is in the daylight saving time.
+
+```python
+print(f'Time zone: {now.timeZoneAbbreviation()}')
+```
+
+The `timeZoneAbbreviation` method returns the time zone abbreviation for the datetime.
+
+``` python
+if now.isDaylightTime():
+...
+```
+The `isDaylightTime` returns if the datetime falls in daylight saving time.
+
+``` sh
+$ ./daylight_saving.py
+Time zone: CEST
+The current date falls into DST time
+```
+The current date falls into DST time The program was executed in Bratislava, which is a city in Central Europe, during summer. Central European Summer Time (CEST) is 2 hours ahead of universtal time. This time zone is a daylight saving time time zone and is used in Europe and Antarctica. The standard time, which is used in winter, is Central European Time (CET).
+
+## PyQt6 unix epoch
+An epoch is an instant in time chosen as the origin of a particular era. For example in western Christian countries the time epoch starts from day 0, when Jesus was born. Another example is the French Republican Calendar which was used for twelve years. The epoch was the beginning of the Republican Era which was proclaimed on September 22, 1792, the day the First Republic was declared and the monarchy abolished.
+
+Computers have their epochs too. One of the most popular is the Unix epoch. The Unix epoch is the time 00:00:00 UTC on 1 January 1970 (or 1970- 01-01T00:00:00Z ISO 8601). The date and time in a computer is determined according to the number of seconds or clock ticks that have elapsed since the defined epoch for that computer or platform.
+
+Unix time is the number of seconds elapsed since Unix epoch.
+
+``` sh
+$ date +%s
+1619172620
+```
+Unix date command can be used to get the Unix time. At this particular moment, 1619172620 seconds have passed since the Unix epoch.
+
+``` python
+# file: unix_time.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDateTime, Qt
+
+now = QDateTime.currentDateTime()
+
+unix_time = now.toSecsSinceEpoch()
+print(unix_time)
+
+d = QDateTime.fromSecsSinceEpoch(unix_time)
+print(d.toString(Qt.DateFormat.ISODate))
+```
+The example prints the Unix time and converts it back to the `QDateTime`.
+
+``` python
+now = QDateTime.currentDateTime()
+```
+
+First, we retrieve the current date and time.
+
+```python
+unix_time = now.toSecsSinceEpoch()
+```
+
+The `toSecsSinceEpoch` returns the Unix time.
+
+```python
+d = QDateTime.fromSecsSinceEpoch(unix_time)
+```
+
+With the `fromSecsSinceEpoch` we convert the Unix time to `QDateTime`.
+
+``` sh
+$ ./unix_time.py
+1619172679
+2021-04-23T12:11:19
+```
+## PyQt6 Julian day
+Julian day refers to a continuous count of days since the beginning of the Julian Period. It is used primarily by astronomers. It should not be confused with the Julian calendar. The Julian Period started in 4713 BC. The Julian day number 0 is assigned to the day starting at noon on January 1, 4713 BC.
+
+The Julian Day Number (JDN) is the number of days elapsed since the beginning of this period. The Julian Date (JD) of any instant is the Julian day number for the preceding noon plus the fraction of the day since that instant. (Qt does not compute this fraction.) Apart from astronomy, Julian dates are often used by military and mainframe programs.
+
+``` python
+# file: julian_day.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate, Qt
+
+now = QDate.currentDate()
+
+print('Gregorian date for today:', now.toString(Qt.DateFormat.ISODate))
+print('Julian day for today:', now.toJulianDay())
+```
+In the example, we compute the Gregorian date and the Julian day for today.
+
+``` python
+print('Julian day for today:', now.toJulianDay())
+```
+
+The Julian day is returned with the toJulianDay() method.
+
+``` sh
+$ ./julian_day.py
+Gregorian date for today: 2021-04-23
+Julian day for today: 2459328
+```
+## Historical battles
+With Julian day it is possible to do calculations that span centuries.
+
+``` python
+# file: battles.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate, Qt
+
+borodino_battle = QDate(1812, 9, 7)
+slavkov_battle = QDate(1805, 12, 2)
+
+now = QDate.currentDate()
+
+j_today = now.toJulianDay()
+j_borodino = borodino_battle.toJulianDay()
+j_slavkov = slavkov_battle.toJulianDay()
+
+d1 = j_today - j_slavkov
+d2 = j_today - j_borodino
+
+print(f'Days since Slavkov battle: {d1}')
+print(f'Days since Borodino battle: {d2}')
+```
+The example counts the number of days passed since two historical events.
+
+``` python
+borodino_battle = QDate(1812, 9, 7)
+slavkov_battle = QDate(1805, 12, 2)
+```
+We have two dates of battles of the Napoleonic era.
+
+```python
+j_today = now.toJulianDay()
+j_borodino = borodino_battle.toJulianDay()
+j_slavkov = slavkov_battle.toJulianDay()
+```
+We compute the Julian days for today and for the Battles of Slavkov and Borodino.
+
+``` python
+d1 = j_today - j_slavkov
+d2 = j_today - j_borodino
+```
+We compute the number of days passed since the two battles.
+
+``` sh
+$ ./battles.py
+Days since Slavkov battle: 78670
+Days since Borodino battle: 76199
+```
+When we run this script, 78670 days have passed since the Slavkov battle, and 76199 since the Borodino battle.
+
+In this part of the PyQt6 tutorial, we have worked with date and time.
\ No newline at end of file
diff --git a/original/pyqt6/dialogs.md b/original/pyqt6/dialogs.md
new file mode 100644
index 0000000..4740f4f
--- /dev/null
+++ b/original/pyqt6/dialogs.md
@@ -0,0 +1,365 @@
+# Dialogs in PyQt6
+*last modified April 30, 2021*
+
+A dialog is defined as a conversation between two or more persons. In a computer application a dialog is a window which is used to "talk" to the application. Dialogs are used for things such as getting data from users or changing application settings.
+
+## PyQt6 QInputDialog
+QInputDialog provides a simple convenience dialog to get a single value from the user. The input value can be a string, a number, or an item from a list.
+
+``` python
+# file: input_dialog.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we receive data from
+a QInputDialog dialog.
+
+Aauthor: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QPushButton, QLineEdit,
+ QInputDialog, QApplication)
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.btn = QPushButton('Dialog', self)
+ self.btn.move(20, 20)
+ self.btn.clicked.connect(self.showDialog)
+
+ self.le = QLineEdit(self)
+ self.le.move(130, 22)
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Input dialog')
+ self.show()
+
+
+ def showDialog(self):
+
+ text, ok = QInputDialog.getText(self, 'Input Dialog',
+ 'Enter your name:')
+
+ if ok:
+ self.le.setText(str(text))
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+The example has a button and a line edit widget. The button shows the input dialog for getting text values. The entered text will be displayed in the line edit widget.
+
+``` python
+text, ok = QInputDialog.getText(self, 'Input Dialog',
+ 'Enter your name:')
+```
+This line displays the input dialog. The first string is a dialog title, the second one is a message within the dialog. The dialog returns the entered text and a boolean value. If we click the Ok button, the boolean value is true.
+
+``` python
+if ok:
+ self.le.setText(str(text))
+```
+The text that we have received from the dialog is set to the line edit widget with `setText()`.
+
+![Input dialog](./images/inputdialog.png)
+
+Figure: Input dialog
+
+## PyQt6 QColorDialog
+`QColorDialog` provides a dialog widget for selecting colour values.
+
+``` python
+# file: color_dialog.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we select a color value
+from the QColorDialog and change the background
+color of a QFrame widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QPushButton, QFrame,
+ QColorDialog, QApplication)
+from PyQt6.QtGui import QColor
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ col = QColor(0, 0, 0)
+
+ self.btn = QPushButton('Dialog', self)
+ self.btn.move(20, 20)
+
+ self.btn.clicked.connect(self.showDialog)
+
+ self.frm = QFrame(self)
+ self.frm.setStyleSheet("QWidget { background-color: %s }"
+ % col.name())
+ self.frm.setGeometry(130, 22, 200, 200)
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Color dialog')
+ self.show()
+
+
+ def showDialog(self):
+
+ col = QColorDialog.getColor()
+
+ if col.isValid():
+
+ self.frm.setStyleSheet("QWidget { background-color: %s }"
+ % col.name())
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+The application example shows a push button and a `QFrame`. The widget background is set to black colour. Using the `QColorDialog`, we can change its background.
+``` python
+col = QColor(0, 0, 0)
+```
+This is an initial colour of the QFrame background.
+``` python
+col = QColorDialog.getColor()
+```
+This line pops up the QColorDialog.
+``` python
+if col.isValid():
+
+ self.frm.setStyleSheet("QWidget { background-color: %s }"
+ % col.name())
+```
+We check if the colour is valid. If we click on the Cancel button, no valid colour is returned. If the colour is valid, we change the background colour using style sheets.
+
+## PyQt6 QFontDialog
+`QFontDialog` is a dialog widget for selecting a font.
+
+``` python
+# file: font_dialog.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we select a font name
+and change the font of a label.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QPushButton,
+ QSizePolicy, QLabel, QFontDialog, QApplication)
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ vbox = QVBoxLayout()
+
+ btn = QPushButton('Dialog', self)
+ btn.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
+ btn.move(20, 20)
+
+ vbox.addWidget(btn)
+
+ btn.clicked.connect(self.showDialog)
+
+ self.lbl = QLabel('Knowledge only matters', self)
+ self.lbl.move(130, 20)
+
+ vbox.addWidget(self.lbl)
+ self.setLayout(vbox)
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Font dialog')
+ self.show()
+
+
+ def showDialog(self):
+
+ font, ok = QFontDialog.getFont()
+
+ if ok:
+ self.lbl.setFont(font)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we have a button and a label. With the `QFontDialog`, we change the font of the label.
+``` python
+font, ok = QFontDialog.getFont()
+```
+Here we pop up the font dialog. The getFont method returns the font name and the ok parameter. It is equal to True if the user clicked Ok; otherwise it is False.
+``` python
+if ok:
+ self.label.setFont(font)
+```
+If we clicked Ok, the font of the label is changed with setFont.
+
+## PyQt6 QFileDialog
+
+`QFileDialog` is a dialog that allows users to select files or directories. The files can be selected for both opening and saving.
+
+``` python
+# file: file_dialog.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we select a file with a
+QFileDialog and display its contents
+in a QTextEdit.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QMainWindow, QTextEdit,
+ QFileDialog, QApplication)
+from PyQt6.QtGui import QIcon, QAction
+from pathlib import Path
+import sys
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.textEdit = QTextEdit()
+ self.setCentralWidget(self.textEdit)
+ self.statusBar()
+
+ openFile = QAction(QIcon('open.png'), 'Open', self)
+ openFile.setShortcut('Ctrl+O')
+ openFile.setStatusTip('Open new File')
+ openFile.triggered.connect(self.showDialog)
+
+ menubar = self.menuBar()
+ fileMenu = menubar.addMenu('&File')
+ fileMenu.addAction(openFile)
+
+ self.setGeometry(300, 300, 550, 450)
+ self.setWindowTitle('File dialog')
+ self.show()
+
+
+ def showDialog(self):
+
+ home_dir = str(Path.home())
+ fname = QFileDialog.getOpenFileName(self, 'Open file', home_dir)
+
+ if fname[0]:
+
+ f = open(fname[0], 'r')
+
+ with f:
+
+ data = f.read()
+ self.textEdit.setText(data)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+The example shows a menubar, centrally set text edit widget, and a statusbar. The menu item shows the `QFileDialog` which is used to select a file. The contents of the file are loaded into the text edit widget.
+
+``` python
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+```
+The example is based on the `QMainWindow` widget because we centrally set a text edit widget.
+``` python
+home_dir = str(Path.home())
+fname = QFileDialog.getOpenFileName(self, 'Open file', home_dir)
+```
+We pop up the QFileDialog. The first string in the getOpenFileName method is the caption. The second string specifies the dialog working directory. We use the path module to determine the user's home directory. By default, the file filter is set to All files (*).
+
+``` python
+if fname[0]:
+
+ f = open(fname[0], 'r')
+
+ with f:
+
+ data = f.read()
+ self.textEdit.setText(data)
+```
+The selected file name is read and the contents of the file are set to the text edit widget.
+
+In this part of the PyQt6 tutorial, we worked with dialogs.
\ No newline at end of file
diff --git a/original/pyqt6/dragdrop.md b/original/pyqt6/dragdrop.md
new file mode 100644
index 0000000..90f439f
--- /dev/null
+++ b/original/pyqt6/dragdrop.md
@@ -0,0 +1,284 @@
+# Drag and drop in PyQt6
+*last modified May 15, 2021*
+
+In this part of the PyQt6 tutorial, we cover drag & drop operations.
+
+In computer graphical user interfaces, drag-and-drop is the action of (or support for the action of) clicking on a virtual object and dragging it to a different location or onto another virtual object. In general, it can be used to invoke many kinds of actions, or create various types of associations between two abstract objects.
+
+Drag and drop is part of the graphical user interface. Drag and drop operations enable users to do complex things intuitively.
+
+Usually, we can drag and drop two things: data or some graphical objects. If we drag an image from one application to another, we drag and drop binary data. If we drag a tab in Firefox and move it to another place, we drag and drop a graphical component.
+
+## QDrag
+QDrag provides support for MIME-based drag and drop data transfer. It handles most of the details of a drag and drop operation. The transferred data is contained in a QMimeData object.
+
+## Simple drag and drop example in PyQt6
+In the first example, we have a `QLineEdit` and a `QPushButton`. We drag plain text from the line edit widget and drop it onto the button widget. The button's label will change.
+
+``` python
+# file: simple.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This is a simple drag and
+drop example.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtWidgets import (QPushButton, QWidget,
+ QLineEdit, QApplication)
+
+
+class Button(QPushButton):
+
+ def __init__(self, title, parent):
+ super().__init__(title, parent)
+
+ self.setAcceptDrops(True)
+
+
+ def dragEnterEvent(self, e):
+
+ if e.mimeData().hasFormat('text/plain'):
+ e.accept()
+ else:
+ e.ignore()
+
+
+ def dropEvent(self, e):
+
+ self.setText(e.mimeData().text())
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ edit = QLineEdit('', self)
+ edit.setDragEnabled(True)
+ edit.move(30, 65)
+
+ button = Button("Button", self)
+ button.move(190, 65)
+
+ self.setWindowTitle('Simple drag and drop')
+ self.setGeometry(300, 300, 300, 150)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ ex.show()
+ app.exec()
+
+
+if __name__ == '__main__':
+ main()
+```
+The example presents a simple drag & drop operation.
+
+``` python
+class Button(QPushButton):
+
+ def __init__(self, title, parent):
+ super().__init__(title, parent)
+
+ ...
+```
+In order to drop text on the `QPushButton` widget, we must reimplement some methods. Therefore, we create our own Button class which inherits from the QPushButton class.
+
+``` python
+self.setAcceptDrops(True)
+```
+We enable drop events for the widget with setAcceptDrops.
+
+```python
+def dragEnterEvent(self, e):
+
+ if e.mimeData().hasFormat('text/plain'):
+ e.accept()
+ else:
+ e.ignore()
+```
+First, we reimplement the dragEnterEvent method. We inform about the data type that we accept. In our case it is plain text.
+
+```python
+def dropEvent(self, e):
+
+ self.setText(e.mimeData().text())
+```
+By reimplementing the dropEvent method we define what happes at the drop event. Here we change the text of the button widget.
+
+```python
+edit = QLineEdit('', self)
+edit.setDragEnabled(True)
+```
+The QLineEdit widget has a built-in support for drag operations. All we need to do is to call the setDragEnabled method to activate it.
+
+![Simple drag and drop](./images/dragdrop.png)
+
+Figure: Simple drag and drop
+
+## Drag and drop a button widget
+The following example demonstrates how to drag and drop a button widget.
+
+``` python
+# file: drag_button.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this program, we can press on a button with a left mouse
+click or drag and drop the button with the right mouse click.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtCore import Qt, QMimeData
+from PyQt6.QtGui import QDrag
+from PyQt6.QtWidgets import QPushButton, QWidget, QApplication
+
+
+class Button(QPushButton):
+
+ def __init__(self, title, parent):
+ super().__init__(title, parent)
+
+
+ def mouseMoveEvent(self, e):
+
+ if e.buttons() != Qt.MouseButtons.RightButton:
+ return
+
+ mimeData = QMimeData()
+
+ drag = QDrag(self)
+ drag.setMimeData(mimeData)
+
+ drag.setHotSpot(e.position().toPoint() - self.rect().topLeft())
+
+ dropAction = drag.exec(Qt.DropActions.MoveAction)
+
+
+ def mousePressEvent(self, e):
+
+ super().mousePressEvent(e)
+
+ if e.button() == Qt.MouseButtons.LeftButton:
+ print('press')
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setAcceptDrops(True)
+
+ self.button = Button('Button', self)
+ self.button.move(100, 65)
+
+ self.setWindowTitle('Click or Move')
+ self.setGeometry(300, 300, 550, 450)
+
+
+ def dragEnterEvent(self, e):
+
+ e.accept()
+
+
+ def dropEvent(self, e):
+
+ position = e.position()
+ self.button.move(position.toPoint())
+
+ e.setDropAction(Qt.DropActions.MoveAction)
+ e.accept()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ ex.show()
+ app.exec()
+
+
+if __name__ == '__main__':
+ main()
+```
+
+In our code example, we have a `QPushButton` on the window. If we click on the button with a left mouse button, the 'press' message is printed to the console. By right clicking and moving the button, we perform a drag and drop operation on the button widget.
+
+``` python
+class Button(QPushButton):
+
+ def __init__(self, title, parent):
+ super().__init__(title, parent)
+```
+We create a Button class which derives from the `QPushButton`. We also reimplement two methods of the `QPushButton`: the mouseMoveEvent and the mousePressEvent. The mouseMoveEvent method is the place where the drag and drop operation begins.
+
+``` python
+if e.buttons() != Qt.MouseButtons.RightButton:
+ return
+```
+Here we decide that we can perform drag and drop only with a right mouse button. The left mouse button is reserved for clicking on the button.
+
+``` python
+drag = QDrag(self)
+drag.setMimeData(mimeData)
+
+drag.setHotSpot(e.position().toPoint() - self.rect().topLeft())
+```
+The `QDrag` object is created. The class provides support for MIME-based drag and drop data transfer.
+
+``` python
+dropAction = drag.exec(Qt.DropActions.MoveAction)
+```
+The exec method of the drag object starts the drag and drop operation.
+
+``` python
+def mousePressEvent(self, e):
+
+ super().mousePressEvent(e)
+
+ if e.button() == Qt.MouseButtons.LeftButton:
+ print('press')
+```
+We print 'press' to the console if we left click on the button with the mouse. Notice that we call mousePressEvent method on the parent as well. Otherwise, we would not see the button being pushed.
+
+``` python
+position = e.pos()
+self.button.move(position)
+```
+In the dropEvent method we specify what happens after we release the mouse button and finish the drop operation. In our case, we find out the current mouse pointer position and move the button accordingly.
+
+``` python
+e.setDropAction(Qt.MoveAction)
+e.accept()
+```
+We specify the type of the drop action with `setDropAction`. In our case it is a move action.
+
+This part of the PyQt6 tutorial was dedicated to drag and drop operations.
\ No newline at end of file
diff --git a/original/pyqt6/eventssignals.md b/original/pyqt6/eventssignals.md
new file mode 100644
index 0000000..7217f77
--- /dev/null
+++ b/original/pyqt6/eventssignals.md
@@ -0,0 +1,411 @@
+# PyQt6 events and signals
+*last modified April 29, 2021*
+
+In this part of the PyQt6 programming tutorial, we explore events and signals occurring in applications.
+
+## Events in PyQt6
+GUI applications are event-driven. Events are generated mainly by the user of an application. But they can be generated by other means as well; e.g. an Internet connection, a window manager, or a timer. When we call the application's `exec()` method, the application enters the main loop. The main loop fetches events and sends them to the objects.
+
+In the event model, there are three participants:
+
+- event source
+- event object
+- event target
+
+The event source is the object whose state changes. It generates events. The *event object* (event) encapsulates the state changes in the event source. The *event target* is the object that wants to be notified. Event source object delegates the task of handling an event to the event target.
+
+PyQt6 has a unique signal and slot mechanism to deal with events. Signals and slots are used for communication between objects. A signal is emitted when a particular event occurs. A slot can be any Python callable. A slot is called when its connected signal is emitted.
+
+## PyQt6 signals and slots
+This is a simple example demonstrating signals and slots in PyQt6.
+
+``` python
+# file: signals_slots.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we connect a signal
+of a QSlider to a slot of a QLCDNumber.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import (QWidget, QLCDNumber, QSlider,
+ QVBoxLayout, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ lcd = QLCDNumber(self)
+ sld = QSlider(Qt.Orientations.Horizontal, self)
+
+ vbox = QVBoxLayout()
+ vbox.addWidget(lcd)
+ vbox.addWidget(sld)
+
+ self.setLayout(vbox)
+ sld.valueChanged.connect(lcd.display)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Signal and slot')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we display a `QtGui.QLCDNumber` and a `QtGui.QSlider`. We change the lcd number by dragging the slider knob.
+``` python
+sld.valueChanged.connect(lcd.display)
+```
+Here we connect a `valueChanged` signal of the slider to the `display` slot of the lcd number.
+
+The *sender* is an object that sends a signal. The *receiver* is the object that receives the signal. The *slot* is the method that reacts to the signal.
+
+![Signal & slot](./images/sigslot.png)
+
+Figure: Signal & slot
+
+## PyQt6 reimplementing event handler
+
+Events in PyQt6 are processed often by reimplementing event handlers.
+
+``` python
+# file: reimplement_handler.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we reimplement an
+event handler.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import QWidget, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Event handler')
+ self.show()
+
+
+ def keyPressEvent(self, e):
+
+ if e.key() == Qt.Key.Key_Escape.value:
+ self.close()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we reimplement the `keyPressEvent` event handler.
+``` python
+def keyPressEvent(self, e):
+
+ if e.key() == Qt.Key.Key_Escape.value:
+ self.close()
+```
+If we click the Escape button, the application terminates.
+
+## PyQt6 event object
+Event object is a Python object that contains a number of attributes describing the event. Event object is specific to the generated event type.
+
+``` python
+# file: event_object.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we display the x and y
+coordinates of a mouse pointer in a label widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import QWidget, QApplication, QGridLayout, QLabel
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ grid = QGridLayout()
+
+ x = 0
+ y = 0
+
+ self.text = f'x: {x}, y: {y}'
+
+ self.label = QLabel(self.text, self)
+ grid.addWidget(self.label, 0, 0, Qt.Alignment.AlignTop)
+
+ self.setMouseTracking(True)
+ self.setLayout(grid)
+
+ self.setGeometry(300, 300, 450, 300)
+ self.setWindowTitle('Event object')
+ self.show()
+
+
+ def mouseMoveEvent(self, e):
+
+ x = int(e.position().x())
+ y = int(e.position().y())
+
+ text = f'x: {x}, y: {y}'
+ self.label.setText(text)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In this example, we display the x and y coordinates of a mouse pointer in a label widget.
+``` python
+self.setMouseTracking(True)
+```
+Mouse tracking is disabled by default, so the widget only receives mouse move events when at least one mouse button is pressed while the mouse is being moved. If mouse tracking is enabled, the widget receives mouse move events even if no buttons are pressed.
+``` python
+def mouseMoveEvent(self, e):
+
+ x = int(e.position().x())
+ y = int(e.position().y())
+ ...
+```
+The e is the event object; it contains data about the event that was triggered. In our case it is mouse move event. With the `position().x()` and `e.position().y()` methods we determine the x and y coordinates of the mouse pointer.
+``` python
+self.text = f'x: {x}, y: {y}'
+self.label = QLabel(self.text, self)
+```
+The x and y coordinates are displayd in a `QLabel` widget.
+
+![Event object](./images/eventobject.png)
+
+Figure: Event object
+
+# PyQt6 event sender
+
+Sometimes it is convenient to know which widget is the sender of a signal. For this, PyQt6 has the sender method.
+
+``` python
+event_sender.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we determine the event sender
+object.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QPushButton, QApplication
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ btn1 = QPushButton("Button 1", self)
+ btn1.move(30, 50)
+
+ btn2 = QPushButton("Button 2", self)
+ btn2.move(150, 50)
+
+ btn1.clicked.connect(self.buttonClicked)
+ btn2.clicked.connect(self.buttonClicked)
+
+ self.statusBar()
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Event sender')
+ self.show()
+
+
+ def buttonClicked(self):
+
+ sender = self.sender()
+
+ msg = f'{sender.text()} was pressed'
+ self.statusBar().showMessage(msg)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+We have two buttons in our example. In the `buttonClicked` method we determine which button we have clicked by calling the sender method.
+``` python
+btn1.clicked.connect(self.buttonClicked)
+btn2.clicked.connect(self.buttonClicked)
+```
+Both buttons are connected to the same slot.
+``` python
+def buttonClicked(self):
+
+ sender = self.sender()
+
+ msg = f'{sender.text()} was pressed'
+ self.statusBar().showMessage(msg)
+```
+We determine the signal source by calling the sender method. In the statusbar of the application, we show the label of the button being pressed.
+
+![Event sender](./images/eventsender.png)
+
+Figure: Event sender
+
+## PyQt6 emit signal
+
+Objects created from a `QObject` can emit signals. The following example shows how we to emit custom signals.
+
+``` python
+# file: custom_signal.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we show how to
+emit a custom signal.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtCore import pyqtSignal, QObject
+from PyQt6.QtWidgets import QMainWindow, QApplication
+
+
+class Communicate(QObject):
+
+ closeApp = pyqtSignal()
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.c = Communicate()
+ self.c.closeApp.connect(self.close)
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Emit signal')
+ self.show()
+
+
+ def mousePressEvent(self, e):
+
+ self.c.closeApp.emit()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+We create a new signal called `closeApp`. This signal is emitted during a mouse press event. The signal is connected to the close slot of the `QMainWindow`.
+
+``` python
+class Communicate(QObject):
+
+ closeApp = pyqtSignal()
+```
+A signal is created with the `pyqtSignal` as a class attribute of the external Communicate class.
+``` python
+self.c = Communicate()
+self.c.closeApp.connect(self.close)
+```
+The custom `closeApp` signal is connected to the close slot of the `QMainWindow`.
+``` python
+def mousePressEvent(self, event):
+
+ self.c.closeApp.emit()
+```
+When we click on the window with a mouse pointer, the `closeApp` signal is emitted. The application terminates.
+
+In this part of the PyQt6 tutorial, we have covered signals and slots.
\ No newline at end of file
diff --git a/original/pyqt6/firstprograms.md b/original/pyqt6/firstprograms.md
new file mode 100644
index 0000000..ba6a364
--- /dev/null
+++ b/original/pyqt6/firstprograms.md
@@ -0,0 +1,402 @@
+# First programs in PyQt6
+*last modified April 23, 2021*
+
+In this part of the PyQt6 tutorial we learn some basic functionality. The examples show a tooltip and an icon, close a window, show a message box and center a window on the desktop.
+
+## PyQt6 simple example
+This is a simple example showing a small window. Yet we can do a lot with this window. We can resize it, maximise it or minimise it. This requires a lot of coding. Someone already coded this functionality. Because it is repeated in most applications, there is no need to code it over again. PyQt6 is a high level toolkit. If we would code in a lower level toolkit, the following code example could easily have hundreds of lines.
+
+``` python
+# file: simple.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create a simple
+window in PyQt6.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+
+import sys
+from PyQt6.QtWidgets import QApplication, QWidget
+
+
+def main():
+
+ app = QApplication(sys.argv)
+
+ w = QWidget()
+ w.resize(250, 200)
+ w.move(300, 300)
+
+ w.setWindowTitle('Simple')
+ w.show()
+
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+
+The above code example shows a small window on the screen.
+
+``` python
+import sys
+from PyQt6.QtWidgets import QApplication, QWidget
+```
+Here we provide the necessary imports. The basic widgets are located in `PyQt6.QtWidgets` module.
+
+``` python
+app = QApplication(sys.argv)
+```
+Every PyQt6 application must create an application object. The `sys.argv` parameter is a list of arguments from a command line. Python scripts can be run from the shell. It is a way how we can control the startup of our scripts.
+
+``` python
+w = QWidget()
+```
+The QWidget widget is the base class of all user interface objects in PyQt6. We provide the default constructor for QWidget. The default constructor has no parent. A widget with no parent is called a window.
+
+``` python
+w.resize(250, 150)
+```
+The resize method resizes the widget. It is 250px wide and 150px high.
+``` python
+w.move(300, 300)
+```
+The move method moves the widget to a position on the screen at x=300, y=300 coordinates.
+``` python
+w.setWindowTitle('Simple')
+```
+We set the title of the window with `setWindowTitle`. The title is shown in the titlebar.
+``` python
+w.show()
+```
+The show method displays the widget on the screen. A widget is first created in memory and later shown on the screen.
+``` python
+sys.exit(app.exec())
+```
+Finally, we enter the mainloop of the application. The event handling starts from this point. The mainloop receives events from the window system and dispatches them to the application widgets. The mainloop ends if we call the exit method or the main widget is destroyed. The `sys.exit` method ensures a clean exit. The environment will be informed how the application ended.
+
+![Simple](./images/simple.png)
+
+Figure: Simple
+## PyQt6 tooltip
+We can provide a balloon help for any of our widgets.
+
+``` python
+# file: tooltip.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows a tooltip on
+a window and a button.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QToolTip,
+ QPushButton, QApplication)
+from PyQt6.QtGui import QFont
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ QToolTip.setFont(QFont('SansSerif', 10))
+
+ self.setToolTip('This is a QWidget widget')
+
+ btn = QPushButton('Button', self)
+ btn.setToolTip('This is a QPushButton widget')
+ btn.resize(btn.sizeHint())
+ btn.move(50, 50)
+
+ self.setGeometry(300, 300, 300, 200)
+ self.setWindowTitle('Tooltips')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In this example, we show a tooltip for two PyQt6 widgets.
+
+``` python
+QToolTip.setFont(QFont('SansSerif', 10))
+```
+
+This static method sets a font used to render tooltips. We use a 10pt SansSerif font.
+
+``` python
+self.setToolTip('This is a QWidget widget')
+```
+
+To create a tooltip, we call the `setTooltip` method. We can use rich text formatting.
+
+``` python
+btn = QPushButton('Button', self)
+btn.setToolTip('This is a QPushButton widget')
+```
+
+We create a push button widget and set a tooltip for it.
+
+``` python
+btn.resize(btn.sizeHint())
+btn.move(50, 50)
+```
+The button is being resized and moved on the window. The `sizeHint` method gives a recommended size for the button.
+
+![Tooltips](./images/tooltips.png)
+Figure: Tooltips
+## PyQt6 quit button
+The obvious way to close a window is to click on the x mark on the titlebar. In the next example, we show how we can programatically close our window. We will briefly touch signals and slots.
+
+The following is the constructor of a QPushButton widget that we use in our example.
+``` python
+QPushButton(string text, QWidget parent = None)
+```
+The text parameter is a text that will be displayed on the button. The parent is a widget on which we place our button. In our case it will be a `QWidget`. Widgets of an application form a hierarchy. In this hierarchy, most widgets have their parents. Widgets without parents are toplevel windows.
+
+```python
+# file: quit_button.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a quit
+button. When we press the button,
+the application terminates.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QPushButton, QApplication
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ qbtn = QPushButton('Quit', self)
+ qbtn.clicked.connect(QApplication.instance().quit)
+ qbtn.resize(qbtn.sizeHint())
+ qbtn.move(50, 50)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Quit button')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In this example, we create a quit button. Upon clicking on the button, the application terminates.
+``` python
+qbtn = QPushButton('Quit', self)
+```
+We create a push button. The button is an instance of the QPushButton class. The first parameter of the constructor is the label of the button. The second parameter is the parent widget. The parent widget is the Example widget, which is a `QWidget` by inheritance.
+``` python
+qbtn.clicked.connect(QApplication.instance().quit)
+```
+The event processing system in PyQt6 is built with the signal & slot mechanism. If we click on the button, the signal clicked is emitted. The slot can be a Qt slot or any Python callable.
+
+`QCoreApplication`, which is retrieved with `QApplication.instance`, contains the main event loop—it processes and dispatches all events. The clicked signal is connected to the quit method which terminates the application. The communication is done between two objects: the sender and the receiver. The sender is the push button, the receiver is the application object.
+
+![Quit button](./images/quitbutton.png)
+
+Figure: Quit button
+
+## PyQt6 message box
+By default, if we click on the x button on the titlebar, the `QWidget` is closed. Sometimes we want to modify this default behaviour. For example, if we have a file opened in an editor to which we did some changes. We show a message box to confirm the action.
+
+``` python
+# file: messagebox.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program shows a confirmation
+message box when we click on the close
+button of the application window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QMessageBox, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 350, 200)
+ self.setWindowTitle('Message box')
+ self.show()
+
+ def closeEvent(self, event):
+
+ reply = QMessageBox.question(self, 'Message',
+ "Are you sure to quit?", QMessageBox.StandardButtons.Yes |
+ QMessageBox.StandardButtons.No, QMessageBox.StandardButtons.No)
+
+ if reply == QMessageBox.StandardButtons.Yes:
+
+ event.accept()
+ else:
+
+ event.ignore()
+
+
+def main():
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+If we close a `QWidget`, the `QCloseEvent` is generated. To modify the widget behaviour we need to reimplement the `closeEvent` event handler.
+
+``` python
+reply = QMessageBox.question(self, 'Message',
+ "Are you sure to quit?", QMessageBox.Yes |
+ QMessageBox.No, QMessageBox.No)
+```
+We show a message box with two buttons: Yes and No. The first string appears on the titlebar. The second string is the message text displayed by the dialog. The third argument specifies the combination of buttons appearing in the dialog. The last parameter is the default button. It is the button which has initially the keyboard focus. The return value is stored in the reply variable.
+
+``` python
+if reply == QtGui.QMessageBox.Yes:
+ event.accept()
+else:
+ event.ignore()
+```
+Here we test the return value. If we click the Yes button, we accept the event which leads to the closure of the widget and to the termination of the application. Otherwise we ignore the close event.
+
+![Message box](./images/messagebox.png)
+
+Figure: Message box
+
+## PyQt6 center window
+The following script shows how we can center a window on the desktop screen.
+``` python
+# file: center.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program centers a window
+on the screen.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+ def initUI(self):
+
+ self.resize(350, 250)
+ self.center()
+
+ self.setWindowTitle('Center')
+ self.show()
+
+ def center(self):
+
+ qr = self.frameGeometry()
+ cp = self.screen().availableGeometry().center()
+
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+The QScreen class class is used to query screen properties.
+``` python
+self.center()
+```
+The code that will center the window is placed in the custom `center` method.
+``` python
+qr = self.frameGeometry()
+```
+We get a rectangle specifying the geometry of the main window. This includes any window frame.
+``` python
+cp = self.screen().availableGeometry().center()
+```
+We figure out the screen resolution of our monitor. And from this resolution, we get the center point.
+``` python
+qr.moveCenter(cp)
+```
+Our rectangle has already its width and height. Now we set the center of the rectangle to the center of the screen. The rectangle's size is unchanged.
+``` python
+self.move(qr.topLeft())
+```
+We move the top-left point of the application window to the top-left point of the qr rectangle, thus centering the window on our screen.
+
+In this part of the PyQt6 tutorial, we have created simple code examples in PyQt6.
\ No newline at end of file
diff --git a/original/pyqt6/images/absolute.png b/original/pyqt6/images/absolute.png
new file mode 100644
index 0000000..cdd9cac
Binary files /dev/null and b/original/pyqt6/images/absolute.png differ
diff --git a/original/pyqt6/images/beziercurve.png b/original/pyqt6/images/beziercurve.png
new file mode 100644
index 0000000..e659a83
Binary files /dev/null and b/original/pyqt6/images/beziercurve.png differ
diff --git a/original/pyqt6/images/brushes.png b/original/pyqt6/images/brushes.png
new file mode 100644
index 0000000..486b163
Binary files /dev/null and b/original/pyqt6/images/brushes.png differ
diff --git a/original/pyqt6/images/burning.png b/original/pyqt6/images/burning.png
new file mode 100644
index 0000000..1ab780e
Binary files /dev/null and b/original/pyqt6/images/burning.png differ
diff --git a/original/pyqt6/images/buttons.png b/original/pyqt6/images/buttons.png
new file mode 100644
index 0000000..beadf46
Binary files /dev/null and b/original/pyqt6/images/buttons.png differ
diff --git a/original/pyqt6/images/calculator.png b/original/pyqt6/images/calculator.png
new file mode 100644
index 0000000..899c5cc
Binary files /dev/null and b/original/pyqt6/images/calculator.png differ
diff --git a/original/pyqt6/images/checkmenu.png b/original/pyqt6/images/checkmenu.png
new file mode 100644
index 0000000..7537b4a
Binary files /dev/null and b/original/pyqt6/images/checkmenu.png differ
diff --git a/original/pyqt6/images/colours.png b/original/pyqt6/images/colours.png
new file mode 100644
index 0000000..97d3a40
Binary files /dev/null and b/original/pyqt6/images/colours.png differ
diff --git a/original/pyqt6/images/coordinates.png b/original/pyqt6/images/coordinates.png
new file mode 100644
index 0000000..38d6c6e
Binary files /dev/null and b/original/pyqt6/images/coordinates.png differ
diff --git a/original/pyqt6/images/dragdrop.png b/original/pyqt6/images/dragdrop.png
new file mode 100644
index 0000000..6893ca4
Binary files /dev/null and b/original/pyqt6/images/dragdrop.png differ
diff --git a/original/pyqt6/images/drawingtext.png b/original/pyqt6/images/drawingtext.png
new file mode 100644
index 0000000..66bbb78
Binary files /dev/null and b/original/pyqt6/images/drawingtext.png differ
diff --git a/original/pyqt6/images/eventobject.png b/original/pyqt6/images/eventobject.png
new file mode 100644
index 0000000..3765552
Binary files /dev/null and b/original/pyqt6/images/eventobject.png differ
diff --git a/original/pyqt6/images/eventsender.png b/original/pyqt6/images/eventsender.png
new file mode 100644
index 0000000..1fa875c
Binary files /dev/null and b/original/pyqt6/images/eventsender.png differ
diff --git a/original/pyqt6/images/inputdialog.png b/original/pyqt6/images/inputdialog.png
new file mode 100644
index 0000000..84accd7
Binary files /dev/null and b/original/pyqt6/images/inputdialog.png differ
diff --git a/original/pyqt6/images/mainwindow.png b/original/pyqt6/images/mainwindow.png
new file mode 100644
index 0000000..cecb754
Binary files /dev/null and b/original/pyqt6/images/mainwindow.png differ
diff --git a/original/pyqt6/images/messagebox.png b/original/pyqt6/images/messagebox.png
new file mode 100644
index 0000000..0a917f5
Binary files /dev/null and b/original/pyqt6/images/messagebox.png differ
diff --git a/original/pyqt6/images/penstyles.png b/original/pyqt6/images/penstyles.png
new file mode 100644
index 0000000..0e14ec6
Binary files /dev/null and b/original/pyqt6/images/penstyles.png differ
diff --git a/original/pyqt6/images/points.png b/original/pyqt6/images/points.png
new file mode 100644
index 0000000..57ce8b9
Binary files /dev/null and b/original/pyqt6/images/points.png differ
diff --git a/original/pyqt6/images/qcheckbox.png b/original/pyqt6/images/qcheckbox.png
new file mode 100644
index 0000000..7f519f8
Binary files /dev/null and b/original/pyqt6/images/qcheckbox.png differ
diff --git a/original/pyqt6/images/qcombobox.png b/original/pyqt6/images/qcombobox.png
new file mode 100644
index 0000000..bb8e9ae
Binary files /dev/null and b/original/pyqt6/images/qcombobox.png differ
diff --git a/original/pyqt6/images/qlineedit.png b/original/pyqt6/images/qlineedit.png
new file mode 100644
index 0000000..ffb8a16
Binary files /dev/null and b/original/pyqt6/images/qlineedit.png differ
diff --git a/original/pyqt6/images/qprogressbar.png b/original/pyqt6/images/qprogressbar.png
new file mode 100644
index 0000000..52a716b
Binary files /dev/null and b/original/pyqt6/images/qprogressbar.png differ
diff --git a/original/pyqt6/images/qslider.png b/original/pyqt6/images/qslider.png
new file mode 100644
index 0000000..7aa8a6d
Binary files /dev/null and b/original/pyqt6/images/qslider.png differ
diff --git a/original/pyqt6/images/qsplitter.png b/original/pyqt6/images/qsplitter.png
new file mode 100644
index 0000000..3536a6d
Binary files /dev/null and b/original/pyqt6/images/qsplitter.png differ
diff --git a/original/pyqt6/images/quitbutton.png b/original/pyqt6/images/quitbutton.png
new file mode 100644
index 0000000..d3ab302
Binary files /dev/null and b/original/pyqt6/images/quitbutton.png differ
diff --git a/original/pyqt6/images/review.png b/original/pyqt6/images/review.png
new file mode 100644
index 0000000..02073a3
Binary files /dev/null and b/original/pyqt6/images/review.png differ
diff --git a/original/pyqt6/images/sigslot.png b/original/pyqt6/images/sigslot.png
new file mode 100644
index 0000000..8a867ba
Binary files /dev/null and b/original/pyqt6/images/sigslot.png differ
diff --git a/original/pyqt6/images/simple.png b/original/pyqt6/images/simple.png
new file mode 100644
index 0000000..310e813
Binary files /dev/null and b/original/pyqt6/images/simple.png differ
diff --git a/original/pyqt6/images/submenu.png b/original/pyqt6/images/submenu.png
new file mode 100644
index 0000000..398b551
Binary files /dev/null and b/original/pyqt6/images/submenu.png differ
diff --git a/original/pyqt6/images/tetris.png b/original/pyqt6/images/tetris.png
new file mode 100644
index 0000000..d225c18
Binary files /dev/null and b/original/pyqt6/images/tetris.png differ
diff --git a/original/pyqt6/images/tetrominoes.png b/original/pyqt6/images/tetrominoes.png
new file mode 100644
index 0000000..9e30a1b
Binary files /dev/null and b/original/pyqt6/images/tetrominoes.png differ
diff --git a/original/pyqt6/images/togglebutton.png b/original/pyqt6/images/togglebutton.png
new file mode 100644
index 0000000..ddecb90
Binary files /dev/null and b/original/pyqt6/images/togglebutton.png differ
diff --git a/original/pyqt6/images/toolbar.png b/original/pyqt6/images/toolbar.png
new file mode 100644
index 0000000..640e829
Binary files /dev/null and b/original/pyqt6/images/toolbar.png differ
diff --git a/original/pyqt6/images/tooltips.png b/original/pyqt6/images/tooltips.png
new file mode 100644
index 0000000..04191b9
Binary files /dev/null and b/original/pyqt6/images/tooltips.png differ
diff --git a/original/pyqt6/index.md b/original/pyqt6/index.md
new file mode 100644
index 0000000..a793559
--- /dev/null
+++ b/original/pyqt6/index.md
@@ -0,0 +1,24 @@
+Python PyQt6
+last modified May 18, 2021
+
+This is PyQt6 tutorial. The tutorial is suited for beginners and intermediate programmers. After reading this tutorial, you will be able to program non-trivial PyQt6 applications. The code examples are availabe at the author's [PyQt6-Tutorial-Examples](https://github.com/janbodnar/PyQt6-Tutorial-Examples) repository.
+
+Table of contents
+Introduction
+Date and time
+First programs
+Menus and toolbars
+Layout management
+Events and signals
+Dialogs
+Widgets
+Widgets II
+Drag & drop
+Painting
+Custom widgets
+The Tetris game
+E-book
+A unique e-book covering advanced features of the PyQt5 library: [Advanced PyQt5 e-book](https://zetcode.com/ebooks/advancedpyqt5/).
+
+Related tutorials
+[PyQt5 tutorial](https://zetcode.com/gui/pyqt5/) covers the previous version of PyQt. [wxPython tutorial](https://zetcode.com/wxpython/), [Python Gtk tutorial](https://zetcode.com/python/gtk/), and [Tkinter tutorial](https://zetcode.com/tkinter/) are tutorials for other popular Python GUI bindings.
\ No newline at end of file
diff --git a/original/pyqt6/introduction.md b/original/pyqt6/introduction.md
new file mode 100644
index 0000000..ce835f2
--- /dev/null
+++ b/original/pyqt6/introduction.md
@@ -0,0 +1,62 @@
+# Introduction to PyQt6
+*last modified April 22, 2021*
+
+This is an introductory PyQt6 tutorial. The purpose of this tutorial is to get you started with the PyQt6 toolkit.
+
+About PyQt6
+PyQt6 is a set of Python bindings for Qt5 application framework from Digia. Qt library is one of the most powerful GUI libraries. The official home site for PyQt6 is www.riverbankcomputing.co.uk/news. PyQt6 is developed by Riverbank Computing.
+
+PyQt6 is implemented as a set of Python modules. It is a multiplatform toolkit which runs on all major operating systems, including Unix, Windows, and Mac OS. PyQt6 is dual licensed; developers can choose between a GPL and a commercial license.
+
+PyQt6 installation
+```sh
+$ pip install PyQt6
+```
+We can install PyQt6 with the `pip` tool.
+
+PyQt6 modules
+PyQt6's classes are divided into several modules, including the following:
+
+- QtCore
+- QtGui
+- QtWidgets
+- QtDBus
+- QtNetwork
+- QtHelp
+- QtXml
+- QtSvg
+- QtSql
+- QtTest
+
+The `QtCore` module contains the core non-GUI functionality. This module is used for working with time, files and directories, various data types, streams, URLs, mime types, threads or processes. The `QtGui` contains classes for windowing system integration, event handling, 2D graphics, basic imaging, fonts and text. The `QtWidgets` module contains classes that provide a set of UI elements to create classic desktop-style user interfaces.
+
+The `QtDBus` contains classes to support IPC using the D-Bus protocol.The `QtNetwork` module contains the classes for network programming. These classes facilitate the coding of TCP/IP and UDP clients and servers by making the network programming easier and more portable. The `QtHelp` contains classes for creating and viewing searchable documentation.
+
+The `QtXml` contains classes for working with XML files. This module provides implementation for both SAX and DOM APIs. The `QtSvg` module provides classes for displaying the contents of SVG files. Scalable Vector Graphics (SVG) is a language for describing two-dimensional graphics and graphical applications in XML. The `QtSql` module provides classes for working with databases. The `QtTest` contains functions that enable unit testing of PyQt6 applications.
+
+Python
+Python is a general-purpose, dynamic, object-oriented programming language. The design purpose of the Python language emphasizes programmer productivity and code readability. It was first released in 1991. Python was inspired by ABC, Haskell, Java, Lisp, Icon, and Perl programming languages. Python is a high-level, general purpose, multiplatform, interpreted language. Python is a minimalistic language. Python is maintained by a large group of volunteers worldwide.
+
+The official web site for the Python programming language is python.org
+
+PyQt6 version
+The `QT_VERSION_STR` provides the version of Qt and the `PYQT_VERSION_STR` version of PyQt6.
+
+``` python
+# file: version.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QT_VERSION_STR
+from PyQt6.QtCore import PYQT_VERSION_STR
+
+print(QT_VERSION_STR)
+print(PYQT_VERSION_STR)
+```
+We print the version of Qt library and PyQt6 module.
+
+``` sh
+$ ./version.py
+6.0.2
+6.0.3
+```
+This chapter was an introduction to PyQt6 toolkit.
\ No newline at end of file
diff --git a/original/pyqt6/layout.md b/original/pyqt6/layout.md
new file mode 100644
index 0000000..5e28d2c
--- /dev/null
+++ b/original/pyqt6/layout.md
@@ -0,0 +1,361 @@
+# Layout management in PyQt6
+*last modified April 27, 2021*
+
+Layout management is the way how we place the widgets on the application window. We can place our widgets using absolute positioning or with layout classes. Managing the layout with layout managers is the preferred way of organizing our widgets.
+
+## Absolute positioning
+The programmer specifies the position and the size of each widget in pixels. When you use absolute positioning, we have to understand the following limitations:
+
+ - The size and the position of a widget do not change if we resize a window
+ - Applications might look different on various platforms
+ - Changing fonts in our application might spoil the layout
+ - If we decide to change our layout, we must completely redo our layout, which is tedious and time consuming
+
+The following example positions widgets in absolute coordinates.
+
+``` python
+# file: absolute.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows three labels on a window
+using absolute positioning.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QLabel, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ lbl1 = QLabel('ZetCode', self)
+ lbl1.move(15, 10)
+
+ lbl2 = QLabel('tutorials', self)
+ lbl2.move(35, 40)
+
+ lbl3 = QLabel('for programmers', self)
+ lbl3.move(55, 70)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Absolute')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+We use the move method to position our widgets. In our case these are labels. We position them by providing the x and y coordinates. The beginning of the coordinate system is at the left top corner. The x values grow from left to right. The y values grow from top to bottom.
+
+``` python
+lbl1 = QLabel('ZetCode', self)
+lbl1.move(15, 10)
+The label widget is positioned at x=15 and y=10.
+```
+
+![Absolute positioning](./images/absolute.png)
+
+Figure: Absolute positioning
+
+## PyQt6 QHBoxLayout
+`QHBoxLayout` and `QVBoxLayout` are basic layout classes that line up widgets horizontally and vertically.
+
+Imagine that we wanted to place two buttons in the right bottom corner. To create such a layout, we use one horizontal and one vertical box. To create the necessary space, we add a *stretch factor*.
+
+``` python
+# file: box_layout.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we position two push
+buttons in the bottom-right corner
+of the window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QPushButton,
+ QHBoxLayout, QVBoxLayout, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ okButton = QPushButton("OK")
+ cancelButton = QPushButton("Cancel")
+
+ hbox = QHBoxLayout()
+ hbox.addStretch(1)
+ hbox.addWidget(okButton)
+ hbox.addWidget(cancelButton)
+
+ vbox = QVBoxLayout()
+ vbox.addStretch(1)
+ vbox.addLayout(hbox)
+
+ self.setLayout(vbox)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Buttons')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+
+The example places two buttons in the bottom-right corner of the window. They stay there when we resize the application window. We use both a `HBoxLayout` and a `QVBoxLayout`.
+``` python
+okButton = QPushButton("OK")
+cancelButton = QPushButton("Cancel")
+```
+Here we create two push buttons.
+``` python
+hbox = QHBoxLayout()
+hbox.addStretch(1)
+hbox.addWidget(okButton)
+hbox.addWidget(cancelButton)
+```
+We create a horizontal box layout and add a stretch factor and both buttons. The stretch adds a stretchable space before the two buttons. This will push them to the right of the window.
+``` python
+vbox = QVBoxLayout()
+vbox.addStretch(1)
+vbox.addLayout(hbox)
+```
+The horizontal layout is placed into the vertical layout. The stretch factor in the vertical box will push the horizontal box with the buttons to the bottom of the window.
+``` python
+self.setLayout(vbox)
+```
+Finally, we set the main layout of the window.
+
+![Buttons](./images/buttons.png)
+
+Figure: Buttons
+
+## PyQt6 QGridLayout
+
+`QGridLayout` is the most universal layout class. It divides the space into rows and columns.
+``` python
+# file: calculator.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create a skeleton
+of a calculator using QGridLayout.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QGridLayout,
+ QPushButton, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ grid = QGridLayout()
+ self.setLayout(grid)
+
+ names = ['Cls', 'Bck', '', 'Close',
+ '7', '8', '9', '/',
+ '4', '5', '6', '*',
+ '1', '2', '3', '-',
+ '0', '.', '=', '+']
+
+ positions = [(i, j) for i in range(5) for j in range(4)]
+
+ for position, name in zip(positions, names):
+
+ if name == '':
+ continue
+
+ button = QPushButton(name)
+ grid.addWidget(button, *position)
+
+ self.move(300, 150)
+ self.setWindowTitle('Calculator')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we create a grid of buttons.
+``` python
+grid = QGridLayout()
+self.setLayout(grid)
+```
+The instance of a `QGridLayout` is created and set to be the layout for the application window.
+``` python
+names = ['Cls', 'Bck', '', 'Close',
+ '7', '8', '9', '/',
+ '4', '5', '6', '*',
+ '1', '2', '3', '-',
+ '0', '.', '=', '+']
+```
+These are the labels used later for buttons.
+``` python
+positions = [(i,j) for i in range(5) for j in range(4)]
+```
+We create a list of positions in the grid.
+``` python
+for position, name in zip(positions, names):
+
+ if name == '':
+ continue
+
+ button = QPushButton(name)
+ grid.addWidget(button, *position)
+```
+Buttons are created and added to the layout with the `addWidget` method.
+
+![Calculator skeleton](./images/calculator.png)
+
+Figure: Calculator skeleton
+
+## Review example
+Widgets can span multiple columns or rows in a grid. In the next example we illustrate this.
+
+``` python
+# file: review.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create a bit
+more complicated window layout using
+the QGridLayout manager.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QLabel, QLineEdit,
+ QTextEdit, QGridLayout, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ title = QLabel('Title')
+ author = QLabel('Author')
+ review = QLabel('Review')
+
+ titleEdit = QLineEdit()
+ authorEdit = QLineEdit()
+ reviewEdit = QTextEdit()
+
+ grid = QGridLayout()
+ grid.setSpacing(10)
+
+ grid.addWidget(title, 1, 0)
+ grid.addWidget(titleEdit, 1, 1)
+
+ grid.addWidget(author, 2, 0)
+ grid.addWidget(authorEdit, 2, 1)
+
+ grid.addWidget(review, 3, 0)
+ grid.addWidget(reviewEdit, 3, 1, 5, 1)
+
+ self.setLayout(grid)
+
+ self.setGeometry(300, 300, 350, 300)
+ self.setWindowTitle('Review')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+We create a window in which we have three labels, two line edits and one text edit widget. The layout is done with the `QGridLayout`.
+
+``` python
+grid = QGridLayout()
+grid.setSpacing(10)
+```
+
+We create a grid layout and set spacing between widgets.
+
+``` python
+grid.addWidget(reviewEdit, 3, 1, 5, 1)
+```
+
+If we add a widget to a grid, we can provide row span and column span of the widget. In our case, we make the `reviewEdit` widget span 5 rows.
+
+![Review example](./images/review.png)
+
+Figure: Review example
+
+This part of the PyQt6 tutorial was dedicated to layout management.
\ No newline at end of file
diff --git a/original/pyqt6/menustoolbars.md b/original/pyqt6/menustoolbars.md
new file mode 100644
index 0000000..b92c90f
--- /dev/null
+++ b/original/pyqt6/menustoolbars.md
@@ -0,0 +1,524 @@
+# Menus and toolbars in PyQt6
+*last modified April 24, 2021*
+
+In this part of the PyQt6 tutorial, we create a statusbar, menubar and a toolbar. A menu is a group of commands located in a menubar. A toolbar has buttons with some common commands in the application. Statusbar shows status information, usually at the bottom of the application window.
+
+## PyQt6 QMainWindow
+The `QMainWindow` class provides a main application window. This enables to create a classic application skeleton with a statusbar, toolbars, and a menubar.
+
+## PyQt6 statusbar
+A statusbar is a widget that is used for displaying status information.
+
+``` python
+# file: statusbar.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a statusbar.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QApplication
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.statusBar().showMessage('Ready')
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Statusbar')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+The statusbar is created with the help of the `QMainWindow` widget.
+``` python
+self.statusBar().showMessage('Ready')
+```
+To get the statusbar, we call the statusBar method of the `QtGui.QMainWindow` class. The first call of the method creates a status bar. Subsequent calls return the statusbar object. The `showMessage` displays a message on the statusbar.
+
+## PyQt6 simple menu
+A menubar is a common part of a GUI application. It is a group of commands located in various menus. (Mac OS treats menubars differently. To get a similar outcome, we can add the following line: `menubar.setNativeMenuBar(False)`.)
+``` python
+# file: simple_menu.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a menubar. The
+menubar has one menu with an exit action.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QApplication
+from PyQt6.QtGui import QIcon, QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ exitAct = QAction(QIcon('exit.png'), '&Exit', self)
+ exitAct.setShortcut('Ctrl+Q')
+ exitAct.setStatusTip('Exit application')
+ exitAct.triggered.connect(QApplication.instance().quit)
+
+ self.statusBar()
+
+ menubar = self.menuBar()
+ fileMenu = menubar.addMenu('&File')
+ fileMenu.addAction(exitAct)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Simple menu')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In the above example, we create a menubar with one menu. This menu contains one action which terminates the application if selected. A statusbar is created as well. The action is accessible with the `Ctrl+Q` shortcut.
+``` python
+exitAct = QAction(QIcon('exit.png'), '&Exit', self)
+exitAct.setShortcut('Ctrl+Q')
+exitAct.setStatusTip('Exit application')
+```
+`QAction` is an abstraction for actions performed with a menubar, toolbar, or with a custom keyboard shortcut. In the above three lines, we create an action with a specific icon and an 'Exit' label. Furthermore, a shortcut is defined for this action. The third line creates a status tip which is shown in the statusbar when we hover a mouse pointer over the menu item.
+``` python
+exitAct.triggered.connect(QApplication.instance().quit)
+```
+When we select this particular action, a triggered signal is emitted. The signal is connected to the quit method of the `QApplication` widget. This terminates the application.
+``` python
+menubar = self.menuBar()
+fileMenu = menubar.addMenu('&File')
+fileMenu.addAction(exitAction)
+```
+The menuBar method creates a menubar. We create a file menu with `addMenu` and add the action with `addAction`.
+
+## PyQt6 submenu
+A submenu is a menu located inside another menu.
+``` python
+submenu.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a submenu.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QMenu, QApplication
+from PyQt6.QtGui import QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ menubar = self.menuBar()
+ fileMenu = menubar.addMenu('File')
+
+ impMenu = QMenu('Import', self)
+ impAct = QAction('Import mail', self)
+ impMenu.addAction(impAct)
+
+ newAct = QAction('New', self)
+
+ fileMenu.addAction(newAct)
+ fileMenu.addMenu(impMenu)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Submenu')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In the example, we have two menu items; one is located in the File menu and the other one in the File's Import submenu.
+
+``` python
+impMenu = QMenu('Import', self)
+```
+
+New menu is created with `QMenu`.
+
+``` python
+impAct = QAction('Import mail', self)
+impMenu.addAction(impAct)
+```
+
+An action is added to the submenu with addAction.
+
+![Submenu](./images/submenu.png)
+
+Figure: Submenu
+
+## PyQt6 check menu
+In the following example, we create a menu that can be checked and unchecked.
+
+``` python
+# file: check_menu.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a checkable menu.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QApplication
+from PyQt6.QtGui import QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.statusbar = self.statusBar()
+ self.statusbar.showMessage('Ready')
+
+ menubar = self.menuBar()
+ viewMenu = menubar.addMenu('View')
+
+ viewStatAct = QAction('View statusbar', self, checkable=True)
+ viewStatAct.setStatusTip('View statusbar')
+ viewStatAct.setChecked(True)
+ viewStatAct.triggered.connect(self.toggleMenu)
+
+ viewMenu.addAction(viewStatAct)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Check menu')
+ self.show()
+
+
+ def toggleMenu(self, state):
+
+ if state:
+ self.statusbar.show()
+ else:
+ self.statusbar.hide()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+The code example creates a View menu with one action. The action shows or hides a statusbar. When the statusbar is visible, the menu item is checked.
+
+``` python
+viewStatAct = QAction('View statusbar', self, checkable=True)
+```
+With the `checkable` option we create a checkable menu.
+
+``` python
+viewStatAct.setChecked(True)
+```
+Since the statusbar is visible from the start, we check the action with `setChecked` method.
+
+``` python
+def toggleMenu(self, state):
+
+ if state:
+ self.statusbar.show()
+ else:
+ self.statusbar.hide()
+```
+Depending on the state of the action, we show or hide the statusbar.
+
+![Check menu](./images/checkmenu.png)
+
+Figure: Check menu
+## PyQt6 context menu
+A context menu, also called a popup menu, is a list of commands that appears under some context. For example, in a Opera web browser when we right click on a web page, we get a context menu. Here we can reload a page, go back, or view a page source. If we right click on a toolbar, we get another context menu for managing toolbars.
+
+``` python
+# file: context_menu.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a context menu.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QMenu, QApplication
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Context menu')
+ self.show()
+
+
+ def contextMenuEvent(self, event):
+
+ cmenu = QMenu(self)
+
+ newAct = cmenu.addAction("New")
+ openAct = cmenu.addAction("Open")
+ quitAct = cmenu.addAction("Quit")
+ action = cmenu.exec(self.mapToGlobal(event.pos()))
+
+ if action == quitAct:
+ QApplication.instance().quit()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+To work with a context menu, we have to reimplement the `contextMenuEvent` method.
+
+``` python
+action = cmenu.exec(self.mapToGlobal(event.pos()))
+```
+The context menu is displayed with the `exec` method. The get the coordinates of the mouse pointer from the event object. The `mapToGlobal` method translates the widget coordinates to the global screen coordinates.
+``` python
+if action == quitAct:
+ QApplication.instance().quit()
+```
+If the action returned from the context menu equals to quit action, we terminate the application.
+
+## PyQt6 toolbar
+Menus group all commands that we can use in an application. Toolbars provide a quick access to the most frequently used commands.
+
+``` python
+# file: toolbar.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a toolbar.
+The toolbar has one action, which
+terminates the application, if triggered.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QApplication
+from PyQt6.QtGui import QIcon, QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
+ exitAct.setShortcut('Ctrl+Q')
+ exitAct.triggered.connect(QApplication.instance().quit)
+
+ self.toolbar = self.addToolBar('Exit')
+ self.toolbar.addAction(exitAct)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Toolbar')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In the above example, we create a simple toolbar. The toolbar has one tool action, an exit action which terminates the application when triggered.
+
+``` python
+exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
+exitAct.setShortcut('Ctrl+Q')
+exitAct.triggered.connect(QApplication.instance().quit)
+```
+Similar to the menubar example above, we create an action object. The object has a label, icon, and a shorcut. A quit method of the `QApplication` is connected to the triggered signal.
+
+``` python
+self.toolbar = self.addToolBar('Exit')
+self.toolbar.addAction(exitAction)
+```
+The toolbar is created with the `addToolBar` method. We add an action object to the toolbar with `addAction`.
+
+![Toolbar](./images/toolbar.png)
+
+Figure: Toolbar
+## PyQt6 main window
+In the last example of this section, we create a menubar, toolbar, and a statusbar. We also create a central widget.
+
+``` python
+# file: main_window.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a skeleton of
+a classic GUI application with a menubar,
+toolbar, statusbar, and a central widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QTextEdit, QApplication
+from PyQt6.QtGui import QIcon, QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ textEdit = QTextEdit()
+ self.setCentralWidget(textEdit)
+
+ exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
+ exitAct.setShortcut('Ctrl+Q')
+ exitAct.setStatusTip('Exit application')
+ exitAct.triggered.connect(self.close)
+
+ self.statusBar()
+
+ menubar = self.menuBar()
+ fileMenu = menubar.addMenu('&File')
+ fileMenu.addAction(exitAct)
+
+ toolbar = self.addToolBar('Exit')
+ toolbar.addAction(exitAct)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Main window')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+This code example creates a skeleton of a classic GUI application with a menubar, toolbar, and a statusbar.
+``` python
+textEdit = QTextEdit()
+self.setCentralWidget(textEdit)
+```
+Here we create a text edit widget. We set it to be the central widget of the `QMainWindow`. The central widget occupies all space that is left.
+
+![Main window](./images/mainwindow.png)
+
+Figure: Main window
+
+In this part of the PyQt6 tutorial, we worked with menus, toolbars, a statusbar, and a main application window.
\ No newline at end of file
diff --git a/original/pyqt6/painting.md b/original/pyqt6/painting.md
new file mode 100644
index 0000000..1fe26a2
--- /dev/null
+++ b/original/pyqt6/painting.md
@@ -0,0 +1,582 @@
+# Painting in PyQt6
+*last modified May 15, 2021*
+
+PyQt6 painting system is able to render vector graphics, images, and outline font-based text. Painting is needed in applications when we want to change or enhance an existing widget, or if we are creating a custom widget from scratch. To do the drawing, we use the painting API provided by the PyQt6 toolkit.
+
+## QPainter
+QPainter performs low-level painting on widgets and other paint devices. It can draw everything from simple lines to complex shapes.
+
+## The paintEvent method
+The painting is done within the paintEvent method. The painting code is placed between the begin and end methods of the QPainter object. It performs low-level painting on widgets and other paint devices.
+
+## PyQt6 draw text
+We begin with drawing some Unicode text on the client area of a window.
+
+``` python
+# file: draw_text.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we draw text in Russian Cylliric.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter, QColor, QFont
+from PyQt6.QtCore import Qt
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.text = "Лев Николаевич Толстой\nАнна Каренина"
+
+ self.setGeometry(300, 300, 350, 300)
+ self.setWindowTitle('Drawing text')
+ self.show()
+
+
+ def paintEvent(self, event):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawText(event, qp)
+ qp.end()
+
+
+ def drawText(self, event, qp):
+
+ qp.setPen(QColor(168, 34, 3))
+ qp.setFont(QFont('Decorative', 10))
+ qp.drawText(event.rect(), Qt.Alignment.AlignCenter, self.text)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we draw some text in Cylliric. The text is vertically and horizontally aligned.
+
+``` python
+def paintEvent(self, event):
+...
+```
+Drawing is done within the paint event.
+
+``` python
+qp = QPainter()
+qp.begin(self)
+self.drawText(event, qp)
+qp.end()
+```
+The `QPainter` class is responsible for all the low-level painting. All the painting methods go between begin and end methods. The actual painting is delegated to the drawText method.
+``` python
+qp.setPen(QColor(168, 34, 3))
+qp.setFont(QFont('Decorative', 10))
+```
+Here we define a pen and a font which are used to draw the text.
+
+``` python
+qp.drawText(event.rect(), Qt.Alignment.AlignCenter, self.text)
+```
+The drawText method draws text on the window. The rect method of the paint event returns the rectangle that needs to be updated. With the `Qt.Alignment.AlignCenter` we align the text in both dimensions.
+
+![Drawing text](./images/drawingtext.png)
+
+Figure: Drawing text
+
+## PyQt6 draw points
+A point is the most simple graphics object that can be drawn. It is a small spot on the window.
+
+``` python
+# file: draw_points.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In the example, we draw randomly 1000 red points
+on the window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter
+from PyQt6.QtCore import Qt
+import sys, random
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setMinimumSize(50, 50)
+ self.setGeometry(300, 300, 350, 300)
+ self.setWindowTitle('Points')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawPoints(qp)
+ qp.end()
+
+
+ def drawPoints(self, qp):
+
+ qp.setPen(Qt.GlobalColor.red)
+ size = self.size()
+
+ for i in range(1000):
+
+ x = random.randint(1, size.width() - 1)
+ y = random.randint(1, size.height() - 1)
+ qp.drawPoint(x, y)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we draw randomly 1000 red points on the client area of the window.
+
+``` python
+qp.setPen(Qt.GlobalColor.red)
+```
+We set the pen to red colour. We use a predefined `Qt.GlobalColor.red` colour constant.
+
+``` python
+size = self.size()
+```
+Each time we resize the window, a paint event is generated. We get the current size of the window with the size method. We use the size of the window to distribute the points all over the client area of the window.
+
+``` python
+qp.drawPoint(x, y)
+```
+We draw the point with the drawPoint method.
+
+![Points](./images/points.png)
+
+Figure: Points
+
+## PyQt6 colours
+
+A colour is an object representing a combination of Red, Green, and Blue (RGB) intensity values. Valid RGB values are in the range from 0 to 255. We can define a colour in various ways. The most common are RGB decimal values or hexadecimal values. We can also use an RGBA value which stands for Red, Green, Blue, and Alpha. Here we add some extra information regarding transparency. Alpha value of 255 defines full opacity, 0 is for full transparency, e.g. the colour is invisible.
+
+``` python
+# file: colours.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example draws three rectangles in three
+different colours.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter, QColor
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 350, 100)
+ self.setWindowTitle('Colours')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawRectangles(qp)
+ qp.end()
+
+
+ def drawRectangles(self, qp):
+
+ col = QColor(0, 0, 0)
+ col.setNamedColor('#d4d4d4')
+ qp.setPen(col)
+
+ qp.setBrush(QColor(200, 0, 0))
+ qp.drawRect(10, 15, 90, 60)
+
+ qp.setBrush(QColor(255, 80, 0, 160))
+ qp.drawRect(130, 15, 90, 60)
+
+ qp.setBrush(QColor(25, 0, 90, 200))
+ qp.drawRect(250, 15, 90, 60)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we draw three coloured rectangles.
+
+``` python
+color = QColor(0, 0, 0)
+color.setNamedColor('#d4d4d4')
+```
+Here we define a colour using a hexadecimal notation.
+
+``` python
+qp.setBrush(QColor(200, 0, 0))
+qp.drawRect(10, 15, 90, 60)
+```
+Here we define a brush and draw a rectangle. A *brush* is an elementary graphics object which is used to draw the background of a shape. The `drawRect` method accepts four parameters. The first two are x and y values on the axis. The third and fourth parameters are the width and height of the rectangle. The method draws the rectangle using the current pen and brush.
+
+![Colours](./images/colours.png)
+
+Figure: Colours
+
+## PyQt6 QPen
+The `QPen` is an elementary graphics object. It is used to draw lines, curves and outlines of rectangles, ellipses, polygons, or other shapes.
+
+``` python
+# file: pens.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example we draw 6 lines using
+different pen styles.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter, QPen
+from PyQt6.QtCore import Qt
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 280, 270)
+ self.setWindowTitle('Pen styles')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawLines(qp)
+ qp.end()
+
+
+ def drawLines(self, qp):
+
+ pen = QPen(Qt.GlobalColor.black, 2, Qt.PenStyle.SolidLine)
+
+ qp.setPen(pen)
+ qp.drawLine(20, 40, 250, 40)
+
+ pen.setStyle(Qt.PenStyle.DashLine)
+ qp.setPen(pen)
+ qp.drawLine(20, 80, 250, 80)
+
+ pen.setStyle(Qt.PenStyle.DashDotLine)
+ qp.setPen(pen)
+ qp.drawLine(20, 120, 250, 120)
+
+ pen.setStyle(Qt.PenStyle.DotLine)
+ qp.setPen(pen)
+ qp.drawLine(20, 160, 250, 160)
+
+ pen.setStyle(Qt.PenStyle.DashDotDotLine)
+ qp.setPen(pen)
+ qp.drawLine(20, 200, 250, 200)
+
+ pen.setStyle(Qt.PenStyle.CustomDashLine)
+ pen.setDashPattern([1, 4, 5, 4])
+ qp.setPen(pen)
+ qp.drawLine(20, 240, 250, 240)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+
+In our example, we draw six lines. The lines are drawn in six different pen styles. There are five predefined pen styles. We can create also custom pen styles. The last line is drawn using a custom pen style.
+
+``` python
+pen = QPen(Qt.GlobalColor.black, 2, Qt.PenStyle.SolidLine)
+```
+We create a `QPen` object. The colour is black. The width is set to 2 pixels so that we can see the differences between the pen styles. `Qt.SolidLine` is one of the predefined pen styles.
+
+``` python
+pen.setStyle(Qt.PenStyle.CustomDashLine)
+pen.setDashPattern([1, 4, 5, 4])
+qp.setPen(pen)
+```
+Here we define a custom pen style. We set a `Qt.PenStyle.CustomDashLine` pen style and call the `setDashPattern` method. The list of numbers defines a style. There must be an even number of numbers. Odd numbers define a dash, even numbers space. The greater the number, the greater the space or the dash. Our pattern is 1 px dash, 4 px space, 5 px dash, 4 px space etc.
+
+![Pen styles](./images/penstyles.png)
+
+Figure: Pen styles
+
+## PyQt6 QBrush
+`QBrush` is an elementary graphics object. It is used to paint the background of graphics shapes, such as rectangles, ellipses, or polygons. A brush can be of three different types: a predefined brush, a gradient, or a texture pattern.
+
+``` python
+# file: brushes.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example draws nine rectangles in different
+brush styles.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter, QBrush
+from PyQt6.QtCore import Qt
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 355, 280)
+ self.setWindowTitle('Brushes')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawBrushes(qp)
+ qp.end()
+
+
+ def drawBrushes(self, qp):
+
+ brush = QBrush(Qt.BrushStyle.SolidPattern)
+ qp.setBrush(brush)
+ qp.drawRect(10, 15, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.Dense1Pattern)
+ qp.setBrush(brush)
+ qp.drawRect(130, 15, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.Dense2Pattern)
+ qp.setBrush(brush)
+ qp.drawRect(250, 15, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.DiagCrossPattern)
+ qp.setBrush(brush)
+ qp.drawRect(10, 105, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.Dense5Pattern)
+ qp.setBrush(brush)
+ qp.drawRect(130, 105, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.Dense6Pattern)
+ qp.setBrush(brush)
+ qp.drawRect(250, 105, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.HorPattern)
+ qp.setBrush(brush)
+ qp.drawRect(10, 195, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.VerPattern)
+ qp.setBrush(brush)
+ qp.drawRect(130, 195, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.BDiagPattern)
+ qp.setBrush(brush)
+ qp.drawRect(250, 195, 90, 60)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we draw nine different rectangles.
+
+``` python
+brush = QBrush(Qt.BrushStyle.SolidPattern)
+qp.setBrush(brush)
+qp.drawRect(10, 15, 90, 60)
+```
+We define a brush object. We set it to the painter object and draw the rectangle by calling the drawRect method.
+
+![Brushes](./images/brushes.png)
+
+Figure: Brushes
+
+## Bézier curve
+Bézier curve is a cubic line. Bézier curve in PyQt6 can be created with `QPainterPath`. A painter path is an object composed of a number of graphical building blocks, such as rectangles, ellipses, lines, and curves.
+
+``` python
+# file: bezier_curve.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program draws a Bézier curve with
+QPainterPath.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtGui import QPainter, QPainterPath
+from PyQt6.QtWidgets import QWidget, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 380, 250)
+ self.setWindowTitle('Bézier curve')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ qp.setRenderHint(QPainter.RenderHints.Antialiasing)
+ self.drawBezierCurve(qp)
+ qp.end()
+
+
+ def drawBezierCurve(self, qp):
+
+ path = QPainterPath()
+ path.moveTo(30, 30)
+ path.cubicTo(30, 30, 200, 350, 350, 30)
+
+ qp.drawPath(path)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+This example draws a Bézier curve.
+
+```python
+path = QPainterPath()
+path.moveTo(30, 30)
+path.cubicTo(30, 30, 200, 350, 350, 30)
+```
+We create a Bézier curve with `QPainterPath` path. The curve is created with `cubicTo` method, which takes three points: starting point, control point, and ending point.
+
+``` python
+qp.drawPath(path)
+```
+The final path is drawn with drawPath method.
+
+![Bézier curve](./images/beziercurve.png)
+
+Figure: Bézier curve
+
+In this part of the PyQt6 tutorial, we did some basic painting.
\ No newline at end of file
diff --git a/original/pyqt6/tetris.md b/original/pyqt6/tetris.md
new file mode 100644
index 0000000..6d97db6
--- /dev/null
+++ b/original/pyqt6/tetris.md
@@ -0,0 +1,861 @@
+# Tetris in PyQt6
+*last modified May 5, 2021*
+
+In this chapter, we create a Tetris game clone.
+
+## Tetris
+The Tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a Russian programmer *Alexey Pajitnov* in 1985. Since then, Tetris is available on almost every computer platform in lots of variations.
+
+Tetris is called a falling block puzzle game. In this game, we have seven different shapes called tetrominoes: an S-shape, a Z-shape, a T-shape, an L-shape, a Line-shape, a MirroredL-shape, and a Square-shape. Each of these shapes is formed with four squares. The shapes are falling down the board. The object of the Tetris game is to move and rotate the shapes so that they fit as much as possible. If we manage to form a row, the row is destroyed and we score. We play the Tetris game until we top out.
+
+![Tetrominoes](./images/tetrominoes.png)
+
+Figure: Tetrominoes
+
+PyQt6 is a toolkit designed to create applications. There are other libraries which are targeted at creating computer games. Nevertheless, PyQt6 and other application toolkits can be used to create simple games.
+
+Creating a computer game is a good way for enhancing programming skills.
+
+## The development
+We do not have images for our Tetris game, we draw the tetrominoes using the drawing API available in the PyQt6 programming toolkit. Behind every computer game, there is a mathematical model. So it is in Tetris.
+
+Some ideas behind the game:
+
+ - We use a `QtCore.QBasicTimer` to create a game cycle.
+ - The tetrominoes are drawn.
+ - The shapes move on a square by square basis (not pixel by pixel).
+ - Mathematically a board is a simple list of numbers.
+
+The code consists of four classes: `Tetris`, `Board`, `Tetrominoe` and `Shape`. The `Tetris` class sets up the game. The `Board` is where the game logic is written. The `Tetrominoe` class contains names for all tetris pieces and the `Shape` class contains the code for a tetris piece.
+
+``` python
+# files: tetris.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This is a Tetris game clone.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import random
+import sys
+
+from PyQt6.QtCore import Qt, QBasicTimer, pyqtSignal
+from PyQt6.QtGui import QPainter, QColor
+from PyQt6.QtWidgets import QMainWindow, QFrame, QApplication
+
+
+class Tetris(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+ """initiates application UI"""
+
+ self.tboard = Board(self)
+ self.setCentralWidget(self.tboard)
+
+ self.statusbar = self.statusBar()
+ self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
+
+ self.tboard.start()
+
+ self.resize(180, 380)
+ self.center()
+ self.setWindowTitle('Tetris')
+ self.show()
+
+
+ def center(self):
+ """centers the window on the screen"""
+
+ qr = self.frameGeometry()
+ cp = self.screen().availableGeometry().center()
+
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+
+class Board(QFrame):
+
+ msg2Statusbar = pyqtSignal(str)
+
+ BoardWidth = 10
+ BoardHeight = 22
+ Speed = 300
+
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ self.initBoard()
+
+
+ def initBoard(self):
+ """initiates board"""
+
+ self.timer = QBasicTimer()
+ self.isWaitingAfterLine = False
+
+ self.curX = 0
+ self.curY = 0
+ self.numLinesRemoved = 0
+ self.board = []
+
+ self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+ self.isStarted = False
+ self.isPaused = False
+ self.clearBoard()
+
+
+ def shapeAt(self, x, y):
+ """determines shape at the board position"""
+
+ return self.board[(y * Board.BoardWidth) + x]
+
+
+ def setShapeAt(self, x, y, shape):
+ """sets a shape at the board"""
+
+ self.board[(y * Board.BoardWidth) + x] = shape
+
+
+ def squareWidth(self):
+ """returns the width of one square"""
+
+ return self.contentsRect().width() // Board.BoardWidth
+
+
+ def squareHeight(self):
+ """returns the height of one square"""
+
+ return self.contentsRect().height() // Board.BoardHeight
+
+
+ def start(self):
+ """starts game"""
+
+ if self.isPaused:
+ return
+
+ self.isStarted = True
+ self.isWaitingAfterLine = False
+ self.numLinesRemoved = 0
+ self.clearBoard()
+
+ self.msg2Statusbar.emit(str(self.numLinesRemoved))
+
+ self.newPiece()
+ self.timer.start(Board.Speed, self)
+
+
+ def pause(self):
+ """pauses game"""
+
+ if not self.isStarted:
+ return
+
+ self.isPaused = not self.isPaused
+
+ if self.isPaused:
+ self.timer.stop()
+ self.msg2Statusbar.emit("paused")
+
+ else:
+ self.timer.start(Board.Speed, self)
+ self.msg2Statusbar.emit(str(self.numLinesRemoved))
+
+ self.update()
+
+
+ def paintEvent(self, event):
+ """paints all shapes of the game"""
+
+ painter = QPainter(self)
+ rect = self.contentsRect()
+
+ boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()
+
+ for i in range(Board.BoardHeight):
+ for j in range(Board.BoardWidth):
+ shape = self.shapeAt(j, Board.BoardHeight - i - 1)
+
+ if shape != Tetrominoe.NoShape:
+ self.drawSquare(painter,
+ rect.left() + j * self.squareWidth(),
+ boardTop + i * self.squareHeight(), shape)
+
+ if self.curPiece.shape() != Tetrominoe.NoShape:
+
+ for i in range(4):
+ x = self.curX + self.curPiece.x(i)
+ y = self.curY - self.curPiece.y(i)
+ self.drawSquare(painter, rect.left() + x * self.squareWidth(),
+ boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
+ self.curPiece.shape())
+
+
+ def keyPressEvent(self, event):
+ """processes key press events"""
+
+ if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
+ super(Board, self).keyPressEvent(event)
+ return
+
+ key = event.key()
+
+ if key == Qt.Key.Key_P:
+ self.pause()
+ return
+
+ if self.isPaused:
+ return
+
+ elif key == Qt.Key.Key_Left.value:
+ self.tryMove(self.curPiece, self.curX - 1, self.curY)
+
+ elif key == Qt.Key.Key_Right.value:
+ self.tryMove(self.curPiece, self.curX + 1, self.curY)
+
+ elif key == Qt.Key.Key_Down.value:
+ self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)
+
+ elif key == Qt.Key.Key_Up.value:
+ self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
+
+ elif key == Qt.Key.Key_Space.value:
+ self.dropDown()
+
+ elif key == Qt.Key.Key_D.value:
+ self.oneLineDown()
+
+ else:
+ super(Board, self).keyPressEvent(event)
+
+
+ def timerEvent(self, event):
+ """handles timer event"""
+
+ if event.timerId() == self.timer.timerId():
+
+ if self.isWaitingAfterLine:
+ self.isWaitingAfterLine = False
+ self.newPiece()
+ else:
+ self.oneLineDown()
+
+ else:
+ super(Board, self).timerEvent(event)
+
+
+ def clearBoard(self):
+ """clears shapes from the board"""
+
+ for i in range(Board.BoardHeight * Board.BoardWidth):
+ self.board.append(Tetrominoe.NoShape)
+
+
+ def dropDown(self):
+ """drops down a shape"""
+
+ newY = self.curY
+
+ while newY > 0:
+
+ if not self.tryMove(self.curPiece, self.curX, newY - 1):
+ break
+
+ newY -= 1
+
+ self.pieceDropped()
+
+
+ def oneLineDown(self):
+ """goes one line down with a shape"""
+
+ if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
+ self.pieceDropped()
+
+
+ def pieceDropped(self):
+ """after dropping shape, remove full lines and create new shape"""
+
+ for i in range(4):
+ x = self.curX + self.curPiece.x(i)
+ y = self.curY - self.curPiece.y(i)
+ self.setShapeAt(x, y, self.curPiece.shape())
+
+ self.removeFullLines()
+
+ if not self.isWaitingAfterLine:
+ self.newPiece()
+
+
+ def removeFullLines(self):
+ """removes all full lines from the board"""
+
+ numFullLines = 0
+ rowsToRemove = []
+
+ for i in range(Board.BoardHeight):
+
+ n = 0
+ for j in range(Board.BoardWidth):
+ if not self.shapeAt(j, i) == Tetrominoe.NoShape:
+ n = n + 1
+
+ if n == 10:
+ rowsToRemove.append(i)
+
+ rowsToRemove.reverse()
+
+ for m in rowsToRemove:
+
+ for k in range(m, Board.BoardHeight):
+ for l in range(Board.BoardWidth):
+ self.setShapeAt(l, k, self.shapeAt(l, k + 1))
+
+ numFullLines = numFullLines + len(rowsToRemove)
+
+ if numFullLines > 0:
+ self.numLinesRemoved = self.numLinesRemoved + numFullLines
+ self.msg2Statusbar.emit(str(self.numLinesRemoved))
+
+ self.isWaitingAfterLine = True
+ self.curPiece.setShape(Tetrominoe.NoShape)
+ self.update()
+
+
+ def newPiece(self):
+ """creates a new shape"""
+
+ self.curPiece = Shape()
+ self.curPiece.setRandomShape()
+ self.curX = Board.BoardWidth // 2 + 1
+ self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
+
+ if not self.tryMove(self.curPiece, self.curX, self.curY):
+
+ self.curPiece.setShape(Tetrominoe.NoShape)
+ self.timer.stop()
+ self.isStarted = False
+ self.msg2Statusbar.emit("Game over")
+
+
+ def tryMove(self, newPiece, newX, newY):
+ """tries to move a shape"""
+
+ for i in range(4):
+
+ x = newX + newPiece.x(i)
+ y = newY - newPiece.y(i)
+
+ if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
+ return False
+
+ if self.shapeAt(x, y) != Tetrominoe.NoShape:
+ return False
+
+ self.curPiece = newPiece
+ self.curX = newX
+ self.curY = newY
+ self.update()
+
+ return True
+
+
+ def drawSquare(self, painter, x, y, shape):
+ """draws a square of a shape"""
+
+ colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
+ 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
+
+ color = QColor(colorTable[shape])
+ painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,
+ self.squareHeight() - 2, color)
+
+ painter.setPen(color.lighter())
+ painter.drawLine(x, y + self.squareHeight() - 1, x, y)
+ painter.drawLine(x, y, x + self.squareWidth() - 1, y)
+
+ painter.setPen(color.darker())
+ painter.drawLine(x + 1, y + self.squareHeight() - 1,
+ x + self.squareWidth() - 1, y + self.squareHeight() - 1)
+ painter.drawLine(x + self.squareWidth() - 1,
+ y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)
+
+
+class Tetrominoe:
+
+ NoShape = 0
+ ZShape = 1
+ SShape = 2
+ LineShape = 3
+ TShape = 4
+ SquareShape = 5
+ LShape = 6
+ MirroredLShape = 7
+
+
+class Shape:
+
+ coordsTable = (
+ ((0, 0), (0, 0), (0, 0), (0, 0)),
+ ((0, -1), (0, 0), (-1, 0), (-1, 1)),
+ ((0, -1), (0, 0), (1, 0), (1, 1)),
+ ((0, -1), (0, 0), (0, 1), (0, 2)),
+ ((-1, 0), (0, 0), (1, 0), (0, 1)),
+ ((0, 0), (1, 0), (0, 1), (1, 1)),
+ ((-1, -1), (0, -1), (0, 0), (0, 1)),
+ ((1, -1), (0, -1), (0, 0), (0, 1))
+ )
+
+ def __init__(self):
+
+ self.coords = [[0, 0] for i in range(4)]
+ self.pieceShape = Tetrominoe.NoShape
+
+ self.setShape(Tetrominoe.NoShape)
+
+
+ def shape(self):
+ """returns shape"""
+
+ return self.pieceShape
+
+
+ def setShape(self, shape):
+ """sets a shape"""
+
+ table = Shape.coordsTable[shape]
+
+ for i in range(4):
+ for j in range(2):
+ self.coords[i][j] = table[i][j]
+
+ self.pieceShape = shape
+
+
+ def setRandomShape(self):
+ """chooses a random shape"""
+
+ self.setShape(random.randint(1, 7))
+
+
+ def x(self, index):
+ """returns x coordinate"""
+
+ return self.coords[index][0]
+
+
+ def y(self, index):
+ """returns y coordinate"""
+
+ return self.coords[index][1]
+
+
+ def setX(self, index, x):
+ """sets x coordinate"""
+
+ self.coords[index][0] = x
+
+
+ def setY(self, index, y):
+ """sets y coordinate"""
+
+ self.coords[index][1] = y
+
+
+ def minX(self):
+ """returns min x value"""
+
+ m = self.coords[0][0]
+ for i in range(4):
+ m = min(m, self.coords[i][0])
+
+ return m
+
+
+ def maxX(self):
+ """returns max x value"""
+
+ m = self.coords[0][0]
+ for i in range(4):
+ m = max(m, self.coords[i][0])
+
+ return m
+
+
+ def minY(self):
+ """returns min y value"""
+
+ m = self.coords[0][1]
+ for i in range(4):
+ m = min(m, self.coords[i][1])
+
+ return m
+
+
+ def maxY(self):
+ """returns max y value"""
+
+ m = self.coords[0][1]
+ for i in range(4):
+ m = max(m, self.coords[i][1])
+
+ return m
+
+
+ def rotateLeft(self):
+ """rotates shape to the left"""
+
+ if self.pieceShape == Tetrominoe.SquareShape:
+ return self
+
+ result = Shape()
+ result.pieceShape = self.pieceShape
+
+ for i in range(4):
+ result.setX(i, self.y(i))
+ result.setY(i, -self.x(i))
+
+ return result
+
+
+ def rotateRight(self):
+ """rotates shape to the right"""
+
+ if self.pieceShape == Tetrominoe.SquareShape:
+ return self
+
+ result = Shape()
+ result.pieceShape = self.pieceShape
+
+ for i in range(4):
+ result.setX(i, -self.y(i))
+ result.setY(i, self.x(i))
+
+ return result
+
+
+def main():
+
+ app = QApplication([])
+ tetris = Tetris()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+
+The game is simplified a bit so that it is easier to understand. The game starts immediately after it is launched. We can pause the game by pressing the p key. The `Space` key will drop the tetris piece instantly to the bottom. The game goes at constant speed, no acceleration is implemented. The score is the number of lines that we have removed.
+
+```python
+self.tboard = Board(self)
+self.setCentralWidget(self.tboard)
+```
+An instance of the Board class is created and set to be the central widget of the application.
+
+```python
+self.statusbar = self.statusBar()
+self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
+```
+We create a statusbar where we will display messages. We will display three possible messages: the number of lines already removed, the paused message, or the game over message. The msg2Statusbar is a custom signal that is implemented in the Board class. The showMessage is a built-in method that displays a message on a statusbar.
+
+``` python
+self.tboard.start()
+```
+This line initiates the game.
+
+```python
+class Board(QFrame):
+
+ msg2Statusbar = pyqtSignal(str)
+...
+```
+A custom signal is created with `pyqtSignal`. The `msg2Statusbar` is a signal that is emitted when we want to write a message or the score to the statusbar.
+
+``` python
+BoardWidth = 10
+BoardHeight = 22
+Speed = 300
+```
+These are `Board`'s class variables. The `BoardWidth` and the `BoardHeight` define the size of the board in blocks. The `Speed` defines the speed of the game. Each 300 ms a new game cycle will start.
+
+``` python
+...
+self.curX = 0
+self.curY = 0
+self.numLinesRemoved = 0
+self.board = []
+...
+```
+In the `initBoard` method we initialize some important variables. The `self.board` variable is a list of numbers from 0 to 7. It represents the position of various shapes and remains of the shapes on the board.
+
+``` python
+def shapeAt(self, x, y):
+ """determines shape at the board position"""
+
+ return self.board[(y * Board.BoardWidth) + x]
+```
+The `shapeAt` method determines the type of a shape at a given block.
+
+```python
+def squareWidth(self):
+ """returns the width of one square"""
+
+ return self.contentsRect().width() // Board.BoardWidth
+```
+The board can be dynamically resized. As a consequence, the size of a block may change. The `squareWidth` calculates the width of the single square in pixels and returns it. The `Board.BoardWidth` is the size of the board in blocks.
+
+```python
+def pause(self):
+ """pauses game"""
+
+ if not self.isStarted:
+ return
+
+ self.isPaused = not self.isPaused
+
+ if self.isPaused:
+ self.timer.stop()
+ self.msg2Statusbar.emit("paused")
+
+ else:
+ self.timer.start(Board.Speed, self)
+ self.msg2Statusbar.emit(str(self.numLinesRemoved))
+
+ self.update()
+```
+The `pause` method pauses the game. It stops the timer and displays a message on the statusbar.
+
+```python
+def paintEvent(self, event):
+ """paints all shapes of the game"""
+
+ painter = QPainter(self)
+ rect = self.contentsRect()
+...
+```
+The painting happens in the `paintEvent` method. The `QPainter` is responsible for all low-level painting in PyQt6.
+
+```python
+for i in range(Board.BoardHeight):
+ for j in range(Board.BoardWidth):
+ shape = self.shapeAt(j, Board.BoardHeight - i - 1)
+
+ if shape != Tetrominoe.NoShape:
+ self.drawSquare(painter,
+ rect.left() + j * self.squareWidth(),
+ boardTop + i * self.squareHeight(), shape)
+```
+The painting of the game is divided into two steps. In the first step, we draw all the shapes, or remains of the shapes that have been dropped to the bottom of the board. All the squares are remembered in the `self.board` list variable. The variable is accessed using the `shapeAt` method.
+
+```python
+if self.curPiece.shape() != Tetrominoe.NoShape:
+
+ for i in range(4):
+
+ x = self.curX + self.curPiece.x(i)
+ y = self.curY - self.curPiece.y(i)
+ self.drawSquare(painter, rect.left() + x * self.squareWidth(),
+ boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
+ self.curPiece.shape())
+```
+The next step is the drawing of the actual piece that is falling down.
+
+```python
+elif key == Qt.Key.Key_Right.value:
+ self.tryMove(self.curPiece, self.curX + 1, self.curY)
+```
+In the `keyPressEvent` method we check for pressed keys. If we press the right arrow key, we try to move the piece to the right. We say try because the piece might not be able to move.
+
+```python
+elif key == Qt.Key.Key_Up.value:
+ self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
+```
+The `Up` arrow key will rotate the falling piece to the left.
+
+```python
+elif key == Qt.Key.Key_Space.value:
+ self.dropDown()
+```
+The `Space` key will drop the falling piece instantly to the bottom.
+
+```python
+elif key == Qt.Key.Key_D.value:
+ self.oneLineDown()
+```
+Pressing the d key, the piece will go one block down. It can be used to accellerate the falling of a piece a bit.
+
+```python
+def timerEvent(self, event):
+ """handles timer event"""
+
+ if event.timerId() == self.timer.timerId():
+
+ if self.isWaitingAfterLine:
+ self.isWaitingAfterLine = False
+ self.newPiece()
+ else:
+ self.oneLineDown()
+
+ else:
+ super(Board, self).timerEvent(event)
+```
+In the timer event, we either create a new piece after the previous one was dropped to the bottom or we move a falling piece one line down.
+
+```python
+def clearBoard(self):
+ """clears shapes from the board"""
+
+ for i in range(Board.BoardHeight * Board.BoardWidth):
+ self.board.append(Tetrominoe.NoShape)
+```
+The `clearBoard` method clears the board by setting `Tetrominoe.NoShape` at each block of the board.
+
+```python
+def removeFullLines(self):
+ """removes all full lines from the board"""
+
+ numFullLines = 0
+ rowsToRemove = []
+
+ for i in range(Board.BoardHeight):
+
+ n = 0
+ for j in range(Board.BoardWidth):
+ if not self.shapeAt(j, i) == Tetrominoe.NoShape:
+ n = n + 1
+
+ if n == 10:
+ rowsToRemove.append(i)
+
+ rowsToRemove.reverse()
+
+
+ for m in rowsToRemove:
+
+ for k in range(m, Board.BoardHeight):
+ for l in range(Board.BoardWidth):
+ self.setShapeAt(l, k, self.shapeAt(l, k + 1))
+
+ numFullLines = numFullLines + len(rowsToRemove)
+ ...
+```
+If the piece hits the bottom, we call the `removeFullLines` method. We find out all full lines and remove them. We do it by moving all lines above the current full line to be removed one line down. Notice that we reverse the order of the lines to be removed. Otherwise, it would not work correctly. In our case we use a `naive gravity`. This means that the pieces may be floating above empty gaps.
+
+```python
+def newPiece(self):
+ """creates a new shape"""
+
+ self.curPiece = Shape()
+ self.curPiece.setRandomShape()
+ self.curX = Board.BoardWidth // 2 + 1
+ self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
+
+ if not self.tryMove(self.curPiece, self.curX, self.curY):
+
+ self.curPiece.setShape(Tetrominoe.NoShape)
+ self.timer.stop()
+ self.isStarted = False
+ self.msg2Statusbar.emit("Game over")
+```
+The `newPiece` method creates randomly a new tetris piece. If the piece cannot go into its initial position, the game is over.
+
+``` python
+def tryMove(self, newPiece, newX, newY):
+ """tries to move a shape"""
+
+ for i in range(4):
+
+ x = newX + newPiece.x(i)
+ y = newY - newPiece.y(i)
+
+ if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
+ return False
+
+ if self.shapeAt(x, y) != Tetrominoe.NoShape:
+ return False
+
+ self.curPiece = newPiece
+ self.curX = newX
+ self.curY = newY
+ self.update()
+
+ return True
+```
+In the `tryMove` method we try to move our shapes. If the shape is at the edge of the board or is adjacent to some other piece, we return `False`. Otherwise we place the current falling piece to a new position.
+
+``` python
+class Tetrominoe:
+
+ NoShape = 0
+ ZShape = 1
+ SShape = 2
+ LineShape = 3
+ TShape = 4
+ SquareShape = 5
+ LShape = 6
+ MirroredLShape = 7
+```
+The `Tetrominoe` class holds names of all possible shapes. We have also a `NoShape` for an empty space.
+
+The Shape class saves information about a tetris piece.
+
+``` python
+class Shape(object):
+
+ coordsTable = (
+ ((0, 0), (0, 0), (0, 0), (0, 0)),
+ ((0, -1), (0, 0), (-1, 0), (-1, 1)),
+ ...
+ )
+...
+```
+The `coordsTable` tuple holds all possible coordinate values of our tetris pieces. This is a template from which all pieces take their coordinate values.
+
+```python
+self.coords = [[0,0] for i in range(4)]
+```
+Upon creation we create an empty coordinates list. The list will save the coordinates of the tetris piece.
+
+![Coordinates](./images/coordinates.png)
+
+Figure: Coordinates
+
+The above image will help understand the coordinate values a bit more. For example, the tuples (0, -1), (0, 0), (-1, 0), (-1, -1) represent a Z-shape. The diagram illustrates the shape.
+
+``` python
+def rotateLeft(self):
+ """rotates shape to the left"""
+
+ if self.pieceShape == Tetrominoe.SquareShape:
+ return self
+
+ result = Shape()
+ result.pieceShape = self.pieceShape
+
+ for i in range(4):
+
+ result.setX(i, self.y(i))
+ result.setY(i, -self.x(i))
+
+ return result
+```
+The `rotateLeft` method rotates a piece to the left. The square does not have to be rotated. That is why we simply return the reference to the current object. A new piece is created and its coordinates are set to the ones of the rotated piece.
+
+![Tetris](./images/tetris.png)
+
+Figure: Tetris
+
+This was Tetris game in PyQt6.
\ No newline at end of file
diff --git a/original/pyqt6/widgets.md b/original/pyqt6/widgets.md
new file mode 100644
index 0000000..d1aa37e
--- /dev/null
+++ b/original/pyqt6/widgets.md
@@ -0,0 +1,528 @@
+# PyQt6 widgets
+*last modified May 3, 2021*
+
+Widgets are basic building blocks of an application. PyQt6 has a wide range of various widgets, including buttons, check boxes, sliders, or list boxes. In this section of the tutorial, we describe several useful widgets: a QCheckBox, a QPushButton in tooggle mode, a QSlider, a QProgressBar, and a QCalendarWidget.
+
+## PyQt6 QCheckBox
+`QCheckBox` is a widget that has two states: on and off. It is a box with a label. Checkboxes are typically used to represent features in an application that can be enabled or disabled.
+
+``` python
+# file: check_box.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, a QCheckBox widget
+is used to toggle the title of a window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QCheckBox, QApplication
+from PyQt6.QtCore import Qt
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ cb = QCheckBox('Show title', self)
+ cb.move(20, 20)
+ cb.toggle()
+ cb.stateChanged.connect(self.changeTitle)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('QCheckBox')
+ self.show()
+
+
+ def changeTitle(self, state):
+
+ if state == Qt.CheckState.Checked.value:
+ self.setWindowTitle('QCheckBox')
+ else:
+ self.setWindowTitle(' ')
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+We create a checkbox that toggles the window title.
+``` python
+cb = QCheckBox('Show title', self)
+```
+This is a `QCheckBox` constructor.
+
+``` python
+cb.toggle()
+```
+We have set the window title, so we also check the checkbox.
+``` python
+cb.stateChanged.connect(self.changeTitle)
+```
+We connect the user defined `changeTitle` method to the stateChanged signal. The `changeTitle` method toggles the window title.
+``` python
+if state == Qt.CheckState.Checked.value:
+ self.setWindowTitle('QCheckBox')
+else:
+ self.setWindowTitle(' ')
+```
+The state of the widget is given to the `changeTitle` method in the state variable. If the widget is checked, we set a title of the window. Otherwise, we set an empty string to the titlebar.
+
+![QCheckBox](./images/qcheckbox.png)
+
+Figure: QCheckBox
+
+## Toggle button
+A toggle button is a QPushButton in a special mode. It is a button that has two states: pressed and not pressed. We toggle between these two states by clicking on it.
+
+``` python
+# file: toggle_button.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create three toggle buttons.
+They control the background color of a QFrame.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QPushButton,
+ QFrame, QApplication)
+from PyQt6.QtGui import QColor
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.col = QColor(0, 0, 0)
+
+ redb = QPushButton('Red', self)
+ redb.setCheckable(True)
+ redb.move(10, 10)
+
+ redb.clicked[bool].connect(self.setColor)
+
+ greenb = QPushButton('Green', self)
+ greenb.setCheckable(True)
+ greenb.move(10, 60)
+
+ greenb.clicked[bool].connect(self.setColor)
+
+ blueb = QPushButton('Blue', self)
+ blueb.setCheckable(True)
+ blueb.move(10, 110)
+
+ blueb.clicked[bool].connect(self.setColor)
+
+ self.square = QFrame(self)
+ self.square.setGeometry(150, 20, 100, 100)
+ self.square.setStyleSheet("QWidget { background-color: %s }" %
+ self.col.name())
+
+ self.setGeometry(300, 300, 300, 250)
+ self.setWindowTitle('Toggle button')
+ self.show()
+
+
+ def setColor(self, pressed):
+
+ source = self.sender()
+
+ if pressed:
+ val = 255
+ else:
+ val = 0
+
+ if source.text() == "Red":
+ self.col.setRed(val)
+ elif source.text() == "Green":
+ self.col.setGreen(val)
+ else:
+ self.col.setBlue(val)
+
+ self.square.setStyleSheet("QFrame { background-color: %s }" %
+ self.col.name())
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we create three toggle buttons and a `QWidget`. We set the background colour of the `QWidget` to black. The toggle buttons toggle the red, green, and blue parts of the colour value. The background colour depends on which toggle buttons is pressed.
+
+``` python
+self.col = QColor(0, 0, 0)
+```
+This is the initial, black colour value.
+``` python
+redb = QPushButton('Red', self)
+redb.setCheckable(True)
+redb.move(10, 10)
+```
+To create a toggle button, we create a QPushButton and make it checkable by calling the `setCheckable` method.
+``` python
+redb.clicked[bool].connect(self.setColor)
+```
+We connect a clicked signal to our user defined method. We use the clicked signal that operates with a Boolean value.
+``` python
+source = self.sender()
+```
+We get the button which was toggled.
+``` python
+if source.text() == "Red":
+ self.col.setRed(val)
+```
+In case it is a red button, we update the red part of the colour accordingly.
+``` python
+self.square.setStyleSheet("QFrame { background-color: %s }" %
+ self.col.name())
+```
+We use style sheets to change the background colour. The stylesheet is updated with `setStyleSheet` method.
+
+![Toggle button](./images/togglebutton.png)
+
+Figure: Toggle button
+
+## PyQt6 QSlider
+A QSlider is a widget that has a simple handle. This handle can be pulled back and forth. This way we are choosing a value for a specific task. Sometimes using a slider is more natural than entering a number or using a spin box.
+
+In our example we show one slider and one label. The label displays an image. The slider controls the label.
+
+``` python
+# file: slider.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows a QSlider widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QSlider,
+ QLabel, QApplication)
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QPixmap
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ sld = QSlider(Qt.Orientations.Horizontal, self)
+ sld.setFocusPolicy(Qt.FocusPolicy.NoFocus)
+ sld.setGeometry(30, 40, 200, 30)
+ sld.valueChanged[int].connect(self.changeValue)
+
+ self.label = QLabel(self)
+ self.label.setPixmap(QPixmap('mute.png'))
+ self.label.setGeometry(250, 40, 80, 30)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('QSlider')
+ self.show()
+
+
+ def changeValue(self, value):
+
+ if value == 0:
+
+ self.label.setPixmap(QPixmap('mute.png'))
+ elif 0 < value <= 30:
+
+ self.label.setPixmap(QPixmap('min.png'))
+ elif 30 < value < 80:
+
+ self.label.setPixmap(QPixmap('med.png'))
+ else:
+
+ self.label.setPixmap(QPixmap('max.png'))
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example we simulate a volume control. By dragging the handle of a slider, we change an image on the label.
+
+``` python
+sld = QSlider(Qt.Orientations.Horizontal, self)
+```
+
+Here we create a horizontal QSlider.
+``` python
+self.label = QLabel(self)
+self.label.setPixmap(QPixmap('mute.png'))
+```
+We create a QLabel widget and set an initial mute image to it.
+``` python
+sld.valueChanged[int].connect(self.changeValue)
+```
+We connect the valueChanged signal to the user defined changeValue method.
+``` python
+if value == 0:
+ self.label.setPixmap(QPixmap('mute.png'))
+...
+```
+Based on the value of the slider, we set an image to the label. In the above code, we set the mute.png image to the label if the slider is equal to zero.
+
+![QSlider widget](./images/qslider.png)
+
+Figure: QSlider widget
+
+## PyQt6 QProgressBar
+
+A progress bar is a widget that is used when we process lengthy tasks. It is animated so that the user knows that the task is progressing. The QProgressBar widget provides a horizontal or a vertical progress bar in PyQt6 toolkit. The programmer can set the minimum and maximum value for the progress bar. The default values are 0 and 99.
+
+``` python
+# file: progressbar.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows a QProgressBar widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QProgressBar,
+ QPushButton, QApplication)
+from PyQt6.QtCore import QBasicTimer
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.pbar = QProgressBar(self)
+ self.pbar.setGeometry(30, 40, 200, 25)
+
+ self.btn = QPushButton('Start', self)
+ self.btn.move(40, 80)
+ self.btn.clicked.connect(self.doAction)
+
+ self.timer = QBasicTimer()
+ self.step = 0
+
+ self.setGeometry(300, 300, 280, 170)
+ self.setWindowTitle('QProgressBar')
+ self.show()
+
+
+ def timerEvent(self, e):
+
+ if self.step >= 100:
+
+ self.timer.stop()
+ self.btn.setText('Finished')
+ return
+
+ self.step = self.step + 1
+ self.pbar.setValue(self.step)
+
+
+ def doAction(self):
+
+ if self.timer.isActive():
+ self.timer.stop()
+ self.btn.setText('Start')
+ else:
+ self.timer.start(100, self)
+ self.btn.setText('Stop')
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example we have a horizontal progress bar and a push button. The push button starts and stops the progress bar.
+``` python
+self.pbar = QProgressBar(self)
+```
+This is a QProgressBar constructor.
+``` python
+self.timer = QBasicTimer()
+```
+To activate the progress bar, we use a timer object.
+``` python
+self.timer.start(100, self)
+```
+To launch a timer event, we call its start method. This method has two parameters: the timeout and the object which receive the events.
+``` python
+def timerEvent(self, e):
+
+ if self.step >= 100:
+
+ self.timer.stop()
+ self.btn.setText('Finished')
+ return
+
+ self.step = self.step + 1
+ self.pbar.setValue(self.step)
+```
+Each `QObject` and its descendants have a timerEvent event handler. In order to react to timer events, we reimplement the event handler.
+``` python
+def doAction(self):
+
+ if self.timer.isActive():
+ self.timer.stop()
+ self.btn.setText('Start')
+
+ else:
+ self.timer.start(100, self)
+ self.btn.setText('Stop')
+```
+Inside the doAction method, we start and stop the timer.
+
+![QProgressBar](./images/qprogressbar.png)
+
+Figure: QProgressBar
+
+## PyQt6 QCalendarWidget
+A QCalendarWidget provides a monthly based calendar widget. It allows a user to select a date in a simple and intuitive way.
+
+``` python
+# file: calendar.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows a QCalendarWidget widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QCalendarWidget,
+ QLabel, QApplication, QVBoxLayout)
+from PyQt6.QtCore import QDate
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ vbox = QVBoxLayout(self)
+
+ cal = QCalendarWidget(self)
+ cal.setGridVisible(True)
+ cal.clicked[QDate].connect(self.showDate)
+
+ vbox.addWidget(cal)
+
+ self.lbl = QLabel(self)
+ date = cal.selectedDate()
+ self.lbl.setText(date.toString())
+
+ vbox.addWidget(self.lbl)
+
+ self.setLayout(vbox)
+
+ self.setGeometry(300, 300, 350, 300)
+ self.setWindowTitle('Calendar')
+ self.show()
+
+
+ def showDate(self, date):
+ self.lbl.setText(date.toString())
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+The example has a calendar widget and a label widget. The currently selected date is displayed in the label widget.
+``` python
+cal = QCalendarWidget(self)
+```
+The QCalendarWidget is created.
+``` python
+cal.clicked[QDate].connect(self.showDate)
+```
+If we select a date from the widget, a clicked[QDate] signal is emitted. We connect this signal to the user defined showDate method.
+``` python
+def showDate(self, date):
+
+ self.lbl.setText(date.toString())
+```
+We retrieve the selected date by calling the selectedDate method. Then we transform the date object into string and set it to the label widget.
+
+In this part of the PyQt6 tutorial, we have covered the following widgets: QCheckBox, `QPushButton` in tooggle mode, `QSlider`, `QProgressBar`, and `QCalendarWidget`.
\ No newline at end of file
diff --git a/original/pyqt6/widgets2.md b/original/pyqt6/widgets2.md
new file mode 100644
index 0000000..2f34edc
--- /dev/null
+++ b/original/pyqt6/widgets2.md
@@ -0,0 +1,346 @@
+# PyQt6 widgets II
+*last modified May 5, 2021*
+
+In this chapter we continue introducing PyQt6 widgets. We cover `QPixmap`, `QLineEdit`, `QSplitter`, and `QComboBox`.
+
+## PyQt6 QPixmap
+A QPixmap is one of the widgets used to work with images. It is optimized for showing images on screen. In our code example, we will use the QPixmap to display an image on the window.
+
+``` python
+# file: pixmap.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we display an image
+on the window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QHBoxLayout,
+ QLabel, QApplication)
+from PyQt6.QtGui import QPixmap
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ hbox = QHBoxLayout(self)
+ pixmap = QPixmap('sid.jpg')
+
+ lbl = QLabel(self)
+ lbl.setPixmap(pixmap)
+
+ hbox.addWidget(lbl)
+ self.setLayout(hbox)
+
+ self.move(300, 200)
+ self.setWindowTitle('Sid')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we display an image on the window.
+``` python
+pixmap = QPixmap('sid.jpg')
+```
+We create a QPixmap object. It takes the name of the file as a parameter.
+``` python
+lbl = QLabel(self)
+lbl.setPixmap(pixmap)
+```
+We put the pixmap into the QLabel widget.
+
+## PyQt6 QLineEdit
+QLineEdit is a widget that allows to enter and edit a single line of plain text. There are undo and redo, cut and paste, and drag & drop functions available for the widget.
+
+``` python
+# file: line_edit.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows text which
+is entered in a QLineEdit
+in a QLabel widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QLabel,
+ QLineEdit, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.lbl = QLabel(self)
+ qle = QLineEdit(self)
+
+ qle.move(60, 100)
+ self.lbl.move(60, 40)
+
+ qle.textChanged[str].connect(self.onChanged)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('QLineEdit')
+ self.show()
+
+
+ def onChanged(self, text):
+
+ self.lbl.setText(text)
+ self.lbl.adjustSize()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+This example shows a line edit widget and a label. The text that we key in the line edit is displayed immediately in the label widget.
+``` python
+qle = QLineEdit(self)
+```
+The QLineEdit widget is created.
+``` python
+qle.textChanged[str].connect(self.onChanged)
+```
+If the text in the line edit widget changes, we call the onChanged method.
+``` python
+def onChanged(self, text):
+
+ self.lbl.setText(text)
+ self.lbl.adjustSize()
+```
+Inside the onChanged method, we set the typed text to the label widget. We call the adjustSize method to adjust the size of the label to the length of the text.
+
+![QLineEdit](./images/qlineedit.png)
+
+Figure: QLineEdit
+
+## PyQt6 QSplitter
+QSplitter lets the user control the size of child widgets by dragging the boundary between its children. In our example, we show three QFrame widgets organized with two splitters.
+``` python
+# file: splitter.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows
+how to use QSplitter widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import (QWidget, QHBoxLayout, QFrame,
+ QSplitter, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ hbox = QHBoxLayout(self)
+
+ topleft = QFrame(self)
+ topleft.setFrameShape(QFrame.Shape.StyledPanel)
+
+ topright = QFrame(self)
+ topright.setFrameShape(QFrame.Shape.StyledPanel)
+
+ bottom = QFrame(self)
+ bottom.setFrameShape(QFrame.Shape.StyledPanel)
+
+ splitter1 = QSplitter(Qt.Orientations.Horizontal)
+ splitter1.addWidget(topleft)
+ splitter1.addWidget(topright)
+
+ splitter2 = QSplitter(Qt.Orientations.Vertical)
+ splitter2.addWidget(splitter1)
+ splitter2.addWidget(bottom)
+
+ hbox.addWidget(splitter2)
+ self.setLayout(hbox)
+
+ self.setGeometry(300, 300, 450, 400)
+ self.setWindowTitle('QSplitter')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+In our example, we have three frame widgets and two splitters. Note that under some themes, the splitters may not be visible very well.
+``` python
+topleft = QFrame(self)
+topleft.setFrameShape(QFrame.Shape.StyledPanel)
+```
+We use a styled frame in order to see the boundaries between the QFrame widgets.
+``` python
+splitter1 = QSplitter(Qt.Orientations.Horizontal)
+splitter1.addWidget(topleft)
+splitter1.addWidget(topright)
+```
+We create a QSplitter widget and add two frames into it.
+``` python
+splitter2 = QSplitter(Qt.Orientations.Vertical)
+splitter2.addWidget(splitter1)
+```
+We can also add a splitter to another splitter widget.
+
+![QSplitter widget](./images/qsplitter.png)
+
+Figure: QSplitter widget
+
+## PyQt6 QComboBox
+`QComboBox` is a widget that allows a user to choose from a list of options.
+
+``` python
+# file: combobox.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows how to use
+a QComboBox widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtWidgets import (QWidget, QLabel,
+ QComboBox, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.lbl = QLabel('Ubuntu', self)
+
+ combo = QComboBox(self)
+
+ combo.addItem('Ubuntu')
+ combo.addItem('Mandriva')
+ combo.addItem('Fedora')
+ combo.addItem('Arch')
+ combo.addItem('Gentoo')
+
+ combo.move(50, 50)
+ self.lbl.move(50, 150)
+
+ combo.textActivated[str].connect(self.onActivated)
+
+ self.setGeometry(300, 300, 450, 400)
+ self.setWindowTitle('QComboBox')
+ self.show()
+
+
+ def onActivated(self, text):
+
+ self.lbl.setText(text)
+ self.lbl.adjustSize()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+The example shows a QComboBox and a QLabel. The combo box has a list of five options. These are the names of Linux distros. The label widget displays the selected option from the combo box.
+
+``` python
+combo = QComboBox(self)
+
+combo.addItem('Ubuntu')
+combo.addItem('Mandriva')
+combo.addItem('Fedora')
+combo.addItem('Arch')
+combo.addItem('Gentoo')
+```
+We create a QComboBox widget with five options.
+``` python
+combo.textActivated[str].connect(self.onActivated)
+```
+Upon an item selection, we call the onActivated method.
+``` python
+def onActivated(self, text):
+
+ self.lbl.setText(text)
+ self.lbl.adjustSize()
+```
+Inside the method, we set the text of the chosen item to the label widget. We adjust the size of the label.
+
+![QComboBox](./images/qcombobox.png)
+
+Figure: QComboBox
+
+In this part of the PyQt6 tutorial, we have covered `QPixmap`, `QLineEdit`, `QSplitter`, and `QComboBox`.
\ No newline at end of file
diff --git a/SUMMARY.md b/translated/pyqt5/SUMMARY.md
similarity index 100%
rename from SUMMARY.md
rename to translated/pyqt5/SUMMARY.md
diff --git a/bu-ju-guan-li.md b/translated/pyqt5/bu-ju-guan-li.md
similarity index 100%
rename from bu-ju-guan-li.md
rename to translated/pyqt5/bu-ju-guan-li.md
diff --git a/cai-dan-he-gong-ju-lan.md b/translated/pyqt5/cai-dan-he-gong-ju-lan.md
similarity index 100%
rename from cai-dan-he-gong-ju-lan.md
rename to translated/pyqt5/cai-dan-he-gong-ju-lan.md
diff --git a/dui-hua-kuang.md b/translated/pyqt5/dui-hua-kuang.md
similarity index 100%
rename from dui-hua-kuang.md
rename to translated/pyqt5/dui-hua-kuang.md
diff --git a/e-luo-si-fang-kuai-you-xi.md b/translated/pyqt5/e-luo-si-fang-kuai-you-xi.md
similarity index 100%
rename from e-luo-si-fang-kuai-you-xi.md
rename to translated/pyqt5/e-luo-si-fang-kuai-you-xi.md
diff --git a/hello_world.md b/translated/pyqt5/hello_world.md
similarity index 100%
rename from hello_world.md
rename to translated/pyqt5/hello_world.md
diff --git a/hui-tu.md b/translated/pyqt5/hui-tu.md
similarity index 100%
rename from hui-tu.md
rename to translated/pyqt5/hui-tu.md
diff --git a/jie-shao.md b/translated/pyqt5/index.md
similarity index 100%
rename from jie-shao.md
rename to translated/pyqt5/index.md
diff --git a/kong-jian-1.md b/translated/pyqt5/kong-jian-1.md
similarity index 97%
rename from kong-jian-1.md
rename to translated/pyqt5/kong-jian-1.md
index f0ffbf5..45e4588 100644
--- a/kong-jian-1.md
+++ b/translated/pyqt5/kong-jian-1.md
@@ -1,10 +1,10 @@
# 控件\(1\)
-控件就像是应用这座房子的一块块砖。PyQt5有很多的控件,比如按钮,单选框,滑动条,复选框等等。在本章,我们将介绍一些很有用的控件:`QCheckBox`,`ToggleButton`,`QSlider`,`QProgressBar`和`QCalendarWidget`。
+控件就像是应用这座房子的一块块砖。PyQt5有很多的控件,比如按钮,单选框,滑动条,复选框等等。在本章,我们将介绍一些很有用的控件:`QCheckBox`,`ToggleButton`,`QSlider`,`QProgressBar` 和 `QCalendarWidget`。
## QCheckBox
-`QCheckBox`组件有俩状态:开和关。通常跟标签一起使用,用在激活和关闭一些选项的场景。
+`QCheckBox` 组件有俩状态:开和关。通常跟标签一起使用,用在激活和关闭一些选项的场景。
```python
#!/usr/bin/python3
@@ -97,7 +97,7 @@ def changeTitle(self, state):
## 切换按钮
-切换按钮就是`QPushButton`的一种特殊模式。 它只有两种状态:按下和未按下。我们再点击的时候切换两种状态,有很多场景会使用到这个功能。
+切换按钮就是`QPushButton`的一种特殊模式。 它只有两种状态:按下和未按下。我们在点击的时候切换两种状态,有很多场景会使用到这个功能。
```python
#!/usr/bin/python3
diff --git a/kong-jian-2.md b/translated/pyqt5/kong-jian-2.md
similarity index 100%
rename from kong-jian-2.md
rename to translated/pyqt5/kong-jian-2.md
diff --git a/shi-jian-he-xin-hao.md b/translated/pyqt5/shi-jian-he-xin-hao.md
similarity index 100%
rename from shi-jian-he-xin-hao.md
rename to translated/pyqt5/shi-jian-he-xin-hao.md
diff --git a/tuo-zhuai.md b/translated/pyqt5/tuo-zhuai.md
similarity index 100%
rename from tuo-zhuai.md
rename to translated/pyqt5/tuo-zhuai.md
diff --git a/zi-ding-yi-zu-jian.md b/translated/pyqt5/zi-ding-yi-zu-jian.md
similarity index 100%
rename from zi-ding-yi-zu-jian.md
rename to translated/pyqt5/zi-ding-yi-zu-jian.md
diff --git a/translated/pyqt6/customwidgets.md b/translated/pyqt6/customwidgets.md
new file mode 100644
index 0000000..55d7378
--- /dev/null
+++ b/translated/pyqt6/customwidgets.md
@@ -0,0 +1,224 @@
+# PyQt6 自定义部件
+*最后更新于 2021.05.17*
+
+PyQt6 已经有丰富的部件,但是没有任何工具包能提供开发者开发应用中需要的全部部件。工具包通常只提供最常见的小部件,如按钮、文本小部件或滑块。如果需要满足特定需求的小部件,我们必须自己创建。
+
+自定义小部件是使用工具包提供的绘图工具创建的。基本上有两种方式:程序员可以修改或增强现有的小部件,或者他可以从头开始创建自定义小部件。
+
+## PyQt6 烧录部件
+这个部件可以在 Nero、K3B 或其他的 CD/DVD 烧录软件里看到。
+
+``` python
+# file: burning_widget.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create a custom widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QSlider, QApplication,
+ QHBoxLayout, QVBoxLayout)
+from PyQt6.QtCore import QObject, Qt, pyqtSignal
+from PyQt6.QtGui import QPainter, QFont, QColor, QPen
+import sys
+
+
+class Communicate(QObject):
+ updateBW = pyqtSignal(int)
+
+
+class BurningWidget(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setMinimumSize(1, 30)
+ self.value = 75
+ self.num = [75, 150, 225, 300, 375, 450, 525, 600, 675]
+
+
+ def setValue(self, value):
+
+ self.value = value
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawWidget(qp)
+ qp.end()
+
+
+ def drawWidget(self, qp):
+
+ MAX_CAPACITY = 700
+ OVER_CAPACITY = 750
+
+ font = QFont('Serif', 7, QFont.Weight.Light)
+ qp.setFont(font)
+
+ size = self.size()
+ w = size.width()
+ h = size.height()
+
+ step = int(round(w / 10))
+
+ till = int(((w / OVER_CAPACITY) * self.value))
+ full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
+
+ if self.value >= MAX_CAPACITY:
+
+ qp.setPen(QColor(255, 255, 255))
+ qp.setBrush(QColor(255, 255, 184))
+ qp.drawRect(0, 0, full, h)
+ qp.setPen(QColor(255, 175, 175))
+ qp.setBrush(QColor(255, 175, 175))
+ qp.drawRect(full, 0, till - full, h)
+
+ else:
+
+ qp.setPen(QColor(255, 255, 255))
+ qp.setBrush(QColor(255, 255, 184))
+ qp.drawRect(0, 0, till, h)
+
+ pen = QPen(QColor(20, 20, 20), 1,
+ Qt.PenStyle.SolidLine)
+
+ qp.setPen(pen)
+ qp.setBrush(Qt.BrushStyle.NoBrush)
+ qp.drawRect(0, 0, w - 1, h - 1)
+
+ j = 0
+
+ for i in range(step, 10 * step, step):
+
+ qp.drawLine(i, 0, i, 5)
+ metrics = qp.fontMetrics()
+ fw = metrics.horizontalAdvance(str(self.num[j]))
+
+ x, y = int(i - fw/2), int(h / 2)
+ qp.drawText(x, y, str(self.num[j]))
+ j = j + 1
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ OVER_CAPACITY = 750
+
+ sld = QSlider(Qt.Orientations.Horizontal, self)
+ sld.setFocusPolicy(Qt.FocusPolicy.NoFocus)
+ sld.setRange(1, OVER_CAPACITY)
+ sld.setValue(75)
+ sld.setGeometry(30, 40, 150, 30)
+
+ self.c = Communicate()
+ self.wid = BurningWidget()
+ self.c.updateBW[int].connect(self.wid.setValue)
+
+ sld.valueChanged[int].connect(self.changeValue)
+ hbox = QHBoxLayout()
+ hbox.addWidget(self.wid)
+ vbox = QVBoxLayout()
+ vbox.addStretch(1)
+ vbox.addLayout(hbox)
+ self.setLayout(vbox)
+
+ self.setGeometry(300, 300, 390, 210)
+ self.setWindowTitle('Burning widget')
+ self.show()
+
+
+ def changeValue(self, value):
+
+ self.c.updateBW.emit(value)
+ self.wid.repaint()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+这个示例中,有一个 `QSlider` 和一个自定义小部件——滑块控制自定义小部件。此小部件以图形方式显示介质的总容量和可用的可用空间。自定义小部件的最小值为 1,最大值为 `OVER_CAPACITY`。 如果值达到 `MAX_CAPACITY` ,会变成红色,代表需要烧录的数据大于介质的容量。
+
+烧录部件位于窗口底部。用 `QHBoxLayout` 和 `QVBoxLayout` 实现。
+
+```python
+class BurningWidget(QWidget):
+
+ def __init__(self):
+ super().__init__()
+```
+烧录组件基于 `QWidget`。
+
+```python
+self.setMinimumSize(1, 30)
+```
+设置部件的高,默认的高度有点小。
+```python
+font = QFont('Serif', 7, QFont.Weight.Light)
+qp.setFont(font)
+```
+这里使用了较小的字体大小,这样看起来更适合我们的需求。
+
+```python
+size = self.size()
+w = size.width()
+h = size.height()
+
+step = int(round(w / 10))
+
+
+till = int(((w / OVER_CAPACITY) * self.value))
+full = int(((w / OVER_CAPACITY) * MAX_CAPACITY))
+```
+部件是动态渲染的。窗口越大,部件就越大,反之亦然。所以我们需要动态计算部件的大小必须计算在其上绘制自定义小部件的小部件的大小。参数 `till` 决定了部件的总大小,这个值来自于滑块部件,它是相对整个区域的一个比例。参数 `full` 是红色色块的起点。
+
+绘图包括三个步骤,先绘制有黄色或红色和黄色的矩形,然后绘制垂直线,将小部件分成几个部分,最后画出表示介质容量的数字。
+
+``` python
+metrics = qp.fontMetrics()
+fw = metrics.horizontalAdvance(str(self.num[j]))
+
+x, y = int(i - fw/2), int(h / 2)
+qp.drawText(x, y, str(self.num[j]))
+```
+我们使用字体材料来绘制文本,所以必须知道文本的宽度才能使其垂直居中。
+
+``` python
+def changeValue(self, value):
+
+ self.c.updateBW.emit(value)
+ self.wid.repaint()
+```
+移动滑块时,调用 `changeValue` 方法。在方法内部,触发一个带有参数的自定义 `updateBW` 信号,参数是滑块的当前值,这个值也要用于计算要绘制的 `Burning` 小部件的容量,这样,这个部件就绘制出来了。
+
+![The burning widget](./images/burning.png)
+
+图像: 烧录组件
+
+本章的 PyQt6 教程里,我们创建了一个自定义部件。
\ No newline at end of file
diff --git a/translated/pyqt6/datetime.md b/translated/pyqt6/datetime.md
new file mode 100644
index 0000000..1d0e80a
--- /dev/null
+++ b/translated/pyqt6/datetime.md
@@ -0,0 +1,359 @@
+# PyQt6 日期和时间
+*最后更新于 2021.04.23*
+
+## QDate, QTime, QDateTime
+
+PyQt6 有 `QDate`, `QDateTime`, `QTime` 类处理日期和时间。`QDate` 是用于处理公历中的日期的类。它有获取、比较或操作日期的方法。`QTime` 类用于处理时间。它提供了比较时间、确定时间和其他各种时间操作方法。`QDateTime` 是 `QDate` 和 `QTime` 的组合。
+
+## PyQt 当前日期和时间
+
+PyQt6 有 `currentDate`、`currentTime` 和 `currentDateTime` 方法获取当前的日期或时间。
+
+``` python
+# file: current_date_time.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate, QTime, QDateTime, Qt
+
+now = QDate.currentDate()
+
+print(now.toString(Qt.DateFormat.ISODate))
+print(now.toString(Qt.DateFormat.RFC2822Date))
+
+datetime = QDateTime.currentDateTime()
+
+print(datetime.toString())
+
+time = QTime.currentTime()
+print(time.toString(Qt.DateFormat.ISODate))
+```
+
+上面的代码打印出了当前日期,当前日期和时间,不同格式的时间。
+
+``` python
+now = QDate.currentDate()
+```
+
+`currentDate` 方法返回当前的日期。
+
+``` python
+print(now.toString(Qt.DateFormat.ISODate))
+print(now.toString(Qt.DateFormat.RFC2822Date))
+```
+给 `toString` 传入不同的参数: `Qt.DateFormat.ISODate` 和 `Qt.DateFormat.RFC2822Date` 获取不同格式的日期。
+
+```python
+datetime = QDateTime.currentDateTime()
+```
+
+`currentDateTime` 方法返回当前的日期和时间。
+
+``` python
+time = QTime.currentTime()
+```
+
+`currentTime` 方法返回了当前时间。
+
+``` sh
+$ ./current_date_time.py
+2021-04-23
+23 Apr 2021
+Fri Apr 23 11:41:39 2021
+11:41:39
+```
+## PyQt6 UTC 时间
+我们的星球是一个球体,绕着它自己的轴旋转。地球向东旋转,所以太阳在不同的时间在不同的地点升起。地球大约每24小时自转一次。因此,世界被划分为24个时区。在每个时区,都有不同的当地时间。当地时间通常会被夏时制进一步修改。
+
+实际上也需要一个标准时间。一个标准时间有助于避免时区和夏令时的混淆。选择UTC(通用协调时间)作为主要的时间标准。UTC时间用于航空、天气预报、飞行计划、空中交通管制许可和地图。与当地时间不同,UTC时间不随季节变化而变化。
+
+``` python
+# file: utc_local.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDateTime, Qt
+
+now = QDateTime.currentDateTime()
+
+print('Local datetime: ', now.toString(Qt.DateFormat.ISODate))
+print('Universal datetime: ', now.toUTC().toString(Qt.DateFormat.ISODate))
+
+print(f'The offset from UTC is: {now.offsetFromUtc()} seconds')
+本例获取了标准时间和本地时间。
+
+print('Local datetime: ', now.toString(Qt.DateFormat.ISODate))
+```
+`currentDateTime` 方法返回了本地时间的当前时间。我们可以使用 `toLocalTime` 方法把标准时间转换成本地时间。
+
+``` python
+print('Universal datetime: ', now.toUTC().toString(Qt.DateFormat.ISODate))
+```
+
+我们使用 `toUTC` 方法从时间对象里获取了标准时间。
+
+``` python
+print(f'The offset from UTC is: {now.offsetFromUtc()} seconds')
+```
+
+`offsetFromUtc` 方法给出了本地时间与标准时间的差,以秒为单位。
+
+``` sh
+$ ./utc_local.py
+Local datetime: 2021-04-23T11:44:15
+Universal datetime: 2021-04-23T09:44:15Z
+The offset from UTC is: 7200 seconds
+```
+## PyQt6 天数
+`daysInMonth` 方法返回了指定月份的天数,`daysInYear` 方法返回了指定年份的天数。
+
+``` python
+# file: n_of_days.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate
+
+now = QDate.currentDate()
+
+d = QDate(1945, 5, 7)
+
+print(f'Days in month: {d.daysInMonth()}')
+print(f'Days in year: {d.daysInYear()}')
+```
+本例打印了指定年份和月份的天数。
+``` sh
+$ ./n_of_days.py
+Days in month: 31
+Days in year: 365
+```
+
+## PyQt6 天数差
+`daysTo` 方法返回了一个日期到另外一个日期的差。
+
+``` python
+# file: xmas.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate, Qt
+
+now = QDate.currentDate()
+y = now.year()
+
+print(f'today is {now.toString(Qt.DateFormat.ISODate)}')
+
+xmas1 = QDate(y-1, 12, 25)
+xmas2 = QDate(y, 12, 25)
+
+dayspassed = xmas1.daysTo(now)
+print(f'{dayspassed} days have passed since last XMas')
+
+nofdays = now.daysTo(xmas2)
+print(f'There are {nofdays} days until next XMas')
+```
+该示例计算出了从上一个圣诞节到下一个圣诞节的天数。
+
+``` sh
+$ ./xmas.py
+today is 2021-04-23
+119 days have passed since last XMas
+There are 246 days until next XMas
+```
+## PyQt6 时间的计算
+我们经常需要对天,秒或者年进行加减等计算。
+``` python
+# file: arithmetic.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDateTime, Qt
+
+now = QDateTime.currentDateTime()
+
+print(f'Today: {now.toString(Qt.DateFormat.ISODate)}')
+print(f'Adding 12 days: {now.addDays(12).toString(Qt.DateFormat.ISODate)}')
+print(f'Subtracting 22 days: {now.addDays(-22).toString(Qt.DateFormat.ISODate)}')
+
+print(f'Adding 50 seconds: {now.addSecs(50).toString(Qt.DateFormat.ISODate)}')
+print(f'Adding 3 months: {now.addMonths(3).toString(Qt.DateFormat.ISODate)}')
+print(f'Adding 12 years: {now.addYears(12).toString(Qt.DateFormat.ISODate)}')
+```
+该示例展示了对当前日期时间添加或减去天、秒、月或年。
+
+``` sh
+$ ./arithmetic.py
+Today: 2021-04-23T12:06:10
+Adding 12 days: 2021-05-05T12:06:10
+Subtracting 22 days: 2021-04-01T12:06:10
+Adding 50 seconds: 2021-04-23T12:07:00
+Adding 3 months: 2021-07-23T12:06:10
+Adding 12 years: 2033-04-23T12:06:10
+```
+## PyQt6 daylight saving time
+夏令时 (DST) 是在夏季调快时间,使晚上变得更长。 初春时调前调一小时,秋季时调后调至标准时间。
+``` python
+# file: daylight_saving.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDateTime, QTimeZone, Qt
+
+now = QDateTime.currentDateTime()
+
+print(f'Time zone: {now.timeZoneAbbreviation()}')
+
+if now.isDaylightTime():
+ print('The current date falls into DST time')
+else:
+ print('The current date does not fall into DST time')
+```
+该例判断一个时间是不是夏令时。
+
+```python
+print(f'Time zone: {now.timeZoneAbbreviation()}')
+```
+
+`timeZoneAbbreviation` 方法返回了时区的缩写。
+
+``` python
+if now.isDaylightTime():
+...
+```
+`isDaylightTime` 判断日期是不是夏令时。
+
+``` sh
+$ ./daylight_saving.py
+Time zone: CEST
+The current date falls into DST time
+```
+当前日期属于夏令时时间,中欧城市布拉迪斯拉发在夏季执行。 中欧夏令时 (CEST) 比世界时间早 2 小时。 该时区是夏令时时区,用于欧洲和南极洲。
+
+## PyQt6 unix 纪元
+纪元是被选为特定纪元起源的时间瞬间。 例如,在西方基督教国家,时间纪元从耶稣诞生的第 0 天开始。 另一个例子是使用了十二年的法国共和历。 这个时代是共和时代的开始,1792 年 9 月 22 日宣布第一共和国成立,君主制也被废除。
+
+计算机也有它的时代。 最受欢迎的时代之一是 Unix 时代。 Unix 纪元是 1970 年 1 月 1 日 UTC 时间 00:00:00(或 1970-01-01T00:00:00Z ISO 8601)。 计算机中的日期和时间是根据自该计算机或平台定义的纪元以来经过的秒数或时钟滴答数确定的。
+
+Unix 时间是自 Unix 纪元以来经过的秒数。
+
+``` sh
+$ date +%s
+1619172620
+```
+Unix date 命令可用于获取 Unix 时间。 在这个特殊的时刻,自 Unix 时代以来已经过去了 1619172620 秒。
+
+``` python
+# file: unix_time.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDateTime, Qt
+
+now = QDateTime.currentDateTime()
+
+unix_time = now.toSecsSinceEpoch()
+print(unix_time)
+
+d = QDateTime.fromSecsSinceEpoch(unix_time)
+print(d.toString(Qt.DateFormat.ISODate))
+```
+该例展示了 Unix 时间,并把它转换成了 `QDateTime`。
+
+``` python
+now = QDateTime.currentDateTime()
+```
+
+首先获取当前的日期和时间。
+
+```python
+unix_time = now.toSecsSinceEpoch()
+```
+
+`toSecsSinceEpoch` 返回了 Unix 时间。
+
+```python
+d = QDateTime.fromSecsSinceEpoch(unix_time)
+```
+
+使用 `fromSecsSinceEpoch` 方法把 Unix 时间转换成 `QDateTime`。
+
+``` sh
+$ ./unix_time.py
+1619172679
+2021-04-23T12:11:19
+```
+## PyQt6 Julian day
+儒略日是指自儒略时期开始以来的连续天数。 它主要由天文学家使用。 它不应与儒略历混淆。 它开始于公元前 4713 年。第 0 天是公元前 4713 年 1 月 1 日中午的那天。
+
+儒略日数 (JDN) 是自此期间开始以来经过的天数。 任何时刻的儒略日期 (JD) 是前一个中午的儒略日数加上自该时刻起当天的分数。 (Qt 不计算这个分数。)除了天文学,儒略日期经常被军事和大型机程序使用。
+
+``` python
+# file: julian_day.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate, Qt
+
+now = QDate.currentDate()
+
+print('Gregorian date for today:', now.toString(Qt.DateFormat.ISODate))
+print('Julian day for today:', now.toJulianDay())
+```
+该例中,我们得到了今天在公历中的表达和在儒略日的表达。
+
+``` python
+print('Julian day for today:', now.toJulianDay())
+```
+
+`toJulianDay()` 返回了儒略日的日期。
+
+``` sh
+$ ./julian_day.py
+Gregorian date for today: 2021-04-23
+Julian day for today: 2459328
+```
+## 历史战役
+使用儒略日可以进行跨越几个世纪的计算。
+
+``` python
+# file: battles.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QDate, Qt
+
+borodino_battle = QDate(1812, 9, 7)
+slavkov_battle = QDate(1805, 12, 2)
+
+now = QDate.currentDate()
+
+j_today = now.toJulianDay()
+j_borodino = borodino_battle.toJulianDay()
+j_slavkov = slavkov_battle.toJulianDay()
+
+d1 = j_today - j_slavkov
+d2 = j_today - j_borodino
+
+print(f'Days since Slavkov battle: {d1}')
+print(f'Days since Borodino battle: {d2}')
+```
+该示例计算自两个历史事件以来经过的天数。
+
+``` python
+borodino_battle = QDate(1812, 9, 7)
+slavkov_battle = QDate(1805, 12, 2)
+```
+这是两个拿破仑战斗日期。
+
+```python
+j_today = now.toJulianDay()
+j_borodino = borodino_battle.toJulianDay()
+j_slavkov = slavkov_battle.toJulianDay()
+```
+这是今天以及斯拉夫科夫和博罗季诺战役的儒略日。
+
+``` python
+d1 = j_today - j_slavkov
+d2 = j_today - j_borodino
+```
+这是两次大战的天数差。
+
+``` sh
+$ ./battles.py
+Days since Slavkov battle: 78670
+Days since Borodino battle: 76199
+```
+当我们运行这个脚本时,距离斯拉夫科夫战役已经过去了 78670 天,距离博罗季诺战役已经过去了 76199 天。
+
+在 PyQt6 教程的这一部分中,我们使用了日期和时间。
\ No newline at end of file
diff --git a/translated/pyqt6/dialogs.md b/translated/pyqt6/dialogs.md
new file mode 100644
index 0000000..4fa2132
--- /dev/null
+++ b/translated/pyqt6/dialogs.md
@@ -0,0 +1,364 @@
+# PyQt6 的对话框
+*最后更新于 2021.04.30*
+
+对话是两个或更多人之间的交谈。在计算机程序中,对话框是用于与应用程序“交谈”的窗口,用于诸如从用户那里获取数据或更改应用程序设置之类的事情。
+
+## PyQt6 QInputDialog
+`QInputDialog` 提供了一个简单方便的对话框来从用户那里获取输入。输入值可以是字符串、数字或列表中的项目。
+
+``` python
+# file: input_dialog.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we receive data from
+a QInputDialog dialog.
+
+Aauthor: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QPushButton, QLineEdit,
+ QInputDialog, QApplication)
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.btn = QPushButton('Dialog', self)
+ self.btn.move(20, 20)
+ self.btn.clicked.connect(self.showDialog)
+
+ self.le = QLineEdit(self)
+ self.le.move(130, 22)
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Input dialog')
+ self.show()
+
+
+ def showDialog(self):
+
+ text, ok = QInputDialog.getText(self, 'Input Dialog',
+ 'Enter your name:')
+
+ if ok:
+ self.le.setText(str(text))
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+这个示例有一个按钮和行内编辑部件,按钮打开输入一个对话框,对话框里有一个文本输入框,用户输入的文本会显示在行内编辑部件里。
+
+``` python
+text, ok = QInputDialog.getText(self, 'Input Dialog',
+ 'Enter your name:')
+```
+这行代码打开了输入对话框,第一个参数是对话框标题,第二个参数是对话框里的提示信息。对话框会返回输入的文本和一个布尔值。如果点击 OK 按钮,这个布尔值是 `true`。
+
+``` python
+if ok:
+ self.le.setText(str(text))
+```
+使用 `setText()` 从对话框里获取输入的文本。
+
+![Input dialog](./images/inputdialog.png)
+
+Figure: Input dialog
+
+## PyQt6 QColorDialog
+`QColorDialog` 是可以选择颜色对话框。
+
+``` python
+# file: color_dialog.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we select a color value
+from the QColorDialog and change the background
+color of a QFrame widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QPushButton, QFrame,
+ QColorDialog, QApplication)
+from PyQt6.QtGui import QColor
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ col = QColor(0, 0, 0)
+
+ self.btn = QPushButton('Dialog', self)
+ self.btn.move(20, 20)
+
+ self.btn.clicked.connect(self.showDialog)
+
+ self.frm = QFrame(self)
+ self.frm.setStyleSheet("QWidget { background-color: %s }"
+ % col.name())
+ self.frm.setGeometry(130, 22, 200, 200)
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Color dialog')
+ self.show()
+
+
+ def showDialog(self):
+
+ col = QColorDialog.getColor()
+
+ if col.isValid():
+
+ self.frm.setStyleSheet("QWidget { background-color: %s }"
+ % col.name())
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+示例里有个按钮和一个 `QFrame`。部件的背景颜色是默认颜色,可以使用 `QColorDialog` 修改部件的背景颜色。
+``` python
+col = QColor(0, 0, 0)
+```
+This is an initial colour of the QFrame background.
+``` python
+col = QColorDialog.getColor()
+```
+This line pops up the QColorDialog.
+``` python
+if col.isValid():
+
+ self.frm.setStyleSheet("QWidget { background-color: %s }"
+ % col.name())
+```
+这里检查了颜色是不是有效的。如果点击取消按钮,没有返回可用的颜色值。如果返回的颜色是有效值,就使用样式表修改背景颜色。
+
+## PyQt6 QFontDialog
+`QFontDialog` 是选择字体的对话框。
+
+``` python
+# file: font_dialog.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we select a font name
+and change the font of a label.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QPushButton,
+ QSizePolicy, QLabel, QFontDialog, QApplication)
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ vbox = QVBoxLayout()
+
+ btn = QPushButton('Dialog', self)
+ btn.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
+ btn.move(20, 20)
+
+ vbox.addWidget(btn)
+
+ btn.clicked.connect(self.showDialog)
+
+ self.lbl = QLabel('Knowledge only matters', self)
+ self.lbl.move(130, 20)
+
+ vbox.addWidget(self.lbl)
+ self.setLayout(vbox)
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Font dialog')
+ self.show()
+
+
+ def showDialog(self):
+
+ font, ok = QFontDialog.getFont()
+
+ if ok:
+ self.lbl.setFont(font)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例中,有个有文本的按钮。使用 `QFontDialog` 可以修改按钮文本的字体。
+``` python
+font, ok = QFontDialog.getFont()
+```
+这里弹出了字体选择对话框。`getFont` 方法返回了选择的字体名称和 `ok` 参数,如果点击 Ok 按钮,`ok` 的值是 `True`,反则是 `False`。
+``` python
+if ok:
+ self.label.setFont(font)
+```
+如果点击 Ok 按钮,`setFont` 方法会修改文本的字体。
+
+## PyQt6 QFileDialog
+
+`QFileDialog` 是选择文件或者文件夹的对话框,可以用作选择或者保存操作。
+
+``` python
+# file: file_dialog.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we select a file with a
+QFileDialog and display its contents
+in a QTextEdit.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QMainWindow, QTextEdit,
+ QFileDialog, QApplication)
+from PyQt6.QtGui import QIcon, QAction
+from pathlib import Path
+import sys
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.textEdit = QTextEdit()
+ self.setCentralWidget(self.textEdit)
+ self.statusBar()
+
+ openFile = QAction(QIcon('open.png'), 'Open', self)
+ openFile.setShortcut('Ctrl+O')
+ openFile.setStatusTip('Open new File')
+ openFile.triggered.connect(self.showDialog)
+
+ menubar = self.menuBar()
+ fileMenu = menubar.addMenu('&File')
+ fileMenu.addAction(openFile)
+
+ self.setGeometry(300, 300, 550, 450)
+ self.setWindowTitle('File dialog')
+ self.show()
+
+
+ def showDialog(self):
+
+ home_dir = str(Path.home())
+ fname = QFileDialog.getOpenFileName(self, 'Open file', home_dir)
+
+ if fname[0]:
+
+ f = open(fname[0], 'r')
+
+ with f:
+
+ data = f.read()
+ self.textEdit.setText(data)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+该示例有一个有居中显示的文本编辑部件的菜单栏和一个状态栏。菜单项显示了用于选择文件的 `QFileDialog`。文件的内容被加载到文本编辑小部件中。
+``` python
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+```
+示例是基于 `QMainWindow` 部件,是因为需要把文本编辑部件居中显示。
+``` python
+home_dir = str(Path.home())
+fname = QFileDialog.getOpenFileName(self, 'Open file', home_dir)
+```
+这里弹出 `QFileDialog`。`getOpenFileName` 的第一个参数字符串是标题,第二个字符串指定对话框工作目录。我们使用 `path` 模块来确定用户的主目录。默认情况下,文件过滤器设置为所有文件 (*)。
+
+``` python
+if fname[0]:
+
+ f = open(fname[0], 'r')
+
+ with f:
+
+ data = f.read()
+ self.textEdit.setText(data)
+```
+读取选择文件并把内容放置岛文本编辑部件里。
+
+本章教程,我们学习了对话框。
diff --git a/translated/pyqt6/dragdrop.md b/translated/pyqt6/dragdrop.md
new file mode 100644
index 0000000..b2475dc
--- /dev/null
+++ b/translated/pyqt6/dragdrop.md
@@ -0,0 +1,286 @@
+# PyQt6 中的拖拽操作
+*最后更新于 2021.05.15*
+
+本章教程,讲的是 PyQt6 中的拖拽操作。
+
+计算机图形界面中,拖拽操作是点击一个对象不放,把它放在另外一个地方或者另外一个对象上面的操作。一半来说,这会触发很多类型的行为,或者在两个对象上建立多种关系。
+
+在计算机图形用户界面中,拖放是(或支持)点击虚拟对象并将其拖到不同位置或另一个虚拟对象上的动作。 一般来说,它可以用来调用多种动作,或者在两个抽象对象之间创建各种类型的关联。
+
+拖放是图形界面的一部分,使用户能够直观地做复杂的事情。
+
+通常,我们可以拖放两个东西:数据或图形对象。将图像从一个应用程序拖到另一个应用程序,操作的是二进制数据。如果在 Firefox 中拖动一个选项卡并将其移动到另一个位置,操作的是一个图形组件。
+
+## QDrag
+`QDrag` 提供对基于 `MIME` 的拖放数据传输的支持。它处理拖放操作的大部分细节。传输的数据包含在 `QMimeData` 对象中
+
+## Simple drag and drop example in PyQt6
+In the first example, we have a `QLineEdit` and a `QPushButton`. We drag plain text from the line edit widget and drop it onto the button widget. The button's label will change.
+示例中,有一个 `QLineEdit` 和 `QPushButton` 部件,我们将纯文本从行编辑小部件拖放到按钮小部件上,以改变按钮的标签。
+
+``` python
+# file: simple.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This is a simple drag and
+drop example.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtWidgets import (QPushButton, QWidget,
+ QLineEdit, QApplication)
+
+
+class Button(QPushButton):
+
+ def __init__(self, title, parent):
+ super().__init__(title, parent)
+
+ self.setAcceptDrops(True)
+
+
+ def dragEnterEvent(self, e):
+
+ if e.mimeData().hasFormat('text/plain'):
+ e.accept()
+ else:
+ e.ignore()
+
+
+ def dropEvent(self, e):
+
+ self.setText(e.mimeData().text())
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ edit = QLineEdit('', self)
+ edit.setDragEnabled(True)
+ edit.move(30, 65)
+
+ button = Button("Button", self)
+ button.move(190, 65)
+
+ self.setWindowTitle('Simple drag and drop')
+ self.setGeometry(300, 300, 300, 150)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ ex.show()
+ app.exec()
+
+
+if __name__ == '__main__':
+ main()
+```
+示例展示了简单的拖拽操作。
+
+``` python
+class Button(QPushButton):
+
+ def __init__(self, title, parent):
+ super().__init__(title, parent)
+
+ ...
+```
+为了完成把文本拖到 `QPushButton` 部件上,我们必须实现某些方法才可以,所以这里创建了一个继承自 `QPushButton` 的 `Button` 类。
+
+``` python
+self.setAcceptDrops(True)
+```
+使用 `setAcceptDrops` 方法处理部件的释放事件。
+
+```python
+def dragEnterEvent(self, e):
+
+ if e.mimeData().hasFormat('text/plain'):
+ e.accept()
+ else:
+ e.ignore()
+```
+`dragEnterEvent` 方法,定义了我们接受的数据类型————纯文本。
+
+```python
+def dropEvent(self, e):
+
+ self.setText(e.mimeData().text())
+```
+`dropEvent` 方法,处理释放事件————修改按钮组件的文本。
+
+```python
+edit = QLineEdit('', self)
+edit.setDragEnabled(True)
+```
+`QLineEdit` 部件支持拖放操作,这里只需要调用 `setDragEnabled` 方法激活它。
+
+![Simple drag and drop](./images/dragdrop.png)
+
+图片:简单的拖放操作
+
+## 拖放按钮组件
+接下来的示例演示了如何拖放按钮组件。
+
+``` python
+# file: drag_button.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+在这个程序里,有一个按钮,可以用鼠标左键点击,也可以鼠标右键拖拽
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtCore import Qt, QMimeData
+from PyQt6.QtGui import QDrag
+from PyQt6.QtWidgets import QPushButton, QWidget, QApplication
+
+
+class Button(QPushButton):
+
+ def __init__(self, title, parent):
+ super().__init__(title, parent)
+
+
+ def mouseMoveEvent(self, e):
+
+ if e.buttons() != Qt.MouseButtons.RightButton:
+ return
+
+ mimeData = QMimeData()
+
+ drag = QDrag(self)
+ drag.setMimeData(mimeData)
+
+ drag.setHotSpot(e.position().toPoint() - self.rect().topLeft())
+
+ dropAction = drag.exec(Qt.DropActions.MoveAction)
+
+
+ def mousePressEvent(self, e):
+
+ super().mousePressEvent(e)
+
+ if e.button() == Qt.MouseButtons.LeftButton:
+ print('press')
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setAcceptDrops(True)
+
+ self.button = Button('Button', self)
+ self.button.move(100, 65)
+
+ self.setWindowTitle('Click or Move')
+ self.setGeometry(300, 300, 550, 450)
+
+
+ def dragEnterEvent(self, e):
+
+ e.accept()
+
+
+ def dropEvent(self, e):
+
+ position = e.position()
+ self.button.move(position.toPoint())
+
+ e.setDropAction(Qt.DropActions.MoveAction)
+ e.accept()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ ex.show()
+ app.exec()
+
+
+if __name__ == '__main__':
+ main()
+```
+
+本例中,窗口里有个 `QPushButton`,鼠标左键点击它,会在控制台打印 'press'消息,鼠标右键可以点击拖拽它。
+
+``` python
+class Button(QPushButton):
+
+ def __init__(self, title, parent):
+ super().__init__(title, parent)
+```
+基于 `QPushButton` 创建了一个 `Button` 类,并实现了两个 `QPushButton` 方法:`mouseMoveEvent` 和 `mousePressEvent`。`mouseMoveEvent` 方法是处理拖放操作开始的地方。
+
+``` python
+if e.buttons() != Qt.MouseButtons.RightButton:
+ return
+```
+定义鼠标右键为触发拖拽操作的按钮,鼠标左键只会触发点击事件。
+
+``` python
+drag = QDrag(self)
+drag.setMimeData(mimeData)
+
+drag.setHotSpot(e.position().toPoint() - self.rect().topLeft())
+```
+创建 `QDrag` 对象,以提供基于 MIME 数据类型的拖拽操作。
+
+``` python
+dropAction = drag.exec(Qt.DropActions.MoveAction)
+```
+`drag` 对象的 `exec` 方法执行拖拽操作。
+
+``` python
+def mousePressEvent(self, e):
+
+ super().mousePressEvent(e)
+
+ if e.button() == Qt.MouseButtons.LeftButton:
+ print('press')
+```
+如果鼠标左键点击按钮,会在控制台打印 'press' 消息,注意,这里在父级上也调用了 `mousePressEvent` 方法,不然按钮按下的动作不会展现出来。
+
+``` python
+position = e.pos()
+self.button.move(position)
+```
+`dropEvent` 方法处理鼠标释放按钮后的操作————把组件的位置修改为鼠标当前坐标。
+
+``` python
+e.setDropAction(Qt.MoveAction)
+e.accept()
+```
+使用 `setDropAction` 指定拖放操作的类型————鼠标移动。
+
+本章讲述了 PyQt6 中的拖拽操作。
diff --git a/translated/pyqt6/eventssignals.md b/translated/pyqt6/eventssignals.md
new file mode 100644
index 0000000..a565d4a
--- /dev/null
+++ b/translated/pyqt6/eventssignals.md
@@ -0,0 +1,410 @@
+# PyQt6 事件和信号
+*最后更新于 2021.04.29*
+
+这部分教程,我们探索 PyQt6 程序中的事件和信号。
+
+## PyQt6 中的事件
+GUI 应用程序是事件驱动的。事件主要由应用程序的用户触发,但也可以通过其他方式生成,例如 Internet 连接、窗口管理器或定时器。当我们调用应用程序的 `exec()` 方法时,应用程序进入主循环。 主循环获取事件并将它们发送到对象。
+
+在事件模型里,有三个要素:
+
+- 事件源 event source
+- 事件对象 event object
+- 事件目标 event target
+
+事件源是状态改变的对象,它会产生事件。*event object*(事件)封装了事件源中的状态变化。 *event target* 是要被通知的对象。事件源对象将处理事件的任务委托给事件目标。
+
+PyQt6 有独特的信号和插槽机制来处理事件,用于对象之间的通信,当特定事件发生时触发。插槽可以是任意可调用的 Python 脚本。当发出连接的信号时,调用插槽脚本
+> 译注:可以理解成钩子 (hooks) 或回调函数(callback)。
+
+## PyQt6 信号和插槽
+下面的示例展示了 PyQt6 的信号和插槽。
+
+``` python
+# file: signals_slots.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+本例中,把 QSlider 触发的事件和 QLCDNumber 插槽绑定起来
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import (QWidget, QLCDNumber, QSlider,
+ QVBoxLayout, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ lcd = QLCDNumber(self)
+ sld = QSlider(Qt.Orientations.Horizontal, self)
+
+ vbox = QVBoxLayout()
+ vbox.addWidget(lcd)
+ vbox.addWidget(sld)
+
+ self.setLayout(vbox)
+ sld.valueChanged.connect(lcd.display)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Signal and slot')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例中,展示了 `QtGui.QLCDNumber` 和 `QtGui.QSlider`。我们可以通过拖动滑块改变显示器里的数字。
+``` python
+sld.valueChanged.connect(lcd.display)
+```
+把滑块的 `valueChanged` 事件和 显示器 `display` 插槽绑定到一起。
+
+*sender* 是触发信号的对象, *receiver* 是接收信号的对象,*slot* 是对信号做出反应的方法。
+
+![信号和插槽](./images/sigslot.png)
+
+Figure: 信号和插槽
+
+## PyQt6 重新实现事件处理器
+
+PyQt6里,事件的处理器一般都会重新实现。
+> 译注:所有的事件处理器都有默认的实现,也就是默认事件。默认事件可能有自己的逻辑,比如拖选,点击,有的可能只是一个空函数。空函数都需要重新覆盖原来的实现,达到事件处理的目的。有默认事件处理函数的,也有可能被覆盖实现,比如禁用自带的拖选,或者重写拖选的效果等。
+
+``` python
+# file: reimplement_handler.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+本例中,我们重新实现了一个事件处理器。
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import QWidget, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Event handler')
+ self.show()
+
+
+ def keyPressEvent(self, e):
+
+ if e.key() == Qt.Key.Key_Escape.value:
+ self.close()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例中,我们重新实现了 `keyPressEvent` 的事件处理器
+``` python
+def keyPressEvent(self, e):
+
+ if e.key() == Qt.Key.Key_Escape.value:
+ self.close()
+```
+按下 Escape 按钮,应用会退出。
+
+## PyQt6 事件对象
+事件对象是一个 Python object,包含了一系列描述这个事件的属性,具体内容要看触发的事件。
+
+``` python
+# file: event_object.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+本例中,在标签组件里,展示了鼠标的坐标。
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import QWidget, QApplication, QGridLayout, QLabel
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ grid = QGridLayout()
+
+ x = 0
+ y = 0
+
+ self.text = f'x: {x}, y: {y}'
+
+ self.label = QLabel(self.text, self)
+ grid.addWidget(self.label, 0, 0, Qt.Alignment.AlignTop)
+
+ self.setMouseTracking(True)
+ self.setLayout(grid)
+
+ self.setGeometry(300, 300, 450, 300)
+ self.setWindowTitle('Event object')
+ self.show()
+
+
+ def mouseMoveEvent(self, e):
+
+ x = int(e.position().x())
+ y = int(e.position().y())
+
+ text = f'x: {x}, y: {y}'
+ self.label.setText(text)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例中,在标签组件里,展示了鼠标的坐标。
+``` python
+self.setMouseTracking(True)
+```
+鼠标跟踪默认是关闭的,鼠标移动时,组件只能在鼠标按下的时候接收到事件。开启鼠标跟踪,只移动鼠标不按下鼠标按钮,也能接收到事件。
+``` python
+def mouseMoveEvent(self, e):
+
+ x = int(e.position().x())
+ y = int(e.position().y())
+ ...
+```
+`e` 是事件对象,它包含了事件触发时候的数据。通过 `position().x()` 和 `e.position().y()` 方法,能获取到鼠标的坐标值。
+``` python
+self.text = f'x: {x}, y: {y}'
+self.label = QLabel(self.text, self)
+```
+坐标值 x 和 y 显示在 `QLabel` 组件里。
+
+![事件对象](./images/eventobject.png)
+
+Figure: 事件对象
+
+# PyQt6 事件触发者
+
+某些时候,需要知道事件的触发者是谁,PyQt6 有获取事件触发者的方法。
+
+``` python
+event_sender.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we determine the event sender
+object.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QPushButton, QApplication
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ btn1 = QPushButton("Button 1", self)
+ btn1.move(30, 50)
+
+ btn2 = QPushButton("Button 2", self)
+ btn2.move(150, 50)
+
+ btn1.clicked.connect(self.buttonClicked)
+ btn2.clicked.connect(self.buttonClicked)
+
+ self.statusBar()
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Event sender')
+ self.show()
+
+
+ def buttonClicked(self):
+
+ sender = self.sender()
+
+ msg = f'{sender.text()} was pressed'
+ self.statusBar().showMessage(msg)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例中有两个按钮。 `buttonClicked` 调用触发者方法确定了是哪个按钮触发的事件。
+``` python
+btn1.clicked.connect(self.buttonClicked)
+btn2.clicked.connect(self.buttonClicked)
+```
+两个按钮绑定了同一个插槽。
+``` python
+def buttonClicked(self):
+
+ sender = self.sender()
+
+ msg = f'{sender.text()} was pressed'
+ self.statusBar().showMessage(msg)
+```
+在应用的状态栏里,显示了是哪个按钮被按下。
+
+![事件触发者](./images/eventsender.png)
+
+Figure: 事件触发者
+
+## PyQt6 触发信号
+
+`QObject` 可以主动触发信号。下面的示例显示了如果触发自定义信号。
+
+``` python
+# file: custom_signal.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we show how to
+emit a custom signal.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtCore import pyqtSignal, QObject
+from PyQt6.QtWidgets import QMainWindow, QApplication
+
+
+class Communicate(QObject):
+
+ closeApp = pyqtSignal()
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.c = Communicate()
+ self.c.closeApp.connect(self.close)
+
+ self.setGeometry(300, 300, 450, 350)
+ self.setWindowTitle('Emit signal')
+ self.show()
+
+
+ def mousePressEvent(self, e):
+
+ self.c.closeApp.emit()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+创建了一个叫 `closeApp` 的信号,在鼠标按下的时候触发,和关闭插槽 `QMainWindow` 绑定。
+
+``` python
+class Communicate(QObject):
+
+ closeApp = pyqtSignal()
+```
+外部 Communicate 类的属性 `pyqtSignal` 创建信号。
+``` python
+self.c = Communicate()
+self.c.closeApp.connect(self.close)
+```
+自定义信号 `closeApp` 绑定到 `QMainWindow` 的关闭插槽上。
+``` python
+def mousePressEvent(self, event):
+
+ self.c.closeApp.emit()
+```
+在窗口上点击鼠标按钮的时候,触发 `closeApp` 信号,程序终止。
+
+本章教程,我们讲述了信号和插槽。
diff --git a/translated/pyqt6/firstprograms.md b/translated/pyqt6/firstprograms.md
new file mode 100644
index 0000000..cb16aeb
--- /dev/null
+++ b/translated/pyqt6/firstprograms.md
@@ -0,0 +1,400 @@
+# PyQt6 的第一个程序
+*最后更新于 2021.04.23*
+
+在 PyQt6 教程的这一部分中,我们将学习一些基本功能。这些示例显示工具提示和图标、关闭窗口、显示消息框以及在桌面上居中显示窗口。
+## PyQt6 简单示例
+这是一个显示小窗口的简单示例。但我们可以用这个窗口做很多事情。我们可以调整它的大小、最大化或最小化它。 实现这些功能通常需要写大量的代码,但这些功能很常见,所以这部分功能已经封装好了,我们直接使用即可。PyQt6 是一个高级工具包,如果使用低级的工具包,下面的代码示例很可能需要数百行才可以实现。
+
+``` python
+# file: simple.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create a simple
+window in PyQt6.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+
+import sys
+from PyQt6.QtWidgets import QApplication, QWidget
+
+
+def main():
+
+ app = QApplication(sys.argv)
+
+ w = QWidget()
+ w.resize(250, 200)
+ w.move(300, 300)
+
+ w.setWindowTitle('Simple')
+ w.show()
+
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+
+上面的代码会再屏幕上显示一个小窗口。
+
+``` python
+import sys
+from PyQt6.QtWidgets import QApplication, QWidget
+```
+这里引入了必要的包,基础小组件位于 `PyQt6.QtWidgets` 模块。
+
+``` python
+app = QApplication(sys.argv)
+```
+每个 PyQt6 应用程序都必须创建一个应用程序对象。`sys.argv` 参数是来自命令行的参数列表。Python 脚本可以从 shell 运行,这是应用启动的一种方式。
+
+``` python
+w = QWidget()
+```
+QWidget 小部件是 PyQt6 中所有用户界面对象的基类。我们为 QWidget 提供了默认构造函数。默认构造函数没有父级。没有父级的小部件称为窗口。
+``` python
+w.resize(250, 150)
+```
+`resize` 方法改变了小部件的尺寸,现在它250像素宽,150像素高。
+``` python
+w.move(300, 300)
+```
+`move` 方法把小部件移动到屏幕的指定坐标(300, 300)。
+``` python
+w.setWindowTitle('Simple')
+```
+使用 `setWindowTitle` 给窗口设置标题,标题显示在标题栏。
+``` python
+w.show()
+```
+`show` 方法是在屏幕上显示小部件的方法。显示一个部件的步骤是首先在内存里创建,然后在屏幕上显示。
+``` python
+sys.exit(app.exec())
+```
+最后,我们进入应用程序的主循环。事件处理从这里开始。主循环从窗口系统接收事件并将它们分派给应用程序小部件。 如果我们调用 exit 方法或主小部件被销毁,则主循环结束。`sys.exit` 方法确保一个干净的退出。环境将被告知应用程序如何结束。
+
+![Simple](./images/simple.png)
+
+Figure: Simple
+## PyQt6 tooltip
+我们可以为程序创建一个气泡提示。
+
+``` python
+# file: tooltip.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows a tooltip on
+a window and a button.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QToolTip,
+ QPushButton, QApplication)
+from PyQt6.QtGui import QFont
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ QToolTip.setFont(QFont('SansSerif', 10))
+
+ self.setToolTip('This is a QWidget widget')
+
+ btn = QPushButton('Button', self)
+ btn.setToolTip('This is a QPushButton widget')
+ btn.resize(btn.sizeHint())
+ btn.move(50, 50)
+
+ self.setGeometry(300, 300, 300, 200)
+ self.setWindowTitle('Tooltips')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+该示例中,我们给两个小部件创建了一个气泡提示框。
+
+``` python
+QToolTip.setFont(QFont('SansSerif', 10))
+```
+
+这个静态方法给气泡提示框设置了字体,这里使用了10pt 的 SansSerif 字体。
+
+``` python
+self.setToolTip('This is a QWidget widget')
+```
+
+调用 `setTooltip` 方法创建气泡提示框,可以使用富文本内容。
+
+``` python
+btn = QPushButton('Button', self)
+btn.setToolTip('This is a QPushButton widget')
+```
+
+在气泡提示框上添加了一个按钮部件。
+
+``` python
+btn.resize(btn.sizeHint())
+btn.move(50, 50)
+```
+`sizeHint` 方法是给按钮一个系统建议的尺寸,然后使用 `move` 方法移动这个按钮的位置。
+
+![Tooltips](./images/tooltips.png)
+Figure: Tooltips
+## PyQt6 退出按钮
+关闭窗口的明显方法是单击标题栏上的 x 标记。在下一个示例中,我们将展示如何以编程方式关闭窗口。 我们将简要介绍信号和插槽。
+
+本例中使用 `QPushButton` 部件完成这个功能。
+``` python
+QPushButton(string text, QWidget parent = None)
+```
+参数 `text` 是将显示在按钮上的文本。`parent` 是我们放置按钮的小部件。在我们的例子中,它将是一个`QWidget`。应用程序的小部件形成层次结构,在这个层次结构中,大多数小部件都有父级。没有父级的小部件的父级是顶级窗口。
+
+```python
+# file: quit_button.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a quit
+button. When we press the button,
+the application terminates.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QPushButton, QApplication
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ qbtn = QPushButton('Quit', self)
+ qbtn.clicked.connect(QApplication.instance().quit)
+ qbtn.resize(qbtn.sizeHint())
+ qbtn.move(50, 50)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Quit button')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例创建了一个退出按钮,点击此按钮退出程序。
+``` python
+qbtn = QPushButton('Quit', self)
+```
+我们创建了一个按钮,它是 `QPushButton` 类的一个实例。构造函数的第一个参数是按钮的标签。 第二个参数是父级小部件。父小部件是 `Example` 小部件,它继承自 `QWidget`。
+``` python
+qbtn.clicked.connect(QApplication.instance().quit)
+```
+PyQt6 的事件处理系统是由信号和插槽机制构成的,点击按钮(事件),会发出点击信号。事件处理插槽可以是 Qt 自带的插槽,也可以是普通 Python 函数
+
+使用 `QApplication.instance` 获取的 `QCoreApplication` 对象包含主事件循环————它处理和分派所有事件。 单击的信号连接到终止应用程序的退出方法。 通信是在两个对象之间完成的:发送者和接收者。 发送者是按钮,接收者是应用程序对象。
+
+![Quit button](./images/quitbutton.png)
+
+Figure: Quit button
+
+## PyQt6 弹窗
+默认情况下,如果我们点击标题栏上的 x 按钮,`QWidget` 会被关闭。有时我们想修改这个默认行为。 例如,如果在编辑器中打开了一个文件,修改了部分内容,我们需要显示一个消息框来确认退出程序的操作。
+
+``` python
+# file: messagebox.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program shows a confirmation
+message box when we click on the close
+button of the application window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QMessageBox, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 350, 200)
+ self.setWindowTitle('Message box')
+ self.show()
+
+ def closeEvent(self, event):
+
+ reply = QMessageBox.question(self, 'Message',
+ "Are you sure to quit?", QMessageBox.StandardButtons.Yes |
+ QMessageBox.StandardButtons.No, QMessageBox.StandardButtons.No)
+
+ if reply == QMessageBox.StandardButtons.Yes:
+
+ event.accept()
+ else:
+
+ event.ignore()
+
+
+def main():
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+关闭 `QWidget` 操作会产生 `QCloseEvent` 事件。重新实现 `closeEvent` 事件处理,替换部件的默认行为。
+
+``` python
+reply = QMessageBox.question(self, 'Message',
+ "Are you sure to quit?", QMessageBox.Yes |
+ QMessageBox.No, QMessageBox.No)
+```
+这里创建了一个带有两个按钮的消息框:是和否。第一个参数是标题栏,第二个参数是对话框显示的消息文本,第三个参数是对话框中的按钮组合,最后一个参数是默认选中的按钮。返回值存储在变量 `replay` 中。
+
+``` python
+if reply == QtGui.QMessageBox.Yes:
+ event.accept()
+else:
+ event.ignore()
+```
+对返回值进行判断,如果单击 Yes 按钮,执行小部件关闭和终止应用程序,否则我们忽略关闭事件。
+
+![Message box](./images/messagebox.png)
+
+Figure: Message box
+
+## PyQt6 窗口居中
+下面的脚本会在屏幕上显示一个居中的窗口。
+``` python
+# file: center.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program centers a window
+on the screen.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+ def initUI(self):
+
+ self.resize(350, 250)
+ self.center()
+
+ self.setWindowTitle('Center')
+ self.show()
+
+ def center(self):
+
+ qr = self.frameGeometry()
+ cp = self.screen().availableGeometry().center()
+
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+`QScreen` 类可以查询屏幕属性。
+``` python
+self.center()
+```
+使用自定义 `center` 方法居中显示窗口。
+``` python
+qr = self.frameGeometry()
+```
+这样就可以得到一个矩形的窗口,这里可以放置所有类型的窗口。
+``` python
+cp = self.screen().availableGeometry().center()
+```
+从屏幕属性里计算出分辨率,然后计算出中心点位置。
+``` python
+qr.moveCenter(cp)
+```
+我们已经知道矩形窗口的宽高,只需要把矩形窗口的中心点放置到屏幕窗口的中心点即可。这不会修改矩形窗口的大小。
+``` python
+self.move(qr.topLeft())
+```
+把应用窗口的左上方点坐标移动到矩形窗口的左上方,这样就可以居中显示了。
+
+本章教程,我们创建了一个简单的 PyQt6 应用。
\ No newline at end of file
diff --git a/translated/pyqt6/images/absolute.png b/translated/pyqt6/images/absolute.png
new file mode 100644
index 0000000..cdd9cac
Binary files /dev/null and b/translated/pyqt6/images/absolute.png differ
diff --git a/translated/pyqt6/images/beziercurve.png b/translated/pyqt6/images/beziercurve.png
new file mode 100644
index 0000000..e659a83
Binary files /dev/null and b/translated/pyqt6/images/beziercurve.png differ
diff --git a/translated/pyqt6/images/brushes.png b/translated/pyqt6/images/brushes.png
new file mode 100644
index 0000000..486b163
Binary files /dev/null and b/translated/pyqt6/images/brushes.png differ
diff --git a/translated/pyqt6/images/burning.png b/translated/pyqt6/images/burning.png
new file mode 100644
index 0000000..1ab780e
Binary files /dev/null and b/translated/pyqt6/images/burning.png differ
diff --git a/translated/pyqt6/images/buttons.png b/translated/pyqt6/images/buttons.png
new file mode 100644
index 0000000..beadf46
Binary files /dev/null and b/translated/pyqt6/images/buttons.png differ
diff --git a/translated/pyqt6/images/calculator.png b/translated/pyqt6/images/calculator.png
new file mode 100644
index 0000000..899c5cc
Binary files /dev/null and b/translated/pyqt6/images/calculator.png differ
diff --git a/translated/pyqt6/images/checkmenu.png b/translated/pyqt6/images/checkmenu.png
new file mode 100644
index 0000000..7537b4a
Binary files /dev/null and b/translated/pyqt6/images/checkmenu.png differ
diff --git a/translated/pyqt6/images/colours.png b/translated/pyqt6/images/colours.png
new file mode 100644
index 0000000..97d3a40
Binary files /dev/null and b/translated/pyqt6/images/colours.png differ
diff --git a/translated/pyqt6/images/coordinates.png b/translated/pyqt6/images/coordinates.png
new file mode 100644
index 0000000..38d6c6e
Binary files /dev/null and b/translated/pyqt6/images/coordinates.png differ
diff --git a/translated/pyqt6/images/dragdrop.png b/translated/pyqt6/images/dragdrop.png
new file mode 100644
index 0000000..6893ca4
Binary files /dev/null and b/translated/pyqt6/images/dragdrop.png differ
diff --git a/translated/pyqt6/images/drawingtext.png b/translated/pyqt6/images/drawingtext.png
new file mode 100644
index 0000000..66bbb78
Binary files /dev/null and b/translated/pyqt6/images/drawingtext.png differ
diff --git a/translated/pyqt6/images/eventobject.png b/translated/pyqt6/images/eventobject.png
new file mode 100644
index 0000000..3765552
Binary files /dev/null and b/translated/pyqt6/images/eventobject.png differ
diff --git a/translated/pyqt6/images/eventsender.png b/translated/pyqt6/images/eventsender.png
new file mode 100644
index 0000000..1fa875c
Binary files /dev/null and b/translated/pyqt6/images/eventsender.png differ
diff --git a/translated/pyqt6/images/inputdialog.png b/translated/pyqt6/images/inputdialog.png
new file mode 100644
index 0000000..84accd7
Binary files /dev/null and b/translated/pyqt6/images/inputdialog.png differ
diff --git a/translated/pyqt6/images/mainwindow.png b/translated/pyqt6/images/mainwindow.png
new file mode 100644
index 0000000..cecb754
Binary files /dev/null and b/translated/pyqt6/images/mainwindow.png differ
diff --git a/translated/pyqt6/images/messagebox.png b/translated/pyqt6/images/messagebox.png
new file mode 100644
index 0000000..0a917f5
Binary files /dev/null and b/translated/pyqt6/images/messagebox.png differ
diff --git a/translated/pyqt6/images/penstyles.png b/translated/pyqt6/images/penstyles.png
new file mode 100644
index 0000000..0e14ec6
Binary files /dev/null and b/translated/pyqt6/images/penstyles.png differ
diff --git a/translated/pyqt6/images/points.png b/translated/pyqt6/images/points.png
new file mode 100644
index 0000000..57ce8b9
Binary files /dev/null and b/translated/pyqt6/images/points.png differ
diff --git a/translated/pyqt6/images/qcheckbox.png b/translated/pyqt6/images/qcheckbox.png
new file mode 100644
index 0000000..7f519f8
Binary files /dev/null and b/translated/pyqt6/images/qcheckbox.png differ
diff --git a/translated/pyqt6/images/qcombobox.png b/translated/pyqt6/images/qcombobox.png
new file mode 100644
index 0000000..bb8e9ae
Binary files /dev/null and b/translated/pyqt6/images/qcombobox.png differ
diff --git a/translated/pyqt6/images/qlineedit.png b/translated/pyqt6/images/qlineedit.png
new file mode 100644
index 0000000..ffb8a16
Binary files /dev/null and b/translated/pyqt6/images/qlineedit.png differ
diff --git a/translated/pyqt6/images/qprogressbar.png b/translated/pyqt6/images/qprogressbar.png
new file mode 100644
index 0000000..52a716b
Binary files /dev/null and b/translated/pyqt6/images/qprogressbar.png differ
diff --git a/translated/pyqt6/images/qslider.png b/translated/pyqt6/images/qslider.png
new file mode 100644
index 0000000..7aa8a6d
Binary files /dev/null and b/translated/pyqt6/images/qslider.png differ
diff --git a/translated/pyqt6/images/qsplitter.png b/translated/pyqt6/images/qsplitter.png
new file mode 100644
index 0000000..3536a6d
Binary files /dev/null and b/translated/pyqt6/images/qsplitter.png differ
diff --git a/translated/pyqt6/images/quitbutton.png b/translated/pyqt6/images/quitbutton.png
new file mode 100644
index 0000000..d3ab302
Binary files /dev/null and b/translated/pyqt6/images/quitbutton.png differ
diff --git a/translated/pyqt6/images/review.png b/translated/pyqt6/images/review.png
new file mode 100644
index 0000000..02073a3
Binary files /dev/null and b/translated/pyqt6/images/review.png differ
diff --git a/translated/pyqt6/images/sigslot.png b/translated/pyqt6/images/sigslot.png
new file mode 100644
index 0000000..8a867ba
Binary files /dev/null and b/translated/pyqt6/images/sigslot.png differ
diff --git a/translated/pyqt6/images/simple.png b/translated/pyqt6/images/simple.png
new file mode 100644
index 0000000..310e813
Binary files /dev/null and b/translated/pyqt6/images/simple.png differ
diff --git a/translated/pyqt6/images/submenu.png b/translated/pyqt6/images/submenu.png
new file mode 100644
index 0000000..398b551
Binary files /dev/null and b/translated/pyqt6/images/submenu.png differ
diff --git a/translated/pyqt6/images/tetris.png b/translated/pyqt6/images/tetris.png
new file mode 100644
index 0000000..d225c18
Binary files /dev/null and b/translated/pyqt6/images/tetris.png differ
diff --git a/translated/pyqt6/images/tetrominoes.png b/translated/pyqt6/images/tetrominoes.png
new file mode 100644
index 0000000..9e30a1b
Binary files /dev/null and b/translated/pyqt6/images/tetrominoes.png differ
diff --git a/translated/pyqt6/images/togglebutton.png b/translated/pyqt6/images/togglebutton.png
new file mode 100644
index 0000000..ddecb90
Binary files /dev/null and b/translated/pyqt6/images/togglebutton.png differ
diff --git a/translated/pyqt6/images/toolbar.png b/translated/pyqt6/images/toolbar.png
new file mode 100644
index 0000000..640e829
Binary files /dev/null and b/translated/pyqt6/images/toolbar.png differ
diff --git a/translated/pyqt6/images/tooltips.png b/translated/pyqt6/images/tooltips.png
new file mode 100644
index 0000000..04191b9
Binary files /dev/null and b/translated/pyqt6/images/tooltips.png differ
diff --git a/translated/pyqt6/index.md b/translated/pyqt6/index.md
new file mode 100644
index 0000000..a81bcd0
--- /dev/null
+++ b/translated/pyqt6/index.md
@@ -0,0 +1,14 @@
+Python PyQt6
+最后更新于 2021.05.18
+
+这是一个 PyQt6 教程。本教程是个初、中级教程,学完本教程后,您可以编写一个非常不错的 PyQt6 应用。代码示例在作者的 Github 仓库 [PyQt6-Tutorial-Examples](https://github.com/janbodnar/PyQt6-Tutorial-Examples)。
+
+
+一本涵盖 PyQt5 库的高级特性的电子书: [Advanced PyQt5 e-book](https://zetcode.com/ebooks/advancedpyqt5/).
+
+相关教程
+
+[PyQt5 tutorial](https://zetcode.com/gui/pyqt5/) 是介绍了上个版本的 PyQt 。
+
+ [wxPython tutorial](https://zetcode.com/wxpython/) 、 [Python Gtk tutorial](https://zetcode.com/python/gtk/) 和
+ [Tkinter tutorial](https://zetcode.com/tkinter/) 是关于其他比较流行的 Python GUI 教程。
\ No newline at end of file
diff --git a/translated/pyqt6/introduction.md b/translated/pyqt6/introduction.md
new file mode 100644
index 0000000..3dc3147
--- /dev/null
+++ b/translated/pyqt6/introduction.md
@@ -0,0 +1,63 @@
+# PyQt6 简介
+*最后更新于 2021.04.22*
+
+本教程是 PyQt6 的入门教程。本教程的目的是让您开始使用 PyQt6 库。
+## 关于 PyQt6
+
+PyQt6 Digia 公司的 Qt 程序的 Python 中间件。Qt库是最强大的GUI库之一。PyQt6的官网:[www.riverbankcomputing.co.uk/news](https://www.riverbankcomputing.co.uk/news)。PyQt6是由Riverbank Computing公司开发的。~~Digia和RiverBank的关系不太清楚,大家多提意见~~
+
+PyQt6 是基于 Python 的一系列模块。它是一个多平台的工具包,可以在包括Unix、Windows和Mac OS在内的大部分主要操作系统上运行。PyQt6 有两个许可证,开发人员可以在 GPL 和商业许可之间进行选择。
+
+## 安装 PyQt6
+```sh
+$ pip install PyQt6
+```
+我们可以使用 `pip` 工具安装 PyQt6。
+
+## PyQt6 模块
+PyQt6 类是由一系列模块组成的,包括如下的模块:
+
+- QtCore
+- QtGui
+- QtWidgets
+- QtDBus
+- QtNetwork
+- QtHelp
+- QtXml
+- QtSvg
+- QtSql
+- QtTest
+
+`QtCore` 模块是非 GUI 的核心库。这个模块用来处理时间、文件、目录、各种类型的数据、流(stream)、URLs,mime 类型、线程和进程。 `QtGui` 有窗口系统集成、事件处理、2D图形,基本图像、字体、文本的类。 `QtWidgets` 有创建经典风格的用户界面的类。
+
+`QtDBus` 是使用 D-Bus 处理 IPC 通讯的类。`QtNetwork` 是网络变成类,这些类使网络编程变得更容易,可移植性也更好,方便了 TCP/IP 和 UDP 服务端和客户端编程。 `QtHelp` 包含了创建、查看和搜索文档的类。
+
+`QtXml` 包含了处理 XML 文件的类,实现了 SAX 和 DOM API。`QtSvg` 提供了显示 SVG 的类,可缩放矢量图形(SVG)是一种描述二维图像和图像应用的 XML 语言。`QtSql` 模块提供了数据库的类,`QtTest` 提供了可以对 PyQt6 应用进行单元测试的工具。
+
+## Python
+
+Python 是一种通用的、动态的、面向对象的编程语言。Python语言的设计目的强调程序员的生产力和代码的可读性。它于1991年首次发行。Python 的灵感来自于 ABC、Haskell、Java、Lisp、Icon 和 Perl 编程语言。Python 是一种高级的、通用的、多平台的解释性语言,是一种极简语言,由世界各地的一大群志愿者维护。
+
+官方网站是 https://python.org
+
+## PyQt6 version
+`QT_VERSION_STR` 可以显示 Qt 的版本信息,`PYQT_VERSION_STR` 可以显示 PyQt 的版本信息
+
+``` python
+# file: version.py
+#!/usr/bin/python
+
+from PyQt6.QtCore import QT_VERSION_STR
+from PyQt6.QtCore import PYQT_VERSION_STR
+
+print(QT_VERSION_STR)
+print(PYQT_VERSION_STR)
+```
+运行这个脚本可以显示 QT 和 PyQt 的版本。
+
+``` sh
+$ ./version.py
+6.0.2
+6.0.3
+```
+这个章节介绍的是 PyQt 的工具类库。
\ No newline at end of file
diff --git a/translated/pyqt6/layout.md b/translated/pyqt6/layout.md
new file mode 100644
index 0000000..db083cb
--- /dev/null
+++ b/translated/pyqt6/layout.md
@@ -0,0 +1,360 @@
+# PyQt6 的布局管理
+*最近更新于 2021.04.27*
+
+布局管理是我们在应用程序窗口中放置小部件的方式。我们可以使用绝对定位或布局类来放置小部件。使用布局管理器管理布局是组织小部件的首选方法。
+
+## 绝对定位
+以像素为单位指定每个小部件的位置和大小。在使用绝对定位时,我们必须了解以下局限性:
+
+-如果我们调整窗口大小,窗口小部件的大小和位置不会改变
+-应用程序在不同的平台上看起来可能不同,改变应用程序的字体可能会破坏布局
+-如果要改变布局,我们必须完全重做我们的布局,这很繁琐耗时
+
+下面的示例以绝对坐标来定位小部件。
+
+``` python
+# file: absolute.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows three labels on a window
+using absolute positioning.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QLabel, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ lbl1 = QLabel('ZetCode', self)
+ lbl1.move(15, 10)
+
+ lbl2 = QLabel('tutorials', self)
+ lbl2.move(35, 40)
+
+ lbl3 = QLabel('for programmers', self)
+ lbl3.move(55, 70)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Absolute')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+我们使用 `move` 方法来定位小部件。在本例中也就是标签。我们通过提供x和y坐标来定位。坐标系的起始点在左上角,x值从左到右递增。y值从上到下递增。
+
+``` python
+lbl1 = QLabel('ZetCode', self)
+lbl1.move(15, 10)
+The label widget is positioned at x=15 and y=10.
+```
+
+![Absolute positioning](./images/absolute.png)
+
+Figure: Absolute positioning
+
+## PyQt6 QHBoxLayout
+`QHBoxLayout` 和 `QVBoxLayout` 是基本的布局类,用于水平和垂直地排列小部件。
+
+假设我们想在右下角放置两个按钮。为了创建这样的布局,我们使用一个水平框和一个垂直框。为了创造必要的空间,我们添加了一个“拉伸因子”。
+
+``` python
+# file: box_layout.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we position two push
+buttons in the bottom-right corner
+of the window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QPushButton,
+ QHBoxLayout, QVBoxLayout, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ okButton = QPushButton("OK")
+ cancelButton = QPushButton("Cancel")
+
+ hbox = QHBoxLayout()
+ hbox.addStretch(1)
+ hbox.addWidget(okButton)
+ hbox.addWidget(cancelButton)
+
+ vbox = QVBoxLayout()
+ vbox.addStretch(1)
+ vbox.addLayout(hbox)
+
+ self.setLayout(vbox)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Buttons')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+
+窗口的右下角有两个按钮。当我们调整应用程序窗口的大小时,它们仍然在那里。这里使用了 `HBoxLayout` 和 `QVBoxLayout`。
+``` python
+okButton = QPushButton("OK")
+cancelButton = QPushButton("Cancel")
+```
+这里创建两个按钮。
+``` python
+hbox = QHBoxLayout()
+hbox.addStretch(1)
+hbox.addWidget(okButton)
+hbox.addWidget(cancelButton)
+```
+创建一个水平框布局,并添加一个拉伸因子和两个按钮。拉伸在两个按钮之前增加了一个可拉伸的空间,这将把他们推到窗口的右边。
+``` python
+vbox = QVBoxLayout()
+vbox.addStretch(1)
+vbox.addLayout(hbox)
+```
+水平布局被放入垂直布局中。垂直框中的拉伸因子将把带有按钮的水平框推到窗口的底部。
+``` python
+self.setLayout(vbox)
+```
+最后,把布局放到窗口中里。
+
+![按钮](./images/buttons.png)
+
+Figure: 按钮
+
+## PyQt6 QGridLayout
+
+`QGridLayout` 是最常用的布局类,它能把空间分为多行多列。
+``` python
+# file: calculator.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create a skeleton
+of a calculator using QGridLayout.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QGridLayout,
+ QPushButton, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ grid = QGridLayout()
+ self.setLayout(grid)
+
+ names = ['Cls', 'Bck', '', 'Close',
+ '7', '8', '9', '/',
+ '4', '5', '6', '*',
+ '1', '2', '3', '-',
+ '0', '.', '=', '+']
+
+ positions = [(i, j) for i in range(5) for j in range(4)]
+
+ for position, name in zip(positions, names):
+
+ if name == '':
+ continue
+
+ button = QPushButton(name)
+ grid.addWidget(button, *position)
+
+ self.move(300, 150)
+ self.setWindowTitle('Calculator')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+这里创建了一组按钮。
+``` python
+grid = QGridLayout()
+self.setLayout(grid)
+```
+`QGridLayout` 实例创建了并把布局设置到窗口中。
+``` python
+names = ['Cls', 'Bck', '', 'Close',
+ '7', '8', '9', '/',
+ '4', '5', '6', '*',
+ '1', '2', '3', '-',
+ '0', '.', '=', '+']
+```
+这些是随后要用到的按钮上的标签。
+``` python
+positions = [(i,j) for i in range(5) for j in range(4)]
+```
+创建了给格栅用到的位置。
+``` python
+for position, name in zip(positions, names):
+
+ if name == '':
+ continue
+
+ button = QPushButton(name)
+ grid.addWidget(button, *position)
+```
+创建了按钮,并用 `addWidget` 方法添加到布局里。
+
+![计算器骨架](./images/calculator.png)
+
+Figure: 计算器骨架
+
+## 示例:回复
+组件可以跨越多个行和列,下面的示例来演示这个。
+
+``` python
+# file: review.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create a bit
+more complicated window layout using
+the QGridLayout manager.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QLabel, QLineEdit,
+ QTextEdit, QGridLayout, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ title = QLabel('Title')
+ author = QLabel('Author')
+ review = QLabel('Review')
+
+ titleEdit = QLineEdit()
+ authorEdit = QLineEdit()
+ reviewEdit = QTextEdit()
+
+ grid = QGridLayout()
+ grid.setSpacing(10)
+
+ grid.addWidget(title, 1, 0)
+ grid.addWidget(titleEdit, 1, 1)
+
+ grid.addWidget(author, 2, 0)
+ grid.addWidget(authorEdit, 2, 1)
+
+ grid.addWidget(review, 3, 0)
+ grid.addWidget(reviewEdit, 3, 1, 5, 1)
+
+ self.setLayout(grid)
+
+ self.setGeometry(300, 300, 350, 300)
+ self.setWindowTitle('Review')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+窗口里有三个标签,两个行编辑器和一个文本编辑组件,布局使用了 `QGridLayout`。
+
+``` python
+grid = QGridLayout()
+grid.setSpacing(10)
+```
+
+创建一个格栅布局,并设置了组件间的分割空间。
+
+``` python
+grid.addWidget(reviewEdit, 3, 1, 5, 1)
+```
+
+如果需要往格栅里添加组件,指定组件的跨行和跨列的个数。本例,`reviewEdit` 组件占用了5行。
+
+![Review example](./images/review.png)
+
+Figure: Review example
+
+本例展示了 PyQt6 的布局管理。
\ No newline at end of file
diff --git a/translated/pyqt6/menustoolbars.md b/translated/pyqt6/menustoolbars.md
new file mode 100644
index 0000000..7c2d43a
--- /dev/null
+++ b/translated/pyqt6/menustoolbars.md
@@ -0,0 +1,523 @@
+# PyQt6 的菜单和工具栏
+*最后更新于 20201.04.24*
+
+在这部分教程中,我们创建了一个状态栏、菜单栏和工具栏。菜单是位于菜单栏中的一组命令。工具栏有一些按钮和应用程序中的一些常用命令。状态栏显示状态信息,通常位于应用程序窗口的底部。
+
+## PyQt6 QMainWindow
+`QMainWindow` 类提供了主程序窗口。在这里可以创建一个具有状态栏、工具栏和菜单栏的经典应用程序框架。
+## PyQt6 状态栏
+状态栏是显示状态信息的小部件。
+
+``` python
+# file: statusbar.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a statusbar.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QApplication
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.statusBar().showMessage('Ready')
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Statusbar')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+使用 `QMainWindow` 创建状态栏。
+``` python
+self.statusBar().showMessage('Ready')
+```
+使用 `QtGui.QMainWindow` 方法创建状态栏,该方法的创建了一个状态栏,并返回statusbar对象,再调用 `showMessage` 方法在状态栏上显示一条消息。
+
+## PyQt6 简单菜单
+菜单栏在GUI应用程序中很常见,它是位于各种菜单中的一组命令。(Mac OS 对菜单栏的处理是不同的,要得到类似的结果,我们可以添加下面这行: `menubar.setNativeMenuBar(False)`
+``` python
+# file: simple_menu.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a menubar. The
+menubar has one menu with an exit action.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QApplication
+from PyQt6.QtGui import QIcon, QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ exitAct = QAction(QIcon('exit.png'), '&Exit', self)
+ exitAct.setShortcut('Ctrl+Q')
+ exitAct.setStatusTip('Exit application')
+ exitAct.triggered.connect(QApplication.instance().quit)
+
+ self.statusBar()
+
+ menubar = self.menuBar()
+ fileMenu = menubar.addMenu('&File')
+ fileMenu.addAction(exitAct)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Simple menu')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+上门的示例中,创建了有一个菜单的菜单栏。这个菜单命令是终止应用,也绑定了快捷键 `Ctrl+Q`。示例中也创建了一个状态栏。
+``` python
+exitAct = QAction(QIcon('exit.png'), '&Exit', self)
+exitAct.setShortcut('Ctrl+Q')
+exitAct.setStatusTip('Exit application')
+```
+`QAction` 是行为抽象类,包括菜单栏,工具栏,或自定义键盘快捷方式。在上面的三行中,创建了一个带有特定图标和 'Exit' 标签的行为。此外,还为该行为定义了一个快捷方式。第三行创建一个状态提示,当我们将鼠标指针悬停在菜单项上时,状态栏中就会显示这个提示。
+``` python
+exitAct.triggered.connect(QApplication.instance().quit)
+```
+当选择指定的行为时,触发了一个信号,这个信号连接了 `QApplication` 组件的退出操作,这会终止这个应用程序。
+``` python
+menubar = self.menuBar()
+fileMenu = menubar.addMenu('&File')
+fileMenu.addAction(exitAction)
+```
+`menuBar` 方法创建了一个菜单栏,然后使用 `addMenu` 创建一个文件菜单,使用 `addAction` 创建一个行为。
+
+## PyQt6 子菜单
+子菜单是位于菜单里的菜单。
+``` python
+submenu.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a submenu.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QMenu, QApplication
+from PyQt6.QtGui import QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ menubar = self.menuBar()
+ fileMenu = menubar.addMenu('File')
+
+ impMenu = QMenu('Import', self)
+ impAct = QAction('Import mail', self)
+ impMenu.addAction(impAct)
+
+ newAct = QAction('New', self)
+
+ fileMenu.addAction(newAct)
+ fileMenu.addMenu(impMenu)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Submenu')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例中,有两个菜单项,一个在 File 菜单里,一个在 File 的 Import 子菜单里。
+
+``` python
+impMenu = QMenu('Import', self)
+```
+
+使用 `QMenu` 创建一个新菜单。
+
+``` python
+impAct = QAction('Import mail', self)
+impMenu.addAction(impAct)
+```
+
+使用`addAction` 给子菜单添加行为.
+
+![Submenu](./images/submenu.png)
+
+图示:子菜单
+
+## PyQt6 勾选菜单
+下面的示例中,创建了一个可以勾选的菜单。
+
+``` python
+# file: check_menu.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a checkable menu.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QApplication
+from PyQt6.QtGui import QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.statusbar = self.statusBar()
+ self.statusbar.showMessage('Ready')
+
+ menubar = self.menuBar()
+ viewMenu = menubar.addMenu('View')
+
+ viewStatAct = QAction('View statusbar', self, checkable=True)
+ viewStatAct.setStatusTip('View statusbar')
+ viewStatAct.setChecked(True)
+ viewStatAct.triggered.connect(self.toggleMenu)
+
+ viewMenu.addAction(viewStatAct)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Check menu')
+ self.show()
+
+
+ def toggleMenu(self, state):
+
+ if state:
+ self.statusbar.show()
+ else:
+ self.statusbar.hide()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+创建只有一个行为的 View 菜单。这个行为用来展现或者隐藏状态栏,如果状态栏可见,菜单是勾选的状态。
+
+``` python
+viewStatAct = QAction('View statusbar', self, checkable=True)
+```
+使用 `checkable` 参数创建一个可以勾选的菜单。
+
+``` python
+viewStatAct.setChecked(True)
+```
+因为状态栏默认可见,所以使用 `setChecked` 方法勾选菜单。
+
+``` python
+def toggleMenu(self, state):
+
+ if state:
+ self.statusbar.show()
+ else:
+ self.statusbar.hide()
+```
+根据行为的状态,设置状态栏的状态。
+
+![Check menu](./images/checkmenu.png)
+
+图书:勾选菜单
+## PyQt6 上下文菜单
+上下文菜单,也称为弹出菜单,是在某些上下文下出现的命令列表。例如,在 Opera 浏览器中,在网页上按下鼠标右键,我们会得到一个上下文菜单,这个菜单上,可以重新加载页面、返回或查看页面源代码。如果右击工具栏,会得到另一个用于管理工具栏的上下文菜单。
+
+``` python
+# file: context_menu.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a context menu.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QMenu, QApplication
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Context menu')
+ self.show()
+
+
+ def contextMenuEvent(self, event):
+
+ cmenu = QMenu(self)
+
+ newAct = cmenu.addAction("New")
+ openAct = cmenu.addAction("Open")
+ quitAct = cmenu.addAction("Quit")
+ action = cmenu.exec(self.mapToGlobal(event.pos()))
+
+ if action == quitAct:
+ QApplication.instance().quit()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+重新实现 `contextMenuEvent` 方法,调出一个上下文菜单。
+
+``` python
+action = cmenu.exec(self.mapToGlobal(event.pos()))
+```
+使用 `exec` 方法调出上下文菜单,通过鼠标事件对象获得鼠标坐标点,再调用 `mapToGlobal` 方法把组件的坐标设置成全局的屏幕坐标。
+``` python
+if action == quitAct:
+ QApplication.instance().quit()
+```
+如果上下文菜单触发的动作是退出动作,就终止程序。
+
+## PyQt6 工具栏
+菜单包含了一个应用程序里所有需要使用到的命令,工具栏则是放置常用命令的地方。
+
+``` python
+# file: toolbar.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a toolbar.
+The toolbar has one action, which
+terminates the application, if triggered.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QApplication
+from PyQt6.QtGui import QIcon, QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
+ exitAct.setShortcut('Ctrl+Q')
+ exitAct.triggered.connect(QApplication.instance().quit)
+
+ self.toolbar = self.addToolBar('Exit')
+ self.toolbar.addAction(exitAct)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Toolbar')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+上面的示例中创建了一个简单的状态栏,只有一个行为,关闭应用。
+
+``` python
+exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
+exitAct.setShortcut('Ctrl+Q')
+exitAct.triggered.connect(QApplication.instance().quit)
+```
+和上面菜单栏示例相似,创建一个行为对象,对象有标签,图标和快捷键。然后把 `QApplication` 的退出方法和行为发出的信号绑定。
+
+``` python
+self.toolbar = self.addToolBar('Exit')
+self.toolbar.addAction(exitAction)
+```
+使用 `addToolBar` 方法创建工具栏,然后使用 `addAction` 方法添加行为。
+
+![Toolbar](./images/toolbar.png)
+
+图示:工具栏
+## PyQt6 主窗口
+这是本章最后一个示例,这里创建一个菜单栏,一个工具栏和一个状态栏,并增加一个中心布局组件。
+
+``` python
+# file: main_window.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program creates a skeleton of
+a classic GUI application with a menubar,
+toolbar, statusbar, and a central widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QMainWindow, QTextEdit, QApplication
+from PyQt6.QtGui import QIcon, QAction
+
+
+class Example(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ textEdit = QTextEdit()
+ self.setCentralWidget(textEdit)
+
+ exitAct = QAction(QIcon('exit24.png'), 'Exit', self)
+ exitAct.setShortcut('Ctrl+Q')
+ exitAct.setStatusTip('Exit application')
+ exitAct.triggered.connect(self.close)
+
+ self.statusBar()
+
+ menubar = self.menuBar()
+ fileMenu = menubar.addMenu('&File')
+ fileMenu.addAction(exitAct)
+
+ toolbar = self.addToolBar('Exit')
+ toolbar.addAction(exitAct)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('Main window')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+示例展示了一个经典的 GUI 应用的布局,有一个菜单栏,一个工具栏和一个状态栏。
+``` python
+textEdit = QTextEdit()
+self.setCentralWidget(textEdit)
+```
+这里创建了一个文本编辑器组件,并把它设置到 `QMainWindow` 的中央。居中布局组件撑满了所有空白的部分。
+
+![Main window](./images/mainwindow.png)
+
+图示:主窗口
+
+本部分 PyQt6 教程,我们展示了菜单,工具栏,状态栏和主程序窗口。
\ No newline at end of file
diff --git a/translated/pyqt6/painting.md b/translated/pyqt6/painting.md
new file mode 100644
index 0000000..98e1eb4
--- /dev/null
+++ b/translated/pyqt6/painting.md
@@ -0,0 +1,580 @@
+# PyQt6 的绘制
+*最后更新于 2021.03.15*
+
+PyQt6绘画系统能够呈现矢量图形、图像和基于字体的文本轮廓。想要更改或增强现有的小部件,或者从头创建自定义小部件时,需要 PyQt6 工具包提供的绘图 API 进行绘制。
+## QPainter
+QPainter 在小部件和其他可绘制单元上执行底层绘制。从简单的线条到复杂的形状,它可以画任何东西。
+
+## paintEvent 方法
+绘制时由 paintEvent 方法完成的。绘制代码位于 QPainter 对象的开始和结束方法之间。它在小部件和其他绘制单元上执行底层绘制。
+
+## PyQt6 绘制文本
+从绘制一些 Unicode 文本开始。
+
+``` python
+# file: draw_text.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we draw text in Russian Cylliric.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter, QColor, QFont
+from PyQt6.QtCore import Qt
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.text = "Лев Николаевич Толстой\nАнна Каренина"
+
+ self.setGeometry(300, 300, 350, 300)
+ self.setWindowTitle('Drawing text')
+ self.show()
+
+
+ def paintEvent(self, event):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawText(event, qp)
+ qp.end()
+
+
+ def drawText(self, event, qp):
+
+ qp.setPen(QColor(168, 34, 3))
+ qp.setFont(QFont('Decorative', 10))
+ qp.drawText(event.rect(), Qt.Alignment.AlignCenter, self.text)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例中,绘制了一些西里尔字母,并水平和垂直对齐了文本。
+
+``` python
+def paintEvent(self, event):
+...
+```
+使用 paintEvent 完成绘画。
+
+``` python
+qp = QPainter()
+qp.begin(self)
+self.drawText(event, qp)
+qp.end()
+```
+`QPainter` 类负责所有的底层绘制。所有的绘制都在开始和结束方法之间。实际的绘制被委托给 drawText 方法。
+``` python
+qp.setPen(QColor(168, 34, 3))
+qp.setFont(QFont('Decorative', 10))
+```
+这里定义了绘制文本的笔触和字体。
+
+``` python
+qp.drawText(event.rect(), Qt.Alignment.AlignCenter, self.text)
+```
+drawText 方法在窗口上绘制文本。paintEvent 的rect方法返回需要更新的矩形。用 `Qt.Alignment.AlignCenter` 在两个维度上对齐文本。
+
+![Drawing text](./images/drawingtext.png)
+
+图示:绘制文本
+
+## PyQt6 绘制点
+点是绘制里最简单的图形对象。
+
+``` python
+# file: draw_points.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In the example, we draw randomly 1000 red points
+on the window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter
+from PyQt6.QtCore import Qt
+import sys, random
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setMinimumSize(50, 50)
+ self.setGeometry(300, 300, 350, 300)
+ self.setWindowTitle('Points')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawPoints(qp)
+ qp.end()
+
+
+ def drawPoints(self, qp):
+
+ qp.setPen(Qt.GlobalColor.red)
+ size = self.size()
+
+ for i in range(1000):
+
+ x = random.randint(1, size.width() - 1)
+ y = random.randint(1, size.height() - 1)
+ qp.drawPoint(x, y)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+上例中,在窗口里绘制了1000个随机的红点。
+
+``` python
+qp.setPen(Qt.GlobalColor.red)
+```
+用预定义 `Qt.GlobalColor.red` 常量常量把笔触设置为红色。
+``` python
+size = self.size()
+```
+每次改变窗口大小,都会产生一个绘制事件。获得当前窗口大小,根据这个大小把点分布到窗口上的各个位置。
+
+``` python
+qp.drawPoint(x, y)
+```
+使用 `drawPoint` 方法绘制点。
+
+![Points](./images/points.png)
+
+图示:点
+
+## PyQt6 颜色
+
+颜色是表示红色、绿色和蓝色 (RGB) 强度值组合的对象。有效的 RGB 值的范围是0到255。可以用不同的方法定义一种颜色。最常见的是RGB十进制值或十六进制值。还可以使用 RGBA 值,它代表红色、绿色、蓝色和 Alpha 通道,添加了透明度信息。Alpha 值为255定义完全不透明,0表示完全透明,也就是颜色不可见。
+
+``` python
+# file: colours.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example draws three rectangles in three
+different colours.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter, QColor
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 350, 100)
+ self.setWindowTitle('Colours')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawRectangles(qp)
+ qp.end()
+
+
+ def drawRectangles(self, qp):
+
+ col = QColor(0, 0, 0)
+ col.setNamedColor('#d4d4d4')
+ qp.setPen(col)
+
+ qp.setBrush(QColor(200, 0, 0))
+ qp.drawRect(10, 15, 90, 60)
+
+ qp.setBrush(QColor(255, 80, 0, 160))
+ qp.drawRect(130, 15, 90, 60)
+
+ qp.setBrush(QColor(25, 0, 90, 200))
+ qp.drawRect(250, 15, 90, 60)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+上例中,绘制了三个不同颜色的矩形。
+
+``` python
+color = QColor(0, 0, 0)
+color.setNamedColor('#d4d4d4')
+```
+使用16进制定义颜色。
+
+``` python
+qp.setBrush(QColor(200, 0, 0))
+qp.drawRect(10, 15, 90, 60)
+```
+这里定义一个笔刷并绘制一个矩形。画笔是一种基本的图形对象,用于绘制形状的背景。drawRect 方法接受四个参数,前两个是轴上的x和y值,第三和第四个参数是矩形的宽度和高度,使用选择的笔触和笔刷绘制矩形。
+
+![Colours](./images/colours.png)
+
+图示:颜色
+
+## PyQt6 QPen
+`QPen` 是一个基本图形对象,可以绘制线条,曲线和矩形,椭圆,多边形等形状的轮廓。
+
+``` python
+# file: pens.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example we draw 6 lines using
+different pen styles.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter, QPen
+from PyQt6.QtCore import Qt
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 280, 270)
+ self.setWindowTitle('Pen styles')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawLines(qp)
+ qp.end()
+
+
+ def drawLines(self, qp):
+
+ pen = QPen(Qt.GlobalColor.black, 2, Qt.PenStyle.SolidLine)
+
+ qp.setPen(pen)
+ qp.drawLine(20, 40, 250, 40)
+
+ pen.setStyle(Qt.PenStyle.DashLine)
+ qp.setPen(pen)
+ qp.drawLine(20, 80, 250, 80)
+
+ pen.setStyle(Qt.PenStyle.DashDotLine)
+ qp.setPen(pen)
+ qp.drawLine(20, 120, 250, 120)
+
+ pen.setStyle(Qt.PenStyle.DotLine)
+ qp.setPen(pen)
+ qp.drawLine(20, 160, 250, 160)
+
+ pen.setStyle(Qt.PenStyle.DashDotDotLine)
+ qp.setPen(pen)
+ qp.drawLine(20, 200, 250, 200)
+
+ pen.setStyle(Qt.PenStyle.CustomDashLine)
+ pen.setDashPattern([1, 4, 5, 4])
+ qp.setPen(pen)
+ qp.drawLine(20, 240, 250, 240)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+
+示例中,我们画了6条线。线条是用六种不同的笔触风格样式的。有五种预定义的笔触。我们也可以创建自定义笔触样式。最后一条线是使用自定义笔触风格样式的。
+
+``` python
+pen = QPen(Qt.GlobalColor.black, 2, Qt.PenStyle.SolidLine)
+```
+这里创建了一个 `QPen` 对象,颜色是黑色,宽度2像素,这样就能区别不同的笔触。`Qt.SolidLine` 是一个预定义的笔触。
+
+``` python
+pen.setStyle(Qt.PenStyle.CustomDashLine)
+pen.setDashPattern([1, 4, 5, 4])
+qp.setPen(pen)
+```
+这里我们自定义了一个笔触。样式设置为 `Qt.PenStyle。CustomDashLine`,用 `setDashPattern` 方法设置具体样式,参数一定是偶数个,奇数定义破折号,偶数定义空格。数字越大,空格或破折号就越大。这里设置的是1px横线,4px空格,5px横线,4px空格等等。
+
+![Pen styles](./images/penstyles.png)
+
+图示:笔触样式
+
+## PyQt6 QBrush
+`QBrush` 是一个基本图形对象。它用于绘制矩形、椭圆等形状的背景。笔刷有三种类型:预定义的笔刷、渐变或纹理模式。
+
+``` python
+# file: brushes.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example draws nine rectangles in different
+brush styles.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QApplication
+from PyQt6.QtGui import QPainter, QBrush
+from PyQt6.QtCore import Qt
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 355, 280)
+ self.setWindowTitle('Brushes')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ self.drawBrushes(qp)
+ qp.end()
+
+
+ def drawBrushes(self, qp):
+
+ brush = QBrush(Qt.BrushStyle.SolidPattern)
+ qp.setBrush(brush)
+ qp.drawRect(10, 15, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.Dense1Pattern)
+ qp.setBrush(brush)
+ qp.drawRect(130, 15, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.Dense2Pattern)
+ qp.setBrush(brush)
+ qp.drawRect(250, 15, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.DiagCrossPattern)
+ qp.setBrush(brush)
+ qp.drawRect(10, 105, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.Dense5Pattern)
+ qp.setBrush(brush)
+ qp.drawRect(130, 105, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.Dense6Pattern)
+ qp.setBrush(brush)
+ qp.drawRect(250, 105, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.HorPattern)
+ qp.setBrush(brush)
+ qp.drawRect(10, 195, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.VerPattern)
+ qp.setBrush(brush)
+ qp.drawRect(130, 195, 90, 60)
+
+ brush.setStyle(Qt.BrushStyle.BDiagPattern)
+ qp.setBrush(brush)
+ qp.drawRect(250, 195, 90, 60)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+示例中绘制了9个不同的矩形。
+
+``` python
+brush = QBrush(Qt.BrushStyle.SolidPattern)
+qp.setBrush(brush)
+qp.drawRect(10, 15, 90, 60)
+```
+这里定义了一个笔刷对象,调用 drawRect 方法绘制矩形。
+
+![Brushes](./images/brushes.png)
+
+图示:笔刷
+
+## 贝塞尔曲线
+贝塞尔曲线是三次方曲线。PyQt6 中的贝塞尔曲线可以用 `QPainterPath` 创建。画线路径是由许多图形构建块(如矩形、椭圆、直线和曲线)组成的对象。
+
+``` python
+# file: bezier_curve.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This program draws a Bézier curve with
+QPainterPath.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtGui import QPainter, QPainterPath
+from PyQt6.QtWidgets import QWidget, QApplication
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.setGeometry(300, 300, 380, 250)
+ self.setWindowTitle('Bézier curve')
+ self.show()
+
+
+ def paintEvent(self, e):
+
+ qp = QPainter()
+ qp.begin(self)
+ qp.setRenderHint(QPainter.RenderHints.Antialiasing)
+ self.drawBezierCurve(qp)
+ qp.end()
+
+
+ def drawBezierCurve(self, qp):
+
+ path = QPainterPath()
+ path.moveTo(30, 30)
+ path.cubicTo(30, 30, 200, 350, 350, 30)
+
+ qp.drawPath(path)
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+This example draws a Bézier curve.
+
+```python
+path = QPainterPath()
+path.moveTo(30, 30)
+path.cubicTo(30, 30, 200, 350, 350, 30)
+```
+使用 `QPainterPath` 创建贝塞尔曲线路径。使用 `cubicTo` 方法绘制曲线,该方法需要三个点:起始点,控制点,结束点。
+
+``` python
+qp.drawPath(path)
+```
+使用 drawPath 方法绘制最终的路径。
+
+![Bézier curve](./images/beziercurve.png)
+
+图示:贝塞尔曲线
+
+本章讲解了基本的绘画。
\ No newline at end of file
diff --git a/translated/pyqt6/tetris.md b/translated/pyqt6/tetris.md
new file mode 100644
index 0000000..50036d8
--- /dev/null
+++ b/translated/pyqt6/tetris.md
@@ -0,0 +1,859 @@
+# 俄罗斯方块
+*更新于 2021.05.05*
+
+本章实现一个俄罗斯方块游戏。
+
+## 简介
+俄罗斯方块游戏是有史以来最受欢迎的电脑游戏之一。最初的游戏是由俄罗斯程序员 Alexey Pajitnov 在1985年设计并编写的。从那时起,《俄罗斯方块》便以多种形式出现在几乎所有平台上。
+
+俄罗斯方块被称为掉落方块拼图游戏。在这款游戏中,我们有7种不同的形状,叫做砖块(tetrminoes):S形、Z形、T形、L形、线形、反向L形和方形。每个形状都是由四个正方形组成的。这些形状从顶部掉落。《俄罗斯方块》游戏的目标是移动和旋转形状,尽可能的拼到一起,如果拼成一行,这一行会消失,这样就能得分,直到方块堆叠到顶部,游戏结束。
+
+![Tetrominoes](./images/tetrominoes.png)
+
+图示:Tetrominoes
+
+PyQt6 目标是创建应用程序,有些其他的库的目标是创造电脑游戏。尽管如此,PyQt6 和其他库也可以用于创建简单的游戏。
+
+制作一个电脑游戏是提高编程技能的好方法。
+
+## 开发
+因为没有游戏砖块的图片,所以这里使用 PyQt6 编程工具包中的绘图 API 来绘制砖块。每一款电脑游戏的背后都有一个数学模型,俄罗斯方块也是如此。
+
+一些思路:
+
+- 使用 `QtCore.QBasicTimer` 创建游戏循环
+- 画出砖块
+- 砖块整体旋转或移动(不是分开操作)
+- 数学意义上,游戏面板是个简单的数字列表
+
+代码包含四个类:`Tetris`,`Board`,`Tetrominoe` 和 `Shape`。`Tetris` 类设置了游戏。`Board` 是编写游戏逻辑的地方。`Tetrominoe` 类包含所有俄罗斯方块的名称,`Shape` 类包含俄罗斯方块的代码。
+
+``` python
+# files: tetris.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This is a Tetris game clone.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import random
+import sys
+
+from PyQt6.QtCore import Qt, QBasicTimer, pyqtSignal
+from PyQt6.QtGui import QPainter, QColor
+from PyQt6.QtWidgets import QMainWindow, QFrame, QApplication
+
+
+class Tetris(QMainWindow):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+ """initiates application UI"""
+
+ self.tboard = Board(self)
+ self.setCentralWidget(self.tboard)
+
+ self.statusbar = self.statusBar()
+ self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
+
+ self.tboard.start()
+
+ self.resize(180, 380)
+ self.center()
+ self.setWindowTitle('Tetris')
+ self.show()
+
+
+ def center(self):
+ """centers the window on the screen"""
+
+ qr = self.frameGeometry()
+ cp = self.screen().availableGeometry().center()
+
+ qr.moveCenter(cp)
+ self.move(qr.topLeft())
+
+
+class Board(QFrame):
+
+ msg2Statusbar = pyqtSignal(str)
+
+ BoardWidth = 10
+ BoardHeight = 22
+ Speed = 300
+
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ self.initBoard()
+
+
+ def initBoard(self):
+ """initiates board"""
+
+ self.timer = QBasicTimer()
+ self.isWaitingAfterLine = False
+
+ self.curX = 0
+ self.curY = 0
+ self.numLinesRemoved = 0
+ self.board = []
+
+ self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+ self.isStarted = False
+ self.isPaused = False
+ self.clearBoard()
+
+
+ def shapeAt(self, x, y):
+ """determines shape at the board position"""
+
+ return self.board[(y * Board.BoardWidth) + x]
+
+
+ def setShapeAt(self, x, y, shape):
+ """sets a shape at the board"""
+
+ self.board[(y * Board.BoardWidth) + x] = shape
+
+
+ def squareWidth(self):
+ """returns the width of one square"""
+
+ return self.contentsRect().width() // Board.BoardWidth
+
+
+ def squareHeight(self):
+ """returns the height of one square"""
+
+ return self.contentsRect().height() // Board.BoardHeight
+
+
+ def start(self):
+ """starts game"""
+
+ if self.isPaused:
+ return
+
+ self.isStarted = True
+ self.isWaitingAfterLine = False
+ self.numLinesRemoved = 0
+ self.clearBoard()
+
+ self.msg2Statusbar.emit(str(self.numLinesRemoved))
+
+ self.newPiece()
+ self.timer.start(Board.Speed, self)
+
+
+ def pause(self):
+ """pauses game"""
+
+ if not self.isStarted:
+ return
+
+ self.isPaused = not self.isPaused
+
+ if self.isPaused:
+ self.timer.stop()
+ self.msg2Statusbar.emit("paused")
+
+ else:
+ self.timer.start(Board.Speed, self)
+ self.msg2Statusbar.emit(str(self.numLinesRemoved))
+
+ self.update()
+
+
+ def paintEvent(self, event):
+ """paints all shapes of the game"""
+
+ painter = QPainter(self)
+ rect = self.contentsRect()
+
+ boardTop = rect.bottom() - Board.BoardHeight * self.squareHeight()
+
+ for i in range(Board.BoardHeight):
+ for j in range(Board.BoardWidth):
+ shape = self.shapeAt(j, Board.BoardHeight - i - 1)
+
+ if shape != Tetrominoe.NoShape:
+ self.drawSquare(painter,
+ rect.left() + j * self.squareWidth(),
+ boardTop + i * self.squareHeight(), shape)
+
+ if self.curPiece.shape() != Tetrominoe.NoShape:
+
+ for i in range(4):
+ x = self.curX + self.curPiece.x(i)
+ y = self.curY - self.curPiece.y(i)
+ self.drawSquare(painter, rect.left() + x * self.squareWidth(),
+ boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
+ self.curPiece.shape())
+
+
+ def keyPressEvent(self, event):
+ """processes key press events"""
+
+ if not self.isStarted or self.curPiece.shape() == Tetrominoe.NoShape:
+ super(Board, self).keyPressEvent(event)
+ return
+
+ key = event.key()
+
+ if key == Qt.Key.Key_P:
+ self.pause()
+ return
+
+ if self.isPaused:
+ return
+
+ elif key == Qt.Key.Key_Left.value:
+ self.tryMove(self.curPiece, self.curX - 1, self.curY)
+
+ elif key == Qt.Key.Key_Right.value:
+ self.tryMove(self.curPiece, self.curX + 1, self.curY)
+
+ elif key == Qt.Key.Key_Down.value:
+ self.tryMove(self.curPiece.rotateRight(), self.curX, self.curY)
+
+ elif key == Qt.Key.Key_Up.value:
+ self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
+
+ elif key == Qt.Key.Key_Space.value:
+ self.dropDown()
+
+ elif key == Qt.Key.Key_D.value:
+ self.oneLineDown()
+
+ else:
+ super(Board, self).keyPressEvent(event)
+
+
+ def timerEvent(self, event):
+ """handles timer event"""
+
+ if event.timerId() == self.timer.timerId():
+
+ if self.isWaitingAfterLine:
+ self.isWaitingAfterLine = False
+ self.newPiece()
+ else:
+ self.oneLineDown()
+
+ else:
+ super(Board, self).timerEvent(event)
+
+
+ def clearBoard(self):
+ """clears shapes from the board"""
+
+ for i in range(Board.BoardHeight * Board.BoardWidth):
+ self.board.append(Tetrominoe.NoShape)
+
+
+ def dropDown(self):
+ """drops down a shape"""
+
+ newY = self.curY
+
+ while newY > 0:
+
+ if not self.tryMove(self.curPiece, self.curX, newY - 1):
+ break
+
+ newY -= 1
+
+ self.pieceDropped()
+
+
+ def oneLineDown(self):
+ """goes one line down with a shape"""
+
+ if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
+ self.pieceDropped()
+
+
+ def pieceDropped(self):
+ """after dropping shape, remove full lines and create new shape"""
+
+ for i in range(4):
+ x = self.curX + self.curPiece.x(i)
+ y = self.curY - self.curPiece.y(i)
+ self.setShapeAt(x, y, self.curPiece.shape())
+
+ self.removeFullLines()
+
+ if not self.isWaitingAfterLine:
+ self.newPiece()
+
+
+ def removeFullLines(self):
+ """removes all full lines from the board"""
+
+ numFullLines = 0
+ rowsToRemove = []
+
+ for i in range(Board.BoardHeight):
+
+ n = 0
+ for j in range(Board.BoardWidth):
+ if not self.shapeAt(j, i) == Tetrominoe.NoShape:
+ n = n + 1
+
+ if n == 10:
+ rowsToRemove.append(i)
+
+ rowsToRemove.reverse()
+
+ for m in rowsToRemove:
+
+ for k in range(m, Board.BoardHeight):
+ for l in range(Board.BoardWidth):
+ self.setShapeAt(l, k, self.shapeAt(l, k + 1))
+
+ numFullLines = numFullLines + len(rowsToRemove)
+
+ if numFullLines > 0:
+ self.numLinesRemoved = self.numLinesRemoved + numFullLines
+ self.msg2Statusbar.emit(str(self.numLinesRemoved))
+
+ self.isWaitingAfterLine = True
+ self.curPiece.setShape(Tetrominoe.NoShape)
+ self.update()
+
+
+ def newPiece(self):
+ """creates a new shape"""
+
+ self.curPiece = Shape()
+ self.curPiece.setRandomShape()
+ self.curX = Board.BoardWidth // 2 + 1
+ self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
+
+ if not self.tryMove(self.curPiece, self.curX, self.curY):
+
+ self.curPiece.setShape(Tetrominoe.NoShape)
+ self.timer.stop()
+ self.isStarted = False
+ self.msg2Statusbar.emit("Game over")
+
+
+ def tryMove(self, newPiece, newX, newY):
+ """tries to move a shape"""
+
+ for i in range(4):
+
+ x = newX + newPiece.x(i)
+ y = newY - newPiece.y(i)
+
+ if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
+ return False
+
+ if self.shapeAt(x, y) != Tetrominoe.NoShape:
+ return False
+
+ self.curPiece = newPiece
+ self.curX = newX
+ self.curY = newY
+ self.update()
+
+ return True
+
+
+ def drawSquare(self, painter, x, y, shape):
+ """draws a square of a shape"""
+
+ colorTable = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC,
+ 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
+
+ color = QColor(colorTable[shape])
+ painter.fillRect(x + 1, y + 1, self.squareWidth() - 2,
+ self.squareHeight() - 2, color)
+
+ painter.setPen(color.lighter())
+ painter.drawLine(x, y + self.squareHeight() - 1, x, y)
+ painter.drawLine(x, y, x + self.squareWidth() - 1, y)
+
+ painter.setPen(color.darker())
+ painter.drawLine(x + 1, y + self.squareHeight() - 1,
+ x + self.squareWidth() - 1, y + self.squareHeight() - 1)
+ painter.drawLine(x + self.squareWidth() - 1,
+ y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)
+
+
+class Tetrominoe:
+
+ NoShape = 0
+ ZShape = 1
+ SShape = 2
+ LineShape = 3
+ TShape = 4
+ SquareShape = 5
+ LShape = 6
+ MirroredLShape = 7
+
+
+class Shape:
+
+ coordsTable = (
+ ((0, 0), (0, 0), (0, 0), (0, 0)),
+ ((0, -1), (0, 0), (-1, 0), (-1, 1)),
+ ((0, -1), (0, 0), (1, 0), (1, 1)),
+ ((0, -1), (0, 0), (0, 1), (0, 2)),
+ ((-1, 0), (0, 0), (1, 0), (0, 1)),
+ ((0, 0), (1, 0), (0, 1), (1, 1)),
+ ((-1, -1), (0, -1), (0, 0), (0, 1)),
+ ((1, -1), (0, -1), (0, 0), (0, 1))
+ )
+
+ def __init__(self):
+
+ self.coords = [[0, 0] for i in range(4)]
+ self.pieceShape = Tetrominoe.NoShape
+
+ self.setShape(Tetrominoe.NoShape)
+
+
+ def shape(self):
+ """returns shape"""
+
+ return self.pieceShape
+
+
+ def setShape(self, shape):
+ """sets a shape"""
+
+ table = Shape.coordsTable[shape]
+
+ for i in range(4):
+ for j in range(2):
+ self.coords[i][j] = table[i][j]
+
+ self.pieceShape = shape
+
+
+ def setRandomShape(self):
+ """chooses a random shape"""
+
+ self.setShape(random.randint(1, 7))
+
+
+ def x(self, index):
+ """returns x coordinate"""
+
+ return self.coords[index][0]
+
+
+ def y(self, index):
+ """returns y coordinate"""
+
+ return self.coords[index][1]
+
+
+ def setX(self, index, x):
+ """sets x coordinate"""
+
+ self.coords[index][0] = x
+
+
+ def setY(self, index, y):
+ """sets y coordinate"""
+
+ self.coords[index][1] = y
+
+
+ def minX(self):
+ """returns min x value"""
+
+ m = self.coords[0][0]
+ for i in range(4):
+ m = min(m, self.coords[i][0])
+
+ return m
+
+
+ def maxX(self):
+ """returns max x value"""
+
+ m = self.coords[0][0]
+ for i in range(4):
+ m = max(m, self.coords[i][0])
+
+ return m
+
+
+ def minY(self):
+ """returns min y value"""
+
+ m = self.coords[0][1]
+ for i in range(4):
+ m = min(m, self.coords[i][1])
+
+ return m
+
+
+ def maxY(self):
+ """returns max y value"""
+
+ m = self.coords[0][1]
+ for i in range(4):
+ m = max(m, self.coords[i][1])
+
+ return m
+
+
+ def rotateLeft(self):
+ """rotates shape to the left"""
+
+ if self.pieceShape == Tetrominoe.SquareShape:
+ return self
+
+ result = Shape()
+ result.pieceShape = self.pieceShape
+
+ for i in range(4):
+ result.setX(i, self.y(i))
+ result.setY(i, -self.x(i))
+
+ return result
+
+
+ def rotateRight(self):
+ """rotates shape to the right"""
+
+ if self.pieceShape == Tetrominoe.SquareShape:
+ return self
+
+ result = Shape()
+ result.pieceShape = self.pieceShape
+
+ for i in range(4):
+ result.setX(i, -self.y(i))
+ result.setY(i, self.x(i))
+
+ return result
+
+
+def main():
+
+ app = QApplication([])
+ tetris = Tetris()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+
+游戏被简化了一点,这样更容易理解。游戏启动后立即开始。我们可以按 `P` 键暂停游戏。按空格键,方块会立即掉落到底部。游戏以固定的速度运行,没有任加速度。分数是消除的行数。
+
+```python
+self.tboard = Board(self)
+self.setCentralWidget(self.tboard)
+```
+实例化 Board 类,并将其设置为应用程序的中心部件。
+
+```python
+self.statusbar = self.statusBar()
+self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
+```
+这里创建一个状态栏,用于显示消息。我们将显示三种可能的消息:已经删除的行数,暂停消息,或游戏结束消息。`msg2Statusbar` 是在 Board 类中实现的自定义信号。`showMessage` 是一个内置方法,用于在状态栏上显示消息。
+
+``` python
+self.tboard.start()
+```
+初始化游戏。
+
+```python
+class Board(QFrame):
+
+ msg2Statusbar = pyqtSignal(str)
+...
+```
+`pyqtSignal` 创建了一个自定义信号。想在状态栏上展示信息或者分数,触发 `msg2Statusbar` 信号即可。
+
+``` python
+BoardWidth = 10
+BoardHeight = 22
+Speed = 300
+```
+这些是 `Board` 的参数。`BoardWidth` 和 `BoardHeight` 定义画板的宽高。 `Speed` 是游戏的速度,每300毫秒游戏循环进行一次。
+
+``` python
+...
+self.curX = 0
+self.curY = 0
+self.numLinesRemoved = 0
+self.board = []
+...
+```
+在 `initBoard` 方法中,我们初始化一些变量。`self.board` 是一个从0到7的数字列表,代表了砖块的各种形状和位置信息。
+
+``` python
+def shapeAt(self, x, y):
+ """determines shape at the board position"""
+
+ return self.board[(y * Board.BoardWidth) + x]
+```
+`shapeAt` 方法决定了形状的位置。
+```python
+def squareWidth(self):
+ """returns the width of one square"""
+
+ return self.contentsRect().width() // Board.BoardWidth
+```
+画板可以动态调整大小。`squareWidth` 计算并返回单个形状的像素宽度。`Board.BoardWidth` 是画板的大小。
+
+```python
+def pause(self):
+ """pauses game"""
+
+ if not self.isStarted:
+ return
+
+ self.isPaused = not self.isPaused
+
+ if self.isPaused:
+ self.timer.stop()
+ self.msg2Statusbar.emit("paused")
+
+ else:
+ self.timer.start(Board.Speed, self)
+ self.msg2Statusbar.emit(str(self.numLinesRemoved))
+
+ self.update()
+```
+`pause` 方法暂停游戏,停止计时并在状态栏上显示一个信息。
+
+```python
+def paintEvent(self, event):
+ """paints all shapes of the game"""
+
+ painter = QPainter(self)
+ rect = self.contentsRect()
+...
+```
+`paintEvent` 方法内绘制游戏。`QPainter` 是 PyQt6 里执行底层绘制的方法。
+
+```python
+for i in range(Board.BoardHeight):
+ for j in range(Board.BoardWidth):
+ shape = self.shapeAt(j, Board.BoardHeight - i - 1)
+
+ if shape != Tetrominoe.NoShape:
+ self.drawSquare(painter,
+ rect.left() + j * self.squareWidth(),
+ boardTop + i * self.squareHeight(), shape)
+```
+游戏绘制分为两个步骤。在第一步中,我们画出所有的形状,或者已经落在画板底部的形状。所有的方格都记录在 `self.board` 的变量列表。可以使用 `shapeAt` 方法访问该变量。
+
+```python
+if self.curPiece.shape() != Tetrominoe.NoShape:
+
+ for i in range(4):
+
+ x = self.curX + self.curPiece.x(i)
+ y = self.curY - self.curPiece.y(i)
+ self.drawSquare(painter, rect.left() + x * self.squareWidth(),
+ boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
+ self.curPiece.shape())
+```
+第二步画出正在下落的砖块。
+
+```python
+elif key == Qt.Key.Key_Right.value:
+ self.tryMove(self.curPiece, self.curX + 1, self.curY)
+```
+在 `keyPressEvent` 方法中,我们检查按下的键。如果按下右箭头键,将尝试部件向右移动。说“尝试”是因为它可能无法移动。
+
+```python
+elif key == Qt.Key.Key_Up.value:
+ self.tryMove(self.curPiece.rotateLeft(), self.curX, self.curY)
+```
+`Up` 键向左旋转砖块。
+
+```python
+elif key == Qt.Key.Key_Space.value:
+ self.dropDown()
+```
+`Space` 键会让砖块直接落到底部。
+
+```python
+elif key == Qt.Key.Key_D.value:
+ self.oneLineDown()
+```
+按下D键,则会让砖块加速下落一会。
+
+```python
+def timerEvent(self, event):
+ """handles timer event"""
+
+ if event.timerId() == self.timer.timerId():
+
+ if self.isWaitingAfterLine:
+ self.isWaitingAfterLine = False
+ self.newPiece()
+ else:
+ self.oneLineDown()
+
+ else:
+ super(Board, self).timerEvent(event)
+```
+在计时器事件中,要么在前一个砖块到底部之后创建一个新的,要么将一个砖块向下移动一行。
+
+```python
+def clearBoard(self):
+ """clears shapes from the board"""
+
+ for i in range(Board.BoardHeight * Board.BoardWidth):
+ self.board.append(Tetrominoe.NoShape)
+```
+`clearBoard` 方法用设置 `Tetrominoe.NoShape` 的方式做到清空画板上的全部砖块。
+
+```python
+def removeFullLines(self):
+ """removes all full lines from the board"""
+
+ numFullLines = 0
+ rowsToRemove = []
+
+ for i in range(Board.BoardHeight):
+
+ n = 0
+ for j in range(Board.BoardWidth):
+ if not self.shapeAt(j, i) == Tetrominoe.NoShape:
+ n = n + 1
+
+ if n == 10:
+ rowsToRemove.append(i)
+
+ rowsToRemove.reverse()
+
+
+ for m in rowsToRemove:
+
+ for k in range(m, Board.BoardHeight):
+ for l in range(Board.BoardWidth):
+ self.setShapeAt(l, k, self.shapeAt(l, k + 1))
+
+ numFullLines = numFullLines + len(rowsToRemove)
+ ...
+```
+如果砖块落在底部,就调用 `removeFullLines` 方法,找出所有完整的一行并删除。将所有行移动到当前整行的位置上,做到删除的效果。注意,我们颠倒了要删除的行的顺序,不然会出现BUG,在我们的例子中,我们使用了 `naive gravity`,这意味着不是整行的砖块可能漂浮在空白的间隙之上。
+
+```python
+def newPiece(self):
+ """creates a new shape"""
+
+ self.curPiece = Shape()
+ self.curPiece.setRandomShape()
+ self.curX = Board.BoardWidth // 2 + 1
+ self.curY = Board.BoardHeight - 1 + self.curPiece.minY()
+
+ if not self.tryMove(self.curPiece, self.curX, self.curY):
+
+ self.curPiece.setShape(Tetrominoe.NoShape)
+ self.timer.stop()
+ self.isStarted = False
+ self.msg2Statusbar.emit("Game over")
+```
+`newPiece` 方法创建随机的砖块,如果创建的砖块不能到达初始位置,游戏结束。
+
+``` python
+def tryMove(self, newPiece, newX, newY):
+ """tries to move a shape"""
+
+ for i in range(4):
+
+ x = newX + newPiece.x(i)
+ y = newY - newPiece.y(i)
+
+ if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
+ return False
+
+ if self.shapeAt(x, y) != Tetrominoe.NoShape:
+ return False
+
+ self.curPiece = newPiece
+ self.curX = newX
+ self.curY = newY
+ self.update()
+
+ return True
+```
+`tryMove` 方法尝试移动一个砖块,如果砖块在画板或者另外一个砖块的边缘,返回 `False`。否则就执行移动。
+
+``` python
+class Tetrominoe:
+
+ NoShape = 0
+ ZShape = 1
+ SShape = 2
+ LineShape = 3
+ TShape = 4
+ SquareShape = 5
+ LShape = 6
+ MirroredLShape = 7
+```
+`Tetrominoe` 类包含了所有形状的名称,这里也有一个叫 `NoShape` 的空形状。
+
+`Shape` 类保存了砖块的信息。
+
+``` python
+class Shape(object):
+
+ coordsTable = (
+ ((0, 0), (0, 0), (0, 0), (0, 0)),
+ ((0, -1), (0, 0), (-1, 0), (-1, 1)),
+ ...
+ )
+...
+```
+`coordsTable` 元组里包括了所有砖块的组合坐标,可以从这个模板里拼出需要的砖块。
+
+```python
+self.coords = [[0,0] for i in range(4)]
+```
+初始化时,创建一个空的坐标列表,用来存储砖块的坐标。
+
+![Coordinates](./images/coordinates.png)
+
+图示: 坐标
+
+上面的图能帮助我们更好的理解坐标值的意义。比如,`(0, -1), (0, 0), (-1, 0), (-1, -1)
+` 代表了Z字形的砖块,图示里就是这个形状。
+
+``` python
+def rotateLeft(self):
+ """rotates shape to the left"""
+
+ if self.pieceShape == Tetrominoe.SquareShape:
+ return self
+
+ result = Shape()
+ result.pieceShape = self.pieceShape
+
+ for i in range(4):
+
+ result.setX(i, self.y(i))
+ result.setY(i, -self.x(i))
+
+ return result
+```
+`rotateLeft` 方法向左旋转砖块。方形没必要旋转,所以就直接返回当前的对象。当砖块发生旋转时,会产生一个新的对象代表这个旋转后的砖块。
+
+![Tetris](./images/tetris.png)
+
+图示: 俄罗斯方块
\ No newline at end of file
diff --git a/translated/pyqt6/widgets.md b/translated/pyqt6/widgets.md
new file mode 100644
index 0000000..8634ae4
--- /dev/null
+++ b/translated/pyqt6/widgets.md
@@ -0,0 +1,529 @@
+# PyQt6 组件(一)
+*最后更新于 2021.05.03*
+
+
+组件是应用程序的基础组成部分。PyQt6 具有各种各样的小部件,包括按钮、复选框、滑块或列表框。在本节教程中,我们将介绍几个有用的小部件:QCheckBox、QPushButton、QSlider、QProgressBar和QCalendarWidget。
+
+## PyQt6 QCheckBox
+`QCheckBox` 组件有两个状态:选中和非选中。由个选框和文字组成,主要用于表示某个属性时开启还是关闭。
+
+``` python
+# file: check_box.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, a QCheckBox widget
+is used to toggle the title of a window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import QWidget, QCheckBox, QApplication
+from PyQt6.QtCore import Qt
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ cb = QCheckBox('Show title', self)
+ cb.move(20, 20)
+ cb.toggle()
+ cb.stateChanged.connect(self.changeTitle)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('QCheckBox')
+ self.show()
+
+
+ def changeTitle(self, state):
+
+ if state == Qt.CheckState.Checked.value:
+ self.setWindowTitle('QCheckBox')
+ else:
+ self.setWindowTitle(' ')
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+创建了一个切换窗口标题的复选框。
+``` python
+cb = QCheckBox('Show title', self)
+```
+这是 `QCheckBox` 构造器。
+
+``` python
+cb.toggle()
+```
+设置了窗口标题,所以这里勾选上复选框。
+``` python
+cb.stateChanged.connect(self.changeTitle)
+```
+把用户定义的 `changeTitle` 方法和 `stateChanged` 信号连接起来。`changeTitle` 方法用来切换窗口标题。
+``` python
+if state == Qt.CheckState.Checked.value:
+ self.setWindowTitle('QCheckBox')
+else:
+ self.setWindowTitle(' ')
+```
+组件的状态是 `changeTitle` 方法改变变量得到的。如果选中组件,就设置窗口的标题。否则,标题栏是一个空字符串。
+
+![QCheckBox](./images/qcheckbox.png)
+
+图示:复选框
+
+## 切换按钮
+切换按钮是 `QPushButton` 的一个特殊情况。它又两个状态:按下与否,鼠标点击触发。
+
+``` python
+# file: toggle_button.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we create three toggle buttons.
+They control the background color of a QFrame.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QPushButton,
+ QFrame, QApplication)
+from PyQt6.QtGui import QColor
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.col = QColor(0, 0, 0)
+
+ redb = QPushButton('Red', self)
+ redb.setCheckable(True)
+ redb.move(10, 10)
+
+ redb.clicked[bool].connect(self.setColor)
+
+ greenb = QPushButton('Green', self)
+ greenb.setCheckable(True)
+ greenb.move(10, 60)
+
+ greenb.clicked[bool].connect(self.setColor)
+
+ blueb = QPushButton('Blue', self)
+ blueb.setCheckable(True)
+ blueb.move(10, 110)
+
+ blueb.clicked[bool].connect(self.setColor)
+
+ self.square = QFrame(self)
+ self.square.setGeometry(150, 20, 100, 100)
+ self.square.setStyleSheet("QWidget { background-color: %s }" %
+ self.col.name())
+
+ self.setGeometry(300, 300, 300, 250)
+ self.setWindowTitle('Toggle button')
+ self.show()
+
+
+ def setColor(self, pressed):
+
+ source = self.sender()
+
+ if pressed:
+ val = 255
+ else:
+ val = 0
+
+ if source.text() == "Red":
+ self.col.setRed(val)
+ elif source.text() == "Green":
+ self.col.setGreen(val)
+ else:
+ self.col.setBlue(val)
+
+ self.square.setStyleSheet("QFrame { background-color: %s }" %
+ self.col.name())
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+示例中,创建了三个切换按钮和一个 `QWidget`。并将 `QWidget` 的背景颜色设置为黑色。切换按钮用于切换颜色值为红色、绿色和蓝色。组件背景颜色取决于按下的按钮。
+
+``` python
+self.col = QColor(0, 0, 0)
+```
+初始化颜色为黑色。
+``` python
+redb = QPushButton('Red', self)
+redb.setCheckable(True)
+redb.move(10, 10)
+```
+创建一个 `QPushButton` 并调用 `setCheckable` 方法设为为可选,就创建了一个切换按钮。
+``` python
+redb.clicked[bool].connect(self.setColor)
+```
+把用户自定义的方法和点击信号绑定,用点击信号改变一个布尔值。
+``` python
+source = self.sender()
+```
+获取到点击的按钮。
+``` python
+if source.text() == "Red":
+ self.col.setRed(val)
+```
+如果点击的是红色按钮,就相应的把颜色改成红色。
+``` python
+self.square.setStyleSheet("QFrame { background-color: %s }" %
+ self.col.name())
+```
+使用样式表修改背景颜色,修改样式需要调用 `setStyleSheet` 方法。
+
+![Toggle button](./images/togglebutton.png)
+
+图示:切换按钮
+
+## PyQt6 QSlider
+QSlider是一个有简单手柄的小部件,这个手柄可以前后拖动。通过这种方式,我们可以为特定的任务选择一个值。有时使用滑块比输入数字或使用旋转框更自然。
+
+在我们的示例中,我们显示了一个滑块和一个标签。标签显示一个图像。滑块控制标签。
+
+在我们的示例中,我们显示了一个滑块和一个标签。标签是一个图像。滑块控制标签。
+
+``` python
+# file: slider.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows a QSlider widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QSlider,
+ QLabel, QApplication)
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QPixmap
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ sld = QSlider(Qt.Orientations.Horizontal, self)
+ sld.setFocusPolicy(Qt.FocusPolicy.NoFocus)
+ sld.setGeometry(30, 40, 200, 30)
+ sld.valueChanged[int].connect(self.changeValue)
+
+ self.label = QLabel(self)
+ self.label.setPixmap(QPixmap('mute.png'))
+ self.label.setGeometry(250, 40, 80, 30)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('QSlider')
+ self.show()
+
+
+ def changeValue(self, value):
+
+ if value == 0:
+
+ self.label.setPixmap(QPixmap('mute.png'))
+ elif 0 < value <= 30:
+
+ self.label.setPixmap(QPixmap('min.png'))
+ elif 30 < value < 80:
+
+ self.label.setPixmap(QPixmap('med.png'))
+ else:
+
+ self.label.setPixmap(QPixmap('max.png'))
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+示例中,模拟了音量控制。通过拖动滑块的手柄,我们可以改变标签上的图像。
+
+``` python
+sld = QSlider(Qt.Orientations.Horizontal, self)
+```
+
+创建一个水平的 `QSlider`。
+``` python
+self.label = QLabel(self)
+self.label.setPixmap(QPixmap('mute.png'))
+```
+创建一个 `QLabel` 组件,并给它初始化一个静音的图标。
+``` python
+sld.valueChanged[int].connect(self.changeValue)
+```
+把 `valueChanged` 信号和用户定义的 `changeValue` 方法绑定。
+``` python
+if value == 0:
+ self.label.setPixmap(QPixmap('mute.png'))
+...
+```
+根据滑块的值,修改标签的图像。在上面的代码中,如果滑块等于零,把标签改为mute.png图像。
+
+![QSlider widget](./images/qslider.png)
+
+图示:QSlider 组件
+
+## PyQt6 QProgressBar
+
+进度条是一个用于处理冗长任务的小部件。它是动态的,以便用户知道任务正在进行中。`QProgressBar` 小部件在 PyQt6 工具包中提供了一个水平或垂直的进度条。可以设置进度条的最小值和最大值,默认值为0和99。
+
+``` python
+# file: progressbar.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows a QProgressBar widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QProgressBar,
+ QPushButton, QApplication)
+from PyQt6.QtCore import QBasicTimer
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.pbar = QProgressBar(self)
+ self.pbar.setGeometry(30, 40, 200, 25)
+
+ self.btn = QPushButton('Start', self)
+ self.btn.move(40, 80)
+ self.btn.clicked.connect(self.doAction)
+
+ self.timer = QBasicTimer()
+ self.step = 0
+
+ self.setGeometry(300, 300, 280, 170)
+ self.setWindowTitle('QProgressBar')
+ self.show()
+
+
+ def timerEvent(self, e):
+
+ if self.step >= 100:
+
+ self.timer.stop()
+ self.btn.setText('Finished')
+ return
+
+ self.step = self.step + 1
+ self.pbar.setValue(self.step)
+
+
+ def doAction(self):
+
+ if self.timer.isActive():
+ self.timer.stop()
+ self.btn.setText('Start')
+ else:
+ self.timer.start(100, self)
+ self.btn.setText('Stop')
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+示例中,有一个水平进度条和一个按钮,点击按钮可以启动和停止进度条。
+``` python
+self.pbar = QProgressBar(self)
+```
+这是 `QProgressBar` 的构造器。
+``` python
+self.timer = QBasicTimer()
+```
+使用定时器对象启动进度条。
+``` python
+self.timer.start(100, self)
+```
+调用定时器的开始方法,触发定时器事件。方法有两个参数,超时时间和接收事件的对象。
+``` python
+def timerEvent(self, e):
+
+ if self.step >= 100:
+
+ self.timer.stop()
+ self.btn.setText('Finished')
+ return
+
+ self.step = self.step + 1
+ self.pbar.setValue(self.step)
+```
+每个`QObject` 和它的后代都有一个 `timerEvent` 事件处理器,这里实现一些函数处理这些事件。
+``` python
+def doAction(self):
+
+ if self.timer.isActive():
+ self.timer.stop()
+ self.btn.setText('Start')
+
+ else:
+ self.timer.start(100, self)
+ self.btn.setText('Stop')
+```
+在 `doAction` 方法里,处理定时器的开启和暂停。
+
+![QProgressBar](./images/qprogressbar.png)
+
+图示:QProgressBar
+
+## PyQt6 QCalendarWidget
+`QCalendarWidget` 提供了一个月视图的日历组件,它能让用户简单直观的选择日期。
+
+``` python
+# file: calendar.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows a QCalendarWidget widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QCalendarWidget,
+ QLabel, QApplication, QVBoxLayout)
+from PyQt6.QtCore import QDate
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ vbox = QVBoxLayout(self)
+
+ cal = QCalendarWidget(self)
+ cal.setGridVisible(True)
+ cal.clicked[QDate].connect(self.showDate)
+
+ vbox.addWidget(cal)
+
+ self.lbl = QLabel(self)
+ date = cal.selectedDate()
+ self.lbl.setText(date.toString())
+
+ vbox.addWidget(self.lbl)
+
+ self.setLayout(vbox)
+
+ self.setGeometry(300, 300, 350, 300)
+ self.setWindowTitle('Calendar')
+ self.show()
+
+
+ def showDate(self, date):
+ self.lbl.setText(date.toString())
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+该示例有一个日历组件和一个标签组件,选择的日期显示在标签组件里。
+``` python
+cal = QCalendarWidget(self)
+```
+创建了一个 `QCalendarWidget`。
+``` python
+cal.clicked[QDate].connect(self.showDate)
+```
+选中一个日期,会触发 `clicked[QDate]` 信号,信号是和用户定义的 `showDate` 方法绑定。
+``` python
+def showDate(self, date):
+
+ self.lbl.setText(date.toString())
+```
+调用 `selectedDate` 方法获取到选中的日期,再把日期转换成字符串,设置到标签组件里。
\ No newline at end of file
diff --git a/translated/pyqt6/widgets2.md b/translated/pyqt6/widgets2.md
new file mode 100644
index 0000000..8a4304f
--- /dev/null
+++ b/translated/pyqt6/widgets2.md
@@ -0,0 +1,344 @@
+# PyQt6 组件(二)
+*最后更新于 2021.05.05*
+
+本章继续介绍 PyQt6 组件,包含了`QPixmap`, `QLineEdit`, `QSplitter` 和 `QComboBox`。
+
+## PyQt6 QPixmap
+`QPixmap` 是用于处理图像的小组件,为显示图像进行了优化。下面是使用 `QPixmap` 渲染图像的示例。
+
+``` python
+# file: pixmap.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+In this example, we display an image
+on the window.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+from PyQt6.QtWidgets import (QWidget, QHBoxLayout,
+ QLabel, QApplication)
+from PyQt6.QtGui import QPixmap
+import sys
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ hbox = QHBoxLayout(self)
+ pixmap = QPixmap('sid.jpg')
+
+ lbl = QLabel(self)
+ lbl.setPixmap(pixmap)
+
+ hbox.addWidget(lbl)
+ self.setLayout(hbox)
+
+ self.move(300, 200)
+ self.setWindowTitle('Sid')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+在窗口里展示了一个图片。
+``` python
+pixmap = QPixmap('sid.jpg')
+```
+用文件名作为参数创建一个 `QPixmap` 对象。
+``` python
+lbl = QLabel(self)
+lbl.setPixmap(pixmap)
+```
+然后把 `pixmap` 放到 `QLabel` 组件里。
+
+## PyQt6 QLineEdit
+QLineEdit是一个可以输入单行文本的组件,它有撤消和重做、剪切和粘贴以及拖放功能。
+
+``` python
+# file: line_edit.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows text which
+is entered in a QLineEdit
+in a QLabel widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+from PyQt6.QtWidgets import (QWidget, QLabel,
+ QLineEdit, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.lbl = QLabel(self)
+ qle = QLineEdit(self)
+
+ qle.move(60, 100)
+ self.lbl.move(60, 40)
+
+ qle.textChanged[str].connect(self.onChanged)
+
+ self.setGeometry(300, 300, 350, 250)
+ self.setWindowTitle('QLineEdit')
+ self.show()
+
+
+ def onChanged(self, text):
+
+ self.lbl.setText(text)
+ self.lbl.adjustSize()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+本例中有一个 `QLineEdit` 组件和一个标签,在编辑器里输入的文本会立即显示在标签里。
+``` python
+qle = QLineEdit(self)
+```
+创建一个 `QLineEdit` 组件。
+``` python
+qle.textChanged[str].connect(self.onChanged)
+```
+如果编辑器的文本发生了变化,就调用 `onChanged` 方法。
+``` python
+def onChanged(self, text):
+
+ self.lbl.setText(text)
+ self.lbl.adjustSize()
+```
+在 `onChanged` 里,把输入的文本设置到标签组件里,同时使用 `adjustSize` 方法调整文字显示。
+
+![QLineEdit](./images/qlineedit.png)
+
+图示:QLineEdit
+
+## PyQt6 QSplitter
+`QSplitter` 允许用户通过拖动子部件之间的边界来控制子部件的大小。下面是被两个分割条分开的三个`QFrame` 组件的示例。
+``` python
+# file: splitter.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows
+how to use QSplitter widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import (QWidget, QHBoxLayout, QFrame,
+ QSplitter, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ hbox = QHBoxLayout(self)
+
+ topleft = QFrame(self)
+ topleft.setFrameShape(QFrame.Shape.StyledPanel)
+
+ topright = QFrame(self)
+ topright.setFrameShape(QFrame.Shape.StyledPanel)
+
+ bottom = QFrame(self)
+ bottom.setFrameShape(QFrame.Shape.StyledPanel)
+
+ splitter1 = QSplitter(Qt.Orientations.Horizontal)
+ splitter1.addWidget(topleft)
+ splitter1.addWidget(topright)
+
+ splitter2 = QSplitter(Qt.Orientations.Vertical)
+ splitter2.addWidget(splitter1)
+ splitter2.addWidget(bottom)
+
+ hbox.addWidget(splitter2)
+ self.setLayout(hbox)
+
+ self.setGeometry(300, 300, 450, 400)
+ self.setWindowTitle('QSplitter')
+ self.show()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+这里有三个 QFrame 组件和连个 QSplitter 组件,注意,在某些主题里,分割条可能不容易看到。
+``` python
+topleft = QFrame(self)
+topleft.setFrameShape(QFrame.Shape.StyledPanel)
+```
+给框架组件设置一些样式,这样更容易看清楚边界。
+``` python
+splitter1 = QSplitter(Qt.Orientations.Horizontal)
+splitter1.addWidget(topleft)
+splitter1.addWidget(topright)
+```
+创建一个有俩框架组件的 `QSplitter` 组件。
+``` python
+splitter2 = QSplitter(Qt.Orientations.Vertical)
+splitter2.addWidget(splitter1)
+```
+再添加一个分割条和一个框架组件。
+
+![QSplitter widget](./images/qsplitter.png)
+
+图示:分割条组件
+
+## PyQt6 QComboBox
+`QComboBox` 是下拉选框组件,能让用户在一系列选项中进行选择。
+
+``` python
+# file: combobox.py
+#!/usr/bin/python
+
+"""
+ZetCode PyQt6 tutorial
+
+This example shows how to use
+a QComboBox widget.
+
+Author: Jan Bodnar
+Website: zetcode.com
+"""
+
+import sys
+
+from PyQt6.QtWidgets import (QWidget, QLabel,
+ QComboBox, QApplication)
+
+
+class Example(QWidget):
+
+ def __init__(self):
+ super().__init__()
+
+ self.initUI()
+
+
+ def initUI(self):
+
+ self.lbl = QLabel('Ubuntu', self)
+
+ combo = QComboBox(self)
+
+ combo.addItem('Ubuntu')
+ combo.addItem('Mandriva')
+ combo.addItem('Fedora')
+ combo.addItem('Arch')
+ combo.addItem('Gentoo')
+
+ combo.move(50, 50)
+ self.lbl.move(50, 150)
+
+ combo.textActivated[str].connect(self.onActivated)
+
+ self.setGeometry(300, 300, 450, 400)
+ self.setWindowTitle('QComboBox')
+ self.show()
+
+
+ def onActivated(self, text):
+
+ self.lbl.setText(text)
+ self.lbl.adjustSize()
+
+
+def main():
+
+ app = QApplication(sys.argv)
+ ex = Example()
+ sys.exit(app.exec())
+
+
+if __name__ == '__main__':
+ main()
+```
+示例中有一个 `QComboBox` 和一个 `QLabel`。下拉选框有一个包含五个选项的列表,是Linux发行版的名称。标签组件显示组合框中选择的选项。
+
+``` python
+combo = QComboBox(self)
+
+combo.addItem('Ubuntu')
+combo.addItem('Mandriva')
+combo.addItem('Fedora')
+combo.addItem('Arch')
+combo.addItem('Gentoo')
+```
+创建有五个选项的 `QComboBox` 组件。
+``` python
+combo.textActivated[str].connect(self.onActivated)
+```
+如果选择了一个选项,就调用 `onActivated` 方法。
+``` python
+def onActivated(self, text):
+
+ self.lbl.setText(text)
+ self.lbl.adjustSize()
+```
+在这个方法里,设置选中的文本到标签组件里,然后调整标签组件大小。
+
+![QComboBox](./images/qcombobox.png)
+
+图示:QComboBox
\ No newline at end of file