Add versions for PySide6, PyQt6 & PySide2.
Break down examples into module files to make easier to read. Use full-definitions on Enums (PyQt6 compatible, better documenting). Add fixes for Qt6 versions & some general bugfixes.
15
pyside2/demos/browser/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Mooseache — How web browsers would be if they'd just been invented
|
||||
|
||||
This is an example web browser built with Python and Qt. Using the
|
||||
QtWebEngineWidgets system introduced in Qt5.6, this provides a single-window
|
||||
browsing experience with the usual controls, as well as saving and loading HTML.
|
||||
|
||||

|
||||
|
||||
> If you want to learn more about build GUI applications with Python,
|
||||
take a look at [Python GUIs](https://www.pythonguis.com/)
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
||||
|
||||
## Other licenses
|
||||
|
||||
Icons used in the application are by [Yusuke Kamiyaman](http://p.yusukekamiyamane.com/).
|
||||
BIN
pyside2/demos/browser/images/arrow-000.png
Normal file
|
After Width: | Height: | Size: 615 B |
BIN
pyside2/demos/browser/images/arrow-180.png
Executable file
|
After Width: | Height: | Size: 589 B |
BIN
pyside2/demos/browser/images/arrow-circle-315.png
Executable file
|
After Width: | Height: | Size: 829 B |
BIN
pyside2/demos/browser/images/cross-circle.png
Executable file
|
After Width: | Height: | Size: 729 B |
BIN
pyside2/demos/browser/images/cross.png
Executable file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
pyside2/demos/browser/images/disk--arrow.png
Executable file
|
After Width: | Height: | Size: 621 B |
BIN
pyside2/demos/browser/images/disk--pencil.png
Executable file
|
After Width: | Height: | Size: 677 B |
BIN
pyside2/demos/browser/images/home.png
Executable file
|
After Width: | Height: | Size: 752 B |
BIN
pyside2/demos/browser/images/lifebuoy.png
Executable file
|
After Width: | Height: | Size: 902 B |
BIN
pyside2/demos/browser/images/lock-nossl.png
Executable file
|
After Width: | Height: | Size: 694 B |
BIN
pyside2/demos/browser/images/lock-ssl.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
pyside2/demos/browser/images/ma-icon-128.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
pyside2/demos/browser/images/ma-icon-256.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
pyside2/demos/browser/images/ma-icon-64.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
pyside2/demos/browser/images/printer.png
Executable file
|
After Width: | Height: | Size: 715 B |
BIN
pyside2/demos/browser/images/question.png
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
pyside2/demos/browser/images/ui-tab--plus.png
Executable file
|
After Width: | Height: | Size: 489 B |
259
pyside2/demos/browser/main.py
Normal file
@@ -0,0 +1,259 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide2.QtCore import QSize, Qt, QUrl
|
||||
from PySide2.QtGui import QIcon, QKeySequence, QPixmap
|
||||
from PySide2.QtPrintSupport import QPrintPreviewDialog
|
||||
from PySide2.QtWebEngineWidgets import QWebEngineView
|
||||
from PySide2.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFileDialog,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QShortcut,
|
||||
QStatusBar,
|
||||
QToolBar,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
class AboutDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok # No cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
title = QLabel("MooseAche")
|
||||
font = title.font()
|
||||
font.setPointSize(20)
|
||||
title.setFont(font)
|
||||
|
||||
layout.addWidget(title)
|
||||
|
||||
logo = QLabel()
|
||||
logo.setPixmap(QPixmap(os.path.join("images", "ma-icon-128.png")))
|
||||
layout.addWidget(logo)
|
||||
|
||||
layout.addWidget(QLabel("Version 23.35.211.233232"))
|
||||
layout.addWidget(QLabel("Copyright 2015 MooseAche Inc."))
|
||||
|
||||
for i in range(0, layout.count()):
|
||||
layout.itemAt(i).setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
layout.addWidget(self.buttonBox)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.browser = QWebEngineView()
|
||||
self.browser.setUrl(QUrl("http://google.com"))
|
||||
|
||||
self.browser.urlChanged.connect(self.update_urlbar)
|
||||
self.browser.loadFinished.connect(self.update_title)
|
||||
self.setCentralWidget(self.browser)
|
||||
|
||||
self.status = QStatusBar()
|
||||
self.setStatusBar(self.status)
|
||||
|
||||
navtb = QToolBar("Navigation")
|
||||
navtb.setIconSize(QSize(16, 16))
|
||||
self.addToolBar(navtb)
|
||||
|
||||
self.shortcut_open = QShortcut(QKeySequence("F5"), self)
|
||||
self.shortcut_open.activated.connect(self.browser.reload)
|
||||
|
||||
back_btn = QAction(QIcon(os.path.join("images", "arrow-180.png")), "Back", self)
|
||||
back_btn.setStatusTip("Back to previous page")
|
||||
back_btn.triggered.connect(self.browser.back)
|
||||
navtb.addAction(back_btn)
|
||||
|
||||
next_btn = QAction(
|
||||
QIcon(os.path.join("images", "arrow-000.png")),
|
||||
"Forward",
|
||||
self,
|
||||
)
|
||||
next_btn.setStatusTip("Forward to next page")
|
||||
next_btn.triggered.connect(self.browser.forward)
|
||||
navtb.addAction(next_btn)
|
||||
|
||||
reload_btn = QAction(
|
||||
QIcon(os.path.join("images", "arrow-circle-315.png")),
|
||||
"Reload",
|
||||
self,
|
||||
)
|
||||
reload_btn.setStatusTip("Reload page")
|
||||
reload_btn.triggered.connect(self.browser.reload)
|
||||
navtb.addAction(reload_btn)
|
||||
|
||||
home_btn = QAction(QIcon(os.path.join("images", "home.png")), "Home", self)
|
||||
home_btn.setStatusTip("Go home")
|
||||
home_btn.triggered.connect(self.navigate_home)
|
||||
navtb.addAction(home_btn)
|
||||
|
||||
navtb.addSeparator()
|
||||
|
||||
self.httpsicon = QLabel() # Yes, really!
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
navtb.addWidget(self.httpsicon)
|
||||
|
||||
self.urlbar = QLineEdit()
|
||||
self.urlbar.returnPressed.connect(self.navigate_to_url)
|
||||
navtb.addWidget(self.urlbar)
|
||||
|
||||
stop_btn = QAction(
|
||||
QIcon(os.path.join("images", "cross-circle.png")),
|
||||
"Stop",
|
||||
self,
|
||||
)
|
||||
stop_btn.setStatusTip("Stop loading current page")
|
||||
stop_btn.triggered.connect(self.browser.stop)
|
||||
navtb.addAction(stop_btn)
|
||||
|
||||
# Uncomment to disable native menubar on Mac
|
||||
# self.menuBar().setNativeMenuBar(False)
|
||||
|
||||
file_menu = self.menuBar().addMenu("&File")
|
||||
|
||||
open_file_action = QAction(
|
||||
QIcon(os.path.join("images", "disk--arrow.png")),
|
||||
"Open file...",
|
||||
self,
|
||||
)
|
||||
open_file_action.setStatusTip("Open from file")
|
||||
open_file_action.triggered.connect(self.open_file)
|
||||
file_menu.addAction(open_file_action)
|
||||
|
||||
save_file_action = QAction(
|
||||
QIcon(os.path.join("images", "disk--pencil.png")),
|
||||
"Save Page As...",
|
||||
self,
|
||||
)
|
||||
save_file_action.setStatusTip("Save current page to file")
|
||||
save_file_action.triggered.connect(self.save_file)
|
||||
file_menu.addAction(save_file_action)
|
||||
|
||||
print_action = QAction(
|
||||
QIcon(os.path.join("images", "printer.png")),
|
||||
"Print...",
|
||||
self,
|
||||
)
|
||||
print_action.setStatusTip("Print current page")
|
||||
print_action.triggered.connect(self.print_page)
|
||||
file_menu.addAction(print_action)
|
||||
|
||||
help_menu = self.menuBar().addMenu("&Help")
|
||||
|
||||
about_action = QAction(
|
||||
QIcon(os.path.join("images", "question.png")),
|
||||
"About MooseAche",
|
||||
self,
|
||||
)
|
||||
about_action.setStatusTip("Find out more about MooseAche") # Hungry!
|
||||
about_action.triggered.connect(self.about)
|
||||
help_menu.addAction(about_action)
|
||||
|
||||
navigate_mozarella_action = QAction(
|
||||
QIcon(os.path.join("images", "lifebuoy.png")),
|
||||
"MooseAche Homepage",
|
||||
self,
|
||||
)
|
||||
navigate_mozarella_action.setStatusTip("Go to MooseAche Homepage")
|
||||
navigate_mozarella_action.triggered.connect(self.navigate_mozarella)
|
||||
help_menu.addAction(navigate_mozarella_action)
|
||||
|
||||
self.show()
|
||||
|
||||
self.setWindowIcon(QIcon(os.path.join("images", "ma-icon-64.png")))
|
||||
|
||||
def update_title(self):
|
||||
title = self.browser.page().title()
|
||||
self.setWindowTitle("%s - MooseAche" % title)
|
||||
|
||||
def navigate_mozarella(self):
|
||||
self.browser.setUrl(QUrl("https://www.pythonguis.com/"))
|
||||
|
||||
def about(self):
|
||||
dlg = AboutDialog()
|
||||
dlg.exec_()
|
||||
|
||||
def open_file(self):
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Open file",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *.html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
with open(filename, "r") as f:
|
||||
html = f.read()
|
||||
|
||||
self.browser.setHtml(html)
|
||||
self.urlbar.setText(filename)
|
||||
|
||||
def save_html(self, html):
|
||||
with open(self.save_file, "w") as f:
|
||||
f.write(html)
|
||||
|
||||
def save_file(self):
|
||||
filename, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Save Page As",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
self.save_file = filename
|
||||
self.browser.page().toHtml(self.save_html)
|
||||
|
||||
def print_page(self):
|
||||
dlg = QPrintPreviewDialog()
|
||||
dlg.paintRequested.connect(self.browser.print_)
|
||||
dlg.exec_()
|
||||
|
||||
def navigate_home(self):
|
||||
self.browser.setUrl(QUrl("http://www.google.com"))
|
||||
|
||||
def navigate_to_url(self): # Does not receive the Url
|
||||
q = QUrl(self.urlbar.text())
|
||||
if q.scheme() == "":
|
||||
q.setScheme("http")
|
||||
|
||||
self.browser.setUrl(q)
|
||||
|
||||
def update_urlbar(self, q):
|
||||
if q.scheme() == "https":
|
||||
# Secure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-ssl.png")))
|
||||
|
||||
else:
|
||||
# Insecure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
|
||||
self.urlbar.setText(q.toString())
|
||||
self.urlbar.setCursorPosition(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("MooseAche")
|
||||
app.setOrganizationName("MooseAche")
|
||||
app.setOrganizationDomain("MooseAche.org")
|
||||
|
||||
window = MainWindow()
|
||||
|
||||
app.exec_()
|
||||
277
pyside2/demos/browser/main_new_windows.py
Normal file
@@ -0,0 +1,277 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide2.QtCore import QSize, Qt, QUrl
|
||||
from PySide2.QtGui import QIcon, QPixmap
|
||||
from PySide2.QtPrintSupport import QPrintPreviewDialog
|
||||
from PySide2.QtWebEngineWidgets import QWebEnginePage, QWebEngineView
|
||||
from PySide2.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFileDialog,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QStatusBar,
|
||||
QToolBar,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
class AboutDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok # No cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
title = QLabel("MooseAche")
|
||||
font = title.font()
|
||||
font.setPointSize(20)
|
||||
title.setFont(font)
|
||||
|
||||
layout.addWidget(title)
|
||||
|
||||
logo = QLabel()
|
||||
logo.setPixmap(QPixmap(os.path.join("images", "ma-icon-128.png")))
|
||||
layout.addWidget(logo)
|
||||
|
||||
layout.addWidget(QLabel("Version 23.35.211.233232"))
|
||||
layout.addWidget(QLabel("Copyright 2015 MooseAche Inc."))
|
||||
|
||||
for i in range(0, layout.count()):
|
||||
layout.itemAt(i).setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
layout.addWidget(self.buttonBox)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class WebEnginePage(QWebEnginePage):
|
||||
# Store externally created windows.
|
||||
external_windows = []
|
||||
|
||||
def acceptNavigationRequest(self, url, _type, isMainFrame):
|
||||
print(url, _type, isMainFrame)
|
||||
if _type == QWebEnginePage.NavigationType.NavigationTypeLinkClicked:
|
||||
w = QWebEngineView()
|
||||
w.setUrl(url)
|
||||
w.show()
|
||||
|
||||
# Keep reference to external window, so it isn't cleared up.
|
||||
self.external_windows.append(w)
|
||||
return False
|
||||
return super().acceptNavigationRequest(url, _type, isMainFrame)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.browser = QWebEngineView()
|
||||
self.browser.setPage(WebEnginePage(self))
|
||||
self.browser.setUrl(QUrl("http://google.com"))
|
||||
|
||||
self.browser.urlChanged.connect(self.update_urlbar)
|
||||
self.browser.loadFinished.connect(self.update_title)
|
||||
self.setCentralWidget(self.browser)
|
||||
|
||||
self.status = QStatusBar()
|
||||
self.setStatusBar(self.status)
|
||||
|
||||
navtb = QToolBar("Navigation")
|
||||
navtb.setIconSize(QSize(16, 16))
|
||||
self.addToolBar(navtb)
|
||||
|
||||
back_btn = QAction(QIcon(os.path.join("images", "arrow-180.png")), "Back", self)
|
||||
back_btn.setStatusTip("Back to previous page")
|
||||
back_btn.triggered.connect(self.browser.back)
|
||||
navtb.addAction(back_btn)
|
||||
|
||||
next_btn = QAction(
|
||||
QIcon(os.path.join("images", "arrow-000.png")),
|
||||
"Forward",
|
||||
self,
|
||||
)
|
||||
next_btn.setStatusTip("Forward to next page")
|
||||
next_btn.triggered.connect(self.browser.forward)
|
||||
navtb.addAction(next_btn)
|
||||
|
||||
reload_btn = QAction(
|
||||
QIcon(os.path.join("images", "arrow-circle-315.png")),
|
||||
"Reload",
|
||||
self,
|
||||
)
|
||||
reload_btn.setStatusTip("Reload page")
|
||||
reload_btn.triggered.connect(self.browser.reload)
|
||||
navtb.addAction(reload_btn)
|
||||
|
||||
home_btn = QAction(QIcon(os.path.join("images", "home.png")), "Home", self)
|
||||
home_btn.setStatusTip("Go home")
|
||||
home_btn.triggered.connect(self.navigate_home)
|
||||
navtb.addAction(home_btn)
|
||||
|
||||
navtb.addSeparator()
|
||||
|
||||
self.httpsicon = QLabel() # Yes, really!
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
navtb.addWidget(self.httpsicon)
|
||||
|
||||
self.urlbar = QLineEdit()
|
||||
self.urlbar.returnPressed.connect(self.navigate_to_url)
|
||||
navtb.addWidget(self.urlbar)
|
||||
|
||||
stop_btn = QAction(
|
||||
QIcon(os.path.join("images", "cross-circle.png")),
|
||||
"Stop",
|
||||
self,
|
||||
)
|
||||
stop_btn.setStatusTip("Stop loading current page")
|
||||
stop_btn.triggered.connect(self.browser.stop)
|
||||
navtb.addAction(stop_btn)
|
||||
|
||||
# Uncomment to disable native menubar on Mac
|
||||
# self.menuBar().setNativeMenuBar(False)
|
||||
|
||||
file_menu = self.menuBar().addMenu("&File")
|
||||
|
||||
open_file_action = QAction(
|
||||
QIcon(os.path.join("images", "disk--arrow.png")),
|
||||
"Open file...",
|
||||
self,
|
||||
)
|
||||
open_file_action.setStatusTip("Open from file")
|
||||
open_file_action.triggered.connect(self.open_file)
|
||||
file_menu.addAction(open_file_action)
|
||||
|
||||
save_file_action = QAction(
|
||||
QIcon(os.path.join("images", "disk--pencil.png")),
|
||||
"Save Page As...",
|
||||
self,
|
||||
)
|
||||
save_file_action.setStatusTip("Save current page to file")
|
||||
save_file_action.triggered.connect(self.save_file)
|
||||
file_menu.addAction(save_file_action)
|
||||
|
||||
print_action = QAction(
|
||||
QIcon(os.path.join("images", "printer.png")),
|
||||
"Print...",
|
||||
self,
|
||||
)
|
||||
print_action.setStatusTip("Print current page")
|
||||
print_action.triggered.connect(self.print_page)
|
||||
file_menu.addAction(print_action)
|
||||
|
||||
help_menu = self.menuBar().addMenu("&Help")
|
||||
|
||||
about_action = QAction(
|
||||
QIcon(os.path.join("images", "question.png")),
|
||||
"About MooseAche",
|
||||
self,
|
||||
)
|
||||
about_action.setStatusTip("Find out more about MooseAche") # Hungry!
|
||||
about_action.triggered.connect(self.about)
|
||||
help_menu.addAction(about_action)
|
||||
|
||||
navigate_mozarella_action = QAction(
|
||||
QIcon(os.path.join("images", "lifebuoy.png")),
|
||||
"MooseAche Homepage",
|
||||
self,
|
||||
)
|
||||
navigate_mozarella_action.setStatusTip("Go to MooseAche Homepage")
|
||||
navigate_mozarella_action.triggered.connect(self.navigate_mozarella)
|
||||
help_menu.addAction(navigate_mozarella_action)
|
||||
|
||||
self.show()
|
||||
|
||||
self.setWindowIcon(QIcon(os.path.join("images", "ma-icon-64.png")))
|
||||
|
||||
def acceptNavigationRequest(self, url, navigation_type, is_main_frame):
|
||||
self._externalview = QWebEngineView(url)
|
||||
return False
|
||||
|
||||
def linkClicked(self, e):
|
||||
print(e)
|
||||
|
||||
def update_title(self):
|
||||
title = self.browser.page().title()
|
||||
self.setWindowTitle("%s - MooseAche" % title)
|
||||
|
||||
def navigate_mozarella(self):
|
||||
self.browser.setUrl(QUrl("https://www.pythonguis.com/"))
|
||||
|
||||
def about(self):
|
||||
dlg = AboutDialog()
|
||||
dlg.exec_()
|
||||
|
||||
def open_file(self):
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Open file",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *.html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
with open(filename, "r") as f:
|
||||
html = f.read()
|
||||
|
||||
self.browser.setHtml(html)
|
||||
self.urlbar.setText(filename)
|
||||
|
||||
def save_file(self):
|
||||
filename, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Save Page As",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
html = self.browser.page().toHtml()
|
||||
with open(filename, "w") as f:
|
||||
f.write(html)
|
||||
|
||||
def print_page(self):
|
||||
dlg = QPrintPreviewDialog()
|
||||
dlg.paintRequested.connect(self.browser.print_)
|
||||
dlg.exec_()
|
||||
|
||||
def navigate_home(self):
|
||||
self.browser.setUrl(QUrl("http://www.google.com"))
|
||||
|
||||
def navigate_to_url(self): # Does not receive the Url
|
||||
q = QUrl(self.urlbar.text())
|
||||
if q.scheme() == "":
|
||||
q.setScheme("http")
|
||||
|
||||
self.browser.setUrl(q)
|
||||
|
||||
def update_urlbar(self, q):
|
||||
if q.scheme() == "https":
|
||||
# Secure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-ssl.png")))
|
||||
|
||||
else:
|
||||
# Insecure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
|
||||
self.urlbar.setText(q.toString())
|
||||
self.urlbar.setCursorPosition(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("MooseAche")
|
||||
app.setOrganizationName("MooseAche")
|
||||
app.setOrganizationDomain("MooseAche.org")
|
||||
|
||||
window = MainWindow()
|
||||
|
||||
app.exec_()
|
||||
277
pyside2/demos/browser/main_one_new_window.py
Normal file
@@ -0,0 +1,277 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide2.QtCore import QSize, Qt, QUrl
|
||||
from PySide2.QtGui import QIcon, QPixmap
|
||||
from PySide2.QtPrintSupport import QPrintPreviewDialog
|
||||
from PySide2.QtWebEngineWidgets import QWebEnginePage, QWebEngineView
|
||||
from PySide2.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFileDialog,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QStatusBar,
|
||||
QToolBar,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
class AboutDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok # No cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
title = QLabel("MooseAche")
|
||||
font = title.font()
|
||||
font.setPointSize(20)
|
||||
title.setFont(font)
|
||||
|
||||
layout.addWidget(title)
|
||||
|
||||
logo = QLabel()
|
||||
logo.setPixmap(QPixmap(os.path.join("images", "ma-icon-128.png")))
|
||||
layout.addWidget(logo)
|
||||
|
||||
layout.addWidget(QLabel("Version 23.35.211.233232"))
|
||||
layout.addWidget(QLabel("Copyright 2015 MooseAche Inc."))
|
||||
|
||||
for i in range(0, layout.count()):
|
||||
layout.itemAt(i).setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
layout.addWidget(self.buttonBox)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class WebEnginePage(QWebEnginePage):
|
||||
# Store externally created window.
|
||||
external_window = None
|
||||
|
||||
def acceptNavigationRequest(self, url, _type, isMainFrame):
|
||||
print(url, _type, isMainFrame)
|
||||
if _type == QWebEnginePage.NavigationType.NavigationTypeLinkClicked:
|
||||
if not self.external_window:
|
||||
self.external_window = QWebEngineView()
|
||||
|
||||
self.external_window.setUrl(url)
|
||||
self.external_window.show()
|
||||
return False
|
||||
|
||||
return super().acceptNavigationRequest(url, _type, isMainFrame)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.browser = QWebEngineView()
|
||||
self.browser.setPage(WebEnginePage(self))
|
||||
self.browser.setUrl(QUrl("http://google.com"))
|
||||
|
||||
self.browser.urlChanged.connect(self.update_urlbar)
|
||||
self.browser.loadFinished.connect(self.update_title)
|
||||
self.setCentralWidget(self.browser)
|
||||
|
||||
self.status = QStatusBar()
|
||||
self.setStatusBar(self.status)
|
||||
|
||||
navtb = QToolBar("Navigation")
|
||||
navtb.setIconSize(QSize(16, 16))
|
||||
self.addToolBar(navtb)
|
||||
|
||||
back_btn = QAction(QIcon(os.path.join("images", "arrow-180.png")), "Back", self)
|
||||
back_btn.setStatusTip("Back to previous page")
|
||||
back_btn.triggered.connect(self.browser.back)
|
||||
navtb.addAction(back_btn)
|
||||
|
||||
next_btn = QAction(
|
||||
QIcon(os.path.join("images", "arrow-000.png")),
|
||||
"Forward",
|
||||
self,
|
||||
)
|
||||
next_btn.setStatusTip("Forward to next page")
|
||||
next_btn.triggered.connect(self.browser.forward)
|
||||
navtb.addAction(next_btn)
|
||||
|
||||
reload_btn = QAction(
|
||||
QIcon(os.path.join("images", "arrow-circle-315.png")),
|
||||
"Reload",
|
||||
self,
|
||||
)
|
||||
reload_btn.setStatusTip("Reload page")
|
||||
reload_btn.triggered.connect(self.browser.reload)
|
||||
navtb.addAction(reload_btn)
|
||||
|
||||
home_btn = QAction(QIcon(os.path.join("images", "home.png")), "Home", self)
|
||||
home_btn.setStatusTip("Go home")
|
||||
home_btn.triggered.connect(self.navigate_home)
|
||||
navtb.addAction(home_btn)
|
||||
|
||||
navtb.addSeparator()
|
||||
|
||||
self.httpsicon = QLabel() # Yes, really!
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
navtb.addWidget(self.httpsicon)
|
||||
|
||||
self.urlbar = QLineEdit()
|
||||
self.urlbar.returnPressed.connect(self.navigate_to_url)
|
||||
navtb.addWidget(self.urlbar)
|
||||
|
||||
stop_btn = QAction(
|
||||
QIcon(os.path.join("images", "cross-circle.png")),
|
||||
"Stop",
|
||||
self,
|
||||
)
|
||||
stop_btn.setStatusTip("Stop loading current page")
|
||||
stop_btn.triggered.connect(self.browser.stop)
|
||||
navtb.addAction(stop_btn)
|
||||
|
||||
# Uncomment to disable native menubar on Mac
|
||||
# self.menuBar().setNativeMenuBar(False)
|
||||
|
||||
file_menu = self.menuBar().addMenu("&File")
|
||||
|
||||
open_file_action = QAction(
|
||||
QIcon(os.path.join("images", "disk--arrow.png")),
|
||||
"Open file...",
|
||||
self,
|
||||
)
|
||||
open_file_action.setStatusTip("Open from file")
|
||||
open_file_action.triggered.connect(self.open_file)
|
||||
file_menu.addAction(open_file_action)
|
||||
|
||||
save_file_action = QAction(
|
||||
QIcon(os.path.join("images", "disk--pencil.png")),
|
||||
"Save Page As...",
|
||||
self,
|
||||
)
|
||||
save_file_action.setStatusTip("Save current page to file")
|
||||
save_file_action.triggered.connect(self.save_file)
|
||||
file_menu.addAction(save_file_action)
|
||||
|
||||
print_action = QAction(
|
||||
QIcon(os.path.join("images", "printer.png")),
|
||||
"Print...",
|
||||
self,
|
||||
)
|
||||
print_action.setStatusTip("Print current page")
|
||||
print_action.triggered.connect(self.print_page)
|
||||
file_menu.addAction(print_action)
|
||||
|
||||
help_menu = self.menuBar().addMenu("&Help")
|
||||
|
||||
about_action = QAction(
|
||||
QIcon(os.path.join("images", "question.png")),
|
||||
"About MooseAche",
|
||||
self,
|
||||
)
|
||||
about_action.setStatusTip("Find out more about MooseAche") # Hungry!
|
||||
about_action.triggered.connect(self.about)
|
||||
help_menu.addAction(about_action)
|
||||
|
||||
navigate_mozarella_action = QAction(
|
||||
QIcon(os.path.join("images", "lifebuoy.png")),
|
||||
"MooseAche Homepage",
|
||||
self,
|
||||
)
|
||||
navigate_mozarella_action.setStatusTip("Go to MooseAche Homepage")
|
||||
navigate_mozarella_action.triggered.connect(self.navigate_mozarella)
|
||||
help_menu.addAction(navigate_mozarella_action)
|
||||
|
||||
self.show()
|
||||
|
||||
self.setWindowIcon(QIcon(os.path.join("images", "ma-icon-64.png")))
|
||||
|
||||
def acceptNavigationRequest(self, url, navigation_type, is_main_frame):
|
||||
self._externalview = QWebEngineView(url)
|
||||
return False
|
||||
|
||||
def linkClicked(self, e):
|
||||
print(e)
|
||||
|
||||
def update_title(self):
|
||||
title = self.browser.page().title()
|
||||
self.setWindowTitle("%s - MooseAche" % title)
|
||||
|
||||
def navigate_mozarella(self):
|
||||
self.browser.setUrl(QUrl("https://www.pythonguis.com/"))
|
||||
|
||||
def about(self):
|
||||
dlg = AboutDialog()
|
||||
dlg.exec_()
|
||||
|
||||
def open_file(self):
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Open file",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *.html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
with open(filename, "r") as f:
|
||||
html = f.read()
|
||||
|
||||
self.browser.setHtml(html)
|
||||
self.urlbar.setText(filename)
|
||||
|
||||
def save_file(self):
|
||||
filename, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Save Page As",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
html = self.browser.page().toHtml()
|
||||
with open(filename, "w") as f:
|
||||
f.write(html)
|
||||
|
||||
def print_page(self):
|
||||
dlg = QPrintPreviewDialog()
|
||||
dlg.paintRequested.connect(self.browser.print_)
|
||||
dlg.exec_()
|
||||
|
||||
def navigate_home(self):
|
||||
self.browser.setUrl(QUrl("http://www.google.com"))
|
||||
|
||||
def navigate_to_url(self): # Does not receive the Url
|
||||
q = QUrl(self.urlbar.text())
|
||||
if q.scheme() == "":
|
||||
q.setScheme("http")
|
||||
|
||||
self.browser.setUrl(q)
|
||||
|
||||
def update_urlbar(self, q):
|
||||
if q.scheme() == "https":
|
||||
# Secure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-ssl.png")))
|
||||
|
||||
else:
|
||||
# Insecure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
|
||||
self.urlbar.setText(q.toString())
|
||||
self.urlbar.setCursorPosition(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("MooseAche")
|
||||
app.setOrganizationName("MooseAche")
|
||||
app.setOrganizationDomain("MooseAche.org")
|
||||
|
||||
window = MainWindow()
|
||||
|
||||
app.exec_()
|
||||
2
pyside2/demos/browser/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
PyQt5>=5.6
|
||||
PyQtWebEngine
|
||||
BIN
pyside2/demos/browser/screenshot-browser.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
28
pyside2/demos/browser_tabbed/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Mozzarella Ashbadger — Upgrade your browsing with tabs
|
||||
|
||||
Mozarella Ashbadger is the latest revolution in web
|
||||
browsing! Go back and forward! Print! Save files! Get help!
|
||||
(you’ll need it). Any similarity to other browsers is entirely
|
||||
coincidental.
|
||||
|
||||

|
||||
|
||||
> If you want to learn more about build GUI applications with Python,
|
||||
take a look at my [PyQt5 tutorials](https://www.pythonguis.com)
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
||||
|
||||
## Code notes
|
||||
|
||||
### Tabbing
|
||||
|
||||
Adding tab support complicates the internals of the browser a bit, since we
|
||||
now need to keep track of the currently active browser view, both to update
|
||||
UI elements (URL bar, HTTPs icon) to changing state in the currently active
|
||||
window, and to ensure the UI events are dispatched to the correct web view.
|
||||
|
||||
This is achieved by using intermediate slots which filter events, and by
|
||||
adding signal redirection (using lamba functions to keep it short).
|
||||
|
||||
## Other licenses
|
||||
|
||||
Icons used in the application are by [Yusuke Kamiyaman](http://p.yusukekamiyamane.com/).
|
||||
BIN
pyside2/demos/browser_tabbed/images/arrow-000.png
Normal file
|
After Width: | Height: | Size: 615 B |
BIN
pyside2/demos/browser_tabbed/images/arrow-180.png
Executable file
|
After Width: | Height: | Size: 589 B |
BIN
pyside2/demos/browser_tabbed/images/arrow-circle-315.png
Executable file
|
After Width: | Height: | Size: 829 B |
BIN
pyside2/demos/browser_tabbed/images/cross-circle.png
Executable file
|
After Width: | Height: | Size: 729 B |
BIN
pyside2/demos/browser_tabbed/images/cross.png
Executable file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
pyside2/demos/browser_tabbed/images/disk--arrow.png
Executable file
|
After Width: | Height: | Size: 621 B |
BIN
pyside2/demos/browser_tabbed/images/disk--pencil.png
Executable file
|
After Width: | Height: | Size: 677 B |
BIN
pyside2/demos/browser_tabbed/images/home.png
Executable file
|
After Width: | Height: | Size: 752 B |
BIN
pyside2/demos/browser_tabbed/images/lifebuoy.png
Executable file
|
After Width: | Height: | Size: 902 B |
BIN
pyside2/demos/browser_tabbed/images/lock-nossl.png
Executable file
|
After Width: | Height: | Size: 694 B |
BIN
pyside2/demos/browser_tabbed/images/lock-ssl.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
pyside2/demos/browser_tabbed/images/ma-icon-128.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
pyside2/demos/browser_tabbed/images/ma-icon-256.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
pyside2/demos/browser_tabbed/images/ma-icon-64.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
pyside2/demos/browser_tabbed/images/printer.png
Executable file
|
After Width: | Height: | Size: 715 B |
BIN
pyside2/demos/browser_tabbed/images/question.png
Executable file
|
After Width: | Height: | Size: 766 B |
BIN
pyside2/demos/browser_tabbed/images/ui-tab--plus.png
Executable file
|
After Width: | Height: | Size: 489 B |
328
pyside2/demos/browser_tabbed/main.py
Normal file
@@ -0,0 +1,328 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide2.QtCore import QSize, Qt, QUrl
|
||||
from PySide2.QtGui import QIcon, QKeySequence, QPixmap
|
||||
from PySide2.QtPrintSupport import QPrintPreviewDialog
|
||||
from PySide2.QtWebEngineWidgets import QWebEngineView
|
||||
from PySide2.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFileDialog,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QShortcut,
|
||||
QStatusBar,
|
||||
QTabWidget,
|
||||
QToolBar,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
class AboutDialog(QDialog):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok # No cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
title = QLabel("Mozarella Ashbadger")
|
||||
font = title.font()
|
||||
font.setPointSize(20)
|
||||
title.setFont(font)
|
||||
|
||||
layout.addWidget(title)
|
||||
|
||||
logo = QLabel()
|
||||
logo.setPixmap(QPixmap(os.path.join("images", "ma-icon-128.png")))
|
||||
layout.addWidget(logo)
|
||||
|
||||
layout.addWidget(QLabel("Version 23.35.211.233232"))
|
||||
layout.addWidget(QLabel("Copyright 2015 Mozarella Inc."))
|
||||
|
||||
for i in range(0, layout.count()):
|
||||
layout.itemAt(i).setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
layout.addWidget(self.buttonBox)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.tabs = QTabWidget()
|
||||
self.tabs.setDocumentMode(True)
|
||||
self.tabs.tabBarDoubleClicked.connect(self.tab_open_doubleclick)
|
||||
self.tabs.currentChanged.connect(self.current_tab_changed)
|
||||
self.tabs.setTabsClosable(True)
|
||||
self.tabs.tabCloseRequested.connect(self.close_current_tab)
|
||||
|
||||
self.setCentralWidget(self.tabs)
|
||||
|
||||
self.status = QStatusBar()
|
||||
self.setStatusBar(self.status)
|
||||
|
||||
navtb = QToolBar("Navigation")
|
||||
navtb.setIconSize(QSize(16, 16))
|
||||
self.addToolBar(navtb)
|
||||
|
||||
"""Shortcuts"""
|
||||
|
||||
self.shortcut_open = QShortcut(QKeySequence("F5"), self)
|
||||
self.shortcut_open.activated.connect(lambda: self.tabs.currentWidget().reload())
|
||||
|
||||
back_btn = QAction(QIcon(os.path.join("images", "arrow-180.png")), "Back", self)
|
||||
back_btn.setStatusTip("Back to previous page")
|
||||
back_btn.triggered.connect(lambda: self.tabs.currentWidget().back())
|
||||
navtb.addAction(back_btn)
|
||||
|
||||
next_btn = QAction(
|
||||
QIcon(os.path.join("images", "arrow-000.png")),
|
||||
"Forward",
|
||||
self,
|
||||
)
|
||||
next_btn.setStatusTip("Forward to next page")
|
||||
next_btn.triggered.connect(lambda: self.tabs.currentWidget().forward())
|
||||
navtb.addAction(next_btn)
|
||||
|
||||
reload_btn = QAction(
|
||||
QIcon(os.path.join("images", "arrow-circle-315.png")),
|
||||
"Reload",
|
||||
self,
|
||||
)
|
||||
reload_btn.setStatusTip("Reload page")
|
||||
reload_btn.triggered.connect(lambda: self.tabs.currentWidget().reload())
|
||||
navtb.addAction(reload_btn)
|
||||
|
||||
home_btn = QAction(QIcon(os.path.join("images", "home.png")), "Home", self)
|
||||
home_btn.setStatusTip("Go home")
|
||||
home_btn.triggered.connect(self.navigate_home)
|
||||
navtb.addAction(home_btn)
|
||||
|
||||
navtb.addSeparator()
|
||||
|
||||
self.httpsicon = QLabel() # Yes, really!
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
navtb.addWidget(self.httpsicon)
|
||||
|
||||
self.urlbar = QLineEdit()
|
||||
self.urlbar.returnPressed.connect(self.navigate_to_url)
|
||||
navtb.addWidget(self.urlbar)
|
||||
|
||||
stop_btn = QAction(
|
||||
QIcon(os.path.join("images", "cross-circle.png")),
|
||||
"Stop",
|
||||
self,
|
||||
)
|
||||
stop_btn.setStatusTip("Stop loading current page")
|
||||
stop_btn.triggered.connect(lambda: self.tabs.currentWidget().stop())
|
||||
navtb.addAction(stop_btn)
|
||||
|
||||
# Uncomment to disable native menubar on Mac
|
||||
# self.menuBar().setNativeMenuBar(False)
|
||||
|
||||
file_menu = self.menuBar().addMenu("&File")
|
||||
|
||||
new_tab_action = QAction(
|
||||
QIcon(os.path.join("images", "ui-tab--plus.png")),
|
||||
"New Tab",
|
||||
self,
|
||||
)
|
||||
new_tab_action.setStatusTip("Open a new tab")
|
||||
new_tab_action.triggered.connect(lambda _: self.add_new_tab())
|
||||
file_menu.addAction(new_tab_action)
|
||||
|
||||
open_file_action = QAction(
|
||||
QIcon(os.path.join("images", "disk--arrow.png")),
|
||||
"Open file...",
|
||||
self,
|
||||
)
|
||||
open_file_action.setStatusTip("Open from file")
|
||||
open_file_action.triggered.connect(self.open_file)
|
||||
file_menu.addAction(open_file_action)
|
||||
|
||||
save_file_action = QAction(
|
||||
QIcon(os.path.join("images", "disk--pencil.png")),
|
||||
"Save Page As...",
|
||||
self,
|
||||
)
|
||||
save_file_action.setStatusTip("Save current page to file")
|
||||
save_file_action.triggered.connect(self.save_file)
|
||||
file_menu.addAction(save_file_action)
|
||||
|
||||
print_action = QAction(
|
||||
QIcon(os.path.join("images", "printer.png")),
|
||||
"Print...",
|
||||
self,
|
||||
)
|
||||
print_action.setStatusTip("Print current page")
|
||||
print_action.triggered.connect(self.print_page)
|
||||
file_menu.addAction(print_action)
|
||||
|
||||
help_menu = self.menuBar().addMenu("&Help")
|
||||
|
||||
about_action = QAction(
|
||||
QIcon(os.path.join("images", "question.png")),
|
||||
"About Mozarella Ashbadger",
|
||||
self,
|
||||
)
|
||||
about_action.setStatusTip("Find out more about Mozarella Ashbadger") # Hungry!
|
||||
about_action.triggered.connect(self.about)
|
||||
help_menu.addAction(about_action)
|
||||
|
||||
navigate_mozarella_action = QAction(
|
||||
QIcon(os.path.join("images", "lifebuoy.png")),
|
||||
"Mozarella Ashbadger Homepage",
|
||||
self,
|
||||
)
|
||||
navigate_mozarella_action.setStatusTip("Go to Mozarella Ashbadger Homepage")
|
||||
navigate_mozarella_action.triggered.connect(self.navigate_mozarella)
|
||||
help_menu.addAction(navigate_mozarella_action)
|
||||
|
||||
self.add_new_tab(QUrl("http://www.google.com"), "Homepage")
|
||||
self.add_new_tab(QUrl("http://www.pythonguis.com"), "PythonGUIs")
|
||||
|
||||
self.show()
|
||||
|
||||
self.setWindowTitle("Mozarella Ashbadger")
|
||||
self.setWindowIcon(QIcon(os.path.join("images", "ma-icon-64.png")))
|
||||
|
||||
def add_new_tab(self, qurl=None, label="Blank"):
|
||||
if qurl is None:
|
||||
qurl = QUrl("")
|
||||
|
||||
browser = QWebEngineView()
|
||||
browser.setUrl(qurl)
|
||||
i = self.tabs.addTab(browser, label)
|
||||
|
||||
self.tabs.setCurrentIndex(i)
|
||||
|
||||
# More difficult! We only want to update the url when it's from the
|
||||
# correct tab
|
||||
browser.urlChanged.connect(
|
||||
lambda qurl, browser=browser: self.update_urlbar(qurl, browser)
|
||||
)
|
||||
browser.titleChanged.connect(
|
||||
lambda _, i=i, browser=browser: self.tabs.setTabText(
|
||||
i, browser.page().title()
|
||||
)
|
||||
)
|
||||
browser.titleChanged.connect(
|
||||
lambda _, i=i, browser=browser: self.tabs.setTabToolTip(
|
||||
i, browser.page().title()
|
||||
)
|
||||
)
|
||||
browser.loadFinished.connect(
|
||||
lambda _, i=i, browser=browser: self.tabs.setTabText(
|
||||
i, browser.page().title()
|
||||
)
|
||||
)
|
||||
|
||||
def tab_open_doubleclick(self, i):
|
||||
if i == -1: # No tab under the click
|
||||
self.add_new_tab()
|
||||
|
||||
def current_tab_changed(self, i):
|
||||
qurl = self.tabs.currentWidget().url()
|
||||
self.update_urlbar(qurl, self.tabs.currentWidget())
|
||||
self.update_title(self.tabs.currentWidget())
|
||||
|
||||
def close_current_tab(self, i):
|
||||
if self.tabs.count() < 2:
|
||||
return
|
||||
|
||||
self.tabs.removeTab(i)
|
||||
|
||||
def update_title(self, browser):
|
||||
if browser != self.tabs.currentWidget():
|
||||
# If this signal is not from the current tab, ignore
|
||||
return
|
||||
|
||||
title = self.tabs.currentWidget().page().title()
|
||||
self.setWindowTitle("%s - Mozarella Ashbadger" % title)
|
||||
|
||||
def navigate_mozarella(self):
|
||||
self.tabs.currentWidget().setUrl(QUrl("https://www.pythonguis.com/"))
|
||||
|
||||
def about(self):
|
||||
dlg = AboutDialog()
|
||||
dlg.exec_()
|
||||
|
||||
def open_file(self):
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Open file",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *.html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
with open(filename, "r") as f:
|
||||
html = f.read()
|
||||
|
||||
self.tabs.currentWidget().setHtml(html)
|
||||
self.urlbar.setText(filename)
|
||||
|
||||
def save_file(self):
|
||||
filename, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"Save Page As",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
html = self.tabs.currentWidget().page().toHtml()
|
||||
with open(filename, "w") as f:
|
||||
f.write(html.encode("utf8"))
|
||||
|
||||
def print_page(self):
|
||||
dlg = QPrintPreviewDialog()
|
||||
dlg.paintRequested.connect(self.browser.print_)
|
||||
dlg.exec_()
|
||||
|
||||
def navigate_home(self):
|
||||
self.tabs.currentWidget().setUrl(QUrl("http://www.google.com"))
|
||||
|
||||
def navigate_to_url(self): # Does not receive the Url
|
||||
q = QUrl(self.urlbar.text())
|
||||
if q.scheme() == "":
|
||||
q.setScheme("http")
|
||||
|
||||
self.tabs.currentWidget().setUrl(q)
|
||||
|
||||
def update_urlbar(self, q, browser=None):
|
||||
if browser != self.tabs.currentWidget():
|
||||
# If this signal is not from the current tab, ignore
|
||||
return
|
||||
|
||||
if q.scheme() == "https":
|
||||
# Secure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-ssl.png")))
|
||||
|
||||
else:
|
||||
# Insecure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
|
||||
self.urlbar.setText(q.toString())
|
||||
self.urlbar.setCursorPosition(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Mozarella Ashbadger")
|
||||
app.setOrganizationName("Mozarella")
|
||||
app.setOrganizationDomain("mozarella.org")
|
||||
|
||||
window = MainWindow()
|
||||
|
||||
app.exec_()
|
||||
2
pyside2/demos/browser_tabbed/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
PyQt5>=5.6
|
||||
PyQtWebEngine
|
||||
BIN
pyside2/demos/browser_tabbed/screenshot-browser-tabbed.jpg
Normal file
|
After Width: | Height: | Size: 89 KiB |
288
pyside2/demos/calculator/MainWindow.py
Normal file
@@ -0,0 +1,288 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'mainwindow.ui'
|
||||
#
|
||||
# Created by: PySide2 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(484, 433)
|
||||
self.centralWidget = QtWidgets.QWidget(MainWindow)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.centralWidget.sizePolicy().hasHeightForWidth())
|
||||
self.centralWidget.setSizePolicy(sizePolicy)
|
||||
self.centralWidget.setObjectName("centralWidget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralWidget)
|
||||
self.verticalLayout.setContentsMargins(11, 11, 11, 11)
|
||||
self.verticalLayout.setSpacing(6)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.lcdNumber = QtWidgets.QLCDNumber(self.centralWidget)
|
||||
self.lcdNumber.setDigitCount(10)
|
||||
self.lcdNumber.setObjectName("lcdNumber")
|
||||
self.verticalLayout.addWidget(self.lcdNumber)
|
||||
self.gridLayout = QtWidgets.QGridLayout()
|
||||
self.gridLayout.setSpacing(6)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.pushButton_n4 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n4.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n4.setFont(font)
|
||||
self.pushButton_n4.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n4.setObjectName("pushButton_n4")
|
||||
self.gridLayout.addWidget(self.pushButton_n4, 3, 0, 1, 1)
|
||||
self.pushButton_n1 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n1.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n1.setFont(font)
|
||||
self.pushButton_n1.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n1.setObjectName("pushButton_n1")
|
||||
self.gridLayout.addWidget(self.pushButton_n1, 4, 0, 1, 1)
|
||||
self.pushButton_n8 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n8.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n8.setFont(font)
|
||||
self.pushButton_n8.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n8.setObjectName("pushButton_n8")
|
||||
self.gridLayout.addWidget(self.pushButton_n8, 2, 1, 1, 1)
|
||||
self.pushButton_mul = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_mul.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(False)
|
||||
font.setWeight(50)
|
||||
self.pushButton_mul.setFont(font)
|
||||
self.pushButton_mul.setObjectName("pushButton_mul")
|
||||
self.gridLayout.addWidget(self.pushButton_mul, 2, 3, 1, 1)
|
||||
self.pushButton_n7 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n7.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n7.setFont(font)
|
||||
self.pushButton_n7.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n7.setObjectName("pushButton_n7")
|
||||
self.gridLayout.addWidget(self.pushButton_n7, 2, 0, 1, 1)
|
||||
self.pushButton_n6 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n6.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n6.setFont(font)
|
||||
self.pushButton_n6.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n6.setObjectName("pushButton_n6")
|
||||
self.gridLayout.addWidget(self.pushButton_n6, 3, 2, 1, 1)
|
||||
self.pushButton_n5 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n5.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n5.setFont(font)
|
||||
self.pushButton_n5.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n5.setObjectName("pushButton_n5")
|
||||
self.gridLayout.addWidget(self.pushButton_n5, 3, 1, 1, 1)
|
||||
self.pushButton_n0 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n0.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n0.setFont(font)
|
||||
self.pushButton_n0.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n0.setObjectName("pushButton_n0")
|
||||
self.gridLayout.addWidget(self.pushButton_n0, 5, 0, 1, 1)
|
||||
self.pushButton_n2 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n2.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n2.setFont(font)
|
||||
self.pushButton_n2.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n2.setObjectName("pushButton_n2")
|
||||
self.gridLayout.addWidget(self.pushButton_n2, 4, 1, 1, 1)
|
||||
self.pushButton_n9 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n9.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n9.setFont(font)
|
||||
self.pushButton_n9.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n9.setObjectName("pushButton_n9")
|
||||
self.gridLayout.addWidget(self.pushButton_n9, 2, 2, 1, 1)
|
||||
self.pushButton_n3 = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_n3.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_n3.setFont(font)
|
||||
self.pushButton_n3.setStyleSheet("QPushButton {\n" "color: #1976D2;\n" "}")
|
||||
self.pushButton_n3.setObjectName("pushButton_n3")
|
||||
self.gridLayout.addWidget(self.pushButton_n3, 4, 2, 1, 1)
|
||||
self.pushButton_div = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_div.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(False)
|
||||
font.setWeight(50)
|
||||
self.pushButton_div.setFont(font)
|
||||
self.pushButton_div.setObjectName("pushButton_div")
|
||||
self.gridLayout.addWidget(self.pushButton_div, 1, 3, 1, 1)
|
||||
self.pushButton_sub = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_sub.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(False)
|
||||
font.setWeight(50)
|
||||
self.pushButton_sub.setFont(font)
|
||||
self.pushButton_sub.setObjectName("pushButton_sub")
|
||||
self.gridLayout.addWidget(self.pushButton_sub, 3, 3, 1, 1)
|
||||
self.pushButton_add = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_add.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(False)
|
||||
font.setWeight(50)
|
||||
self.pushButton_add.setFont(font)
|
||||
self.pushButton_add.setObjectName("pushButton_add")
|
||||
self.gridLayout.addWidget(self.pushButton_add, 4, 3, 1, 1)
|
||||
self.pushButton_ac = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_ac.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(False)
|
||||
font.setWeight(50)
|
||||
self.pushButton_ac.setFont(font)
|
||||
self.pushButton_ac.setStyleSheet("QPushButton {\n" " color: #f44336;\n" "}")
|
||||
self.pushButton_ac.setObjectName("pushButton_ac")
|
||||
self.gridLayout.addWidget(self.pushButton_ac, 1, 0, 1, 1)
|
||||
self.pushButton_mr = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_mr.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(False)
|
||||
font.setWeight(50)
|
||||
self.pushButton_mr.setFont(font)
|
||||
self.pushButton_mr.setStyleSheet("QPushButton {\n" " color: #FFC107;\n" "}")
|
||||
self.pushButton_mr.setObjectName("pushButton_mr")
|
||||
self.gridLayout.addWidget(self.pushButton_mr, 1, 2, 1, 1)
|
||||
self.pushButton_m = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_m.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(False)
|
||||
font.setWeight(50)
|
||||
self.pushButton_m.setFont(font)
|
||||
self.pushButton_m.setStyleSheet("QPushButton {\n" " color: #FFC107;\n" "}")
|
||||
self.pushButton_m.setObjectName("pushButton_m")
|
||||
self.gridLayout.addWidget(self.pushButton_m, 1, 1, 1, 1)
|
||||
self.pushButton_pc = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_pc.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(False)
|
||||
font.setWeight(50)
|
||||
self.pushButton_pc.setFont(font)
|
||||
self.pushButton_pc.setObjectName("pushButton_pc")
|
||||
self.gridLayout.addWidget(self.pushButton_pc, 5, 1, 1, 1)
|
||||
self.pushButton_eq = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pushButton_eq.setMinimumSize(QtCore.QSize(0, 50))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(27)
|
||||
font.setBold(True)
|
||||
font.setWeight(75)
|
||||
self.pushButton_eq.setFont(font)
|
||||
self.pushButton_eq.setStyleSheet("QPushButton {\n" "color: #4CAF50;\n" "}")
|
||||
self.pushButton_eq.setObjectName("pushButton_eq")
|
||||
self.gridLayout.addWidget(self.pushButton_eq, 5, 2, 1, 2)
|
||||
self.verticalLayout.addLayout(self.gridLayout)
|
||||
MainWindow.setCentralWidget(self.centralWidget)
|
||||
self.menuBar = QtWidgets.QMenuBar(MainWindow)
|
||||
self.menuBar.setGeometry(QtCore.QRect(0, 0, 484, 22))
|
||||
self.menuBar.setObjectName("menuBar")
|
||||
self.menuFile = QtWidgets.QMenu(self.menuBar)
|
||||
self.menuFile.setObjectName("menuFile")
|
||||
MainWindow.setMenuBar(self.menuBar)
|
||||
self.statusBar = QtWidgets.QStatusBar(MainWindow)
|
||||
self.statusBar.setObjectName("statusBar")
|
||||
MainWindow.setStatusBar(self.statusBar)
|
||||
self.actionExit = QtWidgets.QAction(MainWindow)
|
||||
self.actionExit.setObjectName("actionExit")
|
||||
self.actionReset = QtWidgets.QAction(MainWindow)
|
||||
self.actionReset.setObjectName("actionReset")
|
||||
self.menuFile.addAction(self.actionReset)
|
||||
self.menuFile.addAction(self.actionExit)
|
||||
self.menuBar.addAction(self.menuFile.menuAction())
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "Calculon"))
|
||||
self.pushButton_n4.setText(_translate("MainWindow", "4"))
|
||||
self.pushButton_n4.setShortcut(_translate("MainWindow", "4"))
|
||||
self.pushButton_n1.setText(_translate("MainWindow", "1"))
|
||||
self.pushButton_n1.setShortcut(_translate("MainWindow", "1"))
|
||||
self.pushButton_n8.setText(_translate("MainWindow", "8"))
|
||||
self.pushButton_n8.setShortcut(_translate("MainWindow", "8"))
|
||||
self.pushButton_mul.setText(_translate("MainWindow", "x"))
|
||||
self.pushButton_mul.setShortcut(_translate("MainWindow", "*"))
|
||||
self.pushButton_n7.setText(_translate("MainWindow", "7"))
|
||||
self.pushButton_n7.setShortcut(_translate("MainWindow", "7"))
|
||||
self.pushButton_n6.setText(_translate("MainWindow", "6"))
|
||||
self.pushButton_n6.setShortcut(_translate("MainWindow", "6"))
|
||||
self.pushButton_n5.setText(_translate("MainWindow", "5"))
|
||||
self.pushButton_n5.setShortcut(_translate("MainWindow", "5"))
|
||||
self.pushButton_n0.setText(_translate("MainWindow", "0"))
|
||||
self.pushButton_n0.setShortcut(_translate("MainWindow", "0"))
|
||||
self.pushButton_n2.setText(_translate("MainWindow", "2"))
|
||||
self.pushButton_n2.setShortcut(_translate("MainWindow", "2"))
|
||||
self.pushButton_n9.setText(_translate("MainWindow", "9"))
|
||||
self.pushButton_n9.setShortcut(_translate("MainWindow", "9"))
|
||||
self.pushButton_n3.setText(_translate("MainWindow", "3"))
|
||||
self.pushButton_n3.setShortcut(_translate("MainWindow", "3"))
|
||||
self.pushButton_div.setText(_translate("MainWindow", "÷"))
|
||||
self.pushButton_div.setShortcut(_translate("MainWindow", "/"))
|
||||
self.pushButton_sub.setText(_translate("MainWindow", "-"))
|
||||
self.pushButton_sub.setShortcut(_translate("MainWindow", "-"))
|
||||
self.pushButton_add.setText(_translate("MainWindow", "+"))
|
||||
self.pushButton_add.setShortcut(_translate("MainWindow", "+"))
|
||||
self.pushButton_ac.setText(_translate("MainWindow", "AC"))
|
||||
self.pushButton_ac.setShortcut(_translate("MainWindow", "Esc"))
|
||||
self.pushButton_mr.setText(_translate("MainWindow", "MR"))
|
||||
self.pushButton_mr.setShortcut(_translate("MainWindow", "R"))
|
||||
self.pushButton_m.setText(_translate("MainWindow", "M"))
|
||||
self.pushButton_m.setShortcut(_translate("MainWindow", "M"))
|
||||
self.pushButton_pc.setText(_translate("MainWindow", "%"))
|
||||
self.pushButton_pc.setShortcut(_translate("MainWindow", "%"))
|
||||
self.pushButton_eq.setText(_translate("MainWindow", "="))
|
||||
self.pushButton_eq.setShortcut(_translate("MainWindow", "Return"))
|
||||
self.menuFile.setTitle(_translate("MainWindow", "File"))
|
||||
self.actionExit.setText(_translate("MainWindow", "Exit"))
|
||||
self.actionExit.setShortcut(_translate("MainWindow", "Ctrl+Q"))
|
||||
self.actionReset.setText(_translate("MainWindow", "Reset"))
|
||||
self.actionReset.setShortcut(_translate("MainWindow", "Ctrl+R"))
|
||||
11
pyside2/demos/calculator/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Calculon - A desktop calculator in PyQt
|
||||
|
||||
A simple calculator application implemented in Python using PyQt. The UI was designed in Qt Designer and the
|
||||
calculator operations are implemented using simple stack-based logic.
|
||||
|
||||

|
||||
|
||||
> If you think this example app is neat and want to learn more about
|
||||
PyQt5, [take a look at my ebook & online course
|
||||
"Create Simple GUI Applications"](https://martinfitzpatrick.name/create-simple-gui-applications)
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
||||
110
pyside2/demos/calculator/main.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import operator
|
||||
import sys
|
||||
|
||||
from MainWindow import Ui_MainWindow
|
||||
from PySide2.QtWidgets import QApplication, QMainWindow
|
||||
|
||||
# Calculator state.
|
||||
READY = 0
|
||||
INPUT = 1
|
||||
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
# Setup numbers.
|
||||
for n in range(0, 10):
|
||||
getattr(self, "pushButton_n%s" % n).pressed.connect(lambda v=n: self.input_number(v))
|
||||
|
||||
# Setup operations.
|
||||
self.pushButton_add.pressed.connect(lambda: self.operation(operator.add))
|
||||
self.pushButton_sub.pressed.connect(lambda: self.operation(operator.sub))
|
||||
self.pushButton_mul.pressed.connect(lambda: self.operation(operator.mul))
|
||||
self.pushButton_div.pressed.connect(lambda: self.operation(operator.truediv)) # operator.div for Python2.7
|
||||
|
||||
self.pushButton_pc.pressed.connect(self.operation_pc)
|
||||
self.pushButton_eq.pressed.connect(self.equals)
|
||||
|
||||
# Setup actions
|
||||
self.actionReset.triggered.connect(self.reset)
|
||||
self.pushButton_ac.pressed.connect(self.reset)
|
||||
|
||||
self.actionExit.triggered.connect(self.close)
|
||||
|
||||
self.pushButton_m.pressed.connect(self.memory_store)
|
||||
self.pushButton_mr.pressed.connect(self.memory_recall)
|
||||
|
||||
self.memory = 0
|
||||
self.reset()
|
||||
|
||||
self.show()
|
||||
|
||||
def display(self):
|
||||
self.lcdNumber.display(self.stack[-1])
|
||||
|
||||
def reset(self):
|
||||
self.state = READY
|
||||
self.stack = [0]
|
||||
self.last_operation = None
|
||||
self.current_op = None
|
||||
self.display()
|
||||
|
||||
def memory_store(self):
|
||||
self.memory = self.lcdNumber.value()
|
||||
|
||||
def memory_recall(self):
|
||||
self.state = INPUT
|
||||
self.stack[-1] = self.memory
|
||||
self.display()
|
||||
|
||||
def input_number(self, v):
|
||||
if self.state == READY:
|
||||
self.state = INPUT
|
||||
self.stack[-1] = v
|
||||
else:
|
||||
self.stack[-1] = self.stack[-1] * 10 + v
|
||||
|
||||
self.display()
|
||||
|
||||
def operation(self, op):
|
||||
if self.current_op: # Complete the current operation
|
||||
self.equals()
|
||||
|
||||
self.stack.append(0)
|
||||
self.state = INPUT
|
||||
self.current_op = op
|
||||
|
||||
def operation_pc(self):
|
||||
self.state = INPUT
|
||||
self.stack[-1] *= 0.01
|
||||
self.display()
|
||||
|
||||
def equals(self):
|
||||
# Support to allow '=' to repeat previous operation
|
||||
# if no further input has been added.
|
||||
if self.state == READY and self.last_operation:
|
||||
s, self.current_op = self.last_operation
|
||||
self.stack.append(s)
|
||||
|
||||
if self.current_op:
|
||||
self.last_operation = self.stack[-1], self.current_op
|
||||
|
||||
try:
|
||||
self.stack = [self.current_op(*self.stack)]
|
||||
except Exception:
|
||||
self.lcdNumber.display("Err")
|
||||
self.stack = [0]
|
||||
else:
|
||||
self.current_op = None
|
||||
self.state = READY
|
||||
self.display()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Calculon")
|
||||
|
||||
window = MainWindow()
|
||||
app.exec_()
|
||||
583
pyside2/demos/calculator/mainwindow-weird.ui
Normal file
@@ -0,0 +1,583 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>484</width>
|
||||
<height>534</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Calculon</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="pushButton_n4">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>4</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QPushButton" name="pushButton_n1">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="pushButton_n8">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>8</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="pushButton_mul">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>x</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>*</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="pushButton_n7">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>7</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>7</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="pushButton_n6">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>6</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>6</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="pushButton_n5">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>5</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QPushButton" name="pushButton_n0">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="pushButton_n2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="pushButton_n9">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>9</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>9</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QPushButton" name="pushButton_n3">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>3</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="pushButton_div">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>÷</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>/</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QPushButton" name="pushButton_sub">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QPushButton" name="pushButton_add">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>+</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="pushButton_ac">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #f44336;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>AC</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Esc</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="pushButton_mr">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #FFC107;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>MR</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>R</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton_m">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #FFC107;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>M</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>M</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QPushButton" name="pushButton_pc">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>%</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2" colspan="2">
|
||||
<widget class="QPushButton" name="pushButton_eq">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #4CAF50;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>=</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Return</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLCDNumber" name="lcdNumber">
|
||||
<property name="digitCount">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>484</width>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionReset"/>
|
||||
<addaction name="actionExit"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusBar"/>
|
||||
<action name="actionExit">
|
||||
<property name="text">
|
||||
<string>Exit</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+R</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
583
pyside2/demos/calculator/mainwindow.ui
Normal file
@@ -0,0 +1,583 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>484</width>
|
||||
<height>433</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Calculon</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLCDNumber" name="lcdNumber">
|
||||
<property name="digitCount">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="pushButton_n4">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>4</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QPushButton" name="pushButton_n1">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="pushButton_n8">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>8</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QPushButton" name="pushButton_mul">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>x</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>*</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="pushButton_n7">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>7</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>7</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="pushButton_n6">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>6</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>6</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="pushButton_n5">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>5</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QPushButton" name="pushButton_n0">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="pushButton_n2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="pushButton_n9">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>9</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>9</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QPushButton" name="pushButton_n3">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #1976D2;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>3</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="pushButton_div">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>÷</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>/</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QPushButton" name="pushButton_sub">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3">
|
||||
<widget class="QPushButton" name="pushButton_add">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>+</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="pushButton_ac">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #f44336;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>AC</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Esc</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="pushButton_mr">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #FFC107;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>MR</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>R</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton_m">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #FFC107;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>M</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>M</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QPushButton" name="pushButton_pc">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>%</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2" colspan="2">
|
||||
<widget class="QPushButton" name="pushButton_eq">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>27</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton {
|
||||
color: #4CAF50;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>=</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Return</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>484</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>File</string>
|
||||
</property>
|
||||
<addaction name="actionReset"/>
|
||||
<addaction name="actionExit"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusBar"/>
|
||||
<action name="actionExit">
|
||||
<property name="text">
|
||||
<string>Exit</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Q</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReset">
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+R</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
1
pyside2/demos/calculator/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
PyQt5>=5.6
|
||||
BIN
pyside2/demos/calculator/screenshot-calculator.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
16
pyside2/demos/camera/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# NSAViewer — Somebody's watching you.
|
||||
|
||||
With this webcam snapshot application you can take photos of what is currently
|
||||
being viewed by your webcam. Uses the Qt QtMultimedia framework for handling
|
||||
all the interaction with the camera, and supports multiple cameras if you have
|
||||
them.
|
||||
|
||||

|
||||
|
||||
> If you want to learn more about build GUI applications with Python,
|
||||
take a look at my [PyQt5 tutorials](https://www.pythonguis.com)
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
||||
|
||||
## Other licenses
|
||||
|
||||
Icons used in the application are by [Yusuke Kamiyaman](http://p.yusukekamiyamane.com/).
|
||||
BIN
pyside2/demos/camera/images/blue-folder-horizontal-open.png
Executable file
|
After Width: | Height: | Size: 504 B |
BIN
pyside2/demos/camera/images/camera-black.png
Executable file
|
After Width: | Height: | Size: 571 B |
116
pyside2/demos/camera/main.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from PySide2.QtCore import QSize
|
||||
from PySide2.QtGui import QIcon
|
||||
from PySide2.QtMultimedia import QCamera, QCameraImageCapture, QCameraInfo
|
||||
from PySide2.QtMultimediaWidgets import QCameraViewfinder
|
||||
from PySide2.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QErrorMessage,
|
||||
QFileDialog,
|
||||
QMainWindow,
|
||||
QStatusBar,
|
||||
QToolBar,
|
||||
)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.available_cameras = QCameraInfo.availableCameras()
|
||||
if not self.available_cameras:
|
||||
pass # quit
|
||||
|
||||
self.status = QStatusBar()
|
||||
self.setStatusBar(self.status)
|
||||
|
||||
self.save_path = ""
|
||||
|
||||
self.viewfinder = QCameraViewfinder()
|
||||
self.viewfinder.show()
|
||||
self.setCentralWidget(self.viewfinder)
|
||||
|
||||
# Set the default camera.
|
||||
self.select_camera(0)
|
||||
|
||||
# Setup tools
|
||||
camera_toolbar = QToolBar("Camera")
|
||||
camera_toolbar.setIconSize(QSize(14, 14))
|
||||
self.addToolBar(camera_toolbar)
|
||||
|
||||
photo_action = QAction(
|
||||
QIcon(os.path.join("images", "camera-black.png")),
|
||||
"Take photo...",
|
||||
self,
|
||||
)
|
||||
photo_action.setStatusTip("Take photo of current view")
|
||||
photo_action.triggered.connect(self.take_photo)
|
||||
camera_toolbar.addAction(photo_action)
|
||||
|
||||
change_folder_action = QAction(
|
||||
QIcon(os.path.join("images", "blue-folder-horizontal-open.png")),
|
||||
"Change save location...",
|
||||
self,
|
||||
)
|
||||
change_folder_action.setStatusTip("Change folder where photos are saved.")
|
||||
change_folder_action.triggered.connect(self.change_folder)
|
||||
camera_toolbar.addAction(change_folder_action)
|
||||
|
||||
camera_selector = QComboBox()
|
||||
camera_selector.addItems([c.description() for c in self.available_cameras])
|
||||
camera_selector.currentIndexChanged.connect(self.select_camera)
|
||||
|
||||
camera_toolbar.addWidget(camera_selector)
|
||||
|
||||
self.setWindowTitle("NSAViewer")
|
||||
self.show()
|
||||
|
||||
def select_camera(self, i):
|
||||
self.camera = QCamera(self.available_cameras[i])
|
||||
self.camera.setViewfinder(self.viewfinder)
|
||||
self.camera.setCaptureMode(QCamera.CaptureStillImage)
|
||||
self.camera.error.connect(lambda: self.alert(self.camera.errorString()))
|
||||
self.camera.start()
|
||||
|
||||
self.capture = QCameraImageCapture(self.camera)
|
||||
self.capture.error.connect(lambda i, e, s: self.alert(s))
|
||||
self.capture.imageCaptured.connect(lambda d, i: self.status.showMessage("Image %04d captured" % self.save_seq))
|
||||
|
||||
self.current_camera_name = self.available_cameras[i].description()
|
||||
self.save_seq = 0
|
||||
|
||||
def take_photo(self):
|
||||
timestamp = time.strftime("%d-%b-%Y-%H_%M_%S")
|
||||
self.capture.capture(
|
||||
os.path.join(
|
||||
self.save_path,
|
||||
"%s-%04d-%s.jpg" % (self.current_camera_name, self.save_seq, timestamp),
|
||||
)
|
||||
)
|
||||
self.save_seq += 1
|
||||
|
||||
def change_folder(self):
|
||||
path = QFileDialog.getExistingDirectory(self, "Snapshot save location", "")
|
||||
if path:
|
||||
self.save_path = path
|
||||
self.save_seq = 0
|
||||
|
||||
def alert(self, s):
|
||||
"""
|
||||
Handle errors coming from QCamera dn QCameraImageCapture by displaying alerts.
|
||||
"""
|
||||
err = QErrorMessage(self)
|
||||
err.showMessage(s)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("NSAViewer")
|
||||
|
||||
window = MainWindow()
|
||||
app.exec_()
|
||||
1
pyside2/demos/camera/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
PyQt5>=5.6
|
||||
BIN
pyside2/demos/camera/screenshot-camera.jpg
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
pyside2/demos/colorpicker/images/color.png
Executable file
|
After Width: | Height: | Size: 1.4 KiB |
72
pyside2/demos/colorpicker/main.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide2.QtGui import QIcon
|
||||
from PySide2.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QColorDialog,
|
||||
QMenu,
|
||||
QSystemTrayIcon,
|
||||
)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
# Create the icon
|
||||
icon = QIcon(os.path.join("images", "color.png"))
|
||||
|
||||
clipboard = QApplication.clipboard()
|
||||
dialog = QColorDialog()
|
||||
|
||||
|
||||
def copy_color_hex():
|
||||
if dialog.exec_():
|
||||
color = dialog.currentColor()
|
||||
clipboard.setText(color.name())
|
||||
|
||||
|
||||
def copy_color_rgb():
|
||||
if dialog.exec_():
|
||||
color = dialog.currentColor()
|
||||
clipboard.setText(
|
||||
"rgb(%d, %d, %d)" % (color.red(), color.green(), color.blue())
|
||||
)
|
||||
|
||||
|
||||
def copy_color_hsv():
|
||||
if dialog.exec_():
|
||||
color = dialog.currentColor()
|
||||
clipboard.setText(
|
||||
"hsv(%d, %d, %d)" % (color.hue(), color.saturation(), color.value())
|
||||
)
|
||||
|
||||
|
||||
# Create the tray
|
||||
tray = QSystemTrayIcon()
|
||||
tray.setIcon(icon)
|
||||
tray.setVisible(True)
|
||||
|
||||
# Create the menu
|
||||
menu = QMenu()
|
||||
action1 = QAction("Hex")
|
||||
action1.triggered.connect(copy_color_hex)
|
||||
menu.addAction(action1)
|
||||
|
||||
action2 = QAction("RGB")
|
||||
action2.triggered.connect(copy_color_rgb)
|
||||
menu.addAction(action2)
|
||||
|
||||
action3 = QAction("HSV")
|
||||
action3.triggered.connect(copy_color_hsv)
|
||||
menu.addAction(action3)
|
||||
|
||||
action4 = QAction("Exit")
|
||||
action4.triggered.connect(app.quit)
|
||||
menu.addAction(action4)
|
||||
|
||||
# Add the menu to the tray
|
||||
tray.setContextMenu(menu)
|
||||
|
||||
|
||||
app.exec_()
|
||||
35
pyside2/demos/crypto/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Doughnut — An exchange rate tracker for people nuts about dough, in PyQt.
|
||||
|
||||
This is a simple currency exchange rate tracker implemented in PyQt, using the [fixer.io](http://fixer.io) API
|
||||
for data. The default setup shows currency data for the preceding 180 days.
|
||||
|
||||

|
||||
|
||||
Data is loaded progressively, with increasing resolution. Currency rates for a given date are shown in the right
|
||||
hand panel and updated to follow the position of the mouse.
|
||||
|
||||

|
||||
|
||||
> If you want to learn more about build GUI applications with Python,
|
||||
take a look at my [PyQt5 tutorials](https://www.pythonguis.com)
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
||||
|
||||
## Code notes
|
||||
|
||||
### Data handling
|
||||
|
||||
The interface presents a tracking plot (using PyQtGraph) of rates over the past 180 days. Since we don't want to
|
||||
spam a free service, requests to the API are rate-limited to 1-per-second, giving a full-data-load time of 180s (3 min).
|
||||
|
||||
To avoid waiting each time, we use `requests_cache` which uses a local sqlite database to store the result of recent
|
||||
requests. The requests for data use a progressive 'search' approach: where there is a gap in the data, the middle
|
||||
point is filled first, and it prefers to load the most recent timepoints first. This means the whole plot gradually
|
||||
increases in resolution over time, rather than working backwards only.
|
||||
|
||||
### Conversions
|
||||
|
||||
By default the app retrieves EUR rates and shows conversions to this base currency. If you change base currency
|
||||
it will retrieve all data again for that new currency. This is daft, since if we have rates vs. EUR we can calculate
|
||||
any other currency->currency conversion via EUR (with a small loss of accuracy).
|
||||
|
||||
|
||||
38
pyside2/demos/crypto/constants.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# CryptoCompare.com API Key
|
||||
CRYPTOCOMPARE_API_KEY = ""
|
||||
|
||||
|
||||
# Base currency is used to retrieve rates from bitcoinaverage.
|
||||
DEFAULT_BASE_CURRENCY = "USD"
|
||||
AVAILABLE_BASE_CURRENCIES = ["USD", "EUR", "GBP"]
|
||||
|
||||
# The crypto currencies to retrieve data about.
|
||||
AVAILABLE_CRYPTO_CURRENCIES = [
|
||||
"BTC",
|
||||
"ETH",
|
||||
"LTC",
|
||||
"EOS",
|
||||
"XRP",
|
||||
"BCH",
|
||||
] #
|
||||
DEFAULT_DISPLAY_CURRENCIES = ["BTC", "ETH", "LTC"]
|
||||
|
||||
|
||||
# Number of historic timepoints to plot (days).
|
||||
NUMBER_OF_TIMEPOINTS = 150
|
||||
|
||||
# Colour cycle to use for plotting currencies.
|
||||
BREWER12PAIRED = [
|
||||
"#a6cee3",
|
||||
"#1f78b4",
|
||||
"#b2df8a",
|
||||
"#33a02c",
|
||||
"#fb9a99",
|
||||
"#e31a1c",
|
||||
"#fdbf6f",
|
||||
"#ff7f00",
|
||||
"#cab2d6",
|
||||
"#6a3d9a",
|
||||
"#ffff99",
|
||||
"#b15928",
|
||||
]
|
||||
305
pyside2/demos/crypto/main.py
Normal file
@@ -0,0 +1,305 @@
|
||||
import sys
|
||||
from itertools import cycle
|
||||
|
||||
import constants
|
||||
import numpy as np
|
||||
|
||||
# import requests_cache
|
||||
from PySide2.QtCore import (
|
||||
Qt,
|
||||
QThreadPool,
|
||||
)
|
||||
from PySide2.QtGui import (
|
||||
QBrush,
|
||||
QColor,
|
||||
QStandardItem,
|
||||
QStandardItemModel,
|
||||
)
|
||||
from PySide2.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QHBoxLayout,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
QProgressBar,
|
||||
QTableView,
|
||||
QToolBar,
|
||||
QWidget,
|
||||
)
|
||||
from workers import UpdateWorker
|
||||
|
||||
color_cycle = cycle(constants.BREWER12PAIRED)
|
||||
# requests_cache.install_cache('cache')
|
||||
|
||||
# Must be imported after PySide2.
|
||||
import pyqtgraph as pg
|
||||
|
||||
pg.setConfigOption("background", "w")
|
||||
pg.setConfigOption("foreground", "k")
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
layout = QHBoxLayout()
|
||||
|
||||
self.ax = pg.PlotWidget()
|
||||
self.ax.showGrid(True, True)
|
||||
|
||||
self.line = pg.InfiniteLine(
|
||||
pos=-20,
|
||||
pen=pg.mkPen("k", width=3),
|
||||
movable=False, # We have our own code to handle dragless moving.
|
||||
)
|
||||
|
||||
self.ax.addItem(self.line)
|
||||
self.ax.setLabel("left", text="Rate")
|
||||
self.p1 = self.ax.getPlotItem()
|
||||
self.p1.scene().sigMouseMoved.connect(self.mouse_move_handler)
|
||||
|
||||
# Add the right-hand axis for the market activity.
|
||||
self.p2 = pg.ViewBox()
|
||||
self.p2.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True)
|
||||
self.p1.showAxis("right")
|
||||
self.p1.scene().addItem(self.p2)
|
||||
self.p2.setXLink(self.p1)
|
||||
self.ax2 = self.p1.getAxis("right")
|
||||
self.ax2.linkToView(self.p2)
|
||||
self.ax2.setGrid(False)
|
||||
self.ax2.setLabel(text="Volume")
|
||||
|
||||
self._market_activity = pg.PlotCurveItem(
|
||||
np.arange(constants.NUMBER_OF_TIMEPOINTS),
|
||||
np.arange(constants.NUMBER_OF_TIMEPOINTS),
|
||||
pen=pg.mkPen("k", style=Qt.PenStyle.DashLine, width=1),
|
||||
)
|
||||
self.p2.addItem(self._market_activity)
|
||||
|
||||
# Automatically rescale our twinned Y axis.
|
||||
self.p1.vb.sigResized.connect(self.update_plot_scale)
|
||||
|
||||
self.base_currency = constants.DEFAULT_BASE_CURRENCY
|
||||
|
||||
# Store a reference to lines on the plot, and items in our
|
||||
# data viewer we can update rather than redraw.
|
||||
self._data_lines = dict()
|
||||
self._data_items = dict()
|
||||
self._data_colors = dict()
|
||||
self._data_visible = constants.DEFAULT_DISPLAY_CURRENCIES
|
||||
|
||||
self.listView = QTableView()
|
||||
self.model = QStandardItemModel()
|
||||
self.model.setHorizontalHeaderLabels(["Currency", "Rate"])
|
||||
self.model.itemChanged.connect(self.check_check_state)
|
||||
|
||||
self.listView.setModel(self.model)
|
||||
|
||||
self.threadpool = QThreadPool()
|
||||
self.worker = False
|
||||
|
||||
layout.addWidget(self.ax)
|
||||
layout.addWidget(self.listView)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setLayout(layout)
|
||||
self.setCentralWidget(widget)
|
||||
self.listView.setFixedSize(226, 400)
|
||||
self.setFixedSize(650, 400)
|
||||
|
||||
toolbar = QToolBar("Main")
|
||||
self.addToolBar(toolbar)
|
||||
self.currencyList = QComboBox()
|
||||
|
||||
toolbar.addWidget(self.currencyList)
|
||||
self.update_currency_list(constants.AVAILABLE_BASE_CURRENCIES)
|
||||
self.currencyList.setCurrentText(self.base_currency)
|
||||
self.currencyList.currentTextChanged.connect(self.change_base_currency)
|
||||
|
||||
self.progress = QProgressBar()
|
||||
self.progress.setRange(0, 100)
|
||||
toolbar.addWidget(self.progress)
|
||||
|
||||
self.refresh_historic_rates()
|
||||
self.setWindowTitle("Goodforbitcoin")
|
||||
self.show()
|
||||
|
||||
def update_currency_list(self, currencies):
|
||||
for currency in currencies:
|
||||
self.currencyList.addItem(currency)
|
||||
|
||||
self.currencyList.model().sort(0)
|
||||
|
||||
def check_check_state(self, i):
|
||||
if not i.isCheckable(): # Skip data columns.
|
||||
return
|
||||
|
||||
currency = i.text()
|
||||
checked = i.checkState() == Qt.CheckState.Checked
|
||||
|
||||
if currency in self._data_visible:
|
||||
if not checked:
|
||||
self._data_visible.remove(currency)
|
||||
self.redraw()
|
||||
else:
|
||||
if checked:
|
||||
self._data_visible.append(currency)
|
||||
self.redraw()
|
||||
|
||||
def get_currency_color(self, currency):
|
||||
if currency not in self._data_colors:
|
||||
self._data_colors[currency] = next(color_cycle)
|
||||
|
||||
return self._data_colors[currency]
|
||||
|
||||
def get_or_create_data_row(self, currency):
|
||||
if currency not in self._data_items:
|
||||
self._data_items[currency] = self.add_data_row(currency)
|
||||
return self._data_items[currency]
|
||||
|
||||
def add_data_row(self, currency):
|
||||
citem = QStandardItem()
|
||||
citem.setText(currency)
|
||||
citem.setForeground(QBrush(QColor(self.get_currency_color(currency))))
|
||||
citem.setColumnCount(2)
|
||||
citem.setCheckable(True)
|
||||
if currency in constants.DEFAULT_DISPLAY_CURRENCIES:
|
||||
citem.setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
vitem = QStandardItem()
|
||||
|
||||
vitem.setTextAlignment(
|
||||
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
self.model.setColumnCount(2)
|
||||
self.model.appendRow([citem, vitem])
|
||||
self.model.sort(0)
|
||||
return citem, vitem
|
||||
|
||||
def mouse_move_handler(self, pos):
|
||||
pos = self.ax.getViewBox().mapSceneToView(pos)
|
||||
self.line.setPos(pos.x())
|
||||
self.update_data_viewer(int(pos.x()))
|
||||
|
||||
def update_data_viewer(self, i):
|
||||
if i not in range(constants.NUMBER_OF_TIMEPOINTS):
|
||||
return
|
||||
|
||||
for currency, data in self.data.items():
|
||||
self.update_data_row(currency, data[i])
|
||||
|
||||
def update_data_row(self, currency, data):
|
||||
citem, vitem = self.get_or_create_data_row(currency)
|
||||
vitem.setText("%.4f" % data["close"])
|
||||
|
||||
def change_base_currency(self, currency):
|
||||
self.base_currency = currency
|
||||
self.refresh_historic_rates()
|
||||
|
||||
def refresh_historic_rates(self):
|
||||
if self.worker:
|
||||
# If we have a current worker, send a kill signal
|
||||
self.worker.signals.cancel.emit()
|
||||
|
||||
# Prefill our data store with None ('no data')
|
||||
self.data = {}
|
||||
self.volume = []
|
||||
|
||||
self.worker = UpdateWorker(self.base_currency)
|
||||
# Handle callbacks with data and trigger refresh.
|
||||
self.worker.signals.data.connect(self.result_data_callback)
|
||||
self.worker.signals.finished.connect(self.refresh_finished)
|
||||
self.worker.signals.progress.connect(self.progress_callback)
|
||||
self.worker.signals.error.connect(self.notify_error)
|
||||
self.threadpool.start(self.worker)
|
||||
|
||||
def result_data_callback(self, rates, volume):
|
||||
self.data = rates
|
||||
self.volume = volume
|
||||
self.redraw()
|
||||
self.update_data_viewer(constants.NUMBER_OF_TIMEPOINTS - 1)
|
||||
|
||||
def progress_callback(self, progress):
|
||||
self.progress.setValue(progress)
|
||||
|
||||
def refresh_finished(self):
|
||||
self.worker = False
|
||||
self.redraw()
|
||||
|
||||
def notify_error(self, error):
|
||||
e, tb = error
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Icon.Warning)
|
||||
msg.setText(e.__class__.__name__)
|
||||
msg.setInformativeText(str(e))
|
||||
msg.setDetailedText(tb)
|
||||
msg.exec_()
|
||||
|
||||
def update_plot_scale(self):
|
||||
self.p2.setGeometry(self.p1.vb.sceneBoundingRect())
|
||||
|
||||
def redraw(self):
|
||||
y_min, y_max = sys.maxsize, 0
|
||||
x = np.arange(constants.NUMBER_OF_TIMEPOINTS)
|
||||
|
||||
# Pre-process data into lists of x, y values.
|
||||
for currency, data in self.data.items():
|
||||
if data:
|
||||
_, close, high, low = zip(
|
||||
*[(v["time"], v["close"], v["high"], v["low"]) for v in data]
|
||||
)
|
||||
|
||||
if currency in self._data_visible:
|
||||
# This line should be visible, if it's not drawn draw it.
|
||||
if currency not in self._data_lines:
|
||||
self._data_lines[currency] = {}
|
||||
self._data_lines[currency]["high"] = self.ax.plot(
|
||||
x,
|
||||
high, # Unpack a list of tuples into two lists, passed as individual args.
|
||||
pen=pg.mkPen(
|
||||
self.get_currency_color(currency),
|
||||
width=2,
|
||||
style=Qt.DotLine,
|
||||
),
|
||||
)
|
||||
self._data_lines[currency]["low"] = self.ax.plot(
|
||||
x,
|
||||
low, # Unpack a list of tuples into two lists, passed as individual args.
|
||||
pen=pg.mkPen(
|
||||
self.get_currency_color(currency),
|
||||
width=2,
|
||||
style=Qt.DotLine,
|
||||
),
|
||||
)
|
||||
self._data_lines[currency]["close"] = self.ax.plot(
|
||||
x,
|
||||
close, # Unpack a list of tuples into two lists, passed as individual args.
|
||||
pen=pg.mkPen(
|
||||
self.get_currency_color(currency),
|
||||
width=3,
|
||||
),
|
||||
)
|
||||
else:
|
||||
self._data_lines[currency]["high"].setData(x, high)
|
||||
self._data_lines[currency]["low"].setData(x, low)
|
||||
self._data_lines[currency]["close"].setData(x, close)
|
||||
|
||||
y_min, y_max = min(y_min, *low), max(y_max, *high)
|
||||
|
||||
else:
|
||||
# This line should not be visible, if it is delete it.
|
||||
if currency in self._data_lines:
|
||||
self._data_lines[currency]["high"].clear()
|
||||
self._data_lines[currency]["low"].clear()
|
||||
self._data_lines[currency]["close"].clear()
|
||||
|
||||
self.ax.setLimits(yMin=y_min * 0.9, yMax=y_max * 1.1, xMin=min(x), xMax=max(x))
|
||||
|
||||
self._market_activity.setData(x, self.volume)
|
||||
self.p2.setYRange(0, max(self.volume))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
app.exec_()
|
||||
4
pyside2/demos/crypto/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
PyQt5>=5.6
|
||||
requests>=2.0.0
|
||||
requests-cache>=0.4.13
|
||||
pyqtgraph>=0.10
|
||||
BIN
pyside2/demos/crypto/resources/bitcoin-icon.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
pyside2/demos/crypto/resources/icon.icns
Normal file
BIN
pyside2/demos/crypto/resources/icon.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
91
pyside2/demos/crypto/workers.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import traceback
|
||||
|
||||
import constants
|
||||
import requests
|
||||
|
||||
# import requests_cache
|
||||
from PySide2.QtCore import (
|
||||
QObject,
|
||||
QRunnable,
|
||||
Signal,
|
||||
Slot,
|
||||
)
|
||||
|
||||
|
||||
class WorkerSignals(QObject):
|
||||
"""
|
||||
Defines the signals available from a running worker thread.
|
||||
"""
|
||||
|
||||
finished = Signal()
|
||||
error = Signal(tuple)
|
||||
progress = Signal(int)
|
||||
data = Signal(dict, list)
|
||||
cancel = Signal()
|
||||
|
||||
|
||||
class UpdateWorker(QRunnable):
|
||||
"""
|
||||
Worker thread for updating currency.
|
||||
"""
|
||||
|
||||
signals = WorkerSignals()
|
||||
|
||||
def __init__(self, base_currency):
|
||||
super().__init__()
|
||||
self.is_interrupted = False
|
||||
self.base_currency = base_currency
|
||||
self.signals.cancel.connect(self.cancel)
|
||||
|
||||
@Slot()
|
||||
def run(self):
|
||||
auth_header = {"Apikey": constants.CRYPTOCOMPARE_API_KEY}
|
||||
try:
|
||||
rates = {}
|
||||
for n, crypto in enumerate(constants.AVAILABLE_CRYPTO_CURRENCIES, 1):
|
||||
url = "https://min-api.cryptocompare.com/data/histoday?fsym={fsym}&tsym={tsym}&limit={limit}"
|
||||
r = requests.get(
|
||||
url.format(
|
||||
**{
|
||||
"fsym": crypto,
|
||||
"tsym": self.base_currency,
|
||||
"limit": constants.NUMBER_OF_TIMEPOINTS - 1,
|
||||
"extraParams": "www.pythonguis.com",
|
||||
"format": "json",
|
||||
}
|
||||
),
|
||||
headers=auth_header,
|
||||
)
|
||||
r.raise_for_status()
|
||||
rates[crypto] = r.json().get("Data")
|
||||
|
||||
self.signals.progress.emit(int(100 * n / len(constants.AVAILABLE_CRYPTO_CURRENCIES)))
|
||||
|
||||
if self.is_interrupted:
|
||||
# Stop without emitting finish signals.
|
||||
return
|
||||
|
||||
url = "https://min-api.cryptocompare.com/data/exchange/histoday?tsym={tsym}&limit={limit}"
|
||||
r = requests.get(
|
||||
url.format(
|
||||
**{
|
||||
"tsym": self.base_currency,
|
||||
"limit": constants.NUMBER_OF_TIMEPOINTS - 1,
|
||||
"extraParams": "www.pythonguis.com",
|
||||
"format": "json",
|
||||
}
|
||||
),
|
||||
headers=auth_header,
|
||||
)
|
||||
r.raise_for_status()
|
||||
volume = [d["volume"] for d in r.json().get("Data")]
|
||||
|
||||
except Exception as e:
|
||||
self.signals.error.emit((e, traceback.format_exc()))
|
||||
return
|
||||
|
||||
self.signals.data.emit(rates, volume)
|
||||
self.signals.finished.emit()
|
||||
|
||||
def cancel(self):
|
||||
self.is_interrupted = True
|
||||
35
pyside2/demos/currency/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Doughnut — An exchange rate tracker for people nuts about dough, in PyQt.
|
||||
|
||||
This is a simple currency exchange rate tracker implemented in PyQt, using the [fixer.io](http://fixer.io) API
|
||||
for data. The default setup shows currency data for the preceding 180 days.
|
||||
|
||||

|
||||
|
||||
Data is loaded progressively, with increasing resolution. Currency rates for a given date are shown in the right
|
||||
hand panel and updated to follow the position of the mouse.
|
||||
|
||||

|
||||
|
||||
> If you want to learn more about build GUI applications with Python,
|
||||
take a look at my [PyQt5 tutorials](https://www.pythonguis.com)
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
||||
|
||||
## Code notes
|
||||
|
||||
### Data handling
|
||||
|
||||
The interface presents a tracking plot (using PyQtGraph) of rates over the past 180 days. Since we don't want to
|
||||
spam a free service, requests to the API are rate-limited to 1-per-second, giving a full-data-load time of 180s (3 min).
|
||||
|
||||
To avoid waiting each time, we use `requests_cache` which uses a local sqlite database to store the result of recent
|
||||
requests. The requests for data use a progressive 'search' approach: where there is a gap in the data, the middle
|
||||
point is filled first, and it prefers to load the most recent timepoints first. This means the whole plot gradually
|
||||
increases in resolution over time, rather than working backwards only.
|
||||
|
||||
### Conversions
|
||||
|
||||
By default the app retrieves EUR rates and shows conversions to this base currency. If you change base currency
|
||||
it will retrieve all data again for that new currency. This is daft, since if we have rates vs. EUR we can calculate
|
||||
any other currency->currency conversion via EUR (with a small loss of accuracy).
|
||||
|
||||
|
||||
31
pyside2/demos/currency/constants.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Base currency is used to retrieve rates from fixer.io.
|
||||
# If we change currency we re-request, though it would
|
||||
# be possible to calculate any rates *through* the base.
|
||||
DEFAULT_BASE_CURRENCY = "EUR"
|
||||
DEFAULT_DISPLAY_CURRENCIES = [
|
||||
"CAD",
|
||||
"CYP",
|
||||
"AUD",
|
||||
"USD",
|
||||
"EUR",
|
||||
"GBP",
|
||||
"NZD",
|
||||
"SGD",
|
||||
]
|
||||
HISTORIC_DAYS_N = 180
|
||||
|
||||
# Colour sets.
|
||||
BREWER12PAIRED = [
|
||||
"#a6cee3",
|
||||
"#1f78b4",
|
||||
"#b2df8a",
|
||||
"#33a02c",
|
||||
"#fb9a99",
|
||||
"#e31a1c",
|
||||
"#fdbf6f",
|
||||
"#ff7f00",
|
||||
"#cab2d6",
|
||||
"#6a3d9a",
|
||||
"#ffff99",
|
||||
"#b15928",
|
||||
]
|
||||
352
pyside2/demos/currency/main.py
Normal file
@@ -0,0 +1,352 @@
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime, timedelta
|
||||
from itertools import cycle
|
||||
|
||||
import constants
|
||||
import requests
|
||||
|
||||
# import requests_cache
|
||||
from PySide2.QtCore import (
|
||||
QObject,
|
||||
QRunnable,
|
||||
Qt,
|
||||
QThreadPool,
|
||||
Signal,
|
||||
Slot,
|
||||
)
|
||||
from PySide2.QtGui import (
|
||||
QBrush,
|
||||
QColor,
|
||||
QStandardItem,
|
||||
QStandardItemModel,
|
||||
)
|
||||
from PySide2.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QHBoxLayout,
|
||||
QMainWindow,
|
||||
QProgressBar,
|
||||
QTableView,
|
||||
QToolBar,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
color_cycle = cycle(constants.BREWER12PAIRED)
|
||||
# requests_cache.install_cache('cache')
|
||||
# PyQtGraph must be imported after Qt.
|
||||
import pyqtgraph as pg
|
||||
|
||||
pg.setConfigOption("background", "w")
|
||||
pg.setConfigOption("foreground", "k")
|
||||
|
||||
|
||||
# Build progressive request order, for filling up data
|
||||
# Uses an depth-first search pattern, filling more recent data
|
||||
# to a higher resolution more quickly with a
|
||||
DATE_REQUEST_OFFSETS = [0]
|
||||
current = [(0, constants.HISTORIC_DAYS_N)]
|
||||
while current:
|
||||
a, b = current.pop(0)
|
||||
n = (a + b) // 2
|
||||
DATE_REQUEST_OFFSETS.append(n)
|
||||
|
||||
if abs(a - n) > 1:
|
||||
current.insert(0, (a, n))
|
||||
|
||||
if abs(b - n) > 1:
|
||||
current.append((b, n))
|
||||
|
||||
|
||||
class WorkerSignals(QObject):
|
||||
"""
|
||||
Defines the signals available from a running worker thread.
|
||||
"""
|
||||
|
||||
finished = Signal()
|
||||
error = Signal(tuple)
|
||||
progress = Signal(int)
|
||||
data = Signal(int, dict)
|
||||
cancel = Signal()
|
||||
|
||||
|
||||
class UpdateWorker(QRunnable):
|
||||
"""
|
||||
Worker thread for updating currency.
|
||||
"""
|
||||
|
||||
signals = WorkerSignals()
|
||||
is_interrupted = False
|
||||
|
||||
def __init__(self, base_currency):
|
||||
super().__init__()
|
||||
self.base_currency = base_currency
|
||||
self.signals.cancel.connect(self.cancel)
|
||||
|
||||
@Slot()
|
||||
def run(self):
|
||||
try:
|
||||
today = date.today()
|
||||
total_requests = len(DATE_REQUEST_OFFSETS)
|
||||
|
||||
for n, offset in enumerate(DATE_REQUEST_OFFSETS, 1):
|
||||
when = today - timedelta(days=offset)
|
||||
url = "http://api.fixer.io/{}".format(when.isoformat())
|
||||
r = requests.get(url, params={"base": self.base_currency})
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
rates = data["rates"]
|
||||
rates[self.base_currency] = 1.0
|
||||
|
||||
self.signals.data.emit(offset, rates)
|
||||
self.signals.progress.emit(int(100 * n / total_requests))
|
||||
|
||||
if not r.from_cache:
|
||||
time.sleep(1) # Don't be rude.
|
||||
|
||||
if self.is_interrupted:
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
exctype, value = sys.exc_info()[:2]
|
||||
self.signals.error.emit((exctype, value, traceback.format_exc()))
|
||||
return
|
||||
|
||||
self.signals.finished.emit()
|
||||
|
||||
def cancel(self):
|
||||
self.is_interrupted = True
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
layout = QHBoxLayout()
|
||||
|
||||
self.ax = pg.PlotWidget()
|
||||
self.ax.showGrid(True, True)
|
||||
|
||||
self.line = pg.InfiniteLine(
|
||||
pos=-20,
|
||||
pen=pg.mkPen("k", width=3),
|
||||
movable=False, # We have our own code to handle dragless moving.
|
||||
)
|
||||
|
||||
self.ax.addItem(self.line)
|
||||
self.ax.setLimits(xMin=-constants.HISTORIC_DAYS_N + 1, xMax=0)
|
||||
self.ax.getPlotItem().scene().sigMouseMoved.connect(self.mouse_move_handler)
|
||||
|
||||
self.base_currency = constants.DEFAULT_BASE_CURRENCY
|
||||
|
||||
# Store a reference to lines on the plot, and items in our
|
||||
# data viewer we can update rather than redraw.
|
||||
self._data_lines = dict()
|
||||
self._data_items = dict()
|
||||
self._data_colors = dict()
|
||||
self._data_visible = constants.DEFAULT_DISPLAY_CURRENCIES
|
||||
|
||||
self._last_updated = None
|
||||
|
||||
self.listView = QTableView()
|
||||
self.model = QStandardItemModel()
|
||||
self.model.setHorizontalHeaderLabels(["Currency", "Rate"])
|
||||
self.model.itemChanged.connect(self.check_check_state)
|
||||
|
||||
self.listView.setModel(self.model)
|
||||
|
||||
self.threadpool = QThreadPool()
|
||||
self.worker = False
|
||||
|
||||
layout.addWidget(self.ax)
|
||||
layout.addWidget(self.listView)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setLayout(layout)
|
||||
self.setCentralWidget(widget)
|
||||
self.listView.setFixedSize(226, 400)
|
||||
self.setFixedSize(650, 400)
|
||||
|
||||
toolbar = QToolBar("Main")
|
||||
self.addToolBar(toolbar)
|
||||
self.currencyList = QComboBox()
|
||||
|
||||
toolbar.addWidget(self.currencyList)
|
||||
self.update_currency_list(constants.DEFAULT_DISPLAY_CURRENCIES)
|
||||
self.currencyList.setCurrentText(self.base_currency)
|
||||
self.currencyList.currentTextChanged.connect(self.change_base_currency)
|
||||
|
||||
self.progress = QProgressBar()
|
||||
self.progress.setRange(0, 100)
|
||||
toolbar.addWidget(self.progress)
|
||||
|
||||
self.refresh_historic_rates()
|
||||
self.setWindowTitle("Doughnut")
|
||||
self.show()
|
||||
|
||||
def update_currency_list(self, currencies):
|
||||
for currency in currencies:
|
||||
if self.currencyList.findText(currency) == -1:
|
||||
self.currencyList.addItem(currency)
|
||||
|
||||
self.currencyList.model().sort(0)
|
||||
|
||||
def check_check_state(self, i):
|
||||
if not i.isCheckable(): # Skip data columns.
|
||||
return
|
||||
|
||||
currency = i.text()
|
||||
checked = i.checkState() == Qt.CheckState.Checked
|
||||
|
||||
if currency in self._data_visible:
|
||||
if not checked:
|
||||
self._data_visible.remove(currency)
|
||||
self.redraw()
|
||||
else:
|
||||
if checked:
|
||||
self._data_visible.append(currency)
|
||||
self.redraw()
|
||||
|
||||
def get_currency_color(self, currency):
|
||||
if currency not in self._data_colors:
|
||||
self._data_colors[currency] = next(color_cycle)
|
||||
|
||||
return self._data_colors[currency]
|
||||
|
||||
def add_data_row(self, currency):
|
||||
citem = QStandardItem()
|
||||
citem.setText(currency)
|
||||
citem.setForeground(QBrush(QColor(self.get_currency_color(currency))))
|
||||
citem.setColumnCount(2)
|
||||
citem.setCheckable(True)
|
||||
if currency in constants.DEFAULT_DISPLAY_CURRENCIES:
|
||||
citem.setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
vitem = QStandardItem()
|
||||
|
||||
vitem.setTextAlignment(
|
||||
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
self.model.setColumnCount(2)
|
||||
self.model.appendRow([citem, vitem])
|
||||
self.model.sort(0)
|
||||
return citem, vitem
|
||||
|
||||
def get_or_create_data_row(self, currency):
|
||||
if currency not in self._data_items:
|
||||
self._data_items[currency] = self.add_data_row(currency)
|
||||
return self._data_items[currency]
|
||||
|
||||
def mouse_move_handler(self, pos):
|
||||
pos = self.ax.getViewBox().mapSceneToView(pos)
|
||||
self.line.setPos(pos.x())
|
||||
self.update_data_viewer(int(pos.x()))
|
||||
|
||||
def update_data_row(self, currency, value):
|
||||
citem, vitem = self.get_or_create_data_row(currency)
|
||||
vitem.setText("%.4f" % value)
|
||||
|
||||
def update_data_viewer(self, d):
|
||||
try:
|
||||
data = self.data[d]
|
||||
except IndexError: # Skip update if out of bounds.
|
||||
return
|
||||
|
||||
if not data: # Skip update if we have no data.
|
||||
return
|
||||
|
||||
for k, v in data.items():
|
||||
self.update_data_row(k, v)
|
||||
|
||||
def change_base_currency(self, currency):
|
||||
self.base_currency = currency
|
||||
self.refresh_historic_rates()
|
||||
|
||||
def refresh_historic_rates(self):
|
||||
if self.worker:
|
||||
# If we have a current worker, send a kill signal
|
||||
self.worker.signals.cancel.emit()
|
||||
|
||||
# Prefill our data store with None ('no data')
|
||||
self.data = [None] * constants.HISTORIC_DAYS_N
|
||||
|
||||
self.worker = UpdateWorker(self.base_currency)
|
||||
# Handle callbacks with data and trigger refresh.
|
||||
self.worker.signals.data.connect(self.result_data_callback)
|
||||
self.worker.signals.finished.connect(self.refresh_finished)
|
||||
self.worker.signals.progress.connect(self.progress_callback)
|
||||
self.threadpool.start(self.worker)
|
||||
|
||||
def result_data_callback(self, n, rates):
|
||||
self.data[n] = rates
|
||||
|
||||
# Refresh plot if we haven't for >1 second.
|
||||
if (
|
||||
self._last_updated is None
|
||||
or self._last_updated < datetime.now() - timedelta(seconds=1)
|
||||
):
|
||||
self.redraw()
|
||||
self._last_updated = datetime.now()
|
||||
|
||||
def progress_callback(self, progress):
|
||||
self.progress.setValue(progress)
|
||||
|
||||
def refresh_finished(self):
|
||||
self.worker = False
|
||||
self.redraw()
|
||||
# Ensure all currencies we know about are in the dropdown list now.
|
||||
self.update_currency_list(self._data_items.keys())
|
||||
|
||||
def redraw(self):
|
||||
"""
|
||||
Process data from store and prefer to draw.
|
||||
:return:
|
||||
"""
|
||||
today = date.today()
|
||||
plotd = defaultdict(list)
|
||||
x_ticks = []
|
||||
|
||||
tick_step_size = constants.HISTORIC_DAYS_N / 6
|
||||
# Pre-process data into lists of x, y values
|
||||
for n, data in enumerate(self.data):
|
||||
if data:
|
||||
for currency, v in data.items():
|
||||
plotd[currency].append((-n, v))
|
||||
|
||||
when = today - timedelta(days=n)
|
||||
if (n - tick_step_size // 2) % tick_step_size == 0:
|
||||
x_ticks.append((-n, when.strftime("%d-%m")))
|
||||
|
||||
# Update the plot
|
||||
keys = sorted(plotd.keys())
|
||||
y_min, y_max = sys.maxsize, 0
|
||||
|
||||
for currency in keys:
|
||||
x, y = zip(*plotd[currency])
|
||||
|
||||
if currency in self._data_visible:
|
||||
y_min = min(y_min, *y)
|
||||
y_max = max(y_max, *y)
|
||||
else:
|
||||
x, y = [], []
|
||||
|
||||
if currency in self._data_lines:
|
||||
self._data_lines[currency].setData(x, y)
|
||||
else:
|
||||
self._data_lines[currency] = self.ax.plot(
|
||||
x,
|
||||
y, # Unpack a list of tuples into two lists, passed as individual args.
|
||||
pen=pg.mkPen(self.get_currency_color(currency), width=2),
|
||||
)
|
||||
|
||||
self.ax.setLimits(yMin=y_min * 0.9, yMax=y_max * 1.1)
|
||||
self.ax.getAxis("bottom").setTicks([x_ticks, []])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
app.exec_()
|
||||
4
pyside2/demos/currency/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
PyQt5>=5.6
|
||||
requests>=2.0.0
|
||||
requests_cache>=0.4.13
|
||||
pyqtgraph>=0.10
|
||||
BIN
pyside2/demos/currency/screenshot-currency1.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
pyside2/demos/currency/screenshot-currency2.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
170
pyside2/demos/mediaplayer/MainWindow.py
Normal file
@@ -0,0 +1,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'mainwindow.ui'
|
||||
#
|
||||
# Created by: PySide2 UI code generator 5.10
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(484, 371)
|
||||
self.centralWidget = QtWidgets.QWidget(MainWindow)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.centralWidget.sizePolicy().hasHeightForWidth())
|
||||
self.centralWidget.setSizePolicy(sizePolicy)
|
||||
self.centralWidget.setObjectName("centralWidget")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralWidget)
|
||||
self.horizontalLayout.setContentsMargins(11, 11, 11, 11)
|
||||
self.horizontalLayout.setSpacing(6)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout.setSpacing(6)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.playlistView = QtWidgets.QListView(self.centralWidget)
|
||||
self.playlistView.setAcceptDrops(True)
|
||||
self.playlistView.setProperty("showDropIndicator", True)
|
||||
self.playlistView.setDragDropMode(QtWidgets.QAbstractItemView.DropOnly)
|
||||
self.playlistView.setAlternatingRowColors(True)
|
||||
self.playlistView.setUniformItemSizes(True)
|
||||
self.playlistView.setObjectName("playlistView")
|
||||
self.verticalLayout.addWidget(self.playlistView)
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_4.setSpacing(6)
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.currentTimeLabel = QtWidgets.QLabel(self.centralWidget)
|
||||
self.currentTimeLabel.setMinimumSize(QtCore.QSize(80, 0))
|
||||
self.currentTimeLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignTrailing | QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.currentTimeLabel.setObjectName("currentTimeLabel")
|
||||
self.horizontalLayout_4.addWidget(self.currentTimeLabel)
|
||||
self.timeSlider = QtWidgets.QSlider(self.centralWidget)
|
||||
self.timeSlider.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
||||
self.timeSlider.setObjectName("timeSlider")
|
||||
self.horizontalLayout_4.addWidget(self.timeSlider)
|
||||
self.totalTimeLabel = QtWidgets.QLabel(self.centralWidget)
|
||||
self.totalTimeLabel.setMinimumSize(QtCore.QSize(80, 0))
|
||||
self.totalTimeLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading | QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter)
|
||||
self.totalTimeLabel.setObjectName("totalTimeLabel")
|
||||
self.horizontalLayout_4.addWidget(self.totalTimeLabel)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout_4)
|
||||
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_5.setSpacing(6)
|
||||
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
|
||||
self.previousButton = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.previousButton.setText("")
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(
|
||||
QtGui.QPixmap("images/control-skip-180.png"),
|
||||
QtGui.QIcon.Normal,
|
||||
QtGui.QIcon.Off,
|
||||
)
|
||||
self.previousButton.setIcon(icon)
|
||||
self.previousButton.setObjectName("previousButton")
|
||||
self.horizontalLayout_5.addWidget(self.previousButton)
|
||||
self.playButton = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.playButton.setText("")
|
||||
icon1 = QtGui.QIcon()
|
||||
icon1.addPixmap(
|
||||
QtGui.QPixmap("images/control.png"),
|
||||
QtGui.QIcon.Normal,
|
||||
QtGui.QIcon.Off,
|
||||
)
|
||||
self.playButton.setIcon(icon1)
|
||||
self.playButton.setObjectName("playButton")
|
||||
self.horizontalLayout_5.addWidget(self.playButton)
|
||||
self.pauseButton = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.pauseButton.setText("")
|
||||
icon2 = QtGui.QIcon()
|
||||
icon2.addPixmap(
|
||||
QtGui.QPixmap("images/control-pause.png"),
|
||||
QtGui.QIcon.Normal,
|
||||
QtGui.QIcon.Off,
|
||||
)
|
||||
self.pauseButton.setIcon(icon2)
|
||||
self.pauseButton.setObjectName("pauseButton")
|
||||
self.horizontalLayout_5.addWidget(self.pauseButton)
|
||||
self.stopButton = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.stopButton.setText("")
|
||||
icon3 = QtGui.QIcon()
|
||||
icon3.addPixmap(
|
||||
QtGui.QPixmap("images/control-stop-square.png"),
|
||||
QtGui.QIcon.Normal,
|
||||
QtGui.QIcon.Off,
|
||||
)
|
||||
self.stopButton.setIcon(icon3)
|
||||
self.stopButton.setObjectName("stopButton")
|
||||
self.horizontalLayout_5.addWidget(self.stopButton)
|
||||
self.nextButton = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.nextButton.setText("")
|
||||
icon4 = QtGui.QIcon()
|
||||
icon4.addPixmap(
|
||||
QtGui.QPixmap("images/control-skip.png"),
|
||||
QtGui.QIcon.Normal,
|
||||
QtGui.QIcon.Off,
|
||||
)
|
||||
self.nextButton.setIcon(icon4)
|
||||
self.nextButton.setObjectName("nextButton")
|
||||
self.horizontalLayout_5.addWidget(self.nextButton)
|
||||
self.viewButton = QtWidgets.QPushButton(self.centralWidget)
|
||||
self.viewButton.setText("")
|
||||
icon5 = QtGui.QIcon()
|
||||
icon5.addPixmap(
|
||||
QtGui.QPixmap("images/application-image.png"),
|
||||
QtGui.QIcon.Normal,
|
||||
QtGui.QIcon.Off,
|
||||
)
|
||||
self.viewButton.setIcon(icon5)
|
||||
self.viewButton.setCheckable(True)
|
||||
self.viewButton.setObjectName("viewButton")
|
||||
self.horizontalLayout_5.addWidget(self.viewButton)
|
||||
spacerItem = QtWidgets.QSpacerItem(
|
||||
40,
|
||||
20,
|
||||
QtWidgets.QSizePolicy.Expanding,
|
||||
QtWidgets.QSizePolicy.Minimum,
|
||||
)
|
||||
self.horizontalLayout_5.addItem(spacerItem)
|
||||
self.label = QtWidgets.QLabel(self.centralWidget)
|
||||
self.label.setText("")
|
||||
self.label.setPixmap(QtGui.QPixmap("images/speaker-volume.png"))
|
||||
self.label.setObjectName("label")
|
||||
self.horizontalLayout_5.addWidget(self.label)
|
||||
self.volumeSlider = QtWidgets.QSlider(self.centralWidget)
|
||||
self.volumeSlider.setMaximum(100)
|
||||
self.volumeSlider.setProperty("value", 100)
|
||||
self.volumeSlider.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
||||
self.volumeSlider.setObjectName("volumeSlider")
|
||||
self.horizontalLayout_5.addWidget(self.volumeSlider)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout_5)
|
||||
self.horizontalLayout.addLayout(self.verticalLayout)
|
||||
MainWindow.setCentralWidget(self.centralWidget)
|
||||
self.menuBar = QtWidgets.QMenuBar(MainWindow)
|
||||
self.menuBar.setGeometry(QtCore.QRect(0, 0, 484, 22))
|
||||
self.menuBar.setObjectName("menuBar")
|
||||
self.menuFIle = QtWidgets.QMenu(self.menuBar)
|
||||
self.menuFIle.setObjectName("menuFIle")
|
||||
MainWindow.setMenuBar(self.menuBar)
|
||||
self.statusBar = QtWidgets.QStatusBar(MainWindow)
|
||||
self.statusBar.setObjectName("statusBar")
|
||||
MainWindow.setStatusBar(self.statusBar)
|
||||
self.open_file_action = QtWidgets.QAction(MainWindow)
|
||||
self.open_file_action.setObjectName("open_file_action")
|
||||
self.menuFIle.addAction(self.open_file_action)
|
||||
self.menuBar.addAction(self.menuFIle.menuAction())
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "Failamp"))
|
||||
self.currentTimeLabel.setText(_translate("MainWindow", "0:00"))
|
||||
self.totalTimeLabel.setText(_translate("MainWindow", "0:00"))
|
||||
self.menuFIle.setTitle(_translate("MainWindow", "FIle"))
|
||||
self.open_file_action.setText(_translate("MainWindow", "Open file..."))
|
||||
20
pyside2/demos/mediaplayer/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Failamp — Simple mediaplayer build in PyQt
|
||||
|
||||
Simple app to listen to and watch videos and audio files,
|
||||
with built in playlist. Uses QtMultimedia and QtMultimediaWidgets
|
||||
to handle playback and manage the playlist.
|
||||
|
||||
The main interface offers a playlist window in which you can drag-drop
|
||||
media files to be played. Standard media controls are provided, along
|
||||
with a timeline scrub widget and a volume control.
|
||||
|
||||

|
||||
|
||||
For video playback you can pop out an external video viewer window
|
||||
which floats on top.
|
||||
|
||||

|
||||
|
||||
> If you want to learn more about build GUI applications with Python,
|
||||
take a look at my [PyQt5 tutorials](https://www.pythonguis.com)
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
||||
BIN
pyside2/demos/mediaplayer/images/application-image.png
Executable file
|
After Width: | Height: | Size: 544 B |
BIN
pyside2/demos/mediaplayer/images/control-pause.png
Executable file
|
After Width: | Height: | Size: 427 B |
BIN
pyside2/demos/mediaplayer/images/control-skip-180.png
Executable file
|
After Width: | Height: | Size: 577 B |
BIN
pyside2/demos/mediaplayer/images/control-skip.png
Executable file
|
After Width: | Height: | Size: 572 B |
BIN
pyside2/demos/mediaplayer/images/control-stop-square.png
Executable file
|
After Width: | Height: | Size: 411 B |
BIN
pyside2/demos/mediaplayer/images/control.png
Executable file
|
After Width: | Height: | Size: 470 B |
BIN
pyside2/demos/mediaplayer/images/speaker-volume.png
Executable file
|
After Width: | Height: | Size: 566 B |
170
pyside2/demos/mediaplayer/main.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from MainWindow import Ui_MainWindow
|
||||
from models import PlaylistModel
|
||||
from PySide2.QtCore import QSize, Qt, QUrl, Signal
|
||||
from PySide2.QtGui import QColor, QPalette
|
||||
from PySide2.QtMultimedia import (
|
||||
QMediaContent,
|
||||
QMediaPlayer,
|
||||
QMediaPlaylist,
|
||||
)
|
||||
from PySide2.QtMultimediaWidgets import QVideoWidget
|
||||
from PySide2.QtWidgets import QApplication, QFileDialog, QMainWindow
|
||||
from utils import hhmmss
|
||||
|
||||
|
||||
class ViewerWindow(QMainWindow):
|
||||
state = Signal(bool)
|
||||
|
||||
def closeEvent(self, e):
|
||||
# Emit the window state, to update the viewer toggle button.
|
||||
self.state.emit(False)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
self.player = QMediaPlayer()
|
||||
|
||||
self.player.error.connect(self.erroralert)
|
||||
self.player.play()
|
||||
|
||||
# Setup the playlist.
|
||||
self.playlist = QMediaPlaylist()
|
||||
self.player.setPlaylist(self.playlist)
|
||||
|
||||
# Add viewer for video playback, separate floating window.
|
||||
self.viewer = ViewerWindow(self)
|
||||
self.viewer.setWindowFlags(
|
||||
self.viewer.windowFlags() | Qt.WindowType.WindowStaysOnTopHint
|
||||
)
|
||||
self.viewer.setMinimumSize(QSize(480, 360))
|
||||
|
||||
videoWidget = QVideoWidget()
|
||||
self.viewer.setCentralWidget(videoWidget)
|
||||
self.player.setVideoOutput(videoWidget)
|
||||
|
||||
# Connect control buttons/slides for media player.
|
||||
self.playButton.pressed.connect(self.player.play)
|
||||
self.pauseButton.pressed.connect(self.player.pause)
|
||||
self.stopButton.pressed.connect(self.player.stop)
|
||||
self.volumeSlider.valueChanged.connect(self.player.setVolume)
|
||||
|
||||
self.viewButton.toggled.connect(self.toggle_viewer)
|
||||
self.viewer.state.connect(self.viewButton.setChecked)
|
||||
|
||||
self.previousButton.pressed.connect(self.playlist.previous)
|
||||
self.nextButton.pressed.connect(self.playlist.next)
|
||||
|
||||
self.model = PlaylistModel(self.playlist)
|
||||
self.playlistView.setModel(self.model)
|
||||
self.playlist.currentIndexChanged.connect(self.playlist_position_changed)
|
||||
selection_model = self.playlistView.selectionModel()
|
||||
selection_model.selectionChanged.connect(self.playlist_selection_changed)
|
||||
|
||||
self.player.durationChanged.connect(self.update_duration)
|
||||
self.player.positionChanged.connect(self.update_position)
|
||||
self.timeSlider.valueChanged.connect(self.player.setPosition)
|
||||
|
||||
self.open_file_action.triggered.connect(self.open_file)
|
||||
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
self.show()
|
||||
|
||||
def dragEnterEvent(self, e):
|
||||
if e.mimeData().hasUrls():
|
||||
e.acceptProposedAction()
|
||||
|
||||
def dropEvent(self, e):
|
||||
for url in e.mimeData().urls():
|
||||
self.playlist.addMedia(QMediaContent(url))
|
||||
|
||||
self.model.layoutChanged.emit()
|
||||
|
||||
# If not playing, seeking to first of newly added + play.
|
||||
if self.player.state() != QMediaPlayer.PlayingState:
|
||||
i = self.playlist.mediaCount() - len(e.mimeData().urls())
|
||||
self.playlist.setCurrentIndex(i)
|
||||
self.player.play()
|
||||
|
||||
def open_file(self):
|
||||
path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Open file",
|
||||
"",
|
||||
"mp3 Audio (*.mp3);;mp4 Video (*.mp4);;Movie files (*.mov);;All files (*.*)",
|
||||
)
|
||||
|
||||
if path:
|
||||
self.playlist.addMedia(QMediaContent(QUrl.fromLocalFile(path)))
|
||||
|
||||
self.model.layoutChanged.emit()
|
||||
|
||||
def update_duration(self, duration):
|
||||
self.timeSlider.setMaximum(duration)
|
||||
|
||||
if duration >= 0:
|
||||
self.totalTimeLabel.setText(hhmmss(duration))
|
||||
|
||||
def update_position(self, position):
|
||||
if position >= 0:
|
||||
self.currentTimeLabel.setText(hhmmss(position))
|
||||
|
||||
# Disable the events to prevent updating triggering a setPosition event (can cause stuttering).
|
||||
self.timeSlider.blockSignals(True)
|
||||
self.timeSlider.setValue(position)
|
||||
self.timeSlider.blockSignals(False)
|
||||
|
||||
def playlist_selection_changed(self, ix):
|
||||
# We receive a QItemSelection from selectionChanged.
|
||||
i = ix.indexes()[0].row()
|
||||
self.playlist.setCurrentIndex(i)
|
||||
|
||||
def playlist_position_changed(self, i):
|
||||
if i > -1:
|
||||
ix = self.model.index(i)
|
||||
self.playlistView.setCurrentIndex(ix)
|
||||
|
||||
def toggle_viewer(self, state):
|
||||
if state:
|
||||
self.viewer.show()
|
||||
else:
|
||||
self.viewer.hide()
|
||||
|
||||
def erroralert(self, *args):
|
||||
print(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ["QT_MULTIMEDIA_PREFERRED_PLUGINS"] = "windowsmediafoundation"
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Failamp")
|
||||
app.setStyle("Fusion")
|
||||
|
||||
# Fusion dark palette from https://gist.github.com/QuantumCD/6245215.
|
||||
palette = QPalette()
|
||||
palette.setColor(QPalette.ColorRole.Window, QColor(53, 53, 53))
|
||||
palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.white)
|
||||
palette.setColor(QPalette.ColorRole.Base, QColor(25, 25, 25))
|
||||
palette.setColor(QPalette.ColorRole.AlternateBase, QColor(53, 53, 53))
|
||||
palette.setColor(QPalette.ColorRole.ToolTipBase, Qt.GlobalColor.white)
|
||||
palette.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.white)
|
||||
palette.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.white)
|
||||
palette.setColor(QPalette.ColorRole.Button, QColor(53, 53, 53))
|
||||
palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.white)
|
||||
palette.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
|
||||
palette.setColor(QPalette.ColorRole.Link, QColor(42, 130, 218))
|
||||
palette.setColor(QPalette.ColorRole.Highlight, QColor(42, 130, 218))
|
||||
palette.setColor(QPalette.ColorRole.HighlightedText, Qt.GlobalColor.black)
|
||||
app.setPalette(palette)
|
||||
app.setStyleSheet(
|
||||
"QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"
|
||||
)
|
||||
|
||||
window = MainWindow()
|
||||
app.exec_()
|
||||
63
pyside2/demos/mediaplayer/mainwindow.ui
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>484</width>
|
||||
<height>371</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Failamp</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusBar"/>
|
||||
<action name="open_file_action">
|
||||
<property name="text">
|
||||
<string>Open file...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
15
pyside2/demos/mediaplayer/models.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from PySide2.QtCore import QAbstractListModel, Qt
|
||||
|
||||
|
||||
class PlaylistModel(QAbstractListModel):
|
||||
def __init__(self, playlist):
|
||||
super().__init__()
|
||||
self.playlist = playlist
|
||||
|
||||
def data(self, index, role):
|
||||
if role == Qt.ItemDataRole.DisplayRole:
|
||||
media = self.playlist.media(index.row())
|
||||
return media.canonicalUrl().fileName()
|
||||
|
||||
def rowCount(self, index):
|
||||
return self.playlist.mediaCount()
|
||||
1
pyside2/demos/mediaplayer/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
PyQt5>=5.6
|
||||
BIN
pyside2/demos/mediaplayer/screenshot-mediaplayer1.jpg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
pyside2/demos/mediaplayer/screenshot-mediaplayer2.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
8
pyside2/demos/mediaplayer/utils.py
Normal file
@@ -0,0 +1,8 @@
|
||||
def hhmmss(ms):
|
||||
# s = 1000
|
||||
# m = 60000
|
||||
# h = 360000
|
||||
s = round(ms / 1000)
|
||||
m, s = divmod(s, 60)
|
||||
h, m = divmod(m, 60)
|
||||
return ("%d:%02d:%02d" % (h, m, s)) if h else ("%d:%02d" % (m, s))
|
||||
39
pyside2/demos/minesweeper/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Moonsweeper — A minesweeper clone, on a moon with aliens, in PyQt.
|
||||
|
||||
Explore the mysterious moon of Q'tee without getting too close to the alien natives!
|
||||
|
||||
Moonsweeper is a single-player puzzle video game. The objective of the game is to
|
||||
explore the area around your landed space rocket, without coming too close to the
|
||||
deadly B'ug aliens. Your trusty tricounter will tell you the number of B'ugs in the
|
||||
vicinity.
|
||||
|
||||

|
||||
|
||||
This a simple single-player exploration game modelled on _Minesweeper_
|
||||
where you must reveal all the tiles without hitting hidden mines.
|
||||
This implementation uses custom `QWidget` objects for the tiles, which
|
||||
individually hold their state as mines, status and the
|
||||
adjacent count of mines. In this version, the mines are replaced with
|
||||
alien bugs (B'ug) but they could just as easily be anything else.
|
||||
|
||||

|
||||
|
||||
> If you want to learn more about build GUI applications with Python,
|
||||
take a look at my [PyQt5 tutorials](https://www.pythonguis.com)
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
||||
|
||||
## Code notes
|
||||
|
||||
### Cheating the first turn
|
||||
|
||||
In many *Minesweeper* variants the initial turn is considered a free
|
||||
go — if you hit a mine on the first click, it is moved somewhere else.
|
||||
Here we cheat a little bit by taking the first go for the player, ensuring that
|
||||
it is on a non-mine spot. This allows us not to worry about the bad first move
|
||||
which would require us to recalculate the adjacencies.
|
||||
We can explain this away as the "initial exploration around the rocket"
|
||||
and make it sound completely sensible.
|
||||
|
||||
## Other licenses
|
||||
|
||||
Icons used in the application are by [Yusuke Kamiyaman](http://p.yusukekamiyamane.com/).
|
||||
39
pyside2/demos/minesweeper/constants.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from enum import IntEnum
|
||||
|
||||
from PySide2.QtGui import (
|
||||
QColor,
|
||||
QImage,
|
||||
)
|
||||
|
||||
IMG_BOMB = QImage("./images/bug.png")
|
||||
IMG_FLAG = QImage("./images/flag.png")
|
||||
IMG_START = QImage("./images/rocket.png")
|
||||
IMG_CLOCK = QImage("./images/clock-select.png")
|
||||
|
||||
NUM_COLORS = {
|
||||
1: QColor("#f44336"),
|
||||
2: QColor("#9C27B0"),
|
||||
3: QColor("#3F51B5"),
|
||||
4: QColor("#03A9F4"),
|
||||
5: QColor("#00BCD4"),
|
||||
6: QColor("#4CAF50"),
|
||||
7: QColor("#E91E63"),
|
||||
8: QColor("#FF9800"),
|
||||
}
|
||||
|
||||
LEVELS = [(8, 10), (16, 40), (24, 99)]
|
||||
|
||||
|
||||
class Status(IntEnum):
|
||||
READY = 0
|
||||
PLAYING = 1
|
||||
FAILED = 2
|
||||
SUCCESS = 3
|
||||
|
||||
|
||||
STATUS_ICONS = {
|
||||
Status.READY: "./images/plus.png",
|
||||
Status.PLAYING: "./images/smiley.png",
|
||||
Status.FAILED: "./images/cross.png",
|
||||
Status.SUCCESS: "./images/smiley-lol.png",
|
||||
}
|
||||
BIN
pyside2/demos/minesweeper/images/bomb.png
Executable file
|
After Width: | Height: | Size: 752 B |
BIN
pyside2/demos/minesweeper/images/bug.png
Executable file
|
After Width: | Height: | Size: 704 B |
BIN
pyside2/demos/minesweeper/images/cactus.png
Executable file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
pyside2/demos/minesweeper/images/cake.png
Executable file
|
After Width: | Height: | Size: 755 B |
BIN
pyside2/demos/minesweeper/images/clock-select.png
Executable file
|
After Width: | Height: | Size: 741 B |
BIN
pyside2/demos/minesweeper/images/cross.png
Executable file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
pyside2/demos/minesweeper/images/cup.png
Executable file
|
After Width: | Height: | Size: 734 B |
BIN
pyside2/demos/minesweeper/images/flag.png
Executable file
|
After Width: | Height: | Size: 728 B |
BIN
pyside2/demos/minesweeper/images/fruit.png
Executable file
|
After Width: | Height: | Size: 904 B |