-
Notifications
You must be signed in to change notification settings - Fork 155
/
myNotebook.py
183 lines (139 loc) · 7.23 KB
/
myNotebook.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
"""
Custom `ttk.Notebook` to fix various display issues.
Hacks to fix various display issues with notebooks and their child widgets on Windows.
- Windows: page background should be White, not SystemButtonFace
Entire file may be imported by plugins.
"""
from __future__ import annotations
import sys
import tkinter as tk
import warnings
from tkinter import ttk, messagebox
from PIL import ImageGrab
from l10n import translations as tr
if sys.platform == 'win32':
PAGEFG = 'SystemWindowText'
PAGEBG = 'SystemWindow' # typically white
class Notebook(ttk.Notebook):
"""Custom ttk.Notebook class to fix some display issues."""
def __init__(self, master: ttk.Frame | None = None, **kw):
super().__init__(master, **kw)
style = ttk.Style()
if sys.platform == 'win32':
style.configure('nb.TFrame', background=PAGEBG)
style.configure('nb.TButton', background=PAGEBG)
style.configure('nb.TCheckbutton', foreground=PAGEFG, background=PAGEBG)
style.configure('nb.TMenubutton', foreground=PAGEFG, background=PAGEBG)
style.configure('nb.TRadiobutton', foreground=PAGEFG, background=PAGEBG)
self.grid(padx=10, pady=10, sticky=tk.NSEW)
class Frame(ttk.Frame):
"""Custom ttk.Frame class to fix some display issues."""
def __init__(self, master: ttk.Notebook | None = None, **kw):
if sys.platform == 'win32':
ttk.Frame.__init__(self, master, style='nb.TFrame', **kw)
ttk.Frame(self).grid(pady=5) # top spacer
else:
ttk.Frame.__init__(self, master, **kw)
ttk.Frame(self).grid(pady=5) # top spacer
self.configure(takefocus=1) # let the frame take focus so that no particular child is focused
class Label(tk.Label):
"""Custom tk.Label class to fix some display issues."""
def __init__(self, master: ttk.Frame | None = None, **kw):
kw['foreground'] = kw.pop('foreground', PAGEFG if sys.platform == 'win32'
else ttk.Style().lookup('TLabel', 'foreground'))
kw['background'] = kw.pop('background', PAGEBG if sys.platform == 'win32'
else ttk.Style().lookup('TLabel', 'background'))
super().__init__(master, **kw)
class EntryMenu(ttk.Entry):
"""Extended entry widget that includes a context menu with Copy, Cut-and-Paste commands."""
def __init__(self, *args, **kwargs) -> None:
ttk.Entry.__init__(self, *args, **kwargs)
self.menu = tk.Menu(self, tearoff=False)
self.menu.add_command(label=tr.tl("Copy"), command=self.copy) # LANG: Label for 'Copy' as in 'Copy and Paste'
self.menu.add_command(label=tr.tl("Cut"), command=self.cut) # LANG: Label for 'Cut' as in 'Cut and Paste'
self.menu.add_separator()
# LANG: Label for 'Paste' as in 'Copy and Paste'
self.menu.add_command(label=tr.tl("Paste"), command=self.paste)
self.menu.add_separator()
# LANG: Label for 'Select All'
self.menu.add_command(label=tr.tl("Select All"), command=self.select_all)
self.bind("<Button-3>", self.display_popup)
def display_popup(self, event: tk.Event) -> None:
"""Display the menu popup."""
self.menu.post(event.x_root, event.y_root)
def select_all(self) -> None:
"""Select all the text within the Entry."""
self.selection_range(0, tk.END)
self.focus_set()
def copy(self) -> None:
"""Copy the selected Entry text."""
if self.selection_present():
self.clipboard_clear()
self.clipboard_append(self.selection_get())
def cut(self) -> None:
"""Cut the selected Entry text."""
if self.selection_present():
self.copy()
self.delete(tk.SEL_FIRST, tk.SEL_LAST)
def paste(self) -> None:
"""Paste the selected Entry text."""
try:
# Attempt to grab an image from the clipboard (apprently also works for files)
img = ImageGrab.grabclipboard()
if img:
# Hijack existing translation, yes it doesn't exactly match here.
messagebox.showwarning(
tr.tl('Error'), # LANG: Generic error prefix - following text is from Frontier auth service;
tr.tl('Cannot paste non-text content.'), # LANG: Can't Paste Images or Files in Text
parent=self.master
)
return
text = self.clipboard_get()
if self.selection_present() and text:
self.delete(tk.SEL_FIRST, tk.SEL_LAST)
self.insert(tk.INSERT, text)
except tk.TclError:
# No text in clipboard or clipboard is not text
pass
class Entry(EntryMenu):
"""Custom ttk.Entry class to fix some display issues."""
# DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later.
def __init__(self, master: ttk.Frame | None = None, **kw):
warnings.warn('Migrate to EntryMenu. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2)
EntryMenu.__init__(self, master, **kw)
class Button(ttk.Button):
"""Custom ttk.Button class to fix some display issues."""
def __init__(self, master: ttk.Frame | None = None, **kw):
if sys.platform == 'win32':
ttk.Button.__init__(self, master, style='nb.TButton', **kw)
else:
ttk.Button.__init__(self, master, **kw)
class ColoredButton(tk.Button):
"""Custom tk.Button class to fix some display issues."""
# DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later.
def __init__(self, master: ttk.Frame | None = None, **kw):
warnings.warn('Migrate to tk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2)
tk.Button.__init__(self, master, **kw)
class Checkbutton(ttk.Checkbutton):
"""Custom ttk.Checkbutton class to fix some display issues."""
def __init__(self, master: ttk.Frame | None = None, **kw):
style = 'nb.TCheckbutton' if sys.platform == 'win32' else None
super().__init__(master, style=style, **kw) # type: ignore
class Radiobutton(ttk.Radiobutton):
"""Custom ttk.Radiobutton class to fix some display issues."""
def __init__(self, master: ttk.Frame | None = None, **kw):
style = 'nb.TRadiobutton' if sys.platform == 'win32' else None
super().__init__(master, style=style, **kw) # type: ignore
class OptionMenu(ttk.OptionMenu):
"""Custom ttk.OptionMenu class to fix some display issues."""
def __init__(self, master, variable, default=None, *values, **kw):
if sys.platform == 'win32':
# OptionMenu derives from Menubutton at the Python level, so uses Menubutton's style
ttk.OptionMenu.__init__(self, master, variable, default, *values, style='nb.TMenubutton', **kw)
self['menu'].configure(background=PAGEBG)
else:
ttk.OptionMenu.__init__(self, master, variable, default, *values, **kw)
self['menu'].configure(background=ttk.Style().lookup('TMenu', 'background'))
# Workaround for https://bugs.python.org/issue25684
for i in range(0, self['menu'].index('end') + 1):
self['menu'].entryconfig(i, variable=variable)