Source code for ttkwidgets.color.colorpicker

# -*- coding: utf-8 -*-
"""
Author: Juliette Monsel
License: GNU GPLv3
Source: https://github.com/j4321/tkColorPicker

Edited by RedFantom for Python 2/3 cross-compatibility and docstring formatting


tkcolorpicker - Alternative to colorchooser for Tkinter.
Copyright 2017 Juliette Monsel <j_4321@protonmail.com>

tkcolorpicker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

tkcolorpicker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Colorpicker dialog
"""


from PIL import ImageTk
from .functions import tk, ttk, round2, create_checkered_image, \
    overlay, PALETTE, hsv_to_rgb, hexa_to_rgb, rgb_to_hexa, col2hue, rgb_to_hsv
from .alphabar import AlphaBar
from .gradientbar import GradientBar
from .colorsquare import ColorSquare
from .spinbox import Spinbox
from .limitvar import LimitVar
from locale import getdefaultlocale
import re


# --- Translation
EN = {}
FR = {"Red": "Rouge", "Green": "Vert", "Blue": "Bleu",
      "Hue": "Teinte", "Saturation": "Saturation", "Value": "Valeur",
      "Cancel": "Annuler", "Color Chooser": "Sélecteur de couleur",
      "Alpha": "Alpha"}

if getdefaultlocale()[0][:2] == 'fr':
    TR = FR
else:
    TR = EN


def _(text):
    """Translate text."""
    return TR.get(text, text)


[docs]class ColorPicker(tk.Toplevel): """Color picker dialog."""
[docs] def __init__(self, parent=None, color=(255, 0, 0), alpha=False, title=_("Color Chooser")): """ Create a ColorPicker dialog. :param parent: parent widget :type parent: widget :param color: initially selected color (RGB(A), HEX or tkinter color name) :type color: sequence[int] or str :param alpha: whether to display the alpha channel :type alpha: bool :param title: dialog title :type title: str """ tk.Toplevel.__init__(self, parent) self.title(title) self.transient(self.master) self.resizable(False, False) self.rowconfigure(1, weight=1) self.color = "" self.alpha_channel = bool(alpha) style = ttk.Style(self) style.map("palette.TFrame", relief=[('focus', 'sunken')], bordercolor=[('focus', "#4D4D4D")]) self.configure(background=style.lookup("TFrame", "background")) if isinstance(color, str): if re.match(r"^#[0-9A-F]{8}$", color.upper()): col = hexa_to_rgb(color) self._old_color = col[:3] if alpha: self._old_alpha = col[3] old_color = color else: old_color = color[:7] elif re.match(r"^#[0-9A-F]{6}$", color.upper()): self._old_color = hexa_to_rgb(color) old_color = color if alpha: self._old_alpha = 255 old_color += 'FF' else: col = self.winfo_rgb(color) self._old_color = tuple(round2(c * 255 / 65535) for c in col) args = self._old_color if alpha: self._old_alpha = 255 args = self._old_color + (255,) old_color = rgb_to_hexa(*args) else: self._old_color = color[:3] if alpha: if len(color) < 4: color += (255,) self._old_alpha = 255 else: self._old_alpha = color[3] old_color = rgb_to_hexa(*color) # --- GradientBar hue = col2hue(*self._old_color) bar = ttk.Frame(self, borderwidth=2, relief='groove') self.bar = GradientBar(bar, hue=hue, width=200, highlightthickness=0) self.bar.pack() # --- ColorSquare square = ttk.Frame(self, borderwidth=2, relief='groove') self.square = ColorSquare(square, hue=hue, width=200, height=200, color=rgb_to_hsv(*self._old_color), highlightthickness=0) self.square.pack() frame = ttk.Frame(self) frame.columnconfigure(1, weight=1) frame.rowconfigure(1, weight=1) # --- color preview: initial color and currently selected color side by side preview_frame = ttk.Frame(frame, relief="groove", borderwidth=2) preview_frame.grid(row=0, column=0, sticky="nw", pady=2) if alpha: self._transparent_bg = create_checkered_image(42, 32) transparent_bg_old = create_checkered_image(42, 32, (100, 100, 100, 255), (154, 154, 154, 255)) prev_old = overlay(transparent_bg_old, hexa_to_rgb(old_color)) prev = overlay(self._transparent_bg, hexa_to_rgb(old_color)) self._im_old_color = ImageTk.PhotoImage(prev_old, master=self) self._im_color = ImageTk.PhotoImage(prev, master=self) old_color_prev = tk.Label(preview_frame, padx=0, pady=0, image=self._im_old_color, borderwidth=0, highlightthickness=0) self.color_preview = tk.Label(preview_frame, pady=0, padx=0, image=self._im_color, borderwidth=0, highlightthickness=0) else: old_color_prev = tk.Label(preview_frame, background=old_color[:7], width=5, highlightthickness=0, height=2, padx=0, pady=0) self.color_preview = tk.Label(preview_frame, width=5, height=2, pady=0, background=old_color[:7], padx=0, highlightthickness=0) old_color_prev.bind("<1>", self._reset_preview) old_color_prev.grid(row=0, column=0) self.color_preview.grid(row=0, column=1) # --- palette palette = ttk.Frame(frame) palette.grid(row=0, column=1, rowspan=2, sticky="ne") for i, col in enumerate(PALETTE): f = ttk.Frame(palette, borderwidth=1, relief="raised", style="palette.TFrame") l = tk.Label(f, background=col, width=2, height=1) l.bind("<1>", self._palette_cmd) f.bind("<FocusOut>", lambda e: e.widget.configure(relief="raised")) l.pack() f.grid(row=i % 2, column=i // 2, padx=2, pady=2) col_frame = ttk.Frame(self) # --- hsv hsv_frame = ttk.Frame(col_frame, relief="ridge", borderwidth=2) hsv_frame.pack(pady=(0, 4), fill="x") hsv_frame.columnconfigure(0, weight=1) self.hue = LimitVar(0, 360, self) self.saturation = LimitVar(0, 100, self) self.value = LimitVar(0, 100, self) s_h = Spinbox(hsv_frame, from_=0, to=360, width=4, name='spinbox', textvariable=self.hue, command=self._update_color_hsv) s_s = Spinbox(hsv_frame, from_=0, to=100, width=4, textvariable=self.saturation, name='spinbox', command=self._update_color_hsv) s_v = Spinbox(hsv_frame, from_=0, to=100, width=4, name='spinbox', textvariable=self.value, command=self._update_color_hsv) h, s, v = rgb_to_hsv(*self._old_color) s_h.delete(0, 'end') s_h.insert(0, h) s_s.delete(0, 'end') s_s.insert(0, s) s_v.delete(0, 'end') s_v.insert(0, v) s_h.grid(row=0, column=1, sticky='w', padx=4, pady=4) s_s.grid(row=1, column=1, sticky='w', padx=4, pady=4) s_v.grid(row=2, column=1, sticky='w', padx=4, pady=4) ttk.Label(hsv_frame, text=_('Hue')).grid(row=0, column=0, sticky='e', padx=4, pady=4) ttk.Label(hsv_frame, text=_('Saturation')).grid(row=1, column=0, sticky='e', padx=4, pady=4) ttk.Label(hsv_frame, text=_('Value')).grid(row=2, column=0, sticky='e', padx=4, pady=4) # --- rgb rgb_frame = ttk.Frame(col_frame, relief="ridge", borderwidth=2) rgb_frame.pack(pady=4, fill="x") rgb_frame.columnconfigure(0, weight=1) self.red = LimitVar(0, 255, self) self.green = LimitVar(0, 255, self) self.blue = LimitVar(0, 255, self) s_red = Spinbox(rgb_frame, from_=0, to=255, width=4, name='spinbox', textvariable=self.red, command=self._update_color_rgb) s_green = Spinbox(rgb_frame, from_=0, to=255, width=4, name='spinbox', textvariable=self.green, command=self._update_color_rgb) s_blue = Spinbox(rgb_frame, from_=0, to=255, width=4, name='spinbox', textvariable=self.blue, command=self._update_color_rgb) s_red.delete(0, 'end') s_red.insert(0, self._old_color[0]) s_green.delete(0, 'end') s_green.insert(0, self._old_color[1]) s_blue.delete(0, 'end') s_blue.insert(0, self._old_color[2]) s_red.grid(row=0, column=1, sticky='e', padx=4, pady=4) s_green.grid(row=1, column=1, sticky='e', padx=4, pady=4) s_blue.grid(row=2, column=1, sticky='e', padx=4, pady=4) ttk.Label(rgb_frame, text=_('Red')).grid(row=0, column=0, sticky='e', padx=4, pady=4) ttk.Label(rgb_frame, text=_('Green')).grid(row=1, column=0, sticky='e', padx=4, pady=4) ttk.Label(rgb_frame, text=_('Blue')).grid(row=2, column=0, sticky='e', padx=4, pady=4) # --- hexa hexa_frame = ttk.Frame(col_frame) hexa_frame.pack(fill="x") self.hexa = ttk.Entry(hexa_frame, justify="center", width=10, name='entry') self.hexa.insert(0, old_color.upper()) ttk.Label(hexa_frame, text="HTML").pack(side="left", padx=4, pady=(4, 1)) self.hexa.pack(side="left", padx=6, pady=(4, 1), fill='x', expand=True) # --- alpha if alpha: alpha_frame = ttk.Frame(self) alpha_frame.columnconfigure(1, weight=1) self.alpha = LimitVar(0, 255, self) alphabar = ttk.Frame(alpha_frame, borderwidth=2, relief='groove') self.alphabar = AlphaBar(alphabar, alpha=self._old_alpha, width=200, color=self._old_color, highlightthickness=0) self.alphabar.pack() s_alpha = Spinbox(alpha_frame, from_=0, to=255, width=4, textvariable=self.alpha, command=self._update_alpha) s_alpha.delete(0, 'end') s_alpha.insert(0, self._old_alpha) alphabar.grid(row=0, column=0, padx=(0, 4), pady=4, sticky='w') ttk.Label(alpha_frame, text=_('Alpha')).grid(row=0, column=1, sticky='e', padx=4, pady=4) s_alpha.grid(row=0, column=2, sticky='w', padx=(4, 6), pady=4) # --- validation button_frame = ttk.Frame(self) ttk.Button(button_frame, text="Ok", command=self.ok).pack(side="right", padx=10) ttk.Button(button_frame, text=_("Cancel"), command=self.destroy).pack(side="right", padx=10) # --- placement bar.grid(row=0, column=0, padx=10, pady=(10, 4), sticky='n') square.grid(row=1, column=0, padx=10, pady=(9, 0), sticky='n') if alpha: alpha_frame.grid(row=2, column=0, columnspan=2, padx=10, pady=(1, 4), sticky='ewn') col_frame.grid(row=0, rowspan=2, column=1, padx=(4, 10), pady=(10, 4)) frame.grid(row=3, column=0, columnspan=2, pady=(4, 10), padx=10, sticky="new") button_frame.grid(row=4, columnspan=2, pady=(0, 10), padx=10) # --- bindings self.bar.bind("<ButtonRelease-1>", self._change_color, True) self.bar.bind("<Button-1>", self._unfocus, True) if alpha: self.alphabar.bind("<ButtonRelease-1>", self._change_alpha, True) self.alphabar.bind("<Button-1>", self._unfocus, True) self.square.bind("<Button-1>", self._unfocus, True) self.square.bind("<ButtonRelease-1>", self._change_sel_color, True) self.square.bind("<B1-Motion>", self._change_sel_color, True) s_red.bind('<FocusOut>', self._update_color_rgb) s_green.bind('<FocusOut>', self._update_color_rgb) s_blue.bind('<FocusOut>', self._update_color_rgb) s_red.bind('<Return>', self._update_color_rgb) s_green.bind('<Return>', self._update_color_rgb) s_blue.bind('<Return>', self._update_color_rgb) s_h.bind('<FocusOut>', self._update_color_hsv) s_s.bind('<FocusOut>', self._update_color_hsv) s_v.bind('<FocusOut>', self._update_color_hsv) s_h.bind('<Return>', self._update_color_hsv) s_s.bind('<Return>', self._update_color_hsv) s_v.bind('<Return>', self._update_color_hsv) if alpha: s_alpha.bind('<Return>', self._update_alpha) s_alpha.bind('<FocusOut>', self._update_alpha) self.hexa.bind("<FocusOut>", self._update_color_hexa) self.hexa.bind("<Return>", self._update_color_hexa) self.wait_visibility() self.lift() self.grab_set()
[docs] def get_color(self): """ Return selected color, return an empty string if no color is selected. :return: selected color as a (RGB, HSV, HEX) tuple or "" """ return self.color
def _unfocus(self, event): """Unfocus palette items when click on bar or square.""" w = self.focus_get() if w != self and 'spinbox' not in str(w) and 'entry' not in str(w): self.focus_set() def _update_preview(self): """Update color preview.""" color = self.hexa.get() if self.alpha_channel: prev = overlay(self._transparent_bg, hexa_to_rgb(color)) self._im_color = ImageTk.PhotoImage(prev, master=self) self.color_preview.configure(image=self._im_color) else: self.color_preview.configure(background=color) def _reset_preview(self, event): """Respond to user click on a palette item.""" label = event.widget label.master.focus_set() label.master.configure(relief="sunken") args = self._old_color if self.alpha_channel: args += (self._old_alpha,) self.alpha.set(self._old_alpha) self.alphabar.set_color(args) color = rgb_to_hexa(*args) h, s, v = rgb_to_hsv(*self._old_color) self.red.set(self._old_color[0]) self.green.set(self._old_color[1]) self.blue.set(self._old_color[2]) self.hue.set(h) self.saturation.set(s) self.value.set(v) self.hexa.delete(0, "end") self.hexa.insert(0, color.upper()) self.bar.set(h) self.square.set_hsv((h, s, v)) self._update_preview() def _palette_cmd(self, event): """Respond to user click on a palette item.""" label = event.widget label.master.focus_set() label.master.configure(relief="sunken") r, g, b = self.winfo_rgb(label.cget("background")) r = round2(r * 255 / 65535) g = round2(g * 255 / 65535) b = round2(b * 255 / 65535) args = (r, g, b) if self.alpha_channel: a = self.alpha.get() args += (a,) self.alphabar.set_color(args) color = rgb_to_hexa(*args) h, s, v = rgb_to_hsv(r, g, b) self.red.set(r) self.green.set(g) self.blue.set(b) self.hue.set(h) self.saturation.set(s) self.value.set(v) self.hexa.delete(0, "end") self.hexa.insert(0, color.upper()) self.bar.set(h) self.square.set_hsv((h, s, v)) self._update_preview() def _change_sel_color(self, event): """Respond to motion of the color selection cross.""" (r, g, b), (h, s, v), color = self.square.get() self.red.set(r) self.green.set(g) self.blue.set(b) self.saturation.set(s) self.value.set(v) self.hexa.delete(0, "end") self.hexa.insert(0, color.upper()) if self.alpha_channel: self.alphabar.set_color((r, g, b)) self.hexa.insert('end', ("%2.2x" % self.alpha.get()).upper()) self._update_preview() def _change_color(self, event): """Respond to motion of the hsv cursor.""" h = self.bar.get() self.square.set_hue(h) (r, g, b), (h, s, v), sel_color = self.square.get() self.red.set(r) self.green.set(g) self.blue.set(b) self.hue.set(h) self.saturation.set(s) self.value.set(v) self.hexa.delete(0, "end") self.hexa.insert(0, sel_color.upper()) if self.alpha_channel: self.alphabar.set_color((r, g, b)) self.hexa.insert('end', ("%2.2x" % self.alpha.get()).upper()) self._update_preview() def _change_alpha(self, event): """Respond to motion of the alpha cursor.""" a = self.alphabar.get() self.alpha.set(a) hexa = self.hexa.get() hexa = hexa[:7] + ("%2.2x" % a).upper() self.hexa.delete(0, 'end') self.hexa.insert(0, hexa) self._update_preview() def _update_color_hexa(self, event=None): """Update display after a change in the HEX entry.""" color = self.hexa.get().upper() self.hexa.delete(0, 'end') self.hexa.insert(0, color) if re.match(r"^#[0-9A-F]{6}$", color): r, g, b = hexa_to_rgb(color) self.red.set(r) self.green.set(g) self.blue.set(b) h, s, v = rgb_to_hsv(r, g, b) self.hue.set(h) self.saturation.set(s) self.value.set(v) self.bar.set(h) self.square.set_hsv((h, s, v)) if self.alpha_channel: a = self.alpha.get() self.hexa.insert('end', ("%2.2x" % a).upper()) self.alphabar.set_color((r, g, b, a)) elif self.alpha_channel and re.match(r"^#[0-9A-F]{8}$", color): r, g, b, a = hexa_to_rgb(color) self.red.set(r) self.green.set(g) self.blue.set(b) self.alpha.set(a) self.alphabar.set_color((r, g, b, a)) h, s, v = rgb_to_hsv(r, g, b) self.hue.set(h) self.saturation.set(s) self.value.set(v) self.bar.set(h) self.square.set_hsv((h, s, v)) else: self._update_color_rgb() self._update_preview() def _update_alpha(self, event=None): """Update display after a change in the alpha spinbox.""" a = self.alpha.get() hexa = self.hexa.get() hexa = hexa[:7] + ("%2.2x" % a).upper() self.hexa.delete(0, 'end') self.hexa.insert(0, hexa) self.alphabar.set(a) self._update_preview() def _update_color_hsv(self, event=None): """Update display after a change in the HSV spinboxes.""" if event is None or event.widget.old_value != event.widget.get(): h = self.hue.get() s = self.saturation.get() v = self.value.get() sel_color = hsv_to_rgb(h, s, v) self.red.set(sel_color[0]) self.green.set(sel_color[1]) self.blue.set(sel_color[2]) if self.alpha_channel: sel_color += (self.alpha.get(),) self.alphabar.set_color(sel_color) hexa = rgb_to_hexa(*sel_color) self.hexa.delete(0, "end") self.hexa.insert(0, hexa) self.square.set_hsv((h, s, v)) self.bar.set(h) self._update_preview() def _update_color_rgb(self, event=None): """Update display after a change in the RGB spinboxes.""" if event is None or event.widget.old_value != event.widget.get(): r = self.red.get() g = self.green.get() b = self.blue.get() h, s, v = rgb_to_hsv(r, g, b) self.hue.set(h) self.saturation.set(s) self.value.set(v) args = (r, g, b) if self.alpha_channel: args += (self.alpha.get(),) self.alphabar.set_color(args) hexa = rgb_to_hexa(*args) self.hexa.delete(0, "end") self.hexa.insert(0, hexa) self.square.set_hsv((h, s, v)) self.bar.set(h) self._update_preview()
[docs] def ok(self): """Validate color selection and destroy dialog.""" rgb, hsv, hexa = self.square.get() if self.alpha_channel: hexa = self.hexa.get() rgb += (self.alpha.get(),) self.color = rgb, hsv, hexa self.destroy()
[docs]def askcolor(color="red", parent=None, title=_("Color Chooser"), alpha=False): """ Open a ColorPicker dialog and return the chosen color. :return: the selected color in RGB(A) and hexadecimal #RRGGBB(AA) formats. (None, None) is returned if the color selection is cancelled. :param color: initially selected color (RGB(A), HEX or tkinter color name) :type color: sequence[int] or str :param parent: parent widget :type parent: widget :param title: dialog title :type title: str :param alpha: whether to display the alpha channel :type alpha: bool """ col = ColorPicker(parent, color, alpha, title) col.wait_window(col) res = col.get_color() if res: return res[0], res[2] else: return None, None