Listbox getting `Key` events with diacritics instead of national characters - python

When I switch the keyboard to Russian layout, my widget gets key events with some diacritics in keysym instead of Russian letters.
Here's a demonstration (using the dump module):
import Tkinter as tkinter
from dump import dump
def show_event(e):
dump(e)
root=tkinter.Tk()
lb = tkinter.Listbox(root)
lb.grid()
root.bind_class('Listbox','<Control-Key>', show_event)
root.mainloop()
Now, when I focus the listbox and press Ctrl with letters фыва (that correspond to asdf), the keysyms in the printed events are: ocircumflex,ucircumflex,acircumflex,agrave.
While I, naturally, expected to get Cyrillic_ef, Cyrillic_yeru etc.
At the same time, if I type text into an Entry, Russian letters are inserted correctly.
Any ideas of what is going on & how to fix? That said, maybe it doesn't even need fixing (it may allow one to use the same binding for any non-English keyboard layout) but I'd still like to know the reason for this highly counterintuitive effect incl. if it's by design.

From what I've found, it depends on what system you're working with. It seems like from the answers here, for Windows you have to hack together things like '<Control-ntilde>' in key bindings, so you might be able to do '<Control-ocircumflex>', '<Control-ucircumflex>', ...
In OSX, I'm not even sure how to go about this. I tried the example that works for Windows and wasn't able to get it to register the keypresses. And for Linux, I haven't been able to try.
EDIT: To actually answer your question, essentially when Tk translates virtual key codes delivered by the system to the Tk window, it uses the function TkpGetKeySym(), which in turn calls Windows ToAscii() function. This function will take into account the current active keymap when processing even though it's only designed to return codes of Latin-1 characters. The specific keymap for Russian is Windows-1251, which is used when TkpGetKeySym() is called but the caller interprets the outputs as if they're Latin-1. Is there a fix? It seems not. I've basically just (attempted to) summarize what's from this page, so I'd suggest reading it for a more in-depth explanation and to check if I misread anything.

Related

Add Widget to Tkinter colorchooser

As far as I understand, it is not possible to modify the tkinter.colorchooser.askcolor as it uses the systems colorpicker dialog. Is this true?
from the source code: https://github.com/python/cpython/blob/3.9/Lib/tkinter/colorchooser.py
# this module provides an interface to the native color dialogue
# available in Tk 4.2 and newer.
The reason being is I wish to add an entry box to the dialog so that I would get the color code and user-entered text returned. Maybe it is possible to embed the dialog within a larger window? Is something like this possible, without using multiple windows?
I cannot find previous discussion anywhere else so I guess it is not a simple issue.
As far as I understand, it is not possible to modify the tkinter.colorchooser.askcolor as it uses the systems colorpicker dialog. Is this true?
Yes, that is true. At least on Windows and OSX. On Linux it's a custom dialog written in tcl/tk. You could start with that code and then make modifications to it, then write a tkinter wrapper around it. That wouldn't be particularly difficult if you know tcl/tk, but it's not exactly trivial either.
Maybe it is possible to embed the dialog within a larger window?
No, it's not.

Python hide already printed text

I'm creating a simple two-player board game where each player must place pieces on their own boards. What I would like to do is by either:
opening a new terminal window (regardless which OS the program is run on) for both players so that the board is saved within a variable but the other player cannot scroll up to see where they placed their pieces.
clearing the current terminal completely so that neither player could scroll and see the other player's board. I am aware of the unix 'clear' command but it doesn't achieve the effect I'm after and doesn't work with all OS's (though this might be something that I'll have to sacrifice to get a working solution)
I have tried clearing the screen but haven't been able to completely remove all the text. I don't have a preference; whichever method is easier. Also, if it would be easier to use a different method that I haven't thought of, all other suggestions are welcome. Thanks in advance!
EDIT: Other solutions give the appearance that text has been cleared but a user could still scroll up and see the text that was cleared. I'd like a way to remove any way that a user could see this text.
EDIT 2: Please read the other answers and the comments as they provide a lot of information about the topic as a whole. In particular, thanks to #zondo.
Consider using a portable terminal handling library. They abstract away the system specifica of common tasks like erasing the "screen" (i.e. terminal), or placing output at a specific position on the "screen" (again, meaning the text terminal). However, to use such a library effectively, you often have to switch to its style of generating output on the screen instead of naively printing strings.
curses is one such library (based on the C library ncurses) and included in the Python standard library. To get started, be sure to have a look at the curses tutorial in the official Python documentation.
I'd personally just use this.
import os
os.system("cls" if os.name == "nt" else "clear") #"cls" for Windows, otherwise "clear"
I would recomend a simple ANSI escape code to move the cursor position, Cursor Escape Codes, to the start of the board everytime. There is also an ANSI escape code that completly clears the console though, so you can choose.
If you are on windows you must first import colorama a module that makes windows prompt be able to use the ANSI codes as such:
import colorama # OR: from colorama import init
colorama.init() # AND THEN: init()
So if your board has n rows, after the user input for their turn, you move the cursor UP n rows + however many were required for user input, so if you wrote Input row, col: ... then you would go UP n+1, etc...
A simple example:
numLines = 1
print("Hello world!")
print("\033[<{0}>A".format(numLines), "This came AFTER hello world line")
You may not like this, it's a bit higher level than a basic two player board game, but there is always using some sort of GUI.
I personally like tkinter myself.
You don't want the option of people scrolling up to see printed text, but you can't remove what has been printed, that's like asking a printer to remove ink off a page. It's going to stay there.
Research a GUI interface, and try and make the game in that. Otherwise, you could let me take a stab at creating a explanatory piece of code that shows you how to use tkinter. If you do, link me the game you have so I can understand what you want.

Switching Windows with Python

I'll preface this by saying that I'm very inexperienced with python, and I'm hoping that means the solution to my problem will be simple.
My program will be performing simple actions in another window, so ideally I would like my script to make sure that this other window is maximized and active before it proceeds with the rest of the commands. This has proved to be much more difficult than I had expected.
I am fairly certain that I can achieve this with win32gui using find_window and setting it to the foreground. I thought I had found my solution when I came across this previous question:
Python Window Activation
Unfortunately I was unable to use the solution code or manipulate it to solve my problem for a few reasons:
-The way that user had defined find_window only allows you to choose by the classname of the window, which I don't know and haven't been able to find since it is just a game running in Java. I couldn't seem to change that line to work for the specific windowname (Which I do know) as it is not the "default argument".
-I don't want to enumerate the windows to find it as I'm not sure how that would work.
-using find_window_wildcard as it's written in that script has managed to bring the window to the foreground a few times, but only if the window was already open, and it only works intermittently.
-set_foreground() requires one input statement and no matter how I try to set it I am always given an error that I either have the wrong number of elements or an invalid handle on the window.
I know that I'm stupid; but a clear and concise solution to this problem or at least a good explanation of the find_window/getwindow syntax would be a godsend to myself and anyone else who's having this problem.
I would strongly suggest you take a look at the pages for Swapy and for pywinauto. They can help you do some awesome things when it comes to UI Automation.
The way that user had defined find_window only allows you to choose by the classname of the window
The way the user defined it is to pass the two parameters class_name and window_name through, untouched, to win32gui.FindWindow (which in turn just calls the Win32 API function FindWindow). So, just do this:
windowmgr.find_window(None, 'My Window Name')
But even if that weren't true, you don't need to use his find_window function; it should be pretty obvious how to call win32gui.FindWindow yourself:
hwnd = win32gui.FindWindow(None, 'My Window Name')
And if you want a good explanation of the FindWindow/EnumWindows/etc. syntax, did you try looking at the docs for them? Is there something you didn't understand there?
Meanwhile:
… the classname of the window, which I don't know and haven't been able to find since it is just a game running in Java
What difference does it make that it's running in Java? You can enumerate the windows and print out their classnames, whether they're written in C++, Java, .NET, Python, or anything else. Or use one of the tools that comes with Visual Studio/VS Express, or any of the free improved versions you can find all over the net, like MS Spy++, which will let you point at a window and give you the caption and class name.
I don't want to enumerate the windows to find it as I'm not sure how that would work.
Just call windowmgr.find_window_wildcard(wildcard) with a regular expression, and it'll enumerate the windows and compare their titles to that regular expression.
If you want to write your own code to do it, just write a function like this:
def my_callback(hwnd, cookie):
Now, when you do this:
win32gui.EnumWindows(my_callback, some_cookie)
… it will call your my_callback function once per window, with hwnd being the window (which you can pass to win32gui functions like, e.g., GetWindowText), and cookie being the same some_cookie value you passed in. (If you don't need anything passed in, just pass None, and don't do anything with the value in your callback function. But you can see how the other answerer used it to pass the regular expression through.)
Meanwhile:
using find_window_wildcard as it's written in that script has managed to bring the window to the foreground a few times, but only if the window was already open, and it only works intermittently.
First, you can't bring a window to the foreground if it doesn't exist. How do you expect that to work?
As far as working intermittently, my guess is that there are lots of windows that match your wildcard, and the program is going to just pick one of them arbitrarily. It may not be the one you want. (It may even be a hidden window or something, so you won't see anything happen at all.)
At any rate, you don't need to use find_window_wildcard; if you know the exact name, use that. Of course it still may not be unique (whatever the game's name is, there's nothing stopping you from opening an email message or a Notepad window with the same title… which, by the way, is why you want to try class names first), but at least it's more likely to be unique than some underspecified wildcard.
So, what if the class name isn't unique (or, even worse, it's one of the special "number" classes, like #32770 for a general dialog window), and neither is the window name? You may be able to narrow things down a little better by looking at, say, the owning process or module (exe/dll), or the parent window, or whatever. You'll have to look through the win32gui and/or MSDN docs (linked above) for likely things to try, and play around through trial and error (remember the Spy tool, too) until you find some way to specify the window uniquely. Then code that up.

Read Only Text widget in python3-tkinter; cross platform

How to suppress end user ability to edit/add/delete text in a Text widget? (Python v3.2.. and tkinter)
The point is to suppress only the ability to change/add/delete text but not to castrate other features. Perhaps a NoEdit Text widged would be a better name.
I've tried .text['state'] = 'disabled' and it works almost OK in Windows (it still allows user to select/copy text highlights the selection, page up/down and up/down buttons work. The only thing broken seems to be the cursor made invisible.)
But on MacIntosh everything is broken. No highlights, no select/copy,... UGH
Since Tkinter has practically no documentation in Python, I've searched and found some TCL advise, to derive a new class and suppress the insert and delete functions.
So, I've tried as so:
class roText(tk.Text):
def insert(self,*args,**kwargs):
print(" Hey - Im inside roText.insert")
pass
def delete(self,*args,**twargs):
pass
def pInsert(self,*args,**twargs):
super().insert(*args,**twargs)
Unfortunately it didn't work right. Apparently tkinter does not use those insert and delete functions when end user enters/deletes code. Perhaps those TCL insert/delete are something else, and I lost something in translation from TCL and Swahili. What functions does tkinter.Text use for end user editing text? Hopefully they are not internal...
So, is there a way to modify the Text widget to suppress only end user editing?
Is there a way to do it without diving inside and overriding internal Tkinter code, so the stuff doesn't get broken by next releases of Tkinter?
Looking at the Idle shell window, I see that they've managed to suppress edits (except for the last line). So there is a way. But what is it and how costly?
Sorry for bumping an old question, but I was searching for an answer to this question also and finally found a solution. The solution I found involves overriding the key bindings when the text widget has focus and is pretty simple. Found here.
To override the bindings of a widget there is a bind function where you pass a string of what is to be overridden and the new function you want it to call.
self.txtBox.bind("<Key>", self.empty)
Somewhere else in the class you'll need to define the function to handle the event.
def empty(self, event):
return "break"
By returning the string "break" the event handler knows to stop after your function, instead of continuing with the default action.
I hope this answers your question. Cheers.
The reason the disabled state doesn't seem to work on the Mac is because it turns off the binding that gives focus to the widget. Without focus, the highlighting on a Mac doesn't show up. If you set the state to disabled but then assign a binding to <ButtonPress-1> to explicitly set focus to the disabled text widget, you can then select and copy text and the highlighting will show.
As for the cursor disappearing... arguably, that's what's supposed to happen. The cursor tells the user "this is where text will get inserted". Since no text will get inserted, having that visual clue would be confusing to the user. What you could do instead, if it was really important, is to insert a small image wherever they click to simulate the cursor.
To answer your question about whether the widget actually uses the insert and delete methods: the methods on the actual underlying widget are what the default bindings use, so overriding them in a subclass has no effect. You would need to redo all the default bindings for that to work. It's doable, but a lot of work.
Unfortunately, this is one area where programming in Tcl really shines, because you can simply disable the insert and delete commands of the widget. Of course, you can do that directly in Tkinter also since ultimately it runs tcl code to do everything, but that would involve writing some tcl code which is not a very good solution from the perspective of a Python coder.
I think the best solution is to use the disabled state, then add in just enough bindings to do what you want.
Here's a simple example that works by explicitly setting focus on a mouse button click. With this code I'm able to click and swipe to select a region, or double- or triple-click to select words and lines:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.text = tk.Text(width=40, height=20)
self.text.bind("<1>", self.set_focus)
self.text.insert("end", "\n".join(dir(tk.Tk)))
self.text.configure(state="disabled")
self.text.pack(fill="both", expand=True)
def set_focus(self, event):
'''Explicitly set focus, so user can select and copy text'''
self.text.focus_set()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
#BryanOakley It took me a while to test your suggestion since I have no Mac.
Unfortunately Mac implementation of Python is buggy.
I've added focus, ie my disable function which I call after creating a window and inserting text, now calls first:
self.txt['state'] = 'disabled'
and then
self.txt.focus_set()
Which is what I think you've suggested.
It "kind of" worked. Ie: when selecting text (click and drag or double-click) highlighting works most of the time. Python must have some bad memory references or such bugs: Sometimes highlighting doesn't work at first, then it starts working (in the same window) after more clicking. Sometimes when program is invoked it works right of the bat. Sometimes selecting with Shift-rightArrow key will work but selecting with the mouse will not. Then starts working again. Or it will work fine in one window but not in another one (both of the same class), then starts working in all windows...etc...
The good thing is that adding focus did not affect badly Windows (ie all works fine as without focus.
I guess at this point I will just hope that future/next release of Python for Mac will fix those bugs..
BTW, it seems that Mac is a bit of an orphan for Python. Implementation is much uglier then for Windows. I mean the fonts look worse, the buttons, etc.. Or it could be due to different screen resolutions and Python ports that poorly account for those. Not sure
Anyway. Thank you for your help and suggestion to use focus for Mac.

Windows Balloon-tooltips in Python

Following the example at http://article.gmane.org/gmane.comp.python.general/541418, I've succeeded in creating a callable class for balloon tooltips, but the greater complexities of that code elude me when it comes to customization. I browsed a bit of how it works through msdn, but being a novice at more windows-esque languagues like c and vb, etc. I was unable to make much sense of it.
So I ask ye snakely academics:
Things I'd like to be able to do with that code aside from the standard icon, title, text:
Perform actions based on clicking the tooltip
Modify the tooltip that pops up over the icon in the system tray after loading it (to reflect changing values)
Multiple lines? (Not sure if this can even be done, really)
More information on other things you could do in a windows 7 environment versus XP (which seems to be what this was written for).
Ideally I'd get some sort of return value or some semblance of an event when the tooltip is clicked so that I could run some code, but currently I'm importing that code as a module and calling at various times, so I'm not sure how to handle clicks outside of the popup code itself...
Information on handling these things with python seems quite scarce. Thanks in advance.
Perform actions based on clicking the tooltip
Whats the problem OnTaskbarNotify? Hock yourself in there.
Modify the tooltip that pops up over the icon in the system tray after loading it (to reflect changing values)
Probably not, I am not sure about the WinAPI here. I haven't seen it in the wild, so...
Multiple lines? (Not sure if this can even be done, really)
With most WinAPI, just insert a \n in the string.
More information on other things you could do in a windows 7 environment versus XP (which seems to be what this was written for).
LOTS... But that is a bit vague... It depends what your needs are. But for kol feturez you need to google on your own...
On Linux and Unix systems I use the notify-send OS already implemented system.
import os
os.system('notify-send "'+title+'" "'+message+'")
Maybe in Windows there is some API32 for this.
Check this https://gist.github.com/wontoncc/1808234

Categories