Read Only Text widget in python3-tkinter; cross platform - python

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.

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.

WxPython Choice Field Invisible just on Mac

I am developing a cross platform app in Python using wxPython. The app is fully developed, and the graphics toolkit is set in stone, at least for the time being.
On Windows, everything looks great. On Linux, everything looks pretty good. On Mac, I am having trouble with a combobox/choice being hidden in the toolbar, even though it shows up fine on Windows.
Here is a snapshot of my app on Linux, noting the entire "CoeffConv ..." section is part of the perfectly displayed combobox:
And here is a snapshot of the same exact codebase on Mac:
I've tried with wx.ComboBox and wx.Choice with the same effect. I've made sure to call Realize() after I've added my toolbar items. I've made sure AddControl is called to actually add the object to the toolbar. It's definitely trying to render because the spacing is exactly what I would expect, given the contents of the choice field.
In fact, if I don't call AddControl, but I create the choice field with the toolbar as the parent, the box gets rendered but things aren't arranged properly due to the missing AddControl call:
As another check, I created a super simple toolbar with choice demo, and it works just fine:
So, here's the summary of things I know:
On Windows, the choice field works perfect, indicating the code isn't necessarily wrong.
On Linux, the choice field works perfect, supporting the idea that the code is actually OK.
On Mac, the choice field is present it seems, but somehow invisible, implying this is a problem with the Mac, or the Python distribution on Mac, or the combobox control in the wxPython distribution on Mac.
On Mac, I can get the choice to render (improperly but still) without an AddControl call, indicating the combobox can render properly, but something is goofy about the placement when added to the toolbar
However, On Mac, I can get a toolbar/choice to render totally fine in a dummy example, indicating it's something about my implementation...but I can't figure out what would cause it as I'm trying to make the exact same calls to the wx objects as in the dummy example.
I can't get the dummy example to reproduce the problem, but I'll keep trying. I'm happy to report out some object properties if they would be helpful in diagnosing. If someone has a clue for what could cause it to not show up, I'd really appreciate it!
While I couldn't find the root as to why it didn't work on Mac, I did find an issue that could help others.
First a little more background. In the app, we have multiple toolbars. Because of that, we are creating toolbars using plain wx.ToolBar objects and adding them to the app frame using sizers. This is in contrast to the more standard method of using self.CreateToolBar() which only allows a frame to have a single toolbar.
When I make a single change to the code to use the more natural CreateToolBar interface, the combobox immediately shows up. When I do that, the second toolbar is messed up, but at least the initial toolbar works perfectly. I'll be investigating how to get multiple toolbars on Mac, but it's a step in the right direction.
It's been a while, but there's another solution to this for wxPython 4 if you can't use CreateToolBar():
The widgets do get added to the bar and reserve the correct amount of space, but they fail to draw properly. You can resolve this by calling control.Hide() followed by control.Show() on each widget control, which then makes them draw properly.

How to delete a subwindow in the python curses module

I've got a curses application that uses subwindows, but I can't seem to be able to delete them.
For example, this code doesn't work:
import curses
def fill(window, ch):
y, x = window.getmaxyx()
s = ch * (x - 1)
for line in range(y):
window.addstr(line, 0, s)
def main(stdscr):
fill(stdscr, 'M')
stdscr.refresh()
stdscr.getch()
subwin = stdscr.subwin(1, 28, 20, 13)
fill(subwin, 'J')
subwin.refresh()
subwin.getch()
del subwin
stdscr.touchwin()
stdscr.refresh()
stdscr.getch()
curses.wrapper(main)
When you run this code, the screen fills with 'M', then when you hit a key, a subwindow is created and filled with 'J'. Finally, when you press a key again, the code deletes the subwindow and completely redraws the screen. However, those Js are still there.
After some experimentation, I've found that calling the clear() method of stdscr will make the subwindow go, but I would like to restore the background as it was, without blanking it and rewriting.
Does anyone know a way in which this could be done?
Is there a good reason why you're using a subwindow? If you create a new top-level window then the code works correctly - simply change stdscr.subwin to curses.newwin and it works as you'd expect.
I'm not a curses expert, but I believe a subwindow shares the character buffer with its parent such that changes to either one will also affect the other. So, if you're looking to sub-divide a window into logical areas (perhaps a menu bar, main area and status bar) then subwindows are useful. If, however, you're looking for something more like a dialog box or pop-up menu then a whole new window (with its own separate buffer) is what you're after.
I can't find any definitive reference for ncurses which agrees or disagrees with me, but man page for AIX seems to corroborate it:
Recall that the subwindow shares its parent's window buffer. Changes made to the shared window buffer in the area covered by a subwindow, through either the parent window or any of its subwindows, affects all windows sharing the window buffer.
Of course, this isn't definitive for ncurses, but I can't find anything to the contrary and it certainly seems to explain the behaviour observed. I also did a crude experiment where, immediately after the subwin.getch() line in your example, I added this line:
raise Exception(stdscr.instr(20, 15, 3))
In your example, I get JJJ as the content of the actual main window. If I change to use curses.newwin() to create the window instead of stdscr.subwin() I get the expected MMM.
I don't know how many specific Python curses resources there are, but most of the standard tutorials and documents about ncurses are quite useful for this sort of level. Back when I had to do some work in it, this document was quite useful. If you scroll down to the "An Example" section, you'll see that the menu pop-ups are not subwindows - he alludes to this with the following slightly vague explanation:
We don't want this new window to overwrite previously written characters on the background. They should stay there after the menu closes. This is why the menu window can't be created as a subwindow of stdscr.
Also, I remember that using both stdscr and your own windows can cause issues - the "official" ncurses introduction has some warnings about this sort of thing. It also suggests avoiding overlapping windows entirely, as they're apparently error-prone, but I don't recall having any issues with them for short-term transient modal dialogs (which is the only use to which I put them). Of course, just because my simple use-case didn't expose any issues doesn't mean there aren't any. In something as complicated as ncurses, however, I can see the wisdom in keeping things as simple as you can.
I hope that's some help. As I said, I'm by no means a curses expert, but hopefully this gets you a few steps further along.
There are two problems with this code.
First, as the previous poster noted, subwindows share a buffer with the parent window, so you should use curses.newwin() if you want a completely independent window.
Second, using del to remove a window is problematic because it relies on reference counting/garbage collection to work properly. (For one thing, you have to delete all references to the window for it to work.) I recommend using the curses.panel module to explicitly show/hide the window.

wxPython, button design

How to change the button decoration with wxPython, generally when the button is clicked, a dotted lines appear on the button.. any way to make that button not show the dotted lines?
Thanks
Assuming you're running your program on Windows (you didn't say which OS, but dotted lines are used by Windows Classic look), the dotted lines are called the focus rect, and they appear to mark a button or widget as focused. They are a system setting, and your program is acting as it should - wxWidgets is meant to emulate the underlying OS default behaviour as closely as possible.
Update
I don't think you can change this behaviour from inside the program. I really doubt that wxWidgets has a setting somewhere for this, as it is OS-dependent and is the standard and correct behaviour for the Classic theme. But the focus rect is shown by default only on the Classic Look which few people use.
Try switching to Luna theme (the default on XP), and you'll see that the focus rect won't appear unless you start hitting Tab while your window is in focus. By the way, the focus rect is needed exactly for when you are switching the focus using the Tab key. You need to see where the focus is, after all. That way you know when you press Enter or Space, which button is going to be pressed. Not everyone uses only the mouse.
You can use a custom button, for instance wx.lib.buttons.GenButton which is in pure python so you can overwrite the look, feel etc.
This also has a method SetUseFocusIndicator to turn off the dotted focus indicator

Change the focus from one Text widget to another

I'm new to Python and I'm trying to create a simple GUI using Tkinter.
So often in many user interfaces, hitting the tab button will change the focus from one Text widget to another. Whenever I'm in a Text widget, tab only indents the text cursor.
Does anyone know if this is configurable?
This is very easy to do with Tkinter.
There are a couple of things that have to happen to make this work. First, you need to make sure that the standard behavior doesn't happen. That is, you don't want tab to both insert a tab and move focus to the next widget. By default events are processed by a specific widget prior to where the standard behavior occurs (typically in class bindings). Tk has a simple built-in mechanism to stop events from further processing.
Second, you need to make sure you send focus to the appropriate widget. There is built-in support for determining what the next widget is.
For example:
def focus_next_window(event):
event.widget.tk_focusNext().focus()
return("break")
text_widget=Text(...)
text_widget.bind("<Tab>", focus_next_window)
Important points about this code:
The method tk_focusNext() returns the next widget in the keyboard traversal hierarchy.
the method focus() sets the focus to that widget
returning "break" is critical in that it prevents the class binding from firing. It is this class binding that inserts the tab character, which you don't want.
If you want this behavior for all text widgets in an application you can use the bind_class() method instead of bind() to make this binding affect all text widgets.
You can also have the binding send focus to a very specific widget but I recommend sticking with the default traversal order, then make sure the traversal order is correct.
It is really simple in PyQt4 simply use this one single line below and you will be able to change focus by pressing tab button:
self.textEdit.setTabChangesFocus(True)
The focus traversal is somewhat customizable, usually letting the X windows manager handle it (with focus follows mouse, or click). According to the manual it should be possible to bind an event to the key press event, for tab presses, and triggering a focusNext event in those cases.

Categories