I am trying to use Nodebox Graph on Ubuntu and python 2.7.
So I got Nodebox OpenGL: http://www.cityinabottle.org/nodebox/
Nodebox Graph: https://www.nodebox.net/code/index.php/Graph
I tried to run their basic example 1 :
graph = ximport("graph")
size(500, 500)
g = graph.create()
# Create some relations.
g.add_edge("roof" , "house")
g.add_edge("garden" , "house")
g.add_edge("room" , "house")
g.add_edge("kitchen" , "room")
g.add_edge("bedroom" , "room")
g.add_edge("bathroom" , "room")
g.add_edge("living room" , "room")
g.add_edge("sofa" , "living room")
g.add_edge("table" , "living room")
# Calculate a good layout.
g.solve()
# Apply default rules for node colors and size,
# for example, important nodes become blue.
g.styles.apply()
# Draw the graph, indicating the direction of each relation
# and the two nodes that get the most traffic.
g.draw(
directed=True,
traffic=1
)
That doesn't work because ximport is not defined, it is only define by nodebox, so instead I tried two things, first doing a normal import
import graph
second putting the ximport function from nodebox in my code:
def ximport(library):
from sys import modules
library = __import__(library)
library._ctx = modules[__name__]
return library
graph = ximport("graph")
size(500, 500)
g = graph.create()
# Create some relations.
g.add_edge("roof" , "house")
g.add_edge("garden" , "house")
g.add_edge("room" , "house")
g.add_edge("kitchen" , "room")
g.add_edge("bedroom" , "room")
g.add_edge("bathroom" , "room")
g.add_edge("living room" , "room")
g.add_edge("sofa" , "living room")
g.add_edge("table" , "living room")
# Calculate a good layout.
g.solve()
# Apply default rules for node colors and size,
# for example, important nodes become blue.
g.styles.apply()
# Draw the graph, indicating the direction of each relation
# and the two nodes that get the most traffic.
g.draw(
directed=True,
traffic=1
)
That still doesn't work because now the function size is not recognized. If I just comment size out, I get the following error:
self.x = _ctx.WIDTH - max.x*self.d - min_.x*self.d
AttributeError: 'NoneType' object has no attribute 'WIDTH'
What do I do?
This question could be similar:
Pydev Nodebox: "AttributeError: 'NoneType' object has no attribute 'WIDTH'"
but the given answer is not helpful at all to me.
The code in https://www.nodebox.net/code/index.php/Graph
doesn't work for nodebox opengl, it is only compatible for nodebox 1 for mac so there is no easy fix. However, nodebox opengl has it's own interactive functions to it is possible to use them instead. Here is partial example code:
from nodebox.graphics import *
from nodebox.graphics.physics import Node, Edge, Graph
class GUI():
"""Draw and interact with the damn thing.
"""
def __init__(self):
"""rawgraph is an instance of the RawGraph class
The GUI allows to interact with it, i.e. view it and change it.
"""
#Layer.__init__(self) # unknown thing, might be necessary.
#Buttons
self.EXPLORE_F = "Explore Further"
self.EXPLORE_D = "Explore Deeper"
self.DELETE = "Delete"
self.CLOSE = "CLOSE"
self.BUTTONS = False #Are buttons showing?
self.CBUTTON = False #Is Close button showing?
self.nodes = []
self.g = Graph()
self.dragged =None
self.clicked = None
def add_node(self, name, root = False):
self.nodes.append(name)
self.g.add_node(id=name, radius = 5, root = root)
def add_edge(self,n1,n2,*args,**kwargs):
self.g.add_edge(n1, n2, *args,**kwargs)
def explore_further(self,node_name):
"""action of explore further button
"""
pass
def explore_deeper(self,node_name):
"""action of explore deeper button
"""
pass
def delete_node(self,node_name):
"""action of delete button
"""
pass
def close_canvas(self):
"""Close the canvas
"""
canvas.clear()
canvas.stop()
def add_close(self):
"""Add close button
"""
self.g.add_node(self.CLOSE, radius = 10, fill=(1,0,0,0.2))
self.g.add_edge(self.rawg.root, self.CLOSE, length=0.6)
self.CBUTTON=True
def remove_close(self):
"""Remove the close button
"""
try:
self.g.remove(self.g.node(self.CLOSE))
except:
pass
self.CBUTTON=False
def add_buttons(self,node_name):
"""Add the buttons to change the graph
"""
self.g.add_node(self.EXPLORE_F, radius = 6, fill=(0,1,0,0.5))
self.g.add_node(self.EXPLORE_D, radius = 6, fill=(0,0,1,0.5))
self.g.add_node(self.DELETE,radius=6, fill=(1,0,0,0.5))
self.g.add_edge(node_name, self.EXPLORE_F, length=0.2)
self.g.add_edge(node_name, self.EXPLORE_D, length=0.2)
self.g.add_edge(node_name, self.DELETE, length=0.2)
self.BUTTONS = True
def remove_buttons(self):
"""Remove the buttons to change the graph
"""
try:
self.g.remove(self.g.node(self.DELETE))
self.g.remove(self.g.node(self.EXPLORE_D))
self.g.remove(self.g.node(self.EXPLORE_F))
except:
pass
self.BUTTONS=False
def draw(self):
canvas.clear()
background(1)
translate(500, 500)
# With directed=True, edges have an arrowhead indicating the direction of the connection.
# With weighted=True, Node.centrality is indicated by a shadow under high-traffic nodes.
# With weighted=0.0-1.0, indicates nodes whose centrality > the given threshold.
# This requires some extra calculations.
self.g.draw(weighted=0.5, directed=False)
self.g.update(iterations=10)
# Make it interactive!
# When the mouse is pressed, remember on which node.
# Drag this node around when the mouse is moved.
dx = canvas.mouse.x - 500 # Undo translate().
dy = canvas.mouse.y - 500
#global dragged
if canvas.mouse.pressed and not self.dragged:
self.dragged = self.g.node_at(dx, dy)
old_clicked = self.clicked
try:
self.clicked = self.dragged.id
except:
self.clicked = None
if self.clicked != None:
if self.clicked == self.DELETE:
if old_clicked != None:
self.delete_node(old_clicked)
self.remove_buttons()
elif self.clicked == self.EXPLORE_D:
if old_clicked != None:
self.explore_deeper(old_clicked)
self.remove_buttons()
elif self.clicked == self.EXPLORE_F:
if old_clicked != None:
self.explore_further(old_clicked)
self.remove_buttons()
elif self.clicked == self.CLOSE:
self.remove_buttons()
self.remove_close()
self.close_canvas()
else:
self.remove_buttons()
self.remove_close()
self.add_buttons(self.clicked)
else:
if self.BUTTONS:
self.remove_buttons()
elif self.CBUTTON:
self.remove_close()
else:
self.remove_buttons()
self.add_close()
if not canvas.mouse.pressed:
self.dragged = None
if self.dragged:
self.dragged.x = dx
self.dragged.y = dy
def start(self, distance = 30, force = 0.01, repulsion_radius=30):
"""Starts the GUI
"""
#self.g.prune(depth=0) # Remove orphaned nodes with no connections.
self.g.distance = distance # Overall spacing between nodes.
self.g.layout.force = force # Strength of the attractive & repulsive force.
self.g.layout.repulsion = repulsion_radius # Repulsion radius.
canvas.draw = self.draw
canvas.size = 1000, 1000
canvas.run()
if __name__ == '__main__':
gui = GUI()
gui.add_node("a")
gui.add_node("b")
gui.add_edge("a","b")
gui.start(distance=30, repulsion_radius=30)
Related
I want to build some visualizations for searching algorithms (BFS, A* etc.) within a grid.
My solution should show each step of the algorithm using CodeSkulptor simplegui (or the offline version using SimpleGUICS2Pygame.)
I've made a version which highlights all the cells visited by changing their color, but I've run into trouble trying to make the path display step-by-step with a time delay between each step.
I've extracted the essence of the problem and created a minimal example representing it in the code below, also run-able online here: http://www.codeskulptor.org/#user47_jB2CYfNrH2_2.py
What I want is during the change_colors() function, for there to be a delay between each iteration.
CodeSkulptor doesn't have time.sleep() available, and I don't think it would help anyway.
CodeSkulptor does have timers available, which might be one solution, although I can't see how to use one in this instance.
Code below:
import time
try:
import simplegui
except ImportError:
import SimpleGUICS2Pygame.simpleguics2pygame as simplegui
simplegui.Frame._hide_status = True
TITLE = "TEST"
FRAME_WIDTH = 400
FRAME_HEIGHT = 400
DELAY = 10
class Square:
"""This class represents a simple Square object."""
def __init__(self, size, pos, pen_size=2, pen_color="red", fill_color="blue"):
"""Constructor - create an instance of Square."""
self._size = size
self._pos = pos
self._pen_size = pen_size
self._pen_color = pen_color
self._fill_color = fill_color
def set_color(self, color):
self._fill_color = color
def get_color(self):
return self._fill_color
def is_in(self, pos):
"""
Determine whether coordinates are within the area of this Square.
"""
return self._pos[0] < pos[0] < self._pos[0] + self._size and self._pos[1] < pos[1] < self._pos[1] + self._size
def draw(self, canvas):
"""
calls canvas.draw_image() to display self on canvas.
"""
points = [(self._pos[0], self._pos[1]), (self._pos[0] + self._size, self._pos[1]),
(self._pos[0] + self._size, self._pos[1] + self._size), (self._pos[0], self._pos[1] + self._size)]
canvas.draw_polygon(points, self._pen_size, self._pen_color, self._fill_color)
def __str__(self):
return "Square: {}".format(self._pos)
def draw(canvas):
for square in squares:
square.draw(canvas)
def change_colors():
for square in squares:
# time.sleep(1) # Not implemented in CodeSkulptor and would'nt work anyway
square.set_color("green")
frame = simplegui.create_frame(TITLE, FRAME_WIDTH, FRAME_HEIGHT)
frame.set_draw_handler(draw)
width = 20
squares = []
for i in range(10):
squares.append(Square(width, (i * width, 0)))
change_colors()
frame.start()
Any help appreciated.
Yes, you need to use a timer. Something like this:
I = 0
def change_next_color():
if I < len(squares):
squares[I].set_color("green")
global I
I += 1
timer = simplegui.create_timer(1000, change_next_color)
timer.start()
http://www.codeskulptor.org/#user47_udyXzppCdw2OqdI.py
I also replaced
simplegui.Frame._hide_status = True
by simplegui.Frame._hide_controlpanel = True
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._hide_controlpanel
See also _keep_timers option of SimpleGUICS2Pygame to help you:
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._keep_timers
Possible improvements:
Find a better solution that don't use a global counter.
Stop timer when all work is finished.
* I believe the lvl1[(x,y)] = getattr(__import__('mapTiles'), tile_name)(x, y) is causing the problem, I changed it to a direct import with the same problem and the circular import here is... mapTiles imports world and inside the load_tiles() function, it imports mapTiles. I'm not sure how to restructure this to stop the circles, any ideas? *
I'm making a text RPG game, I have all the tiles coded and able to be interacted with but when I go to run the game, it gives me the error below. I can't see a circular import anywhere so I don't understand what's going on. (Please tell me if you need any other code)
Error: (caused by load_tiles() which is called in play)
getattr(__import__('mapTiles'), tile_name)(x, y)
AttributeError: module 'mapTiles' has no attribute 'PlainsTile'
Only showing the base class and the one tile because there are about ten different ones
mapTiles.py
import actions, items, enemies, actions, world
class MapTile:
def __init__(self,name, x, y, intro, description):
self.x = x
self.y = y
self.intro = intro
def intro_text(self):
return self.intro
def terrain_interacts(self, terrain):
# For elemental effects
pass
def randomize_interactions(self, tile):
# Randomize enemy and loot spawns
pass
# Default actions
def adjacent_moves(self):
# Returns all move actions for adjacent tiles
moves = []
if world.tile_exists(self.x + 1, self.y):
moves.append(actions.MoveEast())
if world.tile_exists(self.x - 1, self.y):
moves.append(actions.MoveWest())
if world.tile_exists(self.x, self.y - 1):
moves.append(actions.MoveNorth())
if world.tile_exists(self.x, self.y + 1):
moves.append(actions.MoveSouth())
moves.append(actions.ViewInventory)
return moves
def available_actions(self):
# Returns all available actions for the current tile
moves = self.adjacent_moves()
return moves
class PlainsTile(MapTile):
def __init__(self, x, y):
self.name = "PlainsTile"
self.intro = "You enter a plains"
self.description = "A span of clear land, with the tall grass waving in the wind"
super().__init__(
name=self.name,
intro=self.intro,
description=self.description,
x=self.x,
y=self.y
)
play.py
#play.py
import character, world
def play_game(player):
world.load_tiles()
print("Called")
#These lines load the starting room and display the text
tile = world.tile_exists(x=player.location_x, y=player.location_y)
if tile != None:
print(tile)
while player.is_alive() and not player.victory:
tile = world.tile_exists(player.location_x, player.location_y)
# Check again since the room could have changed the player's state
if player.is_alive() and not player.victory:
print("Choose an action:\n")
last[0] = player.location_x
last[1] = player.location_y
if tile != None:
available_actions = tile.available_actions()
if tile.name == 'BossTile' and player.victory:
tile.modify_character(player)
for action in available_actions:
print(available_actions.name)
for action in available_actions:
action_input = input("Choose an action ya prick: ")
if action_input == "quit":
quit()
if action_input == action.keyword:
player.do_action(action, **action.kwargs)
break
else:
print("Please choose one of the listed actions")
else:
print("You cannot go that way")
world.py
# No imports
lvl1 = {}
def load_tiles():
"""Parses a file that describes the world space into the _world object"""
with open('m1.txt', 'r') as f:
rows = f.readlines()
x_max = len(rows[0].split('\t')) # Assumes all rows contain the same number of tabs
for y in range(len(rows)):
cols = rows[y].split('\t')
for x in range(x_max):
tile_name = cols[x].replace('\n', '') # Windows users may need to replace '\r\n'
if tile_name == 'StartingRoom':
global starting_position
starting_position = (x, y)
if tile_name == '':
lvl1[(x, y)] = None
else:
getattr(__import__('mapTiles'), tile_name)(x, y)
def tile_exists(x,y):
return lvl1.get((x,y))
imports from all other files
#items.py
#No imports
#actions.py
from play import player
import items
#enemies.py
import random
#character.py
import world, items, random
I fixed it, the error was the import from getattr. I merged the two files, added a try/except to catch index errors and added a bunch of conditionals that instantiate the tile name at whatever coordinates.
I tried to get an event if the windows visibility is changed.
I found out that there is an event called "Visibility".
The Operating System is Windows 64bit.
So I implemented in the following way:
root.bind('<Visibility>', visibilityChanged)
But I always got the state "VisibilityUnobscured" no matter if there is a window over it or not. What is the normal behaviour of this event? How can I implement a feature like that?
Example Prog:
import tkinter as tk
class GUI:
def __init__(self, master):
self.master = master
master.title("Test GUI")
self.master.bind('<Visibility>', self.visibilityChanged)
self.label = tk.Label(master, text="GUI")
self.label.pack()
self.close_button = tk.Button(master, text="Close", command=master.quit)
self.close_button.pack()
def visibilityChanged(self, event):
if (str(event.type) == "Visibility"):
print(event.state)
root = tk.Tk()
my_gui = GUI(root)
root.mainloop()
What is the normal behaviour of this event?
It's well described in the docs:The X server generates VisibilityNotify event whenever the visibility changes state and for any window.
How can I implement a feature like that?
It depends on how far you are going to go in your wishes, since this isn't a trivial task. Thus, don't treat that answer as a complete solution, but as a problem overview and a set of suggestions.
The event problem
Windows OS uses a message-passing model - the system communicates with your application window via messages, where each message is a numeric code that designates a particular event. Application window has an associated window procedure — a function that processes (responds or ignores) all messages sent.
The most generic solution is to set a hook to catch certain events/messages and it's possible either via SetWindowsHookEx or pyHook.
The main problem is to get event, because the Windows WM has no such message as VisibilityNotify. As I said in comment section - one option, on which we can rely, is the z-order
(there's possibility to check Visibility of the window, whenever this window changes it's position in z-order).Therefore our target message is either WM_WINDOWPOSCHANGING or WM_WINDOWPOSCHANGED.
A naive implementation:
import ctypes
import ctypes.wintypes as wintypes
import tkinter as tk
class CWPRETSTRUCT(ctypes.Structure):
''' a class to represent CWPRETSTRUCT structure
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644963(v=vs.85).aspx '''
_fields_ = [('lResult', wintypes.LPARAM),
('lParam', wintypes.LPARAM),
('wParam', wintypes.WPARAM),
('message', wintypes.UINT),
('hwnd', wintypes.HWND)]
class WINDOWPOS(ctypes.Structure):
''' a class to represent WINDOWPOS structure
https://msdn.microsoft.com/en-gb/library/windows/desktop/ms632612(v=vs.85).aspx '''
_fields_ = [('hwnd', wintypes.HWND),
('hwndInsertAfter', wintypes.HWND),
('x', wintypes.INT),
('y', wintypes.INT),
('cx', wintypes.INT),
('cy', wintypes.INT),
('flags', wintypes.UINT)]
class App(tk.Tk):
''' generic tk app with win api interaction '''
wm_windowposschanged = 71
wh_callwndprocret = 12
swp_noownerzorder = 512
set_hook = ctypes.windll.user32.SetWindowsHookExW
call_next_hook = ctypes.windll.user32.CallNextHookEx
un_hook = ctypes.windll.user32.UnhookWindowsHookEx
get_thread = ctypes.windll.kernel32.GetCurrentThreadId
get_error = ctypes.windll.kernel32.GetLastError
get_parent = ctypes.windll.user32.GetParent
wnd_ret_proc = ctypes.WINFUNCTYPE(ctypes.c_long, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)
def __init__(self):
''' generic __init__ '''
super().__init__()
self.minsize(350, 200)
self.hook = self.setup_hook()
self.protocol('WM_DELETE_WINDOW', self.on_closing)
def setup_hook(self):
''' setting up the hook '''
thread = self.get_thread()
hook = self.set_hook(self.wh_callwndprocret, self.call_wnd_ret_proc, wintypes.HINSTANCE(0), thread)
if not hook:
raise ctypes.WinError(self.get_error())
return hook
def on_closing(self):
''' releasing the hook '''
if self.hook:
self.un_hook(self.hook)
self.destroy()
#staticmethod
#wnd_ret_proc
def call_wnd_ret_proc(nCode, wParam, lParam):
''' an implementation of the CallWndRetProc callback
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644976(v=vs.85).aspx'''
# get a message
msg = ctypes.cast(lParam, ctypes.POINTER(CWPRETSTRUCT)).contents
if msg.message == App.wm_windowposschanged and msg.hwnd == App.get_parent(app.winfo_id()):
# if message, which belongs to owner hwnd, is signaling that windows position is changed - check z-order
wnd_pos = ctypes.cast(msg.lParam, ctypes.POINTER(WINDOWPOS)).contents
print('z-order changed: %r' % ((wnd_pos.flags & App.swp_noownerzorder) != App.swp_noownerzorder))
return App.call_next_hook(None, nCode, wParam, lParam)
app = App()
app.mainloop()
As you can see, this implementation has a similar behavior as a "broken" Visibility event.
This problem stems from the fact, that you can catch only thread-specified messages, hence application doesn't know about changes in the stack. It's just my assumptions, but I think that the cause of the broken Visibility is same.
Of course, we can setup a global hook for all messages, regardless a thread, but this approach requires a DLL injection, which is an another story for sure.
The visibility problem
It's not a problem to determine obscuration of the window, since we can rely on Graphical Device Interface.
The logic is simple:
Represent window (and each visible window, which is higher in the z-order) as a rectangle.
Subtract from main rectangle each rectangle and store result.
If final geometrical subtraction is:
... an empty rectangle — return 'VisibilityFullyObscured'
... a set of rectangles — return 'VisibilityPartiallyObscured'
... a single rectangle:
if geometrical difference between result and original rectangle is:
... an empty rectangle — return 'VisibilityUnobscured'
... a single rectangle — return 'VisibilityPartiallyObscured'
A naive implementation (with self-scheduled loop):
import ctypes
import ctypes.wintypes as wintypes
import tkinter as tk
class App(tk.Tk):
''' generic tk app with win api interaction '''
enum_windows = ctypes.windll.user32.EnumWindows
is_window_visible = ctypes.windll.user32.IsWindowVisible
get_window_rect = ctypes.windll.user32.GetWindowRect
create_rect_rgn = ctypes.windll.gdi32.CreateRectRgn
combine_rgn = ctypes.windll.gdi32.CombineRgn
del_rgn = ctypes.windll.gdi32.DeleteObject
get_parent = ctypes.windll.user32.GetParent
enum_windows_proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
def __init__(self):
''' generic __init__ '''
super().__init__()
self.minsize(350, 200)
self.status_label = tk.Label(self)
self.status_label.pack()
self.after(100, self.continuous_check)
self.state = ''
def continuous_check(self):
''' continuous (self-scheduled) check '''
state = self.determine_obscuration()
if self.state != state:
# mimic the event - fire only when state changes
print(state)
self.status_label.config(text=state)
self.state = state
self.after(100, self.continuous_check)
def enumerate_higher_windows(self, self_hwnd):
''' enumerate window, which has a higher position in z-order '''
#self.enum_windows_proc
def enum_func(hwnd, lParam):
''' clojure-callback for enumeration '''
rect = wintypes.RECT()
if hwnd == lParam:
# stop enumeration if hwnd is equal to lParam (self_hwnd)
return False
else:
# continue enumeration
if self.is_window_visible(hwnd):
self.get_window_rect(hwnd, ctypes.byref(rect))
rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)
# append region
rgns.append(rgn)
return True
rgns = []
self.enum_windows(enum_func, self_hwnd)
return rgns
def determine_obscuration(self):
''' determine obscuration via CombineRgn '''
hwnd = self.get_parent(self.winfo_id())
results = {1: 'VisibilityFullyObscured', 2: 'VisibilityUnobscured', 3: 'VisibilityPartiallyObscured'}
rgns = self.enumerate_higher_windows(hwnd)
result = 2
if len(rgns):
rect = wintypes.RECT()
self.get_window_rect(hwnd, ctypes.byref(rect))
# region of tk-window
reference_rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)
# temp region for storing diff and xor rgn-results
rgn = self.create_rect_rgn(0, 0, 0, 0)
# iterate over stored results
for _ in range(len(rgns)):
_rgn = rgn if _ != 0 else reference_rgn
result = self.combine_rgn(rgn, _rgn, rgns[_], 4)
self.del_rgn(rgns[_])
if result != 2:
# if result isn't a single rectangle
# (NULLREGION - 'VisibilityFullyObscured' or COMPLEXREGION - 'VisibilityPartiallyObscured')
pass
elif self.combine_rgn(rgn, reference_rgn, rgn, 3) == 1:
# if result of XOR is NULLREGION - 'VisibilityUnobscured'
result = 2
else:
# 'VisibilityPartiallyObscured'
result = 3
# clear up regions to prevent memory leaking
self.del_rgn(rgn)
self.del_rgn(reference_rgn)
return results[result]
app = App()
app.mainloop()
Unfortunately, this approach is far from a working solution, but it's tweakable in a perspective.
I guess my question is pretty much summed up in the title.
I am using an update call (similar to the one in the Pong tutorial). Within this call I update the points of a line. Though I can check that the points are indeed being updated, the actual line drawing is not.
I'll put some of the code up here:
class GraphInterface(Widget):
node = ObjectProperty(None)
def update(self, dt):
for widget in self.children:
if isinstance(widget, GraphEdge) and widget.collide_widget(self):
widget.check_connection()
class GraphEdge(Widget):
r = NumericProperty(1.0)
#determines if edge has an attached node
connected_point_0 = Property(False)
connected_point_1 = Property(False)
#provides details of the attached node
connected_node_0 = Widget()
connected_node_1 = Widget()
def __init__(self, **kwargs):
super(GraphEdge, self).__init__(**kwargs)
with self.canvas:
Color(self.r, 1, 1, 1)
self.line = Line(points=[100, 200, 200, 200], width = 2.0, close = True)
def snap_to_node(self, node):
if self.collide_widget(node):
if (self.connected_point_1 is False):
print "collision"
self.connected_point_1 = True
self.connected_node_1 = node
del self.line.points[-2:]
self.line.points[-2:]+=node.center
self.size = [math.sqrt(((self.line.points[0]-self.line.points[2])**2 + (self.line.points[1]-self.line.points[3])**2))]*2
self.center = ((self.line.points[0]+self.line.points[2])/2,(self.line.points[1]+self.line.points[3])/2)
return True
pass
The idea is to check for collisions initially, and once a collision has been made, I attach the line to this node widget. The points are then update as I move the node around. However right now although the points are updated, the drawing of the line is not.
If you need anymore code or information please ask.
del self.line.points[-2:]
self.line.points[-2:]+=node.center
These lines bypass operations that set the property, so the VertexInstruction doesn't know anything has changed and doesn't redraw itself.
They're a bit strange anyway, it would be simpler to just write:
self.line.points = self.line.points[:-2] + node.center
This would also update the instruction graphics, because you set the property directly rather than only modifying the existing list.
I created my own version of a Dialog class that is being used as a parent class for my different dialog windows that have custom fields. At the bottom is the code for the Dialog class. In one of the Dialog type windows there is a Combobox (drop down box) that allows the user to also enter their own value. If they enter their own value (their name) and it isn't contained in the list of customers (from a file) it asks the user if they want to add a new customer. If they select yes I want it to pass the name they entered up to the parent window (the root of the application), close the dialog box where they entered the name, and open a different dialog to enter the new customer's info (containing the name they entered before). Every part of this works except passing the value up. At first I attempted to pass the value up using a custom exception, which doesn't work because of how Tkinter handles exceptions.
How should I pass this value up? My initial thought would be through a custom Tkinter Event. I can't find information on how to do that though.
Root Method creating first child dialog:
def check_in(self):
log_diag = LoggerDialog(self,self.customers)
try:
log_diag.show()
except NewCustomerException, (instance):
self.new_customer(str(instance))
except:
print instance
Inside Child Dialog Box:
def new_customer_error(self):
#parse name and preset values in the new customer dialog
name = self.name.get()
if askquestion(title="New Customer?",
message="Add new customer: " + name,
parent = self.root) == 'yes':
raise NewCustomerException(name)
Dialog class:
class Dialog:
def __init__(self, master, title, class_=None, relx=0.5, rely=0.3):
self.master = master
self.title = title
self.class_ = class_
self.relx = relx
self.rely = rely
def setup(self):
if self.class_:
self.root = Toplevel(self.master, class_=self.class_)
else:
self.root = Toplevel(self.master)
self.root.title(self.title)
self.root.iconname(self.title)
def enable(self):
### enable
self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
self._set_transient(self.relx, self.rely)
self.root.wait_visibility()
self.root.grab_set()
self.root.mainloop()
self.root.destroy()
def _set_transient(self, relx=0.5, rely=0.3):
widget = self.root
widget.withdraw() # Remain invisible while we figure out the geometry
widget.transient(self.master)
widget.update_idletasks() # Actualize geometry information
if self.master.winfo_ismapped():
m_width = self.master.winfo_width()
m_height = self.master.winfo_height()
m_x = self.master.winfo_rootx()
m_y = self.master.winfo_rooty()
else:
m_width = self.master.winfo_screenwidth()
m_height = self.master.winfo_screenheight()
m_x = m_y = 0
w_width = widget.winfo_reqwidth()
w_height = widget.winfo_reqheight()
x = m_x + (m_width - w_width) * relx
y = m_y + (m_height - w_height) * rely
if x+w_width > self.master.winfo_screenwidth():
x = self.master.winfo_screenwidth() - w_width
elif x < 0:
x = 0
if y+w_height > self.master.winfo_screenheight():
y = self.master.winfo_screenheight() - w_height
elif y < 0:
y = 0
widget.geometry("+%d+%d" % (x, y))
widget.deiconify() # Become visible at the desired location
def wm_delete_window(self):
self.root.quit()
I tried using generate_event() and passing a custom parameter (name) and I get this error:
TclError: bad option "-name": must be -when, -above, -borderwidth,
-button, -count, -data, -delta, -detail, -focus, -height, -keycode,
-keysym, -mode, -override, -place, -root, -rootx, -rooty, -sendevent,
-serial, -state, -subwindow, -time, -warp, -width, -window, -x, or -y
I've tried overriding the value of a few of these and I keep getting errors back telling me that I've passed the wrong type. (mode is an int, detail has to be an instance of a specific list of objects) This does not seem to be a very elegant solution.
Here is the solution I settled on:
Code in the child object (for handling a name not in database):
def new_customer_error(self):
if askquestion(title="New Customer?",
message="Add new customer: " + self.name.get(),
parent = self.root) == 'yes':
self.master.event_generate('<<NewCustomer>>')
In __init__ of root window:
self.bind('<<NewCustomer>>',self.new_customer)
Later on in root object:
def new_customer(self, event=None):
#if evert this came as a new customer entry through check in
if event:
temp = self.logger_diag.name.get().split(' ')
self.newc_diag.fname.set(temp[0])
if len(temp) == 2:
self.newc_diag.lname.set(temp[1])
elif len(temp) == 3:
self.newc_diag.mname.set(temp[1])
self.newc_diag.lname.set(temp[2])
elif len(temp) > 3:
self.newc_diag.mname.set(temp[1])
self.newc_diag.lname.set(' '.join(temp[2:4]))