# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX# All rights reserved.## This software is provided without warranty under the terms of the BSD# license included in LICENSE.txt and may be redistributed only under# the conditions described in the aforementioned license. The license# is also available online at http://www.enthought.com/licenses/BSD.txt## Thanks for using Enthought open source!"""TextGrid is a text grid widget that is meant to be used with Numpy."""# Major library importsfromnumpyimportarange,array,dstack,repeat,newaxis# Enthought library importsfromtraits.apiimport(Any,Array,Bool,Int,List,Property,Tuple,observe,)fromenable.trait_defs.kiva_font_traitimportKivaFont# Relative importsfrom.componentimportComponentfrom.colorsimportblack_color_trait,ColorTraitfrom.enable_traitsimportLineStylefrom.font_metrics_providerimportfont_metrics_provider
[docs]classTextGrid(Component):""" A 2D grid of string values """# A 2D array of stringsstring_array=Array# The cell size can be set to a tuple (w,h) or to "auto".cell_size=Property# ------------------------------------------------------------------------# Appereance traits# ------------------------------------------------------------------------# The font to use for the text of the gridfont=KivaFont("modern 14")# The color of the texttext_color=black_color_trait# The padding around each cellcell_padding=Int(5)# The thickness of the border between cellscell_border_width=Int(1)# The color of the border between cellscell_border_color=black_color_trait# The dash style of the border between cellscell_border_style=LineStyle("solid")# Text color of highlighted itemshighlight_color=ColorTrait("red")# Cell background color of highlighted itemshighlight_bgcolor=ColorTrait("lightgray")# A list of tuples of the (i,j) of selected cellsselected_cells=List# ------------------------------------------------------------------------# Private traits# ------------------------------------------------------------------------# Are our cached extent values still valid?_cache_valid=Bool(False)# The maximum width and height of all cells, as a tuple (w,h)_cached_cell_size=Tuple# The maximum (leading, descent) of all the text strings (positive value)_text_offset=Array# An array NxMx2 of the x,y positions of the lower-left coordinates of# each cell_cached_cell_coords=Array# "auto" or a tuple_cell_size=Any("auto")# ------------------------------------------------------------------------# Public methods# ------------------------------------------------------------------------def__init__(self,**kwtraits):super().__init__(**kwtraits)self.selected_cells=[]# ------------------------------------------------------------------------# AbstractComponent interface# ------------------------------------------------------------------------def_draw_mainlayer(self,gc,view_bounds=None,mode="default"):text_color=self.text_color_highlight_color=self.highlight_color_highlight_bgcolor=self.highlight_bgcolor_padding=self.cell_paddingborder_width=self.cell_border_widthwithgc:gc.set_stroke_color(text_color)gc.set_fill_color(text_color)gc.set_font(self.font)gc.set_text_position(0,0)width,height=self._get_actual_cell_size()numrows,numcols=self.string_array.shape# draw selected backgrounds# XXX should this be in the background layer?forj,rowinenumerate(self.string_array):fori,textinenumerate(row):if(i,j)inself.selected_cells:gc.set_fill_color(highlight_bgcolor)ll_x,ll_y=self._cached_cell_coords[i,j+1]# render this a bit big, but covered by bordergc.rect(ll_x,ll_y,width+2*padding+border_width,height+2*padding+border_width,)gc.fill_path()gc.set_fill_color(text_color)self._draw_grid_lines(gc)forj,rowinenumerate(self.string_array):fori,textinenumerate(row):x,y=(self._cached_cell_coords[i,j+1]+self._text_offset+padding+border_width/2.0)if(i,j)inself.selected_cells:gc.set_fill_color(highlight_color)gc.set_stroke_color(highlight_color)gc.set_text_position(x,y)gc.show_text(text)gc.set_stroke_color(text_color)gc.set_fill_color(text_color)else:gc.set_text_position(x,y)gc.show_text(text)# ------------------------------------------------------------------------# Private methods# ------------------------------------------------------------------------def_draw_grid_lines(self,gc):gc.set_stroke_color(self.cell_border_color_)gc.set_line_dash(self.cell_border_style_)gc.set_line_width(self.cell_border_width)# Skip the leftmost and bottommost cell coords (since Y axis is# reversed, the bottommost coord is the last one)x_points=self._cached_cell_coords[:,0,0]y_points=self._cached_cell_coords[0,:,1]forxinx_points:gc.move_to(x,self.y)gc.line_to(x,self.y+self.height)gc.stroke_path()foryiny_points:gc.move_to(self.x,y)gc.line_to(self.x+self.width,y)gc.stroke_path()def_compute_cell_sizes(self):ifnotself._cache_valid:gc=font_metrics_provider()max_w=0max_h=0min_l=0min_d=0fortextinself.string_array.ravel():gc.set_font(self.font)l,d,w,h=gc.get_text_extent(text)if-l+w>max_w:max_w=-l+wif-d+h>max_h:max_h=-d+hifl<min_l:min_l=lifd<min_d:min_d=dself._cached_cell_size=(max_w,max_h)self._text_offset=array([-min_l,-min_d])self._cache_valid=Truedef_compute_positions(self):ifself.string_arrayisNoneorlen(self.string_array.shape)!=2:returnwidth,height=self._get_actual_cell_size()numrows,numcols=self.string_array.shapecell_width=width+2*self.cell_padding+self.cell_border_widthcell_height=height+2*self.cell_padding+self.cell_border_widthx_points=(arange(numcols+1)*cell_width+self.cell_border_width/2.0+self.x)y_points=(arange(numrows+1)*cell_height+self.cell_border_width/2.0+self.y)tmp=dstack((repeat(x_points[:,newaxis],numrows+1,axis=1),repeat(y_points[:,newaxis].T,numcols+1,axis=0),))# We have to reverse the y-axis (e.g. the 0th row needs to be at the# highest y-position).self._cached_cell_coords=tmp[:,::-1]def_update_bounds(self):ifself.string_arrayisnotNoneandlen(self.string_array.shape)==2:rows,cols=self.string_array.shapemargin=2*self.cell_padding+self.cell_border_widthwidth,height=self._get_actual_cell_size()self.bounds=[cols*(width+margin)+self.cell_border_width,rows*(height+margin)+self.cell_border_width,]else:self.bounds=[0,0]def_get_actual_cell_size(self):ifself._cell_size=="auto":ifnotself._cache_valid:self._compute_cell_sizes()returnself._cached_cell_sizeelse:ifnotself._cache_valid:# actually computing the text offsetself._compute_cell_sizes()returnself._cell_size# ------------------------------------------------------------------------# Event handlers# ------------------------------------------------------------------------