I am running Ubuntu 20.4 w/ Python 3.8.2 and coming across a strange behavior with the readline interface in Python.
My target is to provide an input facility that allows at a given location on the terminal to choose from a list of given strings, or - alternatively - simply inputting a desired string. My code does work (almost), until for some reasons for certain strings the result gets corrupted.
My code looks like this
import readline
class __readlineCompleter(object):
"""
Cycles through a list of potential strings the user might want to enter
"""
def __init__(self, options):
self.options = list(options) #sorted([item for item in options])
self.index = 0
return
def complete(self, text, state):
if state == 0:
if text and not text in self.options:
self.options.append(text)
self.index += 1
if self.index >= len(self.options):
self.index = 0
return self.options[self.index]
else:
return None
def input_at(x_pos, y_pos, _text, _prefill, _choices):
"""
positions the cursor at x_pos, y_pos, prints text if given and reads a string from stdin
#param x_pos(int): row to print at
#param y_pos(int): columns to print at
#param _text(string): text to print before the input field
#param _prefill(string): text as default input
#param _choices(list): list of strings as proposed input values
#return: (string): input given by user, or False
"""
_myPrompt = "\033[{};{}H{}\033[0m".format(x_pos, y_pos, _text)
if type(_choices) in (set, list) and len(_choices):
_choices = set([x for x in _choices])
readline.set_completer(__readlineCompleter(_choices).complete)
readline.set_completer_delims('')
readline.parse_and_bind('tab: menu-complete')
try:
_input = input(_myPrompt)
except KeyboardInterrupt as EOFError:
_input = False
return _input
As I said this works pretty well, once pressing TAB the user gets presented a different option from _choices, pressing Enter returns the chosen entry. Alternatively the user can enter a string manually. So this was what I intended, until it comes to certain strings; so when I call above code like this
_temp = [ 'Julius Papp', 'J‐Live', 'Mr. Electric Triangle', 'MullerAnustus Maximilian']
_result = input_at(15, 0, "Name: ", _temp[0], _temp)
The cli looks like this (after each line I pressed Tab and the next line shows the output)
Name:
Name: Julius Papp
Name: J-Live
Name: Mr. Electric Triangle
Name: MullerAnustus Maximilian
Name: MullerAnustJulius Papp
***********
(I inserted the stars in the latter output to show the "wrong" display)
So somehow the final attempt destroyed the linebuffer; any subsequent Tab works as expected. Nevertheless the resulting _input is a valid member of _choices, so this only seems to be a displaying issue.
Does anyone have an idea why the string MullerAnustus Maximilian behaves this strange?
I solved this issue myself, by using the rl 2.4 – GNU Readline Bindings library.
Related
I have a simple form that I have built using tkinter. One of my widgets is a text option menu that allows the user to select 1 of 4 corporate or entity types. The widget straight after that requires the corporate or entity registration number
Logically if the user selects "Sole Proprietor", there is no reg number required as Sole Proprietors don't have reg numbers, so if this the case and the user selects "Sole Proprietor", I want the reg number widget to disappear.
So what I do is trace the OptionMenu Variable and if and pass it to the call back as can be seen below:
# Organisation Type (Drop List)
CurrentRowXAlignment += 3
OrgTypeLabel = tk.Label(SetUpWindow, text="Organisation Type:", font=('Helvetica', 11, 'bold'))
OrgTypeLabel.place(x=ColumnXAlignment1, y=CurrentRowXAlignment, anchor=LabelAnchors)
OrgType = tk.StringVar(SetUpWindow)
OrgType.set(client_data_list[2])
OrgTypeEntry = tk.OptionMenu(SetUpWindow, OrgType, "Private Company (\"(Pty) Ltd\")",
"Public Company (\"Limited\")",
"Closed Corporation (\"cc\")", "Sole Proprietor")
OrgTypeEntry.place(x=ColumnXAlignment2 - 2, y=CurrentRowXAlignment - 3, anchor=EntryBoxAnchors)
OrgTypeEntry.bind("<Tab>",MovetoNextField)
OrgType.trace("w", lambda *args, org_type=OrgType.get(): ShowRegNumber(org_type, *args))
CurrentRowXAlignment += RowGap
The call back function is as follows:
def ShowRegNumber(org_type, *args):
if org_type == "Sole Proprietor":
CoRegNumLabel.forget()
CoRegNumEntry.forget()
else:
pass
For some reason the org-type is not passing - I have tried to debug and it keeps passing '' and therefore is keeps going to the "else" no matter what option on the menu is selected.
Anyone have an idea of what I am doing wrong?
When you use in lambda
org_type=OrgType.get()
then it gets value OrgType.get() only once - at start - and it assign this value.
You could use
lambda *args: ShowRegNumber( OrgType.get(), *args)
but more readable is to use it in function
def ShowRegNumber(*args):
org_type = OrgType.get()
but even better is to use
tk.OptionMenu(..., command=ShowRegNumber)
and then you get selected value as the only argument in ShowRegNumber
def ShowRegNumber(org_type):
Minimal working example
import tkinter as tk
# --- functions ---
def show_reg_number(selected):
print(selected)
# --- main ---
setup_window = tk.Tk()
client_data_list = [
'Private Company ("(Pty) Ltd")',
'Public Company ("Limited")',
'Closed Corporation ("cc")',
'Sole Proprietor',
]
org_type = tk.StringVar(setup_window)
org_type.set(client_data_list[2])
org_type_entry = tk.OptionMenu(setup_window, org_type, *client_data_list, command=show_reg_number)
org_type_entry.pack()
setup_window.mainloop()
BTW:
I used lower case names for variables and function because of PEP 8 -- Style Guide for Python Code
I have a python tkinter application that contains a ttk.treeview widget.
The widget displays a list of files found on a specific directory tree with a specific extension - this was trivial to build with tt.treeview widget.
There is a request to enable "on-the-fly" filtering of the tree - e.g., the user types in an Entry some string, and as he/she types, the tree removes elements that don't match the typed string so far.
I was exploring the Treeview documentation, tried the detach and reattach methods but with no luck.
detach indeed removes the non-matched elements from the tree, but if the user hit Backspace, I can no longer iterate correctly on the tree to restore those detached elements as get_children method will not return them.
def filter_tree(self):
search_by = self.search_entry.get()
self.tree_detach_leaf_by_regex(self.current_loaded_folder, search_by, "")
def tree_detach_leaf_by_regex(self, root, regex, parent):
if self.treeview.get_children(root):
for child in self.treeview.get_children(root):
self.tree_detach_leaf_by_regex(child, regex, root)
else:
if not re.match(regex, self.treeview.item(root)["text"]):
self.elements_index_within_parent[root] = self.treeview.index(root)
self.elements_parents[parent] = 1
self.treeview.detach(root)
else:
self.treeview.reattach(root, parent, self.elements_index_within_parent[root])
Looking forward to read your advice.
To make my answer reusable by anybody, I have to tell more than directly answering your question. If you directly want to see how I do to get detached items (thus without using the method get_children which cannot get detached items' id), jump to the definition of the method whose name is _columns_searcher.
Introduction
Let's first define some attributes.
#property
def _to_search(self):
key = 'to_search'
if key not in self._cache:
self._cache[key] = tk.StringVar()
return self._cache[key]
def _set_search_entry(self):
ent = ttk.Entry(
self.root, # or canvas, or frame ...
#...
textvariable=self._to_search
)
ent.grid(
#...
)
ent.bind(
'<Return>',
self._columns_searcher
)
return ent
#property
def search_entry(self):
key = 'search_entry'
if key not in self._cache:
self._cache[key] = self._set_search_entry()
return self._cache[key]
Core answer
What follows is the part which directly show how to re-attach user-detached items. First note that, as the OP mentions, get_children only return ids of attached items. Second note that the only thing you need to re-attach detached items is their id. Which implies thus to trace/save them when they are detached so as to be able to re-attach them.
_detached = set()
def _columns_searcher(self, event):
# originally a set returns a tuple
children = list(self._detached) + list(self.tree.get_children())
self._detached = set()
query = self._to_search.get()
self._brut_searcher(children, query.lower())
Note that children above contains all items, be them detached.
def _brut_searcher(self, children, query):
i_r = -1
for item_id in children:
text = self.tree.item(item_id)['text'] # already contains the strin-concatenation (over columns) of the row's values
if query in text:
i_r += 1
self.tree.reattach(item_id, '', i_r)
else:
self._detached.add(item_id)
self.tree.detach(item_id)
From what I see, detach is almost same as delete.
row is gone and you have no access to it.
You have to make a copy of "detached" items, just id, name or more, if you have advance treeview structure, and then go over elements in both lists and sort it out.
Difference is that if you detach item and check it's id with "exists" function, it should return true
I have a dialog window containing check-boxes, when each of them is checked a particular class needs to be instantiated and a run a a task on a separated thread (one for each check box). I have 14 check-boxes to check the .isChecked() property and is comprehensible checking the returned Boolean for each of them is not efficient and requires a lot more coding.
Hence I decided to get all the children items corresponding to check-box element, get just those that are checked, appending their names to list and loop through them matching their name to d dictionary which key is the name of the check box and the value is the corresponding class to instantiate.
EXAMPLE:
# class dictionary
self.summary_runnables = {'dupStreetCheckBox': [DupStreetDesc(),0],
'notStreetEsuCheckBox': [StreetsNoEsuDesc(),1],
'notType3CheckBox': [Type3Desc(False),2],
'incFootPathCheckBox': [Type3Desc(True),2],
'dupEsuRefCheckBox': [DupEsuRef(True),3],
'notEsuStreetCheckBox': [NoLinkEsuStreets(),4],
'invCrossRefCheckBox': [InvalidCrossReferences()],
'startEndCheckBox': [CheckStartEnd(tol=10),8],
'tinyEsuCheckBox': [CheckTinyEsus("esu",1)],
'notMaintReinsCheckBox': [CheckMaintReins()],
'asdStartEndCheckBox': [CheckAsdCoords()],
'notMaintPolysCheckBox': [MaintNoPoly(),16],
'notPolysMaintCheckBox': [PolyNoMaint()],
'tinyPolysCheckBox': [CheckTinyEsus("rd_poly",1)]}
# looping through list
self.long_task = QThreadPool(None).globalInstance()
self.long_task.setMaxThreadCount(1)
start_report = StartReport(val_file_path)
end_report = EndReport()
# start_report.setAutoDelete(False)
# end_report.setAutoDelete(False)
end_report.signals.result.connect(self.log_progress)
end_report.signals.finished.connect(self.show_finished)
# end_report.setAutoDelete(False)
start_report.signals.result.connect(self.log_progress)
self.long_task.start(start_report)
# print str(self.check_boxes_names)
for check_box_name in self.check_boxes_names:
run_class = self.summary_runnables[check_box_name]
if run_class[0].__class__.__name__ is 'CheckStartEnd':
run_class[0].tolerance = tolerance
runnable = run_class[0]()
runnable.signals.result.connect(self.log_progress)
self.long_task.start(runnable)
self.long_task.start(end_report)
example of a runnable (even if some of them use different global functions)
I can't post the global functions that write content to file as they are too many and not all 14 tasks execute the same type function. arguments of these functions are int keys to other dictionaries that contain the report static content and the SQL queries to return report main dynamic contents.
class StartReport(QRunnable):
def __init__(self, file_path):
super(StartReport,self).__init__()
# open the db connection in thread
db.open()
self.signals = GeneralSignals()
# self.simple_signal = SimpleSignal()
# print self.signals.result
self.file_path = file_path
self.task = "Starting Report"
self.progress = 1
self.org_name = org_name
self.user = user
self.report_title = "Validation Report"
print "instantiation of start report "
def run(self):
self.signals.result.emit(self.task, self.progress)
if self.file_path is None:
print "I started and found file none "
return
else:
global report_file
# create the file and prints the header
report_file = open(self.file_path, 'wb')
report_file.write(str(self.report_title) + ' for {0} \n'.format(self.org_name))
report_file.write('Created on : {0} at {1} By : {2} \n'.format(datetime.today().strftime("%d/%m/%Y"),
datetime.now().strftime("%H:%M"),
str(self.user)))
report_file.write(
"------------------------------------------------------------------------------------------ \n \n \n \n")
report_file.flush()
os.fsync(report_file.fileno())
class EndReport(QRunnable):
def __init__(self):
super(EndReport,self).__init__()
self.signals = GeneralSignals()
self.task = "Finishing report"
self.progress = 100
def run(self):
self.signals.result.emit(self.task, self.progress)
if report_file is not None:
# write footer and close file
report_file.write("\n \n \n")
report_file.write("---------- End of Report -----------")
report_file.flush()
os.fsync(report_file.fileno())
report_file.close()
self.signals.finished.emit()
# TODO: checking whether opening a db connection in thread might affect the db on the GUI
# if db.isOpen():
# db.close()
else:
return
class DupStreetDesc(QRunnable):
"""
duplicate street description report section creation
:return: void if the report is to text
list[string] if the report is to screen
"""
def __init__(self):
super(DupStreetDesc,self).__init__()
self.signals = GeneralSignals()
self.task = "Checking duplicate street descriptions..."
self.progress = 16.6
def run(self):
self.signals.result.emit(self.task,self.progress)
if report_file is None:
print "report file is none "
# items_list = write_content(0, 0, 0, 0)
# for item in items_list:
# self.signals.list.emit(item)
else:
write_content(0, 0, 0, 0)
Now, I used this approach before and it has always worked fine without using multiprocessing. In this case it works good to some extent, I can run the tasks the first time but if I try to run for the second time I get the following Python Error :
self.long_task.start(run_class[0])
RuntimeError: wrapped C/C++ object of type DupStreetDesc has been deleted
I tried to use run_class[0].setAutoDelete(False) before running them in the loop but pyQt crashes with a minidump error (I am running the code in QGIS) and I the programs exists with few chances to understand what has happened.
On the other hand, if I run my classes separately, checking with an if else statement each check-box, then it works fine, I can run the tasks again and the C++ classes are not deleted, but it isn't a nice coding approach, at least from my very little experience.
Is there anyone else out there who can advise a different approach in order to make this run smoothly without using too many lines of code? Or knows whether there is a more efficient pattern to handle this problem, which I think must be quite common?
It seems that you should create a new instance of each runnable, and allow Qt to automatically delete it. So your dictionary entries could look like this:
'dupStreetCheckBox': [lambda: DupStreetDesc(), 0],
and then you can do:
for check_box_name in self.check_boxes_names:
run_class = self.summary_runnables[check_box_name]
runnable = run_class[0]()
runnable.signals.result.connect(self.log_progress)
self.long_task.start(runnable)
I don't know why setAutoDelete does not work (assuming you are calling it before starting the threadpool). I suppose there might be a bug, but it's impossible to be sure without having a fully-working example to test.
Edit:
I am passing input through using the cmd module (currently testing it by typing take bat), I'm very new to python too so I apologise if that is not what people were asking in the answers. After reading another answer elsewhere on this site I think the error may mean something like, the input is a string, not the object of the same name. If that is the case then is there any way I can define whatever they put as an argument for "take" as the object that matches? I am not sure how to ask that question or even if I'm using the right terminology to search for it but if someone is able to confirm that that is my problem and point me in the general direction of the answer to my second question I would gladly look myself.
The following code is split over four or so files, just to make it easier for myself to read, and it does work (just in case showing it like this makes it look like there are more problems than there are). Right now the only part I hit an error with is the do_take command
import cmd
import json
def get_room(id):
ret = None
with open(str(id)+".json", "r") as new:
jsontext = new.read()
current = json.loads(jsontext)
current["id"] = id
ret = Room(**current)
return ret
class Game(cmd.Cmd):
def __init__(self):
cmd.Cmd.__init__(self)
self.loc = get_room(1)
def do_take(self, item_name):
"""This is my take command, to be followed by an item name, which is giving me the error"""
if item_name.location == self.loc.id:
item_name.location = "backpack"
print(item_name.location)
class Item(object):
"""This is the basic class that all items will inherit from"""
def __init__(self, itemname, defence, attack, weight, location, hidden):
self.itemname = itemname
self.defence = defence
self.attack = attack
self.weight = weight
self.location = location
self.hidden = hidden
bat = Item("Baseball Bat", 1, 4, 1, 1, False)
I am typing "take bat", which triggers do_take(etc...) to try and get the location of the item, check it against my current location (defined earlier) and then if matches, change the location of the item to "backpack"
the locations match, and when I print(item_name.location) I get 1 as the result, but when I try and match it I get AttributeError: 'str' object has no attribute 'location'
Any help working this out would be greatly appreciated, and this is my first post on here so I apologise if I have put something in wrong.
After re-wording the question I managed to find another similar to mine which had the answer I needed.
Convert String to Object name
I changed my code from:
def do_take(self, item_name):
if item_name.location == self.loc.id:
item_name.location = "backpack"
print(item_name.location)
To:
def do_take(self, item_name):
if Item_List[item_name].location == self.loc.id:
Item_List[item_name].location = "backpack"
Item_List = {"bat": bat, "hubcap": hubcap}
And now the problem is fixed. Thank-you for your help.
I'm using Zelle Graphics library and I'm having trouble replacing graphics objects (which, in this case, happens to be text objects).
Here's the code:
from Graphics import *
winName = "Window"
win = Window(winName,600,500)
win.setBackground(Color('silver'))
title = Text((300,20),"Zack's Flash Card Maker")
title.draw(win)
p1 = Rectangle((50, 100),(550,400))
p1.setFill(Color("black"))
p1.draw(win)
class FlashCard:
def __init__(self):
self.commands = {'addQuestion':self.addQuestion,'startGame':self.startGame}
self.stack = []
self.questions = {}
self.questionAnswered = False
self.questionsCorrect = 0
self.questionsIncorrect = 0
def addQuestion(self):
question = ' '.join(self.stack)
self.stack = []
answer = input(question)
self.questions[question] = answer
def startGame(self):
for question in self.questions:
if(self.questionAnswered == False):
answer=input(question)
questionText = Text((300,150),question)
questionText.setFill(Color("white"))
questionText.draw(win)
if(answer == self.questions[question]):
questionAnswer = Text((300,200),answer + " is correct!")
questionAnswer.setFill(Color("green"))
questionAnswer.draw(win)
self.questionsCorrect = self.questionsCorrect + 1
continue
else:
questionAnswer = Text((300,200),answer + " is incorrect. Study this one.")
questionAnswer.setFill(Color("red"))
questionAnswer.draw(win)
self.questionsIncorrect = self.questionsIncorrect + 1
continue
def interpret(self,expression):
for token in expression.split():
if token in self.commands:
operator = self.commands[token]
operator()
else:
self.stack.append(token)
i = FlashCard()
i.interpret('What is your dog\'s name? addQuestion')
i.interpret('What is your favorite thing to do? addQuestion')
i.interpret('startGame')
This is essentially a mini flash card program I'm making. It takes the interpret commands at the bottom and executes them based on the dictionary in the FlashCard class. It basically works: it does the correct text objects. However, text begins to overlap other text objects because it re-draws. I've tried moving the .draw function all over, but it either doesn't appear at all or it overlaps.
Anyone have any suggestions? I want the text to replace for each new flashcard question.
Thanks!
there's an undraw() command that you need to use if you want to make something invisible. I'd recommend placing it right before your continue statements. It's used like
questionText.undraw()
questionAnswer.undraw()
Alternatively, you can use the del command to get rid of each questionText/questionAnswer instance when you're done with it. That's probably a better option since you're actually freeing up the memory that way instead of storing data and not doing anything with it.
You can use setText method to change the text.
example:
string = Text(Point(1, 1), 'original string')
sting.setText('new string')