from ._tksheet_vars import * from ._tksheet_other_classes import * from collections import defaultdict, deque from itertools import islice, repeat, accumulate, chain, product, cycle from math import floor, ceil from tkinter import ttk import bisect import csv as csv_module import io import pickle import re import tkinter as tk import zlib # for mac bindings from platform import system as get_os class ColumnHeaders(tk.Canvas): def __init__(self, parentframe = None, main_canvas = None, row_index_canvas = None, max_colwidth = None, max_header_height = None, default_header = None, header_align = None, header_bg = None, header_border_fg = None, header_grid_fg = None, header_fg = None, header_selected_cells_bg = None, header_selected_cells_fg = None, header_selected_columns_bg = "#5f6368", header_selected_columns_fg = "white", header_select_bold = True, drag_and_drop_bg = None, column_drag_and_drop_perform = True, measure_subset_header = True, resizing_line_fg = None): tk.Canvas.__init__(self,parentframe, background = header_bg, highlightthickness = 0) self.disp_text = {} self.disp_high = {} self.disp_grid = {} self.disp_fill_sels = {} self.disp_bord_sels = {} self.disp_resize_lines = {} self.hidd_text = {} self.hidd_high = {} self.hidd_grid = {} self.hidd_fill_sels = {} self.hidd_bord_sels = {} self.hidd_resize_lines = {} 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.column_drag_and_drop_perform = column_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_double_b1_func = None self.ch_extra_begin_drag_drop_func = None self.ch_extra_end_drag_drop_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.default_hdr = default_header.lower() self.max_cw = float(max_colwidth) self.max_header_height = float(max_header_height) self.current_height = None # is set from within MainTable() __init__ or from Sheet parameters self.MT = main_canvas # is set from within MainTable() __init__ self.RI = row_index_canvas # is set from within MainTable() __init__ self.TL = None # is set from within TopLeftRectangle() __init__ self.header_fg = header_fg self.header_grid_fg = header_grid_fg self.header_border_fg = header_border_fg self.header_selected_cells_bg = header_selected_cells_bg self.header_selected_cells_fg = header_selected_cells_fg self.header_selected_columns_bg = header_selected_columns_bg self.header_selected_columns_fg = header_selected_columns_fg self.select_bold = header_select_bold self.drag_and_drop_bg = drag_and_drop_bg self.resizing_line_fg = resizing_line_fg self.align = header_align self.width_resizing_enabled = False self.height_resizing_enabled = False self.double_click_resizing_enabled = False self.col_selection_enabled = False self.drag_and_drop_enabled = False self.rc_delete_col_enabled = False self.rc_insert_col_enabled = False self.measure_subset_hdr = measure_subset_header self.dragged_col = None self.visible_col_dividers = [] self.col_height_resize_bbox = tuple() self.highlighted_cells = {} self.rsz_w = None self.rsz_h = None self.new_col_height = 0 self.currently_resizing_width = False self.currently_resizing_height = False self.bind("<Motion>", self.mouse_motion) self.bind("<ButtonPress-1>", self.b1_press) self.bind("<Shift-ButtonPress-1>",self.shift_b1_press) self.bind("<B1-Motion>", self.b1_motion) self.bind("<ButtonRelease-1>", self.b1_release) self.bind("<Double-Button-1>", self.double_b1) 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_height(self, new_height, set_TL = False): self.current_height = new_height self.config(height = new_height) if set_TL: self.TL.set_dimensions(new_h = new_height) def enable_bindings(self, binding): if binding == "column_width_resize": self.width_resizing_enabled = True if binding == "column_height_resize": self.height_resizing_enabled = True if binding == "double_click_column_resize": self.double_click_resizing_enabled = True if binding == "column_select": self.col_selection_enabled = True if binding == "drag_and_drop": self.drag_and_drop_enabled = True def disable_bindings(self, binding): if binding == "column_width_resize": self.width_resizing_enabled = False if binding == "column_height_resize": self.height_resizing_enabled = False if binding == "double_click_column_resize": self.double_click_resizing_enabled = False if binding == "column_select": self.col_selection_enabled = False if binding == "drag_and_drop": self.drag_and_drop_enabled = False def check_mouse_position_width_resizers(self, event): x = self.canvasx(event.x) y = self.canvasy(event.y) ov = None for x1, y1, x2, y2 in self.visible_col_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.focus_set() if self.MT.identify_col(x = event.x, allow_end = False) is None: self.MT.deselect("all") self.ch_rc_popup_menu.tk_popup(event.x_root, event.y_root) elif self.col_selection_enabled and not self.currently_resizing_width and not self.currently_resizing_height: c = self.MT.identify_col(x = event.x) if c < len(self.MT.col_positions) - 1: if self.MT.col_selected(c): if self.MT.rc_popup_menus_enabled: self.ch_rc_popup_menu.tk_popup(event.x_root, event.y_root) else: if self.MT.single_selection_enabled and self.MT.rc_select_enabled: self.select_col(c, redraw = True) elif self.MT.toggle_selection_enabled and self.MT.rc_select_enabled: self.toggle_select_col(c, redraw = True) if self.MT.rc_popup_menus_enabled: self.ch_rc_popup_menu.tk_popup(event.x_root, event.y_root) if self.extra_rc_func is not None: self.extra_rc_func(event) def shift_b1_press(self, event): x = event.x c = self.MT.identify_col(x = x) if self.drag_and_drop_enabled or self.col_selection_enabled and self.rsz_h is None and self.rsz_w is None: if c < len(self.MT.col_positions) - 1: c_selected = self.MT.col_selected(c) if not c_selected and self.col_selection_enabled: c = int(c) currently_selected = self.MT.currently_selected() if currently_selected and currently_selected[0] == "column": min_c = int(currently_selected[1]) self.MT.delete_selection_rects(delete_current = False) if c > min_c: self.MT.create_selected(0, min_c, len(self.MT.row_positions) - 1, c + 1, "cols") elif c < min_c: self.MT.create_selected(0, c, len(self.MT.row_positions) - 1, min_c + 1, "cols") else: self.select_col(c) 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(("shift_select_columns", tuple(sorted(self.MT.get_selected_cols())))) elif c_selected: self.dragged_col = c 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.width_resizing_enabled: ov = self.check_mouse_position_width_resizers(event) if ov is not None: for itm in ov: tgs = self.gettags(itm) if "v" == tgs[0]: break c = int(tgs[1]) self.rsz_w = c self.config(cursor = "sb_h_double_arrow") mouse_over_resize = True else: self.rsz_w = None if self.height_resizing_enabled and not mouse_over_resize: try: x1, y1, x2, y2 = self.col_height_resize_bbox[0], self.col_height_resize_bbox[1], self.col_height_resize_bbox[2], self.col_height_resize_bbox[3] if x >= x1 and y >= y1 and x <= x2 and y <= y2: self.config(cursor = "sb_v_double_arrow") self.rsz_h = True mouse_over_resize = True else: self.rsz_h = None except: self.rsz_h = 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 b1_press(self, event = None): self.focus_set() self.MT.unbind("<MouseWheel>") x1, y1, x2, y2 = self.MT.get_canvas_visible_area() if self.check_mouse_position_width_resizers(event) is None: self.rsz_w = None if self.width_resizing_enabled and self.rsz_w is not None: self.currently_resizing_width = True x = self.MT.col_positions[self.rsz_w] line2x = self.MT.col_positions[self.rsz_w - 1] self.create_resize_line(x, 0, x, self.current_height, width = 1, fill = self.resizing_line_fg, tag = "rwl") self.MT.create_resize_line(x, y1, x, y2, width = 1, fill = self.resizing_line_fg, tag = "rwl") self.create_resize_line(line2x, 0, line2x, self.current_height,width = 1, fill = self.resizing_line_fg, tag = "rwl2") self.MT.create_resize_line(line2x, y1, line2x, y2, width = 1, fill = self.resizing_line_fg, tag = "rwl2") elif self.height_resizing_enabled and self.rsz_w is None and self.rsz_h is not None: self.currently_resizing_height = True y = event.y if y < self.MT.hdr_min_rh: y = int(self.MT.hdr_min_rh) self.new_col_height = y self.create_resize_line(x1, y, x2, y, width = 1, fill = self.resizing_line_fg, tag = "rhl") elif self.MT.identify_col(x = event.x, allow_end = False) is None: self.MT.deselect("all") elif self.col_selection_enabled and self.rsz_w is None and self.rsz_h is None: c = self.MT.identify_col(x = event.x) if c < len(self.MT.col_positions) - 1: if self.MT.single_selection_enabled: self.select_col(c, redraw = True) elif self.MT.toggle_selection_enabled: self.toggle_select_col(c, 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.width_resizing_enabled and self.rsz_w is not None and self.currently_resizing_width: x = self.canvasx(event.x) size = x - self.MT.col_positions[self.rsz_w - 1] if not size <= self.MT.min_cw and size < self.max_cw: self.delete_resize_lines() self.MT.delete_resize_lines() line2x = self.MT.col_positions[self.rsz_w - 1] self.create_resize_line(x, 0, x, self.current_height, width = 1, fill = self.resizing_line_fg, tag = "rwl") self.MT.create_resize_line(x, y1, x, y2, width = 1, fill = self.resizing_line_fg, tag = "rwl") self.create_resize_line(line2x, 0, line2x, self.current_height,width = 1, fill = self.resizing_line_fg, tag = "rwl2") self.MT.create_resize_line(line2x, y1, line2x, y2, width = 1, fill = self.resizing_line_fg, tag = "rwl2") elif self.height_resizing_enabled and self.rsz_h is not None and self.currently_resizing_height: evy = event.y self.delete_resize_lines() self.MT.delete_resize_lines() if evy > self.current_height: y = self.MT.canvasy(evy - self.current_height) if evy > self.max_header_height: evy = int(self.max_header_height) y = self.MT.canvasy(evy - self.current_height) self.new_col_height = evy self.MT.create_resize_line(x1, y, x2, y, width = 1, fill = self.resizing_line_fg, tag = "rhl") else: y = evy if y < self.MT.hdr_min_rh: y = int(self.MT.hdr_min_rh) self.new_col_height = y self.create_resize_line(x1, y, x2, y, width = 1, fill = self.resizing_line_fg, tag = "rhl") elif self.drag_and_drop_enabled and self.col_selection_enabled and self.MT.anything_selected(exclude_cells = True, exclude_rows = True) and self.rsz_h is None and self.rsz_w is None and self.dragged_col is not None: x = self.canvasx(event.x) if x > 0 and x < self.MT.col_positions[-1]: x = event.x wend = self.winfo_width() xcheck = self.xview() if x >= wend - 0 and len(xcheck) > 1 and xcheck[1] < 1: if x >= wend + 15: self.MT.xview_scroll(2, "units") self.xview_scroll(2, "units") else: self.MT.xview_scroll(1, "units") self.xview_scroll(1, "units") self.check_xview() self.MT.main_table_redraw_grid_and_text(redraw_header = True) elif x <= 0 and len(xcheck) > 1 and xcheck[0] > 0: if x >= -15: self.MT.xview_scroll(-1, "units") self.xview_scroll(-1, "units") else: self.MT.xview_scroll(-2, "units") self.xview_scroll(-2, "units") self.check_xview() self.MT.main_table_redraw_grid_and_text(redraw_header = True) col = self.MT.identify_col(x = event.x) sels = self.MT.get_selected_cols() selsmin = min(sels) if col in sels: xpos = self.MT.col_positions[selsmin] else: if col < selsmin: xpos = self.MT.col_positions[col] else: xpos = self.MT.col_positions[col + 1] self.delete_resize_lines() self.MT.delete_resize_lines() self.create_resize_line(xpos, 0, xpos, self.current_height, width = 3, fill = self.drag_and_drop_bg, tag = "dd") self.MT.create_resize_line(xpos, y1, xpos, y2, width = 3, fill = self.drag_and_drop_bg, tag = "dd") elif self.MT.drag_selection_enabled and self.col_selection_enabled and self.rsz_h is None and self.rsz_w is None: end_col = self.MT.identify_col(x = event.x) currently_selected = self.MT.currently_selected() if end_col < len(self.MT.col_positions) - 1 and currently_selected: if currently_selected[0] == "column": start_col = currently_selected[1] if end_col >= start_col: rect = (0, start_col, len(self.MT.row_positions) - 1, end_col + 1, "cols") func_event = tuple(range(start_col, end_col + 1)) elif end_col < start_col: rect = (0, end_col, len(self.MT.row_positions) - 1, start_col + 1, "cols") func_event = tuple(range(end_col, start_col + 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(("drag_select_columns", func_event)) xcheck = self.xview() if event.x > self.winfo_width() and len(xcheck) > 1 and xcheck[1] < 1: try: self.MT.xview_scroll(1, "units") self.xview_scroll(1, "units") except: pass self.check_xview() elif event.x < 0 and self.canvasx(self.winfo_width()) > 0 and xcheck and xcheck[0] > 0: try: self.xview_scroll(-1, "units") self.MT.xview_scroll(-1, "units") except: pass self.check_xview() self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = False) if self.extra_b1_motion_func is not None: self.extra_b1_motion_func(event) def check_xview(self): xcheck = self.xview() if xcheck and xcheck[0] < 0: self.MT.set_xviews("moveto", 0) elif len(xcheck) > 1 and xcheck[1] > 1: self.MT.set_xviews("moveto", 1) def b1_release(self, event = None): self.MT.bind("<MouseWheel>", self.MT.mousewheel) if self.width_resizing_enabled and self.rsz_w is not None and self.currently_resizing_width: self.currently_resizing_width = False new_col_pos = self.coords("rwl")[0] self.delete_resize_lines() self.MT.delete_resize_lines() size = new_col_pos - self.MT.col_positions[self.rsz_w - 1] if size < self.MT.min_cw: new_row_pos = ceil(self.MT.col_positions[self.rsz_w - 1] + self.MT.min_cw) elif size > self.max_cw: new_col_pos = floor(self.MT.col_positions[self.rsz_w - 1] + self.max_cw) increment = new_col_pos - self.MT.col_positions[self.rsz_w] self.MT.col_positions[self.rsz_w + 1:] = [e + increment for e in islice(self.MT.col_positions, self.rsz_w + 1, len(self.MT.col_positions))] self.MT.col_positions[self.rsz_w] = new_col_pos self.MT.recreate_all_selection_boxes() self.MT.resize_dropdowns() self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True) elif self.height_resizing_enabled and self.rsz_h is not None and self.currently_resizing_height: self.currently_resizing_height = False self.delete_resize_lines() self.MT.delete_resize_lines() self.set_height(self.new_col_height,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.col_selection_enabled and self.MT.anything_selected(exclude_cells = True, exclude_rows = True) and self.rsz_h is None and self.rsz_w is None and self.dragged_col is not None: self.delete_resize_lines() self.MT.delete_resize_lines() x = event.x c = self.MT.identify_col(x = x) orig_selected_cols = self.MT.get_selected_cols() if c != self.dragged_col and c is not None and c not in orig_selected_cols and len(orig_selected_cols) != len(self.MT.col_positions) - 1: orig_selected_cols = sorted(orig_selected_cols) if len(orig_selected_cols) > 1: orig_min = orig_selected_cols[0] orig_max = orig_selected_cols[1] start_idx = bisect.bisect_left(orig_selected_cols, self.dragged_col) forward_gap = get_index_of_gap_in_sorted_integer_seq_forward(orig_selected_cols, start_idx) reverse_gap = get_index_of_gap_in_sorted_integer_seq_reverse(orig_selected_cols, start_idx) if forward_gap is not None: orig_selected_cols[:] = orig_selected_cols[:forward_gap] if reverse_gap is not None: orig_selected_cols[:] = orig_selected_cols[reverse_gap:] colsiter = orig_selected_cols.copy() rm1start = colsiter[0] rm1end = colsiter[-1] + 1 rm2start = rm1start + (rm1end - rm1start) rm2end = rm1end + (rm1end - rm1start) totalcols = len(colsiter) if c >= len(self.MT.col_positions) - 1: c -= 1 c_ = int(c) if self.ch_extra_begin_drag_drop_func is not None: self.ch_extra_begin_drag_drop_func(("begin_column_header_drag_drop", tuple(orig_selected_cols), int(c))) if self.column_drag_and_drop_perform: if self.MT.all_columns_displayed: if rm1start > c: for rn in range(len(self.MT.data_ref)): try: self.MT.data_ref[rn] = (self.MT.data_ref[rn][:c] + self.MT.data_ref[rn][rm1start:rm1start + totalcols] + self.MT.data_ref[rn][c:rm1start] + self.MT.data_ref[rn][rm1start + totalcols:]) except: continue if not isinstance(self.MT.my_hdrs, int) and self.MT.my_hdrs: try: self.MT.my_hdrs = (self.MT.my_hdrs[:c] + self.MT.my_hdrs[rm1start:rm1start + totalcols] + self.MT.my_hdrs[c:rm1start] + self.MT.my_hdrs[rm1start + totalcols:]) except: pass else: for rn in range(len(self.MT.data_ref)): try: self.MT.data_ref[rn] = (self.MT.data_ref[rn][:rm1start] + self.MT.data_ref[rn][rm1start + totalcols:c + 1] + self.MT.data_ref[rn][rm1start:rm1start + totalcols] + self.MT.data_ref[rn][c + 1:]) except: continue if not isinstance(self.MT.my_hdrs, int) and self.MT.my_hdrs: try: self.MT.my_hdrs = (self.MT.my_hdrs[:rm1start] + self.MT.my_hdrs[rm1start + totalcols:c + 1] + self.MT.my_hdrs[rm1start:rm1start + totalcols] + self.MT.my_hdrs[c + 1:]) except: pass else: if rm1start > c: self.MT.displayed_columns = (self.MT.displayed_columns[:c] + self.MT.displayed_columns[rm1start:rm1start + totalcols] + self.MT.displayed_columns[c:rm1start] + self.MT.displayed_columns[rm1start + totalcols:]) else: self.MT.displayed_columns = (self.MT.displayed_columns[:rm1start] + self.MT.displayed_columns[rm1start + totalcols:c + 1] + self.MT.displayed_columns[rm1start:rm1start + totalcols] + self.MT.displayed_columns[c + 1:]) cws = [int(b - a) for a, b in zip(self.MT.col_positions, islice(self.MT.col_positions, 1, len(self.MT.col_positions)))] if rm1start > c: cws = (cws[:c] + cws[rm1start:rm1start + totalcols] + cws[c:rm1start] + cws[rm1start + totalcols:]) else: cws = (cws[:rm1start] + cws[rm1start + totalcols:c + 1] + cws[rm1start:rm1start + totalcols] + cws[c + 1:]) self.MT.col_positions = list(accumulate(chain([0], (width for width in cws)))) self.MT.deselect("all") if (c_ - 1) + totalcols > len(self.MT.col_positions) - 1: new_selected = tuple(range(len(self.MT.col_positions) - 1 - totalcols, len(self.MT.col_positions) - 1)) self.MT.create_selected(0, len(self.MT.col_positions) - 1 - totalcols, len(self.MT.row_positions) - 1, len(self.MT.col_positions) - 1, "cols") else: if rm1start > c: new_selected = tuple(range(c_, c_ + totalcols)) self.MT.create_selected(0, c_, len(self.MT.row_positions) - 1, c_ + totalcols, "cols") else: new_selected = tuple(range(c_ + 1 - totalcols, c_ + 1)) self.MT.create_selected(0, c_ + 1 - totalcols, len(self.MT.row_positions) - 1, c_ + 1, "cols") self.MT.create_current(0, int(new_selected[0]), type_ = "col", inside = True) if self.MT.undo_enabled: self.MT.undo_storage.append(zlib.compress(pickle.dumps(("move_cols", int(orig_selected_cols[0]), (int(new_selected[0]), int(new_selected[-1])))))) self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True) if self.ch_extra_end_drag_drop_func is not None: self.ch_extra_end_drag_drop_func(("end_column_header_drag_drop", tuple(orig_selected_cols), new_selected, int(c))) self.dragged_col = 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 double_b1(self, event = None): self.focus_set() if self.double_click_resizing_enabled and self.width_resizing_enabled and self.rsz_w is not None and not self.currently_resizing_width: col = self.rsz_w - 1 self.set_col_width(col) self.MT.main_table_redraw_grid_and_text(redraw_header = True, redraw_row_index = True) elif self.col_selection_enabled and self.rsz_h is None and self.rsz_w is None: c = self.MT.identify_col(x = event.x) if c < len(self.MT.col_positions) - 1: if self.MT.single_selection_enabled: self.select_col(c, redraw = True) elif self.MT.toggle_selection_enabled: self.toggle_select_col(c, redraw = True) self.mouse_motion(event) self.rsz_w = None if self.extra_double_b1_func is not None: self.extra_double_b1_func(event) def highlight_cells(self, c = 0, cells = tuple(), bg = None, fg = None, redraw = False): if bg is None and fg is None: return if cells: self.highlighted_cells.update({c_: (bg, fg) for c_ in cells}) else: self.highlighted_cells[c] = (bg, fg) if redraw: self.MT.main_table_redraw_grid_and_text(True, False) def select_col(self, c, redraw = False, keep_other_selections = False): c = int(c) ignore_keep = False if keep_other_selections: if self.MT.col_selected(c): self.MT.create_current(0, c, type_ = "col", inside = True) else: ignore_keep = True if ignore_keep or not keep_other_selections: self.MT.delete_selection_rects() self.MT.create_current(0, c, type_ = "col", inside = True) self.MT.create_selected(0, c, len(self.MT.row_positions) - 1, c + 1, "cols") 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(("select_column", int(c))) def toggle_select_col(self, column, add_selection = True, redraw = True, run_binding_func = True, set_as_current = True): if add_selection: if self.MT.col_selected(column): self.MT.deselect(c = column, redraw = redraw) else: self.add_selection(c = column, redraw = redraw, run_binding_func = run_binding_func, set_as_current = set_as_current) else: if self.MT.col_selected(column): self.MT.deselect(c = column, redraw = redraw) else: self.select_col(column, redraw = redraw) def add_selection(self, c, redraw = False, run_binding_func = True, set_as_current = True): c = int(c) 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(0, c, type_ = "col", 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(0, c, len(self.MT.row_positions) - 1, c + 1, "cols") 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 and run_binding_func: self.selection_binding_func(("select_column", int(c))) def set_col_width(self, col, width = None, only_set_if_too_small = False, displayed_only = False, recreate = True, return_new_width = False): if col < 0: return qconf = self.MT.txt_measure_canvas.itemconfig qbbox = self.MT.txt_measure_canvas.bbox qtxtm = self.MT.txt_measure_canvas_text if width is None: w = self.MT.min_cw if displayed_only: x1, y1, x2, y2 = self.MT.get_canvas_visible_area() start_row, end_row = self.MT.get_visible_rows(y1, y2) else: start_row, end_row = 0, None if self.MT.all_columns_displayed: data_col = col else: data_col = self.MT.displayed_columns[col] try: if isinstance(self.MT.my_hdrs, int): txt = self.MT.data_ref[self.MT.my_hdrs][data_col] else: txt = self.MT.my_hdrs[data_col if self.measure_subset_hdr else col] if txt: qconf(qtxtm, text = txt, font = self.MT.my_hdr_font) b = qbbox(qtxtm) hw = b[2] - b[0] + 5 else: hw = self.MT.min_cw except: if self.default_hdr == "letters": hw = self.MT.GetHdrTextWidth(num2alpha(data_col)) + 5 elif self.default_hdr == "numbers": hw = self.MT.GetHdrTextWidth(f"{data_col + 1}") + 5 else: hw = self.MT.GetHdrTextWidth(f"{data_col + 1} {num2alpha(data_col)}") + 5 for r in islice(self.MT.data_ref, start_row, end_row): try: if isinstance(r[data_col], str): txt = r[data_col] else: txt = f"{r[data_col]}" except: txt = "" if txt: qconf(qtxtm, text = txt, font = self.MT.my_font) b = qbbox(qtxtm) tw = b[2] - b[0] + 5 if tw > w: w = tw if w > hw: new_width = w else: new_width = hw else: new_width = int(width) if new_width <= self.MT.min_cw: new_width = int(self.MT.min_cw) elif new_width > self.max_cw: new_width = int(self.max_cw) if only_set_if_too_small: if new_width <= self.MT.col_positions[col + 1] - self.MT.col_positions[col]: return self.MT.col_positions[col + 1] - self.MT.col_positions[col] if return_new_width: return new_width else: new_col_pos = self.MT.col_positions[col] + new_width increment = new_col_pos - self.MT.col_positions[col + 1] self.MT.col_positions[col + 2:] = [e + increment for e in islice(self.MT.col_positions, col + 2, len(self.MT.col_positions))] self.MT.col_positions[col + 1] = new_col_pos if recreate: self.MT.recreate_all_selection_boxes() self.MT.resize_dropdowns() def set_width_of_all_cols(self, width = None, only_set_if_too_small = False, recreate = True): if width is None: if self.MT.all_columns_displayed: iterable = range(self.MT.total_data_cols()) else: iterable = range(len(self.MT.displayed_columns)) self.MT.col_positions = list(accumulate(chain([0], (self.set_col_width(cn, only_set_if_too_small = only_set_if_too_small, recreate = False, return_new_width = True) for cn in iterable)))) elif width is not None: if self.MT.all_columns_displayed: self.MT.col_positions = list(accumulate(chain([0], (width for cn in range(self.MT.total_data_cols()))))) else: self.MT.col_positions = list(accumulate(chain([0], (width for cn in range(len(self.MT.displayed_columns)))))) if recreate: self.MT.recreate_all_selection_boxes() self.MT.resize_dropdowns() def GetLargestWidth(self, cell): return max(cell.split("\n"), key = self.MT.GetTextWidth) def redraw_highlight_get_text_fg(self, fc, sc, c, c_2, c_3, selected_cols, selected_rows, actual_selected_cols, hlcol): if hlcol in self.highlighted_cells and c in actual_selected_cols: c_1 = self.highlighted_cells[hlcol][0] if self.highlighted_cells[hlcol][0].startswith("#") else Color_Map_[self.highlighted_cells[hlcol][0]] self.redraw_highlight(fc + 1, 0, sc, self.current_height - 1, 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 = "", tag = "s") tf = self.header_selected_columns_fg if self.highlighted_cells[hlcol][1] is None or self.MT.display_selected_fg_over_highlights else self.highlighted_cells[hlcol][1] elif hlcol in self.highlighted_cells and (c in selected_cols or selected_rows): c_1 = self.highlighted_cells[hlcol][0] if self.highlighted_cells[hlcol][0].startswith("#") else Color_Map_[self.highlighted_cells[hlcol][0]] self.redraw_highlight(fc + 1, 0, sc, self.current_height - 1, 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 = "", tag = "s") tf = self.header_selected_cells_fg if self.highlighted_cells[hlcol][1] is None or self.MT.display_selected_fg_over_highlights else self.highlighted_cells[hlcol][1] elif c in actual_selected_cols: tf = self.header_selected_columns_fg elif c in selected_cols or selected_rows: tf = self.header_selected_cells_fg elif hlcol in self.highlighted_cells: self.redraw_highlight(fc + 1, 0, sc, self.current_height - 1, fill = self.highlighted_cells[hlcol][0], outline = "", tag = "s") tf = self.header_fg if self.highlighted_cells[hlcol][1] is None else self.highlighted_cells[hlcol][1] else: tf = self.header_fg return tf, self.MT.my_hdr_font 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 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_grid_and_text(self, last_col_line_pos, x1, x_stop, start_col, end_col, selected_cols, selected_rows, actual_selected_cols): self.configure(scrollregion = (0, 0, last_col_line_pos + self.MT.empty_horizontal, self.current_height)) 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.visible_col_dividers = [] x = self.MT.col_positions[start_col] self.redraw_gridline(x, 0, x, self.current_height, fill = self.header_grid_fg, width = 1, tag = "fv") self.col_height_resize_bbox = (x1, self.current_height - 2, x_stop, self.current_height) yend = self.current_height - 5 for c in range(start_col + 1, end_col): x = self.MT.col_positions[c] if self.width_resizing_enabled: self.visible_col_dividers.append((x - 2, 1, x + 2, yend)) self.redraw_gridline(x, 0, x, self.current_height, fill = self.header_grid_fg, width = 1, tag = ("v", f"{c}")) top = self.canvasy(0) if self.MT.hdr_fl_ins + self.MT.hdr_half_txt_h - 1 > top: incfl = True else: incfl = False c_2 = self.header_selected_cells_bg if self.header_selected_cells_bg.startswith("#") else Color_Map_[self.header_selected_cells_bg] c_3 = self.header_selected_columns_bg if self.header_selected_columns_bg.startswith("#") else Color_Map_[self.header_selected_columns_bg] if self.align == "center": for c in range(start_col, end_col - 1): fc = self.MT.col_positions[c] sc = self.MT.col_positions[c + 1] if self.MT.all_columns_displayed: dcol = c else: dcol = self.MT.displayed_columns[c] tf, font = self.redraw_highlight_get_text_fg(fc, sc, c, c_2, c_3, selected_cols, selected_rows, actual_selected_cols, dcol) mw = sc - fc - 1 if fc + 5 > x_stop or mw <= 5: continue x = fc + floor((sc - fc) / 2) try: if isinstance(self.MT.my_hdrs, int): if isinstance(self.MT.data_ref[self.MT.my_hdrs][dcol], str): lns = self.MT.data_ref[self.MT.my_hdrs][dcol].split("\n") else: lns = (f"{self.MT.data_ref[self.MT.my_hdrs][dcol]}", ) else: if isinstance(self.MT.my_hdrs[dcol], str): lns = self.MT.my_hdrs[dcol].split("\n") else: lns = (f"{self.MT.my_hdrs[dcol]}", ) except: if self.default_hdr == "letters": lns = (num2alpha(c), ) elif self.default_hdr == "numbers": lns = (f"{c + 1}", ) else: lns = (f"{c + 1} {num2alpha(c)}", ) y = self.MT.hdr_fl_ins if incfl: txt = lns[0] t = self.redraw_text(x, y, text = txt, fill = tf, font = font, anchor = "center", 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) if len(lns) > 1: stl = int((top - y) / self.MT.hdr_xtra_lines_increment) - 1 if stl < 1: stl = 1 y += (stl * self.MT.hdr_xtra_lines_increment) if y + self.MT.hdr_half_txt_h - 1 < self.current_height: for i in range(stl, len(lns)): txt = lns[i] t = self.redraw_text(x, y, text = txt, fill = tf, font = font, anchor = "center", 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.hdr_xtra_lines_increment if y + self.MT.hdr_half_txt_h - 1 > self.current_height: break elif self.align == "w": for c in range(start_col, end_col - 1): fc = self.MT.col_positions[c] sc = self.MT.col_positions[c + 1] if self.MT.all_columns_displayed: dcol = c else: dcol = self.MT.displayed_columns[c] tf, font = self.redraw_highlight_get_text_fg(fc, sc, c, c_2, c_3, selected_cols, selected_rows, actual_selected_cols, dcol) mw = sc - fc - 5 x = fc + 5 if x > x_stop or mw <= 5: continue try: if isinstance(self.MT.my_hdrs, int): if isinstance(self.MT.data_ref[self.MT.my_hdrs][dcol], str): lns = self.MT.data_ref[self.MT.my_hdrs][dcol].split("\n") else: lns = (f"{self.MT.data_ref[self.MT.my_hdrs][dcol]}", ) else: if isinstance(self.MT.my_hdrs[dcol], str): lns = self.MT.my_hdrs[dcol].split("\n") else: lns = (f"{self.MT.my_hdrs[dcol]}", ) except: if self.default_hdr == "letters": lns = (num2alpha(c), ) elif self.default_hdr == "numbers": lns = (f"{c + 1}", ) else: lns = (f"{num2alpha(c)} {c + 1}", ) y = self.MT.hdr_fl_ins if incfl: txt = lns[0] t = self.redraw_text(x, y, text = txt, fill = tf, font = font, anchor = "w", tag = "t") 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) if len(lns) > 1: stl = int((top - y) / self.MT.hdr_xtra_lines_increment) - 1 if stl < 1: stl = 1 y += (stl * self.MT.hdr_xtra_lines_increment) if y + self.MT.hdr_half_txt_h - 1 < self.current_height: for i in range(stl, len(lns)): txt = lns[i] t = self.redraw_text(x, y, text = txt, fill = tf, font = font, anchor = "w", tag = "t") 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.hdr_xtra_lines_increment if y + self.MT.hdr_half_txt_h - 1 > self.current_height: break self.redraw_gridline(x1, self.current_height - 1, x_stop, self.current_height - 1, fill = self.header_border_fg, width = 1, tag = "h") 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 def GetCellCoords(self, event = None, r = None, c = None): pass