Adding functionality to the remaining buttons


We will now code the features related to the remaining toolbar buttons:

Specifically, we will code the following functions: draw_text, delete_itemfill_item, duplicate_item, move_to_top, drag_item, enlarge_item_size, and reduce_item_size.

Let's start with the code for draw_text. When a user clicks on the draw_text button, we want to show the following options in the top bar:

The user can enter text in the textbox and specify its font size and fill color. Once the user presses the Go button, the text appears on the center of the canvas.

Let us, therefore, define the draw_text_options method as follows (see code 6.08.py):

def draw_text_options(self):
Label(self.top_bar, text='Text:').pack(side="left")
self.text_entry_widget = Entry(self.top_bar, width=20)
self.text_entry_widget.pack(side="left")
Label(self.top_bar, text='Font size:').pack(side="left")
self.font_size_spinbox = Spinbox(self.top_bar, from_=14, to=100, width=3)
self.font_size_spinbox.pack(side="left")
self.create_fill_options_combobox()
self.create_text_button = Button(self.top_bar,
text="Go", command=self.on_create_text_button_clicked)
self.create_text_button.pack(side="left", padx=5)

The preceding code is self-explanatory. The Go button is attached to a command callback named on_create_text_button_clicked, which is defined as follows (see code 6.08.py)

def on_create_text_button_clicked(self):
entered_text = self.text_entry_widget.get()
center_x = self.canvas.winfo_width()/2
center_y = self.canvas.winfo_height()/2
font_size = self.font_size_spinbox.get()
self.canvas.create_text(center_x, center_y, font=("", font_size),
text=entered_text, fill=self.fill)

Our draw_text method is now functional. Next, let's code the delete_item method.

The operations that we want to do now are slightly different from their predecessors. Earlier, we were creating items on the canvas. Now we have to target items already present on the canvas.

The item that needs to be targeted is the one on which the user clicks with their mouse. Fortunately, getting the item handle for the item under the mouse is very easy using the current tag.

Accordingly, the code for delete_item is as follows (see code 6.08.py):

def delete_item(self):
self.current_item = None
self.canvas.delete("current")

Now, if you select the Delete button from the toolbar and click on any item on the canvas, that item is deleted.

Next, let's code the fill_item and fill_item_options methods (see code 6.08.py):

def fill_item(self):
try:
self.canvas.itemconfig("current", fill=self.fill, outline=self.outline)
except TclError:
self.canvas.itemconfig("current", fill=self.fill)

We had to use a try…except block because some canvas items such as lines and text do not have an outline option:

def fill_item_options(self):
self.create_fill_options_combobox()
self.create_outline_options_combobox()

Next, we code the duplicate_item method. In order to duplicate an item we need to know three things:

  • Type of item—if the item is a lineovalarcrectangle, or polygon
  • The coordinates for the item
  • The configurations of the item

We can get the type of item as a string using the type method as follows: canvas.type(item_specifier)

This returns a string such as line, oval, arc, rectangle, or polygon. In order to recreate an item of the same type, we need to append the string create_ to the returned type and call the method.

The coordinates of a given item can be obtained by calling the coordinates method as follows: 

coordinates = canvas.coords("item_specifier")

The configurations for an item can be obtained as a dictionary using the following command: 

canvas.itemconfig(item_specifier)

This returns all the configurations for an item, whether specified or not specified. For example, here's a sample of a dictionary returned by calling the preceding method on a canvas item:

{'outline': ('outline', '', '', 'black', 'red'), 'outlinestipple':
('outlinestipple', '', '', '', ''), 'activestipple':
('activestipple', '', '', '', ''), 'state': ('state', '', '',
'', ''), 'offset': ('offset', '', '', '0,0', '0,0'),
'activefill': ('activefill', '', '', '', ''), 'disabledwidth':
('disabledwidth', '', '', '0.0', '0'), 'disabledoutlinestipple':
('disabledoutlinestipple', '', '', '', ''), 'outlineoffset':
('outlineoffset', '', '', '0,0', '0,0'), 'width': ('width', '',
'', '1.0', '2.0'), 'disabledfill': ('disabledfill', '', '', '',
''), 'disabledoutline': ('disabledoutline', '', '', '', ''),
'dash': ('dash', '', '', '', ''), 'disableddash':
('disableddash', '', '', '', ''), 'disabledstipple':
('disabledstipple', '', '', '', ''), 'tags': ('tags', '', '',
'', 'current'), 'stipple': ('stipple', '', '', '', ''),
'activewidth': ('activewidth', '', '', '0.0', '0.0'),
'activedash': ('activedash', '', '', '', ''), 'dashoffset':
('dashoffset', '', '', '0', '0'), 'activeoutlinestipple':
('activeoutlinestipple', '', '', '', ''), 'activeoutline':
('activeoutline', '', '', '', ''), 'fill': ('fill', '', '', '',
'red')}

Clearly, we do not require those configuration values that are empty or zero. We, therefore, write a method that filters out all unnecessary configurations:

def get_all_configurations_for_item(self):
configuration_dict = {}
for key, value in self.canvas.itemconfig("current").items():
if value[-1] and value[-1] not in ["0", "0.0", "0,0", "current"]:
configuration_dict[key] = value[-1]
return configuration_dict

Now that we know how to fetch all required elements to duplicate a canvas item, here's the code for duplicate_item (see code 6.08.py):

def duplicate_item(self):
try:
function_name = "create_" + self.canvas.type("current")
except TypeError:
return
coordinates = tuple(map(lambda i: i+10, self.canvas.coords("current")))
configurations = self.get_all_configurations_for_item()
self.canvas_function_wrapper(function_name, coordinates, configurations)

Finally, the last line calls a wrapper function that actually runs the function that duplicates the canvas item (see code 6.08.py):

def canvas_function_wrapper(self, function_name, *arg, **kwargs):
func = getattr(self.canvas, function_name)
func(*arg, **kwargs)

Now, if you create an item, select the duplicate item button, and click on the item, a duplicate item is created. However, since we do not want the duplicate item to be created exactly on top of the existing item, we offset its coordinates by 10 pixels from the coordinates of the item being duplicated. This offsetting is done in the line:

coordinates = tuple(map(lambda i: i+10, self.canvas.coords("current")))

Now, if you create an item on the canvas, select the duplicate item button, and click on the item, its duplicate is created at an offset of 10 pixels from the original item.

Next, we code the move_to_top method. We have already discussed that items added to the canvas are added on top of each other. What if we want to move an item previously added to the canvas? The following figure shows what it means to move an item on top of another:

We use the tag_raise and tag_lower methods to move items higher and lower in the stack. We use tag_raise to define the move_to_top method as follows (see code 6.08.py):

def move_to_top(self):
self.current_item = None
self.canvas.tag_raise("current")

The preceding code raises the clicked item highest up in the item's stack.

When you draw multiple items on the canvas, the items are placed in a stack. By default, new items get added on top of items previously drawn on the canvas. You can, however, change the stacking order using:
canvas.tag_raise(item).
If multiple items match, they are all moved, with their relative order preserved. However, this method will not change the stacking order for any new window item that you draw within the canvas.
Then there are the find_above and find_below methods that you can use to find items above or below an item in the canvas stacking order.

Next, we will define the drag_item method. This method uses the move method to change the coordinates of a given item (see code 6.08.py):

def drag_item(self):
self.canvas.move("current", self.end_x - self.start_x, self.end_y - self.start_y)
self.canvas.bind("<B1-Motion>", self.drag_item_update_x_y)

def drag_item_update_x_y(self, event):
self.start_x, self.start_y = self.end_x, self.end_y
self.end_x, self.end_y = event.x, event.y
self.drag_item()

Since we want the drag to occur continuously and not as a jump from one place to another, we temporarily bind the mouse binding to update the start and end coordinates like we did when we defined the draw_irregular_line method.

Finally, we define two methods to enlarge and reduce item size. We will use the canvas.scale method to increase and reduce item size by 20%:

def enlarge_item_size(self):
self.current_item = None
if self.canvas.find_withtag("current"):
self.canvas.scale("current", self.end_x, self.end_y, 1.2, 1.2)
self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL))

def reduce_item_size(self):
self.current_item = None
if self.canvas.find_withtag("current"):
self.canvas.scale("current", self.end_x, self.end_y, .8, .8)
self.canvas.config(scrollregion=self.canvas.bbox(tk.ALL))

Note that, immediately upon item resize, we reconfigure the scroll region option to update the scroll bar.

The bbox method returns the bounding box for an item. The syntax is:  .canvas.bbox(item_specifier). This returns the bounding box as a tuple of length 4. If the item-specifier is omitted, the bounding box for all items is returned.

Note that bounding box values are approximate and may differ from the real value by a few pixels.

This concludes the iteration. All the buttons in the left toolbar are now functional (see code 6.08.py).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.84.169