Module tkinter_qu.gui_components.vim_grid
Expand source code
from tkinter import messagebox
from tkinter_qu.gui_components.grid import Grid
from tkinter_qu.gui_components.input_field import InputField
from tkinter_qu.base.important_variables import WINDOW
from tkinter_qu.base import important_variables
from tkinter_qu.base.utility_functions import add_key_binding
class VimGrid:
""" A Grid of Items that you can navigate through using vim key bindings (hjklio). You can turn on the navigation mode
by hitting the caps lock key"""
current_column = 0
current_row = 0
is_selected = False
items = []
grid = None
dimensions = None
in_use = False
can_edit_grid = True
class States:
INPUT = "INPUT",
MOVING_BETWEEN_INPUT_FIELDS = "MOVING_BETWEEN_INPUT_FIELDS",
NOTHING = "NOTHING",
current_state = States.INPUT
def __init__(self, items, dimensions, grid_rows, grid_columns, can_edit_grid=True):
"""Initializes all the bindings"""
self.can_edit_grid = can_edit_grid
add_key_binding("<KeyPress-h>", lambda event: self.change_input_field_selection("Left"))
add_key_binding("<KeyPress-b>", lambda event: self.change_input_field_selection("Beginning"))
add_key_binding("<KeyPress-j>", lambda event: self.change_input_field_selection("Down"))
add_key_binding("<KeyPress-k>", lambda event: self.change_input_field_selection("Up"))
add_key_binding("<KeyPress-l>", lambda event: self.change_input_field_selection("Right"))
add_key_binding("<KeyPress-o>", lambda event: self.change_input_field_selection("End"))
add_key_binding("<KeyPress-Tab>", lambda event: self.change_input_field_selection("Right"))
add_key_binding("<KeyPress-i>", lambda event: self.change_state(self.States.INPUT))
add_key_binding("<KeyPress-Escape>", lambda event: self.change_state(self.States.MOVING_BETWEEN_INPUT_FIELDS))
add_key_binding("<KeyPress-s>", lambda event: self.change_state(self.States.NOTHING))
add_key_binding("<KeyPress-a>", lambda event: self.add_row(self.current_row + 1))
add_key_binding("<KeyPress-A>", lambda event: self.add_row(self.current_row))
add_key_binding("<KeyPress-d>", lambda event: self.delete_row(self.current_row))
add_key_binding("<KeyPress-D>", lambda event: self.delete_row(self.current_row - 1))
add_key_binding("<KeyPress-c>", lambda event: self.change_text())
self.grid = Grid(dimensions, grid_rows, grid_columns)
self.items = items
for item in self.items:
item: InputField = item
item.set_command(self.handle_input_field_click)
self.dimensions = dimensions
self.grid.turn_into_grid(items, None, None)
def get_item_row(self, row_number):
"""
Returns:
list[Component]: the items in the current row"""
# By multiplying the row number by row_length, the row start is gotten. Adding row length will bring it to the end of the row
start = row_number * self.row_length
return self.items[start: start + self.row_length + 1]
@property
def grid_rows(self):
return self.grid.get_rows(len(self.items))
@property
def grid_columns(self):
return self.grid.get_columns(len(self.items))
def change_input_field_selection(self, event):
""" Changes the selected input field depending on what key was pressed
Args:
event(str): the event name. Here are all the possible values:
'Up': Selects the next input field above the current one (belongs to the previous way point)
'Down': Selects the next input field below the current one (belongs to the next way point)
'Left': Selects the next input field to the right of the current one (belongs to the current way point)
'Right': Selects the previous input field to the left the current one (belongs to the current way point)
Returns:
None
"""
valid_event_types = ["Up", "Down", "Left", "Right", "Beginning", "End"]
if not valid_event_types.__contains__(event):
raise ValueError("Valid Data was not inputted!!!")
if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS or not self.in_use:
return
# Modifying the numbers, so the selected input_field is not out of bounds of the input_fields on the screen
max_column_number = self.grid_columns - 1
max_row_number = self.grid_rows - 1
if event == "Up":
self.current_row -= 1
if event == "Down":
self.current_row += 1
if event == "Left":
self.current_column -= 1
if event == "Right":
self.current_column += 1
if event == "Beginning":
self.current_column = 0
if event == "End":
self.current_column = max_column_number
# Column Checks
if self.current_column > max_column_number:
self.current_column = 0
self.current_row += 1
if self.current_column < 0:
self.current_column = max_column_number
self.current_row -= 1
# Row Checks
if self.current_row < 0:
self.current_row = max_row_number
if self.current_row > max_row_number:
self.current_row = 0
current_item = self.get_current_item()
if current_item.is_editable:
current_item.focus_force()
else:
self.change_input_field_selection(event)
def change_state(self, new_state):
"""Changes the state of the VIM grid"""
valid_new_states = [self.States.MOVING_BETWEEN_INPUT_FIELDS, self.States.INPUT, self.States.NOTHING]
if not valid_new_states.__contains__(new_state):
raise ValueError("Valid Data was not inputted!!!")
if self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS and new_state == self.States.INPUT:
self.current_state = self.States.INPUT
elif self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS and new_state == self.States.NOTHING:
self.current_state = self.States.NOTHING
elif new_state == self.States.MOVING_BETWEEN_INPUT_FIELDS:
self.current_state = self.States.MOVING_BETWEEN_INPUT_FIELDS
important_variables.input_is_allowed = self.current_state == self.States.INPUT
def add_row(self, row_number):
"""Adds a row to the grid at the row number provided"""
if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS:
return
if not self.in_use or not self.can_edit_grid:
return
current_items_start_index = self.current_row * self.grid_columns
current_items_end_index = current_items_start_index + self.grid_columns - 1
current_items = self.items[current_items_start_index: current_items_end_index + 1]
copied_current_items = [item.get_copy() for item in current_items]
new_index = row_number * self.grid_columns
if new_index < 0 or new_index > len(self.items):
messagebox.showerror("ERROR", "This action can't be completed because the row_number is either too small or large")
return
self.items = self.items[:new_index] + copied_current_items + self.items[new_index:]
self.grid.turn_into_grid(self.items, None, None)
def delete_row(self, row_number, force=False):
"""Deletes the specified row from the grid"""
if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS and not force:
return
if not self.in_use or not self.can_edit_grid:
return
for x in range(self.grid_columns):
end_index = self.grid_columns * row_number + self.grid_columns - 1 # Indexes and lengths are offset by one
index = end_index - x
self.items[index].destroy()
del self.items[index]
self.grid.turn_into_grid(self.items, None, None)
def get_current_item(self):
"""
Returns:
Component: the currently selected component in the grid
"""
return self.items[self.current_row * self.grid_columns + self.current_column]
def change_text(self):
"""Changes the text of the current component"""
if self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS:
self.get_current_item().set_text("")
self.change_state(self.States.INPUT)
def handle_input_field_click(self, input_field):
""" Updates the 'self.current_row' and 'self.current_column' attributes of this class, so
it updates based on where you clicked"""
self.current_row = input_field.row_number
self.current_column = input_field.column_number
def set_in_use(self, value):
"""Sets the variable 'self.in_use' which controls whether the 'Vim actions' are being used"""
self.in_use = value
Classes
class VimGrid (items, dimensions, grid_rows, grid_columns, can_edit_grid=True)
-
A Grid of Items that you can navigate through using vim key bindings (hjklio). You can turn on the navigation mode by hitting the caps lock key
Initializes all the bindings
Expand source code
class VimGrid: """ A Grid of Items that you can navigate through using vim key bindings (hjklio). You can turn on the navigation mode by hitting the caps lock key""" current_column = 0 current_row = 0 is_selected = False items = [] grid = None dimensions = None in_use = False can_edit_grid = True class States: INPUT = "INPUT", MOVING_BETWEEN_INPUT_FIELDS = "MOVING_BETWEEN_INPUT_FIELDS", NOTHING = "NOTHING", current_state = States.INPUT def __init__(self, items, dimensions, grid_rows, grid_columns, can_edit_grid=True): """Initializes all the bindings""" self.can_edit_grid = can_edit_grid add_key_binding("<KeyPress-h>", lambda event: self.change_input_field_selection("Left")) add_key_binding("<KeyPress-b>", lambda event: self.change_input_field_selection("Beginning")) add_key_binding("<KeyPress-j>", lambda event: self.change_input_field_selection("Down")) add_key_binding("<KeyPress-k>", lambda event: self.change_input_field_selection("Up")) add_key_binding("<KeyPress-l>", lambda event: self.change_input_field_selection("Right")) add_key_binding("<KeyPress-o>", lambda event: self.change_input_field_selection("End")) add_key_binding("<KeyPress-Tab>", lambda event: self.change_input_field_selection("Right")) add_key_binding("<KeyPress-i>", lambda event: self.change_state(self.States.INPUT)) add_key_binding("<KeyPress-Escape>", lambda event: self.change_state(self.States.MOVING_BETWEEN_INPUT_FIELDS)) add_key_binding("<KeyPress-s>", lambda event: self.change_state(self.States.NOTHING)) add_key_binding("<KeyPress-a>", lambda event: self.add_row(self.current_row + 1)) add_key_binding("<KeyPress-A>", lambda event: self.add_row(self.current_row)) add_key_binding("<KeyPress-d>", lambda event: self.delete_row(self.current_row)) add_key_binding("<KeyPress-D>", lambda event: self.delete_row(self.current_row - 1)) add_key_binding("<KeyPress-c>", lambda event: self.change_text()) self.grid = Grid(dimensions, grid_rows, grid_columns) self.items = items for item in self.items: item: InputField = item item.set_command(self.handle_input_field_click) self.dimensions = dimensions self.grid.turn_into_grid(items, None, None) def get_item_row(self, row_number): """ Returns: list[Component]: the items in the current row""" # By multiplying the row number by row_length, the row start is gotten. Adding row length will bring it to the end of the row start = row_number * self.row_length return self.items[start: start + self.row_length + 1] @property def grid_rows(self): return self.grid.get_rows(len(self.items)) @property def grid_columns(self): return self.grid.get_columns(len(self.items)) def change_input_field_selection(self, event): """ Changes the selected input field depending on what key was pressed Args: event(str): the event name. Here are all the possible values: 'Up': Selects the next input field above the current one (belongs to the previous way point) 'Down': Selects the next input field below the current one (belongs to the next way point) 'Left': Selects the next input field to the right of the current one (belongs to the current way point) 'Right': Selects the previous input field to the left the current one (belongs to the current way point) Returns: None """ valid_event_types = ["Up", "Down", "Left", "Right", "Beginning", "End"] if not valid_event_types.__contains__(event): raise ValueError("Valid Data was not inputted!!!") if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS or not self.in_use: return # Modifying the numbers, so the selected input_field is not out of bounds of the input_fields on the screen max_column_number = self.grid_columns - 1 max_row_number = self.grid_rows - 1 if event == "Up": self.current_row -= 1 if event == "Down": self.current_row += 1 if event == "Left": self.current_column -= 1 if event == "Right": self.current_column += 1 if event == "Beginning": self.current_column = 0 if event == "End": self.current_column = max_column_number # Column Checks if self.current_column > max_column_number: self.current_column = 0 self.current_row += 1 if self.current_column < 0: self.current_column = max_column_number self.current_row -= 1 # Row Checks if self.current_row < 0: self.current_row = max_row_number if self.current_row > max_row_number: self.current_row = 0 current_item = self.get_current_item() if current_item.is_editable: current_item.focus_force() else: self.change_input_field_selection(event) def change_state(self, new_state): """Changes the state of the VIM grid""" valid_new_states = [self.States.MOVING_BETWEEN_INPUT_FIELDS, self.States.INPUT, self.States.NOTHING] if not valid_new_states.__contains__(new_state): raise ValueError("Valid Data was not inputted!!!") if self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS and new_state == self.States.INPUT: self.current_state = self.States.INPUT elif self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS and new_state == self.States.NOTHING: self.current_state = self.States.NOTHING elif new_state == self.States.MOVING_BETWEEN_INPUT_FIELDS: self.current_state = self.States.MOVING_BETWEEN_INPUT_FIELDS important_variables.input_is_allowed = self.current_state == self.States.INPUT def add_row(self, row_number): """Adds a row to the grid at the row number provided""" if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS: return if not self.in_use or not self.can_edit_grid: return current_items_start_index = self.current_row * self.grid_columns current_items_end_index = current_items_start_index + self.grid_columns - 1 current_items = self.items[current_items_start_index: current_items_end_index + 1] copied_current_items = [item.get_copy() for item in current_items] new_index = row_number * self.grid_columns if new_index < 0 or new_index > len(self.items): messagebox.showerror("ERROR", "This action can't be completed because the row_number is either too small or large") return self.items = self.items[:new_index] + copied_current_items + self.items[new_index:] self.grid.turn_into_grid(self.items, None, None) def delete_row(self, row_number, force=False): """Deletes the specified row from the grid""" if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS and not force: return if not self.in_use or not self.can_edit_grid: return for x in range(self.grid_columns): end_index = self.grid_columns * row_number + self.grid_columns - 1 # Indexes and lengths are offset by one index = end_index - x self.items[index].destroy() del self.items[index] self.grid.turn_into_grid(self.items, None, None) def get_current_item(self): """ Returns: Component: the currently selected component in the grid """ return self.items[self.current_row * self.grid_columns + self.current_column] def change_text(self): """Changes the text of the current component""" if self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS: self.get_current_item().set_text("") self.change_state(self.States.INPUT) def handle_input_field_click(self, input_field): """ Updates the 'self.current_row' and 'self.current_column' attributes of this class, so it updates based on where you clicked""" self.current_row = input_field.row_number self.current_column = input_field.column_number def set_in_use(self, value): """Sets the variable 'self.in_use' which controls whether the 'Vim actions' are being used""" self.in_use = value
Class variables
var States
var can_edit_grid
var current_column
var current_row
var current_state
var dimensions
var grid
var in_use
var is_selected
var items
Instance variables
var grid_columns
-
Expand source code
@property def grid_columns(self): return self.grid.get_columns(len(self.items))
var grid_rows
-
Expand source code
@property def grid_rows(self): return self.grid.get_rows(len(self.items))
Methods
def add_row(self, row_number)
-
Adds a row to the grid at the row number provided
Expand source code
def add_row(self, row_number): """Adds a row to the grid at the row number provided""" if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS: return if not self.in_use or not self.can_edit_grid: return current_items_start_index = self.current_row * self.grid_columns current_items_end_index = current_items_start_index + self.grid_columns - 1 current_items = self.items[current_items_start_index: current_items_end_index + 1] copied_current_items = [item.get_copy() for item in current_items] new_index = row_number * self.grid_columns if new_index < 0 or new_index > len(self.items): messagebox.showerror("ERROR", "This action can't be completed because the row_number is either too small or large") return self.items = self.items[:new_index] + copied_current_items + self.items[new_index:] self.grid.turn_into_grid(self.items, None, None)
def change_input_field_selection(self, event)
-
Changes the selected input field depending on what key was pressed
Args
event(str): the event name. Here are all the possible values: 'Up': Selects the next input field above the current one (belongs to the previous way point) 'Down': Selects the next input field below the current one (belongs to the next way point) 'Left': Selects the next input field to the right of the current one (belongs to the current way point) 'Right': Selects the previous input field to the left the current one (belongs to the current way point)
Returns
None
Expand source code
def change_input_field_selection(self, event): """ Changes the selected input field depending on what key was pressed Args: event(str): the event name. Here are all the possible values: 'Up': Selects the next input field above the current one (belongs to the previous way point) 'Down': Selects the next input field below the current one (belongs to the next way point) 'Left': Selects the next input field to the right of the current one (belongs to the current way point) 'Right': Selects the previous input field to the left the current one (belongs to the current way point) Returns: None """ valid_event_types = ["Up", "Down", "Left", "Right", "Beginning", "End"] if not valid_event_types.__contains__(event): raise ValueError("Valid Data was not inputted!!!") if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS or not self.in_use: return # Modifying the numbers, so the selected input_field is not out of bounds of the input_fields on the screen max_column_number = self.grid_columns - 1 max_row_number = self.grid_rows - 1 if event == "Up": self.current_row -= 1 if event == "Down": self.current_row += 1 if event == "Left": self.current_column -= 1 if event == "Right": self.current_column += 1 if event == "Beginning": self.current_column = 0 if event == "End": self.current_column = max_column_number # Column Checks if self.current_column > max_column_number: self.current_column = 0 self.current_row += 1 if self.current_column < 0: self.current_column = max_column_number self.current_row -= 1 # Row Checks if self.current_row < 0: self.current_row = max_row_number if self.current_row > max_row_number: self.current_row = 0 current_item = self.get_current_item() if current_item.is_editable: current_item.focus_force() else: self.change_input_field_selection(event)
def change_state(self, new_state)
-
Changes the state of the VIM grid
Expand source code
def change_state(self, new_state): """Changes the state of the VIM grid""" valid_new_states = [self.States.MOVING_BETWEEN_INPUT_FIELDS, self.States.INPUT, self.States.NOTHING] if not valid_new_states.__contains__(new_state): raise ValueError("Valid Data was not inputted!!!") if self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS and new_state == self.States.INPUT: self.current_state = self.States.INPUT elif self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS and new_state == self.States.NOTHING: self.current_state = self.States.NOTHING elif new_state == self.States.MOVING_BETWEEN_INPUT_FIELDS: self.current_state = self.States.MOVING_BETWEEN_INPUT_FIELDS important_variables.input_is_allowed = self.current_state == self.States.INPUT
def change_text(self)
-
Changes the text of the current component
Expand source code
def change_text(self): """Changes the text of the current component""" if self.current_state == self.States.MOVING_BETWEEN_INPUT_FIELDS: self.get_current_item().set_text("") self.change_state(self.States.INPUT)
def delete_row(self, row_number, force=False)
-
Deletes the specified row from the grid
Expand source code
def delete_row(self, row_number, force=False): """Deletes the specified row from the grid""" if self.current_state != self.States.MOVING_BETWEEN_INPUT_FIELDS and not force: return if not self.in_use or not self.can_edit_grid: return for x in range(self.grid_columns): end_index = self.grid_columns * row_number + self.grid_columns - 1 # Indexes and lengths are offset by one index = end_index - x self.items[index].destroy() del self.items[index] self.grid.turn_into_grid(self.items, None, None)
def get_current_item(self)
-
Returns
Component
- the currently selected component in the grid
Expand source code
def get_current_item(self): """ Returns: Component: the currently selected component in the grid """ return self.items[self.current_row * self.grid_columns + self.current_column]
def get_item_row(self, row_number)
-
Returns
list[Component]
- the items in the current row
Expand source code
def get_item_row(self, row_number): """ Returns: list[Component]: the items in the current row""" # By multiplying the row number by row_length, the row start is gotten. Adding row length will bring it to the end of the row start = row_number * self.row_length return self.items[start: start + self.row_length + 1]
def handle_input_field_click(self, input_field)
-
Updates the 'self.current_row' and 'self.current_column' attributes of this class, so it updates based on where you clicked
Expand source code
def handle_input_field_click(self, input_field): """ Updates the 'self.current_row' and 'self.current_column' attributes of this class, so it updates based on where you clicked""" self.current_row = input_field.row_number self.current_column = input_field.column_number
def set_in_use(self, value)
-
Sets the variable 'self.in_use' which controls whether the 'Vim actions' are being used
Expand source code
def set_in_use(self, value): """Sets the variable 'self.in_use' which controls whether the 'Vim actions' are being used""" self.in_use = value