from ._tksheet_vars import *
from ._tksheet_other_classes import *

from itertools import islice, accumulate, chain, cycle
from math import floor, ceil
import bisect
import pickle
import tkinter as tk
import zlib


class RowIndex(tk.Canvas):
    def __init__(self,
                 parentframe = None,
                 main_canvas = None,
                 header_canvas = None,
                 max_rh = None,
                 max_row_width = None,
                 row_index_align = None,
                 row_index_width = None,
                 index_bg = None,
                 index_border_fg = None,
                 index_grid_fg = None,
                 index_fg = None,
                 index_selected_cells_bg = None,
                 index_selected_cells_fg = None,
                 index_selected_rows_bg = "#5f6368",
                 index_selected_rows_fg = "white",
                 default_row_index = "numbers",
                 index_hidden_rows_expander_bg = None,
                 drag_and_drop_bg = None,
                 resizing_line_fg = None,
                 row_drag_and_drop_perform = True,
                 auto_resize_width = True,
                 show_default_index_for_empty = True):
        tk.Canvas.__init__(self,
                           parentframe,
                           height = None,
                           background = index_bg,
                           highlightthickness = 0)
        
        self.extra_begin_edit_cell_func = None
        self.extra_end_edit_cell_func = None
        
        self.text_editor = None
        self.text_editor_id = None
        self.text_editor_loc = None

        self.disp_text = {}
        self.disp_high = {}
        self.disp_grid = {}
        self.disp_fill_sels = {}
        self.disp_bord_sels = {}
        self.disp_resize_lines = {}
        self.disp_dropdown = {}
        self.disp_checkbox = {}
        self.hidd_text = {}
        self.hidd_high = {}
        self.hidd_grid = {}
        self.hidd_fill_sels = {}
        self.hidd_bord_sels = {}
        self.hidd_resize_lines = {}
        self.hidd_dropdown = {}
        self.hidd_checkbox = {}
        
        self.b1_pressed_loc = None
        self.existing_dropdown_canvas_id = None
        self.existing_dropdown_window = None
        self.closed_dropdown = None
        
        self.centre_alignment_text_mod_indexes = (slice(1, None), slice(None, -1))
        self.c_align_cyc = cycle(self.centre_alignment_text_mod_indexes)
        self.parentframe = parentframe
        self.row_drag_and_drop_perform = row_drag_and_drop_perform
        self.being_drawn_rect = None
        self.extra_motion_func = None
        self.extra_b1_press_func = None
        self.extra_b1_motion_func = None
        self.extra_b1_release_func = None
        self.extra_rc_func = None
        self.selection_binding_func = None
        self.shift_selection_binding_func = None
        self.drag_selection_binding_func = None
        self.ri_extra_begin_drag_drop_func = None
        self.ri_extra_end_drag_drop_func = None
        self.extra_double_b1_func = None
        self.row_height_resize_func = None
        self.new_row_width = 0
        if row_index_width is None:
            self.set_width(100)
            self.default_width = 100
        else:
            self.set_width(row_index_width)
            self.default_width = row_index_width
        self.max_rh = float(max_rh)
        self.max_row_width = float(max_row_width)
        self.MT = main_canvas         # is set from within MainTable() __init__
        self.CH = header_canvas      # is set from within MainTable() __init__
        self.TL = None                # is set from within TopLeftRectangle() __init__
        self.index_fg = index_fg
        self.index_grid_fg = index_grid_fg
        self.index_border_fg = index_border_fg
        self.index_selected_cells_bg = index_selected_cells_bg
        self.index_selected_cells_fg = index_selected_cells_fg
        self.index_selected_rows_bg = index_selected_rows_bg
        self.index_selected_rows_fg = index_selected_rows_fg
        self.index_hidden_rows_expander_bg = index_hidden_rows_expander_bg
        self.index_bg = index_bg
        self.drag_and_drop_bg = drag_and_drop_bg
        self.resizing_line_fg = resizing_line_fg
        self.align = row_index_align
        self.cell_options = {}
        self.drag_and_drop_enabled = False
        self.dragged_row = None
        self.width_resizing_enabled = False
        self.height_resizing_enabled = False
        self.double_click_resizing_enabled = False
        self.row_selection_enabled = False
        self.rc_insert_row_enabled = False
        self.rc_delete_row_enabled = False
        self.edit_cell_enabled = False
        self.show_default_index_for_empty = show_default_index_for_empty
        self.visible_row_dividers = []
        self.row_width_resize_bbox = tuple()
        self.rsz_w = None
        self.rsz_h = None
        self.currently_resizing_width = False
        self.currently_resizing_height = False
        self.auto_resize_width = auto_resize_width
        self.default_index = default_row_index.lower()
        self.ri_rc_popup_menu = None
        self.basic_bindings()

    def basic_bindings(self, enable = True):
        if enable:
            self.bind("<Motion>", self.mouse_motion)
            self.bind("<ButtonPress-1>", self.b1_press)
            self.bind("<B1-Motion>", self.b1_motion)
            self.bind("<ButtonRelease-1>", self.b1_release)
            self.bind("<Double-Button-1>", self.double_b1)
            self.bind(get_rc_binding(), self.rc)
        else:
            self.unbind("<Motion>")
            self.unbind("<ButtonPress-1>")
            self.unbind("<B1-Motion>")
            self.unbind("<ButtonRelease-1>")
            self.unbind("<Double-Button-1>")
            self.unbind(get_rc_binding())

    def set_width(self, new_width, set_TL = False):
        self.current_width = new_width
        try:
            self.config(width = new_width)
        except:
            return
        if set_TL:
            self.TL.set_dimensions(new_w = new_width)

    def enable_bindings(self, binding):
        if binding == "row_width_resize":
            self.width_resizing_enabled = True
        elif binding == "row_height_resize":
            self.height_resizing_enabled = True
        elif binding == "double_click_row_resize":
            self.double_click_resizing_enabled = True
        elif binding == "row_select":
            self.row_selection_enabled = True
        elif binding == "drag_and_drop":
            self.drag_and_drop_enabled = True
        
    def disable_bindings(self, binding):
        if binding == "row_width_resize":
            self.width_resizing_enabled = False
        elif binding == "row_height_resize":
            self.height_resizing_enabled = False
        elif binding == "double_click_row_resize":
            self.double_click_resizing_enabled = False
        elif binding == "row_select":
            self.row_selection_enabled = False
        elif binding == "drag_and_drop":
            self.drag_and_drop_enabled = False

    def check_mouse_position_height_resizers(self, x, y):
        ov = None
        for x1, y1, x2, y2 in self.visible_row_dividers:
            if x >= x1 and y >= y1 and x <= x2 and y <= y2:
                ov = self.find_overlapping(x1, y1, x2, y2)
                break
        return ov

    def rc(self, event):
        self.MT.hide_dropdown_window()
        self.hide_dropdown_window()
        self.focus_set()
        popup_menu = None
        if self.MT.identify_row(y = event.y, allow_end = False) is None:
            self.MT.deselect("all")
            if self.MT.rc_popup_menus_enabled:
                popup_menu = self.MT.empty_rc_popup_menu
        elif self.row_selection_enabled and not self.currently_resizing_width and not self.currently_resizing_height:
            r = self.MT.identify_row(y = event.y)
            if r < len(self.MT.row_positions) - 1:
                if self.MT.row_selected(r):
                    if self.MT.rc_popup_menus_enabled:
                        popup_menu = self.ri_rc_popup_menu
                else:
                    if self.MT.single_selection_enabled and self.MT.rc_select_enabled:
                        self.select_row(r, redraw = True)
                    elif self.MT.toggle_selection_enabled and self.MT.rc_select_enabled:
                        self.toggle_select_row(r, redraw = True)
                    if self.MT.rc_popup_menus_enabled:
                        popup_menu = self.ri_rc_popup_menu
        if self.extra_rc_func is not None:
            self.extra_rc_func(event)
        if popup_menu is not None:
            popup_menu.tk_popup(event.x_root, event.y_root)

    def shift_b1_press(self, event):
        self.MT.hide_dropdown_window()
        y = event.y
        r = self.MT.identify_row(y = y)
        if self.drag_and_drop_enabled or self.row_selection_enabled and self.rsz_h is None and self.rsz_w is None:
            if r < len(self.MT.row_positions) - 1:
                r_selected = self.MT.row_selected(r)
                if not r_selected and self.row_selection_enabled:
                    r = int(r)
                    currently_selected = self.MT.currently_selected()
                    if currently_selected and currently_selected[0] == "row":
                        min_r = int(currently_selected[1])
                        self.MT.delete_selection_rects(delete_current = False)
                        if r > min_r:
                            self.MT.create_selected(min_r, 0, r + 1, len(self.MT.col_positions) - 1, "rows")
                            func_event = tuple(range(min_r, r + 1))
                        elif r < min_r:
                            self.MT.create_selected(r, 0, min_r + 1, len(self.MT.col_positions) - 1, "rows")
                            func_event = tuple(range(r, min_r + 1))
                    else:
                        self.select_row(r)
                    self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True)
                    if self.shift_selection_binding_func is not None:
                        self.shift_selection_binding_func(SelectionBoxEvent("shift_select_rows", func_event))
                elif r_selected:
                    self.dragged_row = r

    def create_resize_line(self, x1, y1, x2, y2, width, fill, tag):
        if self.hidd_resize_lines:
            t, sh = self.hidd_resize_lines.popitem()
            self.coords(t, x1, y1, x2, y2)
            if sh:
                self.itemconfig(t, width = width, fill = fill, tag = tag)
            else:
                self.itemconfig(t, width = width, fill = fill, tag = tag, state = "normal")
            self.lift(t)
        else:
            t = self.create_line(x1, y1, x2, y2, width = width, fill = fill, tag = tag)
        self.disp_resize_lines[t] = True

    def delete_resize_lines(self):
        self.hidd_resize_lines.update(self.disp_resize_lines)
        self.disp_resize_lines = {}
        for t, sh in self.hidd_resize_lines.items():
            if sh:
                self.itemconfig(t, state = "hidden")
                self.hidd_resize_lines[t] = False

    def mouse_motion(self, event):
        if not self.currently_resizing_height and not self.currently_resizing_width:
            x = self.canvasx(event.x)
            y = self.canvasy(event.y)
            mouse_over_resize = False
            if self.height_resizing_enabled and not mouse_over_resize:
                ov = self.check_mouse_position_height_resizers(x, y)
                if ov is not None:
                    #tgs = next(itm for itm in ov if "h" == self.gettags(itm))
                    for itm in ov:
                        tgs = self.gettags(itm)
                        if "h" == tgs[0]:
                            break
                    r = int(tgs[1])
                    self.config(cursor = "sb_v_double_arrow")
                    self.rsz_h = r
                    mouse_over_resize = True
                else:
                    self.rsz_h = None
            if self.width_resizing_enabled and not mouse_over_resize:
                try:
                    x1, y1, x2, y2 = self.row_width_resize_bbox[0], self.row_width_resize_bbox[1], self.row_width_resize_bbox[2], self.row_width_resize_bbox[3]
                    if x >= x1 and y >= y1 and x <= x2 and y <= y2:
                        self.config(cursor = "sb_h_double_arrow")
                        self.rsz_w = True
                        mouse_over_resize = True
                    else:
                        self.rsz_w = None
                except:
                    self.rsz_w = None
            if not mouse_over_resize:
                self.MT.reset_mouse_motion_creations()
        if self.extra_motion_func is not None:
            self.extra_motion_func(event)

    def double_b1(self, event = None):
        self.MT.hide_dropdown_window()
        self.focus_set()
        if self.double_click_resizing_enabled and self.height_resizing_enabled and self.rsz_h is not None and not self.currently_resizing_height:
            row = self.rsz_h - 1
            old_height = self.MT.row_positions[self.rsz_h] - self.MT.row_positions[self.rsz_h - 1]
            new_height = self.set_row_height(row)
            self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True)
            if self.row_height_resize_func is not None and old_height != new_height:
                self.row_height_resize_func(ResizeEvent("row_height_resize", row, old_height, new_height))
        elif self.width_resizing_enabled and self.rsz_h is None and self.rsz_w == True:
            self.set_width_of_index_to_text()
        elif self.row_selection_enabled and self.rsz_h is None and self.rsz_w is None:
            r = self.MT.identify_row(y = event.y)
            if r < len(self.MT.row_positions) - 1:
                if self.MT.single_selection_enabled:
                    self.select_row(r, redraw = True)
                elif self.MT.toggle_selection_enabled:
                    self.toggle_select_row(r, redraw = True)
                drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
                if ((drow in self.cell_options and 'checkbox' in self.cell_options[drow]) or
                    (drow in self.cell_options and 'dropdown' in self.cell_options[drow]) or
                    self.edit_cell_enabled):
                    self.open_cell(event)
        self.rsz_h = None
        self.mouse_motion(event)
        if self.extra_double_b1_func is not None:
            self.extra_double_b1_func(event)
        
    def b1_press(self, event = None):
        self.MT.hide_dropdown_window()
        self.focus_set()
        self.MT.unbind("<MouseWheel>")
        self.closed_dropdown = self.hide_dropdown_window(b1 = True)
        x = self.canvasx(event.x)
        y = self.canvasy(event.y)
        r = self.MT.identify_row(y = event.y)
        self.b1_pressed_loc = r
        if self.check_mouse_position_height_resizers(x, y) is None:
            self.rsz_h = None
        if not x >= self.row_width_resize_bbox[0] and y >= self.row_width_resize_bbox[1] and x <= self.row_width_resize_bbox[2] and y <= self.row_width_resize_bbox[3]:
            self.rsz_w = None
        if self.height_resizing_enabled and self.rsz_h is not None:
            self.currently_resizing_height = True
            y = self.MT.row_positions[self.rsz_h]
            line2y = self.MT.row_positions[self.rsz_h - 1]
            x1, y1, x2, y2 = self.MT.get_canvas_visible_area()
            self.create_resize_line(0, y, self.current_width, y, width = 1, fill = self.resizing_line_fg, tag = "rhl")
            self.MT.create_resize_line(x1, y, x2, y, width = 1, fill = self.resizing_line_fg, tag = "rhl")
            self.create_resize_line(0, line2y, self.current_width, line2y, width = 1, fill = self.resizing_line_fg, tag = "rhl2")
            self.MT.create_resize_line(x1, line2y, x2, line2y, width = 1, fill = self.resizing_line_fg, tag = "rhl2")
        elif self.width_resizing_enabled and self.rsz_h is None and self.rsz_w == True:
            self.currently_resizing_width = True
            x1, y1, x2, y2 = self.MT.get_canvas_visible_area()
            x = int(event.x)
            if x < self.MT.min_cw:
                x = int(self.MT.min_cw)
            self.new_row_width = x
            self.create_resize_line(x, y1, x, y2, width = 1, fill = self.resizing_line_fg, tag = "rwl")
        elif self.MT.identify_row(y = event.y, allow_end = False) is None:
            self.MT.deselect("all")
        elif self.row_selection_enabled and self.rsz_h is None and self.rsz_w is None:
            r = self.MT.identify_row(y = event.y)
            if r < len(self.MT.row_positions) - 1:
                if self.MT.single_selection_enabled:
                    self.select_row(r, redraw = True)
                elif self.MT.toggle_selection_enabled:
                    self.toggle_select_row(r, redraw = True)
        if self.extra_b1_press_func is not None:
            self.extra_b1_press_func(event)
    
    def b1_motion(self, event):
        x1,y1,x2,y2 = self.MT.get_canvas_visible_area()
        if self.height_resizing_enabled and self.rsz_h is not None and self.currently_resizing_height:
            y = self.canvasy(event.y)
            size = y - self.MT.row_positions[self.rsz_h - 1]
            if not size <= self.MT.min_rh and size < self.max_rh:
                self.delete_resize_lines()
                self.MT.delete_resize_lines()
                line2y = self.MT.row_positions[self.rsz_h - 1]
                self.create_resize_line(0, y, self.current_width, y, width = 1, fill = self.resizing_line_fg, tag = "rhl")
                self.MT.create_resize_line(x1, y, x2, y, width = 1, fill = self.resizing_line_fg, tag = "rhl")
                self.create_resize_line(0, line2y, self.current_width, line2y, width = 1, fill = self.resizing_line_fg, tag = "rhl2")
                self.MT.create_resize_line(x1, line2y, x2, line2y, width = 1, fill = self.resizing_line_fg, tag = "rhl2")
        elif self.width_resizing_enabled and self.rsz_w is not None and self.currently_resizing_width:
            evx = event.x
            self.delete_resize_lines()
            self.MT.delete_resize_lines()
            if evx > self.current_width:
                x = self.MT.canvasx(evx - self.current_width)
                if evx > self.max_row_width:
                    evx = int(self.max_row_width)
                    x = self.MT.canvasx(evx - self.current_width)
                self.new_row_width = evx
                self.MT.create_resize_line(x, y1, x, y2, width = 1, fill = self.resizing_line_fg, tag = "rwl")
            else:
                x = evx
                if x < self.MT.min_cw:
                    x = int(self.MT.min_cw)
                self.new_row_width = x
                self.create_resize_line(x, y1, x, y2, width = 1, fill = self.resizing_line_fg, tag = "rwl")
        if self.drag_and_drop_enabled and self.row_selection_enabled and self.rsz_h is None and self.rsz_w is None and self.dragged_row is not None and self.MT.anything_selected(exclude_cells = True, exclude_columns = True):
            y = self.canvasy(event.y)
            if y > 0 and y < self.MT.row_positions[-1]:
                y = event.y
                hend = self.winfo_height()
                ycheck = self.yview()
                if y >= hend - 0 and len(ycheck) > 1 and ycheck[1] < 1:
                    if y >= hend + 15:
                        self.MT.yview_scroll(2, "units")
                        self.yview_scroll(2, "units")
                    else:
                        self.MT.yview_scroll(1, "units")
                        self.yview_scroll(1, "units")
                    self.check_yview()
                    self.MT.main_table_redraw_grid_and_text(redraw_row_index = True)
                elif y <= 0 and len(ycheck) > 1 and ycheck[0] > 0:
                    if y >= -15:
                        self.MT.yview_scroll(-1, "units")
                        self.yview_scroll(-1, "units")
                    else:
                        self.MT.yview_scroll(-2, "units")
                        self.yview_scroll(-2, "units")
                    self.check_yview()
                    self.MT.main_table_redraw_grid_and_text(redraw_row_index = True)
                row = self.MT.identify_row(y = event.y)
                sels = self.MT.get_selected_rows()
                selsmin = min(sels)
                if row in sels:
                    ypos = self.MT.row_positions[selsmin]
                else:
                    if row < selsmin:
                        ypos = self.MT.row_positions[row]
                    else:
                        ypos = self.MT.row_positions[row + 1]
                self.delete_resize_lines()
                self.MT.delete_resize_lines()
                self.create_resize_line(0, ypos, self.current_width, ypos, width = 3, fill = self.drag_and_drop_bg, tag = "dd")
                self.MT.create_resize_line(x1, ypos, x2, ypos, width = 3, fill = self.drag_and_drop_bg, tag = "dd")
        elif self.MT.drag_selection_enabled and self.row_selection_enabled and self.rsz_h is None and self.rsz_w is None:
            end_row = self.MT.identify_row(y = event.y)
            currently_selected = self.MT.currently_selected()
            if end_row < len(self.MT.row_positions) - 1 and currently_selected:
                if currently_selected[0] == "row":
                    start_row = currently_selected[1]
                    if end_row >= start_row:
                        rect = (start_row, 0, end_row + 1, len(self.MT.col_positions) - 1, "rows")
                        func_event = tuple(range(start_row, end_row + 1))
                    elif end_row < start_row:
                        rect = (end_row, 0, start_row + 1, len(self.MT.col_positions) - 1, "rows")
                        func_event = tuple(range(end_row, start_row + 1))
                    if self.being_drawn_rect != rect:
                        self.MT.delete_selection_rects(delete_current = False)
                        self.MT.create_selected(*rect)
                        self.being_drawn_rect = rect
                        if self.drag_selection_binding_func is not None:
                            self.drag_selection_binding_func(SelectionBoxEvent("drag_select_rows", func_event))
            ycheck = self.yview()
            if event.y > self.winfo_height() and len(ycheck) > 1 and ycheck[1] < 1:
                try:
                    self.MT.yview_scroll(1, "units")
                    self.yview_scroll(1, "units")
                except:
                    pass
                self.check_yview()
            elif event.y < 0 and self.canvasy(self.winfo_height()) > 0 and ycheck and ycheck[0] > 0:
                try:
                    self.yview_scroll(-1, "units")
                    self.MT.yview_scroll(-1, "units")
                except:
                    pass
                self.check_yview()
            self.MT.main_table_redraw_grid_and_text(redraw_header = False, redraw_row_index = True)
        if self.extra_b1_motion_func is not None:
            self.extra_b1_motion_func(event)

    def check_yview(self):
        ycheck = self.yview()
        if ycheck and ycheck[0] < 0:
            self.MT.set_yviews("moveto", 0)
        if len(ycheck) > 1 and ycheck[1] > 1:
            self.MT.set_yviews("moveto", 1)
            
    def b1_release(self, event = None):
        self.MT.bind("<MouseWheel>", self.MT.mousewheel)
        if self.height_resizing_enabled and self.rsz_h is not None and self.currently_resizing_height:
            self.currently_resizing_height = False
            new_row_pos = self.coords("rhl")[1]
            self.delete_resize_lines()
            self.MT.delete_resize_lines()
            old_height = self.MT.row_positions[self.rsz_h] - self.MT.row_positions[self.rsz_h - 1]
            size = new_row_pos - self.MT.row_positions[self.rsz_h - 1]
            if size < self.MT.min_rh:
                new_row_pos = ceil(self.MT.row_positions[self.rsz_h - 1] + self.MT.min_rh)
            elif size > self.max_rh:
                new_row_pos = floor(self.MT.row_positions[self.rsz_h - 1] + self.max_rh)
            increment = new_row_pos - self.MT.row_positions[self.rsz_h]
            self.MT.row_positions[self.rsz_h + 1:] = [e + increment for e in islice(self.MT.row_positions, self.rsz_h + 1, len(self.MT.row_positions))]
            self.MT.row_positions[self.rsz_h] = new_row_pos
            new_height = self.MT.row_positions[self.rsz_h] - self.MT.row_positions[self.rsz_h - 1]
            self.MT.recreate_all_selection_boxes()
            self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True)
            if self.row_height_resize_func is not None and old_height != new_height:
                self.row_height_resize_func(ResizeEvent("row_height_resize", self.rsz_h - 1, old_height, new_height))
        elif self.width_resizing_enabled and self.rsz_w is not None and self.currently_resizing_width:
            self.currently_resizing_width = False
            self.delete_resize_lines()
            self.MT.delete_resize_lines()
            self.set_width(self.new_row_width, set_TL = True)
            self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True)
        if self.drag_and_drop_enabled and self.MT.anything_selected(exclude_cells = True, exclude_columns = True) and self.row_selection_enabled and self.rsz_h is None and self.rsz_w is None and self.dragged_row is not None:
            self.delete_resize_lines()
            self.MT.delete_resize_lines()
            y = event.y
            r = self.MT.identify_row(y = y)
            orig_selected_rows = self.MT.get_selected_rows()
            if r != self.dragged_row and r is not None and r not in orig_selected_rows and len(orig_selected_rows) != (len(self.MT.row_positions) - 1):
                orig_selected_rows = sorted(orig_selected_rows)
                if len(orig_selected_rows) > 1:
                    orig_min = orig_selected_rows[0]
                    orig_max = orig_selected_rows[1]
                    start_idx = bisect.bisect_left(orig_selected_rows, self.dragged_row)
                    forward_gap = get_index_of_gap_in_sorted_integer_seq_forward(orig_selected_rows, start_idx)
                    reverse_gap = get_index_of_gap_in_sorted_integer_seq_reverse(orig_selected_rows, start_idx)
                    if forward_gap is not None:
                        orig_selected_rows[:] = orig_selected_rows[:forward_gap]
                    if reverse_gap is not None:
                        orig_selected_rows[:] = orig_selected_rows[reverse_gap:]
                rowsiter = orig_selected_rows.copy()
                rm1start = rowsiter[0]
                rm1end = rowsiter[-1] + 1
                rm2start = rm1start + (rm1end - rm1start)
                rm2end = rm1end + (rm1end - rm1start)
                totalrows = len(rowsiter)
                extra_func_success = True
                if r >= len(self.MT.row_positions) - 1:
                    r -= 1
                r_ = int(r)
                if self.ri_extra_begin_drag_drop_func is not None:
                    try:
                        self.ri_extra_begin_drag_drop_func(BeginDragDropEvent("begin_row_index_drag_drop", tuple(orig_selected_rows), int(r)))
                    except:
                        extra_func_success = False
                if extra_func_success:
                    if self.row_drag_and_drop_perform:
                        if rm1start > r:
                            self.MT.data_ref = (self.MT.data_ref[:r] +
                                                self.MT.data_ref[rm1start:rm1start + totalrows] +
                                                self.MT.data_ref[r:rm1start] +
                                                self.MT.data_ref[rm1start + totalrows:])
                            if not isinstance(self.MT._row_index, int) and self.MT._row_index:
                                try:
                                    self.MT._row_index = (self.MT._row_index[:r] +
                                                            self.MT._row_index[rm1start:rm1start + totalrows] +
                                                            self.MT._row_index[r:rm1start] +
                                                            self.MT._row_index[rm1start + totalrows:])
                                except:
                                    pass
                        else:
                            self.MT.data_ref = (self.MT.data_ref[:rm1start] +
                                                self.MT.data_ref[rm1start + totalrows:r + 1] +
                                                self.MT.data_ref[rm1start:rm1start + totalrows] +
                                                self.MT.data_ref[r + 1:])
                            if not isinstance(self.MT._row_index, int) and self.MT._row_index:
                                try:
                                    self.MT._row_index = (self.MT._row_index[:rm1start] +
                                                            self.MT._row_index[rm1start + totalrows:r + 1] +
                                                            self.MT._row_index[rm1start:rm1start + totalrows] +
                                                            self.MT._row_index[r + 1:])
                                except:
                                    pass
                    rhs = [int(b - a) for a, b in zip(self.MT.row_positions, islice(self.MT.row_positions, 1, len(self.MT.row_positions)))]
                    if rm1start > r:
                        rhs = (rhs[:r] +
                               rhs[rm1start:rm1start + totalrows] +
                               rhs[r:rm1start] +
                               rhs[rm1start + totalrows:])
                    else:
                        rhs = (rhs[:rm1start] +
                               rhs[rm1start + totalrows:r + 1] +
                               rhs[rm1start:rm1start + totalrows] +
                               rhs[r + 1:])
                    self.MT.row_positions = list(accumulate(chain([0], (height for height in rhs))))
                    self.MT.deselect("all")
                    if (r_ - 1) + totalrows > len(self.MT.row_positions) - 1:
                        new_selected = tuple(range(len(self.MT.row_positions) - 1 - totalrows, len(self.MT.row_positions) - 1))
                        self.MT.create_selected(len(self.MT.row_positions) - 1 - totalrows, 0, len(self.MT.row_positions) - 1, len(self.MT.col_positions) - 1, "rows")
                    else:
                        if rm1start > r:
                            new_selected = tuple(range(r_, r_ + totalrows))
                            self.MT.create_selected(r_, 0, r_ + totalrows, len(self.MT.col_positions) - 1, "rows")
                        else:
                            new_selected = tuple(range(r_ + 1 - totalrows, r_ + 1))
                            self.MT.create_selected(r_ + 1 - totalrows, 0, r_ + 1, len(self.MT.col_positions) - 1, "rows")
                    self.MT.create_current(int(new_selected[0]), 0, type_ = "row", inside = True)
                    rowset = set(rowsiter)
                    if self.MT.undo_enabled:
                        self.MT.undo_storage.append(zlib.compress(pickle.dumps(("move_rows",
                                                                                min(orig_selected_rows),
                                                                                new_selected[0],
                                                                                new_selected[-1],
                                                                                sorted(orig_selected_rows)))))
                    popped_ri = {t1: t2 for t1, t2 in self.cell_options.items() if t1 in rowset}
                    popped_cell = {t1: t2 for t1, t2 in self.MT.cell_options.items() if t1[0] in rowset}
                    popped_row = {t1: t2 for t1, t2 in self.MT.row_options.items() if t1 in rowset}
                    
                    popped_ri = {t1: self.cell_options.pop(t1) for t1 in popped_ri}
                    popped_cell = {t1: self.MT.cell_options.pop(t1) for t1 in popped_cell}
                    popped_row = {t1: self.MT.row_options.pop(t1) for t1 in popped_row}

                    self.cell_options = {t1 if t1 < rm1start else t1 - totalrows: t2 for t1, t2 in self.cell_options.items()}
                    self.cell_options = {t1 if t1 < r_ else t1 + totalrows: t2 for t1, t2 in self.cell_options.items()}

                    self.MT.row_options = {t1 if t1 < rm1start else t1 - totalrows: t2 for t1, t2 in self.MT.row_options.items()}
                    self.MT.row_options = {t1 if t1 < r_ else t1 + totalrows: t2 for t1, t2 in self.MT.row_options.items()}

                    self.MT.cell_options = {(t10 if t10 < rm1start else t10 - totalrows, t11): t2 for (t10, t11), t2 in self.MT.cell_options.items()}
                    self.MT.cell_options = {(t10 if t10 < r_ else t10 + totalrows, t11): t2 for (t10, t11), t2 in self.MT.cell_options.items()}

                    newrowsdct = {t1: t2 for t1, t2 in zip(rowsiter, new_selected)}
                    for t1, t2 in popped_ri.items():
                        self.cell_options[newrowsdct[t1]] = t2

                    for t1, t2 in popped_row.items():
                        self.MT.row_options[newrowsdct[t1]] = t2

                    for (t10, t11), t2 in popped_cell.items():
                        self.MT.cell_options[(newrowsdct[t10], t11)] = t2

                    self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True)
                    if self.ri_extra_end_drag_drop_func is not None:
                        self.ri_extra_end_drag_drop_func(EndDragDropEvent("end_row_index_drag_drop", tuple(orig_selected_rows), new_selected, int(r)))
                        
        elif self.b1_pressed_loc is not None and self.rsz_w is None and self.rsz_h is None:
            r = self.MT.identify_row(y = event.y)
            if r is not None and r == self.b1_pressed_loc and self.b1_pressed_loc != self.closed_dropdown:
                drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
                if ((drow in self.cell_options and 'dropdown' in self.cell_options[drow] and event.x < self.current_width and event.x > self.current_width - self.MT.txt_h - 4) or
                    (drow in self.cell_options and 'checkbox' in self.cell_options[drow] and event.x < self.current_width + self.MT.txt_h + 5)):
                    if event.y < self.MT.row_positions[r] + self.MT.txt_h + 5:
                        self.open_cell(event)
            else:
                self.hide_dropdown_window()
            self.b1_pressed_loc = None
            self.closed_dropdown = None
                        
        self.dragged_row = None
        self.currently_resizing_width = False
        self.currently_resizing_height = False
        self.rsz_w = None
        self.rsz_h = None
        self.being_drawn_rect = None
        self.mouse_motion(event)
        if self.extra_b1_release_func is not None:
            self.extra_b1_release_func(event)
            
    def readonly_index(self, rows = [], readonly = True):
        if isinstance(rows, int):
            rows_ = [rows]
        else:
            rows_ = rows
        if not readonly:
            for r in rows_:
                if r in self.cell_options and 'readonly' in self.cell_options[r]:
                    del self.cell_options[r]['readonly']
        else:
            for r in rows_:
                if r not in self.cell_options:
                    self.cell_options[r] = {}
                self.cell_options[r]['readonly'] = True

    def highlight_cells(self, r = 0, cells = tuple(), bg = None, fg = None, redraw = False, overwrite = True):
        if bg is None and fg is None:
            return
        if cells and not isinstance(cells, int):
            iterable = cells
        elif isinstance(cells, int):
            iterable = (cells, )
        else:
            iterable = (r, )
        for r_ in iterable:
            if r_ not in self.cell_options:
                self.cell_options[r_] = {}
            if 'highlight' in self.cell_options[r_] and not overwrite:
                self.cell_options[r_]['highlight'] = (self.cell_options[r_]['highlight'][0] if bg is None else bg,
                                                        self.cell_options[r_]['highlight'][1] if fg is None else fg)
            else:
                self.cell_options[r_]['highlight'] = (bg, fg)
        if redraw:
            self.MT.main_table_redraw_grid_and_text(False, True)

    def select_row(self, r, redraw = False, keep_other_selections = False):
        ignore_keep = False
        if keep_other_selections:
            if self.MT.row_selected(r):
                self.MT.create_current(r, 0, type_ = "row", inside = True)
            else:
                ignore_keep = True
        if ignore_keep or not keep_other_selections:
            self.MT.delete_selection_rects()
            self.MT.create_current(r, 0, type_ = "row", inside = True)
            self.MT.create_selected(r, 0, r + 1, len(self.MT.col_positions) - 1, "rows")
        if redraw:
            self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True)
        if self.selection_binding_func is not None:
            self.selection_binding_func(SelectRowEvent("select_row", int(r)))

    def toggle_select_row(self, row, add_selection = True, redraw = True, run_binding_func = True, set_as_current = True):
        if add_selection:
            if self.MT.row_selected(row):
                self.MT.deselect(r = row, redraw = redraw)
            else:
                self.add_selection(r = row, redraw = redraw, run_binding_func = run_binding_func, set_as_current = set_as_current)
        else:
            if self.MT.row_selected(row):
                self.MT.deselect(r = row, redraw = redraw)
            else:
                self.select_row(row, redraw = redraw)

    def add_selection(self, r, redraw = False, run_binding_func = True, set_as_current = True):
        r = int(r)
        if set_as_current:
            create_new_sel = False
            current = self.MT.get_tags_of_current()
            if current:
                if current[0] == "Current_Outside":
                    create_new_sel = True
            self.MT.create_current(r, 0, type_ = "row", inside = True)
            if create_new_sel:
                r1, c1, r2, c2 = tuple(int(e) for e in current[1].split("_") if e)
                self.MT.create_selected(r1, c1, r2, c2, current[2] + "s")
        self.MT.create_selected(r, 0, r + 1, len(self.MT.col_positions) - 1, "rows")
        if redraw:
            self.MT.main_table_redraw_grid_and_text(redraw_header = False, redraw_row_index = True)
        if self.selection_binding_func is not None and run_binding_func:
            self.selection_binding_func(("select_row", int(r)))

    def set_row_height(self, row, height = None, only_set_if_too_small = False, recreate = True, return_new_height = False, displayed_only = False):
        r_norm = row + 1
        r_extra = row + 2
        min_rh = self.MT.min_rh
        if height is None:
            if self.MT.all_columns_displayed:
                if displayed_only:
                    x1, y1, x2, y2 = self.MT.get_canvas_visible_area()
                    start_col, end_col = self.MT.get_visible_columns(x1, x2)
                else:
                    start_col, end_col = 0, len(self.MT.data_ref[row])
                iterable = range(start_col, end_col)
            else:
                if displayed_only:
                    x1, y1, x2, y2 = self.MT.get_canvas_visible_area()
                    start_col, end_col = self.MT.get_visible_columns(x1, x2)
                else:
                    start_col, end_col = 0, len(self.MT.displayed_columns)
                iterable = self.MT.displayed_columns[start_col:end_col]
            new_height = int(min_rh)
            try:
                if isinstance(self.MT._row_index[row], str):
                    txt = self.MT._row_index[row]
                else:
                    txt = f"{self.MT._row_index[row]}"
            except:
                txt = ""
            if txt:
                h = self.MT.GetTextHeight(txt) + 5
            else:
                h = min_rh
            if h < min_rh:
                h = int(min_rh)
            elif h > self.max_rh:
                h = int(self.max_rh)
            if h > new_height:
                new_height = h
            for cn in iterable:
                if (row, cn) in self.MT.cell_options and 'checkbox' in self.MT.cell_options[(row, cn)]:
                    txt = self.MT.cell_options[(row, cn)]['checkbox']['text']
                else:
                    try:
                        if isinstance(self.MT.data_ref[row][cn], str):
                            txt = self.MT.data_ref[row][cn]
                        else:
                            txt = f"{self.MT.data_ref[row][cn]}"
                    except:
                        txt = ""
                if txt:
                    h = self.MT.GetTextHeight(txt) + 5
                else:
                    h = min_rh
                if h < min_rh:
                    h = int(min_rh)
                elif h > self.max_rh:
                    h = int(self.max_rh)
                if h > new_height:
                    new_height = h
        else:
            new_height = int(height)
        if new_height < min_rh:
            new_height = int(min_rh)
        elif new_height > self.max_rh:
            new_height = int(self.max_rh)
        if only_set_if_too_small and new_height <= self.MT.row_positions[row + 1] - self.MT.row_positions[row]:
            return self.MT.row_positions[row + 1] - self.MT.row_positions[row]
        if not return_new_height:
            new_row_pos = self.MT.row_positions[row] + new_height
            increment = new_row_pos - self.MT.row_positions[r_norm]
            self.MT.row_positions[r_extra:] = [e + increment for e in islice(self.MT.row_positions, r_extra, len(self.MT.row_positions))]
            self.MT.row_positions[r_norm] = new_row_pos
            if recreate:
                self.MT.recreate_all_selection_boxes()
        return new_height

    def set_width_of_index_to_text(self, recreate = True):
        if not self.MT._row_index and isinstance(self.MT._row_index, list):
            return
        qconf = self.MT.txt_measure_canvas.itemconfig
        qbbox = self.MT.txt_measure_canvas.bbox
        qtxtm = self.MT.txt_measure_canvas_text
        new_width = int(self.MT.min_cw)
        if isinstance(self.MT._row_index, list):
            for rn, row in enumerate(self.MT._row_index):
                if rn in self.cell_options and 'checkbox' in self.cell_options[rn]:
                    txt = self.cell_options[rn]['checkbox']['text']
                else:
                    try:
                        if isinstance(row, str):
                            txt = row
                        else:
                            txt = f"{row}"
                    except:
                        txt = ""
                if txt:
                    qconf(qtxtm, text = txt)
                    b = qbbox(qtxtm)
                    w = b[2] - b[0] + 10
                else:
                    w = self.default_width
                if w < self.MT.min_cw:
                    w = int(self.MT.min_cw)
                elif w > self.max_row_width:
                    w = int(self.max_row_width)
                if rn in self.cell_options and 'checkbox' in self.cell_options[rn]:
                    w += self.MT.txt_h + 6
                elif rn in self.cell_options and 'dropdown' in self.cell_options[rn]:
                    w += self.MT.txt_h + 4
                if w > new_width:
                    new_width = w
        elif isinstance(self.MT._row_index, int):
            c = self.MT._row_index
            for rn, row in enumerate(self.MT.data_ref):
                if (rn, c) in self.MT.cell_options and 'checkbox' in self.MT.cell_options[(rn, c)]:
                    txt = self.MT.cell_options[(rn, c)]['checkbox']['text']
                else:
                    try:
                        if isinstance(row[c], str):
                            txt = row[c]
                        else:
                            txt = f"{row[c]}"
                    except:
                        txt = ""
                if txt:
                    qconf(qtxtm, text = txt)
                    b = qbbox(qtxtm)
                    w = b[2] - b[0] + 10
                else:
                    w = self.default_width
                if w < self.MT.min_cw:
                    w = int(self.MT.min_cw)
                elif w > self.max_row_width:
                    w = int(self.max_row_width)
                if w > new_width:
                    new_width = w
        if new_width == self.MT.min_cw:
            new_width = self.MT.min_cw + 10
        self.set_width(new_width, set_TL = True)
        if recreate:
            self.MT.recreate_all_selection_boxes()
        self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True)

    def set_height_of_all_rows(self, height = None, only_set_if_too_small = False, recreate = True):
        if height is None:
            self.MT.row_positions = list(accumulate(chain([0], (self.set_row_height(rn, only_set_if_too_small = only_set_if_too_small, recreate = False, return_new_height = True) for rn in range(len(self.MT.data_ref))))))
        else:
            self.MT.row_positions = list(accumulate(chain([0], (height for r in range(len(self.MT.data_ref))))))
        if recreate:
            self.MT.recreate_all_selection_boxes()

    def align_cells(self, rows = [], align = "global"):
        if isinstance(rows, int):
            rows = [rows]
        else:
            rows = rows
        if align == "global":
            for r in rows:
                if r in self.cell_options and 'align' in self.cell_options[r]:
                    del self.cell_options[r]['align']
        else:
            for r in rows:
                if r not in self.cell_options:
                    self.cell_options[r] = {}
                self.cell_options[r]['align'] = align

    def auto_set_index_width(self, end_row):
        if not self.MT._row_index and not isinstance(self.MT._row_index, int) and self.auto_resize_width:
            if self.default_index == "letters":
                new_w = self.MT.GetTextWidth(f"{num2alpha(end_row)}") + 20
                if self.current_width - new_w > 15 or new_w - self.current_width > 5:
                    self.set_width(new_w, set_TL = True)
                    return True
            elif self.default_index == "numbers":
                new_w = self.MT.GetTextWidth(f"{end_row}") + 20
                if self.current_width - new_w > 15 or new_w - self.current_width > 5:
                    self.set_width(new_w, set_TL = True)
                    return True
            elif self.default_index == "both":
                new_w = self.MT.GetTextWidth(f"{end_row + 1} {num2alpha(end_row)}") + 20
                if self.current_width - new_w > 15 or new_w - self.current_width > 5:
                    self.set_width(new_w, set_TL = True)
                    return True
        return False

    def redraw_highlight_get_text_fg(self, fr, sr, r, c_2, c_3, selected_rows, selected_cols, actual_selected_rows, hlrow):
        redrawn = False
        if r in self.cell_options and 'highlight' in self.cell_options[r] and r in actual_selected_rows:
            if self.cell_options[r]['highlight'][0] is not None:
                c_1 = self.cell_options[r]['highlight'][0] if self.cell_options[r]['highlight'][0].startswith("#") else Color_Map_[self.cell_options[r]['highlight'][0]]
                redrawn = self.redraw_highlight(0,
                                                fr + 1,
                                                self.current_width - 1,
                                                sr,
                                                fill = (f"#{int((int(c_1[1:3], 16) + int(c_3[1:3], 16)) / 2):02X}" +
                                                        f"{int((int(c_1[3:5], 16) + int(c_3[3:5], 16)) / 2):02X}" +
                                                        f"{int((int(c_1[5:], 16) + int(c_3[5:], 16)) / 2):02X}"),
                                                outline = self.index_fg if hlrow in self.cell_options and 'dropdown' in self.cell_options[hlrow] else "",
                                                tag = "s")
            tf = self.index_selected_rows_fg if self.cell_options[r]['highlight'][1] is None or self.MT.display_selected_fg_over_highlights else self.cell_options[r]['highlight'][1]
        elif r in self.cell_options and 'highlight' in self.cell_options[r] and (r in selected_rows or selected_cols):
            if self.cell_options[r]['highlight'][0] is not None:
                c_1 = self.cell_options[r]['highlight'][0] if self.cell_options[r]['highlight'][0].startswith("#") else Color_Map_[self.cell_options[r]['highlight'][0]]
                redrawn = self.redraw_highlight(0,
                                                fr + 1,
                                                self.current_width - 1,
                                                sr,
                                                fill = (f"#{int((int(c_1[1:3], 16) + int(c_2[1:3], 16)) / 2):02X}" +
                                                        f"{int((int(c_1[3:5], 16) + int(c_2[3:5], 16)) / 2):02X}" +
                                                        f"{int((int(c_1[5:], 16) + int(c_2[5:], 16)) / 2):02X}"),
                                                outline = self.index_fg if hlrow in self.cell_options and 'dropdown' in self.cell_options[hlrow] else "",
                                                tag = "s")
            tf = self.index_selected_cells_fg if self.cell_options[r]['highlight'][1] is None or self.MT.display_selected_fg_over_highlights else self.cell_options[r]['highlight'][1]
        elif r in actual_selected_rows:
            tf = self.index_selected_rows_fg
        elif r in selected_rows or selected_cols:
            tf = self.index_selected_cells_fg
        elif r in self.cell_options and 'highlight' in self.cell_options[r]:
            if self.cell_options[r]['highlight'][0] is not None:
                redrawn = self.redraw_highlight(0, 
                                                fr + 1, 
                                                self.current_width - 1, 
                                                sr,
                                                fill = self.cell_options[r]['highlight'][0], 
                                                outline = self.index_fg if hlrow in self.cell_options and 'dropdown' in self.cell_options[hlrow] else "", 
                                                tag = "s")
            tf = self.index_fg if self.cell_options[r]['highlight'][1] is None else self.cell_options[r]['highlight'][1]
        else:
            tf = self.index_fg
        return tf, redrawn

    def redraw_highlight(self, x1, y1, x2, y2, fill, outline, tag):
        if self.hidd_high:
            t, sh = self.hidd_high.popitem()
            self.coords(t, x1, y1, x2, y2)
            if sh:
                self.itemconfig(t, fill = fill, outline = outline, tag = tag)
            else:
                self.itemconfig(t, fill = fill, outline = outline, tag = tag, state = "normal")
            self.lift(t)
            self.disp_high[t] = True
        else:
            self.disp_high[self.create_rectangle(x1, y1, x2, y2, fill = fill, outline = outline, tag = tag)] = True
        return True

    def redraw_text(self, x, y, text, fill, font, anchor, tag):
        if self.hidd_text:
            t, sh = self.hidd_text.popitem()
            self.coords(t, x, y)
            if sh:
                self.itemconfig(t, text = text, fill = fill, font = font, anchor = anchor)
            else:
                self.itemconfig(t, text = text, fill = fill, font = font, anchor = anchor, state = "normal")
            self.lift(t)
        else:
            t = self.create_text(x, y, text = text, fill = fill, font = font, anchor = anchor, tag = tag)
        self.disp_text[t] = True
        return t

    def redraw_gridline(self, x1, y1, x2, y2, fill, width, tag):
        if self.hidd_grid:
            t, sh = self.hidd_grid.popitem()
            self.coords(t, x1, y1, x2, y2)
            if sh:
                self.itemconfig(t, fill = fill, width = width, tag = tag)
            else:
                self.itemconfig(t, fill = fill, width = width, tag = tag, state = "normal")
            self.disp_grid[t] = True
        else:
            self.disp_grid[self.create_line(x1, y1, x2, y2, fill = fill, width = width, tag = tag)] = True
            
    def redraw_dropdown(self, x1, y1, x2, y2, fill, outline, tag, draw_outline = True, draw_arrow = True):
        if draw_outline:
            self.redraw_highlight(x1 + 1, y1 + 1, x2, y2, fill = "", outline = self.index_fg, tag = tag)
        if draw_arrow:
            topysub = floor(self.MT.half_txt_h / 2)
            mid_y = y1 + floor(self.MT.half_txt_h / 2) + 5
            #top left points for triangle
            ty1 = mid_y - topysub + 2
            tx1 = x2 - self.MT.txt_h + 1
            #bottom points for triangle
            ty2 = mid_y + self.MT.half_txt_h - 4
            tx2 = x2 - self.MT.half_txt_h - 1
            #top right points for triangle
            ty3 = mid_y - topysub + 2
            tx3 = x2 - 3
            if tx2 - tx1 > tx3 - tx2:
                tx1 += (tx2 - tx1) - (tx3 - tx2)
            elif tx2 - tx1 < tx3 - tx2:
                tx1 -= (tx3 - tx2) - (tx2 - tx1)
            points = (tx1, ty1, tx2, ty2, tx3, ty3)
            if self.hidd_dropdown:
                t, sh = self.hidd_dropdown.popitem()
                self.coords(t, points)
                if sh:
                    self.itemconfig(t, fill = fill)
                else:
                    self.itemconfig(t, fill = fill, tag = tag, state = "normal")
                self.lift(t)
            else:
                t = self.create_line(points, fill = fill, width = 2, capstyle = tk.ROUND, joinstyle = tk.ROUND, tag = tag)
            self.disp_dropdown[t] = True
            
    def redraw_checkbox(self, r, x1, y1, x2, y2, fill, outline, tag, draw_check = False):
        points = self.MT.get_checkbox_points(x1, y1, x2, y2)
        if self.hidd_checkbox:
            t, sh = self.hidd_checkbox.popitem()
            self.coords(t, points)
            if sh:
                self.itemconfig(t, fill = outline, outline = fill)
            else:
                self.itemconfig(t, fill = outline, outline = fill, tag = tag, state = "normal")
            self.lift(t)
        else:
            t = self.create_polygon(points, fill = outline, outline = fill, tag = tag, smooth = True)
        self.disp_checkbox[t] = True
        if draw_check:
            # draw filled box
            x1 = x1 + 2
            y1 = y1 + 2
            x2 = x2 - 1
            y2 = y2 - 1
            points = self.MT.get_checkbox_points(x1, y1, x2, y2)
            if self.hidd_checkbox:
                t, sh = self.hidd_checkbox.popitem()
                self.coords(t, points)
                if sh:
                    self.itemconfig(t, fill = fill, outline = outline)
                else:
                    self.itemconfig(t, fill = fill, outline = outline, tag = tag, state = "normal")
                self.lift(t)
            else:
                t = self.create_polygon(points, fill = fill, outline = outline, tag = tag, smooth = True)
            self.disp_checkbox[t] = True

            # draw one line of X
            if self.hidd_grid:
                t, sh = self.hidd_grid.popitem()
                self.coords(t, x1 + 2, y1 + 2, x2 - 2, y2 - 2)
                if sh:
                    self.itemconfig(t, fill = self.get_widget_bg_fg(r)[0], capstyle = tk.ROUND, joinstyle = tk.ROUND, width = 2)
                else:
                    self.itemconfig(t, fill = self.get_widget_bg_fg(r)[0], capstyle = tk.ROUND, joinstyle = tk.ROUND, width = 2, tag = tag, state = "normal")
                self.lift(t)
            else:
                t = self.create_line(x1 + 2, y1 + 2, x2 - 2, y2 - 2, fill = self.get_widget_bg_fg(r)[0], capstyle = tk.ROUND, joinstyle = tk.ROUND, width = 2, tag = tag)
            self.disp_grid[t] = True

            # draw other line of X
            if self.hidd_grid:
                t, sh = self.hidd_grid.popitem()
                self.coords(t, x2 - 2, y1 + 2, x1 + 2, y2 - 2)
                if sh:
                    self.itemconfig(t, fill = self.get_widget_bg_fg(r)[0], capstyle = tk.ROUND, joinstyle = tk.ROUND, width = 2)
                else:
                    self.itemconfig(t, fill = self.get_widget_bg_fg(r)[0], capstyle = tk.ROUND, joinstyle = tk.ROUND, width = 2, tag = tag, state = "normal")
                self.lift(t)
            else:
                t = self.create_line(x2 - 2, y1 + 2, x1 + 2, y2 - 2, fill = self.get_widget_bg_fg(r)[0], capstyle = tk.ROUND, joinstyle = tk.ROUND, width = 2, tag = tag)
            self.disp_grid[t] = True

    def redraw_grid_and_text(self, last_row_line_pos, y1, y_stop, start_row, end_row, y2, x1, x_stop, selected_rows, selected_cols, actual_selected_rows):
        self.configure(scrollregion = (0,
                                       0,
                                       self.current_width,
                                       last_row_line_pos + self.MT.empty_vertical))
        self.hidd_text.update(self.disp_text)
        self.disp_text = {}
        self.hidd_high.update(self.disp_high)
        self.disp_high = {}
        self.hidd_grid.update(self.disp_grid)
        self.disp_grid = {}
        self.hidd_dropdown.update(self.disp_dropdown)
        self.disp_dropdown = {}
        self.hidd_checkbox.update(self.disp_checkbox)
        self.disp_checkbox = {}
        self.visible_row_dividers = []
        y = self.MT.row_positions[start_row]
        self.redraw_gridline(0, y, self.current_width, y, fill = self.index_grid_fg, width = 1, tag = "fh")
        xend = self.current_width - 6
        self.row_width_resize_bbox = (self.current_width - 2, y1, self.current_width, y2)
        for r in range(start_row + 1, end_row):
            y = self.MT.row_positions[r]
            if self.height_resizing_enabled:
                self.visible_row_dividers.append((1, y - 2, xend, y + 2))
            self.redraw_gridline(0, y, self.current_width, y, fill = self.index_grid_fg, width = 1, tag = ("h", f"{r}"))
        sb = y2 + 2
        c_2 = self.index_selected_cells_bg if self.index_selected_cells_bg.startswith("#") else Color_Map_[self.index_selected_cells_bg]
        c_3 = self.index_selected_rows_bg if self.index_selected_rows_bg.startswith("#") else Color_Map_[self.index_selected_rows_bg]
        for r in range(start_row, end_row - 1):
            fr = self.MT.row_positions[r]
            sr = self.MT.row_positions[r + 1]
            if sr - fr < self.MT.txt_h:
                continue
            if sr > sb:
                sr = sb
            if self.MT.all_rows_displayed:
                drow = r
            else:
                drow = self.MT.displayed_rows[r]
            tf, dd_drawn = self.redraw_highlight_get_text_fg(fr, sr, r, c_2, c_3, selected_rows, selected_cols, actual_selected_rows, drow)
            
            if r in self.cell_options and 'align' in self.cell_options[r]:
                cell_alignment = self.cell_options[r]['align']
            elif r in self.MT.row_options and 'align' in self.MT.row_options[r]:
                cell_alignment = self.MT.row_options[r]['align']
            else:
                cell_alignment = self.align
                
            if cell_alignment == "w":
                x = 5
                if r in self.cell_options and 'dropdown' in self.cell_options[r]:
                    mw = self.current_width - self.MT.txt_h - 2
                    self.redraw_dropdown(0, fr, self.current_width - 1, sr - 1, fill = tf, outline = tf, tag = "dd", draw_outline = not dd_drawn, draw_arrow = mw >= 5)
                else:
                    mw = self.current_width - 5

            elif cell_alignment == "e":
                if r in self.cell_options and 'dropdown' in self.cell_options[r]:
                    mw = self.current_width - self.MT.txt_h - 2
                    x = self.current_width - 5 - self.MT.txt_h
                    self.redraw_dropdown(0, fr, self.current_width - 1, sr - 1, fill = tf, outline = tf, tag = "dd", draw_outline = not dd_drawn, draw_arrow = mw >= 5)
                else:
                    mw = self.current_width - 5
                    x = self.current_width - 5

            elif cell_alignment == "center":
                if r in self.cell_options and 'dropdown' in self.cell_options[r]:
                    mw = self.current_width - self.MT.txt_h - 2
                    x = ceil((self.current_width - self.MT.txt_h) / 2)
                    self.redraw_dropdown(0, fr, self.current_width - 1, sr - 1, fill = tf, outline = tf, tag = "dd", draw_outline = not dd_drawn, draw_arrow = mw >= 5)
                else:
                    mw = self.current_width - 1
                    x = floor(self.current_width / 2)

            if r in self.cell_options and 'checkbox' in self.cell_options[r]:
                if mw > + 2:
                    box_w = self.MT.txt_h + 2
                    if cell_alignment == "w":
                        x += box_w
                    elif cell_alignment == "center":
                        x += ceil(box_w / 2)
                    mw = mw - box_w
                    self.redraw_checkbox(r,
                                         0,
                                         fr + 2,
                                         self.MT.txt_h + 2,
                                         fr + self.MT.txt_h + 4,
                                         fill = tf if self.cell_options[r]['checkbox']['state'] == "normal" else self.index_grid_fg,
                                         outline = "",
                                         tag = "cb",
                                         draw_check = self.MT._row_index[r] if isinstance(self.MT._row_index, (list, tuple)) else self.MT.data_ref[r][self.MT._row_index])

            try:
                if r in self.cell_options and 'checkbox' in self.cell_options[r]:
                    lns = self.cell_options[r]['checkbox']['text'].split("\n") if isinstance(self.cell_options[r]['checkbox']['text'], str) else f"{self.cell_options[r]['checkbox']['text']}".split("\n")
                elif isinstance(self.MT._row_index, int):
                    lns = self.MT.data_ref[r][self.MT._row_index].split("\n") if isinstance(self.MT.data_ref[r][self.MT._row_index], str) else f"{self.MT.data_ref[r][self.MT._row_index]}".split("\n")
                else:
                    lns = self.MT._row_index[r].split("\n") if isinstance(self.MT._row_index[r], str) else f"{self.MT._row_index[r]}".split("\n")
                got_idx = True
            except:
                got_idx = False
            if not got_idx or (lns == [""] and self.show_default_index_for_empty):
                if self.default_index == "letters":
                    lns = (num2alpha(r), )
                elif self.default_index == "numbers":
                    lns = (f"{r + 1}", )
                else:
                    lns = (f"{r + 1} {num2alpha(r)}", )

            y = fr + self.MT.fl_ins
            if cell_alignment == "center":
                if mw <= 5:
                    continue
                start_line = int((y1 - y) / self.MT.xtra_lines_increment) - 1
                if start_line > 0:
                    start_line += 1
                    y += (start_line * self.MT.xtra_lines_increment)
                elif start_line < 0:
                    start_line = 0
                if len(lns) > start_line:
                    for txt in islice(lns, start_line, None):
                        if y + self.MT.half_txt_h - 1 > y1:
                            t = self.redraw_text(x, y, text = txt, fill = tf, font = self.MT._font, anchor = cell_alignment, tag = "t")
                            wd = self.bbox(t)
                            wd = wd[2] - wd[0]
                            if wd > mw:
                                tl = len(txt)
                                tmod = ceil((tl - int(tl * (mw / wd))) / 2)
                                txt = txt[tmod - 1:-tmod]
                                self.itemconfig(t, text = txt)
                                wd = self.bbox(t)
                                self.c_align_cyc = cycle(self.centre_alignment_text_mod_indexes)
                                while wd[2] - wd[0] > mw:
                                    txt = txt[next(self.c_align_cyc)]
                                    self.itemconfig(t, text = txt)
                                    wd = self.bbox(t)
                                self.coords(t, x, y)
                        y += self.MT.xtra_lines_increment
                        if y + self.MT.half_txt_h - 1 > sr:
                            break

            elif cell_alignment == "e":
                if mw <= 5:
                    continue
                start_line = int((y1 - y) / self.MT.xtra_lines_increment) - 1
                if start_line > 0:
                    start_line += 1
                    y += (start_line * self.MT.xtra_lines_increment)
                elif start_line < 0:
                    start_line = 0
                if len(lns) > start_line:
                    for txt in islice(lns, start_line, None):
                        if y + self.MT.half_txt_h - 1 > y1:
                            t = self.redraw_text(x, y, text = txt, fill = tf, font = self.MT._font, anchor = cell_alignment, tag = "t")
                            wd = self.bbox(t)
                            wd = wd[2] - wd[0]
                            if wd > mw:
                                txt = txt[len(txt) - int(len(txt) * (mw / wd)):]
                                self.itemconfig(t, text = txt)
                                wd = self.bbox(t)
                                while wd[2] - wd[0] > mw:
                                    txt = txt[1:]
                                    self.itemconfig(t, text = txt)
                                    wd = self.bbox(t)
                        y += self.MT.xtra_lines_increment
                        if y + self.MT.half_txt_h - 1 > sr:
                            break

            elif cell_alignment == "w":
                if mw <= 5:
                    continue
                start_line = int((y1 - y) / self.MT.xtra_lines_increment) - 1
                if start_line > 0:
                    start_line += 1
                    y += (start_line * self.MT.xtra_lines_increment)
                elif start_line < 0:
                    start_line = 0
                if len(lns) > start_line:
                    for txt in islice(lns, start_line, None):
                        if y + self.MT.half_txt_h - 1 > y1:
                            t = self.redraw_text(x, y, text = txt, fill = tf, font = self.MT._font, anchor = cell_alignment, tag = "t")
                            if drow in self.cell_options and 'dropdown' in self.cell_options[drow]:
                                wd = self.bbox(t)
                                wd = wd[2] - wd[0]
                                if wd > mw:
                                    nl = int(len(txt) * (mw / wd))
                                    self.itemconfig(t, text = txt[:nl])
                                    wd = self.bbox(t)
                                    while wd[2] - wd[0] > mw:
                                        nl -= 1
                                        self.dchars(t, nl)
                                        wd = self.bbox(t)
                        y += self.MT.xtra_lines_increment
                        if y + self.MT.half_txt_h - 1 > sr:
                            break
    
        self.redraw_gridline(self.current_width - 1, y1, self.current_width - 1, y_stop, fill = self.index_border_fg, width = 1, tag = "v")
        for t, sh in self.hidd_text.items():
            if sh:
                self.itemconfig(t, state = "hidden")
                self.hidd_text[t] = False
        for t, sh in self.hidd_high.items():
            if sh:
                self.itemconfig(t, state = "hidden")
                self.hidd_high[t] = False
        for t, sh in self.hidd_grid.items():
            if sh:
                self.itemconfig(t, state = "hidden")
                self.hidd_grid[t] = False
        for t, sh in self.hidd_dropdown.items():
            if sh:
                self.itemconfig(t, state = "hidden")
                self.hidd_dropdown[t] = False
        for t, sh in self.hidd_checkbox.items():
            if sh:
                self.itemconfig(t, state = "hidden")
                self.hidd_checkbox[t] = False
                
    def open_cell(self, event = None):
        if not self.MT.anything_selected() or self.text_editor_id is not None:
            return
        currently_selected = self.MT.currently_selected(get_coords = True)
        if not currently_selected:
            return
        r = int(currently_selected[0])
        drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
        if drow in self.cell_options and 'readonly' in self.cell_options[drow]:
            return
        elif drow in self.cell_options and ('dropdown' in self.cell_options[drow] or 'checkbox' in self.cell_options[drow]):
            if self.MT.event_opens_dropdown_or_checkbox(event):
                if 'dropdown' in self.cell_options[drow]:
                    self.display_dropdown_window(r, event = event)
                elif 'checkbox' in self.cell_options[drow]:
                    self._click_checkbox(r, drow)
        elif self.edit_cell_enabled:
            self.edit_cell_(event, r = r, dropdown = False)

    # r is displayed row
    def edit_cell_(self, event = None, r = None, dropdown = False):
        text = None
        extra_func_key = "??"
        if event is None or self.MT.event_opens_dropdown_or_checkbox(event):
            if event is not None:
                if hasattr(event, 'keysym') and event.keysym == 'Return':
                    extra_func_key = "Return"
                elif hasattr(event, 'keysym') and event.keysym == 'F2':
                    extra_func_key = "F2"
            drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
            if isinstance(self.MT._row_index, list):
                if len(self.MT._row_index) <= drow:
                    self.MT._row_index.extend(list(repeat("", drow - len(self.MT._row_index) + 1)))
                text = f"{self.MT._row_index[drow]}"
            elif isinstance(self.MT._row_index, int):
                try:
                    text = f"{self.MT.data_ref[self.MT._row_index][drow]}"
                except:
                    text = ""
            if self.MT.cell_auto_resize_enabled:
                self.set_row_height_run_binding(r)
        elif event is not None and ((hasattr(event, 'keysym') and event.keysym == 'BackSpace') or
                                  event.keycode in (8, 855638143)
                                  ):
            extra_func_key = "BackSpace"
            text = ""
        elif event is not None and ((hasattr(event, "char") and event.char.isalpha()) or
                                    (hasattr(event, "char") and event.char.isdigit()) or
                                    (hasattr(event, "char") and event.char in symbols_set)):
            extra_func_key = event.char
            text = event.char
        else:
            return False
        self.text_editor_loc = r
        if self.extra_begin_edit_cell_func is not None:
            try:
                text2 = self.extra_begin_edit_cell_func(EditIndexEvent(r, extra_func_key, text, "begin_edit_index"))
            except:
                return False
            if text2 is not None:
                text = text2
        text = "" if text is None else text
        self.select_row(r = r, keep_other_selections = True)
        self.create_text_editor(r = r, text = text, set_data_ref_on_destroy = True, dropdown = dropdown)
        return True

    # r is displayed row
    def create_text_editor(self,
                           r = 0,
                           text = None,
                           state = "normal",
                           see = True,
                           set_data_ref_on_destroy = False,
                           binding = None,
                           dropdown = False):
        if r == self.text_editor_loc and self.text_editor is not None:
            self.text_editor.set_text(self.text_editor.get() + "" if not isinstance(text, str) else text)
            return
        if self.text_editor is not None:
            self.destroy_text_editor()
        if see:
            has_redrawn = self.MT.see(r = r, c = 0, keep_yscroll = True, check_cell_visibility = True)
            if not has_redrawn:
                self.MT.refresh()
        self.text_editor_loc = r
        x = 0
        y = self.MT.row_positions[r] + 1
        w = self.current_width + 1
        h = self.MT.row_positions[r + 1] - y
        drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
        if text is None:
            if isinstance(self.MT._row_index, list):
                if len(self.MT._row_index) <= drow:
                    self.MT._row_index.extend(list(repeat("", drow - len(self.MT._row_index) + 1)))
                text = f"{self.MT._row_index[drow]}"
            elif isinstance(self.MT._row_index, int):
                try:
                    text = f"{self.MT.data_ref[self.MT._row_index][drow]}"
                except:
                    text = ""
        bg, fg = self.get_widget_bg_fg(drow)
        self.text_editor = TextEditor(self,
                                      text = text,
                                      font = self.MT._font,
                                      state = state,
                                      width = w,
                                      height = h,
                                      border_color = self.MT.table_selected_cells_border_fg,
                                      show_border = False,
                                      bg = bg,
                                      fg = fg,
                                      popup_menu_font = self.MT.popup_menu_font,
                                      popup_menu_fg = self.MT.popup_menu_fg,
                                      popup_menu_bg = self.MT.popup_menu_bg,
                                      popup_menu_highlight_bg = self.MT.popup_menu_highlight_bg,
                                      popup_menu_highlight_fg = self.MT.popup_menu_highlight_fg)
        self.text_editor_id = self.create_window((x, y), window = self.text_editor, anchor = "nw")
        if not dropdown:
            self.text_editor.textedit.focus_set()
            self.text_editor.scroll_to_bottom()
        self.text_editor.textedit.bind("<Alt-Return>", lambda x: self.text_editor_newline_binding(r))
        if USER_OS == 'Darwin':
            self.text_editor.textedit.bind("<Option-Return>", lambda x: self.text_editor_newline_binding(r))
        for key, func in self.MT.text_editor_user_bound_keys.items():
            self.text_editor.textedit.bind(key, func)
        if binding is not None:
            self.text_editor.textedit.bind("<Tab>", lambda x: binding((r, "Tab")))
            self.text_editor.textedit.bind("<Return>", lambda x: binding((r, "Return")))
            self.text_editor.textedit.bind("<FocusOut>", lambda x: binding((r, "FocusOut")))
            self.text_editor.textedit.bind("<Escape>", lambda x: binding((r, "Escape")))
        elif binding is None and set_data_ref_on_destroy:
            self.text_editor.textedit.bind("<Tab>", lambda x: self.get_text_editor_value((r, "Tab")))
            self.text_editor.textedit.bind("<Return>", lambda x: self.get_text_editor_value((r, "Return")))
            if not dropdown:
                self.text_editor.textedit.bind("<FocusOut>", lambda x: self.get_text_editor_value((r, "FocusOut")))
            self.text_editor.textedit.bind("<Escape>", lambda x: self.get_text_editor_value((r, "Escape")))
        else:
            self.text_editor.textedit.bind("<Escape>", lambda x: self.destroy_text_editor("Escape"))
            
    def bind_cell_edit(self, enable = True):
        if enable:
            self.edit_cell_enabled = True
        else:
            self.edit_cell_enabled = False

    def bind_text_editor_destroy(self, binding, r):
        self.text_editor.textedit.bind("<Return>", lambda x: binding((r, "Return")))
        self.text_editor.textedit.bind("<FocusOut>", lambda x: binding((r, "FocusOut")))
        self.text_editor.textedit.bind("<Escape>", lambda x: binding((r, "Escape")))
        self.text_editor.textedit.focus_set()

    def destroy_text_editor(self, event = None):
        if event is not None and self.extra_end_edit_cell_func is not None and self.text_editor_loc is not None:
            self.extra_end_edit_cell_func(EditHeaderEvent(int(self.text_editor_loc), "Escape", None, "escape_edit_index"))
        self.text_editor_loc = None
        try:
            self.delete(self.text_editor_id)
        except:
            pass
        try:
            self.text_editor.destroy()
        except:
            pass
        try:
            self.text_editor = None
        except:
            pass
        try:
            self.text_editor_id = None
        except:
            pass
        if event is not None and len(event) >= 3 and "Escape" in event:
            self.focus_set()

    # r is displayed row
    def get_text_editor_value(self, editor_info = None, r = None, set_data_ref_on_destroy = True, event = None, destroy = True, move_down = True, redraw = True, recreate = True):
        if self.focus_get() is None and editor_info:
            return
        if editor_info is not None and len(editor_info) >= 2 and editor_info[1] == "Escape":
            self.destroy_text_editor("Escape")
            self.hide_dropdown_window(r)
            return
        if self.text_editor is not None:
            self.text_editor_value = self.text_editor.get()
        if destroy:
            self.destroy_text_editor()
        if set_data_ref_on_destroy:
            if r is None and editor_info is not None and len(editor_info) >= 2:
                r = editor_info[0]
            if self.extra_end_edit_cell_func is None:
                self._set_cell_data(r, drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r], value = self.text_editor_value)
            elif self.extra_end_edit_cell_func is not None and self.MT.edit_cell_validation:
                validation = self.extra_end_edit_cell_func(EditIndexEvent(r, editor_info[1] if len(editor_info) >= 2 else "FocusOut", f"{self.text_editor_value}", "end_edit_index"))
                if validation is not None:
                    self.text_editor_value = validation
                self._set_cell_data(r, drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r], value = self.text_editor_value)
            elif self.extra_end_edit_cell_func is not None and not self.MT.edit_cell_validation:
                self._set_cell_data(r, drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r], value = self.text_editor_value)
                self.extra_end_edit_cell_func(EditIndexEvent(r, editor_info[1] if len(editor_info) >= 2 else "FocusOut", f"{self.text_editor_value}", "end_edit_index"))
        if move_down:
            if r is None and editor_info is not None and len(editor_info) >= 2:
                r = editor_info[0]
            currently_selected = self.MT.currently_selected()
            if r is not None:
                if (
                    currently_selected and
                    r == currently_selected[0] and
                    (self.MT.single_selection_enabled or self.MT.toggle_selection_enabled)
                    ):
                    if editor_info is not None and len(editor_info) >= 2 and editor_info[1] == "Return":
                        self.select_row(r)
                        self.MT.see(r, 0, keep_yscroll = True, bottom_right_corner = True, check_cell_visibility = True)
                    elif editor_info is not None and len(editor_info) >= 2 and editor_info[1] == "Tab":
                        self.select_row(r + 1 if r < len(self.MT.row_positions) - 2 else r)
                        self.MT.see(r + 1 if r < len(self.MT.row_positions) - 2 else r, 0, keep_yscroll = True, bottom_right_corner = True, check_cell_visibility = True)
        self.hide_dropdown_window(r)
        if recreate:
            self.MT.recreate_all_selection_boxes()
        if redraw:
            self.MT.refresh()
        if editor_info is not None and len(editor_info) >= 2 and editor_info[1] != "FocusOut":
            self.focus_set()
        return self.text_editor_value

    #internal event use
    def _set_cell_data(self, r = 0, drow = None, value = "", cell_resize = True, undo = True):
        if drow is None:
            drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
        if isinstance(self.MT._row_index, list):
            if len(self.MT._row_index) <= drow:
                self.MT._row_index.extend(list(repeat("", drow - len(self.MT._row_index) + 1)))
            if self.MT.undo_enabled and undo:
                if self.MT._row_index[drow] != value:
                    self.MT.undo_storage.append(zlib.compress(pickle.dumps(("edit_index",
                                                                           {drow: self.MT._row_index[drow]},
                                                                           (((r, 0, r + 1, len(self.MT.col_positions) - 1), "rows"), ),
                                                                           self.MT.currently_selected()))))
            self.MT._row_index[drow] = value
        elif isinstance(self.MT._row_index, int):
            self.MT._set_cell_data(r = self.MT._row_index, c = c, drow = drow, value = value, undo = True)
        if cell_resize and self.MT.cell_auto_resize_enabled:
            self.set_row_height_run_binding(r)
            self.MT.refresh()
            
    def set_row_height_run_binding(self, r):
        old_height = self.MT.row_positions[r + 1] - self.MT.row_positions[r]
        new_height = self.set_row_height(r, only_set_if_too_small = True)
        if self.row_height_resize_func is not None and old_height != new_height:
            self.row_height_resize_func(ResizeEvent("row_height_resize", r, old_height, new_height))

    #internal event use
    def _click_checkbox(self, r, drow = None, undo = True, redraw = True):
        if drow is None:
            drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
        if self.cell_options[drow]['checkbox']['state'] == "normal":
            if isinstance(self.MT._row_index, list):
                self._set_cell_data(r, drow, value = not self.MT._row_index[drow] if type(self.MT._row_index[drow]) == bool else False, cell_resize = False)
            elif isinstance(self.MT._row_index, int):
                self._set_cell_data(r, drow, value = not self.MT.data_ref[self.MT._row_index][drow] if type(self.MT.data_ref[self.MT._row_index][drow]) == bool else False, cell_resize = False)
            if self.cell_options[drow]['checkbox']['check_function'] is not None:
                self.cell_options[drow]['checkbox']['check_function']((r, 0, "IndexCheckboxClicked", f"{self.MT._row_index[drow] if isinstance(self.MT._row_index, list) else self.MT.data_ref[self.MT._row_index][drow]}"))
            if self.extra_end_edit_cell_func is not None:
                self.extra_end_edit_cell_func(EditIndexEvent(r, "Return", f"{self.MT._row_index[drow] if isinstance(self.MT._row_index, list) else self.MT.data_ref[self.MT._row_index][drow]}", "end_edit_index"))
        if redraw:
            self.MT.refresh()

    def create_checkbox(self, r = 0, checked = False, state = "normal", redraw = False, check_function = None, text = ""):
        if r in self.cell_options and any(x in self.cell_options[r] for x in ('dropdown', 'checkbox')):
            self.destroy_dropdown_and_checkbox(r)
        self._set_cell_data(drow = r, value = checked, cell_resize = False, undo = False)
        if r not in self.cell_options:
            self.cell_options[r] = {}
        self.cell_options[r]['checkbox'] = {'check_function': check_function,
                                            'state': state,
                                            'text': text}
        if redraw:
            self.MT.refresh()

    def create_dropdown(self, r = 0, values = [], set_value = None, state = "readonly", redraw = True, selection_function = None, modified_function = None):
        if r in self.cell_options and any(x in self.cell_options[r] for x in ('dropdown', 'checkbox')):
            self.destroy_dropdown_and_checkbox(r)
        if values:
            self.MT.row_index(newindex = set_value if set_value is not None else values[0], index = r)
        elif not values and set_value is not None:
            self.MT.row_index(newindex = set_value, index = r)
        if r not in self.cell_options:
            self.cell_options[r] = {}
        self.cell_options[r]['dropdown'] = {'values': values,
                                            'align': "w",
                                            'window': "no dropdown open",
                                            'canvas_id': "no dropdown open",
                                            'select_function': selection_function,
                                            'modified_function': modified_function,
                                            'state': state}
        if redraw:
            self.MT.refresh()
            
    def get_widget_bg_fg(self, r):
        bg = self.index_bg
        fg = self.index_fg
        if r in self.cell_options and 'highlight' in self.cell_options[r]:
            if self.cell_options[r]['highlight'][0] is not None:
                bg = self.cell_options[r]['highlight'][0]
            if self.cell_options[r]['highlight'][1] is not None:
                fg = self.cell_options[r]['highlight'][1]
        return bg, fg

    def get_dropdown_height_anchor(self, drow, text_editor_h = None):
        numvalues = sum(len(v.split("\n") if isinstance(v, str) else f"{v}".split("\n")) for v in self.cell_options[drow]['dropdown']['values'])
        xscroll_h = self.MT.parentframe.xscroll.winfo_height()
        if numvalues > 5:
            linespace = 6 * 5 + 3
            win_h = int(self.MT.txt_h * 6 + linespace + xscroll_h)
        else:
            linespace = numvalues * 5 + 3
            win_h = int(self.MT.txt_h * numvalues + linespace + xscroll_h)
        if win_h > 400:
            win_h = 400
        space_bot = self.MT.get_space_bot(0, text_editor_h)
        win_h2 = int(win_h)
        if win_h > space_bot:
            win_h = space_bot - 1
        if win_h < self.MT.txt_h + 5:
            win_h = self.MT.txt_h + 5
        elif win_h > win_h2:
            win_h = win_h2
        return win_h, "nw"

    def text_editor_newline_binding(self, r = None, event = None):
        if self.MT.GetLinesHeight(self.text_editor.get_num_lines() + 1) > self.text_editor.winfo_height():
            self.text_editor.config(height = self.text_editor.winfo_height() + self.MT.xtra_lines_increment)
            drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
            if r if self.MT.all_rows_displayed else self.MT.displayed_rows[r] in self.cell_options and 'dropdown' in self.cell_options[drow]:
                text_editor_h = self.text_editor.winfo_height()
                win_h, anchor = self.get_dropdown_height_anchor(drow, text_editor_h)
                self.coords(self.cell_options[drow]['dropdown']['canvas_id'],
                            self.MT.canvasx(0), self.MT.row_positions[r])
                self.itemconfig(self.cell_options[drow]['dropdown']['canvas_id'],
                                anchor = anchor, height = win_h)
            
    # r is displayed row
    def display_dropdown_window(self, r, drow = None, event = None):
        self.destroy_text_editor("Escape")
        self.delete_opened_dropdown_window()
        if drow is None:
            drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
        if self.cell_options[drow]['dropdown']['state'] == "normal":
            if not self.edit_cell_(r = r, dropdown = True, event = event):
                return
        bg, fg = self.get_widget_bg_fg(drow)
        win_h, anchor = self.get_dropdown_height_anchor(drow)
        window = self.MT.parentframe.dropdown_class(self.MT.winfo_toplevel(),
                                                    r,
                                                    0,
                                                    width = self.current_width,
                                                    height = win_h,
                                                    font = self.MT._font,
                                                    bg = bg,
                                                    fg = fg,
                                                    outline_color = fg,
                                                    values = self.cell_options[drow]['dropdown']['values'],
                                                    hide_dropdown_window = self.hide_dropdown_window,
                                                    arrowkey_RIGHT = self.MT.arrowkey_RIGHT,
                                                    arrowkey_LEFT = self.MT.arrowkey_LEFT,
                                                    align = self.cell_options[drow]['dropdown']['align'],
                                                    single_index = "r")
        ypos = self.MT.row_positions[r + 1]
        self.cell_options[drow]['dropdown']['canvas_id'] = self.create_window((0, ypos),
                                                                               window = window,
                                                                               anchor = anchor)
        if self.cell_options[drow]['dropdown']['state'] == "normal":
            if self.cell_options[drow]['dropdown']['modified_function'] is not None:
                self.text_editor.textedit.bind("<<TextModified>>", self.cell_options[drow]['dropdown']['modified_function'])
            self.update_idletasks()
            try:
                self.after(1, lambda: self.text_editor.textedit.focus())
                self.after(2, self.text_editor.scroll_to_bottom())
            except:
                return
        else:
            window.bind("<FocusOut>", lambda x: self.hide_dropdown_window(r))
            self.update_idletasks()
            window.focus_set()
        self.existing_dropdown_window = window
        self.cell_options[drow]['dropdown']['window'] = window
        self.existing_dropdown_canvas_id = self.cell_options[drow]['dropdown']['canvas_id']
            
    # r is displayed row
    def hide_dropdown_window(self, r = None, selection = None, b1 = False, redraw = True):
        if r is not None and selection is not None:
            drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
            if self.cell_options[drow]['dropdown']['select_function'] is not None: # user has specified a selection function
                self.cell_options[drow]['dropdown']['select_function'](EditIndexEvent(r, "IndexComboboxSelected", f"{selection}", "end_edit_index"))
            if self.extra_end_edit_cell_func is None:
                self._set_cell_data(r, drow, selection, cell_resize = True)
            elif self.extra_end_edit_cell_func is not None and self.MT.edit_cell_validation:
                validation = self.extra_end_edit_cell_func(EditIndexEvent(r, "IndexComboboxSelected", f"{selection}", "end_edit_index"))
                if validation is not None:
                    selection = validation
                self._set_cell_data(r, drow, selection, cell_resize = True)
            elif self.extra_end_edit_cell_func is not None and not self.MT.edit_cell_validation:
                self._set_cell_data(r, drow, selection, cell_resize = True)
                self.extra_end_edit_cell_func(EditIndexEvent(r, "IndexComboboxSelected", f"{selection}", "end_edit_index"))
            self.focus_set()
            self.MT.recreate_all_selection_boxes()
            if redraw:
                self.MT.refresh()
        if self.existing_dropdown_window is not None:
            closedr, return_closedr = int(self.existing_dropdown_window.r), True
        else:
            return_closedr = False
        if b1 and self.text_editor_loc is not None and self.text_editor is not None:
            self.get_text_editor_value(editor_info = (self.text_editor_loc, "Return"))
        else:
            self.destroy_text_editor("Escape")
        self.delete_opened_dropdown_window(r)
        if return_closedr:
            return closedr
            
    # r is displayed row
    def delete_opened_dropdown_window(self, r = None, drow = None):
        if r is not None and drow is None:
            drow = r if self.MT.all_rows_displayed else self.MT.displayed_rows[r]
        try:
            self.delete(self.existing_dropdown_canvas_id)
        except:
            pass
        self.existing_dropdown_canvas_id = None
        try:
            self.existing_dropdown_window.destroy()
        except:
            pass
        self.existing_dropdown_window = None
        if r is not None and drow in self.cell_options and 'dropdown' in self.cell_options[drow]:
            self.cell_options[drow]['dropdown']['canvas_id'] = "no dropdown open"
            self.cell_options[drow]['dropdown']['window'] = "no dropdown open"
            try:
                self.delete(self.cell_options[drow]['dropdown']['canvas_id'])
            except:
                pass
            
    # r is drow
    def destroy_dropdown(self, r):
        self.delete_opened_dropdown_window(r)
        if r in self.cell_options and 'dropdown' in self.cell_options[r]:
            del self.cell_options[r]['dropdown']
            
    # r is drow
    def destroy_checkbox(self, r):
        if r in self.cell_options and 'checkbox' in self.cell_options[r]:
            del self.cell_options[r]['checkbox']

    # r is drow
    def destroy_dropdown_and_checkbox(self, r):
        self.destroy_dropdown(r)
        self.destroy_checkbox(r)
