├── .appveyor.yml
├── .codecov.yml
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── changelog
├── setup.py
├── tests.py
└── tkcolorpicker
├── __init__.py
├── __main__.py
├── alphabar.py
├── colorpicker.py
├── colorsquare.py
├── functions.py
├── gradientbar.py
├── limitvar.py
└── spinbox.py
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - PYTHON: "C:\\PYTHON27"
4 | - PYTHON: "C:\\PYTHON34"
5 | - PYTHON: "C:\\PYTHON35"
6 | - PYTHON: "C:\\PYTHON36"
7 | install:
8 | - "%PYTHON%\\python.exe -m pip install codecov coverage nose pillow"
9 | build: off
10 | test_script:
11 | - "%PYTHON%\\python.exe -m pip install ."
12 | - "%PYTHON%\\python.exe -m nose --with-coverage"
13 | after_test:
14 | - "%PYTHON%\\Scripts\\codecov.exe"
15 |
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | ci:
3 | - travis
4 | - appveyor
5 | status:
6 | patch: false
7 | changes: false
8 | project:
9 | default:
10 | target: '80'
11 | comment: false
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 | cover
4 | cover2
5 | .coverage
6 | __pycache__
7 | .spyproject/
8 | *.pyc
9 | *.egg-info
10 | htmlcov
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: linux
2 | language: python
3 | matrix:
4 | include:
5 | - os: osx
6 | language: generic
7 | env: INSTALL_TYPE=macpython VERSION=3.6 VENV=venv
8 | - os: osx
9 | language: generic
10 | env: INSTALL_TYPE=macpython VERSION=2.7 VENV=venv
11 | required: sudo
12 | python:
13 | - "2.7"
14 | - "3.5"
15 | - "3.6"
16 | before_install:
17 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then git clone https://github.com/MacPython/terryfy.git; source terryfy/travis_tools.sh; get_python_environment $INSTALL_TYPE $VERSION $VENV; fi
18 | - "export DISPLAY=:99.0"
19 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo systemctl start xvfb; fi
20 | - sleep 3
21 | install:
22 | - python -m pip install -U pip
23 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get install python-tk python3-tk; fi
24 | - python -m pip install -U coverage codecov pillow nose
25 | script:
26 | - python -m pip install .
27 | - python -m nose
28 | after_success:
29 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then coverage run nosetests; fi
30 | - if [ "$TRAVIS_OS_NAME" == "linux" ]; then codecov; fi
31 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.rst *.txt changelog
2 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | tkcolorpicker
2 | =============
3 |
4 | |Release| |Travis| |Appveyor| |Codecov| |Windows| |Linux| |Mac| |License|
5 |
6 | Color picker dialog for Tkinter.
7 |
8 | This module contains a ``ColorPicker`` class which implements the color picker
9 | and an ``askcolor`` function that displays the color picker and
10 | returns the chosen color in RGB and HTML formats.
11 |
12 |
13 | Requirements
14 | ------------
15 |
16 | - Linux, Windows, Mac
17 | - Python 2.7 or 3.x
18 |
19 | And the python packages:
20 |
21 | - tkinter (included in the python distribution for Windows)
22 | - `Pillow `_
23 |
24 |
25 | Installation
26 | ------------
27 |
28 | - Ubuntu: use the PPA `ppa:j-4321-i/ppa `__
29 |
30 | ::
31 |
32 | $ sudo add-apt-repository ppa:j-4321-i/ppa
33 | $ sudo apt-get update
34 | $ sudo apt-get install python(3)-tkcolorpicker
35 |
36 |
37 | - Archlinux:
38 |
39 | the package is available on `AUR `__
40 |
41 |
42 | - With pip:
43 |
44 | ::
45 |
46 | $ pip install tkcolorpicker
47 |
48 |
49 | Documentation
50 | -------------
51 |
52 | Syntax:
53 |
54 | ::
55 |
56 | askcolor(color="red", parent=None, title=_("Color Chooser"), alpha=False)
57 |
58 | Open a ColorPicker dialog and return the chosen color.
59 |
60 | The selected color is returned as a tuple (RGB(A), #RRGGBB(AA))
61 | (None, None) is returned if the color selection is cancelled.
62 |
63 | Arguments:
64 |
65 | + color: initially selected color, supported formats:
66 |
67 | - RGB(A)
68 | - #RRGGBB(AA)
69 | - tkinter color name (see http://wiki.tcl.tk/37701 for a list)
70 |
71 | + parent: parent window
72 | + title: dialog title
73 | + alpha: alpha channel suppport
74 |
75 |
76 | Example
77 | -------
78 |
79 | .. code:: python
80 |
81 | import tkinter as tk
82 | import tkinter.ttk as ttk
83 | from tkcolorpicker import askcolor
84 |
85 | root = tk.Tk()
86 | style = ttk.Style(root)
87 | style.theme_use('clam')
88 |
89 | print(askcolor((255, 255, 0), root))
90 | root.mainloop()
91 |
92 |
93 | .. |Release| image:: https://badge.fury.io/py/tkcolorpicker.svg
94 | :alt: Latest Release
95 | :target: https://pypi.org/project/tkcolorpicker/
96 | .. |Linux| image:: https://img.shields.io/badge/platform-Linux-blue.svg
97 | :alt: Platform
98 | .. |Windows| image:: https://img.shields.io/badge/platform-Windows-blue.svg
99 | :alt: Platform
100 | .. |Mac| image:: https://img.shields.io/badge/platform-Mac-blue.svg
101 | :alt: Platform
102 | .. |Travis| image:: https://travis-ci.org/j4321/tkColorPicker.svg?branch=master
103 | :target: https://travis-ci.org/j4321/tkColorPicker
104 | :alt: Travis CI Build Status
105 | .. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/7ow8wfw5by7uiise/branch/master?svg=true
106 | :target: https://ci.appveyor.com/project/j4321/tkcolorpicker/branch/master
107 | :alt: Appveyor Build Status
108 | .. |Codecov| image:: https://codecov.io/gh/j4321/tkColorPicker/branch/master/graph/badge.svg
109 | :target: https://codecov.io/gh/j4321/tkColorPicker
110 | :alt: Code coverage
111 | .. |License| image:: https://img.shields.io/github/license/j4321/tkColorPicker.svg
112 | :target: https://www.gnu.org/licenses/gpl-3.0.en.html
113 | :alt: License
114 |
--------------------------------------------------------------------------------
/changelog:
--------------------------------------------------------------------------------
1 | tkcolorpicker - Color picker dialog for Tkinter
2 | ===============================================
3 | Copyright 2016-2019 Juliette Monsel
4 |
5 | Changelog
6 | ---------
7 |
8 | - tkcolorpicker 2.1.3
9 | * Add selection on Ctrl-A in entry and spinboxes
10 | * Improve spinbox style compliance
11 | * Fix requirements in setup.py
12 |
13 | - tkcolorpicker 2.1.2
14 | * Fix version in setup.py
15 |
16 | - tkcolorpicker 2.1.1
17 | * Fix packaging error
18 |
19 | - tkcolorpicker 2.1.0
20 | * Add optional alpha channel support
21 | * Reorganized module in a package containing one module for each GUI element
22 |
23 | - tkcolorpicker 2.0.0
24 | * Make package name lowercase to follow PEP 8 guidelines
25 | * Add python 2.7 compatibility
26 | * Make askcolor return (None, None) instead of ()
27 |
28 | - tkColorPicker 1.0.0
29 | * Initial version
30 |
31 |
32 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | from codecs import open
4 | from os import path
5 |
6 | here = path.abspath(path.dirname(__file__))
7 |
8 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
9 | long_description = f.read()
10 |
11 | setup(
12 | name='tkcolorpicker',
13 | version='2.1.3',
14 | description='Color picker dialog for Tkinter',
15 | long_description=long_description,
16 | url='https://github.com/j4321/tkColorPicker',
17 | author='Juliette Monsel',
18 | author_email='j_4321@protonmail.com',
19 | license='GPLv3',
20 | classifiers=[
21 | 'Development Status :: 5 - Production/Stable',
22 | 'Intended Audience :: Developers',
23 | 'Topic :: Software Development :: Widget Sets',
24 | 'Topic :: Software Development :: Libraries :: Python Modules',
25 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
26 | 'Programming Language :: Python :: 3',
27 | 'Programming Language :: Python :: 3.4',
28 | 'Programming Language :: Python :: 3.5',
29 | 'Programming Language :: Python :: 3.6',
30 | 'Programming Language :: Python :: 2.7',
31 | 'Natural Language :: English',
32 | 'Natural Language :: French',
33 | 'Operating System :: OS Independent',
34 | ],
35 | keywords=['tkinter', 'color', 'colorchooser'],
36 | py_modules=["tkcolorpicker"],
37 | packages=["tkcolorpicker"],
38 | install_requires=['Pillow']
39 | )
40 |
--------------------------------------------------------------------------------
/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | Tests
20 | """
21 |
22 | import unittest
23 | try:
24 | import Tkinter as tk
25 | except ImportError:
26 | import tkinter as tk
27 | import tkcolorpicker as tkc
28 | import tkcolorpicker.functions as tkf
29 | from tkcolorpicker.spinbox import Spinbox
30 | from tkcolorpicker.limitvar import LimitVar
31 |
32 |
33 | class TestFunctions(unittest.TestCase):
34 | def test_round2(self):
35 | self.assertEqual(tkf.round2(1.1), 1)
36 | self.assertIsInstance(tkf.round2(1.1), int)
37 |
38 | def test_rgb_to_hsv(self):
39 | self.assertEqual(tkf.rgb_to_hsv(255, 0, 0), (0, 100, 100))
40 |
41 | def test_hsv_to_rgb(self):
42 | self.assertEqual(tkf.hsv_to_rgb(0, 100, 100), (255, 0, 0))
43 |
44 | def test_rgb_to_hexa(self):
45 | self.assertEqual(tkf.rgb_to_hexa(255, 255, 255), "#FFFFFF")
46 | self.assertEqual(tkf.rgb_to_hexa(255, 255, 255, 255), "#FFFFFFFF")
47 | self.assertRaises(ValueError, tkf.rgb_to_hexa, 255, 255)
48 |
49 | def test_hexa_to_rgb(self):
50 | self.assertEqual(tkf.hexa_to_rgb("#FFFFFF"), (255, 255, 255))
51 | self.assertEqual(tkf.hexa_to_rgb("#FFFFFFFF"), (255, 255, 255, 255))
52 | self.assertRaises(ValueError, tkf.hexa_to_rgb, "#FFFFF")
53 |
54 | def test_hue2col(self):
55 | self.assertEqual(tkf.hue2col(0), (255, 0, 0))
56 | self.assertRaises(ValueError, tkf.hue2col, 365)
57 | self.assertRaises(ValueError, tkf.hue2col, -20)
58 |
59 | def test_col2hue(self):
60 | self.assertEqual(tkf.col2hue(255, 0, 0), 0)
61 |
62 | def test_create_checkered_image(self):
63 | tkf.create_checkered_image(100, 100, (155, 120, 10, 255),
64 | (0, 0, 0, 255), s=8)
65 |
66 | def test_overlay(self):
67 | im = tkf.create_checkered_image(200, 200)
68 | tkf.overlay(im, (255, 0, 0, 100))
69 |
70 |
71 | class BaseWidgetTest(unittest.TestCase):
72 | def setUp(self):
73 | self.window = tk.Tk()
74 | self.window.update()
75 |
76 | def tearDown(self):
77 | self.window.update()
78 | self.window.destroy()
79 |
80 |
81 | class TestEvent:
82 | """Fake event for testing."""
83 | def __init__(self, **kwargs):
84 | self._prop = kwargs
85 |
86 | def __getattr__(self, attr):
87 | if attr not in self._prop:
88 | raise AttributeError("TestEvent has no attribute %s." % attr)
89 | else:
90 | return self._prop[attr]
91 |
92 |
93 | class TestSpinbox(BaseWidgetTest):
94 | def test_spinbox_init(self):
95 | spinbox = Spinbox(self.window, from_=0, to=10)
96 | spinbox.pack()
97 | self.window.update()
98 |
99 | def test_spinbox_bindings(self):
100 | spinbox = Spinbox(self.window, from_=0, to=10)
101 | spinbox.pack()
102 | self.window.update()
103 | event = TestEvent(widget=spinbox.frame)
104 | spinbox.focusin(event)
105 | spinbox.focusout(event)
106 |
107 |
108 | class TestLimitVar(BaseWidgetTest):
109 | def test_limitvar_init(self):
110 | var = LimitVar(0, 100, self.window, 10)
111 | self.window.update()
112 | self.assertEqual(var.get(), 10)
113 | del var
114 | var = LimitVar('0', '100', self.window)
115 | self.window.update()
116 | self.assertEqual(var.get(), 0)
117 | del var
118 | var = LimitVar(0, 100, self.window, 200)
119 | self.window.update()
120 | self.assertEqual(var.get(), 100)
121 | del var
122 | var = LimitVar(0, 100, self.window, -2)
123 | self.window.update()
124 | self.assertEqual(var.get(), 0)
125 | del var
126 | self.assertRaises(ValueError, LimitVar, 'a', 0, self.window)
127 | self.assertRaises(ValueError, LimitVar, 0, 'b', self.window)
128 | self.assertRaises(ValueError, LimitVar, 100, 0, self.window)
129 |
130 | def test_limitvar_get(self):
131 | var = LimitVar(0, 100, self.window, 10)
132 | self.window.update()
133 | var.set(-2)
134 | self.window.update()
135 | self.assertEqual(var.get(), 0)
136 | var.set(102)
137 | self.window.update()
138 | self.assertEqual(var.get(), 100)
139 | var.set('12')
140 | self.window.update()
141 | self.assertEqual(var.get(), 12)
142 | var.set('a')
143 | self.window.update()
144 | self.assertEqual(var.get(), 0)
145 | self.assertEqual(tk.StringVar.get(var), '0')
146 |
147 |
148 | class TestColorSquare(BaseWidgetTest):
149 | def test_colorsquare_init(self):
150 | cs = tkc.ColorSquare(self.window, hue=60, height=200, width=200)
151 | cs.pack()
152 | self.window.update()
153 |
154 | def test_colorsquare_bindings(self):
155 | cs = tkc.ColorSquare(self.window, hue=0, height=200, width=200)
156 | cs.pack()
157 | self.window.update()
158 | event = TestEvent(x=0, y=0)
159 | cs._on_click(event)
160 | self.assertEqual(cs.get(), ((0, 0, 0), (0, 100, 0), '#000000'))
161 | event.x = cs.winfo_width()
162 | cs._on_move(event)
163 | self.assertEqual(cs.get(), ((255, 0, 0), (0, 100, 100), '#FF0000'))
164 |
165 | def test_colorsquare_functions(self):
166 | cs = tkc.ColorSquare(self.window, hue=60, height=200, width=200)
167 | cs.pack()
168 | self.window.update()
169 | cs._fill()
170 | self.window.update()
171 | cs._draw((60, 100, 100))
172 | self.window.update()
173 | self.assertEqual(cs.get_hue(), 60)
174 | self.window.update()
175 | cs.set_hue(40)
176 | self.assertEqual(cs.get_hue(), 40)
177 | self.window.update()
178 | cs.set_rgb((255, 0, 0))
179 | self.assertEqual(cs.get_hue(), 0)
180 | self.window.update()
181 | cs.set_hsv((0, 100, 100))
182 | self.assertEqual(cs.get_hue(), 0)
183 | self.window.update()
184 | self.assertEqual(cs.get(), ((255, 0, 0), (0, 100, 100), '#FF0000'))
185 | self.window.update()
186 |
187 |
188 | class TestAlphaBar(BaseWidgetTest):
189 | def test_alphabar_init(self):
190 | ab = tkc.AlphaBar(self.window, alpha=200, color=(255, 255, 2),
191 | height=12, width=200)
192 | ab.pack()
193 | self.window.update()
194 | ab.destroy()
195 | self.window.update()
196 | ab = tkc.AlphaBar(self.window, alpha=500, color=(255, 255, 2),
197 | height=12, width=200)
198 | ab.pack()
199 | self.window.update()
200 | ab.destroy()
201 | self.window.update()
202 | ab = tkc.AlphaBar(self.window, alpha=-20, color=(255, 255, 2),
203 | height=12, width=200)
204 | ab.pack()
205 | self.window.update()
206 | ab.destroy()
207 | self.window.update()
208 | var = tk.IntVar(self.window)
209 | ab = tkc.AlphaBar(self.window, alpha=200, color=(255, 255, 2),
210 | height=12, width=200, variable=var)
211 | ab.pack()
212 | self.window.update()
213 | ab.destroy()
214 | self.window.update()
215 | var = tk.StringVar(self.window, 'a')
216 | ab = tkc.AlphaBar(self.window, alpha=200, color=(255, 255, 2),
217 | height=12, width=200, variable=var)
218 | ab.pack()
219 | self.window.update()
220 |
221 | def test_alphabar_bindings(self):
222 | ab = tkc.AlphaBar(self.window, alpha=20, height=12, width=200)
223 | ab.pack()
224 | self.window.update()
225 | event = TestEvent(x=0, y=1)
226 | ab._on_click(event)
227 | self.window.update()
228 | self.assertEqual(ab.get(), 0)
229 | event.x = ab.winfo_width()
230 | ab._on_move(event)
231 | self.window.update()
232 | self.assertEqual(ab.get(), 255)
233 |
234 | def test_alphabar_functions(self):
235 | ab = tkc.AlphaBar(self.window, alpha=20, height=12, width=200)
236 | ab.pack()
237 | self.window.update()
238 | ab._draw_gradient(60, (255, 255, 0))
239 | self.window.update()
240 | self.assertEqual(ab.get(), 60)
241 | self.window.update()
242 | ab.set(40)
243 | self.window.update()
244 | self.assertEqual(ab.get(), 40)
245 | ab.set_color((0, 0, 0))
246 | self.window.update()
247 | ab.set_color((0, 0, 0, 100))
248 | self.window.update()
249 | ab._update_alpha()
250 | self.window.update()
251 | ab._variable.set(455)
252 | self.window.update()
253 | self.assertEqual(ab.get(), 255)
254 | ab._variable.set(-55)
255 | self.window.update()
256 | self.assertEqual(ab.get(), 0)
257 |
258 |
259 | class TestGradientBar(BaseWidgetTest):
260 | def test_gradientbar_init(self):
261 | gb = tkc.GradientBar(self.window, hue=800, height=12, width=200)
262 | gb.pack()
263 | self.window.update()
264 | gb.destroy()
265 | self.window.update()
266 | gb = tkc.GradientBar(self.window, hue=-20, height=12, width=200)
267 | gb.pack()
268 | self.window.update()
269 | gb.destroy()
270 | self.window.update()
271 | gb = tkc.GradientBar(self.window, hue=20, height=12, width=200)
272 | gb.pack()
273 | self.window.update()
274 | gb.destroy()
275 | self.window.update()
276 | var = tk.IntVar(self.window)
277 | gb = tkc.GradientBar(self.window, hue=20, height=12, width=200,
278 | variable=var)
279 | gb.pack()
280 | self.window.update()
281 | gb.destroy()
282 | self.window.update()
283 | var = tk.StringVar(self.window, 'b')
284 | gb = tkc.GradientBar(self.window, hue=20, height=12, width=200,
285 | variable=var)
286 | gb.pack()
287 | self.window.update()
288 |
289 | def test_gradientbar_bindings(self):
290 | gb = tkc.GradientBar(self.window, hue=20, height=12, width=200)
291 | gb.pack()
292 | self.window.update()
293 | event = TestEvent(x=0, y=1)
294 | gb._on_click(event)
295 | self.window.update()
296 | self.assertEqual(gb.get(), 0)
297 | event.x = gb.winfo_width()
298 | gb._on_move(event)
299 | self.window.update()
300 | self.assertEqual(gb.get(), 360)
301 |
302 | def test_gradientbar_functions(self):
303 | gb = tkc.GradientBar(self.window, hue=20, height=12, width=200)
304 | gb.pack()
305 | self.window.update()
306 | gb._draw_gradient(60)
307 | self.window.update()
308 | self.assertEqual(gb.get(), 60)
309 | self.window.update()
310 | gb.set(40)
311 | self.window.update()
312 | self.assertEqual(gb.get(), 40)
313 | gb._update_hue()
314 | self.window.update()
315 | gb._variable.set(455)
316 | self.window.update()
317 | self.assertEqual(gb.get(), 360)
318 | gb._variable.set(-55)
319 | self.window.update()
320 | self.assertEqual(gb.get(), 0)
321 |
322 |
323 | class TestColorPicker(BaseWidgetTest):
324 | def test_colorpicker_init(self):
325 | c = tkc.ColorPicker(self.window, color="red", title='Test')
326 | self.window.update()
327 | c.ok()
328 | self.assertEqual(c.get_color(),
329 | ((255, 0, 0), (0, 100, 100), '#FF0000'))
330 | c.destroy()
331 | self.window.update()
332 | c = tkc.ColorPicker(self.window, color="red", title='Test', alpha=True)
333 | self.window.update()
334 | c.ok()
335 | self.assertEqual(c.get_color(),
336 | ((255, 0, 0, 255), (0, 100, 100), '#FF0000FF'))
337 | c.destroy()
338 | self.window.update()
339 | c = tkc.ColorPicker(self.window, color="#ff0000", title='Test')
340 | self.window.update()
341 | c.ok()
342 | self.assertEqual(c.get_color(),
343 | ((255, 0, 0), (0, 100, 100), '#FF0000'))
344 | c.destroy()
345 | self.window.update()
346 | c = tkc.ColorPicker(self.window, color="#ff0000", title='Test',
347 | alpha=True)
348 | self.window.update()
349 | c.ok()
350 | self.assertEqual(c.get_color(),
351 | ((255, 0, 0, 255), (0, 100, 100), '#FF0000FF'))
352 | c.destroy()
353 | self.window.update()
354 | c = tkc.ColorPicker(self.window, color="#ff000000", title='Test',
355 | alpha=True)
356 | self.window.update()
357 | c.ok()
358 | self.assertEqual(c.get_color(),
359 | ((255, 0, 0, 0), (0, 100, 100), '#FF000000'))
360 | c.destroy()
361 | self.window.update()
362 | c = tkc.ColorPicker(self.window, color="#ff000000", title='Test')
363 | self.window.update()
364 | c.ok()
365 | self.assertEqual(c.get_color(),
366 | ((255, 0, 0), (0, 100, 100), '#FF0000'))
367 | c.destroy()
368 | self.window.update()
369 | c = tkc.ColorPicker(self.window, color=(255, 0, 0), title='Test')
370 | self.window.update()
371 | c.ok()
372 | self.assertEqual(c.get_color(),
373 | ((255, 0, 0), (0, 100, 100), '#FF0000'))
374 | c.destroy()
375 | self.window.update()
376 | c = tkc.ColorPicker(self.window, color=(255, 0, 0), title='Test',
377 | alpha=True)
378 | self.window.update()
379 | c.ok()
380 | self.assertEqual(c.get_color(),
381 | ((255, 0, 0, 255), (0, 100, 100), '#FF0000FF'))
382 | c.destroy()
383 | self.window.update()
384 | c = tkc.ColorPicker(self.window, color=(255, 0, 0, 0), title='Test',
385 | alpha=True)
386 | self.window.update()
387 | c.ok()
388 | self.assertEqual(c.get_color(),
389 | ((255, 0, 0, 0), (0, 100, 100), '#FF000000'))
390 | c.destroy()
391 | self.window.update()
392 | c = tkc.ColorPicker(self.window, color=(255, 0, 0, 0), title='Test')
393 | self.window.update()
394 | c.ok()
395 | self.assertEqual(c.get_color(),
396 | ((255, 0, 0), (0, 100, 100), '#FF0000'))
397 | c.destroy()
398 | self.window.update()
399 |
400 | def test_colorpicker_bindings(self):
401 | cp = tkc.ColorPicker(self.window, color=(0, 255, 0), title='Test',
402 | alpha=True)
403 | self.window.update()
404 | event = TestEvent(x=0, y=1)
405 | cp.bar._on_click(event)
406 | self.window.update()
407 | self.assertEqual(cp.bar.get(), 0)
408 | cp._change_color(event)
409 | self.window.update()
410 | self.assertEqual(cp.hue.get(), 0)
411 |
412 | self.window.update()
413 | event = TestEvent(x=0, y=1)
414 | cp.alphabar._on_click(event)
415 | self.window.update()
416 | self.assertEqual(cp.alphabar.get(), 0)
417 | cp._change_alpha(event)
418 | self.window.update()
419 | self.assertEqual(cp.alpha.get(), 0)
420 | event.x = cp.alphabar.winfo_width()
421 | cp.alphabar._on_click(event)
422 | cp._change_alpha(event)
423 | self.window.update()
424 |
425 | cp.color_preview.focus_force()
426 | cp._unfocus(event)
427 | self.assertEqual(cp.focus_get(), cp)
428 | cp.hexa.focus_force()
429 | cp._unfocus(event)
430 | self.assertNotEqual(cp.focus_get(), cp)
431 | self.window.update()
432 |
433 | event = TestEvent(x=cp.square.winfo_width(), y=cp.square.winfo_height())
434 | cp.square._on_click(event)
435 | self.window.update()
436 | cp._change_sel_color(event)
437 | self.window.update()
438 | self.assertEqual(cp.square.get(), ((255, 255, 255), (0, 0, 100), '#FFFFFF'))
439 | self.assertEqual(cp.alpha.get(), 255)
440 | self.window.update()
441 | event = TestEvent(widget=tk.Label(self.window, bg='white'))
442 | cp._palette_cmd(event)
443 | self.window.update()
444 | self.assertEqual(cp.square.get(), ((255, 255, 255), (0, 0, 100), '#FFFFFF'))
445 | cp._reset_preview(event)
446 | self.window.update()
447 | self.assertEqual(cp.square.get(), ((0, 255, 0), (120, 100, 100), '#00FF00'))
448 |
449 | cp.hexa.focus_set()
450 | self.window.update()
451 | self.assertFalse(cp.hexa.selection_present())
452 | cp.hexa.event_generate('')
453 | self.assertEqual(cp.hexa.selection_get(), cp.hexa.get())
454 |
455 | s = tk.Spinbox(self.window, from_=0, to=100)
456 | s.insert(0, '20')
457 | s.pack()
458 | s.focus_set()
459 | cp._select_all_spinbox(TestEvent(widget=s))
460 | self.assertEqual(s.selection_get(), s.get())
461 |
462 | def test_colorpicker_functions(self):
463 | # with alpha
464 | cp = tkc.ColorPicker(self.window, color=(255, 0, 0, 100), title='Test',
465 | alpha=True)
466 | self.window.update()
467 | # RGB
468 | cp.green.set(255)
469 | self.window.update()
470 | cp._update_color_rgb()
471 | self.window.update()
472 | self.assertEqual(cp.square.get(), ((255, 255, 0), (60, 100, 100), '#FFFF00'))
473 | self.window.update()
474 | # HSV
475 | cp.value.set(0)
476 | self.window.update()
477 | cp._update_color_hsv()
478 | self.window.update()
479 | self.assertEqual(cp.square.get(), ((0, 0, 0), (60, 100, 0), '#000000'))
480 | self.window.update()
481 | # HTML
482 | cp.hexa.delete(0, 'end')
483 | cp.hexa.insert(0, '#FF0000')
484 | self.window.update()
485 | cp._update_color_hexa()
486 | self.window.update()
487 | self.window.update()
488 | self.assertEqual(cp.square.get(), ((255, 0, 0), (0, 100, 100), '#FF0000'))
489 | self.assertEqual(cp.alpha.get(), 100)
490 | cp.hexa.delete(0, 'end')
491 | cp.hexa.insert(0, '#FFFF00FF')
492 | self.window.update()
493 | cp._update_color_hexa()
494 | self.window.update()
495 | self.window.update()
496 | self.assertEqual(cp.square.get(), ((255, 255, 0), (60, 100, 100), '#FFFF00'))
497 | self.assertEqual(cp.alpha.get(), 255)
498 | cp.hexa.delete(0, 'end')
499 | cp.hexa.insert(0, '#AAA')
500 | self.window.update()
501 | cp._update_color_hexa()
502 | self.window.update()
503 | self.window.update()
504 | self.assertEqual(cp.square.get(), ((255, 255, 0), (60, 100, 100), '#FFFF00'))
505 | self.assertEqual(cp.alpha.get(), 255)
506 | # ALPHA
507 | cp.alpha.set(0)
508 | self.window.update()
509 | cp._update_alpha()
510 | self.window.update()
511 | self.assertEqual(cp.get_color(), "")
512 | self.window.update()
513 | cp.ok()
514 | self.assertEqual(cp.get_color(),
515 | ((255, 255, 0, 0), (60, 100, 100), "#FFFF0000"))
516 | self.window.update()
517 |
518 | # without alpha
519 | cp = tkc.ColorPicker(self.window, color=(255, 0, 0), title='Test')
520 | self.window.update()
521 | self.window.update()
522 | # RGB
523 | cp.green.set(255)
524 | self.window.update()
525 | cp._update_color_rgb()
526 | self.window.update()
527 | self.assertEqual(cp.square.get(), ((255, 255, 0), (60, 100, 100), '#FFFF00'))
528 | self.window.update()
529 | # HSV
530 | cp.value.set(0)
531 | self.window.update()
532 | cp._update_color_hsv()
533 | self.window.update()
534 | self.assertEqual(cp.square.get(), ((0, 0, 0), (60, 100, 0), '#000000'))
535 | self.window.update()
536 | # HTML
537 | cp.hexa.delete(0, 'end')
538 | cp.hexa.insert(0, '#FF0000')
539 | self.window.update()
540 | cp._update_color_hexa()
541 | self.window.update()
542 | self.window.update()
543 | self.assertEqual(cp.square.get(), ((255, 0, 0), (0, 100, 100), '#FF0000'))
544 | cp.hexa.delete(0, 'end')
545 | cp.hexa.insert(0, '#AAA')
546 | self.window.update()
547 | cp._update_color_hexa()
548 | self.window.update()
549 | self.window.update()
550 | self.assertEqual(cp.square.get(), ((255, 0, 0), (0, 100, 100), '#FF0000'))
551 | self.assertEqual(cp.get_color(), "")
552 | self.window.update()
553 | cp.ok()
554 | self.assertEqual(cp.get_color(),
555 | ((255, 0, 0), (0, 100, 100), "#FF0000"))
556 | self.window.update()
557 |
558 | def test_askcolor(self):
559 |
560 | def test(event):
561 | event.widget.ok()
562 | self.assertEqual(event.widget.color[-1], '#FF0000')
563 |
564 | def events():
565 | self.window.update()
566 | c = list(self.window.children.values())[0]
567 | c.bind('', test)
568 | self.window.update()
569 | c.withdraw()
570 | self.window.update()
571 | c.deiconify()
572 |
573 | self.window.after(100, events)
574 | tkc.askcolor(parent=self.window)
575 |
--------------------------------------------------------------------------------
/tkcolorpicker/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 | """
19 |
20 |
21 | from tkcolorpicker.colorpicker import ColorPicker, askcolor
22 | from tkcolorpicker.alphabar import AlphaBar
23 | from tkcolorpicker.gradientbar import GradientBar
24 | from tkcolorpicker.colorsquare import ColorSquare
25 |
--------------------------------------------------------------------------------
/tkcolorpicker/__main__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | Example
20 | """
21 |
22 |
23 | from tkcolorpicker.functions import tk, ttk
24 | from tkcolorpicker import askcolor
25 |
26 |
27 | def select_color1():
28 | print(askcolor(color="sky blue", parent=root))
29 |
30 |
31 | def select_color2():
32 | print(askcolor(color=(255, 120, 0, 100), parent=root, alpha=True))
33 |
34 |
35 | root = tk.Tk()
36 | s = ttk.Style(root)
37 | s.theme_use('clam')
38 | ttk.Label(root, text='Color Selection:').pack(padx=4, pady=4)
39 | ttk.Button(root, text='solid color',
40 | command=select_color1).pack(fill='x', padx=4, pady=4)
41 | ttk.Button(root, text='with alpha channel',
42 | command=select_color2).pack(fill='x', padx=4, pady=4)
43 | root.mainloop()
44 |
--------------------------------------------------------------------------------
/tkcolorpicker/alphabar.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | Alpha channel gradient bar
20 | """
21 |
22 |
23 | from PIL import Image, ImageTk
24 | from tkcolorpicker.functions import tk, round2, rgb_to_hsv
25 | from tkcolorpicker.functions import create_checkered_image
26 |
27 |
28 | class AlphaBar(tk.Canvas):
29 | """Bar to select alpha value."""
30 |
31 | def __init__(self, parent, alpha=255, color=(255, 0, 0), height=11,
32 | width=256, variable=None, **kwargs):
33 | """
34 | Create a bar to select the alpha value.
35 |
36 | Keyword arguments:
37 | * parent: parent window
38 | * alpha: initially selected alpha value
39 | * color: gradient color
40 | * variable: IntVar linked to the alpha value
41 | * height, width, and any keyword argument accepted by a tkinter Canvas
42 | """
43 | tk.Canvas.__init__(self, parent, width=width, height=height, **kwargs)
44 | self.gradient = tk.PhotoImage(master=self, width=width, height=height)
45 |
46 | self._variable = variable
47 | if variable is not None:
48 | try:
49 | alpha = int(variable.get())
50 | except Exception:
51 | pass
52 | else:
53 | self._variable = tk.IntVar(self)
54 | if alpha > 255:
55 | alpha = 255
56 | elif alpha < 0:
57 | alpha = 0
58 | self._variable.set(alpha)
59 | try:
60 | self._variable.trace_add("write", self._update_alpha)
61 | except Exception:
62 | self._variable.trace("w", self._update_alpha)
63 |
64 | self.bind('', lambda e: self._draw_gradient(alpha, color))
65 | self.bind('', self._on_click)
66 | self.bind('', self._on_move)
67 |
68 | def _draw_gradient(self, alpha, color):
69 | """Draw the gradient and put the cursor on alpha."""
70 | self.delete("gradient")
71 | self.delete("cursor")
72 | del self.gradient
73 | width = self.winfo_width()
74 | height = self.winfo_height()
75 |
76 | bg = create_checkered_image(width, height)
77 | r, g, b = color
78 | w = width - 1.
79 | gradient = Image.new("RGBA", (width, height))
80 | for i in range(width):
81 | for j in range(height):
82 | gradient.putpixel((i, j), (r, g, b, round2(i / w * 255)))
83 | self.gradient = ImageTk.PhotoImage(Image.alpha_composite(bg, gradient),
84 | master=self)
85 |
86 | self.create_image(0, 0, anchor="nw", tags="gardient",
87 | image=self.gradient)
88 | self.lower("gradient")
89 |
90 | x = alpha / 255. * width
91 | h, s, v = rgb_to_hsv(r, g, b)
92 | if v < 50:
93 | fill = "gray80"
94 | else:
95 | fill = 'black'
96 | self.create_line(x, 0, x, height, width=2, tags='cursor', fill=fill)
97 |
98 | def _on_click(self, event):
99 | """Move selection cursor on click."""
100 | x = event.x
101 | self.coords('cursor', x, 0, x, self.winfo_height())
102 | self._variable.set(round2((255. * x) / self.winfo_width()))
103 |
104 | def _on_move(self, event):
105 | """Make selection cursor follow the cursor."""
106 | w = self.winfo_width()
107 | x = min(max(event.x, 0), w)
108 | self.coords('cursor', x, 0, x, self.winfo_height())
109 | self._variable.set(round2((255. * x) / w))
110 |
111 | def _update_alpha(self, *args):
112 | alpha = int(self._variable.get())
113 | if alpha > 255:
114 | alpha = 255
115 | elif alpha < 0:
116 | alpha = 0
117 | self.set(alpha)
118 | self.event_generate("<>")
119 |
120 | def get(self):
121 | """Return hue of color under cursor."""
122 | coords = self.coords('cursor')
123 | return round2((255. * coords[0]) / self.winfo_width())
124 |
125 | def set(self, alpha):
126 | """Set cursor position on the color corresponding to the hue value."""
127 | x = alpha / 255. * self.winfo_width()
128 | self.coords('cursor', x, 0, x, self.winfo_height())
129 | self._variable.set(alpha)
130 |
131 | def set_color(self, color):
132 | """Set gradient color to color in RGB(A)."""
133 | if len(color) == 3:
134 | alpha = self.get()
135 | else:
136 | alpha = color[3]
137 | self._draw_gradient(alpha, color[:3])
138 |
--------------------------------------------------------------------------------
/tkcolorpicker/colorpicker.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | Colorpicker dialog
20 | """
21 |
22 |
23 | from PIL import ImageTk
24 | from tkcolorpicker.functions import tk, ttk, round2, create_checkered_image, \
25 | overlay, PALETTE, hsv_to_rgb, hexa_to_rgb, rgb_to_hexa, col2hue, rgb_to_hsv
26 | from tkcolorpicker.alphabar import AlphaBar
27 | from tkcolorpicker.gradientbar import GradientBar
28 | from tkcolorpicker.colorsquare import ColorSquare
29 | from tkcolorpicker.spinbox import Spinbox
30 | from tkcolorpicker.limitvar import LimitVar
31 | from locale import getdefaultlocale
32 | import re
33 |
34 |
35 | # --- Translation
36 | EN = {}
37 | FR = {"Red": "Rouge", "Green": "Vert", "Blue": "Bleu",
38 | "Hue": "Teinte", "Saturation": "Saturation", "Value": "Valeur",
39 | "Cancel": "Annuler", "Color Chooser": "Sélecteur de couleur",
40 | "Alpha": "Alpha"}
41 |
42 | try:
43 | if getdefaultlocale()[0][:2] == 'fr':
44 | TR = FR
45 | else:
46 | TR = EN
47 | except ValueError:
48 | TR = EN
49 |
50 |
51 | def _(text):
52 | """Translate text."""
53 | return TR.get(text, text)
54 |
55 |
56 | class ColorPicker(tk.Toplevel):
57 | """Color picker dialog."""
58 |
59 | def __init__(self, parent=None, color=(255, 0, 0), alpha=False,
60 | title=_("Color Chooser")):
61 | """
62 | Create a ColorPicker dialog.
63 |
64 | Arguments:
65 | * parent: parent window
66 | * color: initially selected color in rgb or hexa format
67 | * alpha: alpha channel support (boolean)
68 | * title: dialog title
69 | """
70 | tk.Toplevel.__init__(self, parent)
71 |
72 | self.title(title)
73 | self.transient(self.master)
74 | self.resizable(False, False)
75 | self.rowconfigure(1, weight=1)
76 |
77 | self.color = ""
78 | self.alpha_channel = bool(alpha)
79 | style = ttk.Style(self)
80 | style.map("palette.TFrame", relief=[('focus', 'sunken')],
81 | bordercolor=[('focus', "#4D4D4D")])
82 | self.configure(background=style.lookup("TFrame", "background"))
83 |
84 | if isinstance(color, str):
85 | if re.match(r"^#[0-9A-F]{8}$", color.upper()):
86 | col = hexa_to_rgb(color)
87 | self._old_color = col[:3]
88 | if alpha:
89 | self._old_alpha = col[3]
90 | old_color = color
91 | else:
92 | old_color = color[:7]
93 | elif re.match(r"^#[0-9A-F]{6}$", color.upper()):
94 | self._old_color = hexa_to_rgb(color)
95 | old_color = color
96 | if alpha:
97 | self._old_alpha = 255
98 | old_color += 'FF'
99 | else:
100 | col = self.winfo_rgb(color)
101 | self._old_color = tuple(round2(c * 255 / 65535) for c in col)
102 | args = self._old_color
103 | if alpha:
104 | self._old_alpha = 255
105 | args = self._old_color + (255,)
106 | old_color = rgb_to_hexa(*args)
107 | else:
108 | self._old_color = color[:3]
109 | if alpha:
110 | if len(color) < 4:
111 | color += (255,)
112 | self._old_alpha = 255
113 | else:
114 | self._old_alpha = color[3]
115 | old_color = rgb_to_hexa(*color)
116 |
117 | # --- GradientBar
118 | hue = col2hue(*self._old_color)
119 | bar = ttk.Frame(self, borderwidth=2, relief='groove')
120 | self.bar = GradientBar(bar, hue=hue, width=200, highlightthickness=0)
121 | self.bar.pack()
122 |
123 | # --- ColorSquare
124 | square = ttk.Frame(self, borderwidth=2, relief='groove')
125 | self.square = ColorSquare(square, hue=hue, width=200, height=200,
126 | color=rgb_to_hsv(*self._old_color),
127 | highlightthickness=0)
128 | self.square.pack()
129 |
130 | frame = ttk.Frame(self)
131 | frame.columnconfigure(1, weight=1)
132 | frame.rowconfigure(1, weight=1)
133 |
134 | # --- color preview: initial color and currently selected color side by side
135 | preview_frame = ttk.Frame(frame, relief="groove", borderwidth=2)
136 | preview_frame.grid(row=0, column=0, sticky="nw", pady=2)
137 | if alpha:
138 | self._transparent_bg = create_checkered_image(42, 32)
139 | transparent_bg_old = create_checkered_image(42, 32,
140 | (100, 100, 100, 255),
141 | (154, 154, 154, 255))
142 | prev_old = overlay(transparent_bg_old, hexa_to_rgb(old_color))
143 | prev = overlay(self._transparent_bg, hexa_to_rgb(old_color))
144 | self._im_old_color = ImageTk.PhotoImage(prev_old, master=self)
145 | self._im_color = ImageTk.PhotoImage(prev, master=self)
146 | old_color_prev = tk.Label(preview_frame, padx=0, pady=0,
147 | image=self._im_old_color,
148 | borderwidth=0, highlightthickness=0)
149 | self.color_preview = tk.Label(preview_frame, pady=0, padx=0,
150 | image=self._im_color,
151 | borderwidth=0, highlightthickness=0)
152 | else:
153 | old_color_prev = tk.Label(preview_frame, background=old_color[:7],
154 | width=5, highlightthickness=0, height=2,
155 | padx=0, pady=0)
156 | self.color_preview = tk.Label(preview_frame, width=5, height=2,
157 | pady=0, background=old_color[:7],
158 | padx=0, highlightthickness=0)
159 | old_color_prev.bind("<1>", self._reset_preview)
160 | old_color_prev.grid(row=0, column=0)
161 | self.color_preview.grid(row=0, column=1)
162 |
163 | # --- palette
164 | palette = ttk.Frame(frame)
165 | palette.grid(row=0, column=1, rowspan=2, sticky="ne")
166 | for i, col in enumerate(PALETTE):
167 | f = ttk.Frame(palette, borderwidth=1, relief="raised",
168 | style="palette.TFrame")
169 | l = tk.Label(f, background=col, width=2, height=1)
170 | l.bind("<1>", self._palette_cmd)
171 | f.bind("", lambda e: e.widget.configure(relief="raised"))
172 | l.pack()
173 | f.grid(row=i % 2, column=i // 2, padx=2, pady=2)
174 |
175 | col_frame = ttk.Frame(self)
176 | # --- hsv
177 | hsv_frame = ttk.Frame(col_frame, relief="ridge", borderwidth=2)
178 | hsv_frame.pack(pady=(0, 4), fill="x")
179 | hsv_frame.columnconfigure(0, weight=1)
180 | self.hue = LimitVar(0, 360, self)
181 | self.saturation = LimitVar(0, 100, self)
182 | self.value = LimitVar(0, 100, self)
183 |
184 | s_h = Spinbox(hsv_frame, from_=0, to=360, width=4, name='spinbox',
185 | textvariable=self.hue, command=self._update_color_hsv)
186 | s_s = Spinbox(hsv_frame, from_=0, to=100, width=4,
187 | textvariable=self.saturation, name='spinbox',
188 | command=self._update_color_hsv)
189 | s_v = Spinbox(hsv_frame, from_=0, to=100, width=4, name='spinbox',
190 | textvariable=self.value, command=self._update_color_hsv)
191 | h, s, v = rgb_to_hsv(*self._old_color)
192 | s_h.delete(0, 'end')
193 | s_h.insert(0, h)
194 | s_s.delete(0, 'end')
195 | s_s.insert(0, s)
196 | s_v.delete(0, 'end')
197 | s_v.insert(0, v)
198 | s_h.grid(row=0, column=1, sticky='w', padx=4, pady=4)
199 | s_s.grid(row=1, column=1, sticky='w', padx=4, pady=4)
200 | s_v.grid(row=2, column=1, sticky='w', padx=4, pady=4)
201 | ttk.Label(hsv_frame, text=_('Hue')).grid(row=0, column=0, sticky='e',
202 | padx=4, pady=4)
203 | ttk.Label(hsv_frame, text=_('Saturation')).grid(row=1, column=0, sticky='e',
204 | padx=4, pady=4)
205 | ttk.Label(hsv_frame, text=_('Value')).grid(row=2, column=0, sticky='e',
206 | padx=4, pady=4)
207 |
208 | # --- rgb
209 | rgb_frame = ttk.Frame(col_frame, relief="ridge", borderwidth=2)
210 | rgb_frame.pack(pady=4, fill="x")
211 | rgb_frame.columnconfigure(0, weight=1)
212 | self.red = LimitVar(0, 255, self)
213 | self.green = LimitVar(0, 255, self)
214 | self.blue = LimitVar(0, 255, self)
215 |
216 | s_red = Spinbox(rgb_frame, from_=0, to=255, width=4, name='spinbox',
217 | textvariable=self.red, command=self._update_color_rgb)
218 | s_green = Spinbox(rgb_frame, from_=0, to=255, width=4, name='spinbox',
219 | textvariable=self.green, command=self._update_color_rgb)
220 | s_blue = Spinbox(rgb_frame, from_=0, to=255, width=4, name='spinbox',
221 | textvariable=self.blue, command=self._update_color_rgb)
222 | s_red.delete(0, 'end')
223 | s_red.insert(0, self._old_color[0])
224 | s_green.delete(0, 'end')
225 | s_green.insert(0, self._old_color[1])
226 | s_blue.delete(0, 'end')
227 | s_blue.insert(0, self._old_color[2])
228 | s_red.grid(row=0, column=1, sticky='e', padx=4, pady=4)
229 | s_green.grid(row=1, column=1, sticky='e', padx=4, pady=4)
230 | s_blue.grid(row=2, column=1, sticky='e', padx=4, pady=4)
231 | ttk.Label(rgb_frame, text=_('Red')).grid(row=0, column=0, sticky='e',
232 | padx=4, pady=4)
233 | ttk.Label(rgb_frame, text=_('Green')).grid(row=1, column=0, sticky='e',
234 | padx=4, pady=4)
235 | ttk.Label(rgb_frame, text=_('Blue')).grid(row=2, column=0, sticky='e',
236 | padx=4, pady=4)
237 | # --- hexa
238 | hexa_frame = ttk.Frame(col_frame)
239 | hexa_frame.pack(fill="x")
240 | self.hexa = ttk.Entry(hexa_frame, justify="center", width=10, name='entry')
241 | self.hexa.insert(0, old_color.upper())
242 | ttk.Label(hexa_frame, text="HTML").pack(side="left", padx=4, pady=(4, 1))
243 | self.hexa.pack(side="left", padx=6, pady=(4, 1), fill='x', expand=True)
244 |
245 | # --- alpha
246 | if alpha:
247 | alpha_frame = ttk.Frame(self)
248 | alpha_frame.columnconfigure(1, weight=1)
249 | self.alpha = LimitVar(0, 255, self)
250 | alphabar = ttk.Frame(alpha_frame, borderwidth=2, relief='groove')
251 | self.alphabar = AlphaBar(alphabar, alpha=self._old_alpha, width=200,
252 | color=self._old_color, highlightthickness=0)
253 | self.alphabar.pack()
254 | s_alpha = Spinbox(alpha_frame, from_=0, to=255, width=4,
255 | textvariable=self.alpha, command=self._update_alpha)
256 | s_alpha.delete(0, 'end')
257 | s_alpha.insert(0, self._old_alpha)
258 | alphabar.grid(row=0, column=0, padx=(0, 4), pady=4, sticky='w')
259 | ttk.Label(alpha_frame, text=_('Alpha')).grid(row=0, column=1, sticky='e',
260 | padx=4, pady=4)
261 | s_alpha.grid(row=0, column=2, sticky='w', padx=(4, 6), pady=4)
262 |
263 | # --- validation
264 | button_frame = ttk.Frame(self)
265 | ttk.Button(button_frame, text="Ok",
266 | command=self.ok).pack(side="right", padx=10)
267 | ttk.Button(button_frame, text=_("Cancel"),
268 | command=self.destroy).pack(side="right", padx=10)
269 |
270 | # --- placement
271 | bar.grid(row=0, column=0, padx=10, pady=(10, 4), sticky='n')
272 | square.grid(row=1, column=0, padx=10, pady=(9, 0), sticky='n')
273 | if alpha:
274 | alpha_frame.grid(row=2, column=0, columnspan=2, padx=10,
275 | pady=(1, 4), sticky='ewn')
276 | col_frame.grid(row=0, rowspan=2, column=1, padx=(4, 10), pady=(10, 4))
277 | frame.grid(row=3, column=0, columnspan=2, pady=(4, 10), padx=10, sticky="new")
278 | button_frame.grid(row=4, columnspan=2, pady=(0, 10), padx=10)
279 |
280 | # --- bindings
281 | self.bar.bind("", self._change_color, True)
282 | self.bar.bind("", self._unfocus, True)
283 | if alpha:
284 | self.alphabar.bind("", self._change_alpha, True)
285 | self.alphabar.bind("", self._unfocus, True)
286 | self.square.bind("", self._unfocus, True)
287 | self.square.bind("", self._change_sel_color, True)
288 | self.square.bind("", self._change_sel_color, True)
289 | s_red.bind('', self._update_color_rgb)
290 | s_green.bind('', self._update_color_rgb)
291 | s_blue.bind('', self._update_color_rgb)
292 | s_red.bind('', self._update_color_rgb)
293 | s_green.bind('', self._update_color_rgb)
294 | s_blue.bind('', self._update_color_rgb)
295 | s_red.bind('', self._select_all_spinbox)
296 | s_green.bind('', self._select_all_spinbox)
297 | s_blue.bind('', self._select_all_spinbox)
298 | s_h.bind('', self._update_color_hsv)
299 | s_s.bind('', self._update_color_hsv)
300 | s_v.bind('', self._update_color_hsv)
301 | s_h.bind('', self._update_color_hsv)
302 | s_s.bind('', self._update_color_hsv)
303 | s_v.bind('', self._update_color_hsv)
304 | s_h.bind('', self._select_all_spinbox)
305 | s_s.bind('', self._select_all_spinbox)
306 | s_v.bind('', self._select_all_spinbox)
307 | if alpha:
308 | s_alpha.bind('', self._update_alpha)
309 | s_alpha.bind('', self._update_alpha)
310 | s_alpha.bind('', self._select_all_spinbox)
311 | self.hexa.bind("", self._update_color_hexa)
312 | self.hexa.bind("", self._update_color_hexa)
313 | self.hexa.bind("", self._select_all_entry)
314 |
315 | self.hexa.focus_set()
316 | self.wait_visibility()
317 | self.lift()
318 | self.grab_set()
319 |
320 | def get_color(self):
321 | """Return selected color, return an empty string if no color is selected."""
322 | return self.color
323 |
324 | @staticmethod
325 | def _select_all_spinbox(event):
326 | """Select all entry content."""
327 | event.widget.selection('range', 0, 'end')
328 | return "break"
329 |
330 | @staticmethod
331 | def _select_all_entry(event):
332 | """Select all entry content."""
333 | event.widget.selection_range(0, 'end')
334 | return "break"
335 |
336 | def _unfocus(self, event):
337 | """Unfocus palette items when click on bar or square."""
338 | w = self.focus_get()
339 | if w != self and 'spinbox' not in str(w) and 'entry' not in str(w):
340 | self.focus_set()
341 |
342 | def _update_preview(self):
343 | """Update color preview."""
344 | color = self.hexa.get()
345 | if self.alpha_channel:
346 | prev = overlay(self._transparent_bg, hexa_to_rgb(color))
347 | self._im_color = ImageTk.PhotoImage(prev, master=self)
348 | self.color_preview.configure(image=self._im_color)
349 | else:
350 | self.color_preview.configure(background=color)
351 |
352 | def _reset_preview(self, event):
353 | """Respond to user click on a palette item."""
354 | label = event.widget
355 | label.master.focus_set()
356 | label.master.configure(relief="sunken")
357 | args = self._old_color
358 | if self.alpha_channel:
359 | args += (self._old_alpha,)
360 | self.alpha.set(self._old_alpha)
361 | self.alphabar.set_color(args)
362 | color = rgb_to_hexa(*args)
363 | h, s, v = rgb_to_hsv(*self._old_color)
364 | self.red.set(self._old_color[0])
365 | self.green.set(self._old_color[1])
366 | self.blue.set(self._old_color[2])
367 | self.hue.set(h)
368 | self.saturation.set(s)
369 | self.value.set(v)
370 | self.hexa.delete(0, "end")
371 | self.hexa.insert(0, color.upper())
372 | self.bar.set(h)
373 | self.square.set_hsv((h, s, v))
374 | self._update_preview()
375 |
376 | def _palette_cmd(self, event):
377 | """Respond to user click on a palette item."""
378 | label = event.widget
379 | label.master.focus_set()
380 | label.master.configure(relief="sunken")
381 | r, g, b = self.winfo_rgb(label.cget("background"))
382 | r = round2(r * 255 / 65535)
383 | g = round2(g * 255 / 65535)
384 | b = round2(b * 255 / 65535)
385 | args = (r, g, b)
386 | if self.alpha_channel:
387 | a = self.alpha.get()
388 | args += (a,)
389 | self.alphabar.set_color(args)
390 | color = rgb_to_hexa(*args)
391 | h, s, v = rgb_to_hsv(r, g, b)
392 | self.red.set(r)
393 | self.green.set(g)
394 | self.blue.set(b)
395 | self.hue.set(h)
396 | self.saturation.set(s)
397 | self.value.set(v)
398 | self.hexa.delete(0, "end")
399 | self.hexa.insert(0, color.upper())
400 | self.bar.set(h)
401 | self.square.set_hsv((h, s, v))
402 | self._update_preview()
403 |
404 | def _change_sel_color(self, event):
405 | """Respond to motion of the color selection cross."""
406 | (r, g, b), (h, s, v), color = self.square.get()
407 | self.red.set(r)
408 | self.green.set(g)
409 | self.blue.set(b)
410 | self.saturation.set(s)
411 | self.value.set(v)
412 | self.hexa.delete(0, "end")
413 | self.hexa.insert(0, color.upper())
414 | if self.alpha_channel:
415 | self.alphabar.set_color((r, g, b))
416 | self.hexa.insert('end',
417 | ("%2.2x" % self.alpha.get()).upper())
418 | self._update_preview()
419 |
420 | def _change_color(self, event):
421 | """Respond to motion of the hsv cursor."""
422 | h = self.bar.get()
423 | self.square.set_hue(h)
424 | (r, g, b), (h, s, v), sel_color = self.square.get()
425 | self.red.set(r)
426 | self.green.set(g)
427 | self.blue.set(b)
428 | self.hue.set(h)
429 | self.saturation.set(s)
430 | self.value.set(v)
431 | self.hexa.delete(0, "end")
432 | self.hexa.insert(0, sel_color.upper())
433 | if self.alpha_channel:
434 | self.alphabar.set_color((r, g, b))
435 | self.hexa.insert('end',
436 | ("%2.2x" % self.alpha.get()).upper())
437 | self._update_preview()
438 |
439 | def _change_alpha(self, event):
440 | """Respond to motion of the alpha cursor."""
441 | a = self.alphabar.get()
442 | self.alpha.set(a)
443 | hexa = self.hexa.get()
444 | hexa = hexa[:7] + ("%2.2x" % a).upper()
445 | self.hexa.delete(0, 'end')
446 | self.hexa.insert(0, hexa)
447 | self._update_preview()
448 |
449 | def _update_color_hexa(self, event=None):
450 | """Update display after a change in the HEX entry."""
451 | color = self.hexa.get().upper()
452 | self.hexa.delete(0, 'end')
453 | self.hexa.insert(0, color)
454 | if re.match(r"^#[0-9A-F]{6}$", color):
455 | r, g, b = hexa_to_rgb(color)
456 | self.red.set(r)
457 | self.green.set(g)
458 | self.blue.set(b)
459 | h, s, v = rgb_to_hsv(r, g, b)
460 | self.hue.set(h)
461 | self.saturation.set(s)
462 | self.value.set(v)
463 | self.bar.set(h)
464 | self.square.set_hsv((h, s, v))
465 | if self.alpha_channel:
466 | a = self.alpha.get()
467 | self.hexa.insert('end', ("%2.2x" % a).upper())
468 | self.alphabar.set_color((r, g, b, a))
469 | elif self.alpha_channel and re.match(r"^#[0-9A-F]{8}$", color):
470 | r, g, b, a = hexa_to_rgb(color)
471 | self.red.set(r)
472 | self.green.set(g)
473 | self.blue.set(b)
474 | self.alpha.set(a)
475 | self.alphabar.set_color((r, g, b, a))
476 | h, s, v = rgb_to_hsv(r, g, b)
477 | self.hue.set(h)
478 | self.saturation.set(s)
479 | self.value.set(v)
480 | self.bar.set(h)
481 | self.square.set_hsv((h, s, v))
482 | else:
483 | self._update_color_rgb()
484 | self._update_preview()
485 |
486 | def _update_alpha(self, event=None):
487 | """Update display after a change in the alpha spinbox."""
488 | a = self.alpha.get()
489 | hexa = self.hexa.get()
490 | hexa = hexa[:7] + ("%2.2x" % a).upper()
491 | self.hexa.delete(0, 'end')
492 | self.hexa.insert(0, hexa)
493 | self.alphabar.set(a)
494 | self._update_preview()
495 |
496 | def _update_color_hsv(self, event=None):
497 | """Update display after a change in the HSV spinboxes."""
498 | if event is None or event.widget.old_value != event.widget.get():
499 | h = self.hue.get()
500 | s = self.saturation.get()
501 | v = self.value.get()
502 | sel_color = hsv_to_rgb(h, s, v)
503 | self.red.set(sel_color[0])
504 | self.green.set(sel_color[1])
505 | self.blue.set(sel_color[2])
506 | if self.alpha_channel:
507 | sel_color += (self.alpha.get(),)
508 | self.alphabar.set_color(sel_color)
509 | hexa = rgb_to_hexa(*sel_color)
510 | self.hexa.delete(0, "end")
511 | self.hexa.insert(0, hexa)
512 | self.square.set_hsv((h, s, v))
513 | self.bar.set(h)
514 | self._update_preview()
515 |
516 | def _update_color_rgb(self, event=None):
517 | """Update display after a change in the RGB spinboxes."""
518 | if event is None or event.widget.old_value != event.widget.get():
519 | r = self.red.get()
520 | g = self.green.get()
521 | b = self.blue.get()
522 | h, s, v = rgb_to_hsv(r, g, b)
523 | self.hue.set(h)
524 | self.saturation.set(s)
525 | self.value.set(v)
526 | args = (r, g, b)
527 | if self.alpha_channel:
528 | args += (self.alpha.get(),)
529 | self.alphabar.set_color(args)
530 | hexa = rgb_to_hexa(*args)
531 | self.hexa.delete(0, "end")
532 | self.hexa.insert(0, hexa)
533 | self.square.set_hsv((h, s, v))
534 | self.bar.set(h)
535 | self._update_preview()
536 |
537 | def ok(self):
538 | rgb, hsv, hexa = self.square.get()
539 | if self.alpha_channel:
540 | hexa = self.hexa.get()
541 | rgb += (self.alpha.get(),)
542 | self.color = rgb, hsv, hexa
543 | self.destroy()
544 |
545 |
546 | def askcolor(color="red", parent=None, title=_("Color Chooser"), alpha=False):
547 | """
548 | Open a ColorPicker dialog and return the chosen color.
549 |
550 | The selected color is retunred in RGB(A) and hexadecimal #RRGGBB(AA) formats.
551 | (None, None) is returned if the color selection is cancelled.
552 |
553 | Arguments:
554 | * color: initially selected color (RGB(A), hexa or tkinter color name)
555 | * parent: parent window
556 | * title: dialog title
557 | * alpha: alpha channel suppport
558 | """
559 | col = ColorPicker(parent, color, alpha, title)
560 | col.wait_window(col)
561 | res = col.get_color()
562 | if res:
563 | return res[0], res[2]
564 | else:
565 | return None, None
566 |
--------------------------------------------------------------------------------
/tkcolorpicker/colorsquare.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | Color square gradient with selection cross
20 | """
21 |
22 |
23 | from tkcolorpicker.functions import tk, round2, rgb_to_hexa, hue2col, rgb_to_hsv
24 |
25 |
26 | class ColorSquare(tk.Canvas):
27 | """Square color gradient with selection cross."""
28 |
29 | def __init__(self, parent, hue, color=None, height=256, width=256, **kwargs):
30 | """
31 | Create a ColorSquare.
32 |
33 | Keyword arguments:
34 | * parent: parent window
35 | * hue: color square gradient for given hue (color in top right corner
36 | is (hue, 100, 100) in HSV
37 | * color: initially selected color given in HSV
38 | * width, height and any keyword option accepted by a tkinter Canvas
39 | """
40 | tk.Canvas.__init__(self, parent, height=height, width=width, **kwargs)
41 | self.bg = tk.PhotoImage(width=width, height=height, master=self)
42 | self._hue = hue
43 | if not color:
44 | color = hue2col(self._hue)
45 | self.bind('', lambda e: self._draw(color))
46 | self.bind('', self._on_click)
47 | self.bind('', self._on_move)
48 |
49 | def _fill(self):
50 | """Create the gradient."""
51 | r, g, b = hue2col(self._hue)
52 | width = self.winfo_width()
53 | height = self.winfo_height()
54 | h = float(height - 1)
55 | w = float(width - 1)
56 | if height:
57 | c = [(r + i / h * (255 - r), g + i / h * (255 - g), b + i / h * (255 - b)) for i in range(height)]
58 | data = []
59 | for i in range(height):
60 | line = []
61 | for j in range(width):
62 | rij = round2(j / w * c[i][0])
63 | gij = round2(j / w * c[i][1])
64 | bij = round2(j / w * c[i][2])
65 | color = rgb_to_hexa(rij, gij, bij)
66 | line.append(color)
67 | data.append("{" + " ".join(line) + "}")
68 | self.bg.put(" ".join(data))
69 |
70 | def _draw(self, color):
71 | """Draw the gradient and the selection cross on the canvas."""
72 | width = self.winfo_width()
73 | height = self.winfo_height()
74 | self.delete("bg")
75 | self.delete("cross_h")
76 | self.delete("cross_v")
77 | del self.bg
78 | self.bg = tk.PhotoImage(width=width, height=height, master=self)
79 | self._fill()
80 | self.create_image(0, 0, image=self.bg, anchor="nw", tags="bg")
81 | self.tag_lower("bg")
82 | h, s, v = color
83 | x = v / 100.
84 | y = (1 - s / 100.)
85 | self.create_line(0, y * height, width, y * height, tags="cross_h",
86 | fill="#C2C2C2")
87 | self.create_line(x * width, 0, x * width, height, tags="cross_v",
88 | fill="#C2C2C2")
89 |
90 | def get_hue(self):
91 | """Return hue."""
92 | return self._hue
93 |
94 | def set_hue(self, value):
95 | """Set hue."""
96 | old = self._hue
97 | self._hue = value
98 | if value != old:
99 | self._fill()
100 | self.event_generate("<>")
101 |
102 | def _on_click(self, event):
103 | """Move cross on click."""
104 | x = event.x
105 | y = event.y
106 | self.coords('cross_h', 0, y, self.winfo_width(), y)
107 | self.coords('cross_v', x, 0, x, self.winfo_height())
108 | self.event_generate("<>")
109 |
110 | def _on_move(self, event):
111 | """Make the cross follow the cursor."""
112 | w = self.winfo_width()
113 | h = self.winfo_height()
114 | x = min(max(event.x, 0), w)
115 | y = min(max(event.y, 0), h)
116 | self.coords('cross_h', 0, y, w, y)
117 | self.coords('cross_v', x, 0, x, h)
118 | self.event_generate("<>")
119 |
120 | def get(self):
121 | """Return selected color with format (RGB, HSV, HEX)."""
122 | x = self.coords('cross_v')[0]
123 | y = self.coords('cross_h')[1]
124 | xp = min(x, self.bg.width() - 1)
125 | yp = min(y, self.bg.height() - 1)
126 | try:
127 | r, g, b = self.bg.get(round2(xp), round2(yp))
128 | except ValueError:
129 | r, g, b = self.bg.get(round2(xp), round2(yp)).split()
130 | r, g, b = int(r), int(g), int(b)
131 | hexa = rgb_to_hexa(r, g, b)
132 | h = self.get_hue()
133 | s = round2((1 - float(y) / self.winfo_height()) * 100)
134 | v = round2(100 * float(x) / self.winfo_width())
135 | return (r, g, b), (h, s, v), hexa
136 |
137 | def set_rgb(self, sel_color):
138 | """Put cursor on sel_color given in RGB."""
139 | width = self.winfo_width()
140 | height = self.winfo_height()
141 | h, s, v = rgb_to_hsv(*sel_color)
142 | self.set_hue(h)
143 | x = v / 100.
144 | y = (1 - s / 100.)
145 | self.coords('cross_h', 0, y * height, width, y * height)
146 | self.coords('cross_v', x * width, 0, x * width, height)
147 |
148 | def set_hsv(self, sel_color):
149 | """Put cursor on sel_color given in HSV."""
150 | width = self.winfo_width()
151 | height = self.winfo_height()
152 | h, s, v = sel_color
153 | self.set_hue(h)
154 | x = v / 100.
155 | y = (1 - s / 100.)
156 | self.coords('cross_h', 0, y * height, width, y * height)
157 | self.coords('cross_v', x * width, 0, x * width, height)
158 |
--------------------------------------------------------------------------------
/tkcolorpicker/functions.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | Functions and constants
20 | """
21 |
22 |
23 | try:
24 | import tkinter as tk
25 | from tkinter import ttk
26 | except ImportError:
27 | import Tkinter as tk
28 | import ttk
29 | from PIL import Image, ImageDraw, ImageTk
30 | from math import atan2, sqrt, pi
31 | import colorsys
32 |
33 |
34 | PALETTE = ("red", "dark red", "orange", "yellow", "green", "lightgreen", "blue",
35 | "royal blue", "sky blue", "purple", "magenta", "pink", "black",
36 | "white", "gray", "saddle brown", "lightgray", "wheat")
37 |
38 |
39 | # in some python versions round returns a float instead of an int
40 | if not isinstance(round(1.0), int):
41 | def round2(nb):
42 | """Round number to 0 digits and return an int."""
43 | return int(nb + 0.5) # works because nb >= 0
44 | else:
45 | round2 = round
46 |
47 |
48 | # --- conversion functions
49 | def rgb_to_hsv(r, g, b):
50 | """Convert RGB color to HSV."""
51 | h, s, v = colorsys.rgb_to_hsv(r / 255., g / 255., b / 255.)
52 | return round2(h * 360), round2(s * 100), round2(v * 100)
53 |
54 |
55 | def hsv_to_rgb(h, s, v):
56 | """Convert HSV color to RGB."""
57 | r, g, b = colorsys.hsv_to_rgb(h / 360., s / 100., v / 100.)
58 | return round2(r * 255), round2(g * 255), round2(b * 255)
59 |
60 |
61 | def rgb_to_hexa(*args):
62 | """Convert RGB(A) color to hexadecimal."""
63 | if len(args) == 3:
64 | return ("#%2.2x%2.2x%2.2x" % tuple(args)).upper()
65 | elif len(args) == 4:
66 | return ("#%2.2x%2.2x%2.2x%2.2x" % tuple(args)).upper()
67 | else:
68 | raise ValueError("Wrong number of arguments.")
69 |
70 |
71 | def hexa_to_rgb(color):
72 | """Convert hexadecimal color to RGB."""
73 | r = int(color[1:3], 16)
74 | g = int(color[3:5], 16)
75 | b = int(color[5:7], 16)
76 | if len(color) == 7:
77 | return r, g, b
78 | elif len(color) == 9:
79 | return r, g, b, int(color[7:9], 16)
80 | else:
81 | raise ValueError("Invalid hexadecimal notation.")
82 |
83 |
84 | def col2hue(r, g, b):
85 | """Return hue value corresponding to given RGB color."""
86 | return round2(180 / pi * atan2(sqrt(3) * (g - b), 2 * r - g - b) + 360) % 360
87 |
88 |
89 | def hue2col(h):
90 | """Return the color in RGB format corresponding to (h, 100, 100) in HSV."""
91 | if h < 0 or h > 360:
92 | raise ValueError("Hue should be between 0 and 360")
93 | else:
94 | return hsv_to_rgb(h, 100, 100)
95 |
96 |
97 | # --- Fake transparent image creation with PIL
98 | def create_checkered_image(width, height, c1=(154, 154, 154, 255),
99 | c2=(100, 100, 100, 255), s=6):
100 | """
101 | Return a checkered image of size width x height.
102 |
103 | Arguments:
104 | * width: image width
105 | * height: image height
106 | * c1: first color (RGBA)
107 | * c2: second color (RGBA)
108 | * s: size of the squares
109 | """
110 | im = Image.new("RGBA", (width, height), c1)
111 | draw = ImageDraw.Draw(im, "RGBA")
112 | for i in range(s, width, 2 * s):
113 | for j in range(0, height, 2 * s):
114 | draw.rectangle(((i, j), ((i + s - 1, j + s - 1))), fill=c2)
115 | for i in range(0, width, 2 * s):
116 | for j in range(s, height, 2 * s):
117 | draw.rectangle(((i, j), ((i + s - 1, j + s - 1))), fill=c2)
118 | return im
119 |
120 |
121 | def overlay(image, color):
122 | """
123 | Overlay a rectangle of color (RGBA) on the image and return the result.
124 | """
125 | width, height = image.size
126 | im = Image.new("RGBA", (width, height), color)
127 | preview = Image.alpha_composite(image, im)
128 | return preview
129 |
--------------------------------------------------------------------------------
/tkcolorpicker/gradientbar.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | HSV gradient bar
20 | """
21 |
22 |
23 | from tkcolorpicker.functions import tk, round2, rgb_to_hexa, hue2col
24 |
25 |
26 | class GradientBar(tk.Canvas):
27 | """HSV gradient colorbar with selection cursor."""
28 |
29 | def __init__(self, parent, hue=0, height=11, width=256, variable=None,
30 | **kwargs):
31 | """
32 | Create a GradientBar.
33 |
34 | Keyword arguments:
35 | * parent: parent window
36 | * hue: initially selected hue value
37 | * variable: IntVar linked to the alpha value
38 | * height, width, and any keyword argument accepted by a tkinter Canvas
39 | """
40 | tk.Canvas.__init__(self, parent, width=width, height=height, **kwargs)
41 |
42 | self._variable = variable
43 | if variable is not None:
44 | try:
45 | hue = int(variable.get())
46 | except Exception:
47 | pass
48 | else:
49 | self._variable = tk.IntVar(self)
50 | if hue > 360:
51 | hue = 360
52 | elif hue < 0:
53 | hue = 0
54 | self._variable.set(hue)
55 | try:
56 | self._variable.trace_add("write", self._update_hue)
57 | except Exception:
58 | self._variable.trace("w", self._update_hue)
59 |
60 | self.gradient = tk.PhotoImage(master=self, width=width, height=height)
61 |
62 | self.bind('', lambda e: self._draw_gradient(hue))
63 | self.bind('', self._on_click)
64 | self.bind('', self._on_move)
65 |
66 | def _draw_gradient(self, hue):
67 | """Draw the gradient and put the cursor on hue."""
68 | self.delete("gradient")
69 | self.delete("cursor")
70 | del self.gradient
71 | width = self.winfo_width()
72 | height = self.winfo_height()
73 |
74 | self.gradient = tk.PhotoImage(master=self, width=width, height=height)
75 |
76 | line = []
77 | for i in range(width):
78 | line.append(rgb_to_hexa(*hue2col(float(i) / width * 360)))
79 | line = "{" + " ".join(line) + "}"
80 | self.gradient.put(" ".join([line for j in range(height)]))
81 | self.create_image(0, 0, anchor="nw", tags="gradient",
82 | image=self.gradient)
83 | self.lower("gradient")
84 |
85 | x = hue / 360. * width
86 | self.create_line(x, 0, x, height, width=2, tags='cursor')
87 |
88 | def _on_click(self, event):
89 | """Move selection cursor on click."""
90 | x = event.x
91 | self.coords('cursor', x, 0, x, self.winfo_height())
92 | self._variable.set(round2((360. * x) / self.winfo_width()))
93 |
94 | def _on_move(self, event):
95 | """Make selection cursor follow the cursor."""
96 | w = self.winfo_width()
97 | x = min(max(event.x, 0), w)
98 | self.coords('cursor', x, 0, x, self.winfo_height())
99 | self._variable.set(round2((360. * x) / w))
100 |
101 | def _update_hue(self, *args):
102 | hue = int(self._variable.get())
103 | if hue > 360:
104 | hue = 360
105 | elif hue < 0:
106 | hue = 0
107 | self.set(hue)
108 | self.event_generate("<>")
109 |
110 | def get(self):
111 | """Return hue of color under cursor."""
112 | coords = self.coords('cursor')
113 | return round2(360 * coords[0] / self.winfo_width())
114 |
115 | def set(self, hue):
116 | """Set cursor position on the color corresponding to the hue value."""
117 | x = hue / 360. * self.winfo_width()
118 | self.coords('cursor', x, 0, x, self.winfo_height())
119 | self._variable.set(hue)
120 |
--------------------------------------------------------------------------------
/tkcolorpicker/limitvar.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | Limited StringVar
20 | """
21 |
22 |
23 | from tkcolorpicker.functions import tk
24 |
25 |
26 | class LimitVar(tk.StringVar):
27 | def __init__(self, from_, to, master=None, value=None, name=None):
28 | tk.StringVar.__init__(self, master, value, name)
29 | try:
30 | self._from = int(from_)
31 | self._to = int(to)
32 | except ValueError:
33 | raise ValueError("from_ and to should be integers.")
34 | if self._from >= self._to:
35 | raise ValueError("from_ should be smaller than to.")
36 | # ensure that the initial value is valid
37 | val = self.get()
38 | self.set(val)
39 |
40 | def get(self):
41 | """
42 | Convert the content to int between the limits of the variable.
43 |
44 | If the content is not an integer between the limits, the value is
45 | corrected and the corrected result is returned.
46 | """
47 | val = tk.StringVar.get(self)
48 | try:
49 | val = int(val)
50 | if val < self._from:
51 | val = self._from
52 | self.set(val)
53 | elif val > self._to:
54 | val = self._to
55 | self.set(val)
56 | except ValueError:
57 | val = 0
58 | self.set(0)
59 | return val
60 |
--------------------------------------------------------------------------------
/tkcolorpicker/spinbox.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | tkcolorpicker - Alternative to colorchooser for Tkinter.
4 | Copyright 2017 Juliette Monsel
5 |
6 | tkcolorpicker is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version.
10 |
11 | tkcolorpicker is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with this program. If not, see .
18 |
19 | Nicer Spinbox than the tk.Spinbox
20 | """
21 |
22 |
23 | from tkcolorpicker.functions import tk, ttk
24 |
25 |
26 | class Spinbox(tk.Spinbox):
27 | """Spinbox closer to ttk look (designed to be used with clam)."""
28 |
29 | def __init__(self, parent, **kwargs):
30 | """
31 | Create a Spinbox.
32 |
33 | The keyword arguments are the same as for a tk.Spinbox.
34 | """
35 | self.style = ttk.Style(parent)
36 | self.frame = ttk.Frame(parent, class_="ttkSpinbox",
37 | relief=kwargs.get("relief", "sunken"),
38 | borderwidth=1)
39 | self.style.configure("%s.spinbox.TFrame" % self.frame,
40 | background=self.style.lookup("TSpinbox",
41 | "fieldbackground",
42 | default='white'))
43 | self.frame.configure(style="%s.spinbox.TFrame" % self.frame)
44 | kwargs["relief"] = "flat"
45 | kwargs["highlightthickness"] = 0
46 | kwargs["selectbackground"] = self.style.lookup("TSpinbox",
47 | "selectbackground",
48 | ("focus",))
49 | kwargs["selectforeground"] = self.style.lookup("TSpinbox",
50 | "selectforeground",
51 | ("focus",))
52 | kwargs["background"] = self.style.lookup("TSpinbox",
53 | "fieldbackground",
54 | default='white')
55 | kwargs["foreground"] = self.style.lookup("TSpinbox",
56 | "foreground")
57 | kwargs["buttonbackground"] = self.style.lookup("TSpinbox",
58 | "background")
59 | tk.Spinbox.__init__(self, self.frame, **kwargs)
60 | tk.Spinbox.pack(self, padx=1, pady=1)
61 | self.frame.spinbox = self
62 |
63 | # pack/place/grid methods
64 | self.pack = self.frame.pack
65 | self.pack_slaves = self.frame.pack_slaves
66 | self.pack_propagate = self.frame.pack_propagate
67 | self.pack_configure = self.frame.pack_configure
68 | self.pack_info = self.frame.pack_info
69 | self.pack_forget = self.frame.pack_forget
70 |
71 | self.grid = self.frame.grid
72 | self.grid_slaves = self.frame.grid_slaves
73 | self.grid_size = self.frame.grid_size
74 | self.grid_rowconfigure = self.frame.grid_rowconfigure
75 | self.grid_remove = self.frame.grid_remove
76 | self.grid_propagate = self.frame.grid_propagate
77 | self.grid_info = self.frame.grid_info
78 | self.grid_location = self.frame.grid_location
79 | self.grid_columnconfigure = self.frame.grid_columnconfigure
80 | self.grid_configure = self.frame.grid_configure
81 | self.grid_forget = self.frame.grid_forget
82 | self.grid_bbox = self.frame.grid_bbox
83 | try:
84 | self.grid_anchor = self.frame.grid_anchor
85 | except AttributeError:
86 | pass
87 |
88 | self.place = self.frame.place
89 | self.place_configure = self.frame.place_configure
90 | self.place_forget = self.frame.place_forget
91 | self.place_info = self.frame.place_info
92 | self.place_slaves = self.frame.place_slaves
93 |
94 | self.bind('<1>', lambda e: self.focus_set())
95 |
96 | self.frame.bind("", self.focusin)
97 | self.frame.bind("", self.focusout)
98 |
99 | def focusout(self, event):
100 | """Change style on focus out events."""
101 | bc = self.style.lookup("TEntry", "bordercolor", ("!focus",))
102 | dc = self.style.lookup("TEntry", "darkcolor", ("!focus",))
103 | lc = self.style.lookup("TEntry", "lightcolor", ("!focus",))
104 | self.style.configure("%s.spinbox.TFrame" % self.frame, bordercolor=bc,
105 | darkcolor=dc, lightcolor=lc)
106 |
107 | def focusin(self, event):
108 | """Change style on focus in events."""
109 | self.old_value = self.get()
110 | bc = self.style.lookup("TEntry", "bordercolor", ("focus",))
111 | dc = self.style.lookup("TEntry", "darkcolor", ("focus",))
112 | lc = self.style.lookup("TEntry", "lightcolor", ("focus",))
113 | self.style.configure("%s.spinbox.TFrame" % self.frame, bordercolor=bc,
114 | darkcolor=dc, lightcolor=lc)
115 |
--------------------------------------------------------------------------------