Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge notebook into master for improved Notebook widget #48

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4f0ef79
Add Notebook widget
dogeek Dec 1, 2019
ccbcba5
Improve Notebook example with colour
dogeek Dec 2, 2019
8a46c03
Update Notebook style options
dogeek Dec 6, 2019
a10ee66
Create basic test for Notebook widget
RedFantom Dec 7, 2019
2772be2
Create entry in AUTHORS.md for Notebook widget
RedFantom Dec 7, 2019
d470cb6
Add tests for newly added utilities
dogeek Dec 7, 2019
f2d03e3
Implement some of requested Notebook changes
dogeek Dec 7, 2019
c652c04
Add tests for coords_in_box function
dogeek Dec 7, 2019
17a7e57
Refactor coordinates_in_box to coords_in_box
RedFantom Dec 7, 2019
9d5be6c
Extend newly added utilities unit tests
dogeek Dec 7, 2019
a3e19ea
Addressed more requested changes to Notebook
dogeek Dec 7, 2019
ec87fd0
Fix get_widget_options returning unused keys
RedFantom Dec 7, 2019
a6137eb
Fix parent reference in move_widget tests
dogeek Dec 8, 2019
afdeb3b
Reduce code duplication in TestUtilities
RedFantom Dec 14, 2019
cbea5d9
Extend tests for move_widget for widgets with bindings
RedFantom Dec 14, 2019
dea90d0
Preserve created Tcl commands for bindings
RedFantom Dec 15, 2019
a417614
Extend move_widget tests, add preserve_geometry kwarg
RedFantom Dec 16, 2019
1a77fb7
Modify copy_widget to allow non-visible widgets
RedFantom Dec 16, 2019
d9b50bb
Add test and error raise for moving widget to new Tk instance
RedFantom Dec 16, 2019
695c883
Fix last issues with move_widget function
RedFantom Dec 18, 2019
45178bc
Create tests for Notebook
dogeek Dec 22, 2019
06d8abf
Partially fix Notebook tests
dogeek Dec 22, 2019
adf0ad1
Fix test_notebook_index and test_notebook_forget_tab
RedFantom Jan 9, 2020
43393bc
Add missin return to Notebook.insert
RedFantom Jan 9, 2020
7a3aa52
Extend test_notebook_index with getting widget indices
RedFantom Jan 9, 2020
cb79ec3
Change error raised in Notebook.index
RedFantom Jan 9, 2020
91f2182
Change test_notebook_insert to test for ttk.Notebook.insert compliance
RedFantom Jan 9, 2020
9e5c6c3
Create sphinx documentation Notebook entry
RedFantom Jan 9, 2020
e56757f
Update notebook.py to be consistent with quotes
RedFantom Apr 24, 2020
78f0810
Fix scrolling through tabs on Linux
RedFantom Apr 24, 2020
02806f6
Improve styling of Notebook widget with theme
RedFantom Apr 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ This file contains a list of all the authors of widgets in this repository. Plea
* `AutoHideScrollbar` based on an idea by [Fredrik Lundh](effbot.org/zone/tkinter-autoscrollbar.htm)
* All color widgets: `askcolor`, `ColorPicker`, `GradientBar` and `ColorSquare`, `LimitVar`, `Spinbox`, `AlphaBar` and supporting functions in `functions.py`.
* `AutocompleteEntryListbox`
* [`Notebook`](https://github.com/j4321/PyTkEditor/blob/master/pytkeditorlib/notebook.py), modified by [Dogeek](https://github.com/Dogeek)
- Multiple authors:
* `ScaleEntry` (RedFantom and Juliette Monsel)
2 changes: 1 addition & 1 deletion docs/source/ttkwidgets/ttkwidgets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ ttkwidgets
DebugWindow
ItemsCanvas
LinkLabel
Notebook
ScaleEntry
ScrolledListbox
Table
TickScale
TimeLine

10 changes: 10 additions & 0 deletions docs/source/ttkwidgets/ttkwidgets/ttkwidgets.Notebook.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Notebook
========

.. currentmodule:: ttkwidgets

.. autoclass:: Notebook
:show-inheritance:
:members:

.. automethod:: __init__
30 changes: 30 additions & 0 deletions examples/example_notebook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
try:
import tkinter as tk
from tkinter import ttk
except ImportError:
import Tkinter as tk
import ttk
from ttkwidgets import Notebook


class MainWindow(ttk.Frame):
def __init__(self, master):
ttk.Frame.__init__(self, master)
colors = ['red', 'blue', 'green', 'yellow', 'cyan', 'magenta', 'black', 'white', 'purple', 'brown']
self.nb = Notebook(self, tabdrag=True, tabmenu=True, closebutton=True, closecommand=self.closecmd)
self.frames = [tk.Frame(self, width=300, height=300, bg=color) for i, color in enumerate(colors)]
for i, w in enumerate(self.frames):
self.nb.add(w, text="Frame " + str(i))
w.grid()
self.nb.grid()

def closecmd(self, tab_id):
print("Close tab " + str(tab_id))
self.nb.forget(tab_id)


root = tk.Tk()
root.title("Notebook Example")
gui = MainWindow(root)
gui.grid()
root.mainloop()
109 changes: 109 additions & 0 deletions tests/test_notebook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Copyright (c) Dogeek 2019
# For license see LICENSE
from ttkwidgets import Notebook
from tests import BaseWidgetTest
from tkinter import ttk
import tkinter as tk


class TestNotebook(BaseWidgetTest):
def test_notebook_init(self):
nb = Notebook(self.window)
nb.grid()
self.window.update()

def test_notebook_add_tab(self):
nb = Notebook(self.window)
frame = ttk.Frame(self.window, width=200, height=200)
nb.add(frame, text="Frame")
nb.grid()
self.window.update()

def test_notebook_select_tab(self):
nb = Notebook(self.window)
frame = ttk.Frame(self.window, width=200, height=200)
frame2 = ttk.Frame(self.window, width=200, height=200)
nb.add(frame, text="Frame")
nb.add(frame2, text="Frame2")
nb.grid()
nb.select_next()
nb.select_next()
nb.select_prev()
self.window.update()

def test_notebook_move_tab(self):
nb = Notebook(self.window, drag_to_toplevel=False)
frames = []
for i in range(3):
frame = ttk.Frame(self.window, width=200, height=200)
frames.append(frame)
nb.add(frame, text="Frame" + str(i))
nb._dragged_tab = nb._tab_labels[0]
nb._swap(nb._tab_labels[1])
nb._on_click(None)
self.assertEqual(nb._visible_tabs, [1, 0, 2])

def test_notebook_insert(self):
nb = Notebook(self.window, drag_to_toplevel=False)
ids = list()
n = 3
for i in range(n):
frame = ttk.Frame(self.window, width=200, height=200)
ids.append(nb.add(frame, text="Frame" + str(i)))
print(ids)
id = nb.insert(n-2, ttk.Frame(self.window, width=200, height=200), text="Added")
tabs = nb.tabs()
self.assertIn(id, tabs)
self.assertEquals(tabs.index(id), n-2)
self.assertEquals(nb.index(id), n-2)
self.assertEqual(nb._visible_tabs, [0, 3, 1, 2])

def test_notebook_index(self):
nb = Notebook(self.window)
ids = list()
frames = list()
n = 10
for i in range(n):
frame = ttk.Frame(self.window, width=200, height=200)
frames.append(frame)
ids.append(nb.add(frame, text="Frame" + str(i)))

with self.assertRaises(KeyError):
nb.index(str(self.window) + '.!frame11')

self.assertTrue(all(ids.index(id) == nb.index(id) for id in ids))
self.assertTrue(all(nb.index(id) == nb.index(frame) for id, frame in zip(ids, frames)))

self.assertEqual(nb.index(tk.END), n)
nb.current_tab = 0
self.assertEqual(nb.index(tk.CURRENT), 0)

self.window.update()

def test_notebook_forget_tab(self):
nb = Notebook(self.window)
ids = list()
n = 3
for i in range(n):
frame = ttk.Frame(self.window, width=200, height=200)
id = nb.add(frame, text="Frame" + str(i))
ids.append(id)

tabs = nb.tabs()
self.assertIn(id, tabs)
nb.forget(id) # Test forgetting of the last created tab
tabs = nb.tabs()
self.assertEquals(len(tabs), n-1)
self.assertNotIn(id, tabs)

def test_notebook_config_tab(self):
nb = Notebook(self.window)
for i in range(10):
frame = ttk.Frame(self.window, width=200, height=200)
nb.add(frame, text="Frame" + str(i))

with self.assertRaises(ValueError):
nb.tab(tk.CURRENT, state='random')

nb.tab(tk.CURRENT, text="Changed")
self.window.update()
218 changes: 218 additions & 0 deletions tests/test_utilities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# Copyright (c) Dogeek 2019
# For license see LICENSE
from ttkwidgets.utilities import move_widget, parse_geometry, coords_in_box
from tests import BaseWidgetTest
import tkinter as tk
from tkinter import ttk


class TestUtilities(BaseWidgetTest):
def assertGeometryInfoEquals(self, info1, info2):
info1.pop("in", None)
info2.pop("in", None)
self.assertEquals(info1, info2)

def setUp(self):
BaseWidgetTest.setUp(self)
self._dummy_flag = False

def assertIsChild(self, child, parent):
self.assertIn(child, parent.children.values())
parent_of_parent = parent.winfo_parent()
if not parent_of_parent.endswith("."):
parent_of_parent += "."
self.assertEquals(child.winfo_parent(), parent_of_parent + parent.winfo_name())

def assertHasBeenInvoked(self):
self.assertTrue(self._dummy_flag)
self._dummy_flag = False

def _dummy_bind(self, _=None):
self._dummy_flag = True

def test_move_widget(self):
label = ttk.Label(self.window)
tl = tk.Toplevel(self.window)
label = move_widget(label, tl)
self.assertIsChild(label, tl)

def test_move_widget_pack(self):
label = ttk.Label(self.window)
label.pack(side=tk.LEFT)
info = label.pack_info()
tl = tk.Toplevel(self.window)
label = move_widget(label, tl, preserve_geometry=True)
self.assertIsChild(label, tl)
self.assertIn(label, tl.pack_slaves())
self.assertNotIn(label, self.window.pack_slaves())
self.assertGeometryInfoEquals(info, label.pack_info())

def test_move_widget_grid(self):
label = ttk.Label(self.window)
label.grid(row=1, column=1)
info = label.grid_info()
tl = tk.Toplevel(self.window)
label = move_widget(label, tl, preserve_geometry=True)
self.assertIsChild(label, tl)
self.assertIn(label, tl.grid_slaves(row=1, column=1))
self.assertNotIn(label, self.window.grid_slaves(row=1, column=1))
self.assertGeometryInfoEquals(info, label.grid_info())

def test_move_widget_place(self):
label = ttk.Label(self.window)
label.place(x=0, y=10)
info = label.place_info()
tl = tk.Toplevel(self.window)
label = move_widget(label, tl, preserve_geometry=True)
self.assertIsChild(label, tl)
self.assertIn(label, tl.place_slaves())
self.assertNotIn(label, self.window.place_slaves())
self.assertGeometryInfoEquals(info, label.place_info())

def test_move_widget_none(self):
label = ttk.Label(self.window)
self.assertFalse(label.place_info() is True)
self.assertFalse(label.grid_info() is True)
self.assertRaises(tk.TclError, label.pack_info)
tl = tk.Toplevel(self.window)
label = move_widget(label, tl, preserve_geometry=True)
self.assertFalse(label.place_info() is True)
self.assertFalse(label.grid_info() is True)
self.assertRaises(tk.TclError, label.pack_info)

def test_move_widget_with_binding(self):
label = ttk.Label(self.window)
label.bind('<Enter>', self._dummy_bind)
label.pack()
tl = tk.Toplevel(self.window)
label = move_widget(label, tl)
label.pack()
self.assertIsChild(label, tl)
self.assertIn('<Enter>', label.bind())

def test_move_widget_with_command(self):
widget = ttk.Button(self.window, command=self._dummy_bind)
self.assertIsChild(widget, self.window)
widget.invoke()
self.assertHasBeenInvoked()

parent = tk.Toplevel()
child = move_widget(widget, parent)
self.assertIsChild(child, parent)
child.invoke()
self.assertHasBeenInvoked()

def test_move_widget_with_bound_method_on_parent(self):
tl1 = tk.Toplevel(self.window)
tl2 = tk.Toplevel(self.window)
tl1._dummy_bind = self._dummy_bind

button = ttk.Button(tl1)
button.bind("<Enter>", tl1._dummy_bind)
button.event_generate("<Enter>")
self.assertHasBeenInvoked()

button = move_widget(button, tl2)
button.event_generate("<Enter>")
self.assertHasBeenInvoked()

def test_move_widget_with_command_method_on_parent(self):
tl1 = tk.Toplevel(self.window)
tl2 = tk.Toplevel(self.window)
tl1._dummy_bind = self._dummy_bind

button = ttk.Button(tl1, command=tl1._dummy_bind)
button.invoke()
self.assertHasBeenInvoked()

button = move_widget(button, tl2)
button.invoke()
self.assertHasBeenInvoked()

def test_move_widget_with_binding_on_parent(self):
widget = ttk.Label(self.window)
widget._dummy_bind = self._dummy_bind
self.window.bind("<Enter>", widget._dummy_bind)

self.window.event_generate("<Enter>")
self.assertHasBeenInvoked()

move_widget(widget, tk.Toplevel())
self.window.event_generate("<Enter>")
self.assertHasBeenInvoked()

def test_move_widget_with_children_pack(self):
frame = ttk.Frame(self.window)
label = ttk.Label(frame)
parent = tk.Toplevel()
label.pack(side=tk.BOTTOM)
info = label.pack_info()
frame.pack(expand=True)

frame = move_widget(frame, parent)
self.assertTrue(len(frame.pack_slaves()) == 1)
label2 = frame.nametowidget(frame.pack_slaves()[0])
self.assertTrue(label is not label2)

self.assertGeometryInfoEquals(info, label2.pack_info())
self.assertRaises(tk.TclError, label.pack_info)
self.assertRaises(tk.TclError, frame.pack_info) # Frame is not packed
self.assertIn(label2, frame.pack_slaves())

def test_move_widget_with_children_grid(self):
frame = ttk.Frame(self.window)
label = ttk.Label(frame)
parent = tk.Toplevel()
label.grid(row=1, column=1)
info = label.grid_info()
frame.grid(row=1, column=1)

frame = move_widget(frame, parent)
self.assertTrue(len(frame.grid_slaves()) == 1)
label2 = frame.nametowidget(frame.grid_slaves()[0])
self.assertTrue(label is not label2)

self.assertGeometryInfoEquals(info, label2.grid_info())
self.assertRaises(tk.TclError, label.grid_info)
self.assertTrue(len(frame.grid_info()) == 0) # Frame is not in grid
self.assertIn(label2, frame.grid_slaves())

def test_move_widget_with_children_place(self):
frame = ttk.Frame(self.window)
label = ttk.Label(frame)
parent = tk.Toplevel()
label.place(x=53, y=13)
info = label.place_info()
frame.place(x=100, y=10)

frame = move_widget(frame, parent)
self.assertTrue(len(frame.place_slaves()) == 1)
label2 = frame.nametowidget(frame.place_slaves()[0])
self.assertTrue(label is not label2)

self.assertGeometryInfoEquals(info, label2.place_info())
self.assertRaises(tk.TclError, label.place_info)
self.assertTrue(len(frame.place_info()) == 0)
self.assertIn(label2, frame.place_slaves())

def test_move_widget_to_new_tk(self):
label = tk.Label(self.window)
window = tk.Tk()
self.assertRaises(RuntimeError, move_widget, label, window)

def test_parse_geometry(self):
g = parse_geometry('1x1+1+1')
self.assertEqual(g, (1, 1, 1, 1))
g = parse_geometry('1x1-1-1')
self.assertEqual(g, (-1, -1, 1, 1))

def test_coordinates_in_box(self):
with self.assertRaises(ValueError):
coords_in_box((1,), (1, 1, 3, 3))

with self.assertRaises(ValueError):
coords_in_box((1, 1), (1, 1, 3, 3, 4))

self.assertTrue(coords_in_box((1, 1), (0, 0, 2, 2)))
self.assertFalse(coords_in_box((1, 1), (1, 1, 2, 2), include_edges=False))
self.assertTrue(coords_in_box((0, 0), (-1, -1, 1, 1), bbox_is_x1y1x2y2=True))
1 change: 1 addition & 0 deletions ttkwidgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
from ttkwidgets.timeline import TimeLine
from ttkwidgets.tickscale import TickScale
from ttkwidgets.table import Table
from ttkwidgets.notebook import Notebook
Loading