8 Commits

Author SHA1 Message Date
YourKalamity
68181e58c0 Fix tkmacosx 2020-12-19 22:04:00 +00:00
YourKalamity
50d06876ad Add colour.py 2020-12-19 21:30:06 +00:00
YourKalamity
5937d32b87 More fixes for macOS builds 2020-12-19 11:44:18 +00:00
YourKalamity
6367e8f8cc Attempt to fix mac executables v2 2020-12-19 11:33:56 +00:00
YourKalamity
958973fe26 Attempt to fix macOS issue 2020-12-19 11:31:25 +00:00
YourKalamity
3aaad7a456 Revert back to published 2020-12-19 10:25:13 +00:00
YourKalamity
498755fd49 Change gh actions to created 2020-12-19 10:21:39 +00:00
YourKalamity
fc7e2b2e2e Make .py executable 2020-12-19 10:20:43 +00:00
10 changed files with 5430 additions and 88 deletions

View File

@@ -3,6 +3,7 @@ name: Compile into Executables
on:
release:
types: [published]
workflow_dispatch:
######################################################################################
# Based off of :
@@ -31,12 +32,12 @@ jobs:
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Make binary
run: |
pyinstaller --onefile --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 }}" main.py
pyinstaller --onefile --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 }}-Windows" main.py
cd dist
- name: Publish builds
uses: actions/upload-artifact@v2
with:
path: dist/lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}.exe
path: dist/lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}-Windows.exe
name: windows
python:
@@ -51,9 +52,10 @@ jobs:
- name: Make zip
run: |
mkdir temporary
cp -r certifi chardet Darwin idna Linux requests urllib3 temporary/
cp LICENSE main.py README.md temporary/
cp -r certifi chardet Darwin idna Linux requests urllib3 tkmacosx temporary/
cp LICENSE main.py colour.py README.md temporary/
cd temporary
chmod +x main.py
zip -r ../lazy-dsi-file-downloader-${{ steps.vars.outputs.tag }}-Python3.zip *
- name: Publish artifact
uses: actions/upload-artifact@v2
@@ -61,11 +63,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 "colour.py:." --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

1124
colour.py Normal file

File diff suppressed because it is too large Load Diff

49
main.py
View File

@@ -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,10 +437,10 @@ 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.pack(side=tkinter.RIGHT, padx="5", pady="5")
nextButton = Button(bottomFrame, text="Next",width=button_width, 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,10 +467,10 @@ 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=button_width)
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.pack(side=tkinter.RIGHT, padx="5", pady="5")
nextButton = Button(bottomFrame, text="Next",width=button_width, 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,10 +500,10 @@ 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=button_width)
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.pack(side=tkinter.RIGHT, padx="5", pady="5")
nextButton = Button(bottomFrame, text="Next",width=button_width, 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))
def summonWindow3():
@@ -521,12 +521,12 @@ 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=folder_width)
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=button_width)
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.pack(side=tkinter.RIGHT, padx="5", pady="5")
nextButton = Button(bottomFrame, text="Start",width=button_width, 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))
def globalify(value):
@@ -550,11 +550,11 @@ 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=button_width)
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.pack(side=tkinter.RIGHT, padx="5", pady="5")
finalnextButton = Button(bottomFrame, state="disabled", text="Finish",width=button_width, 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)
def summonWindow5():
@@ -582,11 +582,13 @@ 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.pack(side=tkinter.RIGHT, padx="5", pady="5")
finish = Button(bottomFrame, text="Close",width=button_width, 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 __name__ == "__main__":
if(sys.version_info.major < 3):
print("This program will ONLY work on Python 3 and above")
sys.exit()
@@ -648,18 +650,25 @@ paragraphFont = tkinter.font.Font(
)
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"
button_width = 80
folder_width = 250
else: #Non Jeve Stobs worshippers
from tkinter import Button
backgroundColour = "#252a34"
foregroundColour = "white"
buttonColour = "#7289DA"
backButtonColour = "#567487"
nextButtonColour = "#027b76"
button_width = 8
folder_width = 25
summonWindow0()
root.mainloop()

37
tkmacosx/__init__.py Normal file
View File

@@ -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()

324
tkmacosx/__main__.py Normal file
View File

@@ -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()

1532
tkmacosx/basewidget.py Normal file

File diff suppressed because it is too large Load Diff

1266
tkmacosx/colors.py Normal file

File diff suppressed because it is too large Load Diff

401
tkmacosx/colorscale.py Normal file
View File

@@ -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': '<Configure>', 'func': self._set_size},
{'className': 'b1_on_motion', 'sequence': '<B1-Motion>',
'func': self._move_marker},
{'className': 'b1_press', 'sequence': '<Button-1>', '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='<MouseWheel>', func=on_mousewheel)
tkb._bind(self, className='mousewheel_x11',
sequence='<Button-4>', func=on_mousewheel)
tkb._bind(self, className='mousewheel_x11',
sequence='<Button-5>', func=on_mousewheel)
else:
tkb._bind(self, className='mousewheel',
sequence='<MouseWheel>')
tkb._bind(self, className='mousewheel_x11',
sequence='<Button-4>')
tkb._bind(self, className='mousewheel_x11',
sequence='<Button-5>')
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()

366
tkmacosx/variables.py Normal file
View File

@@ -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()

251
tkmacosx/widget.py Normal file
View File

@@ -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('<Enter>', lambda _: m.play())
m.bind('<Leave>', 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()