1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00
PySolFC/pysollib/tile/tktree.py
Alexandre Detiste dd16750d5f
import tkinter directly (#383)
* import tkinter directly

* linter

* linter
2024-09-21 17:10:10 -04:00

419 lines
14 KiB
Python

#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
# ---------------------------------------------------------------------------
#
# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
# Copyright (C) 2003 Mt. Hood Playing Card Co.
# Copyright (C) 2005-2009 Skomoroh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------------
import os
import tkinter
from pysollib.ui.tktile.tkutil import bind
from .tkwidget import MfxScrolledCanvas
class MfxTreeBaseNode:
def __init__(self, tree, parent_node, text, key):
self.tree = tree
self.parent_node = parent_node
self.text = text
self.key = key
# state
self.selected = 0
self.subnodes = None
# canvas item ids
self.symbol_id = None
self.text_id = None
self.textrect_id = None
def registerKey(self):
if self.key is not None:
lst = self.tree.keys.get(self.key, [])
lst.append(self)
self.tree.keys[self.key] = lst
def whoami(self):
if self.parent_node is None:
return (self.text, )
else:
return self.parent_node.whoami() + (self.text, )
def draw(self, x, y, lastx=None, lasty=None):
canvas, style = self.tree.canvas, self.tree.style
topleftx = x + style.distx
toplefty = y - style.height // 2 # +++
# draw the horizontal line
if lastx is not None:
canvas.create_line(
x, y, topleftx, y, stipple=style.linestyle,
fill=style.linecolor)
# draw myself - ugly, ugly...
self.selected = 0
self.symbol_id = -1
self.drawSymbol(topleftx, toplefty)
linestart = style.distx + style.width + 5
self.text_id = -1
self.drawText(x + linestart, y)
return x, y, x, y + style.disty
#
#
#
def drawText(self, x, y):
canvas, style = self.tree.canvas, self.tree.style
if self.selected:
fg, bg = style.text_selected_fg, style.text_selected_bg
else:
fg, bg = style.text_normal_fg, style.text_normal_bg
#
if self.tree.nodes.get(self.text_id) is self:
canvas.itemconfig(self.text_id, fill=fg)
else:
# note: I don't use Label + canvas.create_window here
# because it doesn't propagate events to the canvas
# and has some other re-display annoyances
# print 'style.font:', style.font
self.text_id = canvas.create_text(x+1, y, text=self.text,
anchor="w", justify="left",
font=style.font,
fill=fg)
self.tree.nodes[self.text_id] = self
#
if self.tree.nodes.get(self.textrect_id) is self:
try:
# _tkinter.TclError: unknown option "-fill" ???
canvas.itemconfig(self.textrect_id, fill=bg)
except tkinter.TclError:
pass
elif self.selected:
b = canvas.bbox(self.text_id)
self.textrect_id = canvas.create_rectangle(
b[0]-1, b[1]-1, b[2]+1, b[3]+1,
fill=bg, outline="")
canvas.tag_lower(self.textrect_id, self.text_id)
self.tree.nodes[self.textrect_id] = self
def updateText(self):
if self.tree.nodes.get(self.text_id) is self:
self.drawText(-1, -1)
#
#
#
def drawSymbol(self, x, y, **kw):
canvas, style = self.tree.canvas, self.tree.style
color = kw.get("color")
if color is None:
if self.selected:
color = "darkgreen"
else:
color = "green"
# note: rectangle outline is one pixel
if self.tree.nodes.get(self.symbol_id) is self:
canvas.itemconfig(self.symbol_id, fill=color)
else:
self.symbol_id = canvas.create_rectangle(
x+1, y+1, x + style.width, y + style.height, fill=color)
self.tree.nodes[self.symbol_id] = self
def updateSymbol(self):
if self.tree.nodes.get(self.symbol_id) is self:
self.drawSymbol(-1, -1)
# ************************************************************************
# * Terminal and non-terminal nodes
# ************************************************************************
class MfxTreeLeaf(MfxTreeBaseNode):
def drawText(self, x, y):
if self.text_id < 0:
self.registerKey()
MfxTreeBaseNode.drawText(self, x, y)
class MfxTreeNode(MfxTreeBaseNode):
def __init__(self, tree, parent_node, text, key, expanded=0):
MfxTreeBaseNode.__init__(self, tree, parent_node, text, key)
self.expanded = expanded
def drawChildren(self, x, y, lastx, lasty):
# get subnodes
self.subnodes = self.tree.getContents(self)
# draw subnodes
lx, ly = lastx, lasty
nx, ny = x, y
for node in self.subnodes:
# update tree
node.tree = self.tree
# draw node
lx, ly, nx, ny = node.draw(nx, ny, lx, ly)
# draw the vertical line
if self.subnodes:
style = self.tree.style
dy = (style.disty-style.width)//2
y = y-style.disty//2-dy
self.tree.canvas.create_line(x, y, nx, ly,
stipple=style.linestyle,
fill=style.linecolor)
return ny
def draw(self, x, y, ilastx=None, ilasty=None):
# draw myself
lx, ly, nx, ny = MfxTreeBaseNode.draw(self, x, y, ilastx, ilasty)
if self.expanded:
style = self.tree.style
childx = nx + style.distx + style.width // 2
childy = ny
clastx = nx + style.distx + style.width // 2
clasty = ly + style.height // 2
ny = self.drawChildren(childx, childy, clastx, clasty)
return lx, ly, x, ny
#
#
#
def drawSymbol(self, x, y, **kw):
color = kw.get("color")
if color is None:
if self.expanded:
color = "red"
else:
color = "pink"
MfxTreeBaseNode.drawSymbol(self, x, y, color=color)
# ************************************************************************
# *
# ************************************************************************
class MfxTreeInCanvas(MfxScrolledCanvas):
class Style:
def __init__(self):
self.distx = 16
self.disty = 18
self.width = 16 # width of symbol
self.height = 16 # height of symbol
self.originx = 0
self.originy = 0
self.text_normal_fg = "black"
self.text_normal_bg = "white"
self.text_selected_fg = "white"
self.text_selected_bg = "#00008b" # "darkblue"
self.font = None
self.linestyle = "gray50"
self.linecolor = "black"
def __init__(self, parent, rootnodes, **kw):
kw['bd'] = 0
kw['bg'] = 'white'
MfxScrolledCanvas.__init__(self, parent, **kw)
#
self.rootnodes = rootnodes
self.updateNodesWithTree(self.rootnodes, self)
self.selection_key = None
self.nodes = {}
self.keys = {}
#
self.style = self.Style()
# self.style.text_normal_fg = self.canvas.cget("insertbackground")
# self.style.text_normal_fg = \
# self.canvas.option_get('foreground', '') or \
# self.canvas.cget("insertbackground")
# self.style.text_normal_bg = self.canvas.option_get(
# 'background', self.canvas.cget("background"))
#
from pysollib.options import calcCustomMouseButtonsBinding
bind(
self.canvas,
calcCustomMouseButtonsBinding("<ButtonPress-{mouse_button1}>"),
self.singleClick
)
bind(
self.canvas,
calcCustomMouseButtonsBinding("<Double-Button-{mouse_button1}>"),
self.doubleClick
)
# bind(self.canvas,
# calcCustomMouseButtonsBinding(
# "<ButtonRelease-{mouse_button1}>"), xxx)
self.pack(fill='both', expand=True)
def destroy(self):
for node in self.keys.get(self.selection_key, []):
node.selected = 0
MfxScrolledCanvas.destroy(self)
def findNode(self, event=None):
id = self.canvas.find_withtag('current')
if id:
return self.nodes.get(id[0])
return None
#
# draw nodes
#
def draw(self):
nx, ny = self.style.originx, self.style.originy
# Account for initial offsets, see topleft[xy] in BaseNode.draw().
# We do this so that our bounding box always starts at (0,0)
# and the yscrollincrement works nicely.
nx -= self.style.distx
ny += self.style.height // 2
for node in self.rootnodes:
# update tree
node.tree = self
# draw
try:
lx, ly, nx, ny = node.draw(nx, ny, None, None)
except tkinter.TclError:
# FIXME: Tk bug ???
raise
# set scroll region
bbox = self.canvas.bbox("all")
# self.canvas.config(scrollregion=bbox)
# self.canvas.config(scrollregion=(0,0,bbox[2],bbox[3]))
dx, dy = 8, 0 # margins
self.canvas.config(scrollregion=(-dx, -dy, bbox[2]+dx, bbox[3]+dy))
self.canvas.config(yscrollincrement=self.style.disty)
def clear(self):
self.nodes = {}
self.keys = {}
self.canvas.delete("all")
def redraw(self):
oldcur = self.canvas["cursor"]
self.canvas["cursor"] = "watch"
self.canvas.update_idletasks()
self.clear()
self.draw()
self.updateSelection(self.selection_key)
self.canvas["cursor"] = oldcur
#
#
#
def getContents(self, node):
# Overload this, supposed to return a list of subnodes of node.
pass
def singleClick(self, event=None):
# Overload this if you want to know when a node is clicked on.
pass
def doubleClick(self, event=None):
# Overload this if you want to know when a node is d-clicked on.
self.singleClick(event)
#
#
#
def updateSelection(self, key):
l1 = self.keys.get(self.selection_key, [])
l2 = self.keys.get(key, [])
for node in l1:
if node.selected and node not in l2:
node.selected = 0
node.updateSymbol()
node.updateText()
for node in l2:
if not node.selected:
node.selected = 1
node.updateSymbol()
node.updateText()
self.selection_key = key
def updateNodesWithTree(self, nodes, tree):
for node in nodes:
node.tree = tree
if node.subnodes:
self.updateNodesWithTree(node.subnodes, tree)
# ************************************************************************
# *
# ************************************************************************
class DirectoryBrowser(MfxTreeInCanvas):
def __init__(self, parent, dirs):
nodes = []
if isinstance(dirs, str):
dirs = (dirs,)
for dir in dirs:
self.addNode(nodes, None, dir, dir)
# note: best results if height is a multiple of style.disty
MfxTreeInCanvas.__init__(self, parent, nodes, height=25*18)
self.draw()
def addNode(self, list, node, filename, text):
try:
if os.path.isdir(filename):
list.append(MfxTreeNode(self, node, text, key=filename))
else:
list.append(MfxTreeLeaf(self, node, text, key=filename))
except EnvironmentError:
pass
def getContents(self, node):
# use cached values
if node.subnodes is not None:
return node.subnodes
#
dir = node.key
print("Getting %s" % dir)
try:
filenames = os.listdir(dir)
filenames.sort()
except EnvironmentError:
return ()
contents = []
for filename in filenames:
self.addNode(contents, node, os.path.join(dir, filename), filename)
# print "gotten"
return contents
def singleClick(self, event=None):
node = self.findNode(event)
if not node:
return
print("Clicked node %s %s" % (node.text, node.key))
if isinstance(node, MfxTreeLeaf):
self.updateSelection(key=node.key)
elif isinstance(node, MfxTreeNode):
node.expanded = not node.expanded
self.redraw()
return "break"
if __name__ == "__main__":
tk = tkinter.Tk()
if os.name == "nt":
app = DirectoryBrowser(tk, ("c:\\", "c:\\windows"))
else:
app = DirectoryBrowser(tk, ("/", "/home"))
tk.mainloop()