Recognize which model object is clicked in Tkinter - python

I have many overlapping shapes representing irrelevant background items on a canvas. I also have a pattern of non-overlapping circles, each of which is a "hole". Each "hole" sprite (circle) has an associated "hole" object, though never explicitly in the code. (side note: I would love to have a logical association between model and view with these objects, but haven't found a smart way to do that). Each "hole" is different, and has different effects.
There is a small circular "ball" which can be dragged into any "hole". I found how to drag and drop from this question. I need to find which hole the ball went into.
The best way I have found to do that so far is to:
create a dict mapping the coordinates of the center of the hole sprite to the hole object
tag each hole like this:
t=("hole", "hole_at_{}_{}".format(x, y))
on releasing the ball, do this:
def on_ball_release(self, event):
'''Process button event when user releases mouse holding ball.'''
# use small invisible rectangle and find all overlapping items
items = self._canvas.find_overlapping(event.x - 10, event.y - 10, event.x + 10, event.y + 10)
for item in items:
# there should only be 1 overlapping hole
if "hole" in self._canvas.gettags(item):
# get the coordinates from the tag
coords = tuple([int(i) for i in self._canvas.gettags(item)[1].replace("hole_at_", "").split("_")])
# get associated object using dictionary established before
hole = self._hole_dict[coords]
hole.process_ball()
return
That seems very messy. I feel there should be some smarter way to do this.

Disclaimer: I don't use Python, but many Tkinter questions can be answered in a useful from an experience with Tcl/Tk, which I have. In this case, it takes some more work to figure out whether what I would do in Tcl is easy to represent with Tkinter.
First, I wouldn't add "identifier tags" (hole_at_...): if I have model objects corresponding to canvas items, I would use the item id (which canvas returns during item creation) as an index, to be able to find an object for an item id without parsing tags. (And if I had to add string identifiers, even if I decided to make them from coordinates, I would use that very string as my dictionary key, to avoid reparsing it. Do we need coordinates later? Then make them properties of the hole object).
Second, I would use pathName find subcommand with multiple criteria to find (canvas id of) item which is tagged as hole and is nearest to the given point (overlapping is fine when we want to ignore drops too far from any hole, closest is for the case where nearest hole should be used even if it's not too near). Here is the problematic part: does Tkinter support multiple criteria in canvases' $pathName find?

Related

Dynamically create/delete drawn objects on tkinter canvas?

I have managed to dynamically create various rectangles on the tkinter canvas widget using the below code:
setattr(self, "wall_" + str(counter), self.canvas_base.create_rectangle(*sel_wall,fill="black"))
Where counter is an increasing and variable integer, canvas_base is the canvas being drawn on, suffix is a string alternating between a and b, and sel_wall is a list of coordinates used to draw the rectangle.
Is there a way to dynamically delete these objects on the canvas (varying from wall_1 to something like wall_5), or is there a better alternative combination to create/delete these canvas objects?
When you need to deal with an arbitrary number of similar objects, the proper solution is to put them in a list (or other container object). You can easily iterate over the list to do something to all the objects, such as deleting them.

Use all coordinates in a grid except with certain value

For a minesweeper I have created a board using python and pygame. When you flagged all bombs, you win. I have separate functions that contain the (randomised) bomb positions, and create the numbers around the bombs(on the proper coordinates). How do I make sure it checks the coordinates 0 to GRID_TILES(the maximum range) with the exception of all bomb locations? As those should remain flagged.
I got a function where when you click a bomb, you get shown the entire board. I want the same except for when the coordinates are in my BOMBS[].
def show_board():
for x in range(0,GRID_TILES):
for y in range(0, GRID_TILES):
"when" not in BOMBS:
draw_item(CELLS[x][y], x, y, (x+1,y+1))
I want to know if there is a "when" function, and how I could implement it.
If I understand you correctly, then the following should work:
If BOMBS is a list of tuples, then the following test should work if (x, y) not in BOMBS:
The in operator works on lists, as well as dicts, sets and tuples - anything that's iterable actually. In general, using it on lists is not such a great idea, because it needs to look through the whole list, in the worst case, to find an element. But for such small lists, it should not be a problem. If you find that it is, make BOMBS be a set and you should be good.

What is the difference between object and node in Maya (scripting)?

This question may sound silly I know, but I would like to know exactly what is the difference and in which way the hierarchy works in their case.
If I create for instance a polyCylinder and bind it to a variable
exVar = cmds.polyCylinder(name='cylinder_01')
And now I print exVar, I get a list with two Unicode string items: one for the name of the object and another one for the name of the node.
[u'cylinder_01', u'polyCylinder1']
If I go to the Outliner I just can see cylinder_01, I cannot see the polyCylinder1 item.
What do they mean? Is there any way to visualize them in the Outliner or the Hypergraph?
Thanks in advance.
cylinder_01 is the transform, which handles translation, rotation, scale, etc.
polyCylinder1 is the shape, which holds the vertices, polygons, shader connections, etc.
The shape is parented to the transform. You can see it in the Outliner if you select Display > Shapes

HTML like layouting

I'm trying to implement my own little flow-based layout engine. It should imitate the behavior of HTML layouting, but only the render-tree, not the DOM part. The base class for elements in the render-tree is the Node class. It has:
A link to the element in the DOM (for the ones that build a render-tree with that library)
A reference to it's parent (which is a ContainerNode instance or None, see later)
A reference to the layouting-options
X, Y, width and height (the position is computed in layout(), after the size has been computed in compute_size(). While the position is defined by the layout() method of the parent, the size is defined by the options reference, for instance).
It's methods are:
reflow() invoking compute_size() and layout()
compute_size() that is intended to compute the width and height of the node.
layout() which is intended to position the sub-nodes of the node, not the node itself.
paint() which is there to be overwritten by the user of the library.
The ContainerNode class is implementing the handling of sub-nodes. It provides a new method called add_node(), which adds the passed node to the containers children. The function also accepts a parameter force which defaults to False, because the container is allowed to deny the passed node, except force is set to True.
These two classes do not implement any layouting algorithm. My aim was to create different classes for the different types of layouts (In CSS, mainly defined by the display attribute). I did some tests with text-layouting last night and you can find my code from at pastebin.com (requires pygame). You can save it to a python script file and invoke it like this:
python text_test block -c -f "Georgia" -s 15
Note: The code is really really crappy. I appreciate comments on deep lying misconceptions.
The class InlineNodeRow from the code mentioned above actually represents my idea of how to implement the node that lays out similar to the display:inline attribute (in combination with the NodeBox).
Problem 1 - Margin & Padding for inline-text
Back to my current approach in the library: A single word from a text would also be represented as a single node (just like in the code above). But I noticed two things about margins and paddings in a <span> tag.
When margin is set, only horizontal margin is taken in account, the vertical margin is ignored.
The padding is overflowing the parent container and does not "move" the span node.
See http://jsfiddle.net/CeRkT/1/.
I see the problem here: When I want to compute the size of the InlineNodeBox, I ask a text-node for it's size and add it to the size of the node. But the text-nodes size is including it's margin and padding, which is not included in the HTML renderer's positioning. Therefore the following code would not be right:
def compute_size(self):
# Propagates the computation to the child-nodes.
super(InlineNodeBox, self).compute_size()
self.w = 0
self.h = 0
for node in self.nodes:
self.w += node.w
if self.h < node.h:
self.h = node.h
node.w would include the margin and padding. Next problem I see is, that I for laying out the text-nodes correctly, I wanted to split them into single TextNodes for each word, but the margin and padding would then be applied to all these nodes, while the margin and padding in HTML is to the <span> tag only.
I think my current idea of putting each word into a seperate node is not ideal. How to browsers structure their render-tree, or do you have a better idea?
Problem 2 - Word too long, put it into the next line.
The InlineNodeBox class currently only organizes a single line. In the code example above, I've created a new InlineNodeBox from within the NodeBox when the former refused to accept the node (which means it didn't fit in). I can not to this with my current approach, as I do not want to rebuild the render-tree all over again. When a node was accepted once, but exceeds the InlineNodeBox on the next reflow, how do I properly manage to put the word into the next line (assuming I keep the idea of the InlineNodeBox class only organizing a single line of nodes)?
I really hope this all makes sense. Feel free to ask if you do not understand my concept. I'm also very open to criticism and ideas for other concepts, links to resources, documentations, publications and alike.
Problem 2:
You can do it like HTML renderers do and render a multiline (e.g. check if the new word to be added exceeds the width and add a new line if it does). You can do it in your InlineNodeRow, by taking care of height too and wrapping words if they exceed the max width.
Problem 1:
If you do figure out problem 2 for text, then you can put in the offset (horizontal padding) only for the first line.
Although <span> doesn't take height into consideration, it does take line-height, so your calculation could be that the default height is the font height unless you have a line-height option available.
Mind you, if you have two or more successive InlineNodeRow representing spans, you'd need some smart logic to make the second one continue from where the first one ended :)
As a side note, From what I remember from Qt's rich text label, each set of words with the same rendering properties is considered to be a node, and its render function takes care of calculating all the stuff. Your approach is a bit more granular and its only disadvantage from what I see is that you can't split words.
HTH,
May have found solution to problem 1 in the box model documentation (you may want to check out the documentation about clearance and the one for overflow as well for problem 2).
"margins of absolutely positioned boxes do not collapse."
You can see this jsfiddle for an example.

How to get the intersection of geometry of two QWidgets with different parents?

With Qt4 (here using PyQt4) to get the area of intersection of two QWidgets---and that's particularly useful with QRubberband when making a selection---one simply does:
intersection = rubberband.geometry().intersected(mydocumentwidget.geometry())
Now this assumes that rubberband and mydocumentwidget have the same parent widget, i.e. more precisely that their coordinates are relative to the same origin. This is because the method call is QRect.intersected() and QRect itself does not have a parent, it's a simple tuple with 4 integers and some method calls.
Now in my case rubberband and mydocumentwidget have the same parent, but I am interested in the interesection of the rubberband with mydocument's children, i.e. pages of the document.
Pages however take reference their coordinate to a different point on the screen.
How can I get to calculate the intersection between the rubberband and my page widgets? Is there a way to convert the coordinates to reference the parent's parent? I can of course just lay down the math myself, but I don't think it's very clean. Does Qt4 provide any means to convert this automatically?
What you can do is map the point of the QRect to the parent widget. You can map QPoints to various targets (parent, other, global), but since you specifically want to map to mydocumentwidget, here is what you do:
doc_child = getChildOfDocument() # pseudo
rect = doc_child.geometry()
mappedPoint = doc_child.mapToParent(rect.topleft())
# alternative #
# mappedPoint = doc_child.mapTo(mydocumentwidget, rect.topleft())
mappedRect = QtCore.QRect(mappedPoint, rect.size())
intersection = rubberband.geometry().intersected(mappedRect)

Categories