Related
I am making a game using a GUI and sockets and I am able to connect clients and initialise the GUI etc. However, when I hover over the button of my GUI the part of the server which receives the data crashes and so does everything else but the server is the one which receives an error message.
Client side:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import socket
from time import sleep
import threading
from ListFunctionsPlayer2 import GameFunctions
import pickle
from threading import main_thread
client = None
Hostname = socket.gethostname()
HostAddress = socket.gethostbyname(Hostname)
PortAddress = 5557
PlayerName = ""
Player2Name = ""
mainthread = main_thread()
class UpdateSignal(QObject):
update = pyqtSignal(str)
class PlaceCards(QObject):
finished = pyqtSignal()
def process(self, index):
print(f"Hello {index}")
self.finished.emit()
class GameBoard(QWidget, GameFunctions):
def __init__(self):
super().__init__()
# setting Window Title
self.setWindowTitle("Player vs Player")
self.setGeometry(0, 0, 1000, 500)
# labeling the sides
self.Player1Label = QLabel("Player One", self)
self.Player1Label.move(400, 450)
self.Player1Label.setFont(QFont("Lucida Console", 20))
self.Player2Label = QLabel("Player Two", self)
self.Player2Label.move(400, 25)
self.Player2Label.setFont(QFont("Lucida Console", 20))
# The GUI for the pause button
self.PauseButton = QPushButton("Pause", self)
self.PauseButton.move(875, 225)
# Asigning functions to be called when the button is clicked
self.PauseButton.clicked.connect(self.PauseAction)
self.PauseButton.clicked.connect(self.Resume)
# Making the buttons for the player 1 side, Each one when pressed, sets a pointer to their location.
# This pointer is then used later on when the card is getting placed
self.Player1Hand1 = QPushButton(" ", self)
self.Player1Hand1.move(150, 375)
self.Player1Hand1.clicked.connect(self.SetPointer1)
self.Player1Hand2 = QPushButton(" ", self)
self.Player1Hand2.move(300, 375)
self.Player1Hand2.clicked.connect(self.SetPointer2)
self.Player1Hand3 = QPushButton(" ", self)
self.Player1Hand3.move(450, 375)
self.Player1Hand3.clicked.connect(self.SetPointer3)
self.Player1Hand4 = QPushButton(" ", self)
self.Player1Hand4.move(600, 375)
self.Player1Hand4.clicked.connect(self.SetPointer4)
self.Player1Hand5 = QPushButton(" ", self)
self.Player1Hand5.move(750, 375)
self.Player1Hand5.clicked.connect(self.SetPointer5)
# buttons for the Playing Field on player 1's side, Similarly, when pressed, the buttons set a pointer ot heir location.
# However, these pointer are used when the card is attacked/is atatacking
self.Player1Field1 = QPushButton(" ", self)
self.Player1Field1.move(150, 250)
self.Player1Field1.setEnabled(False)
self.Player1Field1.clicked.connect(self.setSelectPointer1)
self.Player1Field2 = QPushButton(" ", self)
self.Player1Field2.move(300, 250)
self.Player1Field2.setEnabled(False)
self.Player1Field2.clicked.connect(self.setSelectPointer2)
self.Player1Field3 = QPushButton(" ", self)
self.Player1Field3.move(450, 250)
self.Player1Field3.setEnabled(False)
self.Player1Field3.clicked.connect(self.setSelectPointer3)
self.Player1Field4 = QPushButton(" ", self)
self.Player1Field4.move(600, 250)
self.Player1Field4.setEnabled(False)
self.Player1Field4.clicked.connect(self.setSelectPointer4)
self.Player1Field5 = QPushButton(" ", self)
self.Player1Field5.move(750, 250)
self.Player1Field5.setEnabled(False)
self.Player1Field5.clicked.connect(self.setSelectPointer5)
# The same applies for player 2
self.Player2Field1 = QPushButton(" ", self)
self.Player2Field1.move(150, 200)
self.Player2Field1.setEnabled(False)
self.Player2Field1.clicked.connect(self.setSelectPointer1P2)
self.Player2Field2 = QPushButton(" ", self)
self.Player2Field2.move(300, 200)
self.Player2Field2.setEnabled(False)
self.Player2Field2.clicked.connect(self.setSelectPointer2P2)
self.Player2Field3 = QPushButton(" ", self)
self.Player2Field3.move(450, 200)
self.Player2Field3.setEnabled(False)
self.Player2Field3.clicked.connect(self.setSelectPointer3P2)
self.Player2Field4 = QPushButton(" ", self)
self.Player2Field4.move(600, 200)
self.Player2Field4.setEnabled(False)
self.Player2Field4.clicked.connect(self.setSelectPointer4P2)
self.Player2Field5 = QPushButton(" ", self)
self.Player2Field5.move(750, 200)
self.Player2Field5.setEnabled(False)
self.Player2Field5.clicked.connect(self.setSelectPointer5P2)
self.Player2Hand1 = QPushButton(" ", self)
self.Player2Hand1.move(150, 75)
self.Player2Hand1.clicked.connect(self.P2SetPointer1)
self.Player2Hand2 = QPushButton(" ", self)
self.Player2Hand2.move(300, 75)
self.Player2Hand2.clicked.connect(self.P2SetPointer2)
self.Player2Hand3 = QPushButton(" ", self)
self.Player2Hand3.move(450, 75)
self.Player2Hand3.clicked.connect(self.P2SetPointer3)
self.Player2Hand4 = QPushButton(" ", self)
self.Player2Hand4.move(600, 75)
self.Player2Hand4.clicked.connect(self.P2SetPointer4)
self.Player2Hand5 = QPushButton(" ", self)
self.Player2Hand5.move(750, 75)
self.Player2Hand5.clicked.connect(self.P2SetPointer5)
# Making Butons which forcefully end the turn by calling the 'EndTurn(2)' Function
self.Player1EndTurn = QPushButton("End Turn", self)
self.Player1EndTurn.move(25, 375)
self.Player2EndTurn = QPushButton("End Turn", self)
self.Player2EndTurn.move(875, 75)
# When the Board is instantiated, it sets the Mana values so that the game starts with these specific mana values
self.CurrentManaSaved = 3
self.CurrentManaSaved2 = 3
# Creates a visual for the players displaying how much mana they have left each
self.ManaDisplay1 = QLabel("Remaining Mana " + str(self.CurrentManaSaved), self)
self.ManaDisplay1.setGeometry(0, 0, 300, 100)
self.ManaDisplay1.setFont(QFont("Calibri", 10))
self.ManaDisplay1.move(20, 425)
self.ManaDisplay2 = QLabel("Remaining Mana " + str(self.CurrentManaSaved2), self)
self.ManaDisplay2.setGeometry(0, 0, 300, 100)
self.ManaDisplay2.setFont(QFont("Calibri", 10))
self.ManaDisplay2.move(20, 0)
# This Creates a visual display for the Player to view how much time they have left
self.TimerDisplayPlayer1 = QLabel("Timer ", self)
self.TimerDisplayPlayer1.setGeometry(0, 0, 50, 50)
self.TimerDisplayPlayer1.setFont(QFont("Calibri", 10))
self.TimerDisplayPlayer1.move(25, 400)
self.TimerDisplayPlayer2 = QLabel("Timer", self)
self.TimerDisplayPlayer2.setGeometry(0, 0, 50, 50)
self.TimerDisplayPlayer2.setFont(QFont("Calibri", 10))
self.TimerDisplayPlayer2.move(875, 25)
# Boolean Operators to control the starting and stopping of the timers
# Also holds the amount of seconds each player gets to complete their turn
self.start = False
self.count = 0
self.count2 = 0
self.start2 = False
self.seconds = 30
self.seconds2 = 30
# instantiating the timer objects
self.timer = QTimer(self)
self.timer.start(100)
self.timer.timeout.connect(self.ShowTime)
self.timer2 = QTimer(self)
self.timer2.start(100)
self.timer.timeout.connect(self.ShowTime2)
# arrays containing each of the groups of buttons so they can be accessed in the future
self.Player1buttons = [self.Player1Hand1, self.Player1Hand2, self.Player1Hand3, self.Player1Hand4,
self.Player1Hand5]
self.Player2buttons = [self.Player2Hand1, self.Player2Hand2, self.Player2Hand3, self.Player2Hand4,
self.Player2Hand5]
self.Player1Field = [self.Player1Field1, self.Player1Field2, self.Player1Field3, self.Player1Field4,
self.Player1Field5]
self.Player2Field = [self.Player2Field1, self.Player2Field2, self.Player2Field3, self.Player2Field4,
self.Player2Field5]
self.Player1FieldAttacked = [0, 0, 0, 0, 0]
self.Player2FieldAttacked = [0, 0, 0, 0, 0]
# setting a pointer for each of the arrays
self.Deck1Pointer = 0
self.Deck2Pointer = 0
self.Field1Pointer = 0
self.Field2Pointer = 0
self.PlayerTurn = 1
self.selectpointer = 0
self.selectpointer2 = 0
self.attackpointer = 0
self.attackpointer2 = 0
self.CurrentCard = " "
self.CurrentCard2 = " "
self.cardcount = 4
self.cardcount2 = 4
self.CurrentManaSaved = 3
self.CurrentManaSaved2 = 1
# As this is always running while the GUI is running, whenever a button within
# these arrays are clicked, the assigned function is called
for i in self.Player1buttons:
self.thread = threading.Thread(target=WorkerThread.workingsignal)
i.clicked.connect(lambda: self.threadstart)
def updatebutton(self, i, text):
self.Player1buttons[i].setText(text)
def updatebutton2(self, i, text):
self.Player2buttons[i].setText(text)
def threadstart(self):
self.thread.start()
class ClientWindow(QMainWindow):
def __init__(self):
super().__init__()
global OpponentLabel
self.setWindowTitle("Client Window")
self.setGeometry(0, 0, 250, 250)
self.NameLabel = QLabel("Name:", self)
self.NameLabel.move(100, 25)
self.NameLabel.setFont(QFont("", 7))
self.NameEntry = QLineEdit(self)
self.NameEntry.move(35, 50)
self.NameConfirm = QPushButton("Confirm", self)
self.NameConfirm.move(80, 90)
self.NameConfirm.clicked.connect(self.GetName)
self.ConnectButton = QPushButton("Connect", self)
self.ConnectButton.move(80, 125)
self.ConnectButton.clicked.connect(Connect)
OpponentLabel = QLabel("Opponent:", self)
OpponentLabel.setFont(QFont("", 7))
OpponentLabel.move(82, 15)
def GetName(self):
global PlayerName
global name
name = self.NameEntry.text()
self.NameLabel.setText("Name:" + name)
self.NameLabel.adjustSize()
PlayerName = name
def Close(self):
self.cw = ClientWindow
self.cw.close()
def Connect():
global name
print("Current Thread:" + str(threading.current_thread().getName()))
try:
if len(name) < 2:
print("Error - Enter Full Name")
else:
ConnectToServer(name)
except AttributeError:
print("Error - No Name Presented")
def ConnectToServer(SentName):
global client, HostAddress, PortAddress, PlayerName
print("Current Thread:" + str(threading.current_thread().getName()))
try:
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HostAddress, PortAddress))
client.send(SentName.encode())
threading._start_new_thread(ReceiveMessages, (client, "m"))
except Exception as e:
print(e)
class WorkerThread():
def __init__(self, GameBoard):
global client, Deck
self.Gameboard = GameBoard
Deck = client.recv(8192)
varDeck,varDeck2 = pickle.loads(Deck)
self.DisplayDeckPlayer = varDeck
self.DisplayDeckPlayer2 = varDeck2
def run(self):
DisplayDeckPlayer = self.DisplayDeckPlayer
for i in range(5):
self.Gameboard.updatebutton(i, str(DisplayDeckPlayer[i]))
sleep(2)
self.run2()
def run2(self):
DisplayDeck2 = self.DisplayDeckPlayer2
for i in range(5):
self.Gameboard.updatebutton2(i, str(DisplayDeck2[i]))
def workingsignal(*args):
print("We are Working")
def ReceiveMessages(sck, m):
global PlayerName, Player2Name
print("Current Thread:" + str(threading.current_thread().getName()))
while True:
ServerMessage = str(sck.recv(8192).decode())
if not ServerMessage:
break
if ServerMessage.startswith("Welcome"):
print(ServerMessage)
if ServerMessage == "Welcome1":
print("Welcome Player 1")
elif ServerMessage == "Welcome2":
print("Welcome Player 2")
elif ServerMessage.startswith("OPPONENT"):
print(ServerMessage)
Player2Name = ServerMessage.replace("OPPONENT", "")
OpponentLabel.setText("Opponent:" + Player2Name)
OpponentLabel.adjustSize()
elif ServerMessage.startswith("WaitForStart"):
app = QApplication([])
gb = GameBoard()
workerthread = WorkerThread(gb)
thread = threading.Thread(target=workerthread.run)
thread.start()
gb.show()
app.exec_()
else:
print(ServerMessage)
sck.close()
app = QApplication([])
cw = ClientWindow()
cw.show()
app.exec_()
gb = GameBoard()
Server Side:
PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import socket
from time import sleep
import threading
import sqlite3
import random
from sqlite3 import Error
import pickle
from SQLite1 import sqlsetup1
server = None
Hostname = socket.gethostname()
HostAddress = socket.gethostbyname(Hostname)
PortAddress = 5557
ClientName = " "
Clients = []
ClientNames = []
PlayerData = []
Ready = " "
class ServerWindow(QDialog):
def __init__(self):
super().__init__()
self.HostAddress = HostAddress
self.PortAddress = PortAddress
self.setWindowTitle("Server")
self.setGeometry(0, 0, 250, 250)
self.StartServerButton = QPushButton("Start", self)
self.StartServerButton.move(50, 10)
self.StartServerButton.clicked.connect(StartServer)
self.AddressDisplay = QLabel("HOST ADDRESS:" + self.HostAddress, self)
self.AddressDisplay.setFont(QFont("Ariel", 8))
self.AddressDisplay.move(5, 50)
self.PortDisplay = QLabel("PORT:" + str(self.PortAddress), self)
self.PortDisplay.setFont(QFont("Ariel", 8))
self.PortDisplay.move(5, 70)
PlayersJoined = QLabel("Players Joined:", self)
PlayersJoined.setFont(QFont("Ariel", 8))
PlayersJoined.move(5, 90)
def UpdateClientNameDisplay(NameList):
for i in NameList:
print(str(i))
def StartServer():
global server, HostAddress, PortAddress
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(socket.AF_INET)
print(socket.SOCK_STREAM)
server.bind((HostAddress, PortAddress))
server.listen(5)
threading._start_new_thread(AcceptClients, (server, " "))
def AcceptClients(TheServer, y):
while True:
if len(Clients) < 2:
client, addr = TheServer.accept()
Clients.append(client)
threading._start_new_thread(ClientMessaging, (client, addr))
def ClientMessaging(ClientConnection, *args):
global server, Clients, ClientName
ClientMsg = " "
ClientName = ClientConnection.recv(8192).decode()
if len(Clients) < 2:
ClientConnection.send("Welcome1".encode())
else:
ClientConnection.send("Welcome2".encode())
ClientNames.append(ClientName)
UpdateClientNameDisplay(ClientNames)
if len(Clients) > 1:
Clients[0].send(("OPPONENT" + ClientNames[1]).encode())
Clients[1].send(("OPPONENT" + ClientNames[0]).encode())
Clients[0].send("WaitForStart".encode())
Clients[1].send("WaitForStart".encode())
threading._start_new_thread(GameProcess, (ClientConnection,))
while True:
data = ClientConnection.recv(8192).decode()
printer = data
print(printer)
if not data:
break
index = GetClientIndex(Clients, ClientConnection)
del ClientNames[index]
del Clients[index]
ClientConnection.close()
def GetClientIndex(ClientList, CurrentClient):
index = 0
for conn in ClientList:
if conn == CurrentClient:
break
index = index + 1
return index
def GameProcess(ClientConnection2):
global server, Clients
Player1Deck = sqlsetup1.returnplayerdeck()
Player2Deck = sqlsetup1.returnplayerdeck2()
con = sqlsetup1.sql_connection()
sqlsetup1.SqlTable(con)
message1 = pickle.dumps([Player1Deck, Player2Deck])
message2 = pickle.dumps([Player2Deck, Player1Deck])
Clients[0].sendall(message1)
Clients[1].sendall(message2)
print("While truing")
app = QApplication([])
sw = ServerWindow()
sw.show()
app.exec_()
I'm trying to make et Game of life with tkinter. The matrix of the game is a canvas which every cell is a square (white = dead and green = alive).
I wanted to do some multithreading to avoid a too long process time when the matrix is big. But when I'm trying to modify the square color from the threads the window in not responding and I can't solve this problem. I tried a lot of things like putting a common lock in every thread when they are modifying the canvas but it's not working either.
Does someone have ideas to solve this problem or even why this appends?
Thanks by advance
Here's my code :
import tkinter as tk
import threading
import random
from functools import partial
class MainFrame(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
x_coord = int(self.winfo_screenwidth() / 2) - 600
y_coord = int(self.winfo_screenheight() / 2 - 390)
self.geometry("{}x{}+{}+{}".format(1200, 750, x_coord, y_coord))
"""Graphique elements"""
# Labels
self.labelcountgen = tk.Label(self, text="Generation 0", font=("Arial", 9))
self.labelcountgen.grid(row=0, column=1)
"""Graphique data"""
self.graphiquematrice = tk.Canvas(self, height=740, width=1000, bg='white')
self.windimention = [1200, 750]
self.generation = 100
"""Matrice"""
self.matrice = []
self.firstquarter = []
self.secondquarter = []
self.thirdquarter = []
self.fourthquarter = []
self.newmatrice = []
self.cellids = []
self.dimention = [70, 100]
self.density = 15
self.scale = 10
self.initmatrice()
self.launchtime()
"""Init Methodes"""
def initmatrice(self):
"""
initialisation du premier etat de la matrice
"""
self.dimention = [80, 100]
self.matrice = []
self.cellids = []
self.graphiquematrice.grid(row=1, column=1, rowspan=10)
for line in range(self.dimention[0]):
self.matrice.append([])
self.cellids.append([])
for col in range(self.dimention[1]):
chance = random.randrange(1, 100, 1)
if chance <= self.density:
self.matrice[line].append(1)
self.cellids[line].append(self.graphiquematrice.create_rectangle(col*self.scale, line*self.scale, (col+1)*self.scale, (line+1)*self.scale, outline='black', fill='green'))
else:
self.matrice[line].append(0)
self.cellids[line].append(self.graphiquematrice.create_rectangle(col*self.scale, line*self.scale, (col+1)*self.scale, (line+1)*self.scale, outline='black', fill='white'))
self.graphiquematrice.tag_bind(self.cellids[line][col], '<Button-1>', partial(self.changestateonclick, self.cellids[line][col]))
"""Calcul Next generation"""
def launchtime(self):
for i in range(self.generation):
self.labelcountgen.config(text="Generation " + str(i + 1))
self.evolve()
self.update()
def getneighboor(self, coord):
neighboor = []
for i in range(3):
lineindex = coord[0] + i - 1
if lineindex == -1:
lineindex = self.dimention[0] - 1
elif lineindex == self.dimention[0]:
lineindex = 0
for j in range(3):
colindex = coord[1] + j - 1
if colindex == -1:
colindex = self.dimention[1] - 1
elif colindex == self.dimention[1]:
colindex = 0
if lineindex != coord[0] or colindex != coord[1]:
neighboor.append(self.matrice[lineindex][colindex])
return neighboor
def nextcellstate(self, coord):
state = self.matrice[coord[0]][coord[1]]
neighboor = self.getneighboor(coord)
alive = 0
dead = 0
for one in neighboor:
if one == 0:
dead += 1
else:
alive += 1
if alive == 3:
return 1
elif alive == 2:
return state
else:
return 0
def nextfirstquarterstate(self, quarter, lock):
self.firstquarter = []
start = 0
for line in range(quarter):
self.firstquarter.append([])
for col in range(self.dimention[1]):
self.firstquarter[line].append(self.nextcellstate([line, col]))
with lock:
if self.firstquarter[line][col] == 0:
self.graphiquematrice.itemconfig(self.cellids[line][col], fill='white')
else:
self.graphiquematrice.itemconfig(self.cellids[line][col], fill='green')
def nextsecondquarterstate(self, quarter, lock):
self.secondquarter = []
start = quarter
for line in range(quarter):
self.secondquarter.append([])
for col in range(self.dimention[1]):
self.secondquarter[line].append(self.nextcellstate([line + start, col]))
with lock:
if self.secondquarter[line][col] == 0:
self.graphiquematrice.itemconfig(self.cellids[line][col], fill='white')
else:
self.graphiquematrice.itemconfig(self.cellids[line][col], fill='green')
def nextthirdquarterstate(self, quarter, lock):
self.thirdquarter = []
start = quarter * 2
for line in range(quarter):
self.thirdquarter.append([])
for col in range(self.dimention[1]):
self.thirdquarter[line].append(self.nextcellstate([line + start, col]))
with lock:
if self.thirdquarter[line][col] == 0:
self.graphiquematrice.itemconfig(self.cellids[line + start][col], fill='white')
else:
self.graphiquematrice.itemconfig(self.cellids[line + start][col], fill='green')
def nextfourthquarterstate(self, quarter, lock):
self.fourthquarter = []
start = quarter * 3
for line in range(quarter):
self.fourthquarter.append([])
for col in range(self.dimention[1]):
self.fourthquarter[line].append(self.nextcellstate([start + line, col]))
with lock:
if self.firstquarter[line][col] == 0:
self.graphiquematrice.itemconfig(self.cellids[start + line][col], fill='white')
else:
self.graphiquematrice.itemconfig(self.cellids[start + line][col], fill='green')
def evolve(self):
self.newmatrice = []
threads = []
lock = threading.Lock()
quarter = int(self.dimention[0] / 4) # Division de la matrice en 4 pour reduire le temps de calcul
threads.append(threading.Thread(target=self.nextfirstquarterstate, args=(quarter, lock))) # Premier quart
threads.append(threading.Thread(target=self.nextsecondquarterstate, args=(quarter, lock))) # Deuxieme quart
threads.append(threading.Thread(target=self.nextthirdquarterstate, args=(quarter, lock))) # 3eme quart
threads.append(threading.Thread(target=self.nextfourthquarterstate, args=(quarter, lock))) # 4eme quart
i = 0
for t in threads:
t.setDaemon(True)
try:
t.start() # Lancement des 4 threads
except:
print("Error: not able to lauch thread " + str(i))
i += 1
for t in threads:
t.join() # Attente de la fin de tous les calculs
self.matrice = self.firstquarter + self.secondquarter + self.thirdquarter + self.fourthquarter
"""Binding Methodes"""
def changestateonclick(self, idcell, event):
color = self.graphiquematrice.itemcget(idcell, 'fill')
if color == 'white':
self.graphiquematrice.itemconfig(idcell, fill='green')
else:
self.graphiquematrice.itemconfig(idcell, fill='white')
if __name__ == '__main__':
root = MainFrame()
root.mainloop()
I am developing a QTableView with ItemDelegate to display its specific widget Editor with openPersistentEditor. A pagination and filter mechanism been implemented with QSortFilterProxyModel, whenever there is a action of PageUp, PageDown or Filter changed, the program will close the previous rows of widget Editor and open new rows of widget Editor according to the "display_list" implemented.
The problem with current design is that the widget Editor will be highlighted after running the openPersistentEditor for each column cell in the display_list. I can not find a programmatic way to disable the highlighted cell.
I have tried the self.view.repaint(), self.view.update() without success. But manually bring forward/backward another window that the pyqt application MainWidonw refresh will disable the highlighted cells.
Following is the code segments of the TabWidget and CustomProxyModel been implemented.
Please note there is open_editor() and close_editor() in the class MyTabController will be triggered whenever there is a Page change action or a Filter action to be taken.
# PyQT5 and Filtering a Table Using Multiple Columns
# Refer to: https://www.onooks.com/pyqt5-and-filtering-a-table-using-multiple-columns/
import pandas as pd
from pandas_controller import *
from UI import MyTabWidget
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QTableView, QMenu, QAction, QFileDialog
from datetime import datetime
DATE_FORMAT = "%Y-%m-%d"
# Ref: https://www.programiz.com/python-programming/datetime/strptime
page_record = 20
total_record = 0
last_page = 0
current_page = 0
model_total_record = 0
model_last_page = 0
display_list = []
def count_page_num(total_record, page_record):
page_num = int(total_record / page_record)
mod_num = total_record % page_record
if mod_num == 0 and page_num > 0:
page_num -= 1
return page_num
class MyTabController(MyTabWidget):
def __init__(self, tab, schema_df, table_name):
super().__init__()
self.view = tab.view
self.comboBox = tab.comboBox
self.lineEdit = tab.lineEdit
self.buttonImport = tab.buttonImport
self.buttonExport = tab.buttonExport
self.buttonPFirst = tab.buttonPFirst
self.buttonPLast = tab.buttonPLast
self.buttonPPrev = tab.buttonPPrev
self.buttonPNext = tab.buttonPNext
self.schema_df = schema_df[schema_df['Table'] == table_name]
self.widget_list = list(self.schema_df.Widget)
self.range_list = list(self.schema_df.Range)
self.buttonImport.clicked.connect(self.import_file)
# Event Handler
def import_file(self):
global page_record
global total_record, model_total_record
global last_page, model_last_page
print ("import_file")
self.filename, filetype = QFileDialog.getOpenFileName(self, "Open file", "../data")
if len(self.filename) == 0:
print ("Please select a file")
return
self.df = pd.read_excel(self.filename)
self.df = self.df.fillna('None')
self.header = self.df.columns.to_list()
self.indexes = self.df.index.to_list()
self.model = PandasModel(self.df, self.header, self.indexes)
self.proxy = CustomProxyModel() # Customized Filter
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.view.setAlternatingRowColors(True)
self.view.setSelectionBehavior(QTableView.SelectRows)
self.view.setWordWrap(True)
self.view.resizeColumnsToContents()
self.view.resizeRowsToContents()
self.view.setShowGrid(True)
self.view.isCornerButtonEnabled()
#self.view.setItemDelegateForColumn(0,DateDelegate(self.view))
self.view.setItemDelegate(Delegate(self.widget_list, self.range_list))
self.comboBox.addItems(["{0}".format(col) for col in self.header])
self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged)
self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
self.buttonExport.clicked.connect(self.export_file)
self.buttonPFirst.clicked.connect(self.change_page)
self.buttonPLast.clicked.connect(self.change_page)
self.buttonPPrev.clicked.connect(self.change_page)
self.buttonPNext.clicked.connect(self.change_page)
self.horizontalHeader = self.view.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked)
print ("test-1")
total_record = self.model.rowCount()
last_page = count_page_num(total_record, page_record)
model_total_record = self.model.rowCount()
model_last_page = count_page_num(total_record, page_record)
print ("model_total_record:", model_total_record)
print ("model_last_page:", model_last_page)
self.proxy.setPage()
self.open_editor()
print ("test-2")
print ("import_file completed")
def export_file(self):
output_path = '../output/'
output_filename = output_path + self.filename
df = pd.DataFrame(self.model._data, columns = self.header)
df.to_excel(output_filename)
#self.model._df.to_excel(output_filename)
def change_page(self):
global page_record
#global total_record
global last_page
global current_page
button_name = self.sender().text()
print("button clicked:", button_name)
if button_name == "First":
if current_page == 0:
take_action = False
else:
new_page = 0
take_action = True
elif button_name == "Last":
if current_page == last_page:
take_action = False
else:
new_page = last_page
take_action = True
elif button_name == "Next":
if current_page == last_page:
take_action = False
else:
new_page = current_page + 1
take_action = True
elif button_name == "Prev":
if current_page == 0:
take_action = False
else:
take_action = True
new_page = current_page - 1
if take_action == True:
print (current_page, new_page)
prev_page = current_page
current_page = new_page
self.close_editor()
self.proxy.setPage() # Trigger the invalidateFilter()
self.open_editor()
#self.view.repaint() # Refresh the Tab Widget
def open_editor(self):
global display_list
for row in display_list:
for column in range(self.model.columnCount()):
index = self.proxy.index(row, column, QModelIndex())
self.view.openPersistentEditor(index)
def close_editor(self):
global display_list
for row in display_list:
for column in range(self.model.columnCount()):
index = self.proxy.index(row, column, QModelIndex())
self.view.closePersistentEditor(index)
#QtCore.pyqtSlot(int)
def on_view_horizontalHeader_sectionClicked(self, logicalIndex):
self.logicalIndex = logicalIndex
self.menuValues = QMenu(self)
self.signalMapper = QtCore.QSignalMapper(self)
self.comboBox.blockSignals(True)
self.comboBox.setCurrentIndex(self.logicalIndex)
self.comboBox.blockSignals(False) # True
#valuesUnique = self.model._df.iloc[:, self.logicalIndex].unique()
value_list = [str(item[self.logicalIndex]) for item in self.model._data]
valuesUnique = list(set(value_list)) # To get the unique value list
#print (valuesUnique)
actionAll = QAction("All", self)
actionAll.triggered.connect(self.on_actionAll_triggered)
self.menuValues.addAction(actionAll)
self.menuValues.addSeparator()
for actionNumber, actionName in enumerate(sorted(valuesUnique)):
action = QAction(str(actionName), self)
self.signalMapper.setMapping(action, actionNumber)
action.triggered.connect(self.signalMapper.map)
self.menuValues.addAction(action)
self.signalMapper.mapped.connect(self.on_signalMapper_mapped)
headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
posY = headerPos.y() + self.horizontalHeader.height()
posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex)
self.menuValues.exec_(QtCore.QPoint(posX, posY))
#QtCore.pyqtSlot()
def on_actionAll_triggered(self):
self.close_editor()
filterColumn = self.logicalIndex
self.proxy.setFilter("", filterColumn)
font = QtGui.QFont()
self.model.setFont(filterColumn, font)
self.open_editor()
#QtCore.pyqtSlot(int)
def on_signalMapper_mapped(self, i):
self.close_editor()
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
self.proxy.setFilter(stringAction, filterColumn)
font = QtGui.QFont()
font.setBold(True)
self.model.setFont(filterColumn, font)
self.open_editor()
#QtCore.pyqtSlot(str)
def on_lineEdit_textChanged(self, text):
self.close_editor()
self.proxy.setFilter(text, self.proxy.filterKeyColumn())
self.open_editor()
#QtCore.pyqtSlot(int)
def on_comboBox_currentIndexChanged(self, index):
self.close_editor()
self.proxy.setFilterKeyColumn(index)
self.open_editor()
class Delegate(QItemDelegate):
def __init__(self, widget_list, range_list):
QItemDelegate.__init__(self)
self.widget_list = widget_list
self.range_list = range_list
def createEditor(self, parent, option, index):
column = index.column()
widget = self.widget_list[column]
range_list = self.range_list[column]
#print ("createEditor:", column, widget)
if widget == "QDateEdit":
editor = QDateEdit(parent)
editor.setCalendarPopup(True)
return editor
if widget == "QSpinBox":
editor = QSpinBox(parent)
editor.setMinimum(int(range_list[0]))
editor.setMaximum(int(range_list[1]))
return editor
if widget == "QDoubleSpinBox":
editor = QDoubleSpinBox(parent)
editor.setDecimals(3)
editor.setMinimum(float(range_list[0]))
editor.setMaximum(float(range_list[1]))
#editor.setSingleStep(0.1)
return editor
if widget == "QComboBox":
comboBox = QComboBox(parent)
comboBox.addItems(range_list)
return comboBox
# no need to check for the other columns, as Qt automatically creates a
# QLineEdit for string values and QTimeEdit for QTime values;
return super().createEditor(parent, option, index)
def setEditorData(self, editor, index):
if isinstance(editor, QDateEdit):
#dt_str = index.data(QtCore.Qt.EditRole)
dt_str = index.data(QtCore.Qt.DisplayRole)
#print ("setEditorData, dt_str:", dt_str)
dt = datetime.strptime(dt_str, DATE_FORMAT)
editor.setDate(dt)
return
super().setEditorData(editor, index)
def setModelData(self, editor, model, index):
if isinstance(editor, QDateEdit):
dt = editor.date().toPyDate()
dt_str = dt.strftime(DATE_FORMAT)
#print ("setModelData, dt_str:", dt_str)
model.setData(index, dt_str, QtCore.Qt.EditRole)
return
super().setModelData(editor, model, index)
class CustomProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._filters = dict()
self.fc = 0 # filter counter
self.filter_flag = False
self.filter_list = []
#property
def filters(self):
return self._filters
def setFilter(self, expresion, column):
global total_record
global last_page
global model_total_record
global model_last_page
if expresion:
self.filters[column] = expresion
elif column in self.filters:
del self.filters[column]
if expresion:
print ("setFilter invoke invalidateFilter()")
total_record = model_total_record # restore the original model total record
last_page = model_last_page # restore the original model last page
self.fc = 0 # reset filter counter
self.filter_flag = True # Enabled to append the self.filter_list
self.filter_list = []
self.invalidateFilter()
print ("filter counter:", self.fc)
total_record = self.fc
last_page = count_page_num(total_record, page_record)
self.fc = 0
self.filter_flag = False # Disabled to append the self.filter_list
else:
total_record = model_total_record
last_page = model_last_page
self.invalidateFilter()
def setPage(self):
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
global page_record
global total_record
#global last_page
global current_page
global display_list
#print ("filterAcceptsRow:", source_row, source_parent)
start_record = current_page * page_record
end_record = start_record + page_record
if end_record > total_record:
end_record = total_record
if len(self.filters.items()) == 0: #None Filter Items
# Filter by Current Page
#print (source_row, start_record, end_record)
if source_row < start_record:
return False
elif source_row >= end_record:
return False
else: # Filter Items
for column, expresion in self.filters.items():
text = self.sourceModel().index(source_row, column, source_parent).data()
regex = QRegExp(
expresion, Qt.CaseInsensitive, QRegExp.RegExp
)
if regex.indexIn(text) == -1:
return False
# Filter by Current Page
#print ("Filter:", source_row, start_record, end_record)
if self.filter_flag == True:
self.fc += 1
self.filter_list.append(source_row)
if self.fc < start_record:
return False
elif self.fc >= end_record:
return False
else:
target_list = self.filter_list[start_record:end_record]
if source_row not in target_list:
return False
display_list.append(source_row)
return True
Tried self.view.repaint() and self.view.update() without success.
I'm using two threads Camera and NetworkProcessor in Qt application, which displayes their actual state in QLabel (there is one label for each thread: camStatusLabel, nnStatusLabel). To update text in label I am using signals and slots. There is also 6 labels to display images, which are updated in while loop periodically (cameraView, k1, k2, k3, k4, k5) with signals and slots as well.
import cv2
import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from PySide2.QtGui import *
import threading as th
from PIL import Image
import copy
class SecondPage(QWidget):
def __init__(self):
QWidget.__init__(self)
self.bigSize = QSize(640, 480)
self.smallSize = QSize(320, 240)
blackPic = QPixmap(self.bigSize)
blackPic.fill(Qt.black)
self.layoutTop = QVBoxLayout()
self.layoutTop.setAlignment(Qt.AlignCenter)
self.topRowLayout = QHBoxLayout()
self.buttonBack = QPushButton("Back")
self.camStatusLabel = QLabel("Camera status: not running...")
self.nnStatusLabel = QLabel("Neural net status: not running...")
self.topRowLayout.addWidget(self.buttonBack, alignment=Qt.AlignRight)
self.topRowLayout.addWidget(self.camStatusLabel, alignment=Qt.AlignLeft)
self.topRowLayout.addWidget(self.nnStatusLabel, alignment=Qt.AlignLeft)
self.cameraView = QLabel()
self.cameraView.setFixedSize(self.bigSize)
self.cameraView.setPixmap(blackPic)
self.knnLayout = QHBoxLayout()
self.layoutTop.addLayout(self.topRowLayout, alignment=Qt.AlignLeft)
self.layoutTop.addWidget(self.cameraView, alignment=Qt.AlignCenter)
self.layoutTop.addLayout(self.knnLayout, alignment=Qt.AlignCenter)
self.k1 = QLabel(alignment=Qt.AlignCenter)
self.k1.setFixedSize(self.smallSize)
self.k1.setPixmap(blackPic)
self.k2 = QLabel(alignment=Qt.AlignCenter)
self.k2.setFixedSize(self.smallSize)
self.k2.setPixmap(blackPic)
self.k3 = QLabel(alignment=Qt.AlignCenter)
self.k3.setFixedSize(self.smallSize)
self.k3.setPixmap(blackPic)
self.k4 = QLabel(alignment=Qt.AlignCenter)
self.k4.setFixedSize(self.smallSize)
self.k4.setPixmap(blackPic)
self.k5 = QLabel(alignment=Qt.AlignCenter)
self.k5.setFixedSize(self.smallSize)
self.k5.setPixmap(blackPic)
self.knnLayout.addWidget(self.k1)
self.knnLayout.addWidget(self.k2)
self.knnLayout.addWidget(self.k3)
self.knnLayout.addWidget(self.k4)
self.knnLayout.addWidget(self.k5)
self.setLayout(self.layoutTop)
self.frame = [None]
self.frameLock = QMutex()
startExecute = th.Event()
threadIsLoadedEvent = th.Event()
self.cameraThread = Camera()
self.connect(self.cameraThread, SIGNAL("updateCameraView(QImage)"), self.updateCameraView)
self.connect(self.cameraThread, SIGNAL("updateCameraStatus(QString)"), self.updateCameraStatus)
self.cameraThread.startCapturing(self.frame, self.frameLock, startExecute, threadIsLoadedEvent)
self.processingThread = NetworkProcessor()
self.connect(self.processingThread, SIGNAL("updateResults(QImage,QImage,QImage,QImage,QImage)"), self.updateResults)
self.connect(self.processingThread, SIGNAL("nnStatusLabel(QString)"), self.updateNNStatus)
self.processingThread.startAnalyzing(self.frame, self.frameLock, self.smallSize.toTuple(), startExecute, threadIsLoadedEvent)
startExecute.set()
def updateCameraStatus(self, text):
self.camStatusLabel.setText(text)
self.camStatusLabel.update()
def updateNNStatus(self, text):
self.nnStatusLabel.setText(text)
self.nnStatusLabel.update()
def updateCameraView(self, image):
self.cameraView.setPixmap(QPixmap.fromImage(image).scaled(self.bigSize))
self.cameraView.update()
def updateResults(self, p1, p2, p3, p4, p5):
self.k1.setPixmap(QPixmap.fromImage(p1))
self.k1.update()
self.k2.setPixmap(QPixmap.fromImage(p2))
self.k2.update()
self.k3.setPixmap(QPixmap.fromImage(p3))
self.k3.update()
self.k4.setPixmap(QPixmap.fromImage(p4))
self.k4.update()
self.k5.setPixmap(QPixmap.fromImage(p5))
self.k5.update()
def killPage(self):
self.cameraThread.stopThread()
self.processingThread.stopThread()
class Camera(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exit = False
self.frame = None
self.frameLock = None
self.initSuccessEvent = None
self.startEvent = None
def stopThread(self):
self.exit = True
self.wait()
def startCapturing(self, frame, frameLock, startEvent, initSuccessEvent):
self.frame = frame
self.frameLock = frameLock
self.initSuccessEvent = initSuccessEvent
self.startEvent = startEvent
self.start()
def run(self):
self.startEvent.wait()
self.emit(SIGNAL("updateCameraStatus(QString)"), "Camera status: Opening...")
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
#initialize frame and signal success to main thead
self.frameLock.lock()
_, self.frame[0] = cap.read()
self.frameLock.unlock()
self.initSuccessEvent.set()
self.emit(SIGNAL("updateCameraStatus(QString)"), "Camera status: Running...")
#enter into loop
while True:
_, img = cap.read()
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
self.frameLock.lock()
self.frame[0] = copy.deepcopy(img)
self.frameLock.unlock()
img = QImage(img, img.shape[1], img.shape[0], img.strides[0], QImage.Format_RGB888)
self.emit(SIGNAL("updateCameraView(QImage)"), img)
if self.exit:
cap.release()
break
class NetworkProcessor(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exit = False
self.frame = None
self.frameLock = None
self.imgSize = (100,100)
self.initSuccessEvent = None
self.startEvent = None
def stopThread(self):
self.exit = True
self.wait()
def startAnalyzing(self, frame, frameLock, imgSize, startEvent, initSuccessEvent):
self.frame = frame
self.frameLock = frameLock
self.imgSize = imgSize
self.initSuccessEvent = initSuccessEvent
self.startEvent = startEvent
self.start()
def run(self):
import pydevd;pydevd.settrace(suspend=False)
self.startEvent.wait()
self.emit(SIGNAL("nnStatusLabel(QString)"), "Neural net status: Waiting for camera...")
self.initSuccessEvent.wait()
self.emit(SIGNAL("nnStatusLabel(QString)"), "Neural net status: Loading nn and dataset...")
QThread.msleep(1000) #to simulate initialization
self.emit(SIGNAL("nnStatusLabel(QString)"), "Neural net status: Running...")
while True:
QThread.msleep(1000)
if self.exit:
break
self.frameLock.lock()
camImg = copy.deepcopy(self.frame[0])
self.frameLock.unlock()
products = self._processImg(Image.fromarray(camImg), 5)
data1 = products[0].tobytes('raw', 'RGB')
k1 = QImage(data1, products[0].size[0], products[0].size[1], QImage.Format_RGB888)
data2 = products[1].tobytes('raw', 'RGB')
k2 = QImage(data2, products[1].size[0], products[1].size[1], QImage.Format_RGB888)
data3 = products[2].tobytes('raw', 'RGB')
k3 = QImage(data3, products[2].size[0], products[2].size[1], QImage.Format_RGB888)
data4 = products[3].tobytes('raw', 'RGB')
k4 = QImage(data4, products[3].size[0], products[3].size[1], QImage.Format_RGB888)
data5 = products[4].tobytes('raw', 'RGB')
k5 = QImage(data5, products[4].size[0], products[4].size[1], QImage.Format_RGB888)
self.emit(SIGNAL("updateResults(QImage,QImage,QImage,QImage,QImage)"), k1, k2, k3, k4, k5)
if self.exit:
break
def _processImg(self, queryImg, k):
imgs = []
for i in range(k):
im = queryImg.rotate(20*i)
im = im.resize(self.imgSize)
imgs.append(im)
return imgs
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.loadSecondPage()
def loadSecondPage(self):
widget = SecondPage()
self.setCentralWidget(widget)
self.setWindowTitle("Metriclearning demo - Visualisation")
def closeEvent(self, event):
print("Closing app")
self.centralWidget().killPage()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
The problem is that, when I am updating text labels containing thread state (camStatusLabel, nnStatusLabel), the content does not remain as I have set it, but randomly changes to previous texts and back, even though the text is not updated never again once the thread enters into while loop.
The same problem is observable on labels containing images.
Does anyone know what might cause this problem?
EDIT:
I eddited the code above to be executable. The problem is same as I have already described: The text in QLabel nnStatusLabel changes even though it is not supposed to and similar behavior is occasionally observable on displayed images as well - displayed images returns back in time (cameraView, k1, k2, k3, k4, k5).
I want to create textEdit with line number on the left side in PyQt like Notepad++. I tried this adding another textEdit but scrolling is stuck. I searched and found this question, but there is no good solution for it.
Is this what you are looking for CodeEditor example in pyqt based on the c++ http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html
Putting it together for python3 (Im using PyQt4 not 5 but I guess it is similar) (and using QPlainTextEdit not QTextEdit see QTextEdit vs QPlainTextEdit ):
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import numpy as np
class LineNumberArea(QWidget):
def __init__(self, editor):
super().__init__(editor)
self.myeditor = editor
def sizeHint(self):
return Qsize(self.editor.lineNumberAreaWidth(), 0)
def paintEvent(self, event):
self.myeditor.lineNumberAreaPaintEvent(event)
class CodeEditor(QPlainTextEdit):
def __init__(self):
super().__init__()
self.lineNumberArea = LineNumberArea(self)
self.connect(self, SIGNAL('blockCountChanged(int)'), self.updateLineNumberAreaWidth)
self.connect(self, SIGNAL('updateRequest(QRect,int)'), self.updateLineNumberArea)
self.connect(self, SIGNAL('cursorPositionChanged()'), self.highlightCurrentLine)
self.updateLineNumberAreaWidth(0)
def lineNumberAreaWidth(self):
digits = 1
count = max(1, self.blockCount())
while count >= 10:
count /= 10
digits += 1
space = 3 + self.fontMetrics().width('9') * digits
return space
def updateLineNumberAreaWidth(self, _):
self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)
def updateLineNumberArea(self, rect, dy):
if dy:
self.lineNumberArea.scroll(0, dy)
else:
self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(),
rect.height())
if rect.contains(self.viewport().rect()):
self.updateLineNumberAreaWidth(0)
def resizeEvent(self, event):
super().resizeEvent(event)
cr = self.contentsRect();
self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(),
self.lineNumberAreaWidth(), cr.height()))
def lineNumberAreaPaintEvent(self, event):
mypainter = QPainter(self.lineNumberArea)
mypainter.fillRect(event.rect(), Qt.lightGray)
block = self.firstVisibleBlock()
blockNumber = block.blockNumber()
top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
bottom = top + self.blockBoundingRect(block).height()
# Just to make sure I use the right font
height = self.fontMetrics().height()
while block.isValid() and (top <= event.rect().bottom()):
if block.isVisible() and (bottom >= event.rect().top()):
number = str(blockNumber + 1)
mypainter.setPen(Qt.black)
mypainter.drawText(0, top, self.lineNumberArea.width(), height,
Qt.AlignRight, number)
block = block.next()
top = bottom
bottom = top + self.blockBoundingRect(block).height()
blockNumber += 1
def highlightCurrentLine(self):
extraSelections = []
if not self.isReadOnly():
selection = QTextEdit.ExtraSelection()
lineColor = QColor(Qt.yellow).lighter(160)
selection.format.setBackground(lineColor)
selection.format.setProperty(QTextFormat.FullWidthSelection, True)
selection.cursor = self.textCursor()
selection.cursor.clearSelection()
extraSelections.append(selection)
self.setExtraSelections(extraSelections)
if __name__ == "__main__":
app = QApplication(sys.argv)
txt = CodeEditor()
txt.show()
sys.exit(app.exec_())
It's my code of PyQt5 and python3 which combined #Dan-Dev and #Axel Schneider. You can directly run it, or simply import use code from QCodeEditor import QCodeEditor.
#!/usr/bin/python3
# QcodeEditor.py by acbetter.
# -*- coding: utf-8 -*-
from PyQt5.QtCore import Qt, QRect, QSize
from PyQt5.QtWidgets import QWidget, QPlainTextEdit, QTextEdit
from PyQt5.QtGui import QColor, QPainter, QTextFormat
class QLineNumberArea(QWidget):
def __init__(self, editor):
super().__init__(editor)
self.codeEditor = editor
def sizeHint(self):
return QSize(self.editor.lineNumberAreaWidth(), 0)
def paintEvent(self, event):
self.codeEditor.lineNumberAreaPaintEvent(event)
class QCodeEditor(QPlainTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.lineNumberArea = QLineNumberArea(self)
self.blockCountChanged.connect(self.updateLineNumberAreaWidth)
self.updateRequest.connect(self.updateLineNumberArea)
self.cursorPositionChanged.connect(self.highlightCurrentLine)
self.updateLineNumberAreaWidth(0)
def lineNumberAreaWidth(self):
digits = 1
max_value = max(1, self.blockCount())
while max_value >= 10:
max_value /= 10
digits += 1
space = 3 + self.fontMetrics().width('9') * digits
return space
def updateLineNumberAreaWidth(self, _):
self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)
def updateLineNumberArea(self, rect, dy):
if dy:
self.lineNumberArea.scroll(0, dy)
else:
self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())
if rect.contains(self.viewport().rect()):
self.updateLineNumberAreaWidth(0)
def resizeEvent(self, event):
super().resizeEvent(event)
cr = self.contentsRect()
self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()))
def highlightCurrentLine(self):
extraSelections = []
if not self.isReadOnly():
selection = QTextEdit.ExtraSelection()
lineColor = QColor(Qt.yellow).lighter(160)
selection.format.setBackground(lineColor)
selection.format.setProperty(QTextFormat.FullWidthSelection, True)
selection.cursor = self.textCursor()
selection.cursor.clearSelection()
extraSelections.append(selection)
self.setExtraSelections(extraSelections)
def lineNumberAreaPaintEvent(self, event):
painter = QPainter(self.lineNumberArea)
painter.fillRect(event.rect(), Qt.lightGray)
block = self.firstVisibleBlock()
blockNumber = block.blockNumber()
top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
bottom = top + self.blockBoundingRect(block).height()
# Just to make sure I use the right font
height = self.fontMetrics().height()
while block.isValid() and (top <= event.rect().bottom()):
if block.isVisible() and (bottom >= event.rect().top()):
number = str(blockNumber + 1)
painter.setPen(Qt.black)
painter.drawText(0, top, self.lineNumberArea.width(), height, Qt.AlignRight, number)
block = block.next()
top = bottom
bottom = top + self.blockBoundingRect(block).height()
blockNumber += 1
if __name__ == '__main__':
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
codeEditor = QCodeEditor()
codeEditor.show()
sys.exit(app.exec_())
It runs like this.
By the way, if you want use it in your Qt Designer, you should do it like this and place the *.ui file in the same directory of your QCodeEditor.py file's path unless you set the environment variable. And, you need convert your *.ui file to *.py file by the command pyuic5 -x *.ui -o *.py. Hope Helpful~
here a version for PyQt5 (with Menu etc ...)
#!/usr/bin/python3
# -- coding: utf-8 --
from PyQt5.QtWidgets import QPlainTextEdit, QWidget, QVBoxLayout, QApplication, QFileDialog, QMessageBox, QHBoxLayout, \
QFrame, QTextEdit, QToolBar, QComboBox, QLabel, QAction, QLineEdit, QToolButton, QMenu, QMainWindow
from PyQt5.QtGui import QIcon, QPainter, QTextFormat, QColor, QTextCursor, QKeySequence, QClipboard, QTextCharFormat, QPalette
from PyQt5.QtCore import Qt, QVariant, QRect, QDir, QFile, QFileInfo, QTextStream, QRegExp, QSettings
import sys, os
lineBarColor = QColor("#ACDED5")
lineHighlightColor = QColor("#ACDED5")
class NumberBar(QWidget):
def __init__(self, parent = None):
super(NumberBar, self).__init__(parent)
self.editor = parent
layout = QVBoxLayout()
self.setLayout(layout)
self.editor.blockCountChanged.connect(self.update_width)
self.editor.updateRequest.connect(self.update_on_scroll)
self.update_width('1')
def update_on_scroll(self, rect, scroll):
if self.isVisible():
if scroll:
self.scroll(0, scroll)
else:
self.update()
def update_width(self, string):
width = self.fontMetrics().width(str(string)) + 10
if self.width() != width:
self.setFixedWidth(width)
def paintEvent(self, event):
if self.isVisible():
block = self.editor.firstVisibleBlock()
height = self.fontMetrics().height()
number = block.blockNumber()
painter = QPainter(self)
painter.fillRect(event.rect(), lineBarColor)
painter.drawRect(0, 0, event.rect().width() - 1, event.rect().height() - 1)
font = painter.font()
current_block = self.editor.textCursor().block().blockNumber() + 1
condition = True
while block.isValid() and condition:
block_geometry = self.editor.blockBoundingGeometry(block)
offset = self.editor.contentOffset()
block_top = block_geometry.translated(offset).top()
number += 1
rect = QRect(0, block_top, self.width() - 5, height)
if number == current_block:
font.setBold(True)
else:
font.setBold(False)
painter.setFont(font)
painter.drawText(rect, Qt.AlignRight, '%i'%number)
if block_top > event.rect().bottom():
condition = False
block = block.next()
painter.end()
class myEditor(QMainWindow):
def __init__(self, parent = None):
super(myEditor, self).__init__(parent)
self.MaxRecentFiles = 5
self.windowList = []
self.recentFileActs = []
self.setAttribute(Qt.WA_DeleteOnClose)
# Editor Widget ...
QIcon.setThemeName('Faenza-Dark')
self.editor = QPlainTextEdit()
self.editor.setStyleSheet(stylesheet2(self))
self.editor.setFrameStyle(QFrame.NoFrame)
self.editor.setTabStopWidth(14)
self.extra_selections = []
self.fname = ""
self.filename = ""
# Line Numbers ...
self.numbers = NumberBar(self.editor)
self.createActions()
# Laying out...
layoutH = QHBoxLayout()
layoutH.setSpacing(1.5)
layoutH.addWidget(self.numbers)
layoutH.addWidget(self.editor)
### begin toolbar
tb = QToolBar(self)
tb.setWindowTitle("File Toolbar")
self.newAct = QAction("&New", self, shortcut=QKeySequence.New,
statusTip="Create a new file", triggered=self.newFile)
self.newAct.setIcon(QIcon.fromTheme("document-new"))
self.openAct = QAction("&Open", self, shortcut=QKeySequence.Open,
statusTip="open file", triggered=self.openFile)
self.openAct.setIcon(QIcon.fromTheme("document-open"))
self.saveAct = QAction("&Save", self, shortcut=QKeySequence.Save,
statusTip="save file", triggered=self.fileSave)
self.saveAct.setIcon(QIcon.fromTheme("document-save"))
self.saveAsAct = QAction("&Save as ...", self, shortcut=QKeySequence.SaveAs,
statusTip="save file as ...", triggered=self.fileSaveAs)
self.saveAsAct.setIcon(QIcon.fromTheme("document-save-as"))
self.exitAct = QAction("Exit", self, shortcut=QKeySequence.Quit,
toolTip="Exit", triggered=self.handleQuit)
self.exitAct.setIcon(QIcon.fromTheme("application-exit"))
### find / replace toolbar
self.tbf = QToolBar(self)
self.tbf.setWindowTitle("Find Toolbar")
self.findfield = QLineEdit()
self.findfield.addAction(QIcon.fromTheme("edit-find"), QLineEdit.LeadingPosition)
self.findfield.setClearButtonEnabled(True)
self.findfield.setFixedWidth(150)
self.findfield.setPlaceholderText("find")
self.findfield.setToolTip("press RETURN to find")
self.findfield.setText("")
ft = self.findfield.text()
self.findfield.returnPressed.connect(self.findText)
self.tbf.addWidget(self.findfield)
self.replacefield = QLineEdit()
self.replacefield.addAction(QIcon.fromTheme("edit-find-and-replace"), QLineEdit.LeadingPosition)
self.replacefield.setClearButtonEnabled(True)
self.replacefield.setFixedWidth(150)
self.replacefield.setPlaceholderText("replace with")
self.replacefield.setToolTip("press RETURN to replace the first")
self.replacefield.returnPressed.connect(self.replaceOne)
self.tbf.addSeparator()
self.tbf.addWidget(self.replacefield)
self.tbf.addSeparator()
self.tbf.addAction("replace all", self.replaceAll)
self.tbf.addSeparator()
layoutV = QVBoxLayout()
bar=self.menuBar()
self.filemenu=bar.addMenu("File")
self.separatorAct = self.filemenu.addSeparator()
self.filemenu.addAction(self.newAct)
self.filemenu.addAction(self.openAct)
self.filemenu.addAction(self.saveAct)
self.filemenu.addAction(self.saveAsAct)
self.filemenu.addSeparator()
for i in range(self.MaxRecentFiles):
self.filemenu.addAction(self.recentFileActs[i])
self.updateRecentFileActions()
self.filemenu.addSeparator()
self.filemenu.addAction(self.exitAct)
bar.setStyleSheet(stylesheet2(self))
editmenu = bar.addMenu("Edit")
editmenu.addAction(QAction(QIcon.fromTheme('edit-copy'), "Copy", self, triggered = self.editor.copy, shortcut = QKeySequence.Copy))
editmenu.addAction(QAction(QIcon.fromTheme('edit-cut'), "Cut", self, triggered = self.editor.cut, shortcut = QKeySequence.Cut))
editmenu.addAction(QAction(QIcon.fromTheme('edit-paste'), "Paste", self, triggered = self.editor.paste, shortcut = QKeySequence.Paste))
editmenu.addAction(QAction(QIcon.fromTheme('edit-delete'), "Delete", self, triggered = self.editor.cut, shortcut = QKeySequence.Delete))
editmenu.addSeparator()
editmenu.addAction(QAction(QIcon.fromTheme('edit-select-all'), "Select All", self, triggered = self.editor.selectAll, shortcut = QKeySequence.SelectAll))
layoutV.addWidget(bar)
layoutV.addWidget(self.tbf)
layoutV.addLayout(layoutH)
### main window
mq = QWidget(self)
mq.setLayout(layoutV)
self.setCentralWidget(mq)
# Event Filter ...
self.installEventFilter(self)
self.editor.setFocus()
self.cursor = QTextCursor()
self.editor.setPlainText("hello")
self.editor.moveCursor(self.cursor.End)
self.editor.document().modificationChanged.connect(self.setWindowModified)
# Brackets ExtraSelection ...
self.left_selected_bracket = QTextEdit.ExtraSelection()
self.right_selected_bracket = QTextEdit.ExtraSelection()
def createActions(self):
for i in range(self.MaxRecentFiles):
self.recentFileActs.append(
QAction(self, visible=False,
triggered=self.openRecentFile))
def openRecentFile(self):
action = self.sender()
if action:
if (self.maybeSave()):
self.openFileOnStart(action.data())
### New File
def newFile(self):
if self.maybeSave():
self.editor.clear()
self.editor.setPlainText("")
self.filename = ""
self.setModified(False)
self.editor.moveCursor(self.cursor.End)
### open File
def openFileOnStart(self, path=None):
if path:
inFile = QFile(path)
if inFile.open(QFile.ReadWrite | QFile.Text):
text = inFile.readAll()
try:
# Python v3.
text = str(text, encoding = 'utf8')
except TypeError:
# Python v2.
text = str(text)
self.editor.setPlainText(text)
self.filename = path
self.setModified(False)
self.fname = QFileInfo(path).fileName()
self.setWindowTitle(self.fname + "[*]")
self.document = self.editor.document()
self.setCurrentFile(self.filename)
### open File
def openFile(self, path=None):
if self.maybeSave():
if not path:
path, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.homePath() + "/Documents/",
"Text Files (*.txt *.csv *.py);;All Files (*.*)")
if path:
inFile = QFile(path)
if inFile.open(QFile.ReadWrite | QFile.Text):
text = inFile.readAll()
try:
# Python v3.
text = str(text, encoding = 'utf8')
except TypeError:
# Python v2.
text = str(text)
self.editor.setPlainText(text)
self.filename = path
self.setModified(False)
self.fname = QFileInfo(path).fileName()
self.setWindowTitle(self.fname + "[*]")
self.document = self.editor.document()
self.setCurrentFile(self.filename)
def fileSave(self):
if (self.filename != ""):
file = QFile(self.filename)
print(self.filename)
if not file.open( QFile.WriteOnly | QFile.Text):
QMessageBox.warning(self, "Error",
"Cannot write file %s:\n%s." % (self.filename, file.errorString()))
return
outstr = QTextStream(file)
QApplication.setOverrideCursor(Qt.WaitCursor)
outstr << self.editor.toPlainText()
QApplication.restoreOverrideCursor()
self.setModified(False)
self.fname = QFileInfo(self.filename).fileName()
self.setWindowTitle(self.fname + "[*]")
self.setCurrentFile(self.filename)
else:
self.fileSaveAs()
### save File
def fileSaveAs(self):
fn, _ = QFileDialog.getSaveFileName(self, "Save as...", self.filename,
"Python files (*.py)")
if not fn:
print("Error saving")
return False
lfn = fn.lower()
if not lfn.endswith('.py'):
fn += '.py'
self.filename = fn
self.fname = os.path.splitext(str(fn))[0].split("/")[-1]
return self.fileSave()
def closeEvent(self, e):
if self.maybeSave():
e.accept()
else:
e.ignore()
### ask to save
def maybeSave(self):
if not self.isModified():
return True
if self.filename.startswith(':/'):
return True
ret = QMessageBox.question(self, "Message",
"<h4><p>The document was modified.</p>\n" \
"<p>Do you want to save changes?</p></h4>",
QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
if ret == QMessageBox.Yes:
if self.filename == "":
self.fileSaveAs()
return False
else:
self.fileSave()
return True
if ret == QMessageBox.Cancel:
return False
return True
def findText(self):
ft = self.findfield.text()
if self.editor.find(ft):
return
else:
self.editor.moveCursor(1)
if self.editor.find(ft):
self.editor.moveCursor(QTextCursor.Start, QTextCursor.MoveAnchor)
def handleQuit(self):
print("Goodbye ...")
app.quit()
def set_numbers_visible(self, value = True):
self.numbers.setVisible(False)
def match_left(self, block, character, start, found):
map = {'{': '}', '(': ')', '[': ']'}
while block.isValid():
data = block.userData()
if data is not None:
braces = data.braces
N = len(braces)
for k in range(start, N):
if braces[k].character == character:
found += 1
if braces[k].character == map[character]:
if not found:
return braces[k].position + block.position()
else:
found -= 1
block = block.next()
start = 0
def match_right(self, block, character, start, found):
map = {'}': '{', ')': '(', ']': '['}
while block.isValid():
data = block.userData()
if data is not None:
braces = data.braces
if start is None:
start = len(braces)
for k in range(start - 1, -1, -1):
if braces[k].character == character:
found += 1
if braces[k].character == map[character]:
if found == 0:
return braces[k].position + block.position()
else:
found -= 1
block = block.previous()
start = None
# '''
cursor = self.editor.textCursor()
block = cursor.block()
data = block.userData()
previous, next = None, None
if data is not None:
position = cursor.position()
block_position = cursor.block().position()
braces = data.braces
N = len(braces)
for k in range(0, N):
if braces[k].position == position - block_position or braces[k].position == position - block_position - 1:
previous = braces[k].position + block_position
if braces[k].character in ['{', '(', '[']:
next = self.match_left(block,
braces[k].character,
k + 1, 0)
elif braces[k].character in ['}', ')', ']']:
next = self.match_right(block,
braces[k].character,
k, 0)
if next is None:
next = -1
if next is not None and next > 0:
if next == 0 and next >= 0:
format = QTextCharFormat()
cursor.setPosition(previous)
cursor.movePosition(QTextCursor.NextCharacter,
QTextCursor.KeepAnchor)
format.setBackground(QColor('white'))
self.left_selected_bracket.format = format
self.left_selected_bracket.cursor = cursor
cursor.setPosition(next)
cursor.movePosition(QTextCursor.NextCharacter,
QTextCursor.KeepAnchor)
format.setBackground(QColor('white'))
self.right_selected_bracket.format = format
self.right_selected_bracket.cursor = cursor
# '''
def paintEvent(self, event):
highlighted_line = QTextEdit.ExtraSelection()
highlighted_line.format.setBackground(lineHighlightColor)
highlighted_line.format.setProperty(QTextFormat
.FullWidthSelection,
QVariant(True))
highlighted_line.cursor = self.editor.textCursor()
highlighted_line.cursor.clearSelection()
self.editor.setExtraSelections([highlighted_line,
self.left_selected_bracket,
self.right_selected_bracket])
def document(self):
return self.editor.document
def isModified(self):
return self.editor.document().isModified()
def setModified(self, modified):
self.editor.document().setModified(modified)
def setLineWrapMode(self, mode):
self.editor.setLineWrapMode(mode)
def clear(self):
self.editor.clear()
def setPlainText(self, *args, **kwargs):
self.editor.setPlainText(*args, **kwargs)
def setDocumentTitle(self, *args, **kwargs):
self.editor.setDocumentTitle(*args, **kwargs)
def set_number_bar_visible(self, value):
self.numbers.setVisible(value)
def replaceAll(self):
print("replacing all")
oldtext = self.editor.document().toPlainText()
newtext = oldtext.replace(self.findfield.text(), self.replacefield.text())
self.editor.setPlainText(newtext)
self.setModified(True)
def replaceOne(self):
print("replacing all")
oldtext = self.editor.document().toPlainText()
newtext = oldtext.replace(self.findfield.text(), self.replacefield.text(), 1)
self.editor.setPlainText(newtext)
self.setModified(True)
def setCurrentFile(self, fileName):
self.curFile = fileName
if self.curFile:
self.setWindowTitle("%s - Recent Files" % self.strippedName(self.curFile))
else:
self.setWindowTitle("Recent Files")
settings = QSettings('Axel Schneider', 'PTEdit')
files = settings.value('recentFileList')
try:
files.remove(fileName)
except ValueError:
pass
files.insert(0, fileName)
del files[self.MaxRecentFiles:]
settings.setValue('recentFileList', files)
for widget in QApplication.topLevelWidgets():
if isinstance(widget, myEditor):
widget.updateRecentFileActions()
def updateRecentFileActions(self):
mytext = ""
settings = QSettings('Axel Schneider', 'PTEdit')
files = settings.value('recentFileList')
numRecentFiles = min(len(files), self.MaxRecentFiles)
for i in range(numRecentFiles):
text = "&%d %s" % (i + 1, self.strippedName(files[i]))
self.recentFileActs[i].setText(text)
self.recentFileActs[i].setData(files[i])
self.recentFileActs[i].setVisible(True)
for j in range(numRecentFiles, self.MaxRecentFiles):
self.recentFileActs[j].setVisible(False)
self.separatorAct.setVisible((numRecentFiles > 0))
def clearRecentFileList(self, fileName):
self.rmenu.clear()
def strippedName(self, fullFileName):
return QFileInfo(fullFileName).fileName()
def stylesheet2(self):
return """
QPlainTextEdit
{
background: #ECECEC;
color: #202020;
border: 1px solid #1EAE3D;
selection-background-color: #505050;
selection-color: #ACDED5;
}
QMenu
{
background: #F2F2F2;
color: #0E185F;
border: 1px solid #1EAE3D;
selection-background-color: #ACDED5;
}
"""
if __name__ == '__main__':
app = QApplication(sys.argv)
win = myEditor()
win.setWindowIcon(QIcon.fromTheme("application-text"))
win.setWindowTitle("Plain Text Edit" + "[*]")
win.setMinimumSize(640,250)
win.showMaximized()
if len(sys.argv) > 1:
print(sys.argv[1])
win.openFileOnStart(sys.argv[1])
app.exec_()