2018-05-10 12:01:38 +00:00
from tkinter import BOTH , BOTTOM , END , HORIZONTAL , LEFT , Menu , NSEW , RIGHT , StringVar , Text , TOP , X , Y
2015-08-09 11:05:21 +00:00
from tkinter . font import nametofont
2018-05-10 12:01:38 +00:00
from tkinter . ttk import Entry , Frame , Label , PanedWindow , Progressbar , Scrollbar , Style , Treeview
2015-08-09 11:05:21 +00:00
class Viewer ( Frame ) :
def __init__ ( self ) :
super ( ) . __init__ ( )
self . master . title ( type ( self ) . __name__ )
fontheight = nametofont ( " TkDefaultFont " ) . metrics ( " linespace " )
style = Style ( )
style . configure ( " Treeview " , rowheight = fontheight )
2018-05-10 12:01:38 +00:00
style . configure ( " Superbar.Horizontal.TProgressbar " , foreground = " red " , background = " red " )
2015-08-09 11:05:21 +00:00
self . detached_items = { }
self . find_input = StringVar ( value = " Enter search here " )
self . tree = None
self . item_inspector = None
2018-05-10 12:01:38 +00:00
self . init ( )
self . create_widgets ( )
def init ( self ) - > None :
pass
def create_widgets ( self , create_inspector : bool = True ) - > None :
self . menubar = Menu ( )
self . menubar . add_command ( label = " Open " , command = self . _askopen )
self . master . config ( menu = self . menubar )
2015-08-09 11:05:21 +00:00
find_entry = Entry ( textvariable = self . find_input )
find_entry . pack ( fill = X )
2018-05-10 12:01:38 +00:00
find_entry . bind ( " <Return> " , self . _find )
2015-08-09 11:05:21 +00:00
pane = PanedWindow ( orient = HORIZONTAL )
pane . pack ( fill = BOTH , expand = True )
frame = Frame ( )
scrollbar = Scrollbar ( frame )
scrollbar . pack ( side = RIGHT , fill = Y )
self . tree = Treeview ( frame , columns = ( None , ) , yscrollcommand = scrollbar . set )
self . tree . tag_configure ( " match " , background = " light yellow " )
self . tree . bind ( " <<TreeviewSelect>> " , self . on_item_select )
self . tree . pack ( fill = BOTH , expand = True )
scrollbar . configure ( command = self . tree . yview )
2015-08-09 15:11:27 +00:00
pane . add ( frame )
2018-05-10 12:01:38 +00:00
if create_inspector :
frame = Frame ( )
scrollbar = Scrollbar ( frame )
scrollbar . pack ( side = RIGHT , fill = Y )
2015-08-09 11:05:21 +00:00
2018-05-10 12:01:38 +00:00
self . item_inspector = Text ( frame , font = " TkDefaultFont " , tabs = " 4m " , yscrollcommand = scrollbar . set )
self . item_inspector . insert ( END , " Select an item to inspect it. " )
self . item_inspector . pack ( fill = BOTH , expand = True )
2015-08-09 11:05:21 +00:00
2018-05-10 12:01:38 +00:00
scrollbar . configure ( command = self . item_inspector . yview )
pane . add ( frame )
2015-08-09 11:05:21 +00:00
2018-05-10 12:01:38 +00:00
self . status_frame = Frame ( )
self . status = Label ( self . status_frame )
self . status . grid ( row = 0 , column = 0 )
self . superbar = Progressbar ( self . status_frame , maximum = 0 , style = " Superbar.Horizontal.TProgressbar " )
self . superbar . grid ( row = 0 , column = 1 , sticky = NSEW )
self . superbar . grid_remove ( )
self . progressbar = Progressbar ( self . status_frame )
self . progressbar . grid ( row = 1 , columnspan = 2 , sticky = NSEW )
self . progressbar . grid_remove ( )
self . status_frame . columnconfigure ( 0 , weight = 1 )
def set_superbar ( self , maximum : int ) - > None :
self . superbar . config ( maximum = maximum , value = 0 )
if maximum == 1 :
self . superbar . grid_remove ( )
else :
self . superbar . grid ( )
def step_superbar ( self , arg , desc : str = " " ) - > None :
if self . superbar . cget ( " maximum " ) == 0 :
self . set_superbar ( 1 )
if self . superbar . cget ( " value " ) == 0 :
self . status_frame . pack ( side = BOTTOM , fill = X )
2015-08-09 11:05:21 +00:00
2018-05-10 12:01:38 +00:00
self . status . config ( text = desc )
self . update ( )
if isinstance ( arg , int ) :
iterable = range ( arg )
max = arg
else :
iterable = arg
max = len ( arg )
if max > 1 :
self . progressbar . config ( maximum = max + 1 , value = 0 )
self . progressbar . grid ( )
else :
self . progressbar . grid_remove ( )
for x in iterable :
yield x
self . progressbar . step ( )
self . update ( )
self . superbar . step ( )
if self . superbar . cget ( " value " ) == 0 :
self . status_frame . pack_forget ( )
self . superbar . config ( maximum = 0 )
def set_headings ( self , * cols , treeheading : str = None , treewidth : int = None ) - > None :
if treeheading is not None :
self . tree . heading ( " #0 " , text = treeheading )
self . tree . configure ( columns = cols )
if treewidth is None :
treewidth = self . tree . winfo_width ( )
colwidth = treewidth / / ( len ( cols ) + 1 )
self . tree . column ( " #0 " , width = colwidth )
for i , col in enumerate ( cols ) :
self . tree . heading ( col , text = col , command = ( lambda col : lambda : self . _sort_column ( col , False ) ) ( col ) )
self . tree . column ( i , width = colwidth )
def askopener ( self ) :
raise NotImplementedError
def load ( self , path ) - > None :
raise NotImplementedError
def on_item_select ( self , _ ) - > None :
pass
def _find ( self , _ ) :
2015-08-09 11:05:21 +00:00
query = self . find_input . get ( ) . lower ( )
for item in self . tree . tag_has ( " match " ) :
2015-08-10 10:09:54 +00:00
tags = list ( self . tree . item ( item , " tags " ) )
tags . remove ( " match " )
self . tree . item ( item , tags = tags )
2018-05-10 12:01:38 +00:00
self . _reattach_all ( )
2018-04-21 12:48:06 +00:00
if query :
2018-05-10 12:01:38 +00:00
self . _filter_items ( query )
2018-04-21 12:48:06 +00:00
2018-05-10 12:01:38 +00:00
def _reattach_all ( self ) - > None :
2015-08-09 11:05:21 +00:00
for parent , detached_children in self . detached_items . items ( ) :
2015-10-12 17:33:34 +00:00
for index , item in detached_children :
self . tree . reattach ( item , parent , index )
2015-12-15 17:46:30 +00:00
self . detached_items . clear ( )
2015-08-09 11:05:21 +00:00
2018-05-10 12:01:38 +00:00
def _filter_items ( self , query , parent = " " ) :
2015-08-09 11:05:21 +00:00
all_children = self . tree . get_children ( parent )
2018-04-21 12:48:06 +00:00
detached_children = [ item for item in all_children if not any ( query in i . lower ( ) for i in self . tree . item ( item , " values " ) ) and not query in self . tree . item ( item , " text " ) . lower ( ) ] # first, find all children that don't match
2015-08-09 11:05:21 +00:00
for item in all_children :
if item not in detached_children :
2015-08-10 10:09:54 +00:00
tags = list ( self . tree . item ( item , " tags " ) )
tags . append ( " match " )
self . tree . item ( item , tags = tags )
2015-08-09 11:05:21 +00:00
self . tree . see ( item )
2018-05-10 12:01:38 +00:00
if self . _filter_items ( query , item ) and item in detached_children :
2015-08-09 11:05:21 +00:00
detached_children . remove ( item ) # don't detach if a child matches
2015-10-12 17:33:34 +00:00
self . detached_items [ parent ] = [ ( self . tree . index ( item ) , item ) for item in detached_children ]
for item in detached_children :
self . tree . detach ( item )
2015-08-09 11:05:21 +00:00
return len ( detached_children ) != len ( all_children ) # return true if any children match
2018-05-10 12:01:38 +00:00
def _sort_column ( self , col , reverse , parent = " " ) - > None :
2015-08-09 11:05:21 +00:00
children = list ( self . tree . get_children ( parent ) )
children . sort ( key = lambda x : self . tree . set ( x , col ) , reverse = reverse )
# rearrange items in sorted positions
for index , child in enumerate ( children ) :
self . tree . move ( child , parent , index )
for child in children :
2018-05-10 12:01:38 +00:00
self . _sort_column ( col , reverse , child )
2015-08-09 11:05:21 +00:00
if parent == " " :
# reverse sort next time
2018-05-10 12:01:38 +00:00
self . tree . heading ( col , command = lambda : self . _sort_column ( col , not reverse ) )
def _askopen ( self ) - > None :
path = self . askopener ( )
if path :
self . _reattach_all ( )
self . tree . delete ( * self . tree . get_children ( ) )
self . load ( path )