1from tkinter import *
2from tkinter.ttk import Frame, Scrollbar
3
4from idlelib import macosx
5
6
7class ScrolledList:
8
9    default = "(None)"
10
11    def __init__(self, master, **options):
12        # Create top frame, with scrollbar and listbox
13        self.master = master
14        self.frame = frame = Frame(master)
15        self.frame.pack(fill="both", expand=1)
16        self.vbar = vbar = Scrollbar(frame, name="vbar")
17        self.vbar.pack(side="right", fill="y")
18        self.listbox = listbox = Listbox(frame, exportselection=0,
19            background="white")
20        if options:
21            listbox.configure(options)
22        listbox.pack(expand=1, fill="both")
23        # Tie listbox and scrollbar together
24        vbar["command"] = listbox.yview
25        listbox["yscrollcommand"] = vbar.set
26        # Bind events to the list box
27        listbox.bind("<ButtonRelease-1>", self.click_event)
28        listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
29        if macosx.isAquaTk():
30            listbox.bind("<ButtonPress-2>", self.popup_event)
31            listbox.bind("<Control-Button-1>", self.popup_event)
32        else:
33            listbox.bind("<ButtonPress-3>", self.popup_event)
34        listbox.bind("<Key-Up>", self.up_event)
35        listbox.bind("<Key-Down>", self.down_event)
36        # Mark as empty
37        self.clear()
38
39    def close(self):
40        self.frame.destroy()
41
42    def clear(self):
43        self.listbox.delete(0, "end")
44        self.empty = 1
45        self.listbox.insert("end", self.default)
46
47    def append(self, item):
48        if self.empty:
49            self.listbox.delete(0, "end")
50            self.empty = 0
51        self.listbox.insert("end", str(item))
52
53    def get(self, index):
54        return self.listbox.get(index)
55
56    def click_event(self, event):
57        self.listbox.activate("@%d,%d" % (event.x, event.y))
58        index = self.listbox.index("active")
59        self.select(index)
60        self.on_select(index)
61        return "break"
62
63    def double_click_event(self, event):
64        index = self.listbox.index("active")
65        self.select(index)
66        self.on_double(index)
67        return "break"
68
69    menu = None
70
71    def popup_event(self, event):
72        if not self.menu:
73            self.make_menu()
74        menu = self.menu
75        self.listbox.activate("@%d,%d" % (event.x, event.y))
76        index = self.listbox.index("active")
77        self.select(index)
78        menu.tk_popup(event.x_root, event.y_root)
79        return "break"
80
81    def make_menu(self):
82        menu = Menu(self.listbox, tearoff=0)
83        self.menu = menu
84        self.fill_menu()
85
86    def up_event(self, event):
87        index = self.listbox.index("active")
88        if self.listbox.selection_includes(index):
89            index = index - 1
90        else:
91            index = self.listbox.size() - 1
92        if index < 0:
93            self.listbox.bell()
94        else:
95            self.select(index)
96            self.on_select(index)
97        return "break"
98
99    def down_event(self, event):
100        index = self.listbox.index("active")
101        if self.listbox.selection_includes(index):
102            index = index + 1
103        else:
104            index = 0
105        if index >= self.listbox.size():
106            self.listbox.bell()
107        else:
108            self.select(index)
109            self.on_select(index)
110        return "break"
111
112    def select(self, index):
113        self.listbox.focus_set()
114        self.listbox.activate(index)
115        self.listbox.selection_clear(0, "end")
116        self.listbox.selection_set(index)
117        self.listbox.see(index)
118
119    # Methods to override for specific actions
120
121    def fill_menu(self):
122        pass
123
124    def on_select(self, index):
125        pass
126
127    def on_double(self, index):
128        pass
129
130
131def _scrolled_list(parent):  # htest #
132    top = Toplevel(parent)
133    x, y = map(int, parent.geometry().split('+')[1:])
134    top.geometry("+%d+%d" % (x+200, y + 175))
135    class MyScrolledList(ScrolledList):
136        def fill_menu(self): self.menu.add_command(label="right click")
137        def on_select(self, index): print("select", self.get(index))
138        def on_double(self, index): print("double", self.get(index))
139
140    scrolled_list = MyScrolledList(top)
141    for i in range(30):
142        scrolled_list.append("Item %02d" % i)
143
144if __name__ == '__main__':
145    from unittest import main
146    main('idlelib.idle_test.test_scrolledlist', verbosity=2,)
147
148    from idlelib.idle_test.htest import run
149    run(_scrolled_list)
150