Tkinter TixBalloon bug with dual monitor - python

To create tooltips for my application I'm using the Balloon widget from Tix. This works great and does exactly what I need. However, I run into a bug when moving the application's window from one monitor to the next.
When moving to my second monitor the tooltips show up on my first monitor on the edge where my monitors are extended. It looks like the y-coordinate is still correct but the x-coordinate is erroneous. I added the relevant code below. I have tried to find any prior mention of this behaviour or similar behaviour but couldn't find any references. I don't know where to begin solving this problem.
def create_tooltips(self):
self.tooltips = dict()
for i, (column_name, filter_data) in enumerate(self.filters.items()):
self.tooltips[column_name] = tix.Balloon(self.frame)
tooltip_msg = "e.g. " + str(self.example_column_values[column_name])
self.tooltips[column_name].bind_widget(self.input_labels[column_name],
balloonmsg = tooltip_msg)
So my question is: Why is the Balloon showing up on the wrong monitor and how do I fix it?

Related

Embed widgets with jupyter-cadquery (threejs): wrong position on load

I am using jupyter-cadquery to visualize some 3D models made with CadQuery.
When visualizing the models on a Jupyter notebook, everything works as expected.
But when trying to embed the widget in an HTML document, it seems the camera, on load, is pointing to (0, 0, 0), not as expected. Once you interact with the widget, the camera will point to the expected coordinate.
Here is the code to reproduce the error and an animation of the mentioned problem (see instructions bellow on how to reproduce it with Binder):
from cadquery import Workplane
from ipywidgets import embed
from jupyter_cadquery.cad_view import CadqueryView
from jupyter_cadquery.cadquery import Assembly
from jupyter_cadquery.cadquery import Part
# Create a simple assembly
box1 = Workplane('XY').box(10, 10, 10).translate((0, 0, 5))
a1 = Assembly([Part(box1)], "example 1")
# Generate HTML
a1.collect_shapes()
view = CadqueryView()
for shape in a1.collect_shapes():
view.add_shape(shape["name"], shape["shape"], shape["color"])
renderer = view.render()
embed.embed_minimal_html('export.html', views=renderer, title='Renderer')
renderer
Note how the view of the cube "jumps" suddenly on interaction.
Could it be an issue with ipywidgets? Since the view is okay when displayed in the notebook.
How could it be fixed?
How to reproduce
You can reproduce it with Binder, without needing to create a local environment (admitedly, installing CadQuery/jupyter-cadquery is not the easiest/fastest thing to do):
https://mybinder.org/v2/gh/bernhard-42/jupyter-cadquery/master?urlpath=lab&filepath=examples%2Fcadquery.ipynb
Just execute the code above in a new empty notebook. See how the renderer shows the 3D model without any issues on the notebook:
After execution, an export.html document will also appear in the file list on the left. Open it and make sure to click on the "Trust HTML" button on top of the viewer and hit refresh. If you interact with the view, you can reproduce the issue.
Note that, also, the perspective is lost (that is not an orthogonal view). Fixing that would be a plus! ^^
This can be reproduced without the need of jupyter-cadquery, so a new question has been opened instead:
Embed widgets with pythreejs: wrong perspective and camera look-at
It took a few days, didn't get cadquery working properly, but your second question on this topic without cadquery made it possible to look at the issue...
The jumping happens because orbit.update() for target does not occurs and the function update() is not available in python; only in c++ or c#, etc. From the docs:
When animating the camera rotation above, we used the camera’s
quaternion
. This is the most robust method for
animating free-form rotations. For example, the animation above was created by first moving the camera manually, and then reading out its position and quaternion properties at the wanted views...
The text can be found here on page 12. And also discussed here at github.
However, the jumping can be reproduced in IPython if you apply the following:
renderer = Renderer(scene=scene, camera=camera, controls=[orbit], position=target, width=view_width, height=view_height)
here position is added with target coordinates [0, 5, 0] but the update for this is only done when you mouse-click and adjust to position of the cube/camera. The jump is similar/equal to the jump as seen in the export.HTML.
Conclusion: the programmed camera position is seen as a jump after manual interference due to the absense of the .update() function of the OrbitControls python class and thus not a bug or mistake.

How to open a window by clicking on a specific point on a chart?

I have the task to build up an interactive plot which i have already done more or less. But now I am supposed to give specific information about a point in a graph for example P(8|6) and by clicking on this point there should open a new window with specific information. Adding the information to the window wont be the problem but the window itself. How I can open a window by clicking on this specific point (keep in mind, it is no button since it changes from graph to graph)?
The answer is qwt! Study the documentation and examples for that project like your life depended on it, and you will find exactly what you need.
I wrote up a pretty complete example a year or so ago; but it is in C++; converting it to python should be pretty straight forward.
https://github.com/peteristhegreat/qwt_generic
Hope that helps.

Order of execution of Tkinter mousePressed event

I have recently been trying to finish a project I started a few years ago, and on revisiting the problem, finally decided to seek help since I cannot figure out what is going on exactly.
The scenario is a game called Reversi, and I have created an algorithm (albeit not a very good one) for the computer. Now, when player A plays against the computer, I have written the order of execution to be as follows, upon mouse click event:
makeMove(canvas, row_clicked, column_clicked)
if canvas.data["computer"] and not canvas.data["noMove"]:
#time.sleep(2)
computerAI2(canvas)
makeMove makes a move and updates the main board (update: in tkinter, I delete the entire canvas [my board] then recreate the whole board). This works perfectly fine player to player, but when I include the computer, it seems to complete the updating all in one go, even though makeMove is (supposedly) completed before the computerAI2 starts its calls. computerAI2 also calls makeMove, so it should be updating after my mouseclick, and then once more after the computerAI2 finishes its algorithm.
But it doesn't. I have looked towards threading (note I haven't "actually" tried this, since I have never threaded before and seems difficult to implement and integrate in), towards adding time.sleep(), adding more pauses and phases in between to try and update the board before it gets to the computerAI2.
For absolute conviction that ordering is the problem, I tested by including
makeMove(canvas, row_clicked, column_clicked)
canvas.delete(ALL)
if canvas.data["computer"] and not canvas.data["noMove"]:
computerAI2(canvas)
And no response (same if placed above computerAI2 after the if). It just updates all in one go. Comparing to:
makeMove(canvas, row_clicked, column_clicked)
if canvas.data["computer"] and not canvas.data["noMove"]:
computerAI2(canvas)
canvas.delete(ALL)
Then this does delete everything after all is resolved, and I'm left with a blank slate (as expected). So the fact that it doesn't wipe everything inbetween... the fact is, I have some extra debugging inbetween that shows the computerAI2 is running (prints feedback) between my mouseclick and the eventual completion and update of the canvas/board. So it is not like it can't run and delays things in between, only for some odd reason refuses to reupdate the board in between!
def mousePressed(event):
(... set up etc)
makeMove(canvas, row_clicked, column_clicked)
if canvas.data["computer"] and not canvas.data["noMove"]:
computerAI2(canvas)
So my query is, what exactly is happening with the order here? Does it have something to do with the fact that its a mousePressed event?
This almost certainly, provided that the above information is not enough, requires looking at the source code to see how I am building the board through tkinter and the functions I call to. It is quite comprehensive to take it all in however. I am happy to share if someone is up to the task (it is clear if you know which parts to ignore and jump to the important sections). I will tell you the relevant functions that are being in use.
Thank you so much for your insights.
edit: the functions simplified for logic:
def makeMove(canvas, row, col):
flipCounters(canvas, row, col)
redrawAll(canvas)
def redrawAll(canvas):
canvas.delete(ALL)
drawBoard(canvas)
def ComputerAI2(canvas)
(the source of issue may be originating here, ie it may be nothing to do with order of execution, rather some error in the behaviour. However I have checked extensively here)
algorithm --> makeMove(canvas, row, col)
edit2:
100% determined problem is not to do with computerAI2. It can only originate from makeMove, or really is some ordering problem to do with mousePressed Event.
makeMove(canvas, row_clicked, column_clicked)
if canvas.data["computer"] and not canvas.data["noMove"]:
time.sleep(2)
makeMove(canvas, 1,1)
The above STILL delays. Ie, it updates both moves at once, not update one move, wait 2 seconds, then updates the other move.
However, 95% certain that makeMove is not the problem. If someone would create a quick mousePressed event in tkinter and test "print 1,2,3,4,5, time.sleep(5), print ok", if THAT doesn't delay, and works as 1,2,3,4,5ok all at once, then that resolves the root of the problem is with the mousePressed(event) problem. But how to FIX that, through threading?
edit3: Still no progress, havent managed to find the problem. If anyone could help I would really appreciate it, its a shame not to get such a nice project working.
edit4: Managed to google the solution out of luck, involves using "update" Haven't implemented yet but was from another thread answered by BryanOakley to do with running a function before the frame, "frame.update()" was the solution.

Control position of new Veusz window

This question is about Veusz, a python-based plotting program. Not about usage, but about where to start hacking to fix a particular problem... This is on Windows.
Currently, when the program is launched it starts non-maximized, even if it was maximized last time it was closed. I can modify the shortcut to always start maximized but new windows opened within the app are always non-maximized.
Although it doesn't remember its maximized state, it does remember the size of last non-maximized window. As a workaround, I tried positioning the program top-left and resizing it as if it were maximized. However, when I open new windows from this one, they are offset from the top-left corner by the height of the "window bar". The offset does not cascade though; that is, opening a new window from an offset one results in a window in the same position.
I've been pawing through the program's files looking for somewhere window position might be saved or a default might be set. Not seeing anything, though. This is a Qt app so perhaps it's not Veusz-specific but I'm inclined to think it is. Spyder, for instance is Qt-based but I don't see this problem with it.
Does the community have any suggestions regarding changing this behavior? I don't understand the setup routine well enough yet. The source is on Github if you're feeling that helpful.
The relevant code is here in functions closeEvent (for saving state) and setupWindowGeometry (for loading state).
https://github.com/jeremysanders/veusz/blob/master/veusz/windows/mainwindow.py
Veusz needs to save the state of the window, as well as the geometry. Maybe doing something like this http://doc.qt.io/qt-4.8/restoring-geometry.html

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.

Categories