We edit (change) a shape drawn using the mouse after the drawing is finished.
To limit the complexity and length of the code, we have excluded the facilities provided in the previous recipe for storing and recalling the drawn shape. So for this recipe no storage folders will be used.
# mouse_shape_editor_1.py #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> from Tkinter import * import math root = Tk() root.title("Left drag to draw, right to re-position.") cw = 600 # canvas width ch = 650 # canvas height chart_1 = Canvas(root, width=cw, height=ch, background="#ffffff") chart_1.grid(row=1, column=1) linedrag = {'x_start':0, 'y_start':0, 'x_end':0, 'y_end':0} map_distance = 0 dist_meter = 0 x_initial = 0 y_initial = 0 #============================================== # Adjust the distance between points if desired way_points = 50 # Distance between editable way-points #============================================== magic_circle_flag = 0 # 0-> normal dragging, 1 -> double-click: # Pull point. point_num = 0 x0 = [] y0 = [] #================================================ def separation(x_now, y_now, x_dot, y_dot): # DISTANCE MEASUREMENT # Distance to points - used to find out if the mouse # clicked inside a circle sum_squares = (x_now - x_dot)**2 + (y_now -y_dot)**2 distance= int(math.sqrt(sum_squares)) # Get Pythagorean # distance return( distance) #================================================ # CALLBACK EVENT PROCESSING FUNCTIONS def callback_1(event): # LEFT DOWN global x_initial, y_initial x_initial = event.x y_initial = event.y def callback_2(event): # LEFT DRAG global x_initial, y_initial global map_distance, dist_meter global x0, y0 linedrag['x_start'] = linedrag['x_end'] # update positions linedrag['y_start'] = linedrag['y_end'] linedrag['x_end'] = event.x linedrag['y_end'] = event.y increment = separation(linedrag['x_start'],linedrag['y_start'], linedrag['x_end'], linedrag['y_end'] ) map_distance += increment # Total distance - # potentiasl use as a map odometer. dist_meter += increment # Distance from last circle if dist_meter>way_points: # Action at way-points x0.append(linedrag['x_end']) # append to line y0.append(linedrag['y_end']) xb = linedrag['x_end'] - 5 ; yb = linedrag['y_end'] - 5 # Centre circle on line x1 = linedrag['x_end'] + 5 ; y1 = linedrag['y_end'] + 5 chart_1.create_oval(xb,yb, x1,y1, outline = "green") dist_meter = 0 # re-zero the odometer. linexy = [ x_initial, y_initial, linedrag['x_end'] , linedrag['y_end'] ] chart_1.create_line(linexy, fill='green') x_initial = linedrag['x_end'] # start of next segment y_initial = linedrag['y_end'] def callback_5(event): # RIGHT CLICK global point_num, magic_circle_flag, x0, y0 # Measure distances to each point in turn, determine if any are # inside magic circle. # That is, identify which point has been clicked on. for i in range(0, len(x0)): d = separation(event.x, event.y, x0[i], y0[i]) if d <= 5: point_num = i # this is the index that controls editing magic_circle_flag = 1 chart_1.create_oval(x0[i] - 10,y0[i] - 10, x0[i] + 10, y0[i] + 10 , width = 4, outline = "#ff8800") x0[i] = event.x y0[i] = event.y def callback_6(event): # RIGHT RELEASE global point_num, magic_circle_flag, x0, y0 if magic_circle_flag == 1: # The point is going to be # repositioned. x0[point_num] =event.x y0[point_num] =event.y chart_1.delete(ALL) chart_1.update() # Refreshes the drawing on the # canvas. q=[] for i in range(0,len(x0)): q.append(x0[i]) q.append(y0[i]) chart_1.create_oval(x0[i] - 5,y0[i] - 5, x0[i] + 5, y0[i] + 5 , outline = "#00ff00") chart_1.create_line(q , fill = "#ff00ff") # Now show the # new positions magic_circle_flag = 0 #============================== chart_1.bind("<Button-1>", callback_1) # <Button-1> ->LEFT mouse # button chart_1.bind("<B1-Motion>", callback_2) chart_1.bind("<Button-3>", callback_5) # <Button-3> ->RIGHT mouse # button chart_1.bind("<ButtonRelease-3>", callback_6) root.mainloop()
The preceding program now includes:
callback
functions to deal with left and right mouse clicks and drags.A distance-measuring function separation(x_now, y_now, x_dot, y_dot)
. When the right mouse button is clicked, the distance to every line joint is measured. If one of these distances is inside an existing joint then an orange circle is drawn and control is passed to callback_6
which updates the coordinates of the new point and refreshes the revised drawing. The decision on whether to move a point or not is decided by the value of the magic_circle_flag
. The state of this flag is determined by the distance computed by separation()
. It is set to 1
if the distance measurement finds it inside a joint when the right mouse is pressed and set to 0
after a point has been moved.
Now that we have a means to control and adjust the drawing of lines and curves using mouse manipulation, other possibilities are opened up.
It would be good to extend the features of this program to include:
The list will grow longer as we work on the extensions. In the end, we will have created a useful vector graphics editor and the pressure would be on to match features of existing proprietary and open-source editors. Why re-invent the wheel? What may bear more fruit would be an effort to work with vector images produced by an existing mature vector editor, if this is a practical option.
In the next chapter, we explore ways and means of using vector images from the open-source vector graphics editor Inkscape. Inkscape is able to export images in a wide choice of formats including a standardized web format called Scaled Vector Graphics or SVG for short.
This chapter has made much use of the mouse as a user-interaction tool for drawing shapes on Tkinter canvasses. To complete the job of acquiring the know-how of using the mouse to its fullest the next recipe will be an examination of the full toolkit of mouse interactions.
In the code, there is a variable called map_distance
that has not been used. It can be used to trace the distance travelled on meandering paths on maps. The idea is that if we wanted to measure distances on unmarked paths and roads on something like a Google map, we would be able to adapt this recipe to the task.
3.148.104.242