I'm trying to put text into a QTextEdit in PyQT5, but every time I set the text for one text field it copies the same value into the other text fields on the same page. Even though I specified that it has to only change the contents of the QTextEdit field of which I have given the ID. Is this normal behavior, or is there a workaround? Any advice would be appreciated.
class NodeUIDScanner(QObject):
rfidTag = pyqtSignal(str)
scanEnabled = bool
def init(self):
self.scanEnabled = False
def run(self):
self.scanEnabled = True
dev = rfid_scanner.init('COM7') #change depending on usb port
while self.scanEnabled:
rfid = rfid_scanner.read(dev)
if rfid is None:
continue
if 'rfid' not in rfid:
continue
rfid = rfid['rfid']
self.scanEnabled = False
self.rfidTag.emit(rfid)
def stop(self):
self.scanEnabled = False
class Main:
def __init__(self):
self.main_win = QMainWindow()
self.ui = RFIDScannerDesign.Ui_MainWindow()
self.ui.setupUi(self.main_win)
#default page on load is the create page
self.ui.stackedWidget.setCurrentWidget(self.ui.pageCreate)
#nav buttons
self.ui.buttonCreate.clicked.connect(self.showCreate)
self.ui.buttonRead.clicked.connect(self.showRead)
self.ui.buttonUpdate.clicked.connect(self.showUpdate)
self.ui.buttonDelete.clicked.connect(self.showDelete)
self.ui.read_editButton.clicked.connect(self.showEdit)
#scan buttons
self.ui.create_buttonScan.clicked.connect(self.scanNodeUidCreate)
self.ui.read_buttonScan.clicked.connect(self.scanNodeUidRead)
self.ui.update_buttonScanOld.clicked.connect(self.scanNodeUidOld)
self.ui.update_buttonScanNew.clicked.connect(self.scanNodeUidNew)
self.ui.delete_buttonScan.clicked.connect(self.scanNodeUidDelete)
#submit button gives alert asking if you're sure you have the right input
self.ui.create_buttonSubmit.clicked.connect(self.alertCreate)
self.ui.edit_buttonSubmit.clicked.connect(self.alertEdit)
self.ui.update_buttonSubmit.clicked.connect(self.alertUpdate)
self.ui.delete_buttonSubmit.clicked.connect(self.alertDelete)
#yes or no buttons with alert. Yes sends info to db. No keeps you on the page
self.ui.create_buttonSubmitYes.clicked.connect(self.submitCreate)
self.ui.create_buttonSubmitNo.clicked.connect(self.hideCreateAlert)
self.ui.edit_buttonSubmitYes.clicked.connect(self.submitEdit)
self.ui.edit_buttonSubmitNo.clicked.connect(self.hideEditAlert)
self.ui.update_buttonSubmitYes.clicked.connect(self.submitUpdate)
self.ui.update_buttonSubmitNo.clicked.connect(self.hideUpdateAlert)
self.ui.delete_buttonSubmitYes.clicked.connect(self.submitDelete)
self.ui.delete_buttonSubmitNo.clicked.connect(self.hideDeleteAlert)
#define QThread here so it can be used
self.main_win.thread = QThread()
self.worker = NodeUIDScanner()
self.worker.moveToThread(self.main_win.thread)
self.main_win.thread.started.connect(self.worker.run)
def show(self):
self.main_win.show()
#Functions for nav buttons to show the right page
def showCreate(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageCreate)
self.ui.create_buttonSubmit.show()
self.ui.create_alert.hide()
self.ui.create_buttonSubmitYes.hide()
self.ui.create_buttonSubmitNo.hide()
def showRead(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageRead)
self.ui.read_editButton.show()
def showUpdate(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageUpdate)
self.ui.update_buttonSubmit.show()
self.ui.update_alert.hide()
self.ui.update_buttonSubmitYes.hide()
self.ui.update_buttonSubmitNo.hide()
def showDelete(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageDelete)
self.ui.delete_buttonSubmit.show()
self.ui.delete_alert.hide()
self.ui.delete_buttonSubmitYes.hide()
self.ui.delete_buttonSubmitNo.hide()
def showEdit(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageEdit)
self.ui.edit_buttonSubmit.show()
self.ui.edit_alert.hide()
self.ui.edit_buttonSubmitYes.hide()
self.ui.edit_buttonSubmitNo.hide()
#Threads for scanning nodeUIDs asynchronously
def readRFID(self, rfid):
try:
response_rfid = database.query(IndexName='rfid-index',
KeyConditionExpression=Key('rfid').eq(rfid))
if response_rfid['Count'] > 0:
print('RFID already in database', rfid)
print('Node uid:', int(response_rfid['Items'][0]['node_uid']))
return int(response_rfid['Items'][0]['node_uid'])
except Exception as e:
print(e)
def scanNodeUidCreate(self):
if self.ui.create_buttonScan.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeCreate)
self.main_win.thread.start()
def scanNodeUidRead(self):
if self.ui.read_buttonScan.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeRead)
self.main_win.thread.start()
def scanNodeUidOld(self):
if self.ui.update_buttonScanOld.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeOld)
self.main_win.thread.start()
def scanNodeUidNew(self):
if self.ui.update_buttonScanNew.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeNew)
self.main_win.thread.start()
def scanNodeUidDelete(self):
if self.ui.delete_buttonScan.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeDelete)
self.main_win.thread.start()
#Slot for passing signal from worker thread to keep it asynchronous
def displayNodeCreate(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageCreate:
self.ui.create_nodeUid.setText(str(self.readRFID(node)))
self.ui.create_buttonScan.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
def displayNodeRead(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageRead:
self.ui.read_nodeUid.setText(str(self.readRFID(node)))
self.ui.edit_nodeUid.setText(str(self.readRFID(node)))
self.ui.read_buttonScan.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
def displayNodeOld(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageUpdate:
self.ui.update_nodeUidOld.setText(str(self.readRFID(node)))
self.ui.update_buttonScanOld.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
def displayNodeNew(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageUpdate:
self.ui.update_nodeUidNew.setText(str(self.readRFID(node)))
self.ui.update_buttonScanNew.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
def displayNodeDelete(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageDelete:
self.ui.delete_nodeUid.setText(str(self.readRFID(node)))
self.ui.delete_buttonScan.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
#Submit button, when pressed gives alerts
def alertCreate(self):
self.ui.create_buttonSubmit.hide()
self.ui.create_alert.show()
self.ui.create_buttonSubmitYes.show()
self.ui.create_buttonSubmitNo.show()
def alertEdit(self):
self.ui.edit_buttonSubmit.hide()
self.ui.edit_alert.show()
self.ui.edit_buttonSubmitYes.show()
self.ui.edit_buttonSubmitNo.show()
def alertUpdate(self):
self.ui.update_buttonSubmit.hide()
self.ui.update_alert.show()
self.ui.update_buttonSubmitYes.show()
self.ui.update_buttonSubmitNo.show()
def alertDelete(self):
self.ui.delete_buttonSubmit.hide()
self.ui.delete_alert.show()
self.ui.delete_buttonSubmitYes.show()
self.ui.delete_buttonSubmitNo.show()
#TODO add db connection
def submitCreate(self):
self.showCreate()
self.ui.create_alertSucces.show()
self.fade(self.ui.create_alertSucces)
def submitEdit(self):
self.showRead()
self.ui.read_alertSucces.show()
self.fade(self.ui.read_alertSucces)
def submitUpdate(self):
self.showUpdate()
self.ui.update_alertSucces.show()
self.fade(self.ui.update_alertSucces)
def submitDelete(self):
self.showDelete()
self.ui.delete_alertSucces.show()
self.fade(self.ui.delete_alertSucces)
def hideCreateAlert(self):
self.ui.create_buttonSubmit.show()
self.ui.create_alert.hide()
self.ui.create_buttonSubmitYes.hide()
self.ui.create_buttonSubmitNo.hide()
def hideEditAlert(self):
self.ui.edit_buttonSubmit.show()
self.ui.edit_alert.hide()
self.ui.edit_buttonSubmitYes.hide()
self.ui.edit_buttonSubmitNo.hide()
def hideUpdateAlert(self):
self.ui.update_buttonSubmit.show()
self.ui.update_alert.hide()
self.ui.update_buttonSubmitYes.hide()
self.ui.update_buttonSubmitNo.hide()
def hideDeleteAlert(self):
self.ui.delete_buttonSubmit.show()
self.ui.delete_alert.hide()
self.ui.delete_buttonSubmitYes.hide()
self.ui.delete_buttonSubmitNo.hide()
#fade effect for animations on labels and alerts
def fade(self, widget):
self.effect = QGraphicsOpacityEffect()
widget.setGraphicsEffect(self.effect)
self.animation = QtCore.QPropertyAnimation(self.effect, b"opacity")
self.animation.setDuration(3000)
self.animation.setStartValue(1)
self.animation.setEndValue(0)
self.animation.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
main_win = Main()
main_win.show()
sys.exit(app.exec_())
The problem is that every time a "buttonScan" is clicked, you connect it to a slot. Signals can be connected to an indefinite number of functions, and it is also possible to connect a signal to the same function more than once.
Stopping the worker won't change anything, as the object still exists, and when the signal will be emitted every (previously) connected function will be called as well.
Since all functions do practically the same thing, a better choice is to connect the signal to a single function, and set the target widgets when switching between pages.
Note that there are other problems in your code, most importantly:
clicked is a signal, using if someObject.someSignal: is pointless as it will always be considered valid, and since the function is already connected to that signal there would be no point in checking it anyway;
calling wait() of an external thread in the main thread is wrong, since it is blocking; I won't address the issue here as it's off topic to the question;
Unfortunately, your code is quite convoluted, so I'm only providing modifications for a single page, then you have to implement the rest on your own.
class Main:
def __init__(self):
# ...
self.ui.create_buttonScan.clicked.connect(self.startScan)
# ...
self.worker.rfidTag.connect(self.rfidReceived)
def showCreate(self):
if self.ui.stackedWidget.currentWidget == self.ui.pageCreate:
return
# ...
self.worker.stop() # just to be sure...
self.targetField = self.ui.create_nodeUid
self.targetCheck = self.ui.create_buttonScan
def startScan(self):
self.worker.stop()
self.main_win.thread.start()
def rfidReceived(self, node):
self.targetField.setText(str(self.readRFID(node))
self.targetCheck.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
Note that more than half of your code is boilerplate just like the functions that update the text edits: they always do the same thing, just for different targets. This makes your code unnecessarily long and convoluted, so, prone to errors and annoying editing whenever you need to change its behavior. Since it seems clear that almost all your pages have the same interface (except for the read page) it's pointless to have functions that do the same thing repeated 5 times, especially considering that you're using very similar names that might be easily confused; each function is repeated for its 5 "targets" (the page and its buttons), that makes 30 functions, while you could just have 4 or 5. A better modularization and usage of classes will certainly be a smarter choice, will cut down your code by at least half the size, and will make debugging and maintenance much easier.
Related
I'm building a large, complicated program, one half of which involves a GUI, which I'm building using Tkinter.
Previous iterations of this GUI seemed to work as intended. However, in the latest version, when I try to run a demonstration (see the demo() function in the code below), my whole computer freezes, and my only option is to carry out a hard reset.
Does anyone have any ideas as to why this might be happening? Some points which might be useful:
If I run the code below with the line self.gui.mainloop() commented out, the desired window appears on the screen for long enough for the toaster message to be displayed, and then closes without any freezing.
The ArrivalsManagerDaughter and DeparturesManagerDaughter objects transmit data wirelessly to another device, but they shouldn't be doing anything, other than being initialised, in the code which is causing the freezing. I don't believe that these are the cause of the problem, although I could well be wrong.
Here's the whole Python file which I'm trying to run. I'm happy to post more code if requested.
"""
This code holds a class which manages transitions between the
"recommendations" and "custom placement" windows, and also oversees their
interactions with Erebus.
"""
# GUI imports.
from tkinter import *
# Non-standard imports.
import ptoaster
# Custom imports.
from erebus.arrivals_manager_daughter import ArrivalsManagerDaughter
from erebus.departures_manager_daughter import DeparturesManagerDaughter
# Local imports.
from charon.custom_placement_window import Custom_Placement_Window
from charon.recommendations_window import Recommendations_Window
# Local constants.
REFRESH_INTERVAL = 1000
# ^^^ in miliseconds ^^^
##############
# MAIN CLASS #
##############
class Comptroller:
""" The class in question. """
def __init__(self, welcome=False, delete_existing_ledger=False,
internal=False, diagnostics=False, path_to_icon=None):
self.close_requested = False
self.path_to_icon = path_to_icon
self.recommendations = dict()
self.arrivals_manager = ArrivalsManagerDaughter(self,
diagnostics=diagnostics)
self.departures_manager = DeparturesManagerDaughter(
delete_existing=delete_existing_ledger, internal=internal,
diagnostics=diagnostics)
self.gui = Tk()
self.top = Frame(self.gui)
self.window = Recommendations_Window(self)
self.is_on_recommendations_window = True
self.arrange()
if welcome:
print_welcome()
def add_recommendation(self, ticket, epc, column, row):
""" Add a recommendation to the dictionary. """
recommendation = dict()
recommendation["ticket"] = ticket
recommendation["epc"] = epc
recommendation["column"] = column
recommendation["row"] = row
self.recommendations[ticket] = recommendation
def remove_recommendation(self, ticket):
""" Delete a recommendation from the dictionary. """
del self.recommendations[ticket]
def get_top(self):
""" Return the top-level GUI object. """
return self.top
def arrange(self):
""" Arrange the widgets. """
self.window.get_top().pack()
self.top.pack()
def switch_to_custom_placement(self, ticket, epc):
""" Switch window from "Recommendations" to "Custom Placement". """
columns = self.arrivals_manager.columns
rows = self.arrivals_manager.rows
self.window.get_top().pack_forget()
self.window = Custom_Placement_Window(self, ticket, epc, columns,
rows)
self.window.get_top().pack()
self.is_on_recommendations_window = False
def switch_to_recommendations(self):
""" Switch window from "Custom Placement" to "Recommendations". """
self.window.get_top().pack_forget()
self.window = Recommendations_Window(self)
self.window.get_top().pack()
self.is_on_recommendations_window = True
def refresh(self):
""" Refresh the "recommendations" window, as necessary. """
if (self.is_on_recommendations_window and
self.arrivals_manager.clear_quanta()):
self.window.refresh_rec_table()
self.departures_manager.clear_backlog()
if self.close_requested:
self.kill_me()
else:
self.gui.after(REFRESH_INTERVAL, self.refresh)
def simulate_recommendation(self, ticket, epc, column, row):
""" Simulate receiving a transmission from the Pi. """
self.add_recommendation(ticket, epc, column, row)
self.window.refresh_rec_table()
def request_close(self):
self.close_requested = True
def run_me(self):
""" Run the "mainloop" method on the GUI object. """
self.gui.after(REFRESH_INTERVAL, self.refresh)
self.gui.title("Charon")
if self.path_to_icon:
self.gui.iconphoto(True, PhotoImage(file=self.path_to_icon))
self.gui.protocol("WM_DELETE_WINDOW", self.request_close)
self.gui.mainloop()
def kill_me(self):
""" Kill the mainloop process, and shut the window. """
self.gui.destroy()
####################
# HELPER FUNCTIONS #
####################
def print_welcome():
""" Print a welcome "toaster" message. """
message = ("Notifications about boxes leaving the coldstore will be "+
"posted here.")
ptoaster.notify("Welcome to Charon", message,
display_duration_in_ms=REFRESH_INTERVAL)
def print_exit(epc):
""" Print an exit "toaster" message. """
message = "Box with EPC "+epc+" has left the coldstore."
ptoaster.notify("Exit", message)
###########
# TESTING #
###########
def demo():
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
###################
# RUN AND WRAP UP #
###################
def run():
demo()
if __name__ == "__main__":
run()
FOUND THE PROBLEM: the culprit was actually calling a ptoaster function from within a Tkinter GUI. Which leads me on to my next question: Is it possible to combine ptoaster and Tkinter in an elegant fashion, and, if so, how?
The problem ocurred when calling print_welcome(), which in turn calls one of the ptoaster functions. It seems that Tkinter and ptoaster do not play together nicely. Removing the reference to print_welcome() from the Comptroller class put a stop to any freezing.
(On a side note: I'd be very grateful to anyone who could suggest an elegant method of combing ptoaster with Tkinter.)
Try changing
def demo():
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
###################
# RUN AND WRAP UP #
###################
def run():
demo()
if __name__ == "__main__":
run()
To simply
if __name__ == "__main__":
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
As to why this might happening, since you're creating an object by instantiating Comptroller inside a regular function demo, the object is not being "retained" after the demo exits.
EDIT
If you would like to still maintain demo and run you could create a class Demo and store the instance globally.
Or maybe a simple global variable inside demo to retain a "reference" to the instance Comptroller.
I need to call a member function when both of my QPushButtons have been pressed. I cannot find a way to keep track of if they have been pressed.
I call a function when either of the buttons are clicked using ...clicked.connect(func) and within that function I have tried to: 1) return a value, 2) update a member variable. Below I have shown how I tried to create and update member variable and use a conditional to see if both variables were true so that I could call my next function.
def __init__(self, parent=None):
super(MorphingApp, self).__init__(parent)
self.setupUi(self)
self.startIm = None
self.endIm = None
self.initialState()
def initialState(self):
self.btn_loadStart.clicked.connect(self.loadImageS)
self.btn_loadEnd.clicked.connect(self.loadImageE)
if(self.startIm is True and self.endIm is True):
self.loadedState()
def initialState(self):
self.startIm = True
def loadImageE(self):
self.endIm = True
My functions of course do things, but I removed parts that were irrelevant. When I run the GUI I am able to load the images but the function that is supposed to be called after both buttons have been pushed is not called. I know this because the state of the GUI is not changing as I intend.
This is my first time posting a question so let me know how to improve :)
Use a simple True/False flag to check if the button has been pressed at least once. Both buttons start with the flag set to False, so we can set that up in the init method.
Then place the code that checks to see if both buttons have been pressed inside the functions connected to them. Finally, simply call the respective "final" function if the check passes.
This is an example using two generic buttons:
def __init__(self, parent=None):
super(MorphingApp, self).__init__(parent)
self.btn_01_pressed = False
self.btn_02_pressed = False
self.set_buttons()
def set_buttons(self):
self.btn_01.clicked.connect(self.check01)
self.btn_02.clicked.connect(self.check02)
def check01(self):
self.btn_01_pressed = True
if self.btn_01_pressed is True and self.btn_02_pressed is True:
self.call_final_function()
def check02(self):
self.btn_02_pressed = True
if self.btn_01_pressed is True and self.btn_02_pressed is True:
self.call_final_function()
def call_final_function(self):
# do something great here
Like in your post I've ommited some parts necessary for the actual code (like creating the QPushButton widgets), but hopefully you get the idea.
def __init__(self, parent=None):
super(MorphingApp, self).__init__(parent)
self.setupUi(self)
self.startIm = False
self.endIm = False
self.btn_loadStart.clicked.connect(self.loadImageS)
self.btn_loadEnd.clicked.connect(self.loadImageE)
def loadImageS(self):
self.startIm = True
if self.startIm and self.endIm:
self.loadedState()
def loadImageE(self):
self.endIm = True
if self.startIm and self.endIm:
self.loadedState()
I'm building an app based on the Horizontal Menu example from Urwid.
I have an item that I want to show some information. I have an "OK" button underneath, and I want it to either pop the menu back to the previous state, or maybe restart the entire screen/loop.
In the example, they use ExitMainLoop(), but I don't want to exit - I just want to restart it.
So, I've changed the callback to a different function - but all my attempts either do nothing or crash my program.
Here's the relevant bits:
Starting the menu:
if __name__ == "__main__":
top = HorizontalBoxes()
top.open_box(menu_top([]).menu)
urwid.MainLoop(urwid.Filler(top, 'middle', 40), palette).run()
Relevant menu class/functions. My problem is - what goes in the does_nothing function?
class Choice(urwid.WidgetWrap):
def __init__(self, caption, pid):
super(Choice, self).__init__(
MenuButton(caption, partial(self.item_chosen,pid)))
self.caption = caption
def item_chosen(self, pid, button):
if self.caption == (u"System status"):
showSystemStatus()
def showSystemStatus():
response = urwid.Text( "... some text ")
done = MenuButton(u'OK', does_nothing)
response_box = urwid.Filler(urwid.Pile([response,done]))
top.open_box(urwid.AttrMap(response_box, 'options'))
def does_nothing(key):
????????????????????
return
I found a solution!
I had to add a new method to the HorizontalBoxes class. Specifically, the close_box method:
class HorizontalBoxes(urwid.Columns):
def __init__(self):
super(HorizontalBoxes, self).__init__([], dividechars=1)
def open_box(self, box):
if self.contents:
del self.contents[self.focus_position + 1:]
self.contents.append((urwid.AttrMap(box, 'options', focus_map),
self.options('given', 40)))
self.focus_position = len(self.contents) - 1
def close_box(self):
if self.contents:
del self.contents[self.focus_position :]
self.focus_position = len(self.contents) - 1
And then, it was a simple matter of calling the close_box method from my does_nothing function from before:
def does_nothing(key):
top.close_box()
return
I have issue with GTK's TreeView with ListStore. Records are updated, but sometime only when I hover it.
Bigger problem are new records - It's like it stops to displaying new ones unless I scroll to bottom all the time - which is weird.
I use Glade.
My code (slightly simplified)
class UI(SampleUI):
gladefile = 'ui.glade'
iterdict = {}
def __init__(self, module):
super().__init__(module)
def start(self):
self.fetch_widgets()
self.connect_events()
self.window.show()
def fetch_widgets(self):
self.window = self.builder.get_object('window')
self.liststore = self.builder.get_object('store')
def connect_events(self):
handlers = {
"on_window_close" : Gtk.main_quit,
"on_start_working": self.start_working,
"on_stop_working": self.stop_working
}
self.builder.connect_signals(handlers)
self.module.url_pending_action = self.url_pending
self.module.url_succeed_action = self.url_update
def start_working(self, button):
self.module.start()
def stop_stop(self, button):
self.module.stop()
def url_pending(self, data):
self.iterdict[data['url']] = self.liststore.append([0, data['url'], 0, '', data['text']])
def url_update(self, data):
_iter = self.iterdict[data['url']]
self.liststore[_iter][1] = data['some_field1']
self.liststore[_iter][2] = data['some_field2']
Methods self.url_pending and self.url_update are called by threads (at most 30 running at the same time) created in self.module
I checked and new records are correctly appended into ListStore - I can read data from it. Window is working fine, but there are no new items at the bottom.
Ideas?
Ok, I made research and I figured out that GTK don't like being called from outside of main thread.
There was methods in Gdk to lock GTK calls
Gtk.threads_enter()
...
Gtk.threads_leave()
But now it's deprecated and GTK documentation says that every GTK call should be in main thread.
My workaround:
# [...]
def start(self):
# [...]
GObject.timeout_add(100, self.query_do)
# [...]
def query_do(self):
while len(self.gtk_calls):
self.gtk_calls.pop()()
GObject.timeout_add(100, self.query_do)
And I'm just adding into query
self.gtk_calls.insert(0, lambda: anything())
The code isn't as clear as it was before, but works perfect.
I'm coding a python script using several commanline tools like top, so i need a proper visual feedback. Now it is time to give it a menu, so here comes the problem.
I found here a great approach of what i need, but every try to display a feedback before come back to previous menu is futile.
I just need menus, submenus, launch commands, terminate it, and back to previous menu. a GREAT bonus would be to run them in a split of the term.
Is there any pattern/skeleton/stuff/whatever to use as template in order to display several kind of widget with a predictable output?
here is a example of code,which two examples of functions to run:
#!/usr/bin/env python2
import curses
from curses import panel
class Menu(object):
def __init__(self, items, stdscreen):
self.window = stdscreen.subwin(0,0)
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(1+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()
######################################################### !#
######################################################### !#
############# HERE MY FUNCTIONS examples
############ Everithing works OK, but displays it awfully
def GetPid(name):
import subprocess
command= str(("""pgrep %s""") % name )
p = subprocess.Popen(command, shell = True, stdout = subprocess.PIPE)
procs = []
salida = p.stdout
for line in salida:
procs.append(str.strip(line))
return procs
def top():
os.system("top")
def menuGP():
print GetPid("top")
######################################################### !#
class MyApp(object):
def __init__(self, stdscreen):
self.screen = stdscreen
curses.curs_set(0)
submenu_items = [
('beep', curses.beep),
('top', top)
]
submenu = Menu(submenu_items, self.screen)
main_menu_items = [
('get PID', GetPid),
('submenu', submenu.display)
]
main_menu = Menu(main_menu_items, self.screen)
main_menu.display()
if __name__ == '__main__':
curses.wrapper(MyApp)
Thanks in advise (and sorry for my rough english)
You really have two choices. One you can leave curses mode, execute your program, then resume curses. Two, you can execute your program asynchronously, parse its output and write it to the screen.
The good news on the first option is that you don't actually need to write any fancy save_state / load_state methods for the ui. Curses does this for you. Here's a simple example to show my point
import curses, time, subprocess
class suspend_curses():
"""Context Manager to temporarily leave curses mode"""
def __enter__(self):
curses.endwin()
def __exit__(self, exc_type, exc_val, tb):
newscr = curses.initscr()
newscr.addstr('Newscreen is %s\n' % newscr)
newscr.refresh()
curses.doupdate()
def main(stdscr):
stdscr.addstr('Stdscreen is %s\n' % stdscr)
stdscr.refresh()
time.sleep(1)
with suspend_curses():
subprocess.call(['ls'])
time.sleep(1)
stdscr.refresh()
time.sleep(5)
curses.wrapper(main)
If you run the example, you will notice that the screen created by curses.wrapper and the one created in curses.initscr when resuming are the same object. That is, the window returned by curses.initscr is a singleton. This lets us exit curses and resume like above without having to update each widget's self.screen references each time.
The second option is much more involved but also much more flexible. The following is just to represent the basic idea.
class procWidget():
def __init__(self, stdscr):
# make subwindow / panel
self.proc = subprocess.Popen(my_args, stdout=subprocess.PIPE)
def update(self):
data = self.proc.stdout.readline()
# parse data as necessary
# call addstr() and refresh()
Then somewhere in your program you will want to call update on all your procWidgets on a timer. This gives you the option of making your subwindow any size/place so you can have as many procWidgets as will fit. You will have to add some handling for when the process terminates and other similar events of course.