├── .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 | --------------------------------------------------------------------------------