diff --git a/.github/workflows/executables.yml b/.github/workflows/executables.yml index e6eac3f..88c0fa0 100644 --- a/.github/workflows/executables.yml +++ b/.github/workflows/executables.yml @@ -51,7 +51,7 @@ jobs: - name: Make zip run: | mkdir temporary - cp -r certifi chardet Darwin idna Linux requests urllib3 temporary/ + cp -r certifi chardet Darwin idna Linux requests urllib3 tkmacosx temporary/ cp LICENSE main.py README.md temporary/ cd temporary chmod +x main.py @@ -62,11 +62,41 @@ jobs: path: lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}-Python3.zip name: python + + macos: + runs-on: macos-latest + name: macOS + steps: + - name: Checkout Repo + uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + architecture: "x64" + - name: Install pyinstaller + run: | + pip3 install pyinstaller + - name: Get tag + id: vars + run: echo::set-output name=tag::${GITHUB_REF#refs/*/} + - name: Create executable + run: | + pyinstaller --onefile --add-data "tkmacosx;tkmacosx" --add-data "Darwin;Darwin" --add-data "certifi;certifi" --add-data "requests;requests" --add-data "urllib3;urllib3" --add-data "lazy.ico;." --add-data "chardet;chardet" --icon "lazy.ico" --console --name "lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}-macOS" main.py + cd dist + chmod +x lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}-macOS + zip ../lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}-macOS.zip lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}-macOS + - name: Publish artifact + uses: actions/upload-artifact@v2 + with: + path: lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}-macOS.zip + name: macos + publish: runs-on: ubuntu-latest name: Publish builds if: ${{ success() }} - needs: [windows,python] + needs: [windows,python,macos] steps: - name: Download artifacts uses: actions/download-artifact@v2 diff --git a/main.py b/main.py index 548ac9f..137fec0 100644 --- a/main.py +++ b/main.py @@ -391,7 +391,7 @@ def extraHomebrew(source): frame.pack(fill=tkinter.BOTH, expand=True) - okButton = tkinter.Button(homebrewWindow, text = "OK", font=(buttonFont), command=lambda:okButtonPress(homebrewWindow,source),bg="#f0f0f0",fg="#000000") + okButton = Button(homebrewWindow, text = "OK", font=(buttonFont), command=lambda:okButtonPress(homebrewWindow,source),bg="#f0f0f0",fg="#000000") okButton.pack(side=tkinter.RIGHT, padx=5, pady=5) homebrewWindow.update() canvas.config(scrollregion=canvas.bbox("all")) @@ -437,9 +437,9 @@ def summonWindow0(): bullet = tkinter.Label(topFrame, text=x, font=(paragraphFont),fg=foregroundColour,wraplength=450, justify="left") bullet.grid(column=0,row=count+3, sticky="w", padx=5) - discordButton = tkinter.Button(bottomFrame, text="DS⁽ⁱ⁾ Mode Hacking Discord server", fg=foregroundColour,bg=buttonColour, font=(buttonFont),command=lambda:webbrowser.open("https://discord.gg/yD3spjv",new=1)) + discordButton = Button(bottomFrame, text="DS⁽ⁱ⁾ Mode Hacking Discord server", fg=foregroundColour,bg=buttonColour, font=(buttonFont),command=lambda:webbrowser.open("https://discord.gg/yD3spjv",new=1)) discordButton.pack(side=tkinter.LEFT, padx="5", pady="5") - nextButton = tkinter.Button(bottomFrame, text="Next",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),summonWindow1()]) + nextButton = Button(bottomFrame, text="Next",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),summonWindow1()]) nextButton.pack(side=tkinter.RIGHT, padx="5", pady="5") window.protocol("WM_DELETE_WINDOW",lambda:closeButtonPress(window)) @@ -467,9 +467,9 @@ def summonWindow1(): selector["menu"].config(bg=buttonColour,fg=foregroundColour,font=(buttonFont)) selector.grid(column=0,row=5,sticky="w") - backButton = tkinter.Button(bottomFrame,text="Back", font=(buttonFont),fg=foregroundColour,bg=backButtonColour,command=lambda: [topFrame.destroy(),bottomFrame.destroy(),summonWindow0()], width="8") + backButton = Button(bottomFrame,text="Back", font=(buttonFont),fg=foregroundColour,bg=backButtonColour,command=lambda: [topFrame.destroy(),bottomFrame.destroy(),summonWindow0()], width="8") backButton.pack(side=tkinter.LEFT) - nextButton = tkinter.Button(bottomFrame, text="Next",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),summonWindow2()]) + nextButton = Button(bottomFrame, text="Next",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),summonWindow2()]) nextButton.pack(side=tkinter.RIGHT, padx="5", pady="5") window.protocol("WM_DELETE_WINDOW",lambda:closeButtonPress(window)) @@ -500,9 +500,9 @@ def summonWindow2(): updateHiyaCheck.grid(column=0,row=7,sticky="w") buttonExtraHomebrew = tkinter.Button(topFrame, text = "Additional homebrew...", command =lambda:[extraHomebrew(window)], fg=foregroundColour,font=(buttonFont),bg=buttonColour) buttonExtraHomebrew.grid(column=0,row=8,sticky="w",pady=5) - backButton = tkinter.Button(bottomFrame,text="Back", font=(buttonFont),fg=foregroundColour,bg=backButtonColour,command=lambda: [topFrame.destroy(),bottomFrame.destroy(),summonWindow1()], width="8") + backButton = Button(bottomFrame,text="Back", font=(buttonFont),fg=foregroundColour,bg=backButtonColour,command=lambda: [topFrame.destroy(),bottomFrame.destroy(),summonWindow1()], width="8") backButton.pack(side=tkinter.LEFT) - nextButton = tkinter.Button(bottomFrame, text="Next",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),summonWindow3()]) + nextButton = Button(bottomFrame, text="Next",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),summonWindow3()]) nextButton.pack(side=tkinter.RIGHT, padx="5", pady="5") window.protocol("WM_DELETE_WINDOW",lambda:closeButtonPress(window)) @@ -521,11 +521,11 @@ def summonWindow3(): noticeLabel.grid(column=0,row=2,sticky="w") SDentry = tkinter.Entry(topFrame, fg=foregroundColour,bg=buttonColour,font=(buttonFont),width=25) SDentry.grid(column=0, row=3,sticky="w") - chooseDirButton = tkinter.Button(topFrame, text = "Click to select folder", command =lambda:chooseDir(topFrame,SDentry),fg=foregroundColour,bg=buttonColour,font=(buttonFont),width=25) + chooseDirButton = Button(topFrame, text = "Click to select folder", command =lambda:chooseDir(topFrame,SDentry),fg=foregroundColour,bg=buttonColour,font=(buttonFont),width=25) chooseDirButton.grid(column=0, row=4,sticky="w",pady=5) - backButton = tkinter.Button(bottomFrame,text="Back", font=(buttonFont),fg=foregroundColour,bg=backButtonColour,command=lambda: [topFrame.destroy(),bottomFrame.destroy(),summonWindow2()], width="8") + backButton = Button(bottomFrame,text="Back", font=(buttonFont),fg=foregroundColour,bg=backButtonColour,command=lambda: [topFrame.destroy(),bottomFrame.destroy(),summonWindow2()], width="8") backButton.pack(side=tkinter.LEFT) - nextButton = tkinter.Button(bottomFrame, text="Start",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[globalify(SDentry.get()),topFrame.destroy(),bottomFrame.destroy(),summonWindow4()]) + nextButton = Button(bottomFrame, text="Start",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[globalify(SDentry.get()),topFrame.destroy(),bottomFrame.destroy(),summonWindow4()]) nextButton.pack(side=tkinter.RIGHT, padx="5", pady="5") window.protocol("WM_DELETE_WINDOW",lambda:closeButtonPress(window)) @@ -550,10 +550,10 @@ def summonWindow4(): outputBox.grid(column=0,row=2,sticky="w") startThread.start() global finalbackButton - finalbackButton = tkinter.Button(bottomFrame,state="disabled", text="Back", font=(buttonFont),fg=foregroundColour,bg=backButtonColour,command=lambda: [topFrame.destroy(),bottomFrame.destroy(),summonWindow3()], width="8") + finalbackButton = Button(bottomFrame,state="disabled", text="Back", font=(buttonFont),fg=foregroundColour,bg=backButtonColour,command=lambda: [topFrame.destroy(),bottomFrame.destroy(),summonWindow3()], width="8") finalbackButton.pack(side=tkinter.LEFT) global finalnextButton - finalnextButton = tkinter.Button(bottomFrame, state="disabled", text="Finish",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),summonWindow5()]) + finalnextButton = Button(bottomFrame, state="disabled", text="Finish",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),summonWindow5()]) finalnextButton.pack(side=tkinter.RIGHT, padx="5", pady="5") window.protocol("WM_DELETE_WINDOW",lambda:donothing) @@ -582,84 +582,89 @@ def summonWindow5(): w = w + 1 label= tkinter.Label(topFrame,text="Press the Close button to Exit",font=(bodyFont),fg=foregroundColour) label.grid(column=0,row=w+1,sticky="w") - finish = tkinter.Button(bottomFrame, text="Close",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),closeButtonPress(window)]) + finish = Button(bottomFrame, text="Close",width="8", fg=foregroundColour,bg=nextButtonColour, font=(buttonFont),command=lambda:[topFrame.destroy(),bottomFrame.destroy(),closeButtonPress(window)]) finish.pack(side=tkinter.RIGHT, padx="5", pady="5") window.protocol("WM_DELETE_WINDOW",lambda:closeButtonPress(window)) -if(sys.version_info.major < 3): - print("This program will ONLY work on Python 3 and above") - sys.exit() +if __name__ == "__main__": -root = tkinter.Tk() -window = tkinter.Toplevel(root) -root.withdraw() + if(sys.version_info.major < 3): + print("This program will ONLY work on Python 3 and above") + sys.exit() -#Homebrew Database -homebrewDB = json.loads(requests.get('https://raw.githubusercontent.com/YourKalamity/just-a-dsi-DB/master/just-a-dsi-DB.json').content) -homebrewList = [] -for x in homebrewDB: - homebrewList.append(tkinter.IntVar()) + root = tkinter.Tk() + window = tkinter.Toplevel(root) + root.withdraw() + + #Homebrew Database + homebrewDB = json.loads(requests.get('https://raw.githubusercontent.com/YourKalamity/just-a-dsi-DB/master/just-a-dsi-DB.json').content) + homebrewList = [] + for x in homebrewDB: + homebrewList.append(tkinter.IntVar()) -#TKinter Vars -downloadmemorypit = tkinter.IntVar(value=1) -firmwareVersion = tkinter.StringVar() -firmwareVersion.set(dsiVersions[0]) -downloadtwlmenu = tkinter.IntVar(value=1) -downloaddumptool = tkinter.IntVar(value=1) -unlaunch = tkinter.IntVar(value=0) -godmode9i = tkinter.IntVar(value=0) -updateHiyaCFW = tkinter.IntVar(value=0) + #TKinter Vars + downloadmemorypit = tkinter.IntVar(value=1) + firmwareVersion = tkinter.StringVar() + firmwareVersion.set(dsiVersions[0]) + downloadtwlmenu = tkinter.IntVar(value=1) + downloaddumptool = tkinter.IntVar(value=1) + unlaunch = tkinter.IntVar(value=0) + godmode9i = tkinter.IntVar(value=0) + updateHiyaCFW = tkinter.IntVar(value=0) -#Fonts -titleFont = tkinter.font.Font( - family= "Segoe UI", - size= 15, - weight= "bold" + #Fonts + titleFont = tkinter.font.Font( + family= "Segoe UI", + size= 15, + weight= "bold" + ) + subtitleFont = tkinter.font.Font( + family= "Segoe UI", + size= 11, + slant= "italic" ) -subtitleFont = tkinter.font.Font( - family= "Segoe UI", - size= 11, - slant= "italic" -) - -bodyFont = tkinter.font.Font( - family= "Segoe UI", - underline= False, - size = 11 + + bodyFont = tkinter.font.Font( + family= "Segoe UI", + underline= False, + size = 11 + ) + buttonFont = tkinter.font.Font( + family="Segoe UI", + underline=False, + size = 11, + weight = "bold" ) -buttonFont = tkinter.font.Font( - family="Segoe UI", - underline=False, - size = 11, - weight = "bold" -) -bigListFont = tkinter.font.Font( - family="Segoe UI", - underline=False, - size=9 -) + bigListFont = tkinter.font.Font( + family="Segoe UI", + underline=False, + size=9 + ) -paragraphFont = tkinter.font.Font( - family="Segoe UI", - size=10 -) + paragraphFont = tkinter.font.Font( + family="Segoe UI", + size=10 + ) -if platform.system() == "Darwin": #Why is macOS so difficult... - backgroundColour = "#f0f0f0" #How dull and boring - foregroundColour = "black" - buttonColour = "#f0f0f0" - backButtonColour = "#f0f0f0" - nextButtonColour = "#f0f0f0" -else: #Non Jeve Stobs worshippers - backgroundColour = "#252a34" - foregroundColour = "white" - buttonColour = "#7289DA" - backButtonColour = "#567487" - nextButtonColour = "#027b76" + if platform.system() == "Darwin": #Why is macOS so difficult... + from tkmacosx import Button + backgroundColour = "#f0f0f0" #How dull and boring + foregroundColour = "black" + buttonColour = "#f0f0f0" + backButtonColour = "#f0f0f0" + nextButtonColour = "#f0f0f0" -summonWindow0() -root.mainloop() + else: #Non Jeve Stobs worshippers + from tkinter import Button + backgroundColour = "#252a34" + foregroundColour = "white" + buttonColour = "#7289DA" + backButtonColour = "#567487" + nextButtonColour = "#027b76" + + summonWindow0() + root.mainloop() diff --git a/tkmacosx/__init__.py b/tkmacosx/__init__.py new file mode 100644 index 0000000..282cfe6 --- /dev/null +++ b/tkmacosx/__init__.py @@ -0,0 +1,37 @@ +# Copyright 2020 Saad Mairaj +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This module provides some modified widgets of Tkinter which works better on macos +and some more useful functions and classes as well. For example Button of tkmacosx which +looks and feels exactly like a native tkinter button can change its background +and foreground colors. + +Read more about tkmacosx in detail on +https://github.com/Saadmairaj/tkmacosx/tree/master/tkmacosx. +""" + +__version__ = '0.1.4' + +from tkmacosx.basewidget import check_appearance, get_shade, delta +from tkmacosx.variables import ColorVar, DictVar, SaveVar, demo_colorvar, demo_savevar +from tkmacosx.widget import Button, CircleButton, SFrame, Marquee, demo_sframe, demo_button, demo_marquee +from tkmacosx.colors import Hex, OrderedHex, all_colors, named_colors, hue_color +from tkmacosx.colorscale import Colorscale, demo_colorscale, gradient + +if __name__ == "__main__": + demo_sframe() + demo_button() + demo_colorvar() + demo_colorscale() diff --git a/tkmacosx/__main__.py b/tkmacosx/__main__.py new file mode 100644 index 0000000..23f5594 --- /dev/null +++ b/tkmacosx/__main__.py @@ -0,0 +1,324 @@ +# Copyright 2020 Saad Mairaj +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import random +if sys.version_info.major == 2: + import ttk + import Tkinter as tk + import tkFont as font + from tkColorChooser import askcolor +elif sys.version_info.major == 3: + from tkinter import ttk + import tkinter as tk + import tkinter.font as font + from tkinter.colorchooser import askcolor + +from tkmacosx.widget import * +from tkmacosx.variables import * +from tkmacosx.colorscale import Colorscale +from tkmacosx.colors import Hex as C_dict, all_colors + + +color_list = [list(i.values())[0].get('hex') for i in all_colors] + + +def grid(root, row, column): + "Defines rows and columns if grid method is used" + if column: + for y in range(column): + tk.Grid.columnconfigure(root, y, weight=1) + if row: + for x in range(row): + tk.Grid.rowconfigure(root, x, weight=1) + return + + +def sort_random_values(fn): + def wrapper(*args, **kwargs): + value = fn(*args, **kwargs) + if isinstance(value, (list, tuple)) and len(value) == 1: + return value[0] + return value + return wrapper + + +def choices(seq, k): + "Random choices." + return tuple(random.choice(seq) for i in range(k)) + + +@sort_random_values +def get_random_colors(k=1): + return choices(color_list, k=k) + + +@sort_random_values +def get_random_font(k=1): + def family(): return random.choice(font.families()) + + def weight(): return random.choice(('bold', 'normal')) + + def slant(): return random.choice(('italic', 'roman')) + + def size(): return random.randrange(9, 30) + + def underline(): return random.choice((True, False)) + + overstrike = underline + return tuple(font.Font(family=family(), weight=weight(), + slant=slant(), underline=underline(), + overstrike=overstrike(), size=30,) for i in range(k)) + + +@sort_random_values +def get_random_size(k=1, start=10, end=200): + return tuple(choices([i for i in range(start, end)], k=k)) + + +@sort_random_values +def get_random_relief(k=1): + return choices(('flat', 'groove', 'raised', + 'ridge', 'solid', 'sunken'), k=k) + + +class Sample(tk.Tk): + def __init__(self): + tk.Tk.__init__(self) + self.resizable(0, 0) + self.geometry('420x700+300+100') + self.title('Mac OSX Button Testing') + self.wm_attributes('-modified', 1) + self.main_color = ColorVar(value='#ffe6f4') + self['bg'] = self.main_color + grid(self, 25, 5) + self.L1 = tk.Label(self, text='Comparison', + bg=self.main_color, font=('', 18, 'bold')) + self.L1.grid(row=0, column=0, columnspan=5, sticky='nsew') + Button(self, text='Hello').grid(row=1, column=1, sticky='s') + ttk.Button(self, text='Hello').grid(row=1, column=3, sticky='s') + tk.Button(self, text='Hello').grid(row=1, column=2, sticky='s') + tk.Label(self, bg=self.main_color, font=('', 10), + text='(Mac OSX)').grid(row=2, column=1, sticky='n',) + tk.Label(self, bg=self.main_color, font=('', 10), + text='(ttk themed)').grid(row=2, column=3, sticky='n') + tk.Label(self, bg=self.main_color, font=('', 10), + text='(Default)').grid(row=2, column=2, sticky='n') + ttk.Separator(self, orient='vertical').grid( + row=3, column=0, columnspan=5, sticky='ew') + + # ------------ Seperator ------------- + + # ------------ Demonstration ------------ + + self.sfr = SFrame(self, bg=self.main_color) + self.sfr.grid(rowspan=27, columnspan=5, sticky='nsew') + for i in range(5): + self.sfr.grid_columnconfigure(i, weight=1) + self.L2 = tk.Label(self.sfr, text='Demonstration', + bg=self.main_color, font=('', 20, 'bold')) + self.L2.grid(row=1, column=0, columnspan=5, + sticky='new', pady=(20, 10)) + + # ------------ Active Color ------------ + + self.L3 = tk.Label(self.sfr, text='1. Change Active color', bg=self.main_color, + font=('', 15, 'bold')) + self.L3.grid(row=2, column=0, columnspan=5, sticky='nsew', pady=10) + self.L4 = tk.Label(self.sfr, text='The active color can be changed to any gradient color.', + bg=self.main_color, font=('', 10)) + self.L4.grid(row=3, column=0, columnspan=5, sticky='new') + self.B1 = Button(self.sfr, text='Press Me', pady=20) + self.B1.grid(row=4, column=0, columnspan=5, pady=20) + self.C1 = tk.StringVar(value='Select') + self.L5 = tk.Label(self.sfr, text='From', + bg=self.main_color, font=('', 12)) + self.L5.grid(row=5, column=1, sticky='nwe') + self.Om1 = tk.OptionMenu(self.sfr, self.C1, *C_dict.keys(), + command=self.change_active_color) + self.Om1.config(bg=self.main_color, width=15) + self.Om1.grid(row=6, column=1, sticky='s', pady=(0, 10)) + for i in range(self.Om1['menu'].index('end')+1): + self.Om1['menu'].entryconfig(i, foreground=list(C_dict)[i]) + self.C2 = tk.StringVar(value='Select') + self.L6 = tk.Label(self.sfr, text='To', + bg=self.main_color, font=('', 12)) + self.L6.grid(row=5, column=3, sticky='nwe') + self.Om2 = tk.OptionMenu(self.sfr, self.C2, *C_dict.keys(), + command=self.change_active_color) + self.Om2.config(bg=self.main_color, width=15) + self.Om2.grid(row=6, column=3, sticky='s', pady=(0, 10)) + for i in range(self.Om2['menu'].index('end')+1): + self.Om2['menu'].entryconfig(i, foreground=list(C_dict)[i]) + + # ------------ Background Color ------------ + + # ttk.Separator(self.sfr, orient='vertical').grid(row=6, column=2, columnspan=1, sticky='ew') + self.L7 = tk.Label(self.sfr, text='2. Change Background color', bg=self.main_color, + font=('', 15, 'bold')) + self.L7.grid(row=7, column=0, columnspan=5, + sticky='nsew', pady=(50, 0)) + self.L8 = tk.Label(self.sfr, text='Click on the button to choose the color.', + bg=self.main_color, font=('', 10)) + self.L8.grid(row=8, column=0, columnspan=5, sticky='new', pady=10) + + self.B2 = Button(self.sfr, text='Color me', + font=('', 30,), pady=10, padx=10) + self.B2.grid(row=9, column=0, columnspan=5, sticky='', pady=20) + + self.B3 = Button(self.sfr, text='Change Background Color', + bg='#d0c0ea', borderless=1) + self.B3['command'] = lambda: self.B2.config(bg=askcolor()[1]) + self.B3.grid(row=10, column=0, columnspan=5, sticky='w', pady=10, padx=10) + self.B4 = Button(self.sfr, text='Change Foreground Color', + bg="#d0c0ea", borderless=1) + self.B4['command'] = lambda: self.B2.config(fg=askcolor()[1]) + self.B4.grid(row=10, column=0, columnspan=5, sticky='e', pady=10, padx=10) + + # ------------ Borderless ------------ + + self.L9 = tk.Label(self.sfr, text='3. Switch Between Borderless', bg=self.main_color, + font=('', 15, 'bold')) + self.L9.grid(row=11, column=0, columnspan=5, + sticky='sew', pady=(50, 0)) + self.L10 = tk.Label(self.sfr, text=""" + In borderless it will blend with its parent widget background color. + Give parameter `borderless = True / False` to use it.""", bg=self.main_color, font=('', 10)) + self.L10.grid(row=12, column=0, columnspan=5, sticky='new') + + self.B5 = Button(self.sfr, text='No Borders', borderless=1, height=40, + bg='#212F3D', fg='white', activebackground=("#EAECEE", "#212F3D")) + self.B5.grid(row=13, columnspan=5, pady=(20, 5)) + + self.B6 = Button(self.sfr, text='No Borders', borderless=1, height=40, + bg='#F7DC6F', fg='#21618C', activebackground=('#B3B6B7', '#58D68D')) + self.B6.grid(row=14, columnspan=5, pady=(0, 20)) + self.var1 = tk.BooleanVar(value=True) + self.CB1 = tk.Checkbutton(self.sfr, text='Toggle Borderless', variable=self.var1, + bg=self.main_color, command=self.change_borderless_state) + self.CB1.grid(row=15, columnspan=5, pady=(0, 10)) + + # ------------ Bordercolor ------------ + + self.L11 = tk.Label(self.sfr, text='4. Change Bordercolor', bg=self.main_color, + font=('', 15, 'bold')) + self.L11.grid(row=16, column=0, columnspan=5, + sticky='sew', pady=(50, 0)) + self.L12 = tk.Label(self.sfr, text="Change Bordercolor of the button\nNote: if borderless=True, then the bordercolor won't work.", + bg=self.main_color, font=('', 10)) + self.L12.grid(row=17, column=0, columnspan=5, sticky='new') + + self.B7 = Button(self.sfr, text='Button', pady=10, + padx=5, font=('Zapfino', 12, 'bold')) + self.B7.grid(row=18, columnspan=5, pady=30) + + self.CS1 = Colorscale(self.sfr, value='hex', mousewheel=1, + command=lambda e: self.B7.config(bordercolor=e)) + self.CS1.grid(row=19, columnspan=5, sticky='ew', padx=10) + + self.CS2 = Colorscale(self.sfr, value='hex', mousewheel=1, + gradient=('#FCF6F5', '#990011'), + command=lambda e: self.B7.config(bg=e)) + self.CS2.grid(row=20, columnspan=5, sticky='ew', padx=10, pady=5) + + self.CS3 = Colorscale(self.sfr, value='hex', mousewheel=1, + gradient=('green', 'yellow'), + command=lambda e: self.B7.config(fg=e)) + self.CS3.grid(row=21, columnspan=5, sticky='ew', padx=10) + + self.CS4 = Colorscale(self.sfr, value='hex', mousewheel=1, + gradient=('pink', 'purple'), + command=lambda e: self.B7.config(overforeground=e)) + self.CS4.grid(row=22, columnspan=5, sticky='ew', padx=10, pady=5) + + # ------------ Random button styling ------------ + self.L11 = tk.Label(self.sfr, text='5. Button Styling', bg=self.main_color, + font=('', 15, 'bold')) + self.L11.grid(row=23, column=0, columnspan=5, + sticky='sew', pady=(50, 0)) + self.L12 = tk.Label(self.sfr, text="Press the button to ramdomise the style of the button.", + bg=self.main_color, font=('', 10)) + self.L12.grid(row=24, column=0, columnspan=5, sticky='new') + + self.B10 = Button(self.sfr, text='Button', borderless=1) + self.B10.grid(row=25, columnspan=5, pady=20) + + self.B11 = Button(self.sfr, text='Change Style', borderless=1, fg='#21618C', + activebackground=('#B3B6B7', '#58D68D'), command=self.change_button_style) + self.B11.grid(row=26, columnspan=5, ipady=5) + + self.button_clicks = 1 + self.Text1 = tk.Text(self.sfr, background=self.main_color, highlightthickness=0, + relief='sunken', height=20, bd=2, padx=10) + self.Text1.grid(row=27, columnspan=5, pady=20, padx=20) + self.sfr._avoid_mousewheel((self.Text1, self.CS1, self.CS2, self.CS3, self.CS4)) + self.change_button_style() + self.update_idletasks() + + def change_active_color(self, *ags): + c1 = self.C1.get() if not self.C1.get() == 'Select' else None + c2 = self.C2.get() if not self.C2.get() == 'Select' else None + self.Om1.config(bg=c1) + self.Om2.config(bg=c2) + self.B1['activebackground'] = (c1, c2) + + def change_borderless_state(self): + if self.var1.get(): + self.B5['borderless'] = 1 + self.B6['borderless'] = 1 + else: + self.B5['borderless'] = 0 + self.B6['borderless'] = 0 + + def change_button_style(self): + cnf = dict( + foreground=get_random_colors(), + background=get_random_colors(), + activebackground=get_random_colors(2), + activeforeground=get_random_colors(), + overrelief=get_random_relief(), + relief=get_random_relief(), + highlightthickness=get_random_size(1, 1, 5), + borderwidth=get_random_size(1, 1, 5), + font=get_random_font(), + highlightbackground=get_random_colors(), + focuscolor=get_random_colors(), + overbackground=get_random_colors(), + overforeground=get_random_colors(), + ) + self.B10.config(**cnf) + + self.Text1.insert('end', '\n\nBUTTON STYLE %s\n\n'%self.button_clicks) + for k,v in cnf.items(): + if k == 'font': + self.Text1.insert('end', 'font:\n') + for i, j in v.config().items(): + self.Text1.insert('end', '\t%s\t\t= %s\n' %(i,j)) + else: + self.Text1.insert('end', '%s\t\t\t= %s\n' %(k,v)) + self.Text1.see('end') + self.Text1.insert('end', '\n\n'+'-'*40) + self.button_clicks += 1 + self.sfr['canvas'].yview_moveto('1.0') + self.update_idletasks() + + +def main(): + "Demonstration of tkmacosx." + Sample().mainloop() + + +# Testing Demo +if __name__ == "__main__": + main() diff --git a/tkmacosx/basewidget.py b/tkmacosx/basewidget.py new file mode 100644 index 0000000..3451b1a --- /dev/null +++ b/tkmacosx/basewidget.py @@ -0,0 +1,1532 @@ +# Copyright 2020 Saad Mairaj +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +if sys.version_info.major == 2: + import ttk + import Tkinter as _tk + from Tkinter import _cnfmerge +elif sys.version_info.major == 3: + from tkinter import ttk + from tkinter import _cnfmerge + import tkinter as _tk + +try: + import colour as C +except ImportError as e: + import os + if sys.version_info.major == 2: + import tkMessageBox as ms + elif sys.version_info.major == 3: + import tkinter.messagebox as ms + error_win = _tk.Tk() + error_win.withdraw() + message = """ + Module "tkmacosx" is dependent on + "colour" Module. + + Do you want to install it with pip now? + Or install it manually later. + + Link to the colour github: + https://github.com/vaab/colour""" + if ms._show(e, message, 'warning', 'yesno') == 'yes': + error_win.destroy() + os.system('pip install colour') + import colour as C + else: + sys.exit(0) + +import tkmacosx.variables as tkv + + +def delta(evt): + """Modified delta to work with all platforms.""" + if evt.num == 5 or evt.delta < 0: + return -1 + return 1 + + +def _agsmerge(args): + """Internal functions.\n + Merges lists/tuples.""" + a = [] + if isinstance(args, (tuple, list)): + for i in args: + if isinstance(i, (tuple, list)): + a.extend(i) + else: + a.append(i) + return a or args + + +def check_function_equality(func1, func2): + """Checks if two functions are same.""" + return func1.__code__.co_code == func2.__code__.co_code + + +def _bind(cls=None, *ags, **kw): + """Internal function.\n + Binds and unbinds sequences with any name given as className.""" + cls = kw.pop('cls', cls) or ( + ags.pop(0) if ags and isinstance(ags[0], _tk.Misc) else None) + if not cls: + raise ValueError('Counld not refer to any class instance "cls".') + if len(kw) < 1 and len(ags) > 0: + return [_bind(cls=cls, **i) for i in ags] + + classname = kw['className'] + str(cls) + bindtags = list(cls.bindtags()) + if kw.get('func'): + if classname in bindtags: + bindtags.remove(classname) + _bind(cls, className=kw['className'], sequence=kw['sequence']) + bindtags.append(classname) + cls.bindtags(tuple(bindtags)) + return cls.bind_class(classname, sequence=kw['sequence'], + func=kw['func'], add=kw.get('add', '+')) + else: + if classname in bindtags: + bindtags.remove(classname) + cls.bindtags(tuple(bindtags)) + cls.unbind_class(classname, kw['sequence']) + + +def _on_press_color(cls=None, cnf={}, **kw): + """Internal function. Do not call directly.\n + Give gradient color effect used for activebackground. + Returns ids""" + kw = _cnfmerge((cnf, kw)) + cls = kw.get('cls', cls) + w = cls.cnf.get('height', cls.winfo_width()) + h = cls.cnf.get('height', cls.winfo_height()) + tag = kw.get('tag', 'press') + state = kw.get('state', 'normal' if cls.cnf.get( + 'state') in ('active', 'pressed') else 'hidden') + if not cls: + raise ValueError('Counld not refer to any class instance "cls".') + if kw.get('color') is None: + kw.pop('color', None) + width = cls.coords(tag) or 0 + if isinstance(width, (list, tuple)) and len(width) > 3: + width = int(width[2]) + all_activebg_ids = cls.find('withtag', tag) + cond1 = bool(h == len(all_activebg_ids)) + cond2 = bool(w == width) + # [issue-1] Need a better approach for getting "cond3" + cond3 = bool(kw.get('color', (False,)) == cls.cnf.get('activebackground')) + if cond1 and cond2 and cond3 and not kw.get('force_create', False): + return + cls.cnf['activebackground'] = kw.get('color', cls.cnf.get( + 'activebackground', ("#4b91fe", "#055be5"))) + cls.delete(tag) + ids = [] + # This is the default color for mac + cr = kw.get('color', ("#4b91fe", "#055be5")) + if isinstance(cr, (tuple, list)): + if None in cr: + cr = list(cr) + cr.remove(None) + cr = cr[0] + if not isinstance(cr, (tuple, list)): + cr = (C.Color(cr), C.Color(cr)) + else: + cr = (C.Color(cr[0]), C.Color(cr[1])) + for i, j in enumerate(tuple(cr[0].range_to(cr[1], kw.get('height', h)))): + ags = (0, i, kw.get('width', w), i) + cnf = {'fill': j, 'tag': tag, 'state': state} + ids.append(cls._create('line', ags, cnf)) + cls.tag_lower(tag) # keep the tag last + return tuple(ids) + + +def _info_button(master, cnf={}, **kw): + """Internal Function.\n + This function takes essentials parameters to give + the approximate width and height accordingly. \n + It creates a ttk button and use all the resources given + and returns width and height of the ttk button, after taking + width and height the button gets destroyed also the custom style.""" + kw = _tk._cnfmerge((cnf, kw)) + cnf = dict(**kw) + kw.pop('height', None) + def _get_height(master, **kw): + """Creates temp tkinter button to get approximate + height when -height parameter is given.""" + y_dif = 2 if kw.get('pady') else 0 + y_dif += -kw.get('pady', 1) + 2 if kw.get('image')\ + and kw.get('compound') in ('top', 'bottom') else -2 + tmp = _tk.Button(master, **kw) + height = max(24, tmp.winfo_reqheight()+y_dif) + tmp.destroy() + return height + + name = '%s.TButton' % master + _style_tmp = ttk.Style() + _style_tmp.configure(name, font=kw.pop('font', None)) + _style_tmp.configure(name, padding=(kw.pop('padx', 0), kw.pop('pady', 0))) + tmp = ttk.Button(master, style=name, **kw) + if cnf.get('height', None) and False: + geo = [tmp.winfo_reqwidth(), _get_height(master, **cnf)] + else: + geo = [tmp.winfo_reqwidth(), tmp.winfo_reqheight()] + # [issue-2] Need fix --- doesn't really delete the custom style + del _style_tmp + tmp.destroy() + return geo + + +def check_appearance(cmd='defaults read -g AppleInterfaceStyle'): + """### Checks DARK/LIGHT mode of macos. Returns Boolean. + #### Args: + - `cmd`: Give commands. Like to check DARK/LIGHT mode \ + the command is `'defaults read -g AppleInterfaceStyle'` . + """ + import subprocess + out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + shell=True).communicate() + if out: + return True + elif err: + return False + + +def get_shade(color, shade, mode='auto'): + """### Darken or Lighten a shade of color. + #### Args: + 1. `color`: Give a color as either HEX or name of the color. + 2. `shade`: The amount of change required. Takes float \ + between 0.0 to 1.0 eg: shade=0.225. + 3. `mode`: + - `'-'` for darker shade. + - `'+'` for lighter shade. + - `'auto-110'` automatically decide lighter or \ + darker. where 110 is the intensity. + + return hexcode""" + op = {'+': lambda x, y: float(x+y), + '-': lambda x, y: float(x-y)} + if isinstance(color, str): + color = list(float(i/65535.0) + for i in _tk._default_root.winfo_rgb(color)) + if 'auto' in mode: + intensity = (110.0 if len(mode) <= 4 else float( + mode.split('-')[1])) / 1000.0 + color_intensity = float( + color[0]*0.299 + color[1]*0.587 + color[2]*0.114) + mode = '-' if color_intensity > intensity else '+' + if color_intensity > intensity*2 or color_intensity < intensity/2: + shade += shade + if mode not in op: + raise ValueError( + 'Invalid mode "{}", Takes only "-" or "+"'. format(mode)) + for index, c in enumerate((op[mode](c, shade) for c in color)): + if c > 1.0: + c = 1.0 + elif c < 0.0: + c = 0.0 + color[index] = int(c*255.0) + return '#%02x%02x%02x' % (color[0], color[1], color[2]) + + +class _Canvas(_tk.Widget): + """Internal Class.""" + + def __init__(self, master=None, cnf={}, **kw): + _tk.Widget.__init__(self, master, 'canvas', cnf, kw) + + def find(self, *args): + """Internal function.""" + return self._getints( + self.tk.call((self._w, 'find') + args)) or () + + def bbox(self, *args): + """Return a tuple of X1,Y1,X2,Y2 coordinates for a rectangle + which encloses all items with tags specified as arguments.""" + return self._getints( + self.tk.call((self._w, 'bbox') + args)) or None + + def coords(self, *args): + """Return a list of coordinates for the item given in ARGS.""" + return [self.tk.getdouble(x) for x in + self.tk.splitlist( + self.tk.call((self._w, 'coords') + args))] + + def _create(self, itemType, args, kw): # Args: (val, val, ..., cnf={}) + """Internal function.""" + args = _tk._flatten(args) + cnf = args[-1] + if isinstance(cnf, (dict, tuple)): + args = args[:-1] + else: + cnf = {} + return self.tk.getint(self.tk.call( + self._w, 'create', itemType, + *(args + self._options(cnf, kw)))) + + def delete(self, *args): + """Delete items identified by all tag or ids contained in ARGS.""" + self.tk.call((self._w, 'delete') + args) + + def itemcget(self, tagOrId, option): + """Return the resource value for an OPTION for item TAGORID.""" + return self.tk.call( + (self._w, 'itemcget') + (tagOrId, '-'+option)) + + def tag_lower(self, *args): + """Lower an item TAGORID given in ARGS + (optional below another item).""" + self.tk.call((self._w, 'lower') + args) + + def tag_raise(self, *args): + """Raise an item TAGORID given in ARGS + (optional above another item).""" + self.tk.call((self._w, 'raise') + args) + + def rounded_rect(self, ags=(), *args, **kw): + 'Internal function.' + x, y, w, h, c = _agsmerge((ags, args)) + ids = [] + cnf = dict(kw) + for i in ('extent', 'start', 'style'): + cnf.pop(i, None) + for i in ('joinstyle', 'smooth', 'slinesteps'): + kw.pop(i, None) + points = ( # Arc points:- + (x, y, x+2*c, y+2*c), + (x, y+h-2*c, x+2*c, y+h), + (x+w-2*c, y+h-2*c, x+w, y+h), + (x+w-2*c, y, x+w, y+2*c), + # Polygon points:- + (x+c, y, x+w-c, y), + (x+c, y+h, x+w-c, y+h), + (x, y+c, x, y+h-c), + (x+w, y+c, x+w, y+h-c)) + + for i in range(len(points)): + if i <= 3: + kw['start'] = 90*(i+1) + ids.append(self._create('arc', points[i], kw)) + else: + ids.append(self._create('polygon', points[i], cnf)) + return tuple(ids) + + def _rounded(self, ags=(), *args, **kw): + x1, y1, x2, y2, c = _agsmerge((ags, args)) + ids = [] + points = ( # Arc points:- + (x2-c-1, y1, x2-1, y1+c), + (x1, y1, x1+c, y1+c), + (x1, y2-c-1, x1+c, y2-1), + (x2-c, y2-c, x2-1, y2-1), + # Rectangle points:- + (x1+c/2, y1, x2-c/2, y2), + (x1, y1+c/2, x2, y2-c/2)) + + kw['start'], kw['outline'] = 0, '' + for i in range(len(points)): + if i <= 3: + ids.append(self._create('arc', points[i], kw)) + kw['start'] += 90 + else: + kw.pop('start', None) + kw['width'] = 0 + ids.append(self._create('rectangle', points[i], kw)) + return tuple(ids) + + +class _BaseWidget(_Canvas): + """Internal class.\n + Settings class for Widgets. Do not call directly.""" + + _buttons = [] # list of all buttons + _features = ('overbackground', 'overforeground', 'activeimage', 'activebitmap', + 'anchor', 'bitmap', 'bordercolor', 'borderless', 'command', 'compound', + 'disabledforeground', 'justify', 'disabledbackground', 'fg', 'font', + 'foreground', 'height', 'image', 'overrelief', 'padx', 'pady', 'repeatdelay', + 'repeatinterval', 'text', 'textvariable', 'underline', 'width', 'state', + 'focusthickness', 'focuscolor', 'highlightbackground', 'activebackground', + 'activeforeground') + + def _mouse_state_condition(self): + """Internal function.\n + True if state is normal and cursor is on the widget.""" + con1 = bool(self.cnf.get('state') not in 'disabled') + con2 = bool(self.winfo_containing(*self.winfo_pointerxy()) == self) + return con1 and con2 + + def _make_dictionaries(self, cnf={}, kw={}): + """Internal function.\n + Merges kw into cnf and removes None values.""" + kw['bordercolor'] = kw['highlightbackground'] = kw.get('bordercolor', kw.get('highlightbackground')) + kw = {k: v for k, v in kw.items() if v is not None} + cnf.update(kw) + cnf = {k: v for k, v in cnf.items() if v is not None} + cnf['fg'] = cnf['foreground'] = kw.get('foreground', kw.get('fg', cnf.get('fg', 'black'))) + if cnf.get('textvariable', '') != '': + cnf['text'] = cnf['textvariable'].get() + if kw.get('activebackground'): + cnf.pop('activebackground') + return cnf, kw + + def _set_trace(self, kw): + """Internal function.""" + for i in ('overforeground', 'foreground', 'fg', 'activeforeground'): + if isinstance(kw.get(i), _tk.Variable): + var = kw[i] + cbname = var.trace_variable('w', lambda a, b, c, i=i, var=var, + cls=self: cls.config({i: var.get()})) + if (self, i) in tkv._all_traces_colorvar: + v, cb = tkv._all_traces_colorvar.get((self, i)) + v.trace_vdelete('w', cb) + tkv._all_traces_colorvar[(self, i)] = (var, cbname) + else: + tkv._all_traces_colorvar[(self, i)] = (var, cbname) + kw[i] = var.get() + return kw + + def _create_items(self, cmd, safe_create=False, **kw): + """Internal function.\n + Checks and creates (text, image, bitmap, border, + bordercolor, takefocus ring*) items.""" + + def check_tag(tag): + """Internal function.\n + If `cmd="check"` and the tag does not exist then + the tag is created, but if `cmd="create"` and + safe_create=True this will delete the tag if exists + and creates a new tag with same settings.""" + if cmd == 'check': + c = False + elif cmd == 'create': + c = True + else: + raise ValueError( + '%s is not a valid command! Takes -create, -check' % cmd) + cond1 = bool(not self.find('withtag', tag) or c) + cond2 = bool(tag not in kw.get('avoid', [])) + if safe_create and cond1 and cond2: + self.delete(tag) + return cond1 and cond2 + + ids = [] + cond_image = bool(self.cnf.get('image', + self.cnf.get('activeimage', '') != '')) + cond_bitmap = bool(self.cnf.get('bitmap', + self.cnf.get('activebitmap', '') != '')) + + # Text item. + if check_tag('_txt') and self.cnf.get('text'): + fill = self.cnf.get('foreground', 'black') + if self['state'] in ('pressed', 'active'): + fill = self.cnf.get('activeforeground', 'white') + ids.append(self._create('text', (0, 0), { + 'text': None, 'tag': '_txt', 'fill': fill})) + + # Image item (image and activeimage). + if check_tag('_img') and cond_image: + img = self.cnf.get('image', '') + if self['state'] in ('pressed', 'active'): + img = self.cnf.get('activeimage', img) + ids.append(self._create('image', (0, 0), { + 'image': None, 'tag': '_img', 'image': img})) + + # Bitmap items (bitmap and activebitmap). + if check_tag('_bit') and cond_bitmap: + bit = self.cnf.get('image', '') + if self['state'] in ('pressed', 'active'): + bit = self.cnf.get('activeimage', bit) + ids.append(self._create('bitmap', (0, 0), { + 'bitmap': None, 'tag': '_bit', 'bitmap': bit})) + + # Border color item. + if check_tag('_bd_color'): + bd_color = self.cnf.get('bordercolor', get_shade(self['bg'], 0.04, 'auto-120')) + if self._type == 'circle': + pad = 2 + width = r = int(self.cnf.get('width', 87)/2) # radius = x = y (in pixels) + _bd_points = (pad-width, pad-width, r*2+width-pad, r*2+width-pad) + kw_bd_color = { + 'tag': '_bd_color', + 'state': 'hidden' if self['state'] in ('pressed', 'active') else 'normal', + 'width': width*2,'outline': bd_color} + ids.append(self._create('oval', _bd_points, kw_bd_color)) + else: + _bd_points = (0, -1, self.cnf.get('width', 87), self.cnf.get('height', 24)+3, 7) # was 6 + ids.append(self.rounded_rect( + _bd_points, width=6, tag='_bd_color', style='arc', outline=bd_color)) + + # Border item. + if check_tag('_border'): + bo_color = get_shade(self['bg'], 0.1, 'auto-120') + if self._type == 'circle': + pad = 2 + r = int(self.cnf.get('width', 87)/2) # radius = x = y + _bo_points = (pad, pad, r*2-(pad+1), r*2-(pad+1)) + ids.append(self._create('oval', _bo_points, { + 'tag': '_border', 'outline': self.cnf.get('bordercolor', bo_color) })) + else: + h = 4 + if int(self['highlightthickness']): + h += 1 + _bo_points = (2, 2, self.cnf.get('width', 87)-5, self.cnf.get('height', 24)-h, 4) # was 3 + ids.append(self.rounded_rect( + _bo_points, width=1, outline=bo_color, smooth=1, tag='_border', style='arc', + state='hidden' if self['state'] in ('pressed', 'active') else 'normal')) + + # Takefocus highlight ring. + if check_tag('_tf'): + if self._type == 'circle': + pad = 1 + width = self.cnf.get('focusthickness', 2) + r = int(self.cnf.get('width', 87)/2) # radius = x = y + _tk_points = (pad+width, pad+width, r*2-width-pad, r*2-width-pad) + ids.append(self._create('oval', _tk_points, { + 'tag': '_tf', 'width': width, 'outline': self.cnf.get('focuscolor', '#81b3f4')})) + else: + # takefocuswidth can be changed. + # Focus line is not on point the line is off when thickness is changed. + s = w = self.cnf.get('focusthickness', 2) + if s == 1: + s = 2 + diff2 = (int(self['highlightthickness'])*2) + (s*2) + diff1 = diff2 + 1 + else: + diff1 = diff2 = (int(self['highlightthickness'])*2) + (s*2) + if diff2 == (s*2): + diff2 -= 1 + + _tk_points = (s+int(self['highlightthickness']), + s+int(self['highlightthickness']), + self.cnf.get('width', 87)-diff1, + self.cnf.get('height', 24)-diff2, 4) + ids.append(self.rounded_rect( + _tk_points, width=w, style='arc', + outline=self.cnf.get('focuscolor', '#81b3f4'), + tag='_tf', state='hidden')) + return tuple(ids) + + def _get_functions(self, cmds, kw={}): + """Internal function.\n + Contains all the required functions.""" + + def _borderless_support(cnf={}, **kw): + _return = self.master._configure('configure', cnf, kw) + kw = _cnfmerge((cnf, kw)) + if kw.get('bg') or kw.get('background'): + for i in self._buttons: + if i['borderless']: + i.cnf['highlightbackground'] = i.cnf['bordercolor'] = i.master['bg'] + _Canvas._configure(i, ('itemconfigure', '_bd_color'), { + 'outline': i.master['bg']}, None) + _Canvas._configure(i, 'configure', { + 'highlightbackground': i.master['bg']}, None) + return _return + + def if_state(fn): + """Runs function only if state is normal.""" + def wrapper(*a, **k): + if self.cnf.get('state') not in 'disabled': + return fn(*a, **k) + return wrapper + + def over_img_bit(seq, key): + """Resets coords for text with overimage/overbitmap.""" + if self.cnf['state'] in ('active', 'pressed'): + return + tag = '_img' if key == 'image' else '_bit' + if seq == 'enter': + _Canvas._configure(self, ('itemconfigure', tag), + {key: kw.get('over'+key)}, None) + elif seq == 'leave': + _Canvas._configure(self, ('itemconfigure', tag), + {key: self.cnf.get(key, '')}, None) + self._set_coords(self._get_options( + ('_txt', '_img', '_bit'), self.cnf)) + + def overbg(seq): + """Implement overbackground properly.""" + if seq == 'enter': + _Canvas._configure(self, 'configure', {'bg': kw.get('overbackground')}, None) + _Canvas._configure(self, ('itemconfigure', '_border'), + {'outline': get_shade(kw.get('overbackground'), 0.1, 'auto-120')}, None) + elif seq == 'leave': + self._configure('configure', {'bg': self._org_bg}, None) + + def overfg(seq): + """Handle overforeground, + foreground and activeforeground.""" + if self.cnf['state'] in ('active', 'pressed'): + return + if seq == 'enter': + _Canvas._configure(self, ('itemconfigure', '_txt'), + {'fill': self.cnf.get('overforeground', 'black')}, None) + elif seq == 'leave': + _Canvas._configure(self, ('itemconfigure', '_txt'), + {'fill': self.cnf.get('fg', 'black')}, None) + + binds = { + 'overrelief': {'': if_state(lambda _: self._configure( + 'configure', {'_relief': kw.get('overrelief')})), + '': if_state(lambda _: self._configure( + 'configure', {'_relief': self._rel[0]}))}, + + # [issue-3] doesn't change if overrelief is on. + # [issue-3] (FIXED) using after with 0ms delay fixes the issue. To be safe delay is 1ms. + 'overforeground': {'': if_state(lambda _: self.after(1, overfg, 'enter')), + '': if_state(lambda _: overfg('leave'))}, + + 'overbackground': {'': if_state(lambda _: overbg('enter')), + '': if_state(lambda _: overbg('leave')) }, + + 'overimage': {'': if_state(lambda _: over_img_bit('enter', 'image')), + '': if_state(lambda _: over_img_bit('leave', 'image'))}, + + 'overbitmap': {'': if_state(lambda _: over_img_bit('enter', 'bitmap')), + '': if_state(lambda _: over_img_bit('leave', 'bitmap'))}, + + 'takefocus': {'': lambda _: _Canvas._configure(self, ( + 'itemconfigure', '_tf'), {'state': 'normal'}, None), + '': lambda _: _Canvas._configure(self, ( + 'itemconfigure', '_tf'), {'state': 'hidden'}, None)}, + } + + other_functions = { + 'borderless': _borderless_support, + + 'textvariable': {'w': lambda *a: _Canvas._configure(self, ( + 'itemconfigure', '_txt'), {'text': self.cnf.get( + 'textvariable').get()}, None)}, + + '_activebg': lambda: _on_press_color(*self._get_options( + 'activebackground', self.cnf)), + } + funcs = _cnfmerge((binds, other_functions)) + if isinstance(cmds, (list, tuple)): + value = funcs.copy() + for i in cmds: + value = value.get(i, {}) + return value + else: + return funcs.get(cmds, {}) + + def _get_options(self, cmd, cnf={}, **kw): + """Internal function.""" + kw = _cnfmerge((cnf, kw)) + # If more than one commands are given. + # returns Union[list, dict, tuple(list, dict)] + if isinstance(cmd, (list, tuple)): + ags, cnf = [], {} + for i in cmd: + opts = self._get_options(i, kw) + if isinstance(opts, (list, tuple)): + ags.append(opts) + elif isinstance(opts, dict): + cnf.update(opts) + if isinstance(opts, (list, tuple)) and isinstance(opts, dict): + return ags, cnf + return ags or cnf + + elif cmd == 'activebackground': + color = None + if kw.get(cmd, '') != '': + color = kw.get(cmd, self.cnf.get(cmd)) + return [self, {'tag': '_activebg', + 'width': self.winfo_width(), + 'height': self.winfo_height(), + 'color': color}] + + elif cmd == 'activeforeground': + if self['state'] in ('pressed', 'active'): + return (('itemconfigure', '_txt'), { + 'fill': self.cnf.get(cmd, 'white')}, None) + + elif cmd == 'activebitmap' or cmd == 'activeimage': + tag = '_bit' if cmd == 'activebitmap' else '_img' + name = cmd.rsplit('active')[-1] + if self['state'] in ('pressed', 'active'): + return (('itemconfigure', tag), { + name: self.cnf.get(cmd, '')}, None) + + elif cmd == 'bitmap' or cmd == 'image': + tag = '_bit' if cmd == 'bitmap' else '_img' + if kw.get(cmd, '') != '': + state = 'disabled' if self.cnf.get('state') in ( + 'disabled', 'disable') else 'normal' + cnf = dict(state=state, **{i: kw.get(i, self.cnf.get(i)) + for i in ('anchor', cmd)}) + return (('itemconfigure', tag), cnf, None) + elif kw.get(cmd) == '': + self.delete(tag) + + elif cmd == 'borderless' or cmd == 'bordercolor' or cmd == 'highlightbackground': + cnf = {} + if bool(kw.get('borderless')) or self.cnf.get('borderless'): + if not check_function_equality(self.master.config, self._get_functions('borderless', kw)): + self.master.config = self.master.configure = self._get_functions( + 'borderless', kw) + self.cnf['highlightbackground'] = self.cnf['bordercolor'] = self.master['bg'] + cnf[1] = [('itemconfigure', '_bd_color'), {'outline': self.master['bg']}, None] + cnf[2] = ['configure', {'highlightbackground': self.master['bg']}, None] + return cnf + elif not bool(kw.get('borderless', True)) or not self.cnf.get('borderless'): + if self.cnf.get('bordercolor') == self.master['bg']: + self.cnf.pop('bordercolor', None) + self.cnf.pop('highlightbackground', None) + bd_color = self.cnf.get( + 'bordercolor', get_shade(self['bg'], 0.04, 'auto-120')) + if bd_color == '': + bd_color = get_shade(self['bg'], 0.04, 'auto-120') + elif bd_color.lower() == 'default': + bd_color = get_shade('white', 0.04, 'auto-120') + self.cnf.update({'bordercolor': bd_color}) + self.cnf['highlightbackground'] = self.cnf['bordercolor'] = bd_color + if self.itemcget('_bd_color', 'outline') != bd_color: + cnf[1] = [('itemconfigure', '_bd_color'), {'outline': bd_color}, None] + cnf[2] = ['configure', {'highlightbackground': bd_color}, None] + return cnf + + elif cmd == 'foreground' or cmd == 'fg': + return (('itemconfigure', '_txt'), + {'fill': kw.get(cmd, self.cnf.get(cmd))}, None) + + elif cmd == 'focuscolor': + return (('itemconfigure', '_tf'), + {'outline': kw.get(cmd, self.cnf.get(cmd))}, None) + + elif cmd == 'focusthickness': + self._create_items('create', True, avoid=( + '_txt', '_img', '_bit', '_bd_color', '_border')) + + elif cmd == 'overbackground': + cnf = [] + if kw.get(cmd, '') != '': + fn = self._get_functions(cmd, kw) + cnf = [self, + {'className': cmd, 'sequence': '', + 'func': fn.get('')}, + {'className': cmd, 'sequence': '', + 'func': fn.get('')}] + if self._mouse_state_condition(): + _Canvas._configure(self, 'configure', {'bg': kw.get('overbackground')}, None) + _Canvas._configure(self, ('itemconfigure', '_border'), + {'outline': get_shade(kw.get('overbackground'), 0.1, 'auto-120')}, None) + elif kw.get(cmd) == '': + cnf = [self, + {'className': cmd, 'sequence': ''}, + {'className': cmd, 'sequence': ''}, + ('configure', {'bg': self._org_bg}, None), + (('itemconfigure', '_border'), {'outline': + get_shade(self._org_bg, 0.1, 'auto-120')}, None)] + return cnf + + elif cmd == 'overforeground': + cnf = [] + if kw.get(cmd, '') != '': + fn = self._get_functions(cmd, kw) + cnf = [self, + {'className': cmd, 'sequence': '', + 'func': fn.get('')}, + {'className': cmd, 'sequence': '', + 'func': fn.get('')}] + if self._mouse_state_condition(): + # [issue-4] doesn't change if overrelief is on. + # [issue-4] (FIXED) using after with 0ms delay + # fixes the issue. To be safe delay is 1ms. + self.after(1, lambda: _Canvas._configure(self, + ('itemconfigure', '_txt'), {'fill': kw.get(cmd)}, None)) + elif kw.get(cmd) == '': + cnf = [self, + {'className': cmd, 'sequence': ''}, + {'className': cmd, 'sequence': ''}] + cnf.append((('itemconfigure', '_txt'), + {'fill': self.cnf.get('fg', 'black')}, None)) + return cnf + + elif cmd == 'overrelief': + if kw.get(cmd, '') != '': + if not self._rel[1]: + self._rel = ('flat', False) + if self._mouse_state_condition(): + self._configure( + 'configure', {'_relief': kw.get(cmd)}, None) + fn = self._get_functions(cmd, kw) + return (self, + {'className': cmd, 'sequence': '', + 'func': fn.get('')}, + {'className': cmd, 'sequence': '', + 'func': fn.get('')}) + + elif kw.get(cmd) == '': + return (self, + {'className': cmd, 'sequence': ''}, + {'className': cmd, 'sequence': ''}, + ('configure', {'relief': self._rel[0]}, None)) + + elif cmd == 'size': + cnf = {} + for i in ('text', 'font', 'textvariable', 'image', 'bitmap', + 'compound', 'padx', 'pady', 'activeimage', + 'activebitmap'): # 'width', 'height'}: + if self.cnf.get(i, '') != '': + if i == 'activeimage': + cnf['image'] = self.cnf[i] + elif i == 'activebitmap': + cnf['bitmap'] = self.cnf[i] + elif i in ('width', 'height'): + cnf[i] = str(self.cnf[i]) + 'c' + else: + cnf[i] = self.cnf.get(i) + if self._type == 'circle' and kw.get('radius'): + self.cnf['width'] = self.cnf['height'] = kw.get( + 'width', kw.get('height', int(kw['radius']*2))) + if self._fixed_size['w'] and kw.get('width', True): + kw['width'] = self.cnf['width'] + if self._fixed_size['h'] and kw.get('height', True): + kw['height'] = self.cnf['height'] + self._fixed_size['w'] = True if kw.get('width', kw.get('radius')) else False + self._fixed_size['h'] = True if kw.get('height', kw.get('radius')) else False + W, H = _info_button(self, **cnf) + self.cnf['width'] = self.cnf.get('width') if self._fixed_size['w'] else W + self.cnf['height'] = self.cnf.get('height') if self._fixed_size['h'] else H + return ('configure', {'width': self.cnf['width'], + 'height': self.cnf['height']}, None) + + elif cmd == 'state': + cnf = {} + if kw.get(cmd) in 'disabled': + cnf[1] = ('configure', {'bg': self.cnf.get('disabledbackground'), + 'state': 'disabled'}, None) + cnf[2] = (('itemconfigure', '_txt'), { + 'disabledfill': kw.get('disabledforeground', + self.cnf.get('disabledforeground')), + 'state': 'disabled'}, None) + cnf[3] = (('itemconfigure', '_activebg'), {'state': 'hidden'}, None) + cnf[5] = (('itemconfigure', '_img'), {'state': 'disabled'}, None) + cnf[6] = (('itemconfigure', '_bit'), {'state': 'disabled'}, None) + elif kw.get(cmd) == 'normal': + _bg = self._org_bg + if self._mouse_state_condition() and self.cnf.get('overbackground'): + _bg = self.cnf['overbackground'] + cnf[1] = ('configure', {'bg': _bg, 'state': 'normal'}, None) + cnf[2] = (('itemconfigure', '_txt'), {'state': 'normal', + 'fill': self.cnf.get('foreground', 'black')}, None) + cnf[3] = (('itemconfigure', '_activebg'), {'state': 'hidden'}, None) + cnf[4] = (('itemconfigure', '_border'), {'state': 'normal'}, None) + cnf[5] = (('itemconfigure', '_img'), { + 'image': self.cnf.get('image', '')}, None) + cnf[6] = (('itemconfigure', '_bit'), { + 'bitmap': self.cnf.get('bitmap', '')}, None) + elif kw.get(cmd) in ('active', 'pressed'): + _bg = self._org_bg + if self._mouse_state_condition() and self.cnf.get('overbackground'): + _bg = self.cnf['overbackground'] + cnf[1] = ('configure', {'bg': _bg, 'state': 'normal'}, None) + cnf[2] = (('itemconfigure', '_txt'), {'state': 'normal', + 'fill': self.cnf.get('activeforeground', 'white')}, None) + cnf[3] = (('itemconfigure', '_activebg'), {'state': 'normal'}, None) + cnf[4] = (('itemconfigure', '_border'), {'state': 'hidden'}, None) + cnf[5] = (('itemconfigure', '_img'), { + 'image': self.cnf.get('activeimage', '')}, None) + cnf[6] = (('itemconfigure', '_bit'), { + 'bitmap': self.cnf.get('activebitmap', '')}, None) + return cnf + + elif cmd == 'takefocus': + if int(self[cmd]) and self['state'] in ('normal', 'active', 'pressed'): + fn = self._get_functions(cmd, kw) + return [self, {'className': cmd, 'sequence': + '', 'func': fn.get('')}, + {'className': cmd, 'sequence': + '', 'func': fn.get('')}] + + elif not int(self[cmd]) or self['state'] in 'disabled': + return [self, + {'className': cmd, 'sequence': ''}, + {'className': cmd, 'sequence': ''}, + (('itemconfigure', '_tf'), {'state': 'hidden'}, None)] + + elif cmd == 'text': + if kw.get('textvariable', '') != '': + kw['text'] = self.cnf['text'] = self.cnf['textvariable'].get() + cbn = self.cnf['textvariable'].trace_variable('w', + self._get_functions('textvariable', kw).get('w')) + self._var_cb = (self.cnf['textvariable'], cbn) + + elif kw.get('textvariable') == '' and self._var_cb: + kw['text'] = self.cnf['text'] = self._var_cb[0].get() + self.cnf.pop('textvariable', None) + self._var_cb[0].trace_vdelete('w', self._var_cb[1]) + self._var_cb = [] + + state = 'disabled' if self.cnf.get('state') in ( + 'disabled', 'disable') else 'normal' + fill = kw.get('fg', self.cnf.get('fg')) + if self['state'] in ('pressed', 'active'): + fill = self.cnf.get('activeforeground', 'white') + cnf = dict(state=state, fill=fill, + disabledfill=kw.get( + 'disabledforeground', self.cnf.get('disabledforeground')), + **{i: kw.get(i, self.cnf.get(i)) for i in ('text', 'anchor', 'font', 'justify')}) + return (('itemconfigure', '_txt'), cnf, None) + + elif cmd == '_border': + return [('itemconfigure', '_border'), + {'outline': get_shade(self['bg'], 0.1, 'auto-120')}, None] + + elif cmd == '_txt' or cmd == '_img' or cmd == '_bit': + if not self.cnf.get('width'): + self.cnf['width'] = self.winfo_width() + if not self.cnf.get('height'): + self.cnf['height'] = self.winfo_height() + r1 = self._compound(self.cnf.get('compound'), self.cnf.get('width'), self.cnf.get('height')) + r2 = {cmd: ((self.cnf['width']/2), self.cnf['height']/2)} + return r1 or r2 + + def _set_coords(self, cnf={}, **kw): + """Internal function.\n + Set Coordinates of the items.""" + kw = _cnfmerge((cnf, kw)) + return [self.coords(tag, *kw[tag]) for tag in + ('_txt', '_img', '_bit', '_bd_color', + '_border', '_tf') if kw.get(tag)] + + def _set_configure(self, options): + """Internal function.\n + Configures and binds according to the given options.""" + if not options: + return + if isinstance(options, dict): + return [self._set_configure(i) for i in options.values()] + if len(options) > 1 and isinstance(options[1], dict): + con1 = bool(isinstance(options[0], tuple)) + if options[0] == 'configure' or (con1 and len(options[0]) > 1): + # itemconfigure and configure + _Canvas._configure(self, *options) + if isinstance(options[0], _tk.Misc): + if isinstance(options[-1], (tuple, list)): + # binds, itemconfigure and configure + binds, conf = options[:-2], options[-1] + _Canvas._configure(self, *conf) + return _bind(*binds) + elif options[1].get('tag') == '_activebg': + # _on_press_color + return _on_press_color(*options) + else: # binds + return _bind(*options) + + def _configure1(self, cnf={}, **kw): + """Internal Function. + This function configure all the resources of + the Widget and save it to the class dict.""" + self.cnf, kw = self._make_dictionaries(self.cnf, + self._set_trace(_cnfmerge((cnf, kw)))) + # Checks the items + self._create_items('check') + # >.< + for opt in ('overbackground', 'activebitmap', 'activeimage', + 'bitmap', 'fg', 'bordercolor', 'borderless', 'image', + 'overrelief', 'foreground', 'state', 'overforeground', + 'focuscolor', 'focusthickness', 'activebackground', + 'activeforeground', 'compound'): + if kw.get(opt) is not None: + self._set_configure(self._get_options(opt, kw)) + if (opt == 'state' and kw.get('compound', self.cnf.get('compound'))) \ + or opt == 'compound': + self._set_coords(**self._get_options( + ('_txt', '_img', '_bit'), kw)) + # Size + if bool({'text', 'font', 'textvariable', 'image', 'bitmap', 'compound', + 'padx', 'pady', 'width', 'height', 'activeimage', 'radius', + 'activebitmap',}.intersection(set(kw))): + self._set_configure(self._get_options('size', kw)) + # Text + if {'text', 'anchor', 'font', 'justify', 'textvariable'}.intersection(set(kw)): + self._set_configure(self._get_options('text', kw)) + # Takefocus + cond1 = bool(self.itemcget('_tf', 'state') != 'hidden' and + self['state'] not in 'disabled') + cond2 = bool(self.itemcget('_tf', 'state') != self['state']) + if cond1 or cond2: + self._set_configure(self._get_options('takefocus', kw)) + # Line border: This will darken the border around the button. + if get_shade(self['bg'], 0.1, 'auto-120') != self.itemcget('_border', 'outline'): + self._set_configure(self._get_options('_border', kw)) + + def _focus_in_out(self, intensity): + """Internal function.\n + Focus in and focus out effect maker.""" + main_win = self.winfo_toplevel() + + def _chngIn(evt): + """Internal function.""" + try: + if self.focus_get() is None: + color = get_shade(self['bg'], intensity, 'auto-120') + _Canvas._configure(self, ('itemconfigure', '_border'), + {'outline': color}, None) + c1 = get_shade(self['bg'], intensity, 'auto-120') + c2 = self.itemcget('_border', 'outline') + if self.focus_get() and c1 == c2: + color = get_shade(self['bg'], 0.1, 'auto-120') + _Canvas._configure(self, ('itemconfigure', '_border'), + {'outline': color}, None) + # [issue-8] (Fixed) tkinter issue with combobox (w = w.children[n]) + except KeyError: + pass + + _bind(main_win, + {'className': 'focus%s' % str(self), + 'sequence': '', 'func': _chngIn}, + {'className': 'focus%s' % str(self), + 'sequence': '', 'func': _chngIn}) + return main_win + + def _relief(self, cnf, kw={}): # check + """Internal function.\n + Make overrelief and Relief work together.""" + # [issue-5] (Fixed) overrelief and Relief not working together properly. + kw = _cnfmerge((cnf, kw)) + cond1 = kw.get('overrelief', self.cnf.get('overrelief', '')) != '' + if kw.get('relief') is not None and cond1: + self._rel = (kw['relief'], True) + if kw.get('_relief') is not None and kw.get('relief') is not None: + self._rel = (kw['relief'], True) + kw.pop('_relief', None) + elif kw.get('_relief'): + kw['relief'] = kw.pop('_relief') + return kw + + def _set_size(self, evt=None): + """Internal function.\n + This will resize everything that is in the button""" + if evt.width == self._size[0] and evt.height == self._size[1]: + return + # [issue-8] On resizing the window the circlebutton doesn't resize properly, + # current fix doesn't work properly. + if self._type == 'circle' and evt.width != evt.height: + if evt.width > evt.height: + evt.width = evt.height + else: + evt.height = evt.width + return + self._size = (self.cnf['width'], self.cnf['height']) = (evt.width, evt.height) + self.delete('_activebg') + # [issue-6] (Fixed) Need fix (laggy on resizing) --> workaround: cancel if still resizing + for i in self._after_IDs: + self.after_cancel(self._after_IDs[i]) + self._create_items('create', avoid=( + '_txt', '_img', '_bit'), safe_create=True) + self._set_coords(self._get_options(('_txt', '_img', '_bit'), self.cnf)) + self._after_IDs[1] = self.after(1, self._get_functions('_activebg')) + for t in ('_txt', '_img', '_bit', '_bd_color', '_border', '_tf'): + self.tag_raise(t) + self._after_IDs[2] = self.after(1, self._configure1) + cur_focus = self.master.focus_get() + if cur_focus and self.master != cur_focus and cur_focus != self: + cur_focus.focus() + else: + self.master.focus() + # [issue-7] Need a better fix to get focus back to the button if it has it previously + if cur_focus == self: + # [issue-7] (fixed) using after with 1ms delay solves the issue. + self._after_IDs[3] = self.after(1, self.focus) + + def _active(self, value): + """Internal function.\n + Do not call directly. Changes appearance when active.""" + + if value in ('on_press', 'on_enter') or value == True: + if self['state'] != 'pressed' : + if value == 'on_press': + self.cnf['_state'] = self.cnf['state'] + self.cnf['state'] = 'active' + _Canvas._configure(self, ('itemconfigure', '_activebg'), + {'state': 'normal'}, None) + _Canvas._configure(self, ('itemconfigure', '_border'), + {'state': 'hidden'}, None) + _Canvas._configure(self, ('itemconfigure', '_txt'), + {'fill': self.cnf.get('activeforeground', 'white')}, None) + _Canvas._configure(self, ('itemconfigure', '_img'), + {'image': self.cnf.get('activeimage', self.cnf.get('image', ''))}, None) + _Canvas._configure(self, ('itemconfigure', '_bit'), + {'bitmap': self.cnf.get('activebitmap', self.cnf.get('bitmap', ''))}, None) + + elif value in ('on_leave', 'on_release') or value == False: # When not active (False) + if self['state'] != 'pressed': + if self.cnf.get('_state'): + self.cnf['state'] = self.cnf['_state'] + if value == 'on_release': + self.cnf.pop('_state', None) + if self['state'] == 'active': self['state'] = 'normal' + _Canvas._configure(self, ('itemconfigure', '_activebg'), + {'state': 'hidden'}, None) + _Canvas._configure(self, ('itemconfigure', '_border'), + {'state': 'normal'}, None) + fill = self.cnf.get('fg', 'black') + if self._mouse_state_condition() and self.cnf.get('overforeground'): + fill = self.cnf['overforeground'] + _Canvas._configure(self, ('itemconfigure', '_txt'), + {'fill': fill}, None) + _Canvas._configure(self, ('itemconfigure', '_img'), + {'image': self.cnf.get('image', '')}, None) + _Canvas._configure(self, ('itemconfigure', '_bit'), + {'bitmap': self.cnf.get('bitmap', '')}, None) + # if self.cnf.get('compound'): + self._set_coords(self._get_options( + ('_txt', '_img', '_bit'), self.cnf)) + + def _on_press(self, *ags): + """Internal function. When button is pressed """ + self._rpin = None + self._rpinloop = True + + def cmd(*a): + """trigger function callback.""" + _bind(self, className='button_command', sequence='') + if self.cnf.get('repeatdelay', 0) and self.cnf.get('repeatinterval', 0) and self._rpinloop: + self._rpin = self.after(self.cnf.get('repeatinterval', 0), cmd) + self.cnf['command']() if self.cnf.get('command') else None + + def on_enter(*a): + """Internal function.\n + Enables when pressed and cursor + is moved back on button.""" + self._active('on_enter') + if self.cnf.get('repeatdelay', 0) and self.cnf.get('repeatinterval', 0): + self._rpinloop = True + cmd() + _bind(self, className='button_command', sequence='', + func=lambda *a: self.after(0, cmd), add='+') + + def on_leave(*a): + """Internal function.\n + Disables/Cancels when pressed and cursor + is moved away from the button.""" + self._active('on_leave') + if self.cnf.get('repeatdelay', 0) and self.cnf.get('repeatinterval', 1): + self._rpinloop = False + self.after_cancel(self._rpin) + _bind(self, className='button_command', sequence='') + + if self['state'] not in 'disabled': + self.focus_set() + self._active('on_press') + if self.cnf.get('repeatdelay', 0) and self.cnf.get('repeatinterval', 1): + self._rpin = self.after(self.cnf.get('repeatdelay', 0), cmd) + _bind(self, {'className': 'on_press_enter', 'sequence': '', 'func': on_enter}, + {'className': 'on_press_leave', 'sequence': '', 'func': on_leave}, + {'className': 'button_command', 'sequence': '', 'func': cmd}) + + def _on_release(self, *ags): + """Internal function. When button is released """ + if self['state'] in 'disabled': return + self._active('on_release') + self._rpinloop = False + if getattr(self, '_rpin', None): self.after_cancel(self._rpin) + _bind(self, {'className': 'on_press_enter', 'sequence': ''}, + {'className': 'on_press_leave', 'sequence': ''}, + {'className': 'button_command', 'sequence': ''}) + + def _compound(self, flag, width, height): + """Internal function.\n + Use `compound = 'left'/'right'/'top'/'bottom'` to configure.""" + # width -= 2 + _PiTag = '' + if self.cnf.get('image', self.cnf.get('activeimage')): + _PiTag = '_img' + elif self.cnf.get('bitmap', self.cnf.get('activebitmap')): + _PiTag = '_bit' + _im_size = self.bbox(_PiTag) + _txt_size = self.bbox('_txt') + if _im_size and _txt_size: + W_im = _im_size[2] - _im_size[0] + H_im = _im_size[3] - _im_size[1] + W_txt = _txt_size[2] - _txt_size[0] + H_txt = _txt_size[3] - _txt_size[1] + if flag == 'bottom': + width = (width/2, width/2) + height = (height/2-H_im/2, height/2+H_txt/2) + elif flag == 'top': + width = (width/2, width/2) + height = (height/2+H_im/2, height/2-H_txt/2) + elif flag == 'right': + width = (width/2-W_im/2, width/2+W_txt/2) + height = (height/2, height/2) + elif flag == 'left': + width = (width/2+W_im/2, width/2-W_txt/2) + height = (height/2, height/2) + elif flag is not None: + raise _tk.TclError('bad compound flag "{}", must be \ + -none, -top, -bottom, -left, or -right'.format(flag)) + if isinstance(height, tuple): + if _im_size is None: + return {'_txt': (width[0], height[0])} + return {'_txt': (width[0], height[0]), + _PiTag: (width[1], height[1])} + return None + + +class ButtonBase(_BaseWidget): + """Internal class used for tkinter macos Buttton""" + + def __init__(self, _type=None, master=None, cnf={}, **kw): + kw = self._set_trace(_cnfmerge((cnf, kw))) + kw = {k: v for k, v in kw.items() if v is not None} + self._type = _type # button type (circle, normal) + self._after_IDs = {} # _after_IDs + self._fixed_size = {'w': False, 'h': False} + self._var_cb = None + self.cnf = {} + for i in kw.copy().keys(): + if i in self._features: + self.cnf[i] = kw.pop(i, None) + + self.cnf['fg'] = self.cnf['foreground'] = self.cnf.get('fg', self.cnf.get('foreground', 'black')) + self.cnf['anchor'] = self.cnf.get('anchor', 'center') + self.cnf['borderless'] = self.cnf.get('borderless', False) + self.cnf['disabledforeground'] = self.cnf.get('disabledforeground', 'grey') + self.cnf['state'] = self.cnf.get('state', 'normal') + self.cnf['activeforeground'] = self.cnf.get('activeforeground', 'white') + + if self._type == 'circle': + self.cnf['radius'] = int(kw.pop('radius', 35)) + ra = int(self.cnf['radius']*2 + 4) + kw['width'] = kw['height'] = kw.get('width', kw.get('height', ra)) + else: + kw['width'] = kw.get('width', 87) + kw['height'] = kw.get('height', 24) + + kw['takefocus'] = kw.get('takefocus', 1) + kw['bg'] = kw.pop('bg', kw.pop('background', 'white')) + kw['highlightthickness'] = kw.get('highlightthickness', 0) + + _Canvas.__init__(self, master=master, **kw) + self.cnf['bordercolor'] = self.cnf['highlightbackground'] = self.cnf.get( + 'bordercolor', self.cnf.get('highlightbackground', get_shade(self['bg'], 0.04, 'auto-120'))) + + self._buttons.append(self) + self._size = (self.winfo_width(), self.winfo_height()) + self._create_items('create', safe_create=True) + self._org_bg = self['bg'] + if kw.get('relief') is not None: + self._rel = (kw['relief'], True) + else: + self._rel = ('flat', False) + + _bind(self, + {'className': 'button_release', + 'sequence': '', 'func': self._on_release}, + {'className': 'button_press', + 'sequence': '', 'func': self._on_press}, + {'className': 'set_size', 'sequence': '', 'func': self._set_size}) + + self._focus_in_out(0.04) + self._configure1(self.cnf) + + def _configure(self, cmd, cnf=None, kw=None): + 'Internal function to configure the inherited class' + kw = self._relief(cnf, kw) + cnf = {} + for i in list(kw): + if (i in self._features) or \ + (i == 'radius' and self._type == 'circle'): + cnf[i] = kw.pop(i, None) + _return = _Canvas._configure(self, cmd, None, kw) + if kw.get('bg') or kw.get('background'): + self._org_bg = self['bg'] + self._configure1(cnf) + if _return is not None and isinstance(_return, dict): + _return.update(self.cnf) + return _return + + # @tkv._colorvar_patch_cget + def cget(self, key): + """Return the resource value for a KEY given as string.""" + if key == 'radius' and self._type == 'circle': + return self.cnf.get('radius') + if key in self._features: + return self.cnf.get(key) + else: + return _Canvas.cget(self, key) + __getitem__ = cget + + def keys(self): + """Return a list of all resource names of this widget.""" + _return = _Canvas.keys(self) + _return.extend(self._features) + return sorted(list(set(_return))) + + @tkv._colorvar_patch_destroy + def destroy(self): + """Destroy this and all descendants widgets. This will + end the application of this Tcl interpreter.""" + main_win = self.winfo_toplevel() + _bind(main_win, + {'className': 'focus%s' % str(self), 'sequence': ''}, + {'className': 'focus%s' % str(self), 'sequence': ''}) + if self.cnf.get('textvariable', '') != '': + self.configure(textvariable='') + if self in self._buttons: + self._buttons.remove(self) + return _Canvas.destroy(self) + + +class SFrameBase(_tk.Frame): + """Base Class for SFrame.""" + + _features = ('scrollbarwidth', 'mousewheel', + 'avoidmousewheel', 'canvas', 'scrollbar') + + def __init__(self, master=None, cnf={}, **kw): + kw = _cnfmerge((cnf, kw)) + self.cnf = {} + self._after_ids = {} + self.cnf['scrollbarwidth'] = kw.pop('scrollbarwidth', 10) + self.cnf['mousewheel'] = kw.pop('mousewheel', True) + self.cnf['avoidmousewheel'] = kw.pop('avoidmousewheel', ()) + self.cnf['canvas'] = kw.pop('canvas', _tk.Canvas(master=master, + highlightthickness=0, + width=kw.pop('width', 250), + height=kw.pop('height', 250))) + self.cnf['scrollbar'] = kw.pop('scrollbar', _tk.Scrollbar(self.cnf['canvas'], + orient='vertical', + width=self.cnf['scrollbarwidth'])) + _tk.Frame.__init__(self, self.cnf['canvas'], **kw) + self.cnf['canvas']['bg'] = self['bg'] + self.cnf['scrollbar'].place(relx=1, rely=0, anchor='ne', relheight=1) + self.cnf['scrollbar'].configure(command=self.cnf['canvas'].yview) + self.cnf['canvas'].configure(yscrollcommand=self.cnf['scrollbar'].set) + self.cnf['canvas'].create_window(0, 0, anchor='nw', tags="window", window=self, + width=self.cnf['canvas'].winfo_reqwidth()-\ + self.cnf['scrollbar'].winfo_reqwidth()) + self.cnf['canvas'].bind("", self._configure_height, add="+") + _bind(self, className='configure', + sequence='', func=self._configure_window) + self._mouse_scrolling(self.cnf['mousewheel']) + self._avoid_mousewheel(self.cnf.get('avoidmousewheel')) + self._geometryManager() + + def _avoid_mousewheel(self, widgets): + """Internal function.\n + Use this to have multiple scrollable + widgets inside of SFrame.""" + + def set_widget(wid): + """Internal function.\n + Binds and to the widget + to enable/disable mousewheel scrolling.""" + binds = [{'className': 'mw_state_sframe', 'sequence': + '', 'func': lambda _: self._mouse_scrolling(True)}] + if not isinstance(wid, SFrameBase): + binds.append({'className': 'mw_state_sframe', 'sequence': + '', 'func': lambda _: self._mouse_scrolling(False)}) + _bind(wid, *binds) + + if isinstance(widgets, (list, tuple)): + for widget in widgets: + set_widget(widget) + else: + set_widget(widgets) + + def _mouse_scrolling(self, state): + """Internal function.""" + def enable_mousewheel(evt=None): + """Internal function.""" + self.bind_all('', self._on_mouse_scroll) + + def disable_mousewheel(evt=None): + """Internal function.""" + self.unbind_all('') + + if state: + _bind(self, + {'className': 'mousewheel_state', 'sequence': + '', 'func': enable_mousewheel}, + {'className': 'mousewheel_state', 'sequence': + '', 'func': disable_mousewheel}) + enable_mousewheel() + else: + _bind(self, + {'className': 'mousewheel_state', 'sequence': ''}, + {'className': 'mousewheel_state', 'sequence': ''}) + disable_mousewheel() + + def _on_mouse_scroll(self, evt): + """Internal function.""" + if self.winfo_height() < self.cnf['canvas'].winfo_height(): + return + if evt.state == 0: + self.cnf['canvas'].yview_scroll(-1*delta(evt), 'units') + + def _configure_height(self, evt): + """Internal function.""" + width = self.cnf['canvas'].winfo_width()-self.cnf['scrollbar'].winfo_width() + self.cnf['canvas'].itemconfig('window', width=width) + + def _configure_window(self, evt): + """Internal function.""" + # this will update the position of scrollbar when scrolled from mousewheel. + # fixes some bugs + # makes scrolling more smoother + self.after_cancel(self._after_ids.get(0, ' ')) + self._after_ids[0] = self.after(1, lambda: self.cnf['canvas'].configure( + scrollregion=self.cnf['canvas'].bbox('all'))) + + def _geometryManager(self): + """Internal function.""" + # Use set to support the following in both python 2 and python 3 + geo_methods = [m for m in (set(_tk.Pack.__dict__) | set(_tk.Grid.__dict__) | + set(_tk.Place.__dict__)) if m not in _tk.Frame.__dict__] + for m in geo_methods: + if m[0] != '_' and 'config' not in m: + setattr(self, m, getattr(self.cnf['canvas'], m)) + + def _configure(self, cmd, cnf=None, kw=None): + kw = _tk._cnfmerge((cnf, kw)) + self.cnf['scrollbar']['width'] = kw.pop( + 'scrollbarwidth', self.cnf['scrollbar']['width']) + for key in kw.copy(): + if key in self._features: + self.cnf[key] = kw.pop(key, self.cnf.get(key)) + self.cnf['canvas']['width'] = kw.pop( + 'width', self.cnf['canvas']['width']) + self.cnf['canvas']['height'] = kw.pop( + 'height', self.cnf['canvas']['height']) + self._mouse_scrolling(self.cnf['mousewheel']) + self._avoid_mousewheel(self.cnf['avoidmousewheel']) + _return = _tk.Frame._configure(self, cmd, {}, kw) + if kw.get('bg', kw.get('background')): + self.cnf['canvas']['bg'] = self['bg'] + if isinstance(_return, dict): + _return.update(self.cnf) + return _return + + def cget(self, key): + """Return the resource value for a KEY given as string.""" + if key in self._features: + return self.cnf.get(key) + return _tk.Frame.cget(self, key) + __getitem__ = cget + + +class MarqueeBase(_tk.Canvas): + """Base class for Marquee.""" + + def __init__(self, master=None, cnf={}, **kw): + kw = _cnfmerge((cnf, kw)) + self._stop_state = False + self.cnf = dict( + text=kw.pop('text', ''), + font=kw.pop('font', None), + fg=kw.pop('fg', 'black') if kw.get( + 'fg') else kw.pop('foreground', 'black'), + fps=kw.pop('fps', 30), + left_margin=kw.pop('left_margin', 10), + initial_delay=kw.pop('initial_delay', 1000), + end_delay=kw.pop('end_delay', 1000), + smoothness=kw.pop('smoothness', 1), # 1 <= smooth < 1 + ) + kw['height'] = kw.get('height', 24) + kw['highlightthickness'] = kw.get('highlightthickness', 0) + _tk.Canvas.__init__(self, master=master, **kw) + self._create('text', (3, 1), dict(anchor='w', tag='text', text=self.cnf.get('text'), + font=self.cnf.get('font'), fill=self.cnf.get('fg'))) + _bind(self, className='configure', + sequence='', func=self._check) + self.after_id = ' ' + + def _set_height(self, evt=None): + """Internal function.""" + bbox = self.bbox('text') + height = bbox[3] - bbox[1] + 8 + if int(self['height']) == height: + return + _tk.Canvas._configure(self, 'configure', {'height': height}, None) + + def _reset(self, force_reset=False): + """Internal function.\n + Resets the text position, do not call directly.""" + if self.after_id == ' ' and not force_reset: + return + self.after_cancel(self.after_id) + self.coords('text', 3, self.winfo_height()/2) # RESETS TEXT + self.after_id = ' ' + + def _check(self, evt=None): + """Internal function.\n + Sets the text properly in the frame.""" + self._set_height() + self.coords('text', 3, self.winfo_height()/2) + text_width = self.bbox('text')[2] # TEXT WIDTH + frame_width = self.winfo_width() # FRAME WIDTH + if text_width + 1 < frame_width: + self._reset() + elif self.after_id == ' ': + delay = self.cnf.get('initial_delay') # INITITAL DEALY + self.after_id = self.after(delay, self._animate) + + def _animate(self, evt=None): + """Internal function.\n + Process text and move text.""" + if self._stop_state: return + self._set_height() + text_width = self.bbox('text')[2] # TEXT WIDTH + frame_width = self.winfo_width() # FRAME WIDTH + delay = int(self.cnf.get('smoothness')*1000 / self.cnf.get('fps')) + if text_width + 1 + self.cnf.get('left_margin') < frame_width: + self.after(self.cnf.get('end_delay'), self.coords, + 'text', 3, self.winfo_height()/2) # RESETS TEXT + delay = self.cnf.get('initial_delay') + \ + self.cnf.get('end_delay') # INITITAL DEALY + else: + # MOVE -1 PIXEL EVERYTIME + self.tk.call((self._w, 'move') + ( + 'text', -self.cnf.get('smoothness'), 0)) + self.after_id = self.after(delay, self._animate) + + def _configure(self, cmd, cnf=None, kw=None): + """Internal function.""" + kw = _cnfmerge((cnf, kw)) + self.cnf = dict( + text=kw.pop('text', self.cnf.get('text')), + font=kw.pop('font', self.cnf.get('font')), + fg=kw.pop('fg', self.cnf.get('fg')) if kw.get('fg') + else kw.pop('foreground', self.cnf.get('foreground')), + fps=kw.pop('fps', self.cnf.get('fps')), + left_margin=kw.pop('left_margin', self.cnf.get('left_margin')), + initial_delay=kw.pop( + 'initial_delay', self.cnf.get('initial_delay')), + end_delay=kw.pop('end_delay', self.cnf.get('end_delay')), + smoothness=kw.pop('smoothness', self.cnf.get('smoothness')), + ) + _tk.Canvas._configure(self, ('itemconfigure','text'), dict(text=self.cnf.get('text'), + font=self.cnf.get('font'), fill=self.cnf.get('fg')), None) + self._set_height() + return _tk.Canvas._configure(self, cmd, kw, None) + + def cget(self, key): + """Return the resource value for a KEY given as string.""" + if key in self.cnf.keys(): + return self.cnf[key] + return _tk.Canvas.cget(self, key) + __getitem__ = cget + + def destroy(self): + """Destroy this widget.""" + self.after_cancel(self.after_id) + return _tk.Canvas.destroy(self) diff --git a/tkmacosx/colors.py b/tkmacosx/colors.py new file mode 100644 index 0000000..b6eafc2 --- /dev/null +++ b/tkmacosx/colors.py @@ -0,0 +1,1266 @@ +Hex = { + 'aliceblue': '#F0F8FF', + 'antiquewhite': '#FAEBD7', + 'aqua': '#00FFFF', + 'aquamarine': '#7FFFD4', + 'azure': '#F0FFFF', + 'beige': '#F5F5DC', + 'bisque': '#FFE4C4', + 'black': '#000000', + 'blanchedalmond': '#FFEBCD', + 'blue': '#0000FF', + 'blueviolet': '#8A2BE2', + 'brown': '#A52A2A', + 'burlywood': '#DEB887', + 'cadetblue': '#5F9EA0', + 'chartreuse': '#7FFF00', + 'chocolate': '#D2691E', + 'coral': '#FF7F50', + 'cornflowerblue': '#6495ED', + 'cornsilk': '#FFF8DC', + 'crimson': '#DC143C', + 'cyan': '#00FFFF', + 'darkblue': '#00008B', + 'darkcyan': '#008B8B', + 'darkgoldenrod': '#B8860B', + 'darkgray': '#A9A9A9', + 'darkgreen': '#006400', + 'darkkhaki': '#BDB76B', + 'darkmagenta': '#8B008B', + 'darkolivegreen': '#556B2F', + 'darkorange': '#FF8C00', + 'darkorchid': '#9932CC', + 'darkred': '#8B0000', + 'darksalmon': '#E9967A', + 'darkseagreen': '#8FBC8F', + 'darkslateblue': '#483D8B', + 'darkslategray': '#2F4F4F', + 'darkturquoise': '#00CED1', + 'darkviolet': '#9400D3', + 'deeppink': '#FF1493', + 'deepskyblue': '#00BFFF', + 'dimgray': '#696969', + 'dodgerblue': '#1E90FF', + 'firebrick': '#B22222', + 'floralwhite': '#FFFAF0', + 'forestgreen': '#228B22', + 'fuchsia': '#FF00FF', + 'gainsboro': '#DCDCDC', + 'ghostwhite': '#F8F8FF', + 'gold': '#FFD700', + 'goldenrod': '#DAA520', + 'gray': '#808080', + 'green': '#008000', + 'greenyellow': '#ADFF2F', + 'honeydew': '#F0FFF0', + 'hotpink': '#FF69B4', + 'indianred': '#CD5C5C', + 'indigo': '#4B0082', + 'ivory': '#FFFFF0', + 'khaki': '#F0E68C', + 'lavender': '#E6E6FA', + 'lavenderblush': '#FFF0F5', + 'lawngreen': '#7CFC00', + 'lemonchiffon': '#FFFACD', + 'lightblue': '#ADD8E6', + 'lightcoral': '#F08080', + 'lightcyan': '#E0FFFF', + 'lightgoldenrodyellow': '#FAFAD2', + 'lightgreen': '#90EE90', + 'lightgray': '#D3D3D3', + 'lightpink': '#FFB6C1', + 'lightsalmon': '#FFA07A', + 'lightseagreen': '#20B2AA', + 'lightskyblue': '#87CEFA', + 'lightslategray': '#778899', + 'lightsteelblue': '#B0C4DE', + 'lightyellow': '#FFFFE0', + 'lime': '#00FF00', + 'limegreen': '#32CD32', + 'linen': '#FAF0E6', + 'magenta': '#FF00FF', + 'maroon': '#800000', + 'mediumaquamarine': '#66CDAA', + 'mediumblue': '#0000CD', + 'mediumorchid': '#BA55D3', + 'mediumpurple': '#9370DB', + 'mediumseagreen': '#3CB371', + 'mediumslateblue': '#7B68EE', + 'mediumspringgreen': '#00FA9A', + 'mediumturquoise': '#48D1CC', + 'mediumvioletred': '#C71585', + 'midnightblue': '#191970', + 'mintcream': '#F5FFFA', + 'mistyrose': '#FFE4E1', + 'moccasin': '#FFE4B5', + 'navajowhite': '#FFDEAD', + 'navy': '#000080', + 'oldlace': '#FDF5E6', + 'olive': '#808000', + 'olivedrab': '#6B8E23', + 'orange': '#FFA500', + 'orangered': '#FF4500', + 'orchid': '#DA70D6', + 'palegoldenrod': '#EEE8AA', + 'palegreen': '#98FB98', + 'paleturquoise': '#AFEEEE', + 'palevioletred': '#DB7093', + 'papayawhip': '#FFEFD5', + 'peachpuff': '#FFDAB9', + 'peru': '#CD853F', + 'pink': '#FFC0CB', + 'plum': '#DDA0DD', + 'powderblue': '#B0E0E6', + 'purple': '#800080', + 'red': '#FF0000', + 'rosybrown': '#BC8F8F', + 'royalblue': '#4169E1', + 'saddlebrown': '#8B4513', + 'salmon': '#FA8072', + 'sandybrown': '#FAA460', + 'seagreen': '#2E8B57', + 'seashell': '#FFF5EE', + 'sienna': '#A0522D', + 'silver': '#C0C0C0', + 'skyblue': '#87CEEB', + 'slateblue': '#6A5ACD', + 'slategray': '#708090', + 'snow': '#FFFAFA', + 'springgreen': '#00FF7F', + 'steelblue': '#4682B4', + 'tan': '#D2B48C', + 'teal': '#008080', + 'thistle': '#D8BFD8', + 'tomato': '#FF6347', + 'turquoise': '#40E0D0', + 'violet': '#EE82EE', + 'wheat': '#F5DEB3', + 'white': '#FFFFFF', + 'whitesmoke': '#F5F5F5', + 'yellow': '#FFFF00', + 'yellowgreen': '#9ACD32' + } + + +OrderedHex = { + "#f9ebea":(249, 235, 234), + "#f2d7d5":(242, 215, 213), + "#e6b0aa":(230, 176, 170), + "#d98880":(217, 136, 128), + "#cd6155":(205, 97, 85), + "#c0392b":(192, 57, 43), + "#a93226":(169, 50, 38), + "#922b21":(146, 43, 33), + "#7b241c":(123, 36, 28), + "#641e16":(100, 30, 22), + "#fdedec":(253, 237, 236), + "#fadbd8":(250, 219, 216), + "#f5b7b1":(245, 183, 177), + "#f1948a":(241, 148, 138), + "#ec7063":(236, 112, 99), + "#e74c3c":(231, 76, 60), + "#cb4335":(203, 67, 53), + "#b03a2e":(176, 58, 46), + "#943126":(148, 49, 38), + "#78281f":(120, 40, 31), + "#f5eef8":(245, 238, 248), + "#ebdef0":(235, 222, 240), + "#d7bde2":(215, 189, 226), + "#c39bd3":(195, 155, 211), + "#af7ac5":(175, 122, 197), + "#9b59b6":(155, 89, 182), + "#884ea0":(136, 78, 160), + "#76448a":(118, 68, 138), + "#633974":( 99, 57, 116), + "#512e5f":( 81, 46, 95), + "#f4ecf7":(244, 236, 247), + "#e8daef":(232, 218, 239), + "#d2b4de":(210, 180, 222), + "#bb8fce":(187, 143, 206), + "#a569bd":(165, 105, 189), + "#8e44ad":(142, 68, 173), + "#7d3c98":(125, 60, 152), + "#6c3483":(108, 52, 131), + "#5b2c6f":( 91, 44, 111), + "#4a235a":( 74, 35, 90), + "#eaf2f8":(234, 242, 248), + "#d4e6f1":(212, 230, 241), + "#a9cce3":(169, 204, 227), + "#7fb3d5":(127, 179, 213), + "#5499c7":( 84, 153, 199), + "#2980b9":( 41, 128, 185), + "#2471a3":( 36, 113, 163), + "#1f618d":( 31, 97, 141), + "#1a5276":( 26, 82, 118), + "#154360":( 21, 67, 96), + "#ebf5fb":(235, 245, 251), + "#d6eaf8":(214, 234, 248), + "#aed6f1":(174, 214, 241), + "#85c1e9":(133, 193, 233), + "#5dade2":( 93, 173, 226), + "#3498db":( 52, 152, 219), + "#2e86c1":( 46, 134, 193), + "#2874a6":( 40, 116, 166), + "#21618c":( 33, 97, 140), + "#1b4f72":( 27, 79, 114), + "#e8f8f5":(232, 248, 245), + "#d1f2eb":(209, 242, 235), + "#a3e4d7":(163, 228, 215), + "#76d7c4":(118, 215, 196), + "#48c9b0":( 72, 201, 176), + "#1abc9c":( 26, 188, 156), + "#17a589":( 23, 165, 137), + "#148f77":( 20, 143, 119), + "#117864":( 17, 120, 100), + "#0e6251":( 14, 98, 81), + "#e8f6f3":(232, 246, 243), + "#d0ece7":(208, 236, 231), + "#a2d9ce":(162, 217, 206), + "#73c6b6":(115, 198, 182), + "#45b39d":( 69, 179, 157), + "#16a085":( 22, 160, 133), + "#138d75":( 19, 141, 117), + "#117a65":( 17, 122, 101), + "#0e6655":( 14, 102, 85), + "#0b5345":( 11, 83, 69), + "#e9f7ef":(233, 247, 239), + "#d4efdf":(212, 239, 223), + "#a9dfbf":(169, 223, 191), + "#7dcea0":(125, 206, 160), + "#52be80":( 82, 190, 128), + "#27ae60":( 39, 174, 96), + "#229954":( 34, 153, 84), + "#1e8449":( 30, 132, 73), + "#196f3d":( 25, 111, 61), + "#145a32":( 20, 90, 50), + "#eafaf1":(234, 250, 241), + "#d5f5e3":(213, 245, 227), + "#abebc6":(171, 235, 198), + "#82e0aa":(130, 224, 170), + "#58d68d":( 88, 214, 141), + "#2ecc71":( 46, 204, 113), + "#28b463":( 40, 180, 99), + "#239b56":( 35, 155, 86), + "#1d8348":( 29, 131, 72), + "#186a3b":( 24, 106, 59), + "#fef9e7":(254, 249, 231), + "#fcf3cf":(252, 243, 207), + "#f9e79f":(249, 231, 159), + "#f7dc6f":(247, 220, 111), + "#f4d03f":(244, 208, 63), + "#f1c40f":(241, 196, 15), + "#d4ac0d":(212, 172, 13), + "#b7950b":(183, 149, 11), + "#9a7d0a":(154, 125, 10), + "#7d6608":(125, 102, 8), + "#fef5e7":(254, 245, 231), + "#fdebd0":(253, 235, 208), + "#fad7a0":(250, 215, 160), + "#f8c471":(248, 196, 113), + "#f5b041":(245, 176, 65), + "#f39c12":(243, 156, 18), + "#d68910":(214, 137, 16), + "#b9770e":(185, 119, 14), + "#9c640c":(156, 100, 12), + "#7e5109":(126, 81, 9), + "#fdf2e9":(253, 242, 233), + "#fae5d3":(250, 229, 211), + "#f5cba7":(245, 203, 167), + "#f0b27a":(240, 178, 122), + "#eb984e":(235, 152, 78), + "#e67e22":(230, 126, 34), + "#ca6f1e":(202, 111, 30), + "#af601a":(175, 96, 26), + "#935116":(147, 81, 22), + "#784212":(120, 66, 18), + "#fbeee6":(251, 238, 230), + "#f6ddcc":(246, 221, 204), + "#edbb99":(237, 187, 153), + "#e59866":(229, 152, 102), + "#dc7633":(220, 118, 51), + "#d35400":(211, 84, 0), + "#ba4a00":(186, 74, 0), + "#a04000":(160, 64, 0), + "#873600":(135, 54, 0), + "#6e2c00":(110, 44, 0), + "#fdfefe":(253, 254, 254), + "#fbfcfc":(251, 252, 252), + "#f7f9f9":(247, 249, 249), + "#f4f6f7":(244, 246, 247), + "#f0f3f4":(240, 243, 244), + "#ecf0f1":(236, 240, 241), + "#d0d3d4":(208, 211, 212), + "#b3b6b7":(179, 182, 183), + "#979a9a":(151, 154, 154), + "#7b7d7d":(123, 125, 125), + "#f8f9f9":(248, 249, 249), + "#f2f3f4":(242, 243, 244), + "#e5e7e9":(229, 231, 233), + "#d7dbdd":(215, 219, 221), + "#cacfd2":(202, 207, 210), + "#bdc3c7":(189, 195, 199), + "#a6acaf":(166, 172, 175), + "#909497":(144, 148, 151), + "#797d7f":(121, 125, 127), + "#626567":( 98, 101, 103), + "#f4f6f6":(244, 246, 246), + "#eaeded":(234, 237, 237), + "#d5dbdb":(213, 219, 219), + "#bfc9ca":(191, 201, 202), + "#aab7b8":(170, 183, 184), + "#95a5a6":(149, 165, 166), + "#839192":(131, 145, 146), + "#717d7e":(113, 125, 126), + "#5f6a6a":( 95, 106, 106), + "#4d5656":( 77, 86, 86), + "#f2f4f4":(242, 244, 244), + "#e5e8e8":(229, 232, 232), + "#ccd1d1":(204, 209, 209), + "#b2babb":(178, 186, 187), + "#99a3a4":(153, 163, 164), + "#7f8c8d":(127, 140, 141), + "#707b7c":(112, 123, 124), + "#616a6b":( 97, 106, 107), + "#515a5a":( 81, 90, 90), + "#424949":( 66, 73, 73), + "#ebedef":(235, 237, 239), + "#d6dbdf":(214, 219, 223), + "#aeb6bf":(174, 182, 191), + "#85929e":(133, 146, 158), + "#5d6d7e":( 93, 109, 126), + "#34495e":( 52, 73, 94), + "#2e4053":( 46, 64, 83), + "#283747":( 40, 55, 71), + "#212f3c":( 33, 47, 60), + "#1b2631":( 27, 38, 49), + "#eaecee":(234, 236, 238), + "#d5d8dc":(213, 216, 220), + "#abb2b9":(171, 178, 185), + "#808b96":(128, 139, 150), + "#566573":( 86, 101, 115), + "#2c3e50":( 44, 62, 80), + "#273746":( 39, 55, 70), + "#212f3d":( 33, 47, 61), + "#1c2833":( 28, 40, 51), + "#17202a":( 23, 32, 42) +} + + +all_colors = ( + {'indian red': {'hex': '#B0171F', 'rgb': ('176', '23', '31')}}, + {'crimson': {'hex': '#DC143C', 'rgb': ('220', '20', '60')}}, + {'lightpink': {'hex': '#FFB6C1', 'rgb': ('255', '182', '193')}}, + {'lightpink 1': {'hex': '#FFAEB9', 'rgb': ('255', '174', '185')}}, + {'lightpink 2': {'hex': '#EEA2AD', 'rgb': ('238', '162', '173')}}, + {'lightpink 3': {'hex': '#CD8C95', 'rgb': ('205', '140', '149')}}, + {'lightpink 4': {'hex': '#8B5F65', 'rgb': ('139', '95', '101')}}, + {'pink': {'hex': '#FFC0CB', 'rgb': ('255', '192', '203')}}, + {'pink 1': {'hex': '#FFB5C5', 'rgb': ('255', '181', '197')}}, + {'pink 2': {'hex': '#EEA9B8', 'rgb': ('238', '169', '184')}}, + {'pink 3': {'hex': '#CD919E', 'rgb': ('205', '145', '158')}}, + {'pink 4': {'hex': '#8B636C', 'rgb': ('139', '99', '108')}}, + {'palevioletred': {'hex': '#DB7093', 'rgb': ('219', '112', '147')}}, + {'palevioletred 1': {'hex': '#FF82AB', 'rgb': ('255', '130', '171')}}, + {'palevioletred 2': {'hex': '#EE799F', 'rgb': ('238', '121', '159')}}, + {'palevioletred 3': {'hex': '#CD6889', 'rgb': ('205', '104', '137')}}, + {'palevioletred 4': {'hex': '#8B475D', 'rgb': ('139', '71', '93')}}, + {'lavenderblush 1 (lavenderblush)': {'hex': '#FFF0F5', 'rgb': ('255', '240', '245')}}, + {'lavenderblush 2': {'hex': '#EEE0E5', 'rgb': ('238', '224', '229')}}, + {'lavenderblush 3': {'hex': '#CDC1C5', 'rgb': ('205', '193', '197')}}, + {'lavenderblush 4': {'hex': '#8B8386', 'rgb': ('139', '131', '134')}}, + {'violetred 1': {'hex': '#FF3E96', 'rgb': ('255', '62', '150')}}, + {'violetred 2': {'hex': '#EE3A8C', 'rgb': ('238', '58', '140')}}, + {'violetred 3': {'hex': '#CD3278', 'rgb': ('205', '50', '120')}}, + {'violetred 4': {'hex': '#8B2252', 'rgb': ('139', '34', '82')}}, + {'hotpink': {'hex': '#FF69B4', 'rgb': ('255', '105', '180')}}, + {'hotpink 1': {'hex': '#FF6EB4', 'rgb': ('255', '110', '180')}}, + {'hotpink 2': {'hex': '#EE6AA7', 'rgb': ('238', '106', '167')}}, + {'hotpink 3': {'hex': '#CD6090', 'rgb': ('205', '96', '144')}}, + {'hotpink 4': {'hex': '#8B3A62', 'rgb': ('139', '58', '98')}}, + {'raspberry': {'hex': '#872657', 'rgb': ('135', '38', '87')}}, + {'deeppink 1 (deeppink)': {'hex': '#FF1493', 'rgb': ('255', '20', '147')}}, + {'deeppink 2': {'hex': '#EE1289', 'rgb': ('238', '18', '137')}}, + {'deeppink 3': {'hex': '#CD1076', 'rgb': ('205', '16', '118')}}, + {'deeppink 4': {'hex': '#8B0A50', 'rgb': ('139', '10', '80')}}, + {'maroon 1': {'hex': '#FF34B3', 'rgb': ('255', '52', '179')}}, + {'maroon 2': {'hex': '#EE30A7', 'rgb': ('238', '48', '167')}}, + {'maroon 3': {'hex': '#CD2990', 'rgb': ('205', '41', '144')}}, + {'maroon 4': {'hex': '#8B1C62', 'rgb': ('139', '28', '98')}}, + {'mediumvioletred': {'hex': '#C71585', 'rgb': ('199', '21', '133')}}, + {'violetred': {'hex': '#D02090', 'rgb': ('208', '32', '144')}}, + {'orchid': {'hex': '#DA70D6', 'rgb': ('218', '112', '214')}}, + {'orchid 1': {'hex': '#FF83FA', 'rgb': ('255', '131', '250')}}, + {'orchid 2': {'hex': '#EE7AE9', 'rgb': ('238', '122', '233')}}, + {'orchid 3': {'hex': '#CD69C9', 'rgb': ('205', '105', '201')}}, + {'orchid 4': {'hex': '#8B4789', 'rgb': ('139', '71', '137')}}, + {'thistle': {'hex': '#D8BFD8', 'rgb': ('216', '191', '216')}}, + {'thistle 1': {'hex': '#FFE1FF', 'rgb': ('255', '225', '255')}}, + {'thistle 2': {'hex': '#EED2EE', 'rgb': ('238', '210', '238')}}, + {'thistle 3': {'hex': '#CDB5CD', 'rgb': ('205', '181', '205')}}, + {'thistle 4': {'hex': '#8B7B8B', 'rgb': ('139', '123', '139')}}, + {'plum 1': {'hex': '#FFBBFF', 'rgb': ('255', '187', '255')}}, + {'plum 2': {'hex': '#EEAEEE', 'rgb': ('238', '174', '238')}}, + {'plum 3': {'hex': '#CD96CD', 'rgb': ('205', '150', '205')}}, + {'plum 4': {'hex': '#8B668B', 'rgb': ('139', '102', '139')}}, + {'plum': {'hex': '#DDA0DD', 'rgb': ('221', '160', '221')}}, + {'violet': {'hex': '#EE82EE', 'rgb': ('238', '130', '238')}}, + {'magenta (fuchsia)': {'hex': '#FF00FF', 'rgb': ('255', '0', '255')}}, + {'magenta 2': {'hex': '#EE00EE', 'rgb': ('238', '0', '238')}}, + {'magenta 3': {'hex': '#CD00CD', 'rgb': ('205', '0', '205')}}, + {'magenta 4 (darkmagenta)': {'hex': '#8B008B', 'rgb': ('139', '0', '139')}}, + {'purple': {'hex': '#800080', 'rgb': ('128', '0', '128')}}, + {'mediumorchid': {'hex': '#BA55D3', 'rgb': ('186', '85', '211')}}, + {'mediumorchid 1': {'hex': '#E066FF', 'rgb': ('224', '102', '255')}}, + {'mediumorchid 2': {'hex': '#D15FEE', 'rgb': ('209', '95', '238')}}, + {'mediumorchid 3': {'hex': '#B452CD', 'rgb': ('180', '82', '205')}}, + {'mediumorchid 4': {'hex': '#7A378B', 'rgb': ('122', '55', '139')}}, + {'darkviolet': {'hex': '#9400D3', 'rgb': ('148', '0', '211')}}, + {'darkorchid': {'hex': '#9932CC', 'rgb': ('153', '50', '204')}}, + {'darkorchid 1': {'hex': '#BF3EFF', 'rgb': ('191', '62', '255')}}, + {'darkorchid 2': {'hex': '#B23AEE', 'rgb': ('178', '58', '238')}}, + {'darkorchid 3': {'hex': '#9A32CD', 'rgb': ('154', '50', '205')}}, + {'darkorchid 4': {'hex': '#68228B', 'rgb': ('104', '34', '139')}}, + {'indigo': {'hex': '#4B0082', 'rgb': ('75', '0', '130')}}, + {'blueviolet': {'hex': '#8A2BE2', 'rgb': ('138', '43', '226')}}, + {'purple 1': {'hex': '#9B30FF', 'rgb': ('155', '48', '255')}}, + {'purple 2': {'hex': '#912CEE', 'rgb': ('145', '44', '238')}}, + {'purple 3': {'hex': '#7D26CD', 'rgb': ('125', '38', '205')}}, + {'purple 4': {'hex': '#551A8B', 'rgb': ('85', '26', '139')}}, + {'mediumpurple': {'hex': '#9370DB', 'rgb': ('147', '112', '219')}}, + {'mediumpurple 1': {'hex': '#AB82FF', 'rgb': ('171', '130', '255')}}, + {'mediumpurple 2': {'hex': '#9F79EE', 'rgb': ('159', '121', '238')}}, + {'mediumpurple 3': {'hex': '#8968CD', 'rgb': ('137', '104', '205')}}, + {'mediumpurple 4': {'hex': '#5D478B', 'rgb': ('93', '71', '139')}}, + {'darkslateblue': {'hex': '#483D8B', 'rgb': ('72', '61', '139')}}, + {'lightslateblue': {'hex': '#8470FF', 'rgb': ('132', '112', '255')}}, + {'mediumslateblue': {'hex': '#7B68EE', 'rgb': ('123', '104', '238')}}, + {'slateblue': {'hex': '#6A5ACD', 'rgb': ('106', '90', '205')}}, + {'slateblue 1': {'hex': '#836FFF', 'rgb': ('131', '111', '255')}}, + {'slateblue 2': {'hex': '#7A67EE', 'rgb': ('122', '103', '238')}}, + {'slateblue 3': {'hex': '#6959CD', 'rgb': ('105', '89', '205')}}, + {'slateblue 4': {'hex': '#473C8B', 'rgb': ('71', '60', '139')}}, + {'ghostwhite': {'hex': '#F8F8FF', 'rgb': ('248', '248', '255')}}, + {'lavender': {'hex': '#E6E6FA', 'rgb': ('230', '230', '250')}}, + {'blue': {'hex': '#0000FF', 'rgb': ('0', '0', '255')}}, + {'blue 2': {'hex': '#0000EE', 'rgb': ('0', '0', '238')}}, + {'blue 3 (mediumblue)': {'hex': '#0000CD', 'rgb': ('0', '0', '205')}}, + {'blue 4 (darkblue)': {'hex': '#00008B', 'rgb': ('0', '0', '139')}}, + {'navy': {'hex': '#000080', 'rgb': ('0', '0', '128')}}, + {'midnightblue': {'hex': '#191970', 'rgb': ('25', '25', '112')}}, + {'cobalt': {'hex': '#3D59AB', 'rgb': ('61', '89', '171')}}, + {'royalblue': {'hex': '#4169E1', 'rgb': ('65', '105', '225')}}, + {'royalblue 1': {'hex': '#4876FF', 'rgb': ('72', '118', '255')}}, + {'royalblue 2': {'hex': '#436EEE', 'rgb': ('67', '110', '238')}}, + {'royalblue 3': {'hex': '#3A5FCD', 'rgb': ('58', '95', '205')}}, + {'royalblue 4': {'hex': '#27408B', 'rgb': ('39', '64', '139')}}, + {'cornflowerblue': {'hex': '#6495ED', 'rgb': ('100', '149', '237')}}, + {'lightsteelblue': {'hex': '#B0C4DE', 'rgb': ('176', '196', '222')}}, + {'lightsteelblue 1': {'hex': '#CAE1FF', 'rgb': ('202', '225', '255')}}, + {'lightsteelblue 2': {'hex': '#BCD2EE', 'rgb': ('188', '210', '238')}}, + {'lightsteelblue 3': {'hex': '#A2B5CD', 'rgb': ('162', '181', '205')}}, + {'lightsteelblue 4': {'hex': '#6E7B8B', 'rgb': ('110', '123', '139')}}, + {'lightslategray': {'hex': '#778899', 'rgb': ('119', '136', '153')}}, + {'slategray': {'hex': '#708090', 'rgb': ('112', '128', '144')}}, + {'slategray 1': {'hex': '#C6E2FF', 'rgb': ('198', '226', '255')}}, + {'slategray 2': {'hex': '#B9D3EE', 'rgb': ('185', '211', '238')}}, + {'slategray 3': {'hex': '#9FB6CD', 'rgb': ('159', '182', '205')}}, + {'slategray 4': {'hex': '#6C7B8B', 'rgb': ('108', '123', '139')}}, + {'dodgerblue 1 (dodgerblue)': {'hex': '#1E90FF', 'rgb': ('30', '144', '255')}}, + {'dodgerblue 2': {'hex': '#1C86EE', 'rgb': ('28', '134', '238')}}, + {'dodgerblue 3': {'hex': '#1874CD', 'rgb': ('24', '116', '205')}}, + {'dodgerblue 4': {'hex': '#104E8B', 'rgb': ('16', '78', '139')}}, + {'aliceblue': {'hex': '#F0F8FF', 'rgb': ('240', '248', '255')}}, + {'steelblue': {'hex': '#4682B4', 'rgb': ('70', '130', '180')}}, + {'steelblue 1': {'hex': '#63B8FF', 'rgb': ('99', '184', '255')}}, + {'steelblue 2': {'hex': '#5CACEE', 'rgb': ('92', '172', '238')}}, + {'steelblue 3': {'hex': '#4F94CD', 'rgb': ('79', '148', '205')}}, + {'steelblue 4': {'hex': '#36648B', 'rgb': ('54', '100', '139')}}, + {'lightskyblue': {'hex': '#87CEFA', 'rgb': ('135', '206', '250')}}, + {'lightskyblue 1': {'hex': '#B0E2FF', 'rgb': ('176', '226', '255')}}, + {'lightskyblue 2': {'hex': '#A4D3EE', 'rgb': ('164', '211', '238')}}, + {'lightskyblue 3': {'hex': '#8DB6CD', 'rgb': ('141', '182', '205')}}, + {'lightskyblue 4': {'hex': '#607B8B', 'rgb': ('96', '123', '139')}}, + {'skyblue 1': {'hex': '#87CEFF', 'rgb': ('135', '206', '255')}}, + {'skyblue 2': {'hex': '#7EC0EE', 'rgb': ('126', '192', '238')}}, + {'skyblue 3': {'hex': '#6CA6CD', 'rgb': ('108', '166', '205')}}, + {'skyblue 4': {'hex': '#4A708B', 'rgb': ('74', '112', '139')}}, + {'skyblue': {'hex': '#87CEEB', 'rgb': ('135', '206', '235')}}, + {'deepskyblue 1 (deepskyblue)': {'hex': '#00BFFF', 'rgb': ('0', '191', '255')}}, + {'deepskyblue 2': {'hex': '#00B2EE', 'rgb': ('0', '178', '238')}}, + {'deepskyblue 3': {'hex': '#009ACD', 'rgb': ('0', '154', '205')}}, + {'deepskyblue 4': {'hex': '#00688B', 'rgb': ('0', '104', '139')}}, + {'peacock': {'hex': '#33A1C9', 'rgb': ('51', '161', '201')}}, + {'lightblue': {'hex': '#ADD8E6', 'rgb': ('173', '216', '230')}}, + {'lightblue 1': {'hex': '#BFEFFF', 'rgb': ('191', '239', '255')}}, + {'lightblue 2': {'hex': '#B2DFEE', 'rgb': ('178', '223', '238')}}, + {'lightblue 3': {'hex': '#9AC0CD', 'rgb': ('154', '192', '205')}}, + {'lightblue 4': {'hex': '#68838B', 'rgb': ('104', '131', '139')}}, + {'powderblue': {'hex': '#B0E0E6', 'rgb': ('176', '224', '230')}}, + {'cadetblue 1': {'hex': '#98F5FF', 'rgb': ('152', '245', '255')}}, + {'cadetblue 2': {'hex': '#8EE5EE', 'rgb': ('142', '229', '238')}}, + {'cadetblue 3': {'hex': '#7AC5CD', 'rgb': ('122', '197', '205')}}, + {'cadetblue 4': {'hex': '#53868B', 'rgb': ('83', '134', '139')}}, + {'turquoise 1': {'hex': '#00F5FF', 'rgb': ('0', '245', '255')}}, + {'turquoise 2': {'hex': '#00E5EE', 'rgb': ('0', '229', '238')}}, + {'turquoise 3': {'hex': '#00C5CD', 'rgb': ('0', '197', '205')}}, + {'turquoise 4': {'hex': '#00868B', 'rgb': ('0', '134', '139')}}, + {'cadetblue': {'hex': '#5F9EA0', 'rgb': ('95', '158', '160')}}, + {'darkturquoise': {'hex': '#00CED1', 'rgb': ('0', '206', '209')}}, + {'azure 1 (azure)': {'hex': '#F0FFFF', 'rgb': ('240', '255', '255')}}, + {'azure 2': {'hex': '#E0EEEE', 'rgb': ('224', '238', '238')}}, + {'azure 3': {'hex': '#C1CDCD', 'rgb': ('193', '205', '205')}}, + {'azure 4': {'hex': '#838B8B', 'rgb': ('131', '139', '139')}}, + {'lightcyan 1 (lightcyan)': {'hex': '#E0FFFF', 'rgb': ('224', '255', '255')}}, + {'lightcyan 2': {'hex': '#D1EEEE', 'rgb': ('209', '238', '238')}}, + {'lightcyan 3': {'hex': '#B4CDCD', 'rgb': ('180', '205', '205')}}, + {'lightcyan 4': {'hex': '#7A8B8B', 'rgb': ('122', '139', '139')}}, + {'paleturquoise 1': {'hex': '#BBFFFF', 'rgb': ('187', '255', '255')}}, + {'paleturquoise 2 (paleturquoise)': {'hex': '#AEEEEE', 'rgb': ('174', '238', '238')}}, + {'paleturquoise 3': {'hex': '#96CDCD', 'rgb': ('150', '205', '205')}}, + {'paleturquoise 4': {'hex': '#668B8B', 'rgb': ('102', '139', '139')}}, + {'darkslategray': {'hex': '#2F4F4F', 'rgb': ('47', '79', '79')}}, + {'darkslategray 1': {'hex': '#97FFFF', 'rgb': ('151', '255', '255')}}, + {'darkslategray 2': {'hex': '#8DEEEE', 'rgb': ('141', '238', '238')}}, + {'darkslategray 3': {'hex': '#79CDCD', 'rgb': ('121', '205', '205')}}, + {'darkslategray 4': {'hex': '#528B8B', 'rgb': ('82', '139', '139')}}, + {'cyan / aqua': {'hex': '#00FFFF', 'rgb': ('0', '255', '255')}}, + {'cyan 2': {'hex': '#00EEEE', 'rgb': ('0', '238', '238')}}, + {'cyan 3': {'hex': '#00CDCD', 'rgb': ('0', '205', '205')}}, + {'cyan 4 (darkcyan)': {'hex': '#008B8B', 'rgb': ('0', '139', '139')}}, + {'teal': {'hex': '#008080', 'rgb': ('0', '128', '128')}}, + {'mediumturquoise': {'hex': '#48D1CC', 'rgb': ('72', '209', '204')}}, + {'lightseagreen': {'hex': '#20B2AA', 'rgb': ('32', '178', '170')}}, + {'manganeseblue': {'hex': '#03A89E', 'rgb': ('3', '168', '158')}}, + {'turquoise': {'hex': '#40E0D0', 'rgb': ('64', '224', '208')}}, + {'coldgrey': {'hex': '#808A87', 'rgb': ('128', '138', '135')}}, + {'turquoiseblue': {'hex': '#00C78C', 'rgb': ('0', '199', '140')}}, + {'aquamarine 1 (aquamarine)': {'hex': '#7FFFD4', 'rgb': ('127', '255', '212')}}, + {'aquamarine 2': {'hex': '#76EEC6', 'rgb': ('118', '238', '198')}}, + {'aquamarine 3 (mediumaquamarine)': {'hex': '#66CDAA', 'rgb': ('102', '205', '170')}}, + {'aquamarine 4': {'hex': '#458B74', 'rgb': ('69', '139', '116')}}, + {'mediumspringgreen': {'hex': '#00FA9A', 'rgb': ('0', '250', '154')}}, + {'mintcream': {'hex': '#F5FFFA', 'rgb': ('245', '255', '250')}}, + {'springgreen': {'hex': '#00FF7F', 'rgb': ('0', '255', '127')}}, + {'springgreen 1': {'hex': '#00EE76', 'rgb': ('0', '238', '118')}}, + {'springgreen 2': {'hex': '#00CD66', 'rgb': ('0', '205', '102')}}, + {'springgreen 3': {'hex': '#008B45', 'rgb': ('0', '139', '69')}}, + {'mediumseagreen': {'hex': '#3CB371', 'rgb': ('60', '179', '113')}}, + {'seagreen 1': {'hex': '#54FF9F', 'rgb': ('84', '255', '159')}}, + {'seagreen 2': {'hex': '#4EEE94', 'rgb': ('78', '238', '148')}}, + {'seagreen 3': {'hex': '#43CD80', 'rgb': ('67', '205', '128')}}, + {'seagreen 4 (seagreen)': {'hex': '#2E8B57', 'rgb': ('46', '139', '87')}}, + {'emeraldgreen': {'hex': '#00C957', 'rgb': ('0', '201', '87')}}, + {'mint': {'hex': '#BDFCC9', 'rgb': ('189', '252', '201')}}, + {'cobaltgreen': {'hex': '#3D9140', 'rgb': ('61', '145', '64')}}, + {'honeydew 1 (honeydew)': {'hex': '#F0FFF0', 'rgb': ('240', '255', '240')}}, + {'honeydew 2': {'hex': '#E0EEE0', 'rgb': ('224', '238', '224')}}, + {'honeydew 3': {'hex': '#C1CDC1', 'rgb': ('193', '205', '193')}}, + {'honeydew 4': {'hex': '#838B83', 'rgb': ('131', '139', '131')}}, + {'darkseagreen': {'hex': '#8FBC8F', 'rgb': ('143', '188', '143')}}, + {'darkseagreen 1': {'hex': '#C1FFC1', 'rgb': ('193', '255', '193')}}, + {'darkseagreen 2': {'hex': '#B4EEB4', 'rgb': ('180', '238', '180')}}, + {'darkseagreen 3': {'hex': '#9BCD9B', 'rgb': ('155', '205', '155')}}, + {'darkseagreen 4': {'hex': '#698B69', 'rgb': ('105', '139', '105')}}, + {'palegreen': {'hex': '#98FB98', 'rgb': ('152', '251', '152')}}, + {'palegreen 1': {'hex': '#9AFF9A', 'rgb': ('154', '255', '154')}}, + {'palegreen 2 (lightgreen)': {'hex': '#90EE90', 'rgb': ('144', '238', '144')}}, + {'palegreen 3': {'hex': '#7CCD7C', 'rgb': ('124', '205', '124')}}, + {'palegreen 4': {'hex': '#548B54', 'rgb': ('84', '139', '84')}}, + {'limegreen': {'hex': '#32CD32', 'rgb': ('50', '205', '50')}}, + {'forestgreen': {'hex': '#228B22', 'rgb': ('34', '139', '34')}}, + {'green 1 (lime)': {'hex': '#00FF00', 'rgb': ('0', '255', '0')}}, + {'green 2': {'hex': '#00EE00', 'rgb': ('0', '238', '0')}}, + {'green 3': {'hex': '#00CD00', 'rgb': ('0', '205', '0')}}, + {'green 4': {'hex': '#008B00', 'rgb': ('0', '139', '0')}}, + {'green': {'hex': '#008000', 'rgb': ('0', '128', '0')}}, + {'darkgreen': {'hex': '#006400', 'rgb': ('0', '100', '0')}}, + {'sapgreen': {'hex': '#308014', 'rgb': ('48', '128', '20')}}, + {'lawngreen': {'hex': '#7CFC00', 'rgb': ('124', '252', '0')}}, + {'chartreuse 1 (chartreuse)': {'hex': '#7FFF00', 'rgb': ('127', '255', '0')}}, + {'chartreuse 2': {'hex': '#76EE00', 'rgb': ('118', '238', '0')}}, + {'chartreuse 3': {'hex': '#66CD00', 'rgb': ('102', '205', '0')}}, + {'chartreuse 4': {'hex': '#458B00', 'rgb': ('69', '139', '0')}}, + {'greenyellow': {'hex': '#ADFF2F', 'rgb': ('173', '255', '47')}}, + {'darkolivegreen 1': {'hex': '#CAFF70', 'rgb': ('202', '255', '112')}}, + {'darkolivegreen 2': {'hex': '#BCEE68', 'rgb': ('188', '238', '104')}}, + {'darkolivegreen 3': {'hex': '#A2CD5A', 'rgb': ('162', '205', '90')}}, + {'darkolivegreen 4': {'hex': '#6E8B3D', 'rgb': ('110', '139', '61')}}, + {'darkolivegreen': {'hex': '#556B2F', 'rgb': ('85', '107', '47')}}, + {'olivedrab': {'hex': '#6B8E23', 'rgb': ('107', '142', '35')}}, + {'olivedrab 1': {'hex': '#C0FF3E', 'rgb': ('192', '255', '62')}}, + {'olivedrab 2': {'hex': '#B3EE3A', 'rgb': ('179', '238', '58')}}, + {'olivedrab 3 (yellowgreen)': {'hex': '#9ACD32', 'rgb': ('154', '205', '50')}}, + {'olivedrab 4': {'hex': '#698B22', 'rgb': ('105', '139', '34')}}, + {'ivory 1 (ivory)': {'hex': '#FFFFF0', 'rgb': ('255', '255', '240')}}, + {'ivory 2': {'hex': '#EEEEE0', 'rgb': ('238', '238', '224')}}, + {'ivory 3': {'hex': '#CDCDC1', 'rgb': ('205', '205', '193')}}, + {'ivory 4': {'hex': '#8B8B83', 'rgb': ('139', '139', '131')}}, + {'beige': {'hex': '#F5F5DC', 'rgb': ('245', '245', '220')}}, + {'lightyellow 1 (lightyellow)': {'hex': '#FFFFE0', 'rgb': ('255', '255', '224')}}, + {'lightyellow 2': {'hex': '#EEEED1', 'rgb': ('238', '238', '209')}}, + {'lightyellow 3': {'hex': '#CDCDB4', 'rgb': ('205', '205', '180')}}, + {'lightyellow 4': {'hex': '#8B8B7A', 'rgb': ('139', '139', '122')}}, + {'lightgoldenrodyellow': {'hex': '#FAFAD2', 'rgb': ('250', '250', '210')}}, + {'yellow 1 (yellow)': {'hex': '#FFFF00', 'rgb': ('255', '255', '0')}}, + {'yellow 2': {'hex': '#EEEE00', 'rgb': ('238', '238', '0')}}, + {'yellow 3': {'hex': '#CDCD00', 'rgb': ('205', '205', '0')}}, + {'yellow 4': {'hex': '#8B8B00', 'rgb': ('139', '139', '0')}}, + {'warmgrey': {'hex': '#808069', 'rgb': ('128', '128', '105')}}, + {'olive': {'hex': '#808000', 'rgb': ('128', '128', '0')}}, + {'darkkhaki': {'hex': '#BDB76B', 'rgb': ('189', '183', '107')}}, + {'khaki 1': {'hex': '#FFF68F', 'rgb': ('255', '246', '143')}}, + {'khaki 2': {'hex': '#EEE685', 'rgb': ('238', '230', '133')}}, + {'khaki 3': {'hex': '#CDC673', 'rgb': ('205', '198', '115')}}, + {'khaki 4': {'hex': '#8B864E', 'rgb': ('139', '134', '78')}}, + {'khaki': {'hex': '#F0E68C', 'rgb': ('240', '230', '140')}}, + {'palegoldenrod': {'hex': '#EEE8AA', 'rgb': ('238', '232', '170')}}, + {'lemonchiffon 1 (lemonchiffon)': {'hex': '#FFFACD', 'rgb': ('255', '250', '205')}}, + {'lemonchiffon 2': {'hex': '#EEE9BF', 'rgb': ('238', '233', '191')}}, + {'lemonchiffon 3': {'hex': '#CDC9A5', 'rgb': ('205', '201', '165')}}, + {'lemonchiffon 4': {'hex': '#8B8970', 'rgb': ('139', '137', '112')}}, + {'lightgoldenrod 1': {'hex': '#FFEC8B', 'rgb': ('255', '236', '139')}}, + {'lightgoldenrod 2': {'hex': '#EEDC82', 'rgb': ('238', '220', '130')}}, + {'lightgoldenrod 3': {'hex': '#CDBE70', 'rgb': ('205', '190', '112')}}, + {'lightgoldenrod 4': {'hex': '#8B814C', 'rgb': ('139', '129', '76')}}, + {'banana': {'hex': '#E3CF57', 'rgb': ('227', '207', '87')}}, + {'gold 1 (gold)': {'hex': '#FFD700', 'rgb': ('255', '215', '0')}}, + {'gold 2': {'hex': '#EEC900', 'rgb': ('238', '201', '0')}}, + {'gold 3': {'hex': '#CDAD00', 'rgb': ('205', '173', '0')}}, + {'gold 4': {'hex': '#8B7500', 'rgb': ('139', '117', '0')}}, + {'cornsilk 1 (cornsilk)': {'hex': '#FFF8DC', 'rgb': ('255', '248', '220')}}, + {'cornsilk 2': {'hex': '#EEE8CD', 'rgb': ('238', '232', '205')}}, + {'cornsilk 3': {'hex': '#CDC8B1', 'rgb': ('205', '200', '177')}}, + {'cornsilk 4': {'hex': '#8B8878', 'rgb': ('139', '136', '120')}}, + {'goldenrod': {'hex': '#DAA520', 'rgb': ('218', '165', '32')}}, + {'goldenrod 1': {'hex': '#FFC125', 'rgb': ('255', '193', '37')}}, + {'goldenrod 2': {'hex': '#EEB422', 'rgb': ('238', '180', '34')}}, + {'goldenrod 3': {'hex': '#CD9B1D', 'rgb': ('205', '155', '29')}}, + {'goldenrod 4': {'hex': '#8B6914', 'rgb': ('139', '105', '20')}}, + {'darkgoldenrod': {'hex': '#B8860B', 'rgb': ('184', '134', '11')}}, + {'darkgoldenrod 1': {'hex': '#FFB90F', 'rgb': ('255', '185', '15')}}, + {'darkgoldenrod 2': {'hex': '#EEAD0E', 'rgb': ('238', '173', '14')}}, + {'darkgoldenrod 3': {'hex': '#CD950C', 'rgb': ('205', '149', '12')}}, + {'darkgoldenrod 4': {'hex': '#8B6508', 'rgb': ('139', '101', '8')}}, + {'orange 1 (orange)': {'hex': '#FFA500', 'rgb': ('255', '165', '0')}}, + {'orange 2': {'hex': '#EE9A00', 'rgb': ('238', '154', '0')}}, + {'orange 3': {'hex': '#CD8500', 'rgb': ('205', '133', '0')}}, + {'orange 4': {'hex': '#8B5A00', 'rgb': ('139', '90', '0')}}, + {'floralwhite': {'hex': '#FFFAF0', 'rgb': ('255', '250', '240')}}, + {'oldlace': {'hex': '#FDF5E6', 'rgb': ('253', '245', '230')}}, + {'wheat': {'hex': '#F5DEB3', 'rgb': ('245', '222', '179')}}, + {'wheat 1': {'hex': '#FFE7BA', 'rgb': ('255', '231', '186')}}, + {'wheat 2': {'hex': '#EED8AE', 'rgb': ('238', '216', '174')}}, + {'wheat 3': {'hex': '#CDBA96', 'rgb': ('205', '186', '150')}}, + {'wheat 4': {'hex': '#8B7E66', 'rgb': ('139', '126', '102')}}, + {'moccasin': {'hex': '#FFE4B5', 'rgb': ('255', '228', '181')}}, + {'papayawhip': {'hex': '#FFEFD5', 'rgb': ('255', '239', '213')}}, + {'blanchedalmond': {'hex': '#FFEBCD', 'rgb': ('255', '235', '205')}}, + {'navajowhite 1 (navajowhite)': {'hex': '#FFDEAD', 'rgb': ('255', '222', '173')}}, + {'navajowhite 2': {'hex': '#EECFA1', 'rgb': ('238', '207', '161')}}, + {'navajowhite 3': {'hex': '#CDB38B', 'rgb': ('205', '179', '139')}}, + {'navajowhite 4': {'hex': '#8B795E', 'rgb': ('139', '121', '94')}}, + {'eggshell': {'hex': '#FCE6C9', 'rgb': ('252', '230', '201')}}, + {'tan': {'hex': '#D2B48C', 'rgb': ('210', '180', '140')}}, + {'brick': {'hex': '#9C661F', 'rgb': ('156', '102', '31')}}, + {'cadmiumyellow': {'hex': '#FF9912', 'rgb': ('255', '153', '18')}}, + {'antiquewhite': {'hex': '#FAEBD7', 'rgb': ('250', '235', '215')}}, + {'antiquewhite 1': {'hex': '#FFEFDB', 'rgb': ('255', '239', '219')}}, + {'antiquewhite 2': {'hex': '#EEDFCC', 'rgb': ('238', '223', '204')}}, + {'antiquewhite 3': {'hex': '#CDC0B0', 'rgb': ('205', '192', '176')}}, + {'antiquewhite 4': {'hex': '#8B8378', 'rgb': ('139', '131', '120')}}, + {'burlywood': {'hex': '#DEB887', 'rgb': ('222', '184', '135')}}, + {'burlywood 1': {'hex': '#FFD39B', 'rgb': ('255', '211', '155')}}, + {'burlywood 2': {'hex': '#EEC591', 'rgb': ('238', '197', '145')}}, + {'burlywood 3': {'hex': '#CDAA7D', 'rgb': ('205', '170', '125')}}, + {'burlywood 4': {'hex': '#8B7355', 'rgb': ('139', '115', '85')}}, + {'bisque 1 (bisque)': {'hex': '#FFE4C4', 'rgb': ('255', '228', '196')}}, + {'bisque 2': {'hex': '#EED5B7', 'rgb': ('238', '213', '183')}}, + {'bisque 3': {'hex': '#CDB79E', 'rgb': ('205', '183', '158')}}, + {'bisque 4': {'hex': '#8B7D6B', 'rgb': ('139', '125', '107')}}, + {'melon': {'hex': '#E3A869', 'rgb': ('227', '168', '105')}}, + {'carrot': {'hex': '#ED9121', 'rgb': ('237', '145', '33')}}, + {'darkorange': {'hex': '#FF8C00', 'rgb': ('255', '140', '0')}}, + {'darkorange 1': {'hex': '#FF7F00', 'rgb': ('255', '127', '0')}}, + {'darkorange 2': {'hex': '#EE7600', 'rgb': ('238', '118', '0')}}, + {'darkorange 3': {'hex': '#CD6600', 'rgb': ('205', '102', '0')}}, + {'darkorange 4': {'hex': '#8B4500', 'rgb': ('139', '69', '0')}}, + {'orange': {'hex': '#FF8000', 'rgb': ('255', '128', '0')}}, + {'tan 1': {'hex': '#FFA54F', 'rgb': ('255', '165', '79')}}, + {'tan 2': {'hex': '#EE9A49', 'rgb': ('238', '154', '73')}}, + {'tan 3 (peru)': {'hex': '#CD853F', 'rgb': ('205', '133', '63')}}, + {'tan 4': {'hex': '#8B5A2B', 'rgb': ('139', '90', '43')}}, + {'linen': {'hex': '#FAF0E6', 'rgb': ('250', '240', '230')}}, + {'peachpuff 1 (peachpuff)': {'hex': '#FFDAB9', 'rgb': ('255', '218', '185')}}, + {'peachpuff 2': {'hex': '#EECBAD', 'rgb': ('238', '203', '173')}}, + {'peachpuff 3': {'hex': '#CDAF95', 'rgb': ('205', '175', '149')}}, + {'peachpuff 4': {'hex': '#8B7765', 'rgb': ('139', '119', '101')}}, + {'seashell 1 (seashell)': {'hex': '#FFF5EE', 'rgb': ('255', '245', '238')}}, + {'seashell 2': {'hex': '#EEE5DE', 'rgb': ('238', '229', '222')}}, + {'seashell 3': {'hex': '#CDC5BF', 'rgb': ('205', '197', '191')}}, + {'seashell 4': {'hex': '#8B8682', 'rgb': ('139', '134', '130')}}, + {'sandybrown': {'hex': '#F4A460', 'rgb': ('244', '164', '96')}}, + {'rawsienna': {'hex': '#C76114', 'rgb': ('199', '97', '20')}}, + {'chocolate': {'hex': '#D2691E', 'rgb': ('210', '105', '30')}}, + {'chocolate 1': {'hex': '#FF7F24', 'rgb': ('255', '127', '36')}}, + {'chocolate 2': {'hex': '#EE7621', 'rgb': ('238', '118', '33')}}, + {'chocolate 3': {'hex': '#CD661D', 'rgb': ('205', '102', '29')}}, + {'chocolate 4 (saddlebrown)': {'hex': '#8B4513', 'rgb': ('139', '69', '19')}}, + {'ivoryblack': {'hex': '#292421', 'rgb': ('41', '36', '33')}}, + {'flesh': {'hex': '#FF7D40', 'rgb': ('255', '125', '64')}}, + {'cadmiumorange': {'hex': '#FF6103', 'rgb': ('255', '97', '3')}}, + {'burntsienna': {'hex': '#8A360F', 'rgb': ('138', '54', '15')}}, + {'sienna': {'hex': '#A0522D', 'rgb': ('160', '82', '45')}}, + {'sienna 1': {'hex': '#FF8247', 'rgb': ('255', '130', '71')}}, + {'sienna 2': {'hex': '#EE7942', 'rgb': ('238', '121', '66')}}, + {'sienna 3': {'hex': '#CD6839', 'rgb': ('205', '104', '57')}}, + {'sienna 4': {'hex': '#8B4726', 'rgb': ('139', '71', '38')}}, + {'lightsalmon 1 (lightsalmon)': {'hex': '#FFA07A', 'rgb': ('255', '160', '122')}}, + {'lightsalmon 2': {'hex': '#EE9572', 'rgb': ('238', '149', '114')}}, + {'lightsalmon 3': {'hex': '#CD8162', 'rgb': ('205', '129', '98')}}, + {'lightsalmon 4': {'hex': '#8B5742', 'rgb': ('139', '87', '66')}}, + {'coral': {'hex': '#FF7F50', 'rgb': ('255', '127', '80')}}, + {'orangered 1 (orangered)': {'hex': '#FF4500', 'rgb': ('255', '69', '0')}}, + {'orangered 2': {'hex': '#EE4000', 'rgb': ('238', '64', '0')}}, + {'orangered 3': {'hex': '#CD3700', 'rgb': ('205', '55', '0')}}, + {'orangered 4': {'hex': '#8B2500', 'rgb': ('139', '37', '0')}}, + {'sepia': {'hex': '#5E2612', 'rgb': ('94', '38', '18')}}, + {'darksalmon': {'hex': '#E9967A', 'rgb': ('233', '150', '122')}}, + {'salmon 1': {'hex': '#FF8C69', 'rgb': ('255', '140', '105')}}, + {'salmon 2': {'hex': '#EE8262', 'rgb': ('238', '130', '98')}}, + {'salmon 3': {'hex': '#CD7054', 'rgb': ('205', '112', '84')}}, + {'salmon 4': {'hex': '#8B4C39', 'rgb': ('139', '76', '57')}}, + {'coral 1': {'hex': '#FF7256', 'rgb': ('255', '114', '86')}}, + {'coral 2': {'hex': '#EE6A50', 'rgb': ('238', '106', '80')}}, + {'coral 3': {'hex': '#CD5B45', 'rgb': ('205', '91', '69')}}, + {'coral 4': {'hex': '#8B3E2F', 'rgb': ('139', '62', '47')}}, + {'burntumber': {'hex': '#8A3324', 'rgb': ('138', '51', '36')}}, + {'tomato 1 (tomato)': {'hex': '#FF6347', 'rgb': ('255', '99', '71')}}, + {'tomato 2': {'hex': '#EE5C42', 'rgb': ('238', '92', '66')}}, + {'tomato 3': {'hex': '#CD4F39', 'rgb': ('205', '79', '57')}}, + {'tomato 4': {'hex': '#8B3626', 'rgb': ('139', '54', '38')}}, + {'salmon': {'hex': '#FA8072', 'rgb': ('250', '128', '114')}}, + {'mistyrose 1 (mistyrose)': {'hex': '#FFE4E1', 'rgb': ('255', '228', '225')}}, + {'mistyrose 2': {'hex': '#EED5D2', 'rgb': ('238', '213', '210')}}, + {'mistyrose 3': {'hex': '#CDB7B5', 'rgb': ('205', '183', '181')}}, + {'mistyrose 4': {'hex': '#8B7D7B', 'rgb': ('139', '125', '123')}}, + {'snow 1 (snow)': {'hex': '#FFFAFA', 'rgb': ('255', '250', '250')}}, + {'snow 2': {'hex': '#EEE9E9', 'rgb': ('238', '233', '233')}}, + {'snow 3': {'hex': '#CDC9C9', 'rgb': ('205', '201', '201')}}, + {'snow 4': {'hex': '#8B8989', 'rgb': ('139', '137', '137')}}, + {'rosybrown': {'hex': '#BC8F8F', 'rgb': ('188', '143', '143')}}, + {'rosybrown 1': {'hex': '#FFC1C1', 'rgb': ('255', '193', '193')}}, + {'rosybrown 2': {'hex': '#EEB4B4', 'rgb': ('238', '180', '180')}}, + {'rosybrown 3': {'hex': '#CD9B9B', 'rgb': ('205', '155', '155')}}, + {'rosybrown 4': {'hex': '#8B6969', 'rgb': ('139', '105', '105')}}, + {'lightcoral': {'hex': '#F08080', 'rgb': ('240', '128', '128')}}, + {'indianred': {'hex': '#CD5C5C', 'rgb': ('205', '92', '92')}}, + {'indianred 1': {'hex': '#FF6A6A', 'rgb': ('255', '106', '106')}}, + {'indianred 2': {'hex': '#EE6363', 'rgb': ('238', '99', '99')}}, + {'indianred 4': {'hex': '#8B3A3A', 'rgb': ('139', '58', '58')}}, + {'indianred 3': {'hex': '#CD5555', 'rgb': ('205', '85', '85')}}, + {'brown': {'hex': '#A52A2A', 'rgb': ('165', '42', '42')}}, + {'brown 1': {'hex': '#FF4040', 'rgb': ('255', '64', '64')}}, + {'brown 2': {'hex': '#EE3B3B', 'rgb': ('238', '59', '59')}}, + {'brown 3': {'hex': '#CD3333', 'rgb': ('205', '51', '51')}}, + {'brown 4': {'hex': '#8B2323', 'rgb': ('139', '35', '35')}}, + {'firebrick': {'hex': '#B22222', 'rgb': ('178', '34', '34')}}, + {'firebrick 1': {'hex': '#FF3030', 'rgb': ('255', '48', '48')}}, + {'firebrick 2': {'hex': '#EE2C2C', 'rgb': ('238', '44', '44')}}, + {'firebrick 3': {'hex': '#CD2626', 'rgb': ('205', '38', '38')}}, + {'firebrick 4': {'hex': '#8B1A1A', 'rgb': ('139', '26', '26')}}, + {'red 1 (red)': {'hex': '#FF0000', 'rgb': ('255', '0', '0')}}, + {'red 2': {'hex': '#EE0000', 'rgb': ('238', '0', '0')}}, + {'red 3': {'hex': '#CD0000', 'rgb': ('205', '0', '0')}}, + {'red 4 (darkred)': {'hex': '#8B0000', 'rgb': ('139', '0', '0')}}, + {'maroon': {'hex': '#800000', 'rgb': ('128', '0', '0')}}, + {'sgi beet': {'hex': '#8E388E', 'rgb': ('142', '56', '142')}}, + {'sgi slateblue': {'hex': '#7171C6', 'rgb': ('113', '113', '198')}}, + {'sgi lightblue': {'hex': '#7D9EC0', 'rgb': ('125', '158', '192')}}, + {'sgi teal': {'hex': '#388E8E', 'rgb': ('56', '142', '142')}}, + {'sgi chartreuse': {'hex': '#71C671', 'rgb': ('113', '198', '113')}}, + {'sgi olivedrab': {'hex': '#8E8E38', 'rgb': ('142', '142', '56')}}, + {'sgi brightgray': {'hex': '#C5C1AA', 'rgb': ('197', '193', '170')}}, + {'sgi salmon': {'hex': '#C67171', 'rgb': ('198', '113', '113')}}, + {'sgi darkgray': {'hex': '#555555', 'rgb': ('85', '85', '85')}}, + {'sgi gray 12': {'hex': '#1E1E1E', 'rgb': ('30', '30', '30')}}, + {'sgi gray 16': {'hex': '#282828', 'rgb': ('40', '40', '40')}}, + {'sgi gray 32': {'hex': '#515151', 'rgb': ('81', '81', '81')}}, + {'sgi gray 36': {'hex': '#5B5B5B', 'rgb': ('91', '91', '91')}}, + {'sgi gray 52': {'hex': '#848484', 'rgb': ('132', '132', '132')}}, + {'sgi gray 56': {'hex': '#8E8E8E', 'rgb': ('142', '142', '142')}}, + {'sgi lightgray': {'hex': '#AAAAAA', 'rgb': ('170', '170', '170')}}, + {'sgi gray 72': {'hex': '#B7B7B7', 'rgb': ('183', '183', '183')}}, + {'sgi gray 76': {'hex': '#C1C1C1', 'rgb': ('193', '193', '193')}}, + {'sgi gray 92': {'hex': '#EAEAEA', 'rgb': ('234', '234', '234')}}, + {'sgi gray 96': {'hex': '#F4F4F4', 'rgb': ('244', '244', '244')}}, + {'white': {'hex': '#FFFFFF', 'rgb': ('255', '255', '255')}}, + {'white smoke (gray 96)': {'hex': '#F5F5F5', 'rgb': ('245', '245', '245')}}, + {'gainsboro': {'hex': '#DCDCDC', 'rgb': ('220', '220', '220')}}, + {'lightgrey': {'hex': '#D3D3D3', 'rgb': ('211', '211', '211')}}, + {'silver': {'hex': '#C0C0C0', 'rgb': ('192', '192', '192')}}, + {'darkgray': {'hex': '#A9A9A9', 'rgb': ('169', '169', '169')}}, + {'gray': {'hex': '#808080', 'rgb': ('128', '128', '128')}}, + {'dimgray (gray 42)': {'hex': '#696969', 'rgb': ('105', '105', '105')}}, + {'black': {'hex': '#000000', 'rgb': ('0', '0', '0')}}, + {'gray 99': {'hex': '#FCFCFC', 'rgb': ('252', '252', '252')}}, + {'gray 98': {'hex': '#FAFAFA', 'rgb': ('250', '250', '250')}}, + {'gray 97': {'hex': '#F7F7F7', 'rgb': ('247', '247', '247')}}, + {'white smoke (gray 96)': {'hex': '#F5F5F5', 'rgb': ('245', '245', '245')}}, + {'gray 95': {'hex': '#F2F2F2', 'rgb': ('242', '242', '242')}}, + {'gray 94': {'hex': '#F0F0F0', 'rgb': ('240', '240', '240')}}, + {'gray 93': {'hex': '#EDEDED', 'rgb': ('237', '237', '237')}}, + {'gray 92': {'hex': '#EBEBEB', 'rgb': ('235', '235', '235')}}, + {'gray 91': {'hex': '#E8E8E8', 'rgb': ('232', '232', '232')}}, + {'gray 90': {'hex': '#E5E5E5', 'rgb': ('229', '229', '229')}}, + {'gray 89': {'hex': '#E3E3E3', 'rgb': ('227', '227', '227')}}, + {'gray 88': {'hex': '#E0E0E0', 'rgb': ('224', '224', '224')}}, + {'gray 87': {'hex': '#DEDEDE', 'rgb': ('222', '222', '222')}}, + {'gray 86': {'hex': '#DBDBDB', 'rgb': ('219', '219', '219')}}, + {'gray 85': {'hex': '#D9D9D9', 'rgb': ('217', '217', '217')}}, + {'gray 84': {'hex': '#D6D6D6', 'rgb': ('214', '214', '214')}}, + {'gray 83': {'hex': '#D4D4D4', 'rgb': ('212', '212', '212')}}, + {'gray 82': {'hex': '#D1D1D1', 'rgb': ('209', '209', '209')}}, + {'gray 81': {'hex': '#CFCFCF', 'rgb': ('207', '207', '207')}}, + {'gray 80': {'hex': '#CCCCCC', 'rgb': ('204', '204', '204')}}, + {'gray 79': {'hex': '#C9C9C9', 'rgb': ('201', '201', '201')}}, + {'gray 78': {'hex': '#C7C7C7', 'rgb': ('199', '199', '199')}}, + {'gray 77': {'hex': '#C4C4C4', 'rgb': ('196', '196', '196')}}, + {'gray 76': {'hex': '#C2C2C2', 'rgb': ('194', '194', '194')}}, + {'gray 75': {'hex': '#BFBFBF', 'rgb': ('191', '191', '191')}}, + {'gray 74': {'hex': '#BDBDBD', 'rgb': ('189', '189', '189')}}, + {'gray 73': {'hex': '#BABABA', 'rgb': ('186', '186', '186')}}, + {'gray 72': {'hex': '#B8B8B8', 'rgb': ('184', '184', '184')}}, + {'gray 71': {'hex': '#B5B5B5', 'rgb': ('181', '181', '181')}}, + {'gray 70': {'hex': '#B3B3B3', 'rgb': ('179', '179', '179')}}, + {'gray 69': {'hex': '#B0B0B0', 'rgb': ('176', '176', '176')}}, + {'gray 68': {'hex': '#ADADAD', 'rgb': ('173', '173', '173')}}, + {'gray 67': {'hex': '#ABABAB', 'rgb': ('171', '171', '171')}}, + {'gray 66': {'hex': '#A8A8A8', 'rgb': ('168', '168', '168')}}, + {'gray 65': {'hex': '#A6A6A6', 'rgb': ('166', '166', '166')}}, + {'gray 64': {'hex': '#A3A3A3', 'rgb': ('163', '163', '163')}}, + {'gray 63': {'hex': '#A1A1A1', 'rgb': ('161', '161', '161')}}, + {'gray 62': {'hex': '#9E9E9E', 'rgb': ('158', '158', '158')}}, + {'gray 61': {'hex': '#9C9C9C', 'rgb': ('156', '156', '156')}}, + {'gray 60': {'hex': '#999999', 'rgb': ('153', '153', '153')}}, + {'gray 59': {'hex': '#969696', 'rgb': ('150', '150', '150')}}, + {'gray 58': {'hex': '#949494', 'rgb': ('148', '148', '148')}}, + {'gray 57': {'hex': '#919191', 'rgb': ('145', '145', '145')}}, + {'gray 56': {'hex': '#8F8F8F', 'rgb': ('143', '143', '143')}}, + {'gray 55': {'hex': '#8C8C8C', 'rgb': ('140', '140', '140')}}, + {'gray 54': {'hex': '#8A8A8A', 'rgb': ('138', '138', '138')}}, + {'gray 53': {'hex': '#878787', 'rgb': ('135', '135', '135')}}, + {'gray 52': {'hex': '#858585', 'rgb': ('133', '133', '133')}}, + {'gray 51': {'hex': '#828282', 'rgb': ('130', '130', '130')}}, + {'gray 50': {'hex': '#7F7F7F', 'rgb': ('127', '127', '127')}}, + {'gray 49': {'hex': '#7D7D7D', 'rgb': ('125', '125', '125')}}, + {'gray 48': {'hex': '#7A7A7A', 'rgb': ('122', '122', '122')}}, + {'gray 47': {'hex': '#787878', 'rgb': ('120', '120', '120')}}, + {'gray 46': {'hex': '#757575', 'rgb': ('117', '117', '117')}}, + {'gray 45': {'hex': '#737373', 'rgb': ('115', '115', '115')}}, + {'gray 44': {'hex': '#707070', 'rgb': ('112', '112', '112')}}, + {'gray 43': {'hex': '#6E6E6E', 'rgb': ('110', '110', '110')}}, + {'gray 42': {'hex': '#6B6B6B', 'rgb': ('107', '107', '107')}}, + {'dimgray (gray 42)': {'hex': '#696969', 'rgb': ('105', '105', '105')}}, + {'gray 40': {'hex': '#666666', 'rgb': ('102', '102', '102')}}, + {'gray 39': {'hex': '#636363', 'rgb': ('99', '99', '99')}}, + {'gray 38': {'hex': '#616161', 'rgb': ('97', '97', '97')}}, + {'gray 37': {'hex': '#5E5E5E', 'rgb': ('94', '94', '94')}}, + {'gray 36': {'hex': '#5C5C5C', 'rgb': ('92', '92', '92')}}, + {'gray 35': {'hex': '#595959', 'rgb': ('89', '89', '89')}}, + {'gray 34': {'hex': '#575757', 'rgb': ('87', '87', '87')}}, + {'gray 33': {'hex': '#545454', 'rgb': ('84', '84', '84')}}, + {'gray 32': {'hex': '#525252', 'rgb': ('82', '82', '82')}}, + {'gray 31': {'hex': '#4F4F4F', 'rgb': ('79', '79', '79')}}, + {'gray 30': {'hex': '#4D4D4D', 'rgb': ('77', '77', '77')}}, + {'gray 29': {'hex': '#4A4A4A', 'rgb': ('74', '74', '74')}}, + {'gray 28': {'hex': '#474747', 'rgb': ('71', '71', '71')}}, + {'gray 27': {'hex': '#454545', 'rgb': ('69', '69', '69')}}, + {'gray 26': {'hex': '#424242', 'rgb': ('66', '66', '66')}}, + {'gray 25': {'hex': '#404040', 'rgb': ('64', '64', '64')}}, + {'gray 24': {'hex': '#3D3D3D', 'rgb': ('61', '61', '61')}}, + {'gray 23': {'hex': '#3B3B3B', 'rgb': ('59', '59', '59')}}, + {'gray 22': {'hex': '#383838', 'rgb': ('56', '56', '56')}}, + {'gray 21': {'hex': '#363636', 'rgb': ('54', '54', '54')}}, + {'gray 20': {'hex': '#333333', 'rgb': ('51', '51', '51')}}, + {'gray 19': {'hex': '#303030', 'rgb': ('48', '48', '48')}}, + {'gray 18': {'hex': '#2E2E2E', 'rgb': ('46', '46', '46')}}, + {'gray 17': {'hex': '#2B2B2B', 'rgb': ('43', '43', '43')}}, + {'gray 16': {'hex': '#292929', 'rgb': ('41', '41', '41')}}, + {'gray 15': {'hex': '#262626', 'rgb': ('38', '38', '38')}}, + {'gray 14': {'hex': '#242424', 'rgb': ('36', '36', '36')}}, + {'gray 13': {'hex': '#212121', 'rgb': ('33', '33', '33')}}, + {'gray 12': {'hex': '#1F1F1F', 'rgb': ('31', '31', '31')}}, + {'gray 11': {'hex': '#1C1C1C', 'rgb': ('28', '28', '28')}}, + {'gray 10': {'hex': '#1A1A1A', 'rgb': ('26', '26', '26')}}, + {'gray 9': {'hex': '#171717', 'rgb': ('23', '23', '23')}}, + {'gray 8': {'hex': '#141414', 'rgb': ('20', '20', '20')}}, + {'gray 7': {'hex': '#121212', 'rgb': ('18', '18', '18')}}, + {'gray 6': {'hex': '#0F0F0F', 'rgb': ('15', '15', '15')}}, + {'gray 5': {'hex': '#0D0D0D', 'rgb': ('13', '13', '13')}}, + {'gray 4': {'hex': '#0A0A0A', 'rgb': ('10', '10', '10')}}, + {'gray 3': {'hex': '#080808', 'rgb': ('8', '8', '8')}}, + {'gray 2': {'hex': '#050505', 'rgb': ('5', '5', '5')}}, + {'gray 1': {'hex': '#030303', 'rgb': ('3', '3', '3')}}, +) + +named_colors = ( + {'lightpink': {'hex': '#FFB6C1', 'rgb': ('255', '182', '193')}}, + {'pink': {'hex': '#FFC0CB', 'rgb': ('255', '192', '203')}}, + {'crimson': {'hex': '#DC143C', 'rgb': ('220', '20', '60')}}, + {'lavenderblush': {'hex': '#FFF0F5', 'rgb': ('255', '240', '245')}}, + {'palevioletred': {'hex': '#DB7093', 'rgb': ('219', '112', '147')}}, + {'hotpink': {'hex': '#FF69B4', 'rgb': ('255', '105', '180')}}, + {'deeppink': {'hex': '#FF1493', 'rgb': ('255', '20', '147')}}, + {'mediumvioletred': {'hex': '#C71585', 'rgb': ('199', '21', '133')}}, + {'orchid': {'hex': '#DA70D6', 'rgb': ('218', '112', '214')}}, + {'thistle': {'hex': '#D8BFD8', 'rgb': ('216', '191', '216')}}, + {'plum': {'hex': '#DDA0DD', 'rgb': ('221', '160', '221')}}, + {'violet': {'hex': '#EE82EE', 'rgb': ('238', '130', '238')}}, + {'fuchsia': {'hex': '#FF00FF', 'rgb': ('255', '0', '255')}}, + {'fuchsia': {'hex': '#FF00FF', 'rgb': ('255', '0', '255')}}, + {'darkmagenta': {'hex': '#8B008B', 'rgb': ('139', '0', '139')}}, + {'purple': {'hex': '#800080', 'rgb': ('128', '0', '128')}}, + {'mediumorchid': {'hex': '#BA55D3', 'rgb': ('186', '85', '211')}}, + {'darkviolet': {'hex': '#9400D3', 'rgb': ('148', '0', '211')}}, + {'darkorchid': {'hex': '#9932CC', 'rgb': ('153', '50', '204')}}, + {'indigo': {'hex': '#4B0082', 'rgb': ('75', '0', '130')}}, + {'blueviolet': {'hex': '#8A2BE2', 'rgb': ('138', '43', '226')}}, + {'mediumpurple': {'hex': '#9370DB', 'rgb': ('147', '112', '219')}}, + {'mediumslateblue': {'hex': '#7B68EE', 'rgb': ('123', '104', '238')}}, + {'slateblue': {'hex': '#6A5ACD', 'rgb': ('106', '90', '205')}}, + {'darkslateblue': {'hex': '#483D8B', 'rgb': ('72', '61', '139')}}, + {'ghostwhite': {'hex': '#F8F8FF', 'rgb': ('248', '248', '255')}}, + {'lavender': {'hex': '#E6E6FA', 'rgb': ('230', '230', '250')}}, + {'blue': {'hex': '#0000FF', 'rgb': ('0', '0', '255')}}, + {'mediumblue': {'hex': '#0000CD', 'rgb': ('0', '0', '205')}}, + {'darkblue': {'hex': '#00008B', 'rgb': ('0', '0', '139')}}, + {'navy': {'hex': '#000080', 'rgb': ('0', '0', '128')}}, + {'midnightblue': {'hex': '#191970', 'rgb': ('25', '25', '112')}}, + {'royalblue': {'hex': '#4169E1', 'rgb': ('65', '105', '225')}}, + {'cornflowerblue': {'hex': '#6495ED', 'rgb': ('100', '149', '237')}}, + {'lightsteelblue': {'hex': '#B0C4DE', 'rgb': ('176', '196', '222')}}, + {'lightslategray': {'hex': '#778899', 'rgb': ('119', '136', '153')}}, + {'slategray': {'hex': '#708090', 'rgb': ('112', '128', '144')}}, + {'dodgerblue': {'hex': '#1E90FF', 'rgb': ('30', '144', '255')}}, + {'aliceblue': {'hex': '#F0F8FF', 'rgb': ('240', '248', '255')}}, + {'steelblue': {'hex': '#4682B4', 'rgb': ('70', '130', '180')}}, + {'lightskyblue': {'hex': '#87CEFA', 'rgb': ('135', '206', '250')}}, + {'skyblue': {'hex': '#87CEEB', 'rgb': ('135', '206', '235')}}, + {'deepskyblue': {'hex': '#00BFFF', 'rgb': ('0', '191', '255')}}, + {'lightblue': {'hex': '#ADD8E6', 'rgb': ('173', '216', '230')}}, + {'powderblue': {'hex': '#B0E0E6', 'rgb': ('176', '224', '230')}}, + {'cadetblue': {'hex': '#5F9EA0', 'rgb': ('95', '158', '160')}}, + {'darkturquoise': {'hex': '#00CED1', 'rgb': ('0', '206', '209')}}, + {'azure': {'hex': '#F0FFFF', 'rgb': ('240', '255', '255')}}, + {'lightcyan': {'hex': '#E0FFFF', 'rgb': ('224', '255', '255')}}, + {'paleturquoise': {'hex': '#AFEEEE', 'rgb': ('175', '238', '238')}}, + {'aqua': {'hex': '#00FFFF', 'rgb': ('0', '255', '255')}}, + {'aqua': {'hex': '#00FFFF', 'rgb': ('0', '255', '255')}}, + {'darkcyan': {'hex': '#008B8B', 'rgb': ('0', '139', '139')}}, + {'teal': {'hex': '#008080', 'rgb': ('0', '128', '128')}}, + {'darkslategray': {'hex': '#2F4F4F', 'rgb': ('47', '79', '79')}}, + {'mediumturquoise': {'hex': '#48D1CC', 'rgb': ('72', '209', '204')}}, + {'lightseagreen': {'hex': '#20B2AA', 'rgb': ('32', '178', '170')}}, + {'turquoise': {'hex': '#40E0D0', 'rgb': ('64', '224', '208')}}, + {'aquamarine': {'hex': '#7FFFD4', 'rgb': ('127', '255', '212')}}, + {'mediumaquamarine': {'hex': '#66CDAA', 'rgb': ('102', '205', '170')}}, + {'mediumspringgreen': {'hex': '#00FA9A', 'rgb': ('0', '250', '154')}}, + {'mintcream': {'hex': '#F5FFFA', 'rgb': ('245', '255', '250')}}, + {'springgreen': {'hex': '#00FF7F', 'rgb': ('0', '255', '127')}}, + {'mediumseagreen': {'hex': '#3CB371', 'rgb': ('60', '179', '113')}}, + {'seagreen': {'hex': '#2E8B57', 'rgb': ('46', '139', '87')}}, + {'honeydew': {'hex': '#F0FFF0', 'rgb': ('240', '255', '240')}}, + {'darkseagreen': {'hex': '#8FBC8F', 'rgb': ('143', '188', '143')}}, + {'palegreen': {'hex': '#98FB98', 'rgb': ('152', '251', '152')}}, + {'lightgreen': {'hex': '#90EE90', 'rgb': ('144', '238', '144')}}, + {'limegreen': {'hex': '#32CD32', 'rgb': ('50', '205', '50')}}, + {'lime': {'hex': '#00FF00', 'rgb': ('0', '255', '0')}}, + {'forestgreen': {'hex': '#228B22', 'rgb': ('34', '139', '34')}}, + {'green': {'hex': '#008000', 'rgb': ('0', '128', '0')}}, + {'darkgreen': {'hex': '#006400', 'rgb': ('0', '100', '0')}}, + {'lawngreen': {'hex': '#7CFC00', 'rgb': ('124', '252', '0')}}, + {'chartreuse': {'hex': '#7FFF00', 'rgb': ('127', '255', '0')}}, + {'greenyellow': {'hex': '#ADFF2F', 'rgb': ('173', '255', '47')}}, + {'darkolivegreen': {'hex': '#556B2F', 'rgb': ('85', '107', '47')}}, + {'yellowgreen': {'hex': '#9ACD32', 'rgb': ('154', '205', '50')}}, + {'olivedrab': {'hex': '#6B8E23', 'rgb': ('107', '142', '35')}}, + {'ivory': {'hex': '#FFFFF0', 'rgb': ('255', '255', '240')}}, + {'beige': {'hex': '#F5F5DC', 'rgb': ('245', '245', '220')}}, + {'lightyellow': {'hex': '#FFFFE0', 'rgb': ('255', '255', '224')}}, + {'lightgoldenrodyellow': {'hex': '#FAFAD2', 'rgb': ('250', '250', '210')}}, + {'yellow': {'hex': '#FFFF00', 'rgb': ('255', '255', '0')}}, + {'olive': {'hex': '#808000', 'rgb': ('128', '128', '0')}}, + {'darkkhaki': {'hex': '#BDB76B', 'rgb': ('189', '183', '107')}}, + {'palegoldenrod': {'hex': '#EEE8AA', 'rgb': ('238', '232', '170')}}, + {'lemonchiffon': {'hex': '#FFFACD', 'rgb': ('255', '250', '205')}}, + {'khaki': {'hex': '#F0E68C', 'rgb': ('240', '230', '140')}}, + {'gold': {'hex': '#FFD700', 'rgb': ('255', '215', '0')}}, + {'cornsilk': {'hex': '#FFF8DC', 'rgb': ('255', '248', '220')}}, + {'goldenrod': {'hex': '#DAA520', 'rgb': ('218', '165', '32')}}, + {'darkgoldenrod': {'hex': '#B8860B', 'rgb': ('184', '134', '11')}}, + {'floralwhite': {'hex': '#FFFAF0', 'rgb': ('255', '250', '240')}}, + {'oldlace': {'hex': '#FDF5E6', 'rgb': ('253', '245', '230')}}, + {'wheat': {'hex': '#F5DEB3', 'rgb': ('245', '222', '179')}}, + {'orange': {'hex': '#FFA500', 'rgb': ('255', '165', '0')}}, + {'moccasin': {'hex': '#FFE4B5', 'rgb': ('255', '228', '181')}}, + {'papayawhip': {'hex': '#FFEFD5', 'rgb': ('255', '239', '213')}}, + {'blanchedalmond': {'hex': '#FFEBCD', 'rgb': ('255', '235', '205')}}, + {'navajowhite': {'hex': '#FFDEAD', 'rgb': ('255', '222', '173')}}, + {'antiquewhite': {'hex': '#FAEBD7', 'rgb': ('250', '235', '215')}}, + {'tan': {'hex': '#D2B48C', 'rgb': ('210', '180', '140')}}, + {'burlywood': {'hex': '#DEB887', 'rgb': ('222', '184', '135')}}, + {'darkorange': {'hex': '#FF8C00', 'rgb': ('255', '140', '0')}}, + {'bisque': {'hex': '#FFE4C4', 'rgb': ('255', '228', '196')}}, + {'linen': {'hex': '#FAF0E6', 'rgb': ('250', '240', '230')}}, + {'peru': {'hex': '#CD853F', 'rgb': ('205', '133', '63')}}, + {'peachpuff': {'hex': '#FFDAB9', 'rgb': ('255', '218', '185')}}, + {'sandybrown': {'hex': '#F4A460', 'rgb': ('244', '164', '96')}}, + {'chocolate': {'hex': '#D2691E', 'rgb': ('210', '105', '30')}}, + {'saddlebrown': {'hex': '#8B4513', 'rgb': ('139', '69', '19')}}, + {'seashell': {'hex': '#FFF5EE', 'rgb': ('255', '245', '238')}}, + {'sienna': {'hex': '#A0522D', 'rgb': ('160', '82', '45')}}, + {'lightsalmon': {'hex': '#FFA07A', 'rgb': ('255', '160', '122')}}, + {'coral': {'hex': '#FF7F50', 'rgb': ('255', '127', '80')}}, + {'orangered': {'hex': '#FF4500', 'rgb': ('255', '69', '0')}}, + {'darksalmon': {'hex': '#E9967A', 'rgb': ('233', '150', '122')}}, + {'tomato': {'hex': '#FF6347', 'rgb': ('255', '99', '71')}}, + {'salmon': {'hex': '#FA8072', 'rgb': ('250', '128', '114')}}, + {'mistyrose': {'hex': '#FFE4E1', 'rgb': ('255', '228', '225')}}, + {'lightcoral': {'hex': '#F08080', 'rgb': ('240', '128', '128')}}, + {'snow': {'hex': '#FFFAFA', 'rgb': ('255', '250', '250')}}, + {'rosybrown': {'hex': '#BC8F8F', 'rgb': ('188', '143', '143')}}, + {'indianred': {'hex': '#CD5C5C', 'rgb': ('205', '92', '92')}}, + {'red': {'hex': '#FF0000', 'rgb': ('255', '0', '0')}}, + {'brown': {'hex': '#A52A2A', 'rgb': ('165', '42', '42')}}, + {'firebrick': {'hex': '#B22222', 'rgb': ('178', '34', '34')}}, + {'darkred': {'hex': '#8B0000', 'rgb': ('139', '0', '0')}}, + {'maroon': {'hex': '#800000', 'rgb': ('128', '0', '0')}}, + {'white': {'hex': '#FFFFFF', 'rgb': ('255', '255', '255')}}, + {'whitesmoke': {'hex': '#F5F5F5', 'rgb': ('245', '245', '245')}}, + {'gainsboro': {'hex': '#DCDCDC', 'rgb': ('220', '220', '220')}}, + {'lightgrey': {'hex': '#D3D3D3', 'rgb': ('211', '211', '211')}}, + {'silver': {'hex': '#C0C0C0', 'rgb': ('192', '192', '192')}}, + {'darkgray': {'hex': '#A9A9A9', 'rgb': ('169', '169', '169')}}, + {'gray': {'hex': '#808080', 'rgb': ('128', '128', '128')}}, + {'dimgray': {'hex': '#696969', 'rgb': ('105', '105', '105')}}, + {'black': {'hex': '#000000', 'rgb': ('0', '0', '0')}}, +) + +hue_color = ( + {'hex': '#993366', 'rgb': ('153', '51', '102')}, + {'hex': '#CC6699', 'rgb': ('204', '102', '153')}, + {'hex': '#FF99CC', 'rgb': ('255', '153', '204')}, + {'hex': '#FF0099', 'rgb': ('255', '0', '153')}, + {'hex': '#990066', 'rgb': ('153', '0', '102')}, + {'hex': '#CC3399', 'rgb': ('204', '51', '153')}, + {'hex': '#FF66CC', 'rgb': ('255', '102', '204')}, + {'hex': '#CC0099', 'rgb': ('204', '0', '153')}, + {'hex': '#FF33CC', 'rgb': ('255', '51', '204')}, + {'hex': '#FF00CC', 'rgb': ('255', '0', '204')}, + {'hex': '#FF00FF', 'rgb': ('255', '0', '255')}, + {'hex': '#CC00CC', 'rgb': ('204', '0', '204')}, + {'hex': '#FF33FF', 'rgb': ('255', '51', '255')}, + {'hex': '#990099', 'rgb': ('153', '0', '153')}, + {'hex': '#CC33CC', 'rgb': ('204', '51', '204')}, + {'hex': '#FF66FF', 'rgb': ('255', '102', '255')}, + {'hex': '#660066', 'rgb': ('102', '0', '102')}, + {'hex': '#993399', 'rgb': ('153', '51', '153')}, + {'hex': '#CC66CC', 'rgb': ('204', '102', '204')}, + {'hex': '#FF99FF', 'rgb': ('255', '153', '255')}, + {'hex': '#330033', 'rgb': ('51', '0', '51')}, + {'hex': '#663366', 'rgb': ('102', '51', '102')}, + {'hex': '#996699', 'rgb': ('153', '102', '153')}, + {'hex': '#CC99CC', 'rgb': ('204', '153', '204')}, + {'hex': '#FFCCFF', 'rgb': ('255', '204', '255')}, + {'hex': '#CC00FF', 'rgb': ('204', '0', '255')}, + {'hex': '#9900CC', 'rgb': ('153', '0', '204')}, + {'hex': '#CC33FF', 'rgb': ('204', '51', '255')}, + {'hex': '#660099', 'rgb': ('102', '0', '153')}, + {'hex': '#9933CC', 'rgb': ('153', '51', '204')}, + {'hex': '#CC66FF', 'rgb': ('204', '102', '255')}, + {'hex': '#9900FF', 'rgb': ('153', '0', '255')}, + {'hex': '#330066', 'rgb': ('51', '0', '102')}, + {'hex': '#663399', 'rgb': ('102', '51', '153')}, + {'hex': '#9966CC', 'rgb': ('153', '102', '204')}, + {'hex': '#CC99FF', 'rgb': ('204', '153', '255')}, + {'hex': '#6600CC', 'rgb': ('102', '0', '204')}, + {'hex': '#9933FF', 'rgb': ('153', '51', '255')}, + {'hex': '#6600FF', 'rgb': ('102', '0', '255')}, + {'hex': '#330099', 'rgb': ('51', '0', '153')}, + {'hex': '#6633CC', 'rgb': ('102', '51', '204')}, + {'hex': '#9966FF', 'rgb': ('153', '102', '255')}, + {'hex': '#3300CC', 'rgb': ('51', '0', '204')}, + {'hex': '#6633FF', 'rgb': ('102', '51', '255')}, + {'hex': '#3300FF', 'rgb': ('51', '0', '255')}, + {'hex': '#0000FF', 'rgb': ('0', '0', '255')}, + {'hex': '#0000CC', 'rgb': ('0', '0', '204')}, + {'hex': '#000099', 'rgb': ('0', '0', '153')}, + {'hex': '#000066', 'rgb': ('0', '0', '102')}, + {'hex': '#000033', 'rgb': ('0', '0', '51')}, + {'hex': '#3333FF', 'rgb': ('51', '51', '255')}, + {'hex': '#3333CC', 'rgb': ('51', '51', '204')}, + {'hex': '#333399', 'rgb': ('51', '51', '153')}, + {'hex': '#333366', 'rgb': ('51', '51', '102')}, + {'hex': '#6666FF', 'rgb': ('102', '102', '255')}, + {'hex': '#6666CC', 'rgb': ('102', '102', '204')}, + {'hex': '#666699', 'rgb': ('102', '102', '153')}, + {'hex': '#9999FF', 'rgb': ('153', '153', '255')}, + {'hex': '#9999CC', 'rgb': ('153', '153', '204')}, + {'hex': '#CCCCFF', 'rgb': ('204', '204', '255')}, + {'hex': '#0033FF', 'rgb': ('0', '51', '255')}, + {'hex': '#0033CC', 'rgb': ('0', '51', '204')}, + {'hex': '#3366FF', 'rgb': ('51', '102', '255')}, + {'hex': '#003399', 'rgb': ('0', '51', '153')}, + {'hex': '#3366CC', 'rgb': ('51', '102', '204')}, + {'hex': '#6699FF', 'rgb': ('102', '153', '255')}, + {'hex': '#0066FF', 'rgb': ('0', '102', '255')}, + {'hex': '#0066CC', 'rgb': ('0', '102', '204')}, + {'hex': '#3399FF', 'rgb': ('51', '153', '255')}, + {'hex': '#003366', 'rgb': ('0', '51', '102')}, + {'hex': '#336699', 'rgb': ('51', '102', '153')}, + {'hex': '#6699CC', 'rgb': ('102', '153', '204')}, + {'hex': '#99CCFF', 'rgb': ('153', '204', '255')}, + {'hex': '#0099FF', 'rgb': ('0', '153', '255')}, + {'hex': '#006699', 'rgb': ('0', '102', '153')}, + {'hex': '#3399CC', 'rgb': ('51', '153', '204')}, + {'hex': '#66CCFF', 'rgb': ('102', '204', '255')}, + {'hex': '#0099CC', 'rgb': ('0', '153', '204')}, + {'hex': '#33CCFF', 'rgb': ('51', '204', '255')}, + {'hex': '#00CCFF', 'rgb': ('0', '204', '255')}, + {'hex': '#00FFFF', 'rgb': ('0', '255', '255')}, + {'hex': '#00CCCC', 'rgb': ('0', '204', '204')}, + {'hex': '#009999', 'rgb': ('0', '153', '153')}, + {'hex': '#006666', 'rgb': ('0', '102', '102')}, + {'hex': '#003333', 'rgb': ('0', '51', '51')}, + {'hex': '#33FFFF', 'rgb': ('51', '255', '255')}, + {'hex': '#33CCCC', 'rgb': ('51', '204', '204')}, + {'hex': '#339999', 'rgb': ('51', '153', '153')}, + {'hex': '#336666', 'rgb': ('51', '102', '102')}, + {'hex': '#66FFFF', 'rgb': ('102', '255', '255')}, + {'hex': '#66CCCC', 'rgb': ('102', '204', '204')}, + {'hex': '#669999', 'rgb': ('102', '153', '153')}, + {'hex': '#99FFFF', 'rgb': ('153', '255', '255')}, + {'hex': '#99CCCC', 'rgb': ('153', '204', '204')}, + {'hex': '#CCFFFF', 'rgb': ('204', '255', '255')}, + {'hex': '#00FFCC', 'rgb': ('0', '255', '204')}, + {'hex': '#00CC99', 'rgb': ('0', '204', '153')}, + {'hex': '#33FFCC', 'rgb': ('51', '255', '204')}, + {'hex': '#009966', 'rgb': ('0', '153', '102')}, + {'hex': '#33CC99', 'rgb': ('51', '204', '153')}, + {'hex': '#66FFCC', 'rgb': ('102', '255', '204')}, + {'hex': '#00FF99', 'rgb': ('0', '255', '153')}, + {'hex': '#006633', 'rgb': ('0', '102', '51')}, + {'hex': '#339966', 'rgb': ('51', '153', '102')}, + {'hex': '#66CC99', 'rgb': ('102', '204', '153')}, + {'hex': '#99FFCC', 'rgb': ('153', '255', '204')}, + {'hex': '#00CC66', 'rgb': ('0', '204', '102')}, + {'hex': '#33FF99', 'rgb': ('51', '255', '153')}, + {'hex': '#00FF66', 'rgb': ('0', '255', '102')}, + {'hex': '#009933', 'rgb': ('0', '153', '51')}, + {'hex': '#33CC66', 'rgb': ('51', '204', '102')}, + {'hex': '#66FF99', 'rgb': ('102', '255', '153')}, + {'hex': '#00CC33', 'rgb': ('0', '204', '51')}, + {'hex': '#33FF66', 'rgb': ('51', '255', '102')}, + {'hex': '#00FF33', 'rgb': ('0', '255', '51')}, + {'hex': '#00FF00', 'rgb': ('0', '255', '0')}, + {'hex': '#00CC00', 'rgb': ('0', '204', '0')}, + {'hex': '#009900', 'rgb': ('0', '153', '0')}, + {'hex': '#006600', 'rgb': ('0', '102', '0')}, + {'hex': '#003300', 'rgb': ('0', '51', '0')}, + {'hex': '#33FF33', 'rgb': ('51', '255', '51')}, + {'hex': '#33CC33', 'rgb': ('51', '204', '51')}, + {'hex': '#339933', 'rgb': ('51', '153', '51')}, + {'hex': '#336633', 'rgb': ('51', '102', '51')}, + {'hex': '#66FF66', 'rgb': ('102', '255', '102')}, + {'hex': '#66CC66', 'rgb': ('102', '204', '102')}, + {'hex': '#669966', 'rgb': ('102', '153', '102')}, + {'hex': '#99FF99', 'rgb': ('153', '255', '153')}, + {'hex': '#99CC99', 'rgb': ('153', '204', '153')}, + {'hex': '#CCFFCC', 'rgb': ('204', '255', '204')}, + {'hex': '#33FF00', 'rgb': ('51', '255', '0')}, + {'hex': '#33CC00', 'rgb': ('51', '204', '0')}, + {'hex': '#66FF33', 'rgb': ('102', '255', '51')}, + {'hex': '#339900', 'rgb': ('51', '153', '0')}, + {'hex': '#66CC33', 'rgb': ('102', '204', '51')}, + {'hex': '#99FF66', 'rgb': ('153', '255', '102')}, + {'hex': '#66FF00', 'rgb': ('102', '255', '0')}, + {'hex': '#66CC00', 'rgb': ('102', '204', '0')}, + {'hex': '#99FF33', 'rgb': ('153', '255', '51')}, + {'hex': '#336600', 'rgb': ('51', '102', '0')}, + {'hex': '#669933', 'rgb': ('102', '153', '51')}, + {'hex': '#99CC66', 'rgb': ('153', '204', '102')}, + {'hex': '#CCFF99', 'rgb': ('204', '255', '153')}, + {'hex': '#99FF00', 'rgb': ('153', '255', '0')}, + {'hex': '#669900', 'rgb': ('102', '153', '0')}, + {'hex': '#99CC33', 'rgb': ('153', '204', '51')}, + {'hex': '#CCFF66', 'rgb': ('204', '255', '102')}, + {'hex': '#99CC00', 'rgb': ('153', '204', '0')}, + {'hex': '#CCFF33', 'rgb': ('204', '255', '51')}, + {'hex': '#CCFF00', 'rgb': ('204', '255', '0')}, + {'hex': '#FFFF00', 'rgb': ('255', '255', '0')}, + {'hex': '#CCCC00', 'rgb': ('204', '204', '0')}, + {'hex': '#999900', 'rgb': ('153', '153', '0')}, + {'hex': '#666600', 'rgb': ('102', '102', '0')}, + {'hex': '#333300', 'rgb': ('51', '51', '0')}, + {'hex': '#FFFF33', 'rgb': ('255', '255', '51')}, + {'hex': '#CCCC33', 'rgb': ('204', '204', '51')}, + {'hex': '#999933', 'rgb': ('153', '153', '51')}, + {'hex': '#666633', 'rgb': ('102', '102', '51')}, + {'hex': '#FFFF66', 'rgb': ('255', '255', '102')}, + {'hex': '#CCCC66', 'rgb': ('204', '204', '102')}, + {'hex': '#999966', 'rgb': ('153', '153', '102')}, + {'hex': '#FFFF99', 'rgb': ('255', '255', '153')}, + {'hex': '#CCCC99', 'rgb': ('204', '204', '153')}, + {'hex': '#FFFFCC', 'rgb': ('255', '255', '204')}, + {'hex': '#FFCC00', 'rgb': ('255', '204', '0')}, + {'hex': '#CC9900', 'rgb': ('204', '153', '0')}, + {'hex': '#FFCC33', 'rgb': ('255', '204', '51')}, + {'hex': '#996600', 'rgb': ('153', '102', '0')}, + {'hex': '#CC9933', 'rgb': ('204', '153', '51')}, + {'hex': '#FFCC66', 'rgb': ('255', '204', '102')}, + {'hex': '#FF9900', 'rgb': ('255', '153', '0')}, + {'hex': '#663300', 'rgb': ('102', '51', '0')}, + {'hex': '#996633', 'rgb': ('153', '102', '51')}, + {'hex': '#CC9966', 'rgb': ('204', '153', '102')}, + {'hex': '#FFCC99', 'rgb': ('255', '204', '153')}, + {'hex': '#CC6600', 'rgb': ('204', '102', '0')}, + {'hex': '#FF9933', 'rgb': ('255', '153', '51')}, + {'hex': '#FF6600', 'rgb': ('255', '102', '0')}, + {'hex': '#993300', 'rgb': ('153', '51', '0')}, + {'hex': '#CC6633', 'rgb': ('204', '102', '51')}, + {'hex': '#FF9966', 'rgb': ('255', '153', '102')}, + {'hex': '#CC3300', 'rgb': ('204', '51', '0')}, + {'hex': '#FF6633', 'rgb': ('255', '102', '51')}, + {'hex': '#FF3300', 'rgb': ('255', '51', '0')}, + {'hex': '#FF0000', 'rgb': ('255', '0', '0')}, + {'hex': '#CC0000', 'rgb': ('204', '0', '0')}, + {'hex': '#990000', 'rgb': ('153', '0', '0')}, + {'hex': '#660000', 'rgb': ('102', '0', '0')}, + {'hex': '#330000', 'rgb': ('51', '0', '0')}, + {'hex': '#FF3333', 'rgb': ('255', '51', '51')}, + {'hex': '#CC3333', 'rgb': ('204', '51', '51')}, + {'hex': '#993333', 'rgb': ('153', '51', '51')}, + {'hex': '#663333', 'rgb': ('102', '51', '51')}, + {'hex': '#FF6666', 'rgb': ('255', '102', '102')}, + {'hex': '#CC6666', 'rgb': ('204', '102', '102')}, + {'hex': '#996666', 'rgb': ('153', '102', '102')}, + {'hex': '#FF9999', 'rgb': ('255', '153', '153')}, + {'hex': '#CC9999', 'rgb': ('204', '153', '153')}, + {'hex': '#FFCCCC', 'rgb': ('255', '204', '204')}, + {'hex': '#FF0033', 'rgb': ('255', '0', '51')}, + {'hex': '#CC0033', 'rgb': ('204', '0', '51')}, + {'hex': '#FF3366', 'rgb': ('255', '51', '102')}, + {'hex': '#990033', 'rgb': ('153', '0', '51')}, + {'hex': '#CC3366', 'rgb': ('204', '51', '102')}, + {'hex': '#FF6699', 'rgb': ('255', '102', '153')}, + {'hex': '#FF0066', 'rgb': ('255', '0', '102')}, + {'hex': '#CC0066', 'rgb': ('204', '0', '102')}, + {'hex': '#FF3399', 'rgb': ('255', '51', '153')}, + {'hex': '#660033', 'rgb': ('102', '0', '51')}, + {'hex': '#FFFFFF', 'rgb': ('255', '255', '255')}, + {'hex': '#CCCCCC', 'rgb': ('204', '204', '204')}, + {'hex': '#999999', 'rgb': ('153', '153', '153')}, + {'hex': '#666666', 'rgb': ('102', '102', '102')}, + {'hex': '#333333', 'rgb': ('51', '51', '51')}, + {'hex': '#000000', 'rgb': ('0', '0', '0')}, +) \ No newline at end of file diff --git a/tkmacosx/colorscale.py b/tkmacosx/colorscale.py new file mode 100644 index 0000000..832d891 --- /dev/null +++ b/tkmacosx/colorscale.py @@ -0,0 +1,401 @@ +# Copyright 2020 Saad Mairaj +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +''' +Newer style colorchoosers for tkinter module. + +Version: 0.1.4 +''' + +__version__ = "0.1.4" + +import re +import sys + +if sys.version_info.major == 2: # For python 2.x + import Tkinter as _tk + from tkFont import Font + import tkFont as font +elif sys.version_info.major == 3: # For python 3.x + import tkinter as _tk + from tkinter import font + from tkinter.font import Font + +import colour +import tkmacosx.basewidget as tkb + + +HEX = 'hex' +RGB = 'rgb' + + +def gradient(iteration): + """This function returns a list of HSL values + of all the colors in an order.""" + + ops = {'+': lambda c, step: min(1.0, c + step), + '-': lambda c, step: max(0.0, c - step)} + + index = 0 + operation = '+' + iteration = max(0, iteration-2) + rgb, _list = [1.0, 0.0, 0.0], [] + combo = ((2, 1, 0), (2, 0, 1), (0, 2, 1), (0, 1, 2), (1, 0, 2), (1, 2, 0)) + step = float(len(combo)) / float(iteration) + _list.append('#%02x%02x%02x' % (round(rgb[0]*255), + round(rgb[1]*255), round(rgb[2]*255))) + for i in range(iteration): + if (rgb[combo[index][1]] == 1.0 and operation == '+') or \ + (rgb[combo[index][1]] == 0.0 and operation == '-'): + operation = '-' if operation == '+' else '+' + index += 1 + rgb[combo[index][1]] = ops[operation](rgb[combo[index][1]], step) + _list.append('#%02x%02x%02x' % (round(rgb[0]*255), + round(rgb[1]*255), round(rgb[2]*255))) + _list.append('#%02x%02x%02x' % (round(1.0*255), + round(0.0*255), round(0.0*255))) + return _list + + +class Colorscale(tkb._Canvas): + """ + ## Color Scale. + This is ColorScale alternate to tkinter's colorchooser. + + ### Args: + - `value`: Get either 'RGB' or 'HEX'. + - `command`: callback function with an argument. + - `orient`: Set the orientation. + - `mousewheel`: Set mousewheel to scroll marker. + - `variable`: Give tkinter variable (`StringVar`). + - `showinfo`: Shows hex or rbg while selecting color. + - `gradient`: Take tuple of two colors or default. + - `showinfodelay`: Delay before the showinfo disappears (in ms). + """ + + _features = ('value', 'command', 'orient', 'mousewheel', 'variable', 'showinfo', + 'showinfodelay', 'gradient',) # add more features + + def __init__(self, master=None, cnf={}, **kw): + kw = {k: v for k, v in _tk._cnfmerge( + (cnf, kw)).items() if v is not None} + self.cnf = {k: kw.pop(k, None) + for k in kw.copy() if k in self._features} + + self.cnf['value'] = self.cnf.get('value', 'hex') + self.cnf['orient'] = self.cnf.get('orient', 'vertical') + self.cnf['gradient'] = self.cnf.get('gradient', 'default') + self.cnf['showinfo'] = self.cnf.get('showinfo', True) + self.cnf['showinfodelay'] = self.cnf.get('showinfodelay', 1500) + + kw['width'] = kw.get( + "width", 250 if 'ver' in self.cnf['orient'] else 30) + kw['height'] = kw.get( + "height", 30 if 'ver' in self.cnf['orient'] else 250) + kw['highlightthickness'] = kw.get('highlightthickness', 0) + tkb._Canvas.__init__(self, master=master, cnf={}, **kw) + + # Protected members of the class + self._size = (0, 0) + self._marker_color = 'black' + self._xy = int((self.winfo_reqwidth() if 'ver' in \ + self['orient'] else self.winfo_reqheight()) / 3) + + binds = [{'className': 'set_size', 'sequence': '', 'func': self._set_size}, + {'className': 'b1_on_motion', 'sequence': '', + 'func': self._move_marker}, + {'className': 'b1_press', 'sequence': '', 'func': self._move_marker}] + tkb._bind(self, *binds) + self._set_mousewheel() + + def _set_size(self, evt=None): + """Internal function.""" + if evt.width == self._size[0] and evt.height == self._size[1]: + return + self._size = (evt.width, evt.height) + self._create_items('create', safe_create=True) + + def _callback(self, color): + """Internal function.""" + if self.cnf.get('command'): + self.cnf['command'](color) + if self.cnf.get('variable'): + self.cnf['variable'].set(color) + + def _create_items(self, cmd, safe_create=False, **kw): + """Internal function.\n + Checks and creates (text, marker, + showinfo, gradient lines) items.""" + + def check_tag(tag): + """Internal function.\n + If `cmd="check"` and the tag does not exist then + the tag is created, but if `cmd="create"` and + safe_create=True this will delete the tag if exists + and creates a new tag with same settings.""" + if cmd == 'check': + c = False + elif cmd == 'create': + c = True + else: + raise ValueError( + '%s is not a valid command! Takes -create, -check' % cmd) + cond1 = bool(not self.find('withtag', tag) or c) + cond2 = bool(tag not in kw.get('avoid', [])) + if safe_create and cond1 and cond2: + self.delete(tag) + return cond1 and cond2 + + ids = [] + + if check_tag('gradient'): + w, h = self.winfo_width(), self.winfo_height() + iteration = w if 'ver' in self.cnf['orient'] else h + # iteration -= + if self.cnf.get('gradient', 'default') == 'default': + color_list = gradient(iteration) + elif isinstance(self.cnf.get('gradient'), (list, tuple)): + c1 = colour.Color(self.cnf.get('gradient')[0]) + c2 = colour.Color(self.cnf.get('gradient')[1]) + color_list = c1.range_to(c2, iteration) + elif isinstance(self.cnf.get('gradient'), str): + c = colour.Color(self.cnf.get('gradient')) + color_list = c.range_to(c, iteration) + + for count, c in enumerate(color_list): + if self.cnf['orient'] == 'vertical': + ags = (count, -1, count, h) + elif self.cnf['orient'] == 'horizontal': + ags = (-1, count, w, count) + ids.append(self._create('line', ags, { + 'fill': c, 'tag': 'gradient'})) + + if check_tag('borderline'): + w, h = self.winfo_width(), self.winfo_height() + borderline_points = kw.get('borderline_points', + (1, 1, self.winfo_width()-2, + self.winfo_height()-2, 0)) + ids.append(self.rounded_rect(borderline_points, width=2, + outline='#81b3f4', tag='borderline', + style='arc')) + + if check_tag('marker'): + marker_points = kw.get('marker_points', ( + self._xy if 'ver' in self.cnf['orient'] else 2, + 2 if 'ver' in self.cnf['orient'] else self._xy, + 5 if 'ver' in self.cnf['orient'] else self.winfo_width() - 4, + self.winfo_height() - 4 if 'ver' in self.cnf['orient'] else 5, + 2)) + ids.append(self.rounded_rect(marker_points, width=2, + outline=self._marker_color, + tag="marker", style='arc')) + + if check_tag('markerbg'): + markerbg_points = kw.get('markerbg_points') + cnf = kw.get('markerbg_cnf') + if not markerbg_points: + return None + ids.append(self._rounded(markerbg_points, **cnf)) + + if check_tag('info'): + info_points = kw.get('info_points') + cnf = kw.get('info_cnf') + if not info_points: + return None + ids.append(self._create('text', info_points, cnf)) + + return ids + + def _release(self, evt=None): + """Internal function.""" + self.delete('info', 'markerbg') + + def _move_marker(self, evt, mw=None): + """Internal function.""" + if mw: + evt.x = mw if 'ver' in self.cnf['orient'] else 10 + evt.y = 10 if 'ver' in self.cnf['orient'] else mw + + self.after_cancel(getattr(self, '_remove_id', ' ')) + self._remove_id = self.after(self.cnf['showinfodelay'], self._release) + + cond_x = bool(evt.x > 0 and evt.x < self.winfo_width()) + cond_y = bool(evt.y > 0 and evt.y < self.winfo_height()) + cond_state = bool(self['state'] not in 'disabled') + + if not (cond_x and cond_y and cond_state): + return + + if not mw: + self._xy = evt.x if 'ver' in self.cnf['orient'] else evt.y + + c_id = self.find('overlapping', evt.x, evt.y, evt.x, evt.y) + hexcode = self.itemcget(c_id[0], 'fill') + rgb = [int(i/65535.0*255.0) for i in self.winfo_rgb(hexcode)] + self._marker_color = 'black' if ( + rgb[0]*0.299 + rgb[1]*0.587 + rgb[2]*0.114) > 110 else 'white' + + self._configure(('itemconfigure', 'borderline'), + {'outline': hexcode}, None) + + if self.cnf['value'] == "rgb": + spacer, spacbg = 65, 55 + text = ' | '.join([v+':'+str(f) + for f, v in zip(rgb, ('R', 'G', 'B'))]) + self._callback(rgb) + elif self.cnf['value'] == "hex": + spacer, spacbg = 35, 25 + text = hexcode + self._callback(hexcode) + + ver_cond = evt.x < self.winfo_width() - (spacbg+spacer)\ + and self['orient'] == 'vertical' + hor_cond = evt.y < self.winfo_height() - (spacbg+spacer)\ + and self['orient'] == 'horizontal' + markerbg_points = info_points = () + + if bool(ver_cond or hor_cond) and self['showinfo']: + markerbg_points = ( + evt.x+spacer - spacbg if 'ver' in self.cnf['orient'] else self.winfo_width()/2-6, + self.winfo_height()/2-6 if 'ver' in self.cnf['orient'] else evt.y+spacer-spacbg, + evt.x+spacer + spacbg if 'ver' in self.cnf['orient'] else self.winfo_width()/2+7, + self.winfo_height()/2+7 if 'ver' in self.cnf['orient'] else evt.y+spacer+spacbg, + 6) + + info_points = ( + (evt.x+spacer if 'ver' in self.cnf['orient'] else self.winfo_width()/2, + self.winfo_height()/2 if 'ver' in self.cnf['orient'] else evt.y+spacer)) + + elif self['showinfo']: + markerbg_points = ( + evt.x-spacer - spacbg if 'ver' in self.cnf['orient'] else self.winfo_width()/2-6, + self.winfo_height()/2-6 if 'ver' in self.cnf['orient'] else evt.y-spacer-spacbg, + evt.x-spacer +spacbg if 'ver' in self.cnf['orient'] else self.winfo_width()/2+7, + self.winfo_height()/2+7 if 'ver' in self.cnf['orient'] else evt.y-spacer+spacbg, + 6) + + info_points = ( + (evt.x-spacer if 'ver' in self.cnf['orient'] else self.winfo_width()/2, + self.winfo_height()/2 if 'ver' in self.cnf['orient'] else evt.y-spacer)) + + markerbg_cnf = {'fill': self._marker_color, 'tag': 'markerbg'} + info_cnf = {'angle': 0 if 'ver' in self.cnf['orient'] else 90, + 'text': text, 'font': Font(size=10), 'tag': "info", 'fill': hexcode} + + self._create_items('create', safe_create=True, avoid=('gradient', 'borderline'), + info_points=info_points, markerbg_points=markerbg_points, + info_cnf=info_cnf, markerbg_cnf=markerbg_cnf) + + return True + + def _set_mousewheel(self, evt=None): + """Internal function.\n + Sets mousewheel scrolling.""" + + def on_mousewheel(evt=None): + "Internal function." + ver_cond = self._xy < self.winfo_width() \ + and self['orient'] == 'vertical' + hor_cond = self._xy < self.winfo_height() \ + and self['orient'] == 'horizontal' + if tkb.delta(evt) <= -1 and (ver_cond or hor_cond): + self._xy += 1 + if not self._move_marker(evt, mw=self._xy): + self._xy -= 1 + if tkb.delta(evt) >= 1 and self._xy > 1: + self._xy -= 1 + if not self._move_marker(evt, mw=self._xy): + self._xy += 1 + + if self.cnf.get('mousewheel'): + tkb._bind(self, className='mousewheel', + sequence='', func=on_mousewheel) + tkb._bind(self, className='mousewheel_x11', + sequence='', func=on_mousewheel) + tkb._bind(self, className='mousewheel_x11', + sequence='', func=on_mousewheel) + else: + tkb._bind(self, className='mousewheel', + sequence='') + tkb._bind(self, className='mousewheel_x11', + sequence='') + tkb._bind(self, className='mousewheel_x11', + sequence='') + + def _configure(self, cmd, cnf=None, kw=None): + """Internal function.""" + kw1 = _tk._cnfmerge((cnf, kw)) + self.cnf.update( + {k: kw.pop(k, None) for k in kw1.copy() if k in self._features}) + self.cnf = {k: v for k, v in self.cnf.copy().items() if v is not None} + _return = tkb._Canvas._configure(self, cmd, None, kw1) + if _tk._cnfmerge((cnf, kw)).get('gradient'): + self._create_items('create', safe_create=True, + avoid=('borderline', 'marker', + 'markerbg', 'info')) + self._set_mousewheel() + if _return and isinstance(_return, dict): + _return.update(self.cnf) + return _return + + def cget(self, key): + """Return the resource value for a KEY given as string.""" + if key in self.cnf.keys(): + return self.cnf.get(key) + return tkb._Canvas.cget(self, key) + __getitem__ = cget + + def keys(self): + """Return a list of all resource names of this widget.""" + return list(sorted(self.config() + self._features)) + + def destroy(self): + """Destroy this widget.""" + self.after_cancel(getattr(self, '_remove_id', ' ')) + return tkb._Canvas.destroy(self) + + +# ------------------------------------ # +# Testing and demo # +# ------------------------------------ # + +def demo_colorscale(): + from tkmacosx.variables import ColorVar + + root = _tk.Tk() + root.title("Tkinter Color Bar") + var = ColorVar() + _tk.Label(root, text="I am a Label, Hello! :-)", + bg=var).pack(padx=10, pady=10) + Colorscale(root, value='hex', variable=var, mousewheel=1, + gradient=('pink', 'purple')).pack(padx=10, pady=10) + Colorscale(root, value='hex', variable=var, mousewheel=1, + gradient=('green', 'yellow')).pack(padx=10, pady=10) + Colorscale(root, value='hex', variable=var, mousewheel=1, + gradient=('purple', 'cyan')).pack(padx=10, pady=10) + Colorscale(root, value='hex', variable=var, mousewheel=1, + gradient=('white', '#89ABE3')).pack(padx=10, pady=10) + Colorscale(root, value='hex', variable=var, mousewheel=1, + gradient=('#5F4B8B', '#E69A8D'), orient='horizontal' + ).pack(padx=10, pady=10, side='left') + Colorscale(root, value='hex', variable=var, mousewheel=1, + gradient=('#990011', '#FCF6F5'), orient='horizontal' + ).pack(padx=60, pady=10, side='left') + Colorscale(root, value='hex', variable=var, mousewheel=1, + orient='horizontal').pack(padx=10, pady=10, side='left') + root.mainloop() + + +if __name__ == "__main__": + demo_colorscale() diff --git a/tkmacosx/variables.py b/tkmacosx/variables.py new file mode 100644 index 0000000..cc12a70 --- /dev/null +++ b/tkmacosx/variables.py @@ -0,0 +1,366 @@ +# Copyright 2020 Saad Mairaj +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import sys +import ast +import pickle as pkl + +if sys.version_info.major == 2: + import Tkinter as _tk +elif sys.version_info.major == 3: + import tkinter as _tk + + +# Modified Misc._options(...) to make ColorVar work with tkinter +# {(self, option, tag): (var, cbname)} +_all_traces_colorvar = {} + + +def _colorvar_patch_cget(fn): + """Internal function.""" + def _patch(self, key): + """Return the resource value for a KEY given as string.""" + if (self, key) in _all_traces_colorvar: + return _all_traces_colorvar[(self, key)][0] + return fn(self, key) + return _patch + + +def _colorvar_patch_destroy(fn): + """Internal function.\n + Deletes the traces if any when widget is destroy.""" + + def _patch(self): + """Interanl function.""" + if self._tclCommands is not None: + # Deletes the widget from the _all_traces_colorvar + # and deletes the traces too. + for key, value in dict(_all_traces_colorvar).items(): + if self == key[0]: + var, cbname = value + try: var.trace_vdelete('w', cbname) + except: pass + _all_traces_colorvar.pop(key) + return fn(self) + return _patch + + +def _colorvar_patch_configure(fn): + """Internal function.\n + Patch for Canvas items to support ColorVar functionality.""" + + def _patch(self, cmd, cnf, kw): + """Internal function.""" + + cnf = _tk._cnfmerge((cnf, kw)) + cnf_copy = dict(cnf) + # -------------------- Added the below block -------------------- + # Add the resources to the list to have ColorVar functionality. + if isinstance(cmd, tuple) and isinstance(self, _tk.Canvas): + tags = self.find('withtag', cmd[1]) + for tag in tags: + for i in ('activefill', 'activeoutline', 'disabledfill', + 'disabledoutline', 'fill', 'outline', 'background', + 'activebackground', 'activeforeground', + 'disabledbackground', 'disabledforeground', + 'foreground'): + if isinstance(cnf_copy.get(i), _tk.Variable): + var = cnf_copy[i] + cbname = var.trace_variable('w', lambda a, b, c, + cls=self, opt=i, + tagId=tag, var=var: + cls._configure(('itemconfigure',tagId), + {opt: var.get()}, None)) + if (self, (i, tag)) in _all_traces_colorvar: + v, cb = _all_traces_colorvar.get((self, (i, tag))) + v.trace_vdelete('w', cb) + _all_traces_colorvar[(self, (i, tag))] = (var, cbname) + else: + _all_traces_colorvar[(self, (i, tag))] = (var, cbname) + cnf[i] = var.get() + return fn(self, cmd, cnf, None) + return _patch + + +def _colorvar_patch_options(fn): + """Internal function.\n + Patch for ColorVar to work with tkinter widgets.""" + + def _patch(self, cnf, kw=None): + """Internal function.""" + if kw: + cnf = _tk._cnfmerge((cnf, kw)) + else: + cnf = _tk._cnfmerge(cnf) + + for i in ('fg', 'foreground', 'bg', 'background', + 'activebackground', 'activeforeground', 'disabledforeground', + 'highlightbackground', 'highlightcolor', 'selectforeground', + 'readonlybackground', 'selectbackground', 'insertbackground', + 'disabledbackground'): + if isinstance(cnf.get(i), _tk.Variable): + var = cnf[i] + cbname = var.trace_variable('w', lambda a, b, c, i=i, var=var, + cls=self: cls.config({i: var.get()})) + if (self, i) in _all_traces_colorvar: + v, cb = _all_traces_colorvar.get((self, i)) + v.trace_vdelete('w', cb) + _all_traces_colorvar[(self, i)] = (var, cbname) + else: + _all_traces_colorvar[(self, i)] = (var, cbname) + cnf[i] = var.get() + # [issue-1] once a ColorVar is assigned, it cannot be removed + # untill the widget is destroyed or give another ColorVar + # [issue-1] (trial) the below doesn't work as excepted. + # elif (self, i) in _all_traces_colorvar: + # if self[i] != _all_traces_colorvar[(self, i)][0].get(): + # print( self, i, self[i]) + # v, cb = _all_traces_colorvar.pop((self, i)) + # v.trace_vdelete('w', cb) + return fn(self, cnf, None) + return _patch + + +def _create(self, itemType, args, kw): # Args: (val, val, ..., cnf={}) + """Internal function.""" + args = _tk._flatten(args) + cnf = args[-1] + if isinstance(cnf, (dict, tuple)): + args = args[:-1] + else: + cnf = {} + + # -------------------- Added the below block -------------------- + # Add the resources to the list to have ColorVar functionality. + ckw = _tk._cnfmerge((cnf, kw)) + var = None + for i in ('activefill', 'activeoutline', 'disabledfill', + 'disabledoutline', 'fill', 'outline', 'background', + 'activebackground', 'activeforeground', 'foreground', + 'disabledbackground', 'disabledforeground'): + if isinstance(ckw.get(i), _tk.Variable): + var = ckw[i] + _all_traces_colorvar[(self, (i, None))] = (var, None) + if isinstance(cnf, dict) and i in cnf: + cnf[i] = var.get() + elif i in kw: + kw[i] = var.get() + # --------------------------------------------------------------- + + tagId = self.tk.getint(self.tk.call( + self._w, 'create', itemType, + *(args + self._options(cnf, kw)))) + + for key, value in dict(_all_traces_colorvar).items(): + if isinstance(key[1], (tuple, list)): + wid, (opt, tag_id) = key + var, cbname = value + if tag_id is None and cbname is None: + cbname = var.trace_variable('w', lambda a, b, c, + cls=self, opt=opt, + tagId=tagId, var=var: + cls._configure(('itemconfigure',tagId), + {opt: var.get()}, None)) + _all_traces_colorvar[(self, (opt, tagId))] = (var, cbname) + _all_traces_colorvar.pop((self, (opt, None))) + return tagId + + +_tk.Misc.destroy = _colorvar_patch_destroy(_tk.Misc.destroy) +_tk.Misc._options = _colorvar_patch_options(_tk.Misc._options) +_tk.Misc._configure = _colorvar_patch_configure(_tk.Misc._configure) +_tk.Canvas._create = _create + + +class ColorVar(_tk.Variable): + """Value holder for HEX color. Default is white""" + + _default = "white" + _rgbstring = re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$') + + def __init__(self, master=None, value=None, name=None): + """Construct a color variable. (bg, fg, ..) + + MASTER can be given as master widget. + VALUE is an optional value (defaults to "") + NAME is an optional Tcl name (defaults to PY_VARnum). + + If NAME matches an existing variable and VALUE is omitted + then the existing value is retained. + """ + _tk.Variable.__init__(self, master, value, name) + + def set(self, value=''): + """Set the variable to VALUE.""" + if value.startswith('#'): + if not bool(self._rgbstring.match(value)): + raise ValueError('"{}" is not a valid HEX.'.format(value)) + elif isinstance(value, str): + try: + r, g, b = self._root.winfo_rgb(value) + c = (r/257, g/257, b/257) + value = '#%02x%02x%02x' % (int(c[0]), int(c[1]), int(c[2])) + except: + raise ValueError( + 'Could not find right HEX for "{}".'.format(value)) + return self._tk.globalsetvar(self._name, value) + initialize = set + + def get(self): + """Return value of variable color.""" + value = self._tk.globalgetvar(self._name) + if isinstance(value, str): + return value + return str(value) + + +class DictVar(_tk.Variable): + """ + #### Value holder for Dictionaries. + Get a specific value by getting the key from this \ + `get(self, key=None, d=None)` method if exists in the dictionary. \n + if `key=None` it will return the complete dictionary. + """ + _default = {} + + def __init__(self, master=None, value=None, name=None): + """Construct a string variable. + + MASTER can be given as master widget. + VALUE is an optional value (defaults to {}) + NAME is an optional Tcl name (defaults to PY_VARnum). + + If NAME matches an existing variable and VALUE is omitted + then the existing value is retained. + """ + _tk.Variable.__init__(self, master, value, name) + + def get(self, key=None, d=None): + """Return value of variable as string.""" + value = self._tk.globalgetvar(self._name) + if not isinstance(value, dict): + value = ast.literal_eval(value) + if key: + return value.get(key, d) + else: + return value + + +def SaveVar(var, master=None, value=None, name=None, filename='data.pkl'): + """Save tkinter variable data in a pickle file and load the + same value when the program is executed next time. + + #### If the content of the file changes, it might not load correct values \ + to the assigned variables. To avoid this issue use `name` to \ + refer to the exact assigned values later. + + ### Args: + - `var`: Give the `tkinter.Variable` class like (`tk.StringVar`, `tk.IntVar`). + - `master`: Parent widget. + - `value`: Set value. + - `name`: Set a name to group variables or to refer to assigned value when loaded. + - `filename`: Set the name of the save file. (To make the file invisible in the \ + directory start the name of the file with "." like ".cache-savevar") + + ### Return: + - returns the tk.Variable instance passed to `var` argument. + + ### Example: + root = tk.Tk() + var1 = SaveVar(tk.StringVar,'Enter Username','Var1','.cache-savevar') + var2 = SaveVar(tk.StringVar,'Enter Password','Var2','.cache-savevar') + tk.Entry(root,textvariable=var1).pack() + tk.Entry(root,textvariable=var2).pack() + root.mainloop()""" + + def update_val(*args): + """Internal function for updating the value for variable""" + try: # try/except , if the file doesn't exists. + open1 = open(filename, 'rb') + tmpdict = pkl.load(open1) # load saved dictionary data. + # Block of code to check for the right value. + if tmpdict.get(str(var)): + old, default = tmpdict.get(str(var)) + new = var.get() + if new != default: + var.set(new) + elif new == default and not startup[0]: + var.set(default) + else: + var.set(old) + tmpdict.update({str(var): (var.get(), defaultval)}) + open1.close() + except Exception as e: + tmpdict = {} + tmpdict[str(var)] = (var.get(), defaultval) + + open2 = open(filename, 'wb') + pkl.dump(tmpdict, open2) + startup[0] = False + open2.close() + + startup = [True] + if not(filename.endswith('.pickle') or filename.endswith('.pkl')) \ + and not filename.startswith('.'): + filename = filename+'.pkl' + var = var(master=master, value=value, name=name) + defaultval = var.get() # get a default value of the variable + update_val() + for mode, cbname in (var.trace_vinfo()): + if mode[0] == 'w' and update_val.__name__ in cbname: + try: + var.trace_vdelete('w', cbname) + except: + pass + res = var.trace_variable('w', update_val) + return var + + +# ------------------ Testing ------------------ + +def demo_colorvar(): + import tkmacosx.colors as colors + root = _tk.Tk() + root.geometry('100x100') + color = ColorVar() + color_list = list(colors.OrderedHex) + L = _tk.Label(root, textvariable=color, bg=color) + L.place(relx=0.5, rely=0.5, anchor='center') + + def change_color(c=0): + if root.winfo_exists(): + if c >= len(color_list): + c = 0 + color.set(color_list[c]) + root.after(100, change_color, c+1) + + change_color() + root.mainloop() + + +def demo_savevar(): + root = _tk.Tk() + var1 = SaveVar(_tk.StringVar, root, 'Enter Username', + 'Var1', '.cache-savevar') + var2 = SaveVar(_tk.StringVar, root, 'Enter Password', + 'Var2', '.cache-savevar') + _tk.Entry(root, textvariable=var1).pack() + _tk.Entry(root, textvariable=var2).pack() + root.mainloop() + + +if __name__ == "__main__": + demo_colorvar() + demo_savevar() diff --git a/tkmacosx/widget.py b/tkmacosx/widget.py new file mode 100644 index 0000000..109f879 --- /dev/null +++ b/tkmacosx/widget.py @@ -0,0 +1,251 @@ +# Copyright 2020 Saad Mairaj +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +if sys.version_info.major == 2: + import Tkinter as _TK +elif sys.version_info.major == 3: + import tkinter as _TK +import tkmacosx.basewidget as tkb + + +class SFrame(tkb.SFrameBase): + """### Scrollable Frame ButtonBase. + (Only supports vertical scrolling) + + Sames as tkinter Frame. These are some extra resources. + - `scrollbarwidth`: Set the width of scrollbar. + - `mousewheel`: Set mousewheel scrolling. + - `avoidmousewheel`: Give widgets that also have mousewheel scrolling and is a child of SFrame \ + this will configure widgets to support their mousewheel scrolling as well. \ + For eg:- Text widget inside SFrame can have mousewheel scrolling as well as SFrame. + + Scrollbar of SFrame can be configured by calling `scrollbar_configure(**options)`. + To access methods of the scrollbar it can be called through the scrollbar instance `self['scrollbar']`. + + ### How to use? + Use it like a normal frame. + + ### Example: + + root = Tk() + frame = SFrame(root, bg='pink') + frame.pack() + + for i in range(100): + Button(frame, text='Button %s'%i).pack() + + root.mainloop() + """ + + def __init__(self, master=None, cnf={}, **kw): + tkb.SFrameBase.__init__(self, master=master, cnf=cnf, **kw) + # Extra functions + self.scrollbar_configure = self['scrollbar'].configure + + +class Button(tkb.ButtonBase): + """Button for macos, supports almost all the features of tkinter button, + - Looks very similar to ttk Button. + - There are few extra features as compared to default Tkinter Button: + - To check the list of all the resources. To get an overview about + the allowed keyword arguments call the method `keys`. + print(Button().keys()) + + ### Examples: + import tkinter as tk + import tkmacosx as tkm + import tkinter.ttk as ttk + + root = tk.Tk() + root.geometry('200x200') + tkm.Button(root, text='Mac OSX', bg='lightblue', fg='yellow').pack() + tk.Button(root, text='Mac OSX', bg='lightblue', fg='yellow').pack() + ttk.Button(root, text='Mac OSX').pack() + root.mainloop() + + ### Get a cool gradient effect in activebackground color. + import tkinter as tk + import tkmacosx as tkm + + root = tk.Tk() + root.geometry('200x200') + tkm.Button(root, text='Press Me!!', activebackground=('pink','blue') ).pack() + tkm.Button(root, text='Press Me!!', activebackground=('yellow','green') ).pack() + tkm.Button(root, text='Press Me!!', activebackground=('red','blue') ).pack() + root.mainloop()""" + + def __init__(self, master=None, cnf={}, **kw): + tkb.ButtonBase.__init__(self, 'normal', master, cnf, **kw) + + def invoke(self): + """Invoke the command associated with the button. + + The return value is the return value from the command, + or an empty string if there is no command associated with + the button. This command is ignored if the button's state + is disabled. + """ + if self['state'] not in ('disable', 'disabled'): + return self.cnf['command']() if self.cnf.get('command') else None + + +class CircleButton(tkb.ButtonBase): + """ + #### Beta-Disclaimer: Please note that this is a BETA version of this widget. \ + Issues at https://github.com/Saadmairaj/tkmacosx/issues/new/choose \ + or email me at saadmairaj@yahoo.in. + + Circle shaped Button supports almost all options of a tkinter button + and have some few more useful options such as 'activebackground', overbackground', + 'overforeground', 'activeimage', 'activeforeground', 'borderless' and much more. + + To check all the configurable options for CircleButton run `print(CircleButton().keys())`. + + Example: + ``` + import tkinter as tk + import tkmacosx as tkm + + root = tk.Tk() + cb = tkm.CircleButton(root, text='Hello') + cb.pack() + root.mainloop() + ``` + """ + def __init__(self, master=None, cnf={}, **kw): + tkb.ButtonBase.__init__(self, 'circle', master, cnf, **kw) + + def invoke(self): + """Invoke the command associated with the button. + + The return value is the return value from the command, + or an empty string if there is no command associated with + the button. This command is ignored if the button's state + is disabled. + """ + if self['state'] not in ('disable', 'disabled'): + return self.cnf['command']() if self.cnf.get('command') else None + + +class Marquee(tkb.MarqueeBase): + """Use `Marquee` for creating scrolling text which moves from + right to left only if the text does not fit completely. + + ### Args: + - `text`: Give a string to display. + - `font`: Font of the text. + - `fg`: Set foreground color of the text. + - `fps`: Set fps(frames per seconds). + - `left_margin`: Set left margin to make text move further to right before reset. + - `initial_delay`: Delay before text start to move. + - `end_delay`: Delay before text reset. + - `smoothness`: Set the smoothness of the animation. + + ### Example: + root=tk.Tk() + marquee = Marquee(root, + text='This text will move from right to left if does not fit the window.') + marquee.pack() + root.mainloop() + + ### To move the text when cursor is over the text then follow the below example. + + text = "Please hover over the text to move it. \ + This text will move only if the cursor hovers over the text widget". + root = tk.Tk() + m = tkm.Marquee(root, bg='lightgreen', text=text) + m.pack() + m.stop(True) + m.bind('', lambda _: m.play()) + m.bind('', lambda _: m.stop()) + root.mainloop() + """ + + def reset(self): + """Reset the text postion.""" + self._reset(True) + self._stop_state = False + + def stop(self, reset=False): + """Stop the text movement.""" + if reset: + self.reset() + self._stop_state = True + self.after_cancel(self.after_id) + self.after_id = ' ' + + def play(self, reset=False): + """Play/continue the text movement.""" + if not self._stop_state and not reset: + return + self._stop_state = False + if reset: + self.reset() + self._animate() + + +# ------------------ Testing ------------------ + + +def demo_button(): + if sys.version_info.major == 2: + import ttk + elif sys.version_info.major == 3: + from tkinter import ttk + root = _TK.Tk() + root.title('Mac OSX Button Demo') + page_color = '#FFFFC6' + root.geometry('300x400') + root['bg'] = page_color + _TK.Label(root, text='Can you tell the difference?', font=( + '', 16, 'bold'), bg=page_color).pack(pady=(20, 5)) + Button(root, text='Press Me', disabledbackground='red').pack(pady=(0, 5)) + ttk.Button(root, text="Press Me").pack() + _TK.Label(root, text='Change Background Color', font=( + '', 16, 'bold'), bg=page_color).pack(pady=(20, 5)) + Button(root, text='Press Me', bg='pink', + activebackground=('pink', 'blue')).pack() + _TK.Label(root, text='Blend In', font=('', 16, 'bold'), + bg=page_color).pack(pady=(20, 5)) + Button(root, text='Press Me', bg='yellow', activebackground=( + 'orange', 'lime'), borderless=1).pack() + _TK.Label(root, text='Change bordercolor', font=( + '', 16, 'bold'), bg=page_color).pack(pady=(20, 5)) + Button(root, text='Press Me', bg='red', fg='white', + activebackground=('red', 'blue'), bordercolor='blue').pack() + root.mainloop() + + +def demo_sframe(): + root = _TK.Tk() + frame = SFrame(root, bg='pink') + frame.pack() + for i in range(50): + Button(frame, text='Button %s' % i, borderless=1).pack() + root.mainloop() + + +def demo_marquee(): + root = _TK.Tk() + marquee = Marquee( + root, text='This text will move from right to left if does not fit the window.') + marquee.pack() + root.mainloop() + + +if __name__ == "__main__": + demo_sframe() + demo_button() + demo_marquee()