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.
1
.gitignore
vendored
@ -71,3 +71,4 @@ dist
|
||||
|
||||
# Misc
|
||||
*.autosave
|
||||
.vscode
|
||||
|
98
README.md
@ -1,62 +1,92 @@
|
||||
# 15 Minute Apps
|
||||
# Python GUIs Examples
|
||||
|
||||
A collection of 15 small — *minute* — desktop applications written in Python
|
||||
using the PyQt framework. These apps are intended as examples from
|
||||
which you can poke, hack and prod your way to writing your own tools.
|
||||
**This repository contains 100s of GUI examples written in Python**. From complete working applications to reusable widgets snippets, these examples can
|
||||
be _freely_ re-used, re-mixed and tweaked to build your own Python GUI applications.
|
||||
|
||||
> Many of these apps have more detailed write-ups on my PyQt5/PySide2 site at [LearnPyQt.com](https://www.learnpyqt.com/apps/).
|
||||
If you're new to creating GUI apps check out the introductory [pyqt5 tutorial](https://www.learnpyqt.com/courses/start/).
|
||||
Examples are available for
|
||||
[PyQt6](https://github.com/pythonguis/15-minute-apps/tree/master/pyqt6),
|
||||
[PySide6](https://github.com/pythonguis/15-minute-apps/tree/master/pyside6),
|
||||
[PySide2](https://github.com/pythonguis/15-minute-apps/tree/master/pyside2)
|
||||
and [PyQt5](https://github.com/pythonguis/15-minute-apps/tree/master/pyqt5)
|
||||
|
||||
## The apps
|
||||
> Many of these examples have more detailed write-ups on the [Python GUIs website](https://www.pythonguis.com/). If you're new to creating GUI apps check out the introductory [PyQt6 tutorial](https://www.pythonguis.com/pyqt6-tutorial/) or [PySide6 tutorial](https://www.pythonguis.com/pyside6-tutorial/).
|
||||
|
||||
## The demo apps
|
||||
|
||||
The apps showcase various parts of the Qt framework, including advanced widgets,
|
||||
multimedia, graphics views and decorationless windows. However, the most
|
||||
generally interesting/feature complete applications are Minesweeper, Solitaire
|
||||
and Paint.
|
||||
|
||||
1. [Web Browser (untabbed)](browser/) - "MooseAche"
|
||||
1. [Web Browser (tabbed)](browser_tabbed/) - "Mozzarella Ashbadger"
|
||||
1. **[Minesweeper](minesweeper/) - "Moonsweeper"**
|
||||
1. [Notepad](notepad/) - "No2Pads"
|
||||
1. [Calculator](calculator/) - "Calculon" (QtDesigner)
|
||||
1. [Word Processor](wordprocessor/) - "Megasolid Idiom"
|
||||
1. [Webcam/Snapshot](camera/) - "NSAViewer"
|
||||
1. [Media Player](mediaplayer/) - "Failamp"
|
||||
1. [Post-it Notes](notes/) - "Brown Note" (QtDesigner)
|
||||
1. **[Paint](paint/) - "Piecasso" (QtDesigner)**
|
||||
1. [Unzip](unzip/) - "7Pez" (QtDesigner)
|
||||
1. [Translator](translate/) - "Translataarrr" (QtDesigner)
|
||||
1. [Weather](weather/) - "Raindar" (QtDesigner)
|
||||
1. [Currency converter](currency/) - "Doughnut" (PyQtGraph)
|
||||
1. **[Solitaire](solitaire/) - "Ronery" (QGraphicsScene)**
|
||||
1. Web Browser (untabbed) - "MooseAche"
|
||||
1. Web Browser (tabbed) - "Mozzarella Ashbadger"
|
||||
1. Minesweeper - "Moonsweeper"
|
||||
1. Notepad - "No2Pads"
|
||||
1. Calculator - "Calculon" (QtDesigner)
|
||||
1. Word Processor - "Megasolid Idiom"
|
||||
1. Webcam/Snapshot - "NSAViewer"
|
||||
1. Media Player - "Failamp"
|
||||
1. Post-it Notes - "Brown Note" (QtDesigner)
|
||||
1. Paint - "Piecasso" (QtDesigner)
|
||||
1. Unzip - "7Pez" (QtDesigner)
|
||||
1. Translator - "Translataarrr" (QtDesigner)
|
||||
1. Weather - "Raindar" (QtDesigner)
|
||||
1. Currency converter - "Doughnut" (PyQtGraph)
|
||||
1. Solitaire - "Ronery" (QGraphicsScene)
|
||||
|
||||
## The widgets
|
||||
|
||||
|
||||
![Graphical Equalizer](https://i.imgur.com/0F2ZgqE.gif)
|
||||
**Graphical Equalizer** Visualize audio frequency changes with configurable styles and decay.
|
||||
|
||||
![Power Meter](https://i.imgur.com/0dpZIMV.gif)
|
||||
**Power Bar** Rotary control with amplitude display.
|
||||
|
||||
![Palette](https://cdn.learnpyqt.com/media/images/Screenshot_2019-06-15_at_15.18.14.max-500x500.png)
|
||||
**Palette** Select colors from a configurable linear or grid palette.
|
||||
|
||||
![Gradient Editor](https://cdn.learnpyqt.com/media/images/Screenshot_2019-06-15_at_18.32.52.max-500x500.png)
|
||||
**Linear Gradient Editor** Design custom linear gradients with multiple stops and colors.
|
||||
|
||||
**Color Button** Simple button that displays and selects colors.
|
||||
|
||||
**Paint** Draw pictures with a custom bitmap canvas, with color and pen control.
|
||||
|
||||
**Password Edit** A password line editor with toggleable visibility action.
|
||||
|
||||
![Toggle Widget](https://i.imgur.com/rHrkkG3.gif)
|
||||
Replace checkboxes with this handy toggle widget, with custom colors and optional animations.
|
||||
|
||||
## The tutorials
|
||||
|
||||
As well as the complete apps & re-usable widgets we've got selection of code snippets taken from tutorials on the [Python GUIs](https://www.pythonguis.com) website. These guide you through building GUIs with PyQt, PySide and other libraries.
|
||||
|
||||
## Getting started
|
||||
|
||||
To use each app you first need to install the requirements. In most cases
|
||||
the only requirements are PyQt5, and occasionally requests. To install
|
||||
app-specific requirements change to the folder of the app and run:
|
||||
the only requirements are the GUI library and occasionally requests. To install
|
||||
example specific requirements change to the folder of the example and run:
|
||||
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
Once the requirements are installed, you can run the app using Python 3.
|
||||
|
||||
python3 <filename>.py
|
||||
|
||||
Once the requirements are installed, you can run the app. Every example in this repo comes with a main Python file called `main.py` to keep things simple.
|
||||
|
||||
python3 main.py
|
||||
|
||||
The application window should appear.
|
||||
|
||||
## Want to build your own apps?
|
||||
|
||||
> If you think these apps are neat and want to learn more about
|
||||
PyQt in general, take a look at my [PyQt5 tutorial](https://www.learnpyqt.com)
|
||||
which covers everything you need to know to start building your own applications with PyQt.
|
||||
take a look at my [PyQt6 tutorial](https://www.pythonguis.com/pyqt6-tutorial)
|
||||
which covers everything you need to know to start building your own applications with Python.
|
||||
|
||||
You can also find write-ups about these "minute apps" [on the same site](http://www.learnpyqt.com/apps).
|
||||
You can also find more write-ups and tips [on the same site](http://www.pythonguis.com/).
|
||||
|
||||
## License
|
||||
|
||||
All code is **licensed under an MIT license**. This allows you to re-use the code freely,
|
||||
remixed in both commercial and non-commercial projects. The only requirement is to
|
||||
include the same license when distributing.
|
||||
All code is **licensed under an MIT license**. This allows you to re-use the code freely, remixed in both commercial and non-commercial projects. The only requirement is to include the same license when distributing.
|
||||
|
||||
## Other licenses
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['crypto.py'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='Goodforbitcoin',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False,
|
||||
icon='resources/icon.ico'
|
||||
)
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='Goodforbitcoin'
|
||||
)
|
||||
app = BUNDLE(coll,
|
||||
name='Goodforbitcoin.app',
|
||||
icon='resources/icon.icns',
|
||||
bundle_identifier='com.learnpyqt.Goodforbitcoin',
|
||||
info_plist={
|
||||
'NSHighResolutionCapable': 'True'
|
||||
},
|
||||
)
|
@ -1,227 +0,0 @@
|
||||
<?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="QListView" name="playlistView">
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DropOnly</enum>
|
||||
</property>
|
||||
<property name="defaultDropAction">
|
||||
<enum>Qt::CopyAction</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="uniformItemSizes">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="currentTimeLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0:00</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="timeSlider">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="totalTimeLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0:00</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="previousButton">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>images/control-skip-180.png</normaloff>images/control-skip-180.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="playButton">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>images/control.png</normaloff>images/control.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pauseButton">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>images/control-pause.png</normaloff>images/control-pause.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="stopButton">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>images/control-stop-square.png</normaloff>images/control-stop-square.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="nextButton">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>images/control-skip.png</normaloff>images/control-skip.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="viewButton">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>images/application-image.png</normaloff>images/application-image.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap>images/speaker-volume.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="volumeSlider">
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</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="open_file_action"/>
|
||||
</widget>
|
||||
<addaction name="menuFIle"/>
|
||||
</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>
|
@ -1,10 +0,0 @@
|
||||
# No2Pads — A Notepad clone in PyQt
|
||||
|
||||
A very simple notepad clone using the QTextEdit widget to handle more or less
|
||||
everything. Supports file loading, saving and printing.
|
||||
|
||||
![No2Pads](screenshot-notepad.jpg)
|
||||
|
||||
> If you think this app is neat and want to learn more about
|
||||
PyQt in general, take a look at my [free PyQt tutorials](https://www.learnpyqt.com)
|
||||
which cover everything you need to know to start building your own applications with PyQt.
|
24704
paint/resources_rc.py
11
pyqt5-pyqt6.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Black the SOURCE files to limit subsequent reformatting.
|
||||
black -l 128 $1
|
||||
|
||||
# PyQt6 (built from PyQt5) --------------------------------------------------
|
||||
find $1 -type f -name "*.py" -print0 | xargs -0 sed -i 's/PyQt5/PyQt6/g'
|
||||
find $1 -type f -name "*.py" -print0 | xargs -0 sed -i 's/exec_/exec/g'
|
||||
find $1 -type f -name "*.py" -print0 | xargs -0 sed -i 's/qt-5/qt-6/g'
|
||||
|
||||
black -l 128 $1
|
12
pyqt5-pyside2.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Black the SOURCE files to limit subsequent reformatting (highlights possible issues).
|
||||
black -l 128 $1
|
||||
|
||||
# PySide2 (built from PyQt5) --------------------------------------------------
|
||||
find $1 -type f -name "*.py" -print0 | xargs -0 sed -i 's/PyQt5/PySide2/g'
|
||||
find $1 -type f -name "*.py" -print0 | xargs -0 sed -i 's/pyqtSignal/Signal/g'
|
||||
find $1 -type f -name "*.py" -print0 | xargs -0 sed -i 's/pyqtProperty/Property/g'
|
||||
find $1 -type f -name "*.py" -print0 | xargs -0 sed -i 's/pyqtSlot/Slot/g'
|
||||
|
||||
black -l 128 $1
|
@ -1,14 +1,14 @@
|
||||
# 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
|
||||
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.
|
||||
|
||||
![Browser](screenshot-browser.jpg)
|
||||
|
||||
> If you think this app is neat and want to learn more about
|
||||
PyQt in general, take a look at my [free PyQt tutorials](https://www.learnpyqt.com)
|
||||
which cover everything you need to know to start building your own applications with PyQt.
|
||||
> 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
|
||||
|
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 615 B |
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 829 B After Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 729 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 621 B |
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 752 B |
Before Width: | Height: | Size: 902 B After Width: | Height: | Size: 902 B |
Before Width: | Height: | Size: 694 B After Width: | Height: | Size: 694 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 489 B After Width: | Height: | Size: 489 B |
@ -1,19 +1,31 @@
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWebEngineWidgets import *
|
||||
from PyQt5.QtPrintSupport import *
|
||||
from PyQt5.QtWidgets import QShortcut, QLabel, QHBoxLayout
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt, QUrl
|
||||
from PyQt5.QtGui import QIcon, QKeySequence, QPixmap
|
||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt5.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFileDialog,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QShortcut,
|
||||
QStatusBar,
|
||||
QToolBar,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
class AboutDialog(QDialog):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AboutDialog, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
QBtn = QDialogButtonBox.Ok # No cancel
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok # No cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
@ -28,14 +40,14 @@ class AboutDialog(QDialog):
|
||||
layout.addWidget(title)
|
||||
|
||||
logo = QLabel()
|
||||
logo.setPixmap(QPixmap(os.path.join('images', 'ma-icon-128.png')))
|
||||
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.AlignHCenter)
|
||||
layout.itemAt(i).setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
layout.addWidget(self.buttonBox)
|
||||
|
||||
@ -43,8 +55,8 @@ class AboutDialog(QDialog):
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainWindow, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.browser = QWebEngineView()
|
||||
self.browser.setUrl(QUrl("http://google.com"))
|
||||
@ -59,26 +71,34 @@ class MainWindow(QMainWindow):
|
||||
navtb = QToolBar("Navigation")
|
||||
navtb.setIconSize(QSize(16, 16))
|
||||
self.addToolBar(navtb)
|
||||
|
||||
self.shortcut_open = QShortcut(QKeySequence('F5'), self)
|
||||
self.shortcut_open.triggered.connect(self.browser.reload)
|
||||
|
||||
back_btn = QAction(QIcon(os.path.join('images', 'arrow-180.png')), "Back", self)
|
||||
|
||||
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 = 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 = 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 = 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)
|
||||
@ -86,14 +106,18 @@ class MainWindow(QMainWindow):
|
||||
navtb.addSeparator()
|
||||
|
||||
self.httpsicon = QLabel() # Yes, really!
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join('images', 'lock-nossl.png')))
|
||||
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 = 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)
|
||||
@ -103,74 +127,99 @@ class MainWindow(QMainWindow):
|
||||
|
||||
file_menu = self.menuBar().addMenu("&File")
|
||||
|
||||
open_file_action = QAction(QIcon(os.path.join('images', 'disk--arrow.png')), "Open file...", self)
|
||||
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 = 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 = 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 = 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 = 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')))
|
||||
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.udemy.com/522076"))
|
||||
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 (*.*)")
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Open file",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *.html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
with open(filename, 'r') as f:
|
||||
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:
|
||||
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 (*.*)")
|
||||
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_)
|
||||
@ -187,24 +236,24 @@ class MainWindow(QMainWindow):
|
||||
self.browser.setUrl(q)
|
||||
|
||||
def update_urlbar(self, q):
|
||||
|
||||
if q.scheme() == 'https':
|
||||
if q.scheme() == "https":
|
||||
# Secure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join('images', 'lock-ssl.png')))
|
||||
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.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
|
||||
self.urlbar.setText(q.toString())
|
||||
self.urlbar.setCursorPosition(0)
|
||||
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("MooseAche")
|
||||
app.setOrganizationName("MooseAche")
|
||||
app.setOrganizationDomain("MooseAche.org")
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("MooseAche")
|
||||
app.setOrganizationName("MooseAche")
|
||||
app.setOrganizationDomain("MooseAche.org")
|
||||
|
||||
window = MainWindow()
|
||||
window = MainWindow()
|
||||
|
||||
app.exec_()
|
||||
app.exec_()
|
277
pyqt5/demos/browser/main_new_windows.py
Normal file
@ -0,0 +1,277 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt, QUrl
|
||||
from PyQt5.QtGui import QIcon, QPixmap
|
||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView
|
||||
from PyQt5.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
pyqt5/demos/browser/main_one_new_window.py
Normal file
@ -0,0 +1,277 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt, QUrl
|
||||
from PyQt5.QtGui import QIcon, QPixmap
|
||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineView
|
||||
from PyQt5.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_()
|
@ -1,3 +1,2 @@
|
||||
PyQt5>=5.6
|
||||
PyQtWebEngine
|
||||
sip
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
@ -1,15 +1,15 @@
|
||||
# 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
|
||||
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.
|
||||
|
||||
![Browser tabbed](screenshot-browser-tabbed.jpg)
|
||||
|
||||
> If you think this app is neat and want to learn more about
|
||||
PyQt in general, take a look at my [free PyQt tutorials](https://www.learnpyqt.com)
|
||||
which cover everything you need to know to start building your own applications with PyQt.
|
||||
> 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
|
||||
|
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 615 B |
Before Width: | Height: | Size: 589 B After Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 829 B After Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 729 B After Width: | Height: | Size: 729 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 621 B |
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 752 B |
Before Width: | Height: | Size: 902 B After Width: | Height: | Size: 902 B |
Before Width: | Height: | Size: 694 B After Width: | Height: | Size: 694 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 715 B After Width: | Height: | Size: 715 B |
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 489 B After Width: | Height: | Size: 489 B |
@ -1,19 +1,32 @@
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWebEngineWidgets import *
|
||||
from PyQt5.QtPrintSupport import *
|
||||
from PyQt5.QtWidgets import QShortcut, QLabel, QHBoxLayout
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt, QUrl
|
||||
from PyQt5.QtGui import QIcon, QKeySequence, QPixmap
|
||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt5.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFileDialog,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QShortcut,
|
||||
QStatusBar,
|
||||
QTabWidget,
|
||||
QToolBar,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
class AboutDialog(QDialog):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AboutDialog, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
QBtn = QDialogButtonBox.Ok # No cancel
|
||||
QBtn = QDialogButtonBox.StandardButton.Ok # No cancel
|
||||
self.buttonBox = QDialogButtonBox(QBtn)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
@ -28,14 +41,14 @@ class AboutDialog(QDialog):
|
||||
layout.addWidget(title)
|
||||
|
||||
logo = QLabel()
|
||||
logo.setPixmap(QPixmap(os.path.join('images', 'ma-icon-128.png')))
|
||||
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.AlignHCenter)
|
||||
layout.itemAt(i).setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
layout.addWidget(self.buttonBox)
|
||||
|
||||
@ -43,8 +56,8 @@ class AboutDialog(QDialog):
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainWindow, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.tabs = QTabWidget()
|
||||
self.tabs.setDocumentMode(True)
|
||||
@ -61,28 +74,36 @@ class MainWindow(QMainWindow):
|
||||
navtb = QToolBar("Navigation")
|
||||
navtb.setIconSize(QSize(16, 16))
|
||||
self.addToolBar(navtb)
|
||||
|
||||
'''Shortcuts'''
|
||||
|
||||
self.shortcut_open = QShortcut(QKeySequence('F5'), self)
|
||||
self.shortcut_open.triggered.connect(lambda: self.tabs.currentWidget().reload())
|
||||
|
||||
back_btn = QAction(QIcon(os.path.join('images', 'arrow-180.png')), "Back", self)
|
||||
"""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 = 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 = 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 = 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)
|
||||
@ -90,14 +111,18 @@ class MainWindow(QMainWindow):
|
||||
navtb.addSeparator()
|
||||
|
||||
self.httpsicon = QLabel() # Yes, really!
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join('images', 'lock-nossl.png')))
|
||||
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 = 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)
|
||||
@ -107,50 +132,73 @@ class MainWindow(QMainWindow):
|
||||
|
||||
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 = 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 = 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 = 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 = 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 = 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 = 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.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')))
|
||||
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('')
|
||||
qurl = QUrl("")
|
||||
|
||||
browser = QWebEngineView()
|
||||
browser.setUrl(qurl)
|
||||
@ -160,14 +208,24 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 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.setTabText(i, browser.page().title()))
|
||||
browser.titleChanged.connect(lambda _, i=i, browser=browser:
|
||||
self.setTabToolTip(i, browser.page().title()))
|
||||
browser.loadFinished.connect(lambda _, i=i, browser=browser:
|
||||
self.tabs.setTabText(i, browser.page().title()))
|
||||
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
|
||||
@ -193,33 +251,39 @@ class MainWindow(QMainWindow):
|
||||
self.setWindowTitle("%s - Mozarella Ashbadger" % title)
|
||||
|
||||
def navigate_mozarella(self):
|
||||
self.tabs.currentWidget().setUrl(QUrl("https://www.udemy.com/522076"))
|
||||
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 (*.*)")
|
||||
filename, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Open file",
|
||||
"",
|
||||
"Hypertext Markup Language (*.htm *.html);;" "All files (*.*)",
|
||||
)
|
||||
|
||||
if filename:
|
||||
with open(filename, 'r') as f:
|
||||
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 (*.*)")
|
||||
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'))
|
||||
with open(filename, "w") as f:
|
||||
f.write(html.encode("utf8"))
|
||||
|
||||
def print_page(self):
|
||||
dlg = QPrintPreviewDialog()
|
||||
@ -237,28 +301,28 @@ class MainWindow(QMainWindow):
|
||||
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':
|
||||
if q.scheme() == "https":
|
||||
# Secure padlock icon
|
||||
self.httpsicon.setPixmap(QPixmap(os.path.join('images', 'lock-ssl.png')))
|
||||
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.httpsicon.setPixmap(QPixmap(os.path.join("images", "lock-nossl.png")))
|
||||
|
||||
self.urlbar.setText(q.toString())
|
||||
self.urlbar.setCursorPosition(0)
|
||||
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Mozarella Ashbadger")
|
||||
app.setOrganizationName("Mozarella")
|
||||
app.setOrganizationDomain("mozarella.org")
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Mozarella Ashbadger")
|
||||
app.setOrganizationName("Mozarella")
|
||||
app.setOrganizationDomain("mozarella.org")
|
||||
|
||||
window = MainWindow()
|
||||
window = MainWindow()
|
||||
|
||||
app.exec_()
|
||||
app.exec_()
|
@ -1,3 +1,2 @@
|
||||
PyQt5>=5.6
|
||||
PyQtWebEngine
|
||||
sip
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
@ -1,318 +1,288 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'mainwindow.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.10
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 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_2 = QtWidgets.QVBoxLayout(self.centralWidget)
|
||||
self.verticalLayout_2.setContentsMargins(11, 11, 11, 11)
|
||||
self.verticalLayout_2.setSpacing(6)
|
||||
self.verticalLayout_2.setObjectName("verticalLayout_2")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
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)
|
||||
self.verticalLayout_2.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.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"))
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'mainwindow.ui'
|
||||
#
|
||||
# Created by: PyQt5 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 PyQt5 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"))
|
@ -1,11 +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
|
||||
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.
|
||||
|
||||
![Calculon](screenshot-calculator.jpg)
|
||||
|
||||
> If you think this example app is neat and want to learn more about
|
||||
PyQt in general, [take a look at my ebook & online course
|
||||
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 PyQt.
|
||||
which covers everything you need to know to start building your own applications with PyQt5.
|
@ -1,10 +1,8 @@
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtCore import *
|
||||
|
||||
import operator
|
||||
import sys
|
||||
|
||||
from MainWindow import Ui_MainWindow
|
||||
from PyQt5.QtWidgets import QApplication, QMainWindow
|
||||
|
||||
# Calculator state.
|
||||
READY = 0
|
||||
@ -12,13 +10,13 @@ INPUT = 1
|
||||
|
||||
|
||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainWindow, self).__init__(*args, **kwargs)
|
||||
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))
|
||||
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))
|
||||
@ -96,7 +94,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
try:
|
||||
self.stack = [self.current_op(*self.stack)]
|
||||
except Exception:
|
||||
self.lcdNumber.display('Err')
|
||||
self.lcdNumber.display("Err")
|
||||
self.stack = [0]
|
||||
else:
|
||||
self.current_op = None
|
||||
@ -104,8 +102,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
||||
self.display()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication([])
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Calculon")
|
||||
|
||||
window = MainWindow()
|
583
pyqt5/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>
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
@ -7,9 +7,9 @@ them.
|
||||
|
||||
![Camera](screenshot-camera.jpg)
|
||||
|
||||
> If you think this app is neat and want to learn more about
|
||||
PyQt in general, take a look at my [free PyQt tutorials](https://www.learnpyqt.com)
|
||||
which cover everything you need to know to start building your own applications with PyQt.
|
||||
> 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
|
||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 571 B After Width: | Height: | Size: 571 B |
@ -1,28 +1,34 @@
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtPrintSupport import *
|
||||
from PyQt5.QtMultimedia import *
|
||||
from PyQt5.QtMultimediaWidgets import *
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import QSize
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtMultimedia import QCamera, QCameraImageCapture, QCameraInfo
|
||||
from PyQt5.QtMultimediaWidgets import QCameraViewfinder
|
||||
from PyQt5.QtWidgets import (
|
||||
QAction,
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QErrorMessage,
|
||||
QFileDialog,
|
||||
QMainWindow,
|
||||
QStatusBar,
|
||||
QToolBar,
|
||||
)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainWindow, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.available_cameras = QCameraInfo.availableCameras()
|
||||
if not self.available_cameras:
|
||||
pass #quit
|
||||
pass # quit
|
||||
|
||||
self.status = QStatusBar()
|
||||
self.setStatusBar(self.status)
|
||||
|
||||
|
||||
self.save_path = ""
|
||||
|
||||
self.viewfinder = QCameraViewfinder()
|
||||
@ -37,24 +43,30 @@ class MainWindow(QMainWindow):
|
||||
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 = 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 = 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_selector.currentIndexChanged.connect(self.select_camera)
|
||||
|
||||
camera_toolbar.addWidget(camera_selector)
|
||||
|
||||
|
||||
self.setWindowTitle("NSAViewer")
|
||||
self.show()
|
||||
|
||||
@ -74,11 +86,12 @@ class MainWindow(QMainWindow):
|
||||
|
||||
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.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):
|
||||
@ -95,10 +108,9 @@ class MainWindow(QMainWindow):
|
||||
err.showMessage(s)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("NSAViewer")
|
||||
|
||||
window = MainWindow()
|
||||
app.exec_()
|
||||
app.exec_()
|
@ -1,2 +1 @@
|
||||
PyQt5>=5.6
|
||||
sip
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -1,13 +1,20 @@
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
app = QApplication([])
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.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"))
|
||||
icon = QIcon(os.path.join("images", "color.png"))
|
||||
|
||||
clipboard = QApplication.clipboard()
|
||||
dialog = QColorDialog()
|
||||
@ -17,20 +24,23 @@ 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()
|
||||
))
|
||||
|
||||
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()
|
||||
))
|
||||
clipboard.setText(
|
||||
"hsv(%d, %d, %d)" % (color.hue(), color.saturation(), color.value())
|
||||
)
|
||||
|
||||
|
||||
# Create the tray
|
||||
tray = QSystemTrayIcon()
|
||||
@ -47,13 +57,16 @@ 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_()
|
||||
app.exec_()
|
@ -10,19 +10,19 @@ hand panel and updated to follow the position of the mouse.
|
||||
|
||||
![Doughnut](screenshot-currency2.jpg)
|
||||
|
||||
> If you think this app is neat and want to learn more about
|
||||
PyQt in general, take a look at my [free PyQt tutorials](https://www.learnpyqt.com)
|
||||
which cover everything you need to know to start building your own applications with PyQt.
|
||||
> 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
|
||||
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
|
||||
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.
|
||||
|
38
pyqt5/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",
|
||||
]
|
@ -1,124 +1,46 @@
|
||||
from datetime import datetime, timedelta, date
|
||||
from itertools import cycle
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtCore import *
|
||||
from itertools import cycle
|
||||
|
||||
import constants
|
||||
import numpy as np
|
||||
|
||||
# import requests_cache
|
||||
from PyQt5.QtCore import (
|
||||
Qt,
|
||||
QThreadPool,
|
||||
)
|
||||
from PyQt5.QtGui import (
|
||||
QBrush,
|
||||
QColor,
|
||||
QStandardItem,
|
||||
QStandardItemModel,
|
||||
)
|
||||
from PyQt5.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 PyQt5.
|
||||
import pyqtgraph as pg
|
||||
import requests
|
||||
import requests_cache
|
||||
|
||||
# CryptoCompare.com API Key
|
||||
CRYPTOCOMPARE_API_KEY = ''
|
||||
|
||||
# Define a requests http cache to minimise API requests.
|
||||
requests_cache.install_cache(os.path.expanduser('~/.goodforbitcoin'))
|
||||
|
||||
# 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 = cycle(['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f', '#ff7f00',
|
||||
'#cab2d6', '#6a3d9a', '#ffff99', '#b15928' ])
|
||||
|
||||
# Base PyQtGraph configuration.
|
||||
pg.setConfigOption('background', 'w')
|
||||
pg.setConfigOption('foreground', 'k')
|
||||
|
||||
|
||||
class WorkerSignals(QObject):
|
||||
"""
|
||||
Defines the signals available from a running worker thread.
|
||||
"""
|
||||
finished = pyqtSignal()
|
||||
error = pyqtSignal(tuple)
|
||||
progress = pyqtSignal(int)
|
||||
data = pyqtSignal(dict, list)
|
||||
cancel = pyqtSignal()
|
||||
|
||||
|
||||
class UpdateWorker(QRunnable):
|
||||
"""
|
||||
Worker thread for updating currency.
|
||||
"""
|
||||
signals = WorkerSignals()
|
||||
|
||||
def __init__(self, base_currency):
|
||||
super(UpdateWorker, self).__init__()
|
||||
self.is_interrupted = False
|
||||
self.base_currency = base_currency
|
||||
self.signals.cancel.connect(self.cancel)
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
auth_header = {
|
||||
'Apikey': CRYPTOCOMPARE_API_KEY
|
||||
}
|
||||
try:
|
||||
rates = {}
|
||||
for n, crypto in enumerate(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': NUMBER_OF_TIMEPOINTS-1,
|
||||
'extraParams': 'www.learnpyqt.com',
|
||||
'format': 'json',
|
||||
}),
|
||||
headers=auth_header,
|
||||
)
|
||||
r.raise_for_status()
|
||||
rates[crypto] = r.json().get('Data')
|
||||
|
||||
self.signals.progress.emit(int(100 * n / len(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': NUMBER_OF_TIMEPOINTS-1,
|
||||
'extraParams': 'www.learnpyqt.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
|
||||
pg.setConfigOption("background", "w")
|
||||
pg.setConfigOption("foreground", "k")
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainWindow, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
layout = QHBoxLayout()
|
||||
|
||||
@ -127,43 +49,44 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.line = pg.InfiniteLine(
|
||||
pos=-20,
|
||||
pen=pg.mkPen('k', width=3),
|
||||
movable=False # We have our own code to handle dragless moving.
|
||||
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.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.showAxis("right")
|
||||
self.p1.scene().addItem(self.p2)
|
||||
self.p2.setXLink(self.p1)
|
||||
self.ax2 = self.p1.getAxis('right')
|
||||
self.ax2 = self.p1.getAxis("right")
|
||||
self.ax2.linkToView(self.p2)
|
||||
self.ax2.setGrid(False)
|
||||
self.ax2.setLabel(text='Volume')
|
||||
self.ax2.setLabel(text="Volume")
|
||||
|
||||
self._market_activity = pg.PlotCurveItem(
|
||||
np.arange(NUMBER_OF_TIMEPOINTS), np.arange(NUMBER_OF_TIMEPOINTS),
|
||||
pen=pg.mkPen('k', style=Qt.DashLine, width=1)
|
||||
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 = DEFAULT_BASE_CURRENCY
|
||||
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 = DEFAULT_DISPLAY_CURRENCIES
|
||||
self._data_visible = constants.DEFAULT_DISPLAY_CURRENCIES
|
||||
|
||||
self.listView = QTableView()
|
||||
self.model = QStandardItemModel()
|
||||
@ -189,7 +112,7 @@ class MainWindow(QMainWindow):
|
||||
self.currencyList = QComboBox()
|
||||
|
||||
toolbar.addWidget(self.currencyList)
|
||||
self.update_currency_list(AVAILABLE_BASE_CURRENCIES)
|
||||
self.update_currency_list(constants.AVAILABLE_BASE_CURRENCIES)
|
||||
self.currencyList.setCurrentText(self.base_currency)
|
||||
self.currencyList.currentTextChanged.connect(self.change_base_currency)
|
||||
|
||||
@ -212,7 +135,7 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
|
||||
currency = i.text()
|
||||
checked = i.checkState() == Qt.Checked
|
||||
checked = i.checkState() == Qt.CheckState.Checked
|
||||
|
||||
if currency in self._data_visible:
|
||||
if not checked:
|
||||
@ -225,7 +148,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def get_currency_color(self, currency):
|
||||
if currency not in self._data_colors:
|
||||
self._data_colors[currency] = next(BREWER12PAIRED)
|
||||
self._data_colors[currency] = next(color_cycle)
|
||||
|
||||
return self._data_colors[currency]
|
||||
|
||||
@ -237,17 +160,17 @@ class MainWindow(QMainWindow):
|
||||
def add_data_row(self, currency):
|
||||
citem = QStandardItem()
|
||||
citem.setText(currency)
|
||||
citem.setForeground(QBrush(QColor(
|
||||
self.get_currency_color(currency)
|
||||
)))
|
||||
citem.setForeground(QBrush(QColor(self.get_currency_color(currency))))
|
||||
citem.setColumnCount(2)
|
||||
citem.setCheckable(True)
|
||||
if currency in DEFAULT_DISPLAY_CURRENCIES:
|
||||
citem.setCheckState(Qt.Checked)
|
||||
if currency in constants.DEFAULT_DISPLAY_CURRENCIES:
|
||||
citem.setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
vitem = QStandardItem()
|
||||
|
||||
vitem.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
vitem.setTextAlignment(
|
||||
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
self.model.setColumnCount(2)
|
||||
self.model.appendRow([citem, vitem])
|
||||
self.model.sort(0)
|
||||
@ -259,7 +182,7 @@ class MainWindow(QMainWindow):
|
||||
self.update_data_viewer(int(pos.x()))
|
||||
|
||||
def update_data_viewer(self, i):
|
||||
if i not in range(NUMBER_OF_TIMEPOINTS):
|
||||
if i not in range(constants.NUMBER_OF_TIMEPOINTS):
|
||||
return
|
||||
|
||||
for currency, data in self.data.items():
|
||||
@ -267,7 +190,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def update_data_row(self, currency, data):
|
||||
citem, vitem = self.get_or_create_data_row(currency)
|
||||
vitem.setText("%.4f" % data['close'])
|
||||
vitem.setText("%.4f" % data["close"])
|
||||
|
||||
def change_base_currency(self, currency):
|
||||
self.base_currency = currency
|
||||
@ -294,7 +217,7 @@ class MainWindow(QMainWindow):
|
||||
self.data = rates
|
||||
self.volume = volume
|
||||
self.redraw()
|
||||
self.update_data_viewer(NUMBER_OF_TIMEPOINTS-1)
|
||||
self.update_data_viewer(constants.NUMBER_OF_TIMEPOINTS - 1)
|
||||
|
||||
def progress_callback(self, progress):
|
||||
self.progress.setValue(progress)
|
||||
@ -306,7 +229,7 @@ class MainWindow(QMainWindow):
|
||||
def notify_error(self, error):
|
||||
e, tb = error
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Warning)
|
||||
msg.setIcon(QMessageBox.Icon.Warning)
|
||||
msg.setText(e.__class__.__name__)
|
||||
msg.setInformativeText(str(e))
|
||||
msg.setDetailedText(tb)
|
||||
@ -317,45 +240,58 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def redraw(self):
|
||||
y_min, y_max = sys.maxsize, 0
|
||||
x = np.arange(NUMBER_OF_TIMEPOINTS)
|
||||
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
|
||||
])
|
||||
_, 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]["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]["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)
|
||||
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)
|
||||
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._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))
|
||||
|
||||
@ -363,8 +299,7 @@ class MainWindow(QMainWindow):
|
||||
self.p2.setYRange(0, max(self.volume))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
app = QApplication([])
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
app.exec_()
|
||||
app.exec_()
|
4
pyqt5/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
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
91
pyqt5/demos/crypto/workers.py
Normal file
@ -0,0 +1,91 @@
|
||||
import traceback
|
||||
|
||||
import constants
|
||||
import requests
|
||||
|
||||
# import requests_cache
|
||||
from PyQt5.QtCore import (
|
||||
QObject,
|
||||
QRunnable,
|
||||
pyqtSignal,
|
||||
pyqtSlot,
|
||||
)
|
||||
|
||||
|
||||
class WorkerSignals(QObject):
|
||||
"""
|
||||
Defines the signals available from a running worker thread.
|
||||
"""
|
||||
|
||||
finished = pyqtSignal()
|
||||
error = pyqtSignal(tuple)
|
||||
progress = pyqtSignal(int)
|
||||
data = pyqtSignal(dict, list)
|
||||
cancel = pyqtSignal()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@pyqtSlot()
|
||||
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
|
@ -10,19 +10,19 @@ hand panel and updated to follow the position of the mouse.
|
||||
|
||||
![Doughnut](screenshot-currency2.jpg)
|
||||
|
||||
> If you think this app is neat and want to learn more about
|
||||
PyQt in general, take a look at my [free PyQt tutorials](https://www.learnpyqt.com)
|
||||
which cover everything you need to know to start building your own applications with PyQt.
|
||||
> 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
|
||||
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
|
||||
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.
|
||||
|
31
pyqt5/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",
|
||||
]
|
@ -1,42 +1,54 @@
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtCore import *
|
||||
|
||||
import pyqtgraph as pg
|
||||
import requests
|
||||
import requests_cache
|
||||
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta, date
|
||||
from itertools import cycle
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime, timedelta
|
||||
from itertools import cycle
|
||||
|
||||
requests_cache.install_cache('http_cache')
|
||||
import constants
|
||||
import requests
|
||||
|
||||
# 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
|
||||
# import requests_cache
|
||||
from PyQt5.QtCore import (
|
||||
QObject,
|
||||
QRunnable,
|
||||
Qt,
|
||||
QThreadPool,
|
||||
pyqtSignal,
|
||||
pyqtSlot,
|
||||
)
|
||||
from PyQt5.QtGui import (
|
||||
QBrush,
|
||||
QColor,
|
||||
QStandardItem,
|
||||
QStandardItemModel,
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QHBoxLayout,
|
||||
QMainWindow,
|
||||
QProgressBar,
|
||||
QTableView,
|
||||
QToolBar,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
# Colour sets.
|
||||
BREWER12PAIRED = cycle(['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f', '#ff7f00',
|
||||
'#cab2d6', '#6a3d9a', '#ffff99', '#b15928' ])
|
||||
color_cycle = cycle(constants.BREWER12PAIRED)
|
||||
# requests_cache.install_cache("cache")
|
||||
|
||||
# Base PyQtGraph configuration
|
||||
pg.setConfigOption('background', 'w')
|
||||
pg.setConfigOption('foreground', 'k')
|
||||
# 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, HISTORIC_DAYS_N)]
|
||||
current = [(0, constants.HISTORIC_DAYS_N)]
|
||||
while current:
|
||||
a, b = current.pop(0)
|
||||
n = (a + b) // 2
|
||||
@ -49,11 +61,11 @@ while current:
|
||||
current.append((b, n))
|
||||
|
||||
|
||||
|
||||
class WorkerSignals(QObject):
|
||||
'''
|
||||
"""
|
||||
Defines the signals available from a running worker thread.
|
||||
'''
|
||||
"""
|
||||
|
||||
finished = pyqtSignal()
|
||||
error = pyqtSignal(tuple)
|
||||
progress = pyqtSignal(int)
|
||||
@ -62,14 +74,15 @@ class WorkerSignals(QObject):
|
||||
|
||||
|
||||
class UpdateWorker(QRunnable):
|
||||
'''
|
||||
"""
|
||||
Worker thread for updating currency.
|
||||
'''
|
||||
"""
|
||||
|
||||
signals = WorkerSignals()
|
||||
is_interrupted = False
|
||||
|
||||
def __init__(self, base_currency):
|
||||
super(UpdateWorker, self).__init__()
|
||||
super().__init__()
|
||||
self.base_currency = base_currency
|
||||
self.signals.cancel.connect(self.cancel)
|
||||
|
||||
@ -81,14 +94,14 @@ class UpdateWorker(QRunnable):
|
||||
|
||||
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})
|
||||
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 = data["rates"]
|
||||
rates[self.base_currency] = 1.0
|
||||
|
||||
self.signals.data.emit(offset ,rates)
|
||||
self.signals.data.emit(offset, rates)
|
||||
self.signals.progress.emit(int(100 * n / total_requests))
|
||||
|
||||
if not r.from_cache:
|
||||
@ -97,7 +110,6 @@ class UpdateWorker(QRunnable):
|
||||
if self.is_interrupted:
|
||||
break
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
exctype, value = sys.exc_info()[:2]
|
||||
@ -110,11 +122,9 @@ class UpdateWorker(QRunnable):
|
||||
self.is_interrupted = True
|
||||
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MainWindow, self).__init__(*args, **kwargs)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
layout = QHBoxLayout()
|
||||
|
||||
@ -123,22 +133,22 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.line = pg.InfiniteLine(
|
||||
pos=-20,
|
||||
pen=pg.mkPen('k', width=3),
|
||||
movable=False # We have our own code to handle dragless moving.
|
||||
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=-HISTORIC_DAYS_N + 1, xMax=0)
|
||||
self.ax.setLimits(xMin=-constants.HISTORIC_DAYS_N + 1, xMax=0)
|
||||
self.ax.getPlotItem().scene().sigMouseMoved.connect(self.mouse_move_handler)
|
||||
|
||||
self.base_currency = DEFAULT_BASE_CURRENCY
|
||||
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 = DEFAULT_DISPLAY_CURRENCIES
|
||||
self._data_visible = constants.DEFAULT_DISPLAY_CURRENCIES
|
||||
|
||||
self._last_updated = None
|
||||
|
||||
@ -166,7 +176,7 @@ class MainWindow(QMainWindow):
|
||||
self.currencyList = QComboBox()
|
||||
|
||||
toolbar.addWidget(self.currencyList)
|
||||
self.update_currency_list(DEFAULT_DISPLAY_CURRENCIES)
|
||||
self.update_currency_list(constants.DEFAULT_DISPLAY_CURRENCIES)
|
||||
self.currencyList.setCurrentText(self.base_currency)
|
||||
self.currencyList.currentTextChanged.connect(self.change_base_currency)
|
||||
|
||||
@ -190,7 +200,7 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
|
||||
currency = i.text()
|
||||
checked = i.checkState() == Qt.Checked
|
||||
checked = i.checkState() == Qt.CheckState.Checked
|
||||
|
||||
if currency in self._data_visible:
|
||||
if not checked:
|
||||
@ -203,24 +213,24 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def get_currency_color(self, currency):
|
||||
if currency not in self._data_colors:
|
||||
self._data_colors[currency] = next(BREWER12PAIRED)
|
||||
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.setForeground(QBrush(QColor(self.get_currency_color(currency))))
|
||||
citem.setColumnCount(2)
|
||||
citem.setCheckable(True)
|
||||
if currency in DEFAULT_DISPLAY_CURRENCIES:
|
||||
citem.setCheckState(Qt.Checked)
|
||||
if currency in constants.DEFAULT_DISPLAY_CURRENCIES:
|
||||
citem.setCheckState(Qt.CheckState.Checked)
|
||||
|
||||
vitem = QStandardItem()
|
||||
|
||||
vitem.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
vitem.setTextAlignment(
|
||||
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
|
||||
)
|
||||
self.model.setColumnCount(2)
|
||||
self.model.appendRow([citem, vitem])
|
||||
self.model.sort(0)
|
||||
@ -262,7 +272,7 @@ class MainWindow(QMainWindow):
|
||||
self.worker.signals.cancel.emit()
|
||||
|
||||
# Prefill our data store with None ('no data')
|
||||
self.data = [None] * HISTORIC_DAYS_N
|
||||
self.data = [None] * constants.HISTORIC_DAYS_N
|
||||
|
||||
self.worker = UpdateWorker(self.base_currency)
|
||||
# Handle callbacks with data and trigger refresh.
|
||||
@ -275,9 +285,10 @@ class MainWindow(QMainWindow):
|
||||
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)
|
||||
):
|
||||
if (
|
||||
self._last_updated is None
|
||||
or self._last_updated < datetime.now() - timedelta(seconds=1)
|
||||
):
|
||||
self.redraw()
|
||||
self._last_updated = datetime.now()
|
||||
|
||||
@ -299,7 +310,7 @@ class MainWindow(QMainWindow):
|
||||
plotd = defaultdict(list)
|
||||
x_ticks = []
|
||||
|
||||
tick_step_size = HISTORIC_DAYS_N / 6
|
||||
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:
|
||||
@ -307,8 +318,8 @@ class MainWindow(QMainWindow):
|
||||
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')))
|
||||
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())
|
||||
@ -327,20 +338,16 @@ class MainWindow(QMainWindow):
|
||||
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
|
||||
)
|
||||
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,[]])
|
||||
self.ax.getAxis("bottom").setTicks([x_ticks, []])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
app = QApplication([])
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
app.exec_()
|
||||
app.exec_()
|
@ -1,5 +1,4 @@
|
||||
PyQt5>=5.6
|
||||
sip
|
||||
requests>=2.0.0
|
||||
requests_cache>=0.4.13
|
||||
pyqtgraph>=0.10
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
@ -8,6 +8,7 @@
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
@ -39,16 +40,16 @@ class Ui_MainWindow(object):
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.currentTimeLabel = QtWidgets.QLabel(self.centralWidget)
|
||||
self.currentTimeLabel.setMinimumSize(QtCore.QSize(80, 0))
|
||||
self.currentTimeLabel.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
|
||||
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.Horizontal)
|
||||
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.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
|
||||
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)
|
||||
@ -58,47 +59,76 @@ class Ui_MainWindow(object):
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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("")
|
||||
@ -108,7 +138,7 @@ class Ui_MainWindow(object):
|
||||
self.volumeSlider = QtWidgets.QSlider(self.centralWidget)
|
||||
self.volumeSlider.setMaximum(100)
|
||||
self.volumeSlider.setProperty("value", 100)
|
||||
self.volumeSlider.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.volumeSlider.setOrientation(QtCore.Qt.Orientation.Horizontal)
|
||||
self.volumeSlider.setObjectName("volumeSlider")
|
||||
self.horizontalLayout_5.addWidget(self.volumeSlider)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout_5)
|
||||
@ -138,4 +168,3 @@ class Ui_MainWindow(object):
|
||||
self.totalTimeLabel.setText(_translate("MainWindow", "0:00"))
|
||||
self.menuFIle.setTitle(_translate("MainWindow", "FIle"))
|
||||
self.open_file_action.setText(_translate("MainWindow", "Open file..."))
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Failamp — Simple mediaplayer build in PyQt
|
||||
|
||||
Simple app to listen to and watch videos and audio files,
|
||||
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.
|
||||
|
||||
@ -15,6 +15,6 @@ which floats on top.
|
||||
|
||||
![Mediaplayer](screenshot-mediaplayer2.jpg)
|
||||
|
||||
> If you think this app is neat and want to learn more about
|
||||
PyQt in general, take a look at my [free PyQt tutorials](https://www.learnpyqt.com)
|
||||
which cover everything you need to know to start building your own applications with PyQt.
|
||||
> 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.
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 427 B |
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 577 B |
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 572 B |
Before Width: | Height: | Size: 411 B After Width: | Height: | Size: 411 B |
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 470 B |
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B |
170
pyqt5/demos/mediaplayer/main.py
Normal file
@ -0,0 +1,170 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from MainWindow import Ui_MainWindow
|
||||
from models import PlaylistModel
|
||||
from PyQt5.QtCore import QSize, Qt, QUrl, pyqtSignal
|
||||
from PyQt5.QtGui import QColor, QPalette
|
||||
from PyQt5.QtMultimedia import (
|
||||
QMediaContent,
|
||||
QMediaPlayer,
|
||||
QMediaPlaylist,
|
||||
)
|
||||
from PyQt5.QtMultimediaWidgets import QVideoWidget
|
||||
from PyQt5.QtWidgets import QApplication, QFileDialog, QMainWindow
|
||||
from utils import hhmmss
|
||||
|
||||
|
||||
class ViewerWindow(QMainWindow):
|
||||
state = pyqtSignal(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.State.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
pyqt5/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
pyqt5/demos/mediaplayer/models.py
Normal file
@ -0,0 +1,15 @@
|
||||
from PyQt5.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,2 +1 @@
|
||||
PyQt5>=5.6
|
||||
sip
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
8
pyqt5/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))
|
@ -18,9 +18,9 @@ alien bugs (B'ug) but they could just as easily be anything else.
|
||||
|
||||
![Moonsweeper](screenshot-minesweeper2.jpg)
|
||||
|
||||
> If you think this app is neat and want to learn more about
|
||||
PyQt in general, take a look at my [free PyQt tutorials](https://www.learnpyqt.com)
|
||||
which cover everything you need to know to start building your own applications with PyQt.
|
||||
> 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
|
||||
|
39
pyqt5/demos/minesweeper/constants.py
Normal file
@ -0,0 +1,39 @@
|
||||
from enum import IntEnum
|
||||
|
||||
from PyQt5.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",
|
||||
}
|
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 752 B |