6 years ago they proposed an excellent solution on this issue, but my changes led to an unexpected result, the window does not clear when I call the submenu function and the main menu, help me understand what is wrong.
import curses
from curses import panel
from test_print import get_menu_list, get_timings, time_to_seconds, GetPageChoise,\
main_menu_items, list_of_themes_end, text_discription_get
page = GetPageChoise()
class Menu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(5,2)
self.window.keypad(1)
self.panel = panel.new_panel(self.window)
self.panel.hide()
panel.update_panels()
self.position = 0
self.items = items
self.items.append(('exit','exit'))
def navigate(self, n):
self.position += n
if self.position < 0:
self.position = 0
elif self.position >= len(self.items):
self.position = len(self.items)-1
def display(self):
self.panel.top()
self.panel.show()
self.window.clear()
while True:
self.window.refresh()
curses.doupdate()
for index, item in enumerate(self.items):
if index == self.position:
mode = curses.A_REVERSE
else:
mode = curses.A_NORMAL
msg = '%d. %s' % (index, item[0])
self.window.addstr(10+ index, 1, msg, mode)
key = self.window.getch()
if key in [curses.KEY_ENTER, ord('\n')]:
if self.position == len(self.items)-1:
break
else:
self.items[self.position][1]()
elif key == curses.KEY_UP:
self.navigate(-1)
elif key == curses.KEY_DOWN:
self.navigate(1)
self.window.clear()
self.panel.hide()
panel.update_panels()
curses.doupdate()
class SubMenu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(5,2)
self.window.keypad(1)
self.panel = panel.new_panel(self.window)
self.panel.hide()
panel.update_panels()
self.position = 0
self.items = items
self.items.append(('exit','exit'))
def navigate(self, n):
self.position += n
if self.position < 0:
self.position = 0
elif self.position >= len(self.items):
self.position = len(self.items)-1
def display_sub(self):
while True:
self.window.refresh()
curses.doupdate()
for index, item in enumerate(self.items):
if index == self.position:
mode = curses.A_REVERSE
else:
mode = curses.A_NORMAL
next = 1
for disript in text_discription_get():
self.window.addstr(next, 1, disript)
next +=1
msg = '%d. %s' % (index, item[0])
self.window.addstr(10+ index, 1, msg, mode)
key = self.window.getch()
if key in [curses.KEY_ENTER, ord('\n')]:
if self.position == len(self.items)-1:
break
else:
self.items[self.position][1]()
elif key == curses.KEY_UP:
self.navigate(-1)
elif key == curses.KEY_DOWN:
self.navigate(1)
class MyApp(object):
def __init__(self, stdscreen):
self.screen = stdscreen
curses.curs_set(0)
submenu_items = [
('beep', curses.beep),
('flash', curses.flash)
]
submenu = SubMenu(sub_menu_items, self.screen) #Вывел конкретный подкаст, нужно изменить на выбор подкастов.
main_menu_items = [
('beep', curses.beep),
('flash', curses.flash),
('submenu', submenu.display_sub)
]
main_menu = Menu(main_menu_items, self.screen)
main_menu.display()
if __name__ == '__main__':
curses.wrapper(MyApp)
#Вывел меню, сделал выбор, нужно вывести Discription, сделать вызов плеера по выбору темы.
You can see how it looks at me at the GIF, the fact is that the proposed solution does not fit, since the submenu should display new content each time, depending on the choice.
gif how work this
Inserting a clear() after the function that shows the submenu worked for me:
class Menu(object):
...
def display(self):
...
if key in [curses.KEY_ENTER, ord('\n')]:
if self.position == len(self.items)-1:
break
else:
self.items[self.position][1]()
self.window.clear() # This will clear the main menu window every time an item is selected
You should also clear the screen before showing another window because the same thing could happen if your submenu was smaller than your main menu.
Alternatively, you could clear the window at the start of every loop, as you are already redrawing the list after each key press. This way you can update the items in the menu, and will not see parts of the old menu, as you would with your current program.
Related
I've been designing a GUI-based chess game in PyQt5 (Python) . Since it is incomplete , there are some issues . But the biggest issue is , that after every move , the time lag in the GUI increases . After a particular time (say , move 8) , the GUI would almost stop working . Please help . The code is given below :
import PyQt5
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import uic
import chess
import sys
import tabulate
class Chess_App(QtWidgets.QMainWindow):
def __init__(self):
super(Chess_App, self).__init__()
uic.loadUi("CHESS.ui", self)
self.move_name = ""
self.move = ""
self.board_rows = [8,7,6,5,4,3,2,1]
self.board_columns = 'a b c d e f g h'.split(" ")
# variable_needed = f"{self.board_columns[7-(n//8)]}{self.board_rows[n%8]}"
font = QtGui.QFont()
font.setPointSize(1)
self.win = None
self.draw = None
self.valid_move = True
self.Chess_Board = {n:QtWidgets.QPushButton(self) for n in range(64)}
for i in range(64):
self.Chess_Board[i].setStyleSheet("QPushButton {background-color : lightgreen ; color : lightgreen}")
self.Chess_Board[i].setFont(font)
self.Chess_Board[i].setObjectName(f"{self.board_columns[i%8]}{self.board_rows[i//8]}")
self.lineEdit_2.setDisabled(True)
for i in range(64):
if (i // 8) % 2 == 0:
if i % 2 == 0:
self.Chess_Board[i].setStyleSheet("QPushButton {background-color : white}")
if (i // 8) % 2 == 1:
if i % 2 == 1:
self.Chess_Board[i].setStyleSheet("QPushButton {background-color : white}")
self.Chess_Board[i].setGeometry(60+((i%8)*50),140+((i//8)*50),50,50)
self.Chess_Board[i].clicked.connect(self.initiate_move)
self.lineEdit.isReadOnly()
self.pushButton_A.clicked.connect(self.play_move)
self.pushButton_B.clicked.connect(self.resignation)
self.pushButton_C.clicked.connect(self.draw_game)
self.pushButton_D.clicked.connect(self.save_game)
self.pushButton_E.clicked.connect(self.reset_game)
self.board = chess.Board()
self.chessboard()
self.show_board()
self.show()
def chessboard(self):
self.pgn = self.board.epd()
self.final_list = []
self.printable_list = []
pieces = self.pgn.split(" ", 1)[0]
rows = pieces.split("/")
for row in rows:
self.initial_list = []
for chessmen in row:
if chessmen.isdigit():
for i in range(0, int(chessmen)):
self.initial_list.append(".")
else:
self.initial_list.append(chessmen)
self.final_list.append(self.initial_list)
def initiate_move(self):
for i in range(64):
self.Chess_Board[i].setStyleSheet("QPushButton {background-color : lightgreen ; color : lightgreen}")
if (i // 8) % 2 == 0:
if i % 2 == 0:
self.Chess_Board[i].setStyleSheet("QPushButton {background-color : white}")
if (i // 8) % 2 == 1:
if i % 2 == 1:
self.Chess_Board[i].setStyleSheet("QPushButton {background-color : white}")
self.value = self.sender()
self.move = self.value.text().upper()
if self.move == "":
pass
elif len(self.move) == 1:
if self.move == "P":
pass
else:
self.move_name += self.move
self.value.setStyleSheet("QPushButton {background-color : cyan}")
for i in range(64):
self.Chess_Board[i].clicked.connect(self.play_move)
def play_move(self):
self.textBrowser.setText("")
self.valid_move = True
self.button = self.sender()
if len(self.button.text()) == 1:
self.move_name += f"x{self.button.objectName()}"
if self.button.text() == "":
self.move_name += self.button.objectName()
# if self.lineEdit.isEnabled():
# self.move = self.lineEdit.text()
# elif self.lineEdit_2.isEnabled():
# self.move = self.lineEdit_2.text()
try:
self.board.push_san(self.move_name)
except Exception as ex:
self.textBrowser.setText(f"{ex}")
self.valid_move = False
if self.valid_move == True:
if self.lineEdit.isEnabled():
self.lineEdit.setDisabled(True)
self.lineEdit_2.setDisabled(False)
else:
self.lineEdit.setDisabled(False)
self.lineEdit_2.setDisabled(True)
elif self.valid_move == False:
self.move_name = ""
if self.board.is_checkmate():
self.textBrowser.setText("Checkmate !")
self.win = True
self.end_the_game()
if self.board.is_variant_draw():
self.textBrowser.setText("Draw !")
self.draw = True
self.end_the_game()
if self.board.is_stalemate():
self.textBrowser.setText("Stalemate !")
self.draw = True
self.end_the_game()
if self.board.is_fifty_moves():
self.textBrowser.setText("Draw by fifty-move rule !")
self.draw = True
self.end_the_game()
if self.board.is_repetition():
self.textBrowser.setText("Draw by three-fold repitition !")
self.draw = True
self.end_the_game()
self.chessboard()
self.show_board()
for i in range(64):
self.Chess_Board[i].clicked.connect(self.initiate_move)
self.move = ""
self.move_name = ""
def show_board(self):
for x in range(8):
for y in range(8):
if self.final_list[x][y].isupper():
self.Chess_Board[x*8+y].setText(f"{self.final_list[x][y]}".upper())
self.Chess_Board[x*8+y].setIcon(QtGui.QIcon(f"{self.final_list[x][y]}#.png"))
self.Chess_Board[x*8+y].setIconSize(QtCore.QSize(40,40))
elif self.final_list[x][y].islower():
self.Chess_Board[x*8+y].setText(f"{self.final_list[x][y]}".lower())
self.Chess_Board[x*8+y].setIcon(QtGui.QIcon(f"{self.final_list[x][y]}&.png"))
self.Chess_Board[x*8+y].setIconSize(QtCore.QSize(40,40))
elif self.final_list[x][y] == ".":
self.Chess_Board[x*8+y].setIcon(QtGui.QIcon())
self.Chess_Board[x*8+y].setText("")
def reset_game(self):
self.board.reset()
self.lineEdit.setText("")
self.lineEdit_2.setText("")
self.lineEdit.setDisabled(False)
self.lineEdit_2.setDisabled(True)
self.textBrowser.setText("")
self.chessboard()
self.show_board()
self.end_the_game()
def draw_game(self):
self.draw = True
self.end_the_game()
self.textBrowser.setText("It's a draw .")
def resignation(self):
if self.lineEdit.text() == self.lineEdit_2.text() == "":
self.textBrowser.setText("Match Aborted")
else:
if self.move == self.lineEdit.text():
self.win = True
self.end_the_game()
who_resigned = "Black"
elif self.move == self.lineEdit_2.text():
self.win = False
self.end_the_game()
who_resigned = "White"
self.textBrowser.setText(f"{who_resigned} resigned")
def save_game(self):
self.chesstable = tabulate.tabulate(self.final_list, tablefmt="grid", stralign="left")
with open("chess_save_list.txt","a") as writer:
writer.write(self.chesstable)
def end_the_game(self):
if self.win != self.draw:
self.pushButton_A.setDisabled(True)
self.pushButton_B.setDisabled(True)
self.pushButton_C.setDisabled(True)
self.win, self.draw = None, None
elif self.win == self.draw:
self.pushButton_A.setDisabled(False)
self.pushButton_B.setDisabled(False)
self.pushButton_C.setDisabled(False)
app = QtWidgets.QApplication(sys.argv)
window = Chess_App()
app.exec_()
The link to get the .ui link for the code : https://github.com/Vishal01-VKP/Python-Projects/blob/master/GUIs/Games/Chess%20-%20By%20VK/CHESS.ui
Thank you .
It is lagging over time because after each and every move you are connecting the clicked signal again for all 64 buttons in play_move and initiate_move.. connected signal's slot function will be executed in event loop, causing the lag in game
if you are connecting the signal again doesn't mean it will disconnect previous signal but rather add other signals, so after one or two moves each move will cause the 64 signals to connect again, after 5 to 6 moves it will have more than 5000 slots connected to each signal..
If the signal is connected to slot, it will remain connected until it is destroyed or explicitly disconnected..
So Modify your code accordingly so signals doesn't have to be connected again or Disconnect the signals before connecting them again(It could work but not recommended)
How to avoid the processing, if an unnecessary key is pressed? For Example, In my code, I use Right Arrow and Left Arrowkey in keyPressEvent to move from one label to another label. and I assign keys F5, Alt+ A and ctrl+alt+p as shortcuts for labels. By default to print the First label "Accounts" is selected. Now if I press any key (like u,v,a,alt,ctrl etc), I get a result "Accounts is selected" as much as time, I pressed anyother keys. If I press F5, Label Manufacture is selected/print, But now I press any key then I get a print message "Manufacture is selected"
My intention is, If I press right, left arrow keys or assigned Qkeysequence keys, then only I got a response(print selected Item), If I press any other keys, Avoid processing and I don't want to display anything. (Here, QKeySequence keys is a dynamic ones, not constant, it will differ form user to user)
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Dynamic_Widgets(QWidget):
def __init__(self):
super(). __init__()
self.setWindowTitle("Dynamic Widget")
self.vbox1 = QVBoxLayout()
self.vbox2 = QVBoxLayout()
self.hbox = QHBoxLayout()
self.hbox.addLayout(self.vbox1),self.hbox.addLayout(self.vbox2)
self.hbox.addSpacing(5),self.setLayout(self.hbox)
self.lbl_1 = QLabel("Accounts")
self.lbl_2 = QLabel("Inventory")
self.lbl_3 = QLabel("Manufacture")
self.lbl_1_sc = QLabel("Alt+A")
self.lbl_2_sc = QLabel("alt+ctrl+P")
self.lbl_3_sc = QLabel("F5")
self.store_sc = {}
self.item_list = []
self.item_list_sc = []
self.vbox1.addWidget(self.lbl_1)
self.vbox1.addWidget(self.lbl_2)
self.vbox1.addWidget(self.lbl_3)
self.item_list.append(self.lbl_1.text())
self.item_list.append(self.lbl_2.text())
self.item_list.append(self.lbl_3.text())
self.vbox2.addWidget(self.lbl_1_sc)
self.vbox2.addWidget(self.lbl_2_sc)
self.vbox2.addWidget(self.lbl_3_sc)
self.item_list_sc.append(self.lbl_1_sc.text())
self.item_list_sc.append(self.lbl_2_sc.text())
self.item_list_sc.append(self.lbl_3_sc.text())
self.active_part = 1
self.first_option = 0
self.first_option_result = ""
self.first_option_result = self.item_list[self.first_option]
self.func_final_result()
for count,item in enumerate(self.item_list):
self.store_sc[count] = QShortcut(QKeySequence(f'{(self.item_list_sc[count])}'),self)
self.store_sc[count].activated.connect(self.result)
def result(self):
shortcut = self.sender()
sc_key = QKeySequence(shortcut.key()).toString()
sc_key_index = self.item_list_sc.index(sc_key)
self.first_option = sc_key_index
self.first_option_result = self.item_list[sc_key_index]
self.func_final_result()
def keyPressEvent(self, event):
if self.active_part == 1:
if (event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter) and self.first_option == 0:
self.first_option = 0
if event.key() == Qt.Key_Right and self.first_option < len(self.item_list)-1:
self.first_option = self.first_option + 1
if event.key() == Qt.Key_Left and self.first_option > 0:
self.first_option = self.first_option - 1
if self.first_option != -1:
self.first_option_result = self.item_list[self.first_option]
self.func_final_result()
def func_final_result(self):
print(self.first_option_result, "is selected")
def main():
app = QApplication(sys.argv)
ex = Dynamic_Widgets()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If you only want to process specific keys, then you either check if the event key is in the list of "handled" keys and proceed with its processing, or just ignore the event if it's not.
Note that you must not compare the string representation of the key, but its QKeySequence instead.
class Dynamic_Widgets(QWidget):
def __init__(self):
# ...
self.item_list_sc.append(QKeySequence(self.lbl_1_sc.text()))
self.item_list_sc.append(QKeySequence(self.lbl_2_sc.text()))
self.item_list_sc.append(QKeySequence(self.lbl_3_sc.text()))
self.key_indexes = {}
for i, key in enumerate(self.item_list_sc):
shortcut = self.store_sc[i] = QShortcut(key, self)
shortcut.activated.connect(self.result)
self.key_indexes[key] = i
def result(self):
shortcut = self.sender()
sc_key_index = self.key_indexes.get(shortcut.key())
if sc_key_index is None:
return
self.first_option = sc_key_index
self.first_option_result = self.item_list[sc_key_index]
self.func_final_result()
def keyPressEvent(self, event):
key = event.key()
if key not in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Left, Qt.Key_Right):
return
if self.active_part == 1:
if key in (Qt.Key_Return, Qt.Key_Enter):
self.first_option = 0
elif (key == Qt.Key_Right
and self.first_option < len(self.item_list) - 1):
self.first_option += 1
elif key == Qt.Key_Left and self.first_option > 0:
self.first_option -= 1
if self.first_option >= 0:
self.first_option_result = self.item_list[self.first_option]
self.func_final_result()
Alternatively, use an if/elif/else block that always check only for the key, and eventually process the key according to the other conditions, otherwise return since no key match has been found to that point:
def keyPressEvent(self, event):
if self.active_part == 1:
key = event.key()
if key in (Qt.Key_Return, Qt.Key_Enter):
self.first_option = 0
elif key == Qt.Key_Right:
if self.first_option < len(self.item_list) - 1:
self.first_option += 1
elif key == Qt.Key_Left:
if self.first_option > 0:
self.first_option -= 1
else:
return
if self.first_option >= 0:
self.first_option_result = self.item_list[self.first_option]
self.func_final_result()
Note: for such situations, you must always use a QGridLayout, otherwise items might become not properly aligned under certain circumstances; eventually, consider QFormLayout. Also, avoid putting multiple function calls in a single line, there is absolutely no benefit in that, and it only makes your code cumbersome and difficult to read (and, thus, to debug).
So I found this beautiful module which provides the abbility to autocomplete combobox out of provided list of values.
Yet, it does not seem to work with <<ComboboxSelected>> (the values just do not appear) event in bind method, can it be fixed?
Module itelf is below:
"""
tkentrycomplete.py
A Tkinter widget that features autocompletion.
Created by Mitja Martini on 2008-11-29.
Updated by Russell Adams, 2011/01/24 to support Python 3 and Combobox.
Updated by Dominic Kexel to use Tkinter and ttk instead of tkinter and tkinter.ttk
Licensed same as original (not specified?), or public domain, whichever is less restrictive.
"""
import sys
import os
import tkinter
import tkinter.ttk
__version__ = "1.1"
# I may have broken the unicode...
Tkinter_umlauts=['odiaeresis', 'adiaeresis', 'udiaeresis', 'Odiaeresis', 'Adiaeresis', 'Udiaeresis', 'ssharp']
class AutocompleteEntry(tkinter.Entry):
"""
Subclass of Tkinter.Entry that features autocompletion.
To enable autocompletion use set_completion_list(list) to define
a list of possible strings to hit.
To cycle through hits use down and up arrow keys.
"""
def set_completion_list(self, completion_list):
self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list
self._hits = []
self._hit_index = 0
self.position = 0
self.bind('<KeyRelease>', self.handle_keyrelease)
def autocomplete(self, delta=0):
"""autocomplete the Entry, delta may be 0/1/-1 to cycle through possible hits"""
if delta: # need to delete selection otherwise we would fix the current position
self.delete(self.position, tkinter.END)
else: # set position to end so selection starts where textentry ended
self.position = len(self.get())
# collect hits
_hits = []
for element in self._completion_list:
if element.lower().startswith(self.get().lower()): # Match case-insensitively
_hits.append(element)
# if we have a new hit list, keep this in mind
if _hits != self._hits:
self._hit_index = 0
self._hits=_hits
# only allow cycling if we are in a known hit list
if _hits == self._hits and self._hits:
self._hit_index = (self._hit_index + delta) % len(self._hits)
# now finally perform the auto completion
if self._hits:
self.delete(0,tkinter.END)
self.insert(0,self._hits[self._hit_index])
self.select_range(self.position,tkinter.END)
def handle_keyrelease(self, event):
"""event handler for the keyrelease event on this widget"""
if event.keysym == "BackSpace":
self.delete(self.index(tkinter.INSERT), tkinter.END)
self.position = self.index(tkinter.END)
if event.keysym == "Left":
if self.position < self.index(tkinter.END): # delete the selection
self.delete(self.position, tkinter.END)
else:
self.position = self.position-1 # delete one character
self.delete(self.position, tkinter.END)
if event.keysym == "Right":
self.position = self.index(tkinter.END) # go to end (no selection)
if event.keysym == "Down":
self.autocomplete(1) # cycle to next hit
if event.keysym == "Up":
self.autocomplete(-1) # cycle to previous hit
if len(event.keysym) == 1 or event.keysym in Tkinter_umlauts:
self.autocomplete()
class AutocompleteCombobox(tkinter.ttk.Combobox):
def set_completion_list(self, completion_list):
"""Use our completion list as our drop down selection menu, arrows move through menu."""
self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list
self._hits = []
self._hit_index = 0
self.position = 0
self.bind('<KeyRelease>', self.handle_keyrelease)
self['values'] = self._completion_list # Setup our popup menu
def autocomplete(self, delta=0):
"""autocomplete the Combobox, delta may be 0/1/-1 to cycle through possible hits"""
if delta: # need to delete selection otherwise we would fix the current position
self.delete(self.position, tkinter.END)
else: # set position to end so selection starts where textentry ended
self.position = len(self.get())
# collect hits
_hits = []
for element in self._completion_list:
if element.lower().startswith(self.get().lower()): # Match case insensitively
_hits.append(element)
# if we have a new hit list, keep this in mind
if _hits != self._hits:
self._hit_index = 0
self._hits=_hits
# only allow cycling if we are in a known hit list
if _hits == self._hits and self._hits:
self._hit_index = (self._hit_index + delta) % len(self._hits)
# now finally perform the auto completion
if self._hits:
self.delete(0,tkinter.END)
self.insert(0,self._hits[self._hit_index])
self.select_range(self.position,tkinter.END)
def handle_keyrelease(self, event):
"""event handler for the keyrelease event on this widget"""
if event.keysym == "BackSpace":
self.delete(self.index(tkinter.INSERT), tkinter.END)
self.position = self.index(tkinter.END)
if event.keysym == "Left":
if self.position < self.index(tkinter.END): # delete the selection
self.delete(self.position, tkinter.END)
else:
self.position = self.position-1 # delete one character
self.delete(self.position,tkinter.END)
if event.keysym == "Right":
self.position = self.index(tkinter.END) # go to end (no selection)
if len(event.keysym) == 1:
self.autocomplete()
# No need for up/down, we'll jump to the popup
# list at the position of the autocompletion
def test(test_list):
"""Run a mini application to test the AutocompleteEntry Widget."""
root = tkinter.Tk(className=' AutocompleteEntry demo')
entry = AutocompleteEntry(root)
entry.set_completion_list(test_list)
entry.pack()
entry.focus_set()
combo = AutocompleteCombobox(root)
combo.set_completion_list(test_list)
combo.pack()
combo.focus_set()
# I used a tiling WM with no controls, added a shortcut to quit
root.bind('<Control-Q>', lambda event=None: root.destroy())
root.bind('<Control-q>', lambda event=None: root.destroy())
root.mainloop()
if __name__ == '__main__':
test_list = ('apple', 'banana', 'CranBerry', 'dogwood', 'alpha', 'Acorn', 'Anise' )
test(test_list)
Found solution, just rename class Autocompletion Combobox to Combobox
Change the first part of the auto complete class to this.
You need to use a StringVar to trace any changes. <<ComboboxSelected>> will only be raised when the value is selected.
class AutocompleteCombobox(tkinter.ttk.Combobox):
def set_completion_list(self, completion_list):
"""Use our completion list as our drop down selection menu, arrows move through menu."""
self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list
self._hits = []
self._hit_index = 0
self.position = 0
self.set_val=tkinter.StringVar()
self.bind('<KeyRelease>', self.handle_keyrelease)
self.set_val.trace('w',self.change_values)
self['textvariable']=self.set_val
print(completion_list)
self['values'] = self._completion_list # Setup our popup menu
def change_values(self,*event):
self['values'] = self._hits
I am adding QgraphicsPathItem to scene and want to erase particular portion of the Qgraphicspathitem. I am using QgraphicsrectItem as Eraser here.Both the items are colliding, but the QgraphicsPathItem is not getting erased.It remains the same in the scene.I am subtracting the two paths and adding it to the QGraphicspathItem
class GraphicsSceneClass(QGraphicsScene):
def __init__(self):
super().__init__()
self.setItemIndexMethod(QGraphicsScene.NoIndex)
self.setBackgroundBrush(QBrush(Qt.black))
self.setSceneRect(0,0,1000,1000)
self.horizontal=QHBoxLayout()
self.widget=QWidget()
self.drawrect=False
self.gridOn = 0
self.selectionMode = 1
self.targetForLine = QRect()
self.drawEnd = True
self.resizemode=0
self.eraser_item = None
self.erasing=False
self.rect=QGraphicsRectItem()
def mousePressEvent(self,event):
try:
sampleTransform = QTransform()
self.objectAtMouse = self.itemAt(event.scenePos(), sampleTransform)
if self.selectionMode == 2:
if self.drawEnd == True:
self.drawEnd = False
elif self.drawEnd == False:
self.drawEnd = True
self.drawLineObj = None
if self.drawEnd == False:
self.lineInsertPoint = self.TargPosForLine(event.scenePos(), "ForLine")
tempPainterPath = QPainterPath()
self.drawLineObj = QGraphicsPathItem()
self.drawLineObj.setPos(self.lineInsertPoint.x(), self.lineInsertPoint.y())
self.drawLineObj.setPen(QPen(Qt.NoPen))
self.drawLineObj.setBrush(QBrush(QColor(Qt.gray)))
self.drawLineObj.setPath(tempPainterPath)
self.addItem(self.drawLineObj)
self.drawLineObj.setFlag(QGraphicsItem.ItemIsSelectable)
if event.buttons() == Qt.RightButton and self.resizemode == 1:
self.erasing = True
self.rect.setRect(event.scenePos().x()-5, event.scenePos().y()-5, 10, 10)
print(event.pos().x(),event.pos().y())
self.addItem(self.rect)
self.rect.setPen(QColor(Qt.red))
except Exception as e:
print("Error in mousepress",e)
def mouseMoveEvent(self,event):
try:
if self.selectionMode == 2 and self.drawEnd == False and self.erasing == False:
f1 = QPainterPath()
gridPos = self.TargPosForLine(event.scenePos(), "ForLine")
f1.moveTo(0, 0)
f1.lineTo(0, 6)
f1.lineTo((gridPos.x() - self.lineInsertPoint.x()), 6)
f1.lineTo((gridPos.x() - self.lineInsertPoint.x()), 0)
f1.closeSubpath()
self.drawLineObj.setPath(f1)
self.drawLineObj.setPos(self.lineInsertPoint.x(), self.lineInsertPoint.y() + 5)
self.drawLineObj.setBrush(QBrush(QColor(Qt.gray)))
pen = QPen(QColor(Qt.gray))
pen.setWidth(0)
self.drawLineObj.setPen(pen)
self.drawLineObj._property = []
self.drawLineObj._angle = 0
if self.selectionMode == 2 and self.drawEnd == True:
self.targetForLine = self.TargPosForLine(event.scenePos(), "ForRect")
self.update()
if event.buttons() & Qt.RightButton and self.erasing:
print(event.scenePos().x(),event.scenePos().y())
self.rect.setRect(event.scenePos().x() - 5, event.scenePos().y() - 5,
10, 10)
for item in self.collidingItems(self.rect):
new = item.path()-(self.rect.shape())
item.setPath(new)
print('collided')
else:
self.targetForLine = QRect()
except Exception as e:
print("Error in mousemove",e)
def mouseReleaseEvent(self, event):
if self.drawrect==True or self.tLext==2 or self.tRext==2 or self.BLext==2 or self.BRext==2:
self.drawrect=False
self.tLext=0
self.tRext=0
self.BLext=0
self.BRext=0
QApplication.changeOverrideCursor(Qt.ArrowCursor)
QApplication.setOverrideCursor(Qt.ArrowCursor)
mainWindow.pointer.setChecked(False)
if self.rect != None:
self.removeItem(self.rect)
def TargPosForLine(self, position, mode):
try:
clicked_column = int((position.y() // 16)) * 16
clicked_row = int((position.x() // 16)) * 16
if clicked_column < 0:
clicked_column = 0
if clicked_row < 0:
clicked_row = 0
if (mode == "ForRect"):
return QRect(clicked_row, clicked_column, 16, 16)
elif (mode == "ForLine"):
return QPointF(clicked_row, clicked_column)
except Exception as e:
print("Error in TargPosForLine", e)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.scene = GraphicsSceneClass()
self.view=QGraphicsView(self.scene)
self.view.setMouseTracking(True)
self.view.setAcceptDrops(True)
self.view.setRenderHint(QPainter.HighQualityAntialiasing)
self.linePointerButton = QToolButton()
self.linePointerButton.setCheckable(True)
self.linePointerButton.setText("Drawline")
self.linePointerButton.setToolTip("Draw Track")
self.linePointerButton.clicked.connect(self.setPointerMode)
self.resizebutton = QToolButton()
self.resizebutton.setText("Resize")
self.resizebutton.setCheckable(True)
self.resizebutton.clicked.connect(self.resizepath)
self.widg=QWidget()
self.horizontal=QHBoxLayout()
self.horizontal.addWidget(self.view)
self.widg.setLayout(self.horizontal)
self.setCentralWidget(self.widg)
self.tool=self.addToolBar("Tool")
self.tool.addWidget(self.linePointerButton)
self.tool.addWidget(self.resizebutton)
def keyPressEvent(self, event):
try:
key = event.key()
if key == Qt.Key_Escape:
but = self.linePointerButton
self.scene.selectionMode = 1
but.setChecked(True)
except Exception:
print("Keypress is not working")
def setPointerMode(self):
try:
self.scene.selectionMode =2
except Exception:
print("Not able to change the selectionmode")
def resizepath(self):
self.scene.resizemode=1
self.scene.selectionMode = 1
self.linePointerButton.setChecked(False)
if __name__=="__main__":
import sys
app=QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
The path and the item and the shape from self.rect don't share the same coordinates system. So, when you want to remove the eraser from the line, the rect, you have to use a common coordinates system (for example, the one of you scene):
for item in self.collidingItems(self.rect):
new = item.mapToScene(item.path())-self.rect.mapToScene(self.rect.shape()) # translate the paths to the scene coords system
item.setPath(item.mapFromScene(new)) # translate the path from the scene coords system
What I would like to do is make an image viewer program for myself. I started experimenting with pygame and Tkinter but concluded that my best option would be to use pyglet.
What I'm trying to achieve is a fast JPEG viewer with just basic features. The most important feature would be to display images fast. I concluded some kind of image cache is needed to achieve this. Loading images into pyglet as Sprites works great for speed but I need to do this dynamically. A few images should be kept in memory ready for quick display.
While "old" images should be cleared out to prevent RAM and GPU memory from filling up. All this without interfering with the execution of the program and displaying of images that are already in RAM and/or GPU.
I chose pyglet because it has a better event loop system than PyGame. Doesn't consume so much CPU. I'm using python 2.7 and latest Pyglet from repository because it supports 64bit Python. What i have so far:
import pyglet
from pyglet.window import key
import sys
import glob
import os
def generateFileList(folder):
searchstring = folder + "\*.jpg"
flist = list()
for match in glob.iglob(searchstring):
if os.path.isfile(match):
flist.append(match)
return flist
class HelloWorldWindow(pyglet.window.Window):
def __init__(self):
super(HelloWorldWindow, self).__init__(fullscreen=True)
if len(sys.argv) > 1:
files = list(sys.argv[1:]) #exclude this scripts name
else:
print('Not enough arguments... terminating program.')
raise SystemExit(0)
folder = os.path.split(files[0])[0]
self.flist = generateFileList(folder)
self.loader = pyglet.resource.Loader(folder)
self.index = 0
self.ipreloadA = 1
self.ipreloadB = 0
self.sprite = False
self.createSprite(0)
def next(self):
if (len(self.flist) - 1) > self.index:
self.index += 1
self.setpreload()
self.createSprite(1)
def previews(self):
if self.index > 0:
self.index -= 1
self.setpreload()
self.createSprite(-1)
def setpreload(self):
if self.index == 0:
self.ipreloadA = 0
self.ipreloadB = 1
elif self.index == (len(self.flist) - 1):
self.ipreloadA = self.index - 1
self.ipreloadB = self.index
else:
self.ipreloadA = self.index - 1
self.ipreloadB = self.index + 1
def createSprite(self, direction):
if direction == 0:
self.image = pyglet.image.load(self.flist[self.index])
elif direction == -1:
self.image = self.imageA
elif direction == 1:
self.image = self.imageB
if self.sprite:
self.sprite.delete()
self.sprite = pyglet.sprite.Sprite(self.image)
self.preload()
def preload(self):
if not self.ipreloadA == self.index:
fName = os.path.basename(self.flist[self.ipreloadA])
self.imageA = self.loader.image(fName)
else:
self.imageA = self.image
if not self.ipreloadB == self.index:
fName = os.path.basename(self.flist[self.ipreloadB])
self.imageB = self.loader.image(fName)
else:
self.imageB = self.image
def on_draw(self):
self.clear()
if self.sprite:
self.sprite.draw()
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE:
pyglet.app.exit()
elif symbol == key.LEFT:
self.previews()
elif symbol == key.RIGHT:
self.next()
if __name__ == '__main__':
window = HelloWorldWindow()
pyglet.app.run()
What I've tried to achieve with above code was that the images get loaded first and then a sprite to be created just before displaying. Problem with the above code is that it still slows down. Seems to wait for images to load from HDD.
Here is a more basic version without my failed atempt:
import pyglet
from pyglet.window import key
import sys
import glob
import os
def generateFileList(folder):
searchstring = folder + "\*.jpg"
flist = list()
for match in glob.iglob(searchstring):
if os.path.isfile(match):
flist.append(match)
return flist
def createSprite(imagePath):
pass
class HelloWorldWindow(pyglet.window.Window):
def __init__(self):
super(HelloWorldWindow, self).__init__(fullscreen=True)
if len(sys.argv) > 1:
files = list(sys.argv[1:]) #exclude this scripts name
else:
print('Not enough arguments... terminating program.')
raise SystemExit(0)
folder = os.path.split(files[0])[0]
self.flist = generateFileList(folder)
self.index = 0
self.sprite = False
self.createSprite()
def next(self):
if (len(self.flist) - 1) > self.index:
self.index += 1
self.createSprite()
def previews(self):
if self.index > 0:
self.index -= 1
self.createSprite()
def createSprite(self):
self.image = pyglet.image.load(self.flist[self.index])
if self.sprite:
self.sprite.delete()
self.sprite = pyglet.sprite.Sprite(self.image)
def on_draw(self):
self.clear()
if self.sprite:
self.sprite.draw()
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE:
pyglet.app.exit()
elif symbol == key.LEFT:
self.previews()
elif symbol == key.RIGHT:
self.next()
if __name__ == '__main__':
window = HelloWorldWindow()
pyglet.app.run()
Also note that there is no image resizing here for simplicity.