├── .gitignore ├── LICENSE ├── README.md ├── customtkinter_test.py ├── main.py ├── requirements.txt ├── tests └── scale.py └── tk_to_ctk ├── __init__.py ├── app_parser.py ├── call.py ├── converter.py ├── ctkwidgets.py ├── listbox.py ├── lists.py ├── slider.py ├── templates.py ├── test.py ├── tk_to_ctk.py ├── tree.py ├── util.py └── widget_replacer.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | venv/ 3 | .flake8 4 | tests/ 5 | test.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tkinter to CustomTkinter Converter 2 | 3 | ![Python_logo_icon](https://user-images.githubusercontent.com/108424001/226063288-66da2f57-f5b7-49f1-bdd5-f465e963b125.png) 4 | 5 | # HOW TO USE 6 | For Windows 7 | ``` 8 | git clone https://github.com/Donny-GUI/tkinter-to-customtkinter-converter.git 9 | cd tkinter-to-customtkinter-converter 10 | python main.py 11 | ``` 12 | For Linux 13 | ``` 14 | git clone https://github.com/Donny-GUI/tkinter-to-customtkinter-converter.git 15 | cd tkinter-to-customtkinter-converter 16 | python3 main.py 17 | ``` 18 | # HOW TO BUILD 19 | ``` 20 | git clone https://github.com/Donny-GUI/tkinter-to-customtkinter-converter.git 21 | cd tkinter-to-customtkinter-converter 22 | pip install pyinstaller 23 | pyinstaller --onefile main.py tk2ctk 24 | cd dist 25 | .\tk2ctk 26 | ``` 27 | 28 | 29 | # tkinter-to-customtkinter-converter 30 | Convert your tkinter scripts and guis to custom tkinter with this command line tool. Can determine programming paradigm and import structure to keep consistency across files 31 | 32 | please, please, please if you experience a bug or bad operations please submit them to me so i can fix it immediatly. i dont have hundreds of guis to test this on. 33 | 34 | # New Cmdline Application 35 | 36 | ``` 37 | tk2ctk - Tkinter to CustomTkinter 38 | 39 | Description: tkinter to customtkinter file converter. 40 | 41 | Usage: tk2ctk [file] [Options] 42 | tk2ctk -m [file] [file2] [file3] ... 43 | tk2ctk [file] -o [file] 44 | 45 | Options: 46 | -h, --help Show this help message and exit 47 | -o, --output Define the output file 48 | -v, --verbose Operate with higher verbosity level 49 | -l, --listboxes Convert listboxes to custom listboxes 50 | -m, --multiple Convert multiple target scripts 51 | -e, --examples Show examples for flags and options 52 | ``` 53 | 54 | -o Flag is used to set the outfile 55 | 56 | 57 | -l flag converts tk.Listbox to a custom version 58 | 59 | 60 | -m is the multiple targets flag. Use this to target more then one tkinter script 61 | 62 | 63 | -e shows example usages 64 | 65 | 66 | ![Screenshot 2024-02-13 195702](https://github.com/Donny-GUI/tkinter-to-customtkinter-converter/assets/108424001/796ab2eb-3edb-40fe-953e-0009b3aa8e42) 67 | 68 | 69 | ## Before 70 | 71 | ![before](https://github.com/Donny-GUI/tkinter-to-customtkinter-converter/assets/108424001/900c08c2-e364-4533-bf0d-227536aae7df) 72 | 73 | 74 | ## After 75 | 76 | ![after](https://github.com/Donny-GUI/tkinter-to-customtkinter-converter/assets/108424001/4a692be9-a57e-4b8a-9061-d32eebded834) 77 | 78 | 79 | # New Source Converter Class 80 | The SourceConverter class is a standalone class that can convert source to source. 81 | That is that it can convert tkinter python source strings to customtkinter source strings. 82 | 83 | ### Usage 84 | 85 | Case #1 from a string 86 | 87 | ```Python3 88 | from .converter import SourceConverter 89 | sc = SourceConverter() 90 | with open("myfile.py", "r) as f: 91 | content = f.read() 92 | ctk_source = sc.from_string(content) 93 | ``` 94 | 95 | or 96 | 97 | ```Python3 98 | import .converter 99 | sc = converter.SourceConverter() 100 | with open("myfile.py", "r) as f: 101 | content = f.read() 102 | ctk_source = sc.from_string(content) 103 | ``` 104 | 105 | 106 | Case #2 from a file 107 | 108 | ```Python3 109 | from .converter import SourceConverter 110 | sc = SourceConverter() 111 | ctk_source = sc.from_file(content) 112 | ``` 113 | 114 | or 115 | 116 | ```Python3 117 | import .converter 118 | sc = converter.SourceConverter() 119 | ctk_source = sc.from_file(content) 120 | ``` 121 | 122 | 123 | # UPDATE Feb 24 2025 124 | - added call.py file 125 | - added CallArgumentNameChanger class 126 | - added CallNameChanger class 127 | - added CallArgumentRemover class 128 | - now correctly changes "borderwidth" -> "border_width" 129 | - converts or removes unused or incorrect parameters for any ctk or tk class. 130 | - thank you to those who helped find these bugs 131 | 132 | # Update April 18 2024 133 | - Added converter.py file 134 | - Added SourceConverter class 135 | 136 | 137 | # Update March 23 2024 138 | - Now fixes textvariable -> variable function/class instance parameter names. 139 | - Now fixes orient -> orientation function/class instance parameter names. 140 | - tree.py file added to use the ast module to find function and class instances that use the parameters 141 | 142 | 143 | # Update Feb 13 2024 144 | - New command line interface 145 | - now supports listboxes 146 | 147 | 148 | # Update Feb 9 2024 149 | - fixed base and meta class tkinter widgets to represent ctk ones 150 | - fixed Checkbutton to CheckBox 151 | - fixed Radiobutton to RadioButton 152 | - No longer falsly import tk.Text 153 | - uses CTkSlider for tk.Scale 154 | 155 | # Updates April 14 2023 156 | 157 | - Now supports ttk by default 158 | - General syntax improvements in source code 159 | - General performance improvements 160 | - now converts all widget background_color to bg_color as specified in the property exception in customtkinter 161 | - converts all widget foreground_color to fg_color as specified in the the property exception in customtkinter 162 | 163 | 164 | # Coming Up Next 165 | 166 | - tkinter.ListBox conversion to customtkinter class ScrollableFrame or "ScrollableFrameBox/ScrollableCheckBox/ScrollableSwitchBox/ScrollableLabelBox" 167 | - possible CTkMessageBox addition from Akascape 168 | 169 | # Thank you! 170 | 171 | Thank you Tom Schimansky for this wonderful and beautiful tkinter addition! 172 | I am not associated with customtkinter in any way. 173 | 174 | Please see https://github.com/TomSchimansky/CustomTkinter 175 | 176 | 177 | # Getting Started 178 | 179 | ### Windows Users 180 | 181 | ```Powershell 182 | git clone https://github.com/Donny-GUI/tkinter-to-customtkinter-converter.git 183 | cd tkinter-to-customtkinter-converter 184 | python tk_to_ctk.py 185 | ``` 186 | 187 | ### Unix/Linux Users 188 | 189 | 190 | ```Bash 191 | git clone https://github.com/Donny-GUI/tkinter-to-customtkinter-converter.git 192 | cd tkinter-to-customtkinter-converter 193 | python3 tk_to_ctk.py 194 | ``` 195 | 196 | 197 | # Files 198 | 199 | ## tkinter-to-customtkinter.util 200 | Contains utility functions and constants 201 | ```Python3 202 | get_operating_system() 203 | # get the name of the operating system 204 | pip_str 205 | # Literal string for using pip aka pip or pip3 206 | ``` 207 | 208 | ## tkinter-to-customtkinter.widget_replacer 209 | Contains the WidgetReplacer class 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from tk_to_ctk import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | markdown-it-py==3.0.0 2 | mdurl==0.1.2 3 | Pygments==2.19.1 4 | rich==13.9.4 5 | -------------------------------------------------------------------------------- /tests/scale.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import customtkinter as ctk 3 | 4 | 5 | root = tk.Tk() 6 | scale = tk.Scale(root, from_=0, to=1, resolution=0.01, orient=tk.HORIZONTAL) 7 | cscale = ctk.CTkSlider(root, from_=0, to=1, resolution=0.01, orientation=tk.HORIZONTAL) 8 | root.mainloop() 9 | 10 | -------------------------------------------------------------------------------- /tk_to_ctk/__init__.py: -------------------------------------------------------------------------------- 1 | from .tk_to_ctk import main 2 | 3 | __all__ = ["main"] -------------------------------------------------------------------------------- /tk_to_ctk/app_parser.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | 4 | def get_parser(): 5 | 6 | argument_parser = argparse.ArgumentParser( 7 | description="Convert Tkinter scripts to Customtkinter scripts", 8 | prog="tk2ctk", 9 | usage="tk2ctk [Target] [-h] [-o Output] [-l] [-v] [-m Target Target Target Target ... ]", 10 | add_help=False, 11 | exit_on_error=False, 12 | ) 13 | argument_parser.add_argument( 14 | "Target", 15 | default=None, 16 | help="Input file path for Tkinter script", 17 | metavar="TARGET", 18 | nargs="?", 19 | ) 20 | argument_parser.add_argument( 21 | "-h", 22 | "--help", 23 | default=False, 24 | dest="Help", 25 | action="store_true", 26 | help="Show this help message and exit", 27 | ) 28 | argument_parser.add_argument( 29 | "-o", 30 | "--outfile", 31 | dest="Output", 32 | default=None, 33 | nargs="?", 34 | metavar="OUTFILE", 35 | const="", 36 | help="Specify output file", 37 | ) 38 | argument_parser.add_argument( 39 | "-v", 40 | "--verbose", 41 | dest="Verbose", 42 | default=False, 43 | action="store_true", 44 | help="Operate with verbosity", 45 | ) 46 | argument_parser.add_argument( 47 | "-l", 48 | "--listboxes", 49 | default=False, 50 | dest="Listboxes", 51 | action="store_true", 52 | help="Convert listboxes to custom listboxes", 53 | ) 54 | argument_parser.add_argument( 55 | "-m", 56 | "--multiple", 57 | dest="Multiple", 58 | default=[], 59 | nargs="+", 60 | help="Specify multiple targets", 61 | ) 62 | argument_parser.add_argument( 63 | "-e", 64 | "--examples", 65 | dest="Examples", 66 | default=False, 67 | action="store_true", 68 | help="Show examples for additional help", 69 | ) 70 | return argument_parser 71 | -------------------------------------------------------------------------------- /tk_to_ctk/call.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | 4 | 5 | class CallTransformer(ast.NodeTransformer): 6 | def __init__(self): 7 | super().__init__() 8 | self._inside_call = False 9 | self._call_name = None 10 | self.name_changes = {} 11 | self.parameter_removals = {} 12 | self.argument_value_changes = {} 13 | self._stack = [] 14 | 15 | def add_name_change(self, original: str, replacement: str) -> None: 16 | self.name_changes[original] = replacement 17 | 18 | def add_argument_name_change(self, call_name: str, argument_name: str, replacement: str) -> None: 19 | if call_name not in self.name_changes: 20 | self.name_changes[call_name] = {argument_name: replacement} 21 | else: 22 | if not isinstance(self.name_changes[call_name], dict): 23 | # Convert to dict if it's a direct replacement 24 | self.name_changes[call_name] = {argument_name: replacement} 25 | else: 26 | self.name_changes[call_name][argument_name] = replacement 27 | 28 | def add_keyword_removal(self, call_name: str, argument_name: str) -> None: 29 | if call_name not in self.parameter_removals: 30 | self.parameter_removals[call_name] = [argument_name] 31 | else: 32 | self.parameter_removals[call_name].append(argument_name) 33 | 34 | def add_argument_value_change(self, call_name: str, argument_name: str, replacement) -> None: 35 | if call_name not in self.argument_value_changes: 36 | self.argument_value_changes[call_name] = {argument_name: replacement} 37 | else: 38 | self.argument_value_changes[call_name][argument_name] = replacement 39 | 40 | def visit_Call(self, node: ast.Call) -> ast.Call: 41 | call_name = ast.unparse(node.func) 42 | 43 | # Push this call onto the stack 44 | self._stack.append(call_name) 45 | self._inside_call = True 46 | self._call_name = call_name 47 | 48 | # Continue with normal visit 49 | node = self.generic_visit(node) 50 | 51 | # Pop from stack when done with this call 52 | self._stack.pop() 53 | 54 | # Update state based on stack 55 | if self._stack: 56 | self._call_name = self._stack[-1] 57 | else: 58 | self._inside_call = False 59 | self._call_name = None 60 | 61 | return node 62 | 63 | 64 | class CallNameChanger(CallTransformer): 65 | def visit_Call(self, node: ast.Call) -> ast.Call: 66 | # First execute parent's visit_Call to maintain proper stack 67 | node = super().visit_Call(node) 68 | 69 | # Then handle function name changes 70 | call_name = ast.unparse(node.func) 71 | if call_name in self.name_changes and isinstance(self.name_changes[call_name], str): 72 | # Only change if it's a direct name replacement (not arg changes) 73 | if isinstance(node.func, ast.Name): 74 | node.func.id = self.name_changes[call_name] 75 | # Handle attribute calls (e.g., obj.method()) 76 | elif isinstance(node.func, ast.Attribute): 77 | node.func.attr = self.name_changes[call_name] 78 | 79 | return node 80 | 81 | 82 | class CallArgumentRemover(ast.NodeTransformer): 83 | def __init__(self): 84 | super().__init__() 85 | self.parameter_removals = {} 86 | 87 | def add_keyword_removal(self, call_name:str, argument_name: str) -> None: 88 | if self.parameter_removals.get(call_name, None) is None: 89 | self.parameter_removals[call_name] = [argument_name] 90 | else: 91 | self.parameter_removals[call_name].append(argument_name) 92 | 93 | def visit_Call(self, node: ast.Call) -> ast.Call: 94 | # First, recursively visit child nodes 95 | node = self.generic_visit(node) 96 | 97 | call_name = ast.unparse(node.func) 98 | if call_name in self.parameter_removals: 99 | # Filter out keywords that should be removed 100 | retained_keywords = [] 101 | for kw in node.keywords: 102 | arg_name = kw.arg if isinstance(kw.arg, str) else ast.unparse(kw.arg) 103 | if arg_name not in self.parameter_removals[call_name]: 104 | retained_keywords.append(kw) 105 | 106 | # Update the node's keywords 107 | node.keywords = retained_keywords 108 | 109 | return node 110 | class CallArgumentNameChanger(CallTransformer): 111 | def visit_Call(self, node: ast.Call) -> ast.Call: 112 | # First execute parent's visit_Call to maintain proper stack 113 | node = super().visit_Call(node) 114 | 115 | # Then handle argument name changes 116 | call_name = ast.unparse(node.func) 117 | if call_name in self.name_changes and isinstance(self.name_changes[call_name], dict): 118 | modified_keywords = [] 119 | for kw in node.keywords: 120 | arg_name = kw.arg if isinstance(kw.arg, str) else ast.unparse(kw.arg) 121 | if arg_name in self.name_changes[call_name]: 122 | # Create a new keyword node with the changed name 123 | modified_keywords.append( 124 | ast.keyword(self.name_changes[call_name][arg_name], kw.value) 125 | ) 126 | else: 127 | modified_keywords.append(kw) 128 | node.keywords = modified_keywords 129 | 130 | return node 131 | 132 | 133 | class CallArgumentValueChanger(CallTransformer): 134 | def visit_Call(self, node: ast.Call) -> ast.Call: 135 | # First execute parent's visit_Call to maintain proper stack 136 | node = super().visit_Call(node) 137 | 138 | # Then handle argument value changes 139 | call_name = ast.unparse(node.func) 140 | if call_name in self.argument_value_changes: 141 | modified_keywords = [] 142 | for kw in node.keywords: 143 | arg_name = kw.arg if isinstance(kw.arg, str) else ast.unparse(kw.arg) 144 | if arg_name in self.argument_value_changes[call_name]: 145 | # Create a new AST node for the replacement value 146 | replacement_value = self.argument_value_changes[call_name][arg_name] 147 | # Handle if the replacement is already an AST node 148 | if not isinstance(replacement_value, ast.AST): 149 | replacement_value = ast.parse(str(replacement_value)).body[0].value 150 | 151 | # Create a new keyword with the same name but different value 152 | modified_keywords.append( 153 | ast.keyword(kw.arg, replacement_value) 154 | ) 155 | else: 156 | modified_keywords.append(kw) 157 | node.keywords = modified_keywords 158 | 159 | return node 160 | # 161 | #class CallArgumentNameChanger(ast.NodeTransformer): 162 | # def __init__(self, changes:dict={}): 163 | # self.changes = changes 164 | # if not isinstance(self.changes, dict): 165 | # raise TypeError("changes must be a dictionary") 166 | # 167 | # def add_change(self, call_name:str, argument_name: str, replacement: str) -> None: 168 | # """ 169 | # Add a new change to the dictionary of changes. 170 | # 171 | # :param call_name: The name of the function call to modify. 172 | # :param argument_name: The name of the argument to modify in the function call. 173 | # :param replacement: The new name for the argument. 174 | # """ 175 | # 176 | # # Check if the call name already exists in the changes dictionary 177 | # if self.changes.get(call_name, None) is None: 178 | # # If not, create a new dictionary with the argument name and replacement 179 | # self.changes[call_name] = {argument_name: replacement} 180 | # else: 181 | # # If the call name already exists, add the new argument name and replacement to the dictionary 182 | # self.changes[call_name][argument_name] = replacement 183 | # 184 | # def visit_Call(self, node: ast.Call) -> ast.Call: 185 | # """ 186 | # Modify the keyword arguments of a function call based on the changes dictionary. 187 | # 188 | # :param node: The AST node representing the function call. 189 | # :return: The modified AST node. 190 | # """ 191 | # node = self.generic_visit(node) 192 | # string = ast.unparse(node.func) 193 | # print(string) 194 | # 195 | # # Check if the function name is in the changes dictionary 196 | # if string in self.changes: 197 | # # Iterate over each keyword argument in the function call 198 | # for keyword in node.keywords: 199 | # # Check if the keyword argument name needs to be changed 200 | # kw = ast.unparse(keyword.arg) 201 | # if kw in self.changes[string]: 202 | # setattr(keyword, 'arg', self.changes[string][kw]) 203 | # keyword.arg = self.changes[string][kw] 204 | # 205 | # 206 | # # Return the modified node 207 | # return node 208 | # 209 | # 210 | #class CallArgumentRemover(ast.NodeTransformer): 211 | # def __init__(self, changes:dict={}): 212 | # self.changes = changes 213 | # if not isinstance(self.changes, dict): 214 | # raise TypeError("changes must be a dictionary") 215 | # self._inside_call = False 216 | # self._call_name = None 217 | # 218 | # def add_change(self, call_name:str, argument_name: str) -> None: 219 | # """ 220 | # Add a new change to the dictionary of changes. 221 | # 222 | # :param call_name: The name of the function call to modify. 223 | # :param argument_name: The name of the argument to remove in the function call. 224 | # """ 225 | # 226 | # # Check if the call name already exists in the changes dictionary 227 | # if self.changes.get(call_name, None) is None: 228 | # # If not, create a new dictionary with the argument name and replacement 229 | # self.changes[call_name] = [argument_name] 230 | # else: 231 | # # If the call name already exists, add the new argument name and replacement to the dictionary 232 | # self.changes[call_name].append(argument_name) 233 | # 234 | # def visit_keyword(self, node: ast.keyword) -> ast.keyword: 235 | # if self._inside_call: 236 | # if self.changes.get(self._call_name, None) != None and node.arg in self.changes[self._call_name]: 237 | # print(f"[Removing argument]: {node.arg}") 238 | # return 239 | # 240 | # return node 241 | # 242 | # def visit_Call(self, node: ast.Call) -> ast.Call: 243 | # """ 244 | # Modify the keyword arguments of a function call based on the changes dictionary. 245 | # 246 | # :param node: The AST node representing the function call. 247 | # :return: The modified AST node. 248 | # """ 249 | # self._inside_call = True 250 | # self._call_name = ast.unparse(node.func) 251 | # 252 | # self.generic_visit(node) 253 | # 254 | # function_name = ast.unparse(node.func) 255 | # # Print for debugging 256 | # print(f"Function: {function_name}, Keywords: {[kw.arg for kw in node.keywords]}") 257 | # # Check if the function name is in the changes dictionary 258 | # if function_name in self.changes: 259 | # removing_args = self.changes[function_name] 260 | # 261 | # # Create a new list excluding the keywords we want to remove 262 | # retained_keywords = [] 263 | # for keyword in node.keywords: 264 | # if keyword.arg not in removing_args: 265 | # retained_keywords.append(keyword) 266 | # else: 267 | # print(f"Removing argument: {keyword.arg}") 268 | # 269 | # # Replace the keywords with our filtered list 270 | # node.keywords = retained_keywords 271 | # 272 | # self._inside_call = False 273 | # return node -------------------------------------------------------------------------------- /tk_to_ctk/converter.py: -------------------------------------------------------------------------------- 1 | import re 2 | import ast 3 | from copy import deepcopy 4 | 5 | 6 | tkinter_widgets = [ "Button", "Canvas", "Checkbutton", 7 | "Entry", "Label", "Menubutton", 8 | "Message", "Radiobutton", "Scale", 9 | "Scrollbar", "Text", "Toplevel", 10 | "Treeview", "Frame", "Progressbar", 11 | "Separator"] 12 | ctk_widgets = ["CTk" + x for x in tkinter_widgets] 13 | 14 | 15 | 16 | def change_textvariable_to_variable(source_code: str) -> str: 17 | """Change occurrences of the parameter 'textvariable' to 'variable' in function calls and class instantiations. 18 | 19 | Args: 20 | source_code (str): The source code to modify. 21 | 22 | Returns: 23 | str: The modified source code with 'textvariable' replaced by 'variable'. 24 | """ 25 | 26 | class TextVariableVisitor(ast.NodeTransformer): 27 | """AST visitor to modify occurrences of 'textvariable' to 'variable'.""" 28 | 29 | def visit_Call(self, node: ast.Call) -> ast.Call: 30 | """Visit function calls and replace 'textvariable' keyword argument with 'variable'.""" 31 | if isinstance(node.func, ast.Name): 32 | if node.keywords: 33 | for keyword in node.keywords: 34 | if ( 35 | isinstance(keyword, ast.keyword) 36 | and keyword.arg == "textvariable" 37 | ): 38 | keyword.arg = "variable" 39 | return node 40 | 41 | def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: 42 | """Visit class definitions and replace 'textvariable' keyword argument with 'variable'.""" 43 | for child in node.body: 44 | if isinstance(child, ast.Expr) and isinstance(child.value, ast.Call): 45 | call = child.value 46 | if isinstance(call.func, ast.Name): 47 | if call.keywords: 48 | for keyword in call.keywords: 49 | if ( 50 | isinstance(keyword, ast.keyword) 51 | and keyword.arg == "textvariable" 52 | ): 53 | keyword.arg = "variable" 54 | return node 55 | 56 | # Parse the source code into an AST 57 | tree = parsetree(source_code) 58 | 59 | # Transform the AST with the TextVariableVisitor 60 | transformer = TextVariableVisitor() 61 | transformed_tree = transformer.visit(tree) 62 | 63 | # Convert the AST back to source code 64 | modified_source_code = ast.unparse(transformed_tree) 65 | 66 | return modified_source_code 67 | 68 | def change_orient_to_orientation(source_code: str) -> str: 69 | """ 70 | Change occurrences of the parameter 'orient' to 'orientation' 71 | in function calls and class instantiations. 72 | Args: 73 | source_code (str): The source code to modify. 74 | Returns: 75 | str: The modified source code with 'orient' replaced by 'orientation'. 76 | """ 77 | 78 | class OrientVisitor(ast.NodeTransformer): 79 | """AST visitor to modify occurrences of 'orient' to 'orientation'.""" 80 | 81 | def visit_Call(self, node: ast.Call) -> ast.Call: 82 | """Visit function calls and replace 'orient' keyword argument with 'orientation'.""" 83 | if isinstance(node.func, ast.Name): 84 | if node.keywords: 85 | for keyword in node.keywords: 86 | if isinstance(keyword, ast.keyword) and keyword.arg == "orient": 87 | keyword.arg = "orientation" 88 | return node 89 | 90 | def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: 91 | """Visit class definitions and replace 'orient' keyword argument with 'orientation'.""" 92 | for child in node.body: 93 | if isinstance(child, ast.Expr) and isinstance(child.value, ast.Call): 94 | call = child.value 95 | if isinstance(call.func, ast.Name): 96 | if call.keywords: 97 | for keyword in call.keywords: 98 | if ( 99 | isinstance(keyword, ast.keyword) 100 | and keyword.arg == "orient" 101 | ): 102 | keyword.arg = "orientation" 103 | return node 104 | 105 | # Parse the source code into an AST 106 | tree = ast.parse(source_code) 107 | 108 | # Transform the AST with the OrientVisitor 109 | transformer = OrientVisitor() 110 | transformed_tree = transformer.visit(tree) 111 | 112 | # Convert the AST back to source code 113 | modified_source_code = ast.unparse(transformed_tree) 114 | 115 | return modified_source_code 116 | 117 | def change_frame_borderwidth_to_border_width(source_code: str) -> str: 118 | """ 119 | Change occurrences of the parameter 'borderwidth' to 'border_width' 120 | in function calls and class instantiations. 121 | Args: 122 | source_code (str): The source code to modify. 123 | Returns: 124 | str: The modified source code with 'borderwidth' replaced by 'border_width'. 125 | """ 126 | 127 | class FrameVisitor(ast.NodeTransformer): 128 | """AST visitor to modify occurrences of 'borderwidth' to 'border_width'.""" 129 | 130 | def visit_Call(self, node: ast.Call) -> ast.Call: 131 | """Visit function calls and replace 'borderwidth' keyword argument with 'border_width'.""" 132 | if isinstance(node.func, ast.Name): 133 | if node.keywords: 134 | for keyword in node.keywords: 135 | if ( 136 | isinstance(keyword, ast.keyword) 137 | and keyword.arg == "borderwidth" 138 | ): 139 | keyword.arg = "border_width" 140 | return node 141 | 142 | tree = parsetree(source_code) 143 | 144 | # Transform the AST with the FrameVisitor 145 | transformer = FrameVisitor() 146 | transformed_tree = transformer.visit(tree) 147 | 148 | # Convert the AST back to source code 149 | modified_source_code = ast.unparse(transformed_tree) 150 | 151 | return modified_source_code 152 | 153 | def remove_relief_from_CTkFrame(content: str) -> str: 154 | 155 | tree = parsetree(content) 156 | for node in ast.walk(tree): 157 | if isinstance(node, ast.Call): 158 | string_name = ast.unparse(node.func) 159 | if string_name == "ctk.CTkFrame" or string_name == "CTkFrame": 160 | remove_parameter_from_call(node, "relief") 161 | 162 | return ast.unparse(tree) 163 | 164 | def remove_resolution_from_ctkslider(content: str) -> str: 165 | """ 166 | Remove the 'resolution' parameter from calls to ctk.CTkSlider or CTkSlider 167 | in the provided Python code. 168 | 169 | Args: 170 | content (str): The Python code content. 171 | 172 | Returns: 173 | str: The modified Python code without the 'resolution' parameter. 174 | """ 175 | tree = parsetree(content) 176 | slidernodes = [] 177 | for node in ast.walk(tree): 178 | if isinstance(node, ast.Call): 179 | string_name = ast.unparse(node.func) 180 | if string_name == "ctk.CTkSlider" or string_name == "CTkSlider": 181 | n = deepcopy(node) 182 | slidernodes.append(node) 183 | for node in slidernodes: 184 | remove_parameter_from_call(node, "resolution") 185 | return ast.unparse(tree) 186 | 187 | def remove_parameter_from_call(node: ast.Call, parameter: str) -> None: 188 | """ 189 | Remove the specified parameter from the given AST call node. 190 | 191 | Args: 192 | node (ast.Call): The AST call node. 193 | parameter (str): The name of the parameter to remove. 194 | 195 | Returns: 196 | None 197 | """ 198 | node.keywords = [kw for kw in node.keywords if kw.arg != parameter] 199 | 200 | def parsetree(source: str) -> ast.AST: 201 | try: 202 | tree = ast.parse(source, type_comments=True) 203 | except: 204 | tree = ast.parse(source) 205 | finally: 206 | return tree 207 | 208 | 209 | 210 | class WidgetReplacer: 211 | """ 212 | A class to replace widgets in a script and add custom constants. 213 | 214 | Attributes: 215 | source (str): The path to the source script file. 216 | output (str): The path to the output script file. 217 | findables (dict): A dictionary containing findable patterns and their replacements. 218 | constants (list): A list of existing tkinter constants found in the script. 219 | used_constants (list): A list of constants used in the script. 220 | """ 221 | 222 | tkinter_constants = [ 223 | "ACTIVE", 224 | "ALL", 225 | "ANCHOR", 226 | "ARC", 227 | "BASELINE", 228 | "BEVEL", 229 | "BOTH", 230 | "BOTTOM", 231 | "BROWSE", 232 | "BUTT", 233 | "CASCADE", 234 | "CENTER", 235 | "CHAR", 236 | "CHECKBUTTON", 237 | "CHORD", 238 | "COMMAND", 239 | "DISABLED", 240 | "E", 241 | "END", 242 | "EW", 243 | "EXCEPTION", 244 | "EXTENDED", 245 | "FALSE", 246 | "FIRST", 247 | "FLAT", 248 | "GROOVE", 249 | "HIDDEN", 250 | "HORIZONTAL", 251 | "INSERT", 252 | "INSIDE", 253 | "LAST", 254 | "LEFT", 255 | "MITER", 256 | "MULTIPLE", 257 | "N", 258 | "NE", 259 | "NO", 260 | "NONE", 261 | "NORMAL", 262 | "NS", 263 | "NSEW", 264 | "NW", 265 | "OFF", 266 | "ON", 267 | "OUTSIDE", 268 | "PAGES", 269 | "PIESLICE", 270 | "PROJECTING", 271 | "RADIOBUTTON", 272 | "RAISED", 273 | "READABLE", 274 | "RIDGE", 275 | "RIGHT", 276 | "ROUND", 277 | "S", 278 | "SCROLL", 279 | "SE", 280 | "SEL", 281 | "SEL_FIRST", 282 | "SEL_LAST", 283 | "SEPARATOR", 284 | "SINGLE", 285 | "SOLID", 286 | "SUNKEN", 287 | "SW", 288 | "Synchronous", 289 | "SystemButton", 290 | "TOP", 291 | "TRUE", 292 | "UNITS", 293 | "VERTICAL", 294 | "W", 295 | "WORD", 296 | "WRITABLE", 297 | "X", 298 | "Y", 299 | ] 300 | 301 | def __init__(self, source: str, output: str) -> None: 302 | """ 303 | Initialize the WidgetReplacer instance. 304 | 305 | Args: 306 | source (str): The path to the source script file. 307 | output (str): The path to the output script file. 308 | """ 309 | self.source: str = source 310 | self.output: str = output 311 | self.findables: dict[str, str] = {} 312 | self.constants: list[str] = [] 313 | self.used_constants: list[str] = [] 314 | 315 | def set_source(self, source: str): 316 | self.source = source 317 | 318 | def set_output(self, output: str): 319 | self.output = output 320 | 321 | def add_findable(self, original: str, replacement: str) -> None: 322 | """ 323 | Add a findable pattern and its replacement. 324 | 325 | Args: 326 | original (str): The original pattern to find. 327 | replacement (str): The replacement pattern. 328 | """ 329 | self.findables[re.escape(original)] = replacement 330 | 331 | def replace_widgets(self): 332 | """ 333 | Replace widgets in the script using findable patterns and replacements. 334 | """ 335 | with open(self.source, "r", encoding="utf-8", errors="ignore") as f: 336 | script_content = f.read() 337 | out = "" 338 | for onst in self.tkinter_constants: 339 | if re.search(onst, script_content): 340 | self.constants.append(onst) 341 | 342 | for original, replacement in self.findables.items(): 343 | pattern = r"\b{}\b".format(original) 344 | script_content = re.sub(pattern, replacement, script_content) 345 | 346 | with open(self.output, "w", errors="ignore") as f: 347 | f.write(script_content) 348 | 349 | def double_check(self): 350 | """ 351 | Double-check and add custom tkinter constants to the script. 352 | """ 353 | with open(self.output, "r") as f: 354 | script_content = f.readlines() 355 | 356 | out = "import customtkinter as ctk\nfrom customtkinter import " 357 | m = len(self.constants) - 1 358 | for index, constant in enumerate(self.constants): 359 | if index == m: 360 | out += f"{constant}" 361 | else: 362 | out += f"{constant}, " 363 | out += "\n" 364 | 365 | with open(self.output, "w") as f: 366 | f.write(out) 367 | for line in script_content: 368 | f.write(line) 369 | 370 | def add_constant(self, constant): 371 | """ 372 | Add a custom constant to the list of constants. 373 | 374 | Args: 375 | constant (str): The constant to add. 376 | """ 377 | self.constants.append(constant) 378 | 379 | 380 | 381 | class SourceConverter: 382 | 383 | def __init__(self) -> None: 384 | 385 | self.replacer = WidgetReplacer("None", "None") 386 | for widget in tkinter_widgets: 387 | ctk_widget = f"ctk.CTk{widget}" 388 | self.replacer.add_findable(f" {widget}(", f" {ctk_widget}(") 389 | self.replacer.add_findable(f" {widget}(", f" {ctk_widget}(") 390 | self.replacer.add_findable(f"{widget}(", f"{ctk_widget}(") 391 | self.replacer.add_findable(f"{widget}, ", "") 392 | self.replacer.add_findable(f"{widget},", f"{ctk_widget},") 393 | self.replacer.add_findable(f" = {widget}(", f" = {ctk_widget}(") 394 | self.replacer.add_findable(f"={widget}(", f"={ctk_widget}(") 395 | self.replacer.add_findable(f": {widget} ", f": {ctk_widget} ") 396 | self.replacer.add_findable(f":{widget},", f":{ctk_widget},") 397 | self.replacer.add_findable(f":{widget}", f":{ctk_widget}") 398 | for widg in tkinter_widgets: 399 | widget = "tk." + widg 400 | ctk_widget = f"ctk.CTk{widg}" 401 | self.replacer.add_findable(f"{widget}(", f"{ctk_widget}(") 402 | self.replacer.add_findable(f" {widget}(", f" {ctk_widget}(") 403 | self.replacer.add_findable(f" {widget}(", f" {ctk_widget}(") 404 | self.replacer.add_findable(f"{widget}, ", f"") 405 | self.replacer.add_findable(f"{widget},", f"{ctk_widget},") 406 | self.replacer.add_findable(f" = {widget}(", f" = {ctk_widget}(") 407 | self.replacer.add_findable(f"={widget}(", f"={ctk_widget}(") 408 | self.replacer.add_findable(f": {widget} ", f": {ctk_widget} ") 409 | self.replacer.add_findable(f":{widget},", f":{ctk_widget},") 410 | self.replacer.add_findable(f":{widget}", f":{ctk_widget}") 411 | for widg in tkinter_widgets: 412 | widget: str = "ttk." + widg 413 | ctk_widget: str = f"ctk.CTk{widg}" 414 | self.replacer.add_findable("{0}(".format(widget), "{0}(".format(ctk_widget)) 415 | self.replacer.add_findable("{0}, ".format(widget), "") 416 | self.replacer.add_findable(widget + ",", "{0},".format(ctk_widget)) 417 | self.replacer.add_findable(" = {0}(".format(widget), " = {0}(".format(ctk_widget)) 418 | self.replacer.add_findable("={0}(".format(widget), "={0}(".format(ctk_widget)) 419 | self.replacer.add_findable(": {0} ".format(widget), ": {0} ".format(ctk_widget)) 420 | self.replacer.add_findable(":{0},".format(widget), ":{0},".format(ctk_widget)) 421 | self.replacer.add_findable(":{0}".format(widget), ":{0}".format(ctk_widget)) 422 | 423 | def from_string(self, string: str) -> str: 424 | script_content = string 425 | 426 | self.replacer.constants = [] 427 | for onst in self.replacer.tkinter_constants: 428 | if re.search(onst, script_content): 429 | self.replacer.constants.append(onst) 430 | 431 | for original, replacement in self.replacer.findables.items(): 432 | pattern = r"\b{}\b".format(original) 433 | script_content = re.sub(pattern, replacement, script_content) 434 | 435 | out = "import customtkinter as ctk\nfrom customtkinter import " 436 | m = len(self.replacer.constants) - 1 437 | if m == -1: 438 | out = "import customtkinter as ctk\n" 439 | 440 | for index, constant in enumerate(self.replacer.constants): 441 | if index == m: 442 | out += f"{constant}" 443 | else: 444 | out += f"{constant}, " 445 | 446 | out += "\n\n" 447 | 448 | script_content = out + script_content 449 | cont1: str = re.sub(r"\.config\(", ".configure(", script_content) 450 | cont2 = cont1.replace(", bg=", ", bg_color=").replace(", bg =", ", bg_color =") 451 | cont3 = cont2.replace(", fg=", ", fg_color=").replace(", fg =", ", fg_color =") 452 | cont4 = cont3.replace(r"(tk.Tk):", r"(ctk.CTk):") 453 | for index, widget in enumerate(tkinter_widgets): 454 | cont4 = cont4.replace(f"(tk.{widget}):", f"(ctk.{ctk_widgets[index]}):") 455 | cont4 = cont4.replace("(Tk):", "(ctk.CTk):") 456 | cont5 = cont4.replace("Tk()", "ctk.CTk()") 457 | 458 | lines = cont5.splitlines() 459 | new_lines = [] 460 | for line in lines: 461 | l = re.sub(r"ttk.ctk.", r"ctk.", line) 462 | l2 = re.sub(r"tk.ctk.", r"ctk.", l) 463 | l3 = re.sub(r".CTkText", r".CTkTextbox", l2) 464 | l4 = re.sub(r".CTkRadiobutton", r".CTkRadioButton", l3) 465 | # CTkCheckButton 466 | l5 = re.sub(r".CTkCheckbutton", r".CTkCheckBox", l4) 467 | l6 = re.sub(r".CTkScale", r".CTkSlider", l5) 468 | l7 = l6 469 | new_lines.append(l7) 470 | 471 | source = "\n".join(new_lines) 472 | try: 473 | source = change_textvariable_to_variable(source) 474 | except: 475 | source = source 476 | try: 477 | source = change_orient_to_orientation(source) 478 | except: 479 | source = source 480 | try: 481 | source = remove_resolution_from_ctkslider(source) 482 | except: 483 | source = source 484 | try: 485 | source = change_frame_borderwidth_to_border_width(source) 486 | except: 487 | source = source 488 | try: 489 | source = remove_relief_from_CTkFrame(source) 490 | except: 491 | source = source 492 | return source 493 | 494 | def from_file(self, filepath: str) -> str: 495 | with open(filepath, 'r') as f: 496 | content = f.read() 497 | return self.from_string(content) 498 | 499 | 500 | -------------------------------------------------------------------------------- /tk_to_ctk/ctkwidgets.py: -------------------------------------------------------------------------------- 1 | 2 | from multiprocessing.spawn import import_main_path 3 | from optparse import NO_DEFAULT 4 | from tkinter import NO 5 | from dataclasses import dataclass 6 | from enum import Enum, StrEnum 7 | 8 | 9 | CTkButton = 'CTkButton' 10 | CTk = 'CTk' 11 | CTkInputDialog = 'CTkInputDialog' 12 | CTkToplevel = 'CTkToplevel' 13 | CTkCheckBox = 'CTkCheckBox' 14 | CTkComboBox = 'CTkComboBox' 15 | CTkEntry = 'CTkEntry' 16 | CTkFrame = 'CTkFrame' 17 | CTkLabel = 'CTkLabel' 18 | CTkOptionMenu = 'CTkOptionMenu' 19 | CTkProgressBar = 'CTkProgressBar' 20 | CTkRadioButton = 'CTkRadioButton' 21 | CTkScrollableFrame = 'CTkScrollableFrame' 22 | CTkScrollbar = 'CTkScrollbar' 23 | CTkSegmentedButton = 'CTkSegmentedButton' 24 | CTkSlider = 'CTkSlider' 25 | CTkSwitch = 'CTkSwitch' 26 | CTkTabview = 'CTkTabview' 27 | CTkFrames = 'CTkFrames' 28 | CTkTextbox = 'CTkTextbox' 29 | CTkFont = 'CTkFont' 30 | CTkImage = 'CTkImage' 31 | 32 | ctk_widget_names = [ 33 | "CTk", 34 | "CTkInputDialog", 35 | "CTkToplevel", 36 | "CTkButton", 37 | "CTkCheckBox", 38 | "CTkComboBox", 39 | "CTkEntry", 40 | "CTkFrame", 41 | "CTkLabel", 42 | "CTkOptionMenu", 43 | "CTkProgressBar", 44 | "CTkRadioButton", 45 | "CTkScrollableFrame", 46 | "CTkScrollbar", 47 | "CTkSegmentedButton", 48 | "CTkSlider", 49 | "CTkSwitch", 50 | "CTkTabview", 51 | "CTkFrames", 52 | "CTkTextbox", 53 | ] 54 | 55 | 56 | class ctk: 57 | CTkButton = 'ctk.CTkButton' 58 | CTk = 'ctk.CTk' 59 | CTkInputDialog = 'ctk.CTkInputDialog' 60 | CTkToplevel = 'ctk.CTkToplevel' 61 | CTkCheckBox = 'ctk.CTkCheckBox' 62 | CTkComboBox = 'ctk.CTkComboBox' 63 | CTkEntry = 'ctk.CTkEntry' 64 | CTkFrame = 'ctk.CTkFrame' 65 | CTkLabel = 'ctk.CTkLabel' 66 | CTkOptionMenu = 'ctk.CTkOptionMenu' 67 | CTkProgressBar = 'ctk.CTkProgressBar' 68 | CTkRadioButton = 'ctk.CTkRadioButton' 69 | CTkScrollableFrame = 'ctk.CTkScrollableFrame' 70 | CTkScrollbar = 'ctk.CTkScrollbar' 71 | CTkSegmentedButton = 'ctk.CTkSegmentedButton' 72 | CTkSlider = 'ctk.CTkSlider' 73 | CTkSwitch = 'ctk.CTkSwitch' 74 | CTkTabview = 'ctk.CTkTabview' 75 | CTkFrames = 'ctk.CTkFrames' 76 | CTkTextbox = 'ctk.CTkTextbox' 77 | CTkFont = 'ctk.CTkFont' 78 | CTkImage = 'ctk.CTkImage' 79 | CTkVariable = 'ctk.CTkVariable' 80 | CTkCanvas = 'ctk.CTkCanvas' 81 | CTkIntVar = 'ctk.CTkIntVar' 82 | CTkDoubleVar = 'ctk.CTkDoubleVar' 83 | CTkStringVar = 'ctk.CTkStringVar' 84 | CTkBooleanVar = 'ctk.CTkBooleanVar' 85 | CTkPhotoImage = 'ctk.CTkPhotoImage' 86 | 87 | 88 | REMOVE = 333 89 | 90 | class CTkWidgets(ctk): 91 | def __init__(self): 92 | super().__init__() 93 | self._ctk_members = [self.CTkButton, self.CTk, self.CTkInputDialog, self.CTkToplevel, self.CTkCheckBox, self.CTkComboBox, self.CTkEntry, self.CTkFrame, self.CTkLabel, self.CTkOptionMenu, self.CTkProgressBar, self.CTkRadioButton, self.CTkScrollableFrame, self.CTkScrollbar, self.CTkSegmentedButton, self.CTkSlider, self.CTkSwitch, self.CTkTabview, self.CTkFrames, self.CTkTextbox, self.CTkFont, self.CTkImage, self.CTkVariable, self.CTkCanvas, self.CTkIntVar, self.CTkDoubleVar, self.CTkStringVar, self.CTkBooleanVar, self.CTkPhotoImage] 94 | self._ctk_names = self._ctk_members + ctk_widget_names 95 | self._ctk_widgets = ctk_widget_names 96 | self._ctk_arguments = ctk_widget_arguments 97 | self._tk_arguments = tk_widget_arguments 98 | self._tk2ctk_map = tk_widget_to_ctk_widget 99 | self._tk_names = tk_widgets 100 | self._tk_keywords_to_ctk = { 101 | "orient": "orientation", 102 | "borderwidth": "border_width", 103 | 104 | } 105 | def match(self, name:str) -> str: 106 | if name in self._tk_names: 107 | return self._tk2ctk_map[name] 108 | else: 109 | return name 110 | 111 | def match_args(self, name:str) -> list[str]: 112 | if name in self._ctk_names: 113 | return self._ctk_arguments[name] 114 | elif name in self._tk_names: 115 | return tk_widget_arguments[name] 116 | else: 117 | return [] 118 | 119 | def is_ctk(self, name:str) -> bool: 120 | return name in self._ctk_names 121 | 122 | def is_tk(self, name:str) -> bool: 123 | return name in self._tk_names 124 | 125 | 126 | ctk_qualified_widget_names = ["ctk." + x for x in ctk_widget_names] 127 | 128 | 129 | 130 | 131 | 132 | tk_widget_arguments = { 133 | "tk.Tk": ["screenName", "baseName", "className", "useTk", "sync", "use"], 134 | "ttk.Button": ["command", "default", "takefocus", "text", "textvariable", "underline", 135 | "width", "image", "compound", "padding", "state", "cursor", "style", "class"], 136 | "ttk.Checkbox": ["variable", "onvalue", "offvalue", "command", "takefocus", "text", "textvariable", 137 | "underline", "width", "image", "compound", "padding", "state", "cursor", "style", "class" ], 138 | "ttk.Combobox": ["height", "postcommand", "values", "exportselection", "font", "invalidcommand", "justify", 139 | "show", "state", "textvariable", "validate", "validatecommand", "width", "xscrollcommand", 140 | "foreground", "background", "takefocus", "cursor", "style", "class"], 141 | "ttk.Entry": ["exportselection", "font", "invalidcommand", "justify", "show", "state", "textvariable", 142 | "validate", "validatecommand", "width", "xscrollcommand", "foreground", "background", 143 | "takefocus", "cursor", "style"], 144 | "ttk.Label": ["background", "bg", "fg", "foreground", "font", "borderwidth", "relief", "anchor", "justify", 145 | "wraplength", "takefocus", "text", "textvariable", "underline", "width", "image", "compound", 146 | "padding", "state", "cursor", "style"], 147 | "ttk.LabeledScale": ["borderwidth", "padding", "relief", "width", "height", "takefocus", "cursor", "style"], 148 | "ttk.Labelframe": ["labelanchor", "text", "underline", "labelwidget", "borderwidth", "padding", "relief", 149 | "width", "height", "takefocus", "cursor", "style"], 150 | "ttk.Menubutton": ["menu", "direction", "takefocus", "text", "textvariable", "underline", "width", 151 | "image", "compound", "padding", "state", "cursor", "style"], 152 | "ttk.Notebook": ["height", "padding", "takefocus", "cursor", "style"], 153 | "ttk.Panedwindow": ["orient", "width", "height", "takefocus", "cursor", "style"], 154 | "ttk.Progressbar": ["orient", "length", "mode", "maximum", "value", "variable", "phase", "takefocus", "cursor", "style"], 155 | "ttk.Radiobutton": ["variable", "value", "command", "takefocus", "text", "textvariable", "underline", 156 | "width", "image", "compound", "padding", "state", "cursor", "style"], 157 | "ttk.Scale": ["orient", "length", "from", "to", "variable", "command", "takefocus", "cursor", "style"], 158 | "ttk.Scrollbar": ["command", "orient", "takefocus", "cursor", "style"], 159 | "ttk.Separator": ["orient", "takefocus", "cursor", "style"], 160 | "ttk.Spinbox": ["values", "from", "to", "increment", "format", "command", "wrap", "exportselection", "font", 161 | "invalidcommand", "justify", "show", "state", "textvariable", "validate", "validatecommand", 162 | "width", "xscrollcommand", "foreground", "background", "takefocus", "cursor", "style"], 163 | "ttk.Treeview": ["columns", "displaycolumns", "show", "selectmode", "height", "padding", "xscrollcommand", 164 | "yscrollcommand", "takefocus", "cursor", "style"], 165 | 166 | "tk.Canvas": ["background", "bd", "bg", "borderwidth", "closeenough", "confine", "cursor", "height", 167 | "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", 168 | "insertofftime", "insertontime", "insertwidth", "offset", "relief", "scrollregion", "selectbackground", 169 | "selectborderwidth", "selectforeground", "state", "takefocus", "width", "xscrollcommand", "xscrollincrement", 170 | "yscrollcommand", "yscrollincrement"], 171 | "tk.Listbox": ["activestyle", "background", "bd", "bg", "borderwidth", "cursor", "disabledforeground", 172 | "exportselection", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", 173 | "highlightthickness", "justify", "relief", "selectbackground", "selectborderwidth", "selectforeground", 174 | "selectmode", "setgrid", "state", "takefocus", "width", "xscrollcommand", "yscrollcommand", "listvariable"], 175 | "tk.Menu": ["activebackground", "activeborderwidth", "activeforeground", "background", "bd", "bg", "borderwidth", 176 | "cursor", "disabledforeground", "fg", "font", "foreground", "postcommand", "relief", "selectcolor", 177 | "takefocus", "tearoff", "tearoffcommand", "title", "type"], 178 | "tk.Text": ["autoseparators", "background", "bd", "bg", "blockcursor", "borderwidth", "cursor", "endline", 179 | "exportselection", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", 180 | "highlightthickness", "inactiveselectbackground", "insertbackground", "insertborderwidth", "insertofftime", 181 | "insertontime", "insertunfocussed", "insertwidth", "maxundo", "padx", "pady", "relief", "selectbackground", 182 | "selectborderwidth", "selectforeground", "setgrid", "spacing1", "spacing2", "spacing3", "startline", "state", 183 | "tabs", "tabstyle", "takefocus", "undo", "width", "wrap", "xscrollcommand", "yscrollcommand"], 184 | "tk.Toplevel": ["bd", "borderwidth", "class", "menu", "relief", "screen", "use", "background", "bg", "colormap", 185 | "container", "cursor", "height", "highlightbackground", "highlightcolor", "highlightthickness", 186 | "padx", "pady", "takefocus", "visual", "width"], 187 | "tk.Button": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", 188 | "command", "compound", "cursor", "default", "disabledforeground", "fg", "font", "foreground", "height", 189 | "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "overrelief", "padx", 190 | "pady", "relief", "repeatdelay", "repeatinterval", "state", "takefocus", "text", "textvariable", "underline", 191 | "width", "wraplength"], 192 | "tk.Checkbutton": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", "command", "compound", "cursor", "disabledforeground", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "indicatoron", "justify", "offrelief", "offvalue", "onvalue", "overrelief", "padx", "pady", "relief", "selectcolor", "selectimage", "state", "takefocus", "text", "textvariable", "tristateimage", "tristatevalue", "underline", "variable", "width", "wraplength"], 193 | "tk.Entry": ["background", "bd", "bg", "borderwidth", "cursor", "disabledbackground", "disabledforeground", "exportselection", "fg", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "invalidcommand", "invcmd", "justify", "readonlybackground", "relief", "selectbackground", "selectborderwidth", "selectforeground", "show", "state", "takefocus", "textvariable", "validate", "validatecommand", "vcmd", "width", "xscrollcommand"], 194 | "tk.Frame": ["bd", "borderwidth", "class", "relief", "background", "bg", "colormap", "container", "cursor", "height", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "takefocus", "visual", "width"], 195 | "tk.Label": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", "compound", "cursor", "disabledforeground", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "padx", "pady", "relief", "state", "takefocus", "text", "textvariable", "underline", "width", "wraplength"], 196 | "tk.Labelframe": ["bd", "borderwidth", "class", "fg", "font", "foreground", "labelanchor", "labelwidget", "relief", "text", "background", "bg", "colormap", "container", "cursor", "height", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "takefocus", "visual", "width"], 197 | "tk.Menubutton": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", "cursor", "direction", "disabledforeground", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "indicatoron", "justify", "menu", "padx", "pady", "relief", "compound", "state", "takefocus", "text", "textvariable", "underline", "width", "wraplength"], 198 | "tk.Message": ["anchor", "aspect", "background", "bd", "bg", "borderwidth", "cursor", "fg", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "justify", "padx", "pady", "relief", "takefocus", "text", "textvariable", "width"], 199 | "tk.OptionMenu": ["master", "variable", "value", "*", "**"], 200 | "tk.PanedWindow": ["background", "bd", "bg", "borderwidth", "cursor", "handlepad", "handlesize", "height", "opaqueresize", "orient", "proxybackground", "proxyborderwidth", "proxyrelief", "relief", "sashcursor", "sashpad", "sashrelief", "sashwidth", "showhandle", "width"], 201 | "tk.Radiobutton": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", "command", "compound", "cursor", "disabledforeground", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "indicatoron", "justify", "offrelief", "overrelief", "padx", "pady", "relief", "selectcolor", "selectimage", "state", "takefocus", "text", "textvariable", "tristateimage", "tristatevalue", "underline", "value", "variable", "width", "wraplength"], 202 | "tk.Scale": ["activebackground", "background", "bigincrement", "bd", "bg", "borderwidth", "command", "cursor", "digits", "fg", "font", "foreground", "from", "highlightbackground", "highlightcolor", "highlightthickness", "label", "length", "orient", "relief", "repeatdelay", "repeatinterval", "resolution", "showvalue", "sliderlength", "sliderrelief", "state", "takefocus", "tickinterval", "to", "troughcolor", "variable", "width", ], 203 | "tk.Scrollbar": ["activebackground", "activerelief", "background", "bd", "bg", "borderwidth", "command", "cursor", "elementborderwidth", "highlightbackground", "highlightcolor", "highlightthickness", "jump", "orient", "relief", "repeatdelay", "repeatinterval", "takefocus", "troughcolor", "width"], 204 | "tk.Spinbox": ["activebackground", "background", "bd", "bg", "borderwidth", "buttonbackground", "buttoncursor", "buttondownrelief", "buttonuprelief", "command", "cursor", "disabledbackground", "disabledforeground", "exportselection", "fg", "font", "foreground", "format", "from", "highlightbackground", "highlightcolor", "highlightthickness", "increment", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "invalidcommand", "invcmd", "justify", "relief", "readonlybackground", "repeatdelay", "repeatinterval", "selectbackground", "selectborderwidth", "selectforeground", "state", "takefocus", "textvariable", "to", "validate", "validatecommand", "values", "vcmd", "width", "wrap", "xscrollcommand"], 205 | "tk.Variable": ["master", "value", "name"], 206 | "tk.BoolenVar": ["master", "value", "name"], 207 | "tk.DoubleVar": ["master", "value", "name"], 208 | "tk.IntVar": ["master", "value", "name"], 209 | "tk.StringVar": ["master", "value", "name"], 210 | "tk.BitmapImage": ["name", "cnf", "master", "**"], 211 | "tk.PhotoImage": ["name", "cnf", "master", "**"], 212 | "tk.filedialog.Directory": ["master", "**"], 213 | "tk.filedialog.Open": ["master", "**"], 214 | "tk.filedialog.SaveAs": ["master", "**"], 215 | "tk.colorchooser.Chooser": ["master", "**"], 216 | "ttk.Style": ["master"], 217 | "ttk.Font": ["root", "font", "name", "exists", "**"], 218 | } 219 | ctk_widget_arguments = { 220 | "ctk.CTkButton": ["master", "width", "height", "corner_radius", "border_width", "border_spacing", "fg_color", "hover_color", "border_color", "text_color", "text_color_disabled", "text", "font", "textvariable", "image", "state", "hover", "command", "compound", "anchor"], 221 | "ctk.CTk": ["fg_color"], 222 | "ctk.CTkInputDialog": ["title", "text", "fg_color", "button_hover_color", "button_text_color", "entry_fg_color", "entry_border_color", "entry_text_color" ], 223 | "ctk.CTkToplevel": ["fg_color"], 224 | "ctk.CTkCheckBox": ["master", "width", "height", "checkbox_width", "corner_radius", "border_width", "fg_color", "border_color", "text_color", "text", "textvariable", "font", "hover", "state", "variable", "offvalue" ], 225 | "ctk.CTkComboBox": ["master", "width", "height", "corner_radius", "border_width", "fg_color", "border_color", "button_color", "button_hover_color", "dropdown_fg_color", "dropdown_hover_color", "text_color", "font", "dropdown_font", "values", "hover", "state", "variable" ], 226 | "ctk.CTkEntry": ["master", "textvariable", "width", "height", "corner_radius", "fg_color", "text_color", "placeholder_text_color", "placeholder_text", "font", "state" ], 227 | "ctk.CTkFrame": ["master", "width", "height", "border_width", "fg_color", "border_color" ], 228 | "ctk.CTkLabel": ["master", "textvariable", "text", "width", "height", "corner_radius", "fg_color", "text_color", "font", "anchor", "compound", "padx", "pady" ], 229 | "ctk.CTkOptionMenu": ["master", "width", "height", "corner_radius", "fg_color", "button_color", "button_hover_color", "dropdown_fg_color", "dropdown_hover_color", "text_color", "font", "dropdown_font", "hover", "state", "variable", "values", "dynamic_resizing", "anchor" ], 230 | "ctk.CTkProgressBar": ["master", "width", "height", "border_width", "corner_radius", "fg_color", "border_color", "progress_color", "mode", "determinate_speed" ], 231 | "ctk.CTkRadioButton": ["master", "width", "height", "radiobutton_width", "radiobutton_height", "corner_radius", "border_width_unchecked", "border_width_checked", "fg_color", "border_color", "text_color", "text", "textvariable", "font", "hover", "state", "variable", "value" ], 232 | "ctk.CTkScrollableFrame": ["master", "width", "height", "corner_radius", "border_width", "fg_color", "border_color", "scrollbar_fg_color", "scrollbar_button_color", "scrollbar_button_hover_color", "label_fg_color", "label_text_color", "label_text", "label_font", "label_anchor" ], 233 | "ctk.CTkScrollbar": ["master", "width", "height", "corner_radius", "border_spacing", "fg_color", "button_color", "button_hover_color", "minimum_pixel_length", "hover" ], 234 | "ctk.CTkSegmentedButton": ["master", "width", "height", "corner_radius", "border_width", "fg_color", "selected_color", "selected_hover_color", "unselected_color", "unselected_hover_color", "text_color", "font", "values", "variable", "state", "dynamic_resizing" ], 235 | "ctk.CTkSlider": ["master", "variable", "width", "height", "border_width", "from_", "to", "fg_color", "progress_color", "border_color", "button_color", "button_hover_color", "state", "hover" ], 236 | "ctk.CTkSwitch": ["master", "width", "height", "switch_width", "switch_height", "corner_radius", "border_width", "fg_color", "border_color", "progress_color", "button_color", "button_hover_color", "text_color", "text", "textvariable", "font", "variable", "offvalue", "statemaster", "width", "height", "switch_width", "switch_height", "corner_radius", "border_width", "fg_color", "border_color", "progress_color", "button_color", "button_hover_color", "text_color", "text", "textvariable", "font", "variable", "offvalue", "state" ], 237 | "ctk.CTkTabview": ["master", "width", "height", "corner_radius", "border_width", "fg_color", "border_color", "segmented_button_fg_color", "segmented_button_selected_hover_color", "segmented_button_unselected_color", "text_color", "anchor", "state" ], 238 | "ctk.CTkTextbox": ["master", "width", "height", "corner_radius", "border_width", "border_spacing", "fg_color", "border_color", "text_color", "scrollbar_button_color", "scrollbar_button_hover_color", "font", "state", "wrap" ], 239 | "ctk.CTkFont": ["family", "size", "weight", "slant", "underline", "overstrike"], 240 | "ctk.CTkImage": ["light_image", "dark_image", "size"], 241 | "ctk.CTkCanvas": ["background", "bd", "bg", "borderwidth", "closeenough", "confine", "cursor", "height", "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "offset", "relief", "scrollregion", "selectbackground", "selectborderwidth", "selectforeground", "state", "takefocus", "width", "xscrollcommand", "xscrollincrement", "yscrollcommand", "yscrollincrement"], 242 | "ctk.Variable": ["master", "value", "name"], 243 | "ctk.BoolenVar": ["master", "value", "name"], 244 | "ctk.DoubleVar": ["master", "value", "name"], 245 | "ctk.IntVar": ["master", "value", "name"], 246 | "ctk.StringVar": ["master", "value", "name"], 247 | "ctk.filedialog.Directory": ["master", "**"], 248 | "ctk.filedialog.Open": ["master", "**"], 249 | "ctk.filedialog.SaveAs": ["master", "**"], 250 | "ctk.colorchooser.Chooser": ["master", "**"], 251 | "ctk.CTkVariable": ["master", "value", "name"], 252 | "ctk.CTkVariable": ["master", "value", "name"], 253 | "ctk.CTkBoolenVar": ["master", "value", "name"], 254 | "ctk.CTkDoubleVar": ["master", "value", "name"], 255 | "ctk.CTkIntVar": ["master", "value", "name"], 256 | "ctk.CTkStringVar": ["master", "value", "name"], 257 | "ctk.CTkPhotoImage": ["master", "light_image", "dark_image", "size"], 258 | 259 | } 260 | 261 | universal_kw_translation = { 262 | "background": "bg_color", 263 | "foreground": "fg_color", 264 | "fg": "fg_color", 265 | "orient": "orientation", 266 | "bg": "bg_color", 267 | "borderwidth": "border_width", 268 | "text_variable": "textvariable", 269 | "from": "from_", 270 | "labelanchor": "label_anchor" 271 | } 272 | 273 | tk_widget_to_ctk_widget = { 274 | "tk.Tk": ctk.CTk, 275 | "tk.Canvas": ctk.CTkCanvas, 276 | "tk.ButtonBox": ctk.CTkSegmentedButton, 277 | "tk.Button": ctk.CTkButton, 278 | "tk.Checkbutton": ctk.CTkCheckBox, 279 | "tk.Entry": ctk.CTkEntry, 280 | "tk.Frame": ctk.CTkFrame, 281 | "tk.Label": ctk.CTkLabel, 282 | "tk.LabelFrame": ctk.CTkScrollableFrame, 283 | "tk.Menu": ctk.CTkOptionMenu, 284 | "tk.OptionMenu": ctk.CTkOptionMenu, 285 | "tk.Panedwindow": ctk.CTkTabview, 286 | "tk.Progressbar": ctk.CTkProgressBar, 287 | "tk.Radiobutton": ctk.CTkRadioButton, 288 | "tk.Scale": ctk.CTkSlider, 289 | "tk.Scrollbar": ctk.CTkScrollbar, 290 | #"tk.Spinbox": ctk.CTkSpinbox, 291 | "tk.Text": ctk.CTkTextbox, 292 | "tk.Toplevel": ctk.CTkToplevel, 293 | "tk.Variable": ctk.CTkVariable, 294 | "tk.BooleanVar": ctk.CTkBooleanVar, 295 | "tk.DoubleVar": ctk.CTkDoubleVar, 296 | "tk.IntVar": ctk.CTkIntVar, 297 | "tk.StringVar": ctk.CTkStringVar, 298 | "tk.BitmapImage": ctk.CTkImage, 299 | "tk.PhotoImage": ctk.CTkPhotoImage, 300 | "tk.font.Font": ctk.CTkFont, 301 | 302 | "ttk.Button": ctk.CTkButton, 303 | "ttk.Menubutton": ctk.CTkButton, 304 | "ttk.LabeledScale": ctk.CTkSlider, 305 | "ttk.Labelframe": ctk.CTkScrollableFrame, 306 | "ttk.Checkbutton": ctk.CTkCheckBox, 307 | "ttk.Checkbox": ctk.CTkCheckBox, 308 | "ttk.Combobox": ctk.CTkComboBox, 309 | "ttk.Entry": ctk.CTkEntry, 310 | "ttk.Frame": ctk.CTkFrame, 311 | "ttk.Label": ctk.CTkLabel, 312 | "ttk.LabelFrame": ctk.CTkScrollableFrame, 313 | "ttk.Menu": ctk.CTkOptionMenu, 314 | "ttk.OptionMenu": ctk.CTkOptionMenu, 315 | "ttk.Panedwindow": ctk.CTkTabview, 316 | "ttk.Progressbar": ctk.CTkProgressBar, 317 | "ttk.Radiobutton": ctk.CTkRadioButton, 318 | "ttk.Scale": ctk.CTkSlider, 319 | "ttk.Scrollbar": ctk.CTkScrollbar, 320 | #"tk.Spinbox": ctk.CTkSpinbox, 321 | "ttk.Text": ctk.CTkTextbox, 322 | "ttk.Toplevel": ctk.CTkToplevel, 323 | "ttk.Variable": ctk.CTkVariable, 324 | "ttk.BooleanVar": ctk.CTkVariable, 325 | "ttk.DoubleVar": ctk.CTkVariable, 326 | "ttk.IntVar": ctk.CTkVariable, 327 | "ttk.StringVar": ctk.CTkVariable, 328 | "ttk.BitmapImage": ctk.CTkImage, 329 | "ttk.PhotoImage": ctk.CTkImage, 330 | "ttk.font.Font": ctk.CTkFont, 331 | "ttk.Notebook": ctk.CTkTabview, 332 | } 333 | 334 | def match_tk(tk:str): 335 | return tk_widget_to_ctk_widget.get(tk, None) 336 | 337 | def replace_tk(tk:str): 338 | return tk_widget_to_ctk_widget.get(tk, tk) 339 | 340 | 341 | tk_widgets = [ 342 | "tk.Tk", 343 | "tk.Button", 344 | "tk.Canvas", 345 | "tk.Checkbutton", 346 | "tk.Entry", 347 | "tk.Frame", 348 | "tk.Label", 349 | "tk.LabelFrame", 350 | "tk.Listbox", 351 | "tk.Menu", 352 | "tk.Menubutton", 353 | "tk.Message", 354 | "tk.OptionMenu", 355 | "tk.Panedwindow", 356 | "tk.Progressbar", 357 | "tk.Radiobutton", 358 | "tk.Scale", 359 | "tk.Scrollbar", 360 | "tk.Separator", 361 | "tk.Spinbox", 362 | "tk.Text", 363 | "tk.Toplevel", 364 | "tk.Treeview", 365 | "tk.Variable", 366 | "tk.BooleanVar", 367 | "tk.DoubleVar", 368 | "tk.IntVar", 369 | "tk.StringVar", 370 | "tk.BitmapImage", 371 | "tk.PhotoImage", 372 | "tk.filedialog.Directory", 373 | "tk.filedialog.Open", 374 | "tk.filedialog.SaveAs", 375 | "tk.colorchooser.Chooser", 376 | "tk.font.Font", 377 | "tk.ttk.Style" 378 | # TTK --------------------------------------- 379 | "ttk.Button", 380 | "ttk.Checkbutton", 381 | "ttk.Combobox", 382 | "ttk.Entry", 383 | "ttk.Frame", 384 | "ttk.Label", 385 | "ttk.LabeledScale", 386 | "ttk.Labelframe", 387 | "ttk.Menubutton", 388 | "ttk.Notebook", 389 | "ttk.OptionMenu", 390 | "ttk.Panedwindow", 391 | "ttk.Progressbar", 392 | "ttk.Radiobutton", 393 | "ttk.Scale", 394 | "ttk.Scrollbar", 395 | "ttk.Separator", 396 | "ttk.Sizegrip", 397 | "ttk.Spinbox", 398 | "ttk.Treeview", 399 | 'Tk', 'Button', 'Canvas', 'Checkbutton', 'Entry', 'Frame', 'Label', 'LabelFrame', 'Listbox', 'Menu', 'Menubutton', 'Message', 'OptionMenu', 'Panedwindow', 'Progressbar', 'Radiobutton', 'Scale', 'Scrollbar', 'Separator', 'Spinbox', 'Text', 'Toplevel', 'Treeview', 'Variable', 'BooleanVar', 'DoubleVar', 'IntVar', 'StringVar', 'BitmapImage', 'PhotoImage', 'filedialog.Directory', 'filedialog.Open', 'filedialog.SaveAs', 'colorchooser.Chooser', 'font.Font', 'ttk.Stylettk.Button', 'Checkbutton', 'Combobox', 'Entry', 'Frame', 'Label', 'LabeledScale', 'Labelframe', 'Menubutton', 'Notebook', 'OptionMenu', 'Panedwindow', 'Progressbar', 'Radiobutton', 'Scale', 'Scrollbar', 'Separator', 'Sizegrip', 'Spinbox', 'Treeview', 400 | ] 401 | 402 | tk_available_events = [ 403 | "", 404 | "", 405 | "", 406 | "", 407 | "", 408 | "", 409 | "", 410 | "", 411 | "", 412 | "", 413 | "", 414 | "", 415 | "", 416 | "", 417 | ] 418 | 419 | def test_to_see_by_name_which_ctk_widgets_lose_kws(): 420 | import json 421 | data = {} 422 | for tk_widget, tk_args in tk_widget_arguments.items(): 423 | ctk_widget = match_tk(tk_widget) 424 | if ctk_widget is None: 425 | continue 426 | data[ctk_widget] = [] 427 | ctk_args = ctk_widget_arguments[ctk_widget] 428 | for arg in tk_args: 429 | if arg not in ctk_args: 430 | if arg in universal_kw_translation: 431 | continue 432 | data[ctk_widget].append(arg) 433 | from pprint import pprint 434 | pprint(data) 435 | 436 | 437 | def test_to_find_bisected_keywords(): 438 | 439 | def letters_percentage(string:str, string2:str): 440 | return len([x for x in string if x in string2]) / len(string) 441 | 442 | 443 | def normalize(string:str): 444 | return string.replace("_", "") 445 | 446 | for tk_widget, tk_args in tk_widget_arguments.items(): 447 | 448 | ctk_widget = match_tk(tk_widget) 449 | if ctk_widget is None: 450 | continue 451 | ctk_args = ctk_widget_arguments[ctk_widget] 452 | 453 | 454 | for arg in tk_args: 455 | current_match = "" 456 | highest = 0.0 457 | for ctk_arg in ctk_args: 458 | if arg == ctk_arg: 459 | break 460 | percent = letters_percentage(arg, ctk_arg) 461 | if percent > highest: 462 | highest = percent 463 | current_match = ctk_arg 464 | else: 465 | continue 466 | print("possible match tk -> ctk: " + arg + " -> " + current_match, highest) 467 | 468 | norm_tk_args = [normalize(x) for x in tk_args] 469 | norm_ctk_args = [normalize(x) for x in ctk_args] 470 | 471 | for index, norm_ctk_arg in enumerate(norm_ctk_args): 472 | 473 | if norm_ctk_arg in norm_tk_args: 474 | c = ctk_args[index] 475 | n = tk_args[norm_tk_args.index(norm_ctk_arg)] 476 | 477 | print(ctk_widget + " : " + c + " <- " + n) 478 | 479 | 480 | 481 | 482 | 483 | 484 | def test_to_see_what_keywords_dont_translate_at_all(): 485 | 486 | def letters_percentage(string:str, string2:str): 487 | return len([x for x in string if x in string2]) / len(string) 488 | 489 | ctks = [] 490 | for ctk_widget, ctk_args in ctk_widget_arguments.items(): 491 | ctks.extend(ctk_args) 492 | ctks = set(ctks) 493 | 494 | percents = [] 495 | tks = set() 496 | for tk_widget, tk_args in tk_widget_arguments.items(): 497 | for tk_arg in tk_args: 498 | if tk_arg not in ctks: 499 | if tk_arg in tks: 500 | continue 501 | tks.add(tk_arg) 502 | 503 | for index, tk in enumerate(tks): 504 | print(tk) 505 | 506 | 507 | -------------------------------------------------------------------------------- /tk_to_ctk/listbox.py: -------------------------------------------------------------------------------- 1 | import customtkinter as ctk 2 | 3 | 4 | class CTkListbox(ctk.CTkScrollableFrame): 5 | def __init__( 6 | self, 7 | master: any, 8 | height: int = 100, 9 | width: int = 200, 10 | highlight_color: str = "default", 11 | fg_color: str = "transparent", 12 | bg_color: str = None, 13 | text_color: str = "default", 14 | hover_color: str = "default", 15 | button_fg_color: str = "default", 16 | border_width: int = 3, 17 | font: tuple = "default", 18 | multiple_selection: bool = False, 19 | listvariable=None, 20 | hover: bool = True, 21 | command=None, 22 | justify="left", 23 | **kwargs, 24 | ): 25 | super().__init__( 26 | master, 27 | width=width, 28 | height=height, 29 | fg_color=fg_color, 30 | border_width=border_width, 31 | **kwargs, 32 | ) 33 | self._scrollbar.grid_configure(padx=(0, border_width + 4)) 34 | self._scrollbar.configure(width=12) 35 | 36 | if bg_color: 37 | super().configure(bg_color=bg_color) 38 | 39 | self.select_color = ( 40 | ctk.ThemeManager.theme["CTkButton"]["fg_color"] 41 | if highlight_color == "default" 42 | else highlight_color 43 | ) 44 | self.text_color = ( 45 | ctk.ThemeManager.theme["CTkButton"]["text_color"] 46 | if text_color == "default" 47 | else text_color 48 | ) 49 | self.hover_color = ( 50 | ctk.ThemeManager.theme["CTkButton"]["hover_color"] 51 | if hover_color == "default" 52 | else hover_color 53 | ) 54 | self.font = ( 55 | (ctk.ThemeManager.theme["CTkFont"]["family"], 13) 56 | if font == "default" 57 | else font 58 | ) 59 | self.button_fg_color = ( 60 | "transparent" if button_fg_color == "default" else button_fg_color 61 | ) 62 | 63 | if justify == "left": 64 | self.justify = "w" 65 | elif justify == "right": 66 | self.justify = "e" 67 | else: 68 | self.justify = "c" 69 | self.buttons = {} 70 | self.command = command 71 | self.multiple = multiple_selection 72 | self.selected = None 73 | self.hover = hover 74 | self.end_num = 0 75 | self.selections = [] 76 | self.selected_index = 0 77 | 78 | if listvariable: 79 | self.listvariable = listvariable 80 | self.listvariable.trace_add("write", lambda a, b, c: self.update_listvar()) 81 | self.update_listvar() 82 | 83 | super().bind("", lambda e: self.unbind_all("")) 84 | 85 | def update_listvar(self): 86 | values = list(eval(self.listvariable.get())) 87 | self.delete("all") 88 | for i in values: 89 | self.insert("END", option=i) 90 | 91 | def select(self, index): 92 | """select the option""" 93 | for options in self.buttons.values(): 94 | options.configure(fg_color=self.button_fg_color) 95 | 96 | if isinstance(index, int): 97 | if index in self.buttons: 98 | selected_button = self.buttons[index] 99 | else: 100 | selected_button = list(self.buttons.values())[index] 101 | else: 102 | selected_button = self.buttons[index] 103 | 104 | if self.multiple: 105 | if selected_button in self.selections: 106 | self.selections.remove(selected_button) 107 | selected_button.configure(fg_color=self.button_fg_color, hover=False) 108 | self.after(100, lambda: selected_button.configure(hover=self.hover)) 109 | else: 110 | self.selections.append(selected_button) 111 | for i in self.selections: 112 | i.configure(fg_color=self.select_color, hover=False) 113 | self.after(100, lambda button=i: button.configure(hover=self.hover)) 114 | else: 115 | self.selected = selected_button 116 | selected_button.configure(fg_color=self.select_color, hover=False) 117 | self.after(100, lambda: selected_button.configure(hover=self.hover)) 118 | 119 | if self.command: 120 | self.command(self.get()) 121 | 122 | self.event_generate("<>") 123 | 124 | def activate(self, index): 125 | if str(index).lower() == "all": 126 | if self.multiple: 127 | for i in self.buttons: 128 | self.select(i) 129 | return 130 | 131 | if str(index).lower() == "end": 132 | index = -1 133 | 134 | selected = list(self.buttons.keys())[index] 135 | self.select(selected) 136 | 137 | def curselection(self): 138 | index = 0 139 | if self.multiple: 140 | indexes = [] 141 | for i in self.buttons.values(): 142 | if i in self.selections: 143 | indexes.append(index) 144 | index += 1 145 | return tuple(indexes) 146 | 147 | else: 148 | for i in self.buttons.values(): 149 | if i == self.selected: 150 | return index 151 | else: 152 | index += 1 153 | 154 | def bind(self, key, func): 155 | super().bind_all(key, lambda e: func(self.get()), add="+") 156 | 157 | def deselect(self, index): 158 | if not self.multiple: 159 | self.selected.configure(fg_color=self.button_fg_color) 160 | self.selected = None 161 | return 162 | if self.buttons[index] in self.selections: 163 | self.selections.remove(self.buttons[index]) 164 | self.buttons[index].configure(fg_color=self.button_fg_color) 165 | 166 | def deactivate(self, index): 167 | if str(index).lower() == "all": 168 | for i in self.buttons: 169 | self.deselect(i) 170 | return 171 | 172 | if str(index).lower() == "end": 173 | index = -1 174 | 175 | selected = list(self.buttons.keys())[index] 176 | self.deselect(selected) 177 | 178 | def insert(self, index, option, **args): 179 | """add new option in the listbox""" 180 | 181 | if str(index).lower() == "end": 182 | index = f"END{self.end_num}" 183 | self.end_num += 1 184 | 185 | if index in self.buttons: 186 | self.buttons[index].destroy() 187 | 188 | self.buttons[index] = ctk.CTkButton( 189 | self, 190 | text=option, 191 | fg_color=self.button_fg_color, 192 | anchor=self.justify, 193 | text_color=self.text_color, 194 | font=self.font, 195 | hover_color=self.hover_color, 196 | **args, 197 | ) 198 | self.buttons[index].configure(command=lambda num=index: self.select(num)) 199 | self.buttons[index].pack(padx=0, pady=(0, 5), fill="x", expand=True) 200 | self.update() 201 | 202 | if self.multiple: 203 | self.buttons[index].bind( 204 | "", lambda e: self.select_multiple(self.buttons[index]) 205 | ) 206 | 207 | return self.buttons[index] 208 | 209 | def select_multiple(self, button): 210 | selections = list(self.buttons.values()) 211 | if len(self.selections) > 0: 212 | last = selections.index(self.selections[-1]) 213 | to = selections.index(button) 214 | 215 | if last < to: 216 | for i in range(last + 1, to + 1): 217 | if list(self.buttons.values())[i] not in self.selections: 218 | self.select(i) 219 | else: 220 | for i in range(to, last): 221 | if list(self.buttons.values())[i] not in self.selections: 222 | self.select(i) 223 | 224 | def delete(self, index, last=None): 225 | """delete options from the listbox""" 226 | if str(index).lower() == "all": 227 | for i in self.buttons: 228 | self.buttons[i].destroy() 229 | self.buttons = {} 230 | self.end_num = 0 231 | return 232 | 233 | if str(index).lower() == "end": 234 | index = f"END{self.end_num}" 235 | self.end_num -= 1 236 | else: 237 | if int(index) >= len(self.buttons): 238 | return 239 | if not last: 240 | index = list(self.buttons.keys())[int(index)] 241 | 242 | if last: 243 | if str(last).lower() == "end": 244 | last = len(self.buttons) - 1 245 | elif int(last) >= len(self.buttons): 246 | last = len(self.buttons) - 1 247 | 248 | deleted_list = [] 249 | for i in range(int(index), int(last) + 1): 250 | list(self.buttons.values())[i].destroy() 251 | deleted_list.append(list(self.buttons.keys())[i]) 252 | self.update() 253 | for i in deleted_list: 254 | del self.buttons[i] 255 | else: 256 | self.buttons[index].destroy() 257 | if self.multiple: 258 | if self.buttons[index] in self.selections: 259 | self.selections.remove(self.buttons[index]) 260 | del self.buttons[index] 261 | 262 | def size(self): 263 | """return total number of items in the listbox""" 264 | return len(self.buttons.keys()) 265 | 266 | def get(self, index=None): 267 | """get the selected value""" 268 | if index is not None: 269 | if str(index).lower() == "all": 270 | return list(item.cget("text") for item in self.buttons.values()) 271 | else: 272 | index = list(self.buttons.keys())[int(index)] 273 | return self.buttons[index].cget("text") 274 | else: 275 | if self.multiple: 276 | return ( 277 | [x.cget("text") for x in self.selections] 278 | if len(self.selections) > 0 279 | else None 280 | ) 281 | else: 282 | return self.selected.cget("text") if self.selected is not None else None 283 | 284 | def configure(self, **kwargs): 285 | """configurable options of the listbox""" 286 | 287 | if "hover_color" in kwargs: 288 | self.hover_color = kwargs.pop("hover_color") 289 | for i in self.buttons.values(): 290 | i.configure(hover_color=self.hover_color) 291 | if "button_fg_color" in kwargs: 292 | self.button_fg_color = kwargs.pop("button_fg_color") 293 | for i in self.buttons.values(): 294 | i.configure(fg_color=self.button_fg_color) 295 | if "highlight_color" in kwargs: 296 | self.select_color = kwargs.pop("highlight_color") 297 | if self.selected: 298 | self.selected.configure(fg_color=self.select_color) 299 | if len(self.selections) > 0: 300 | for i in self.selections: 301 | i.configure(fg_color=self.select_color) 302 | if "text_color" in kwargs: 303 | self.text_color = kwargs.pop("text_color") 304 | for i in self.buttons.values(): 305 | i.configure(text_color=self.text_color) 306 | if "font" in kwargs: 307 | self.font = kwargs.pop("font") 308 | for i in self.buttons.values(): 309 | i.configure(font=self.font) 310 | if "command" in kwargs: 311 | self.command = kwargs.pop("command") 312 | 313 | super().configure(**kwargs) 314 | 315 | def move_up(self, index): 316 | """Move the option up in the listbox""" 317 | if index > 0: 318 | current_key = list(self.buttons.keys())[index] 319 | previous_key = list(self.buttons.keys())[index - 1] 320 | 321 | # Store the text of the button to be moved 322 | current_text = self.buttons[current_key].cget("text") 323 | 324 | # Update the text of the buttons 325 | self.buttons[current_key].configure( 326 | text=self.buttons[previous_key].cget("text") 327 | ) 328 | self.buttons[previous_key].configure(text=current_text) 329 | 330 | # Clear the selection from the current option 331 | self.deselect(current_key) 332 | 333 | # Update the selection 334 | self.select(previous_key) 335 | 336 | # Update the scrollbar position 337 | if self._parent_canvas.yview() != (0.0, 1.0): 338 | self._parent_canvas.yview("scroll", -int(100 / 6), "units") 339 | 340 | def move_down(self, index): 341 | """Move the option down in the listbox""" 342 | if index < len(self.buttons) - 1: 343 | current_key = list(self.buttons.keys())[index] 344 | next_key = list(self.buttons.keys())[index + 1] 345 | 346 | # Store the text of the button to be moved 347 | current_text = self.buttons[current_key].cget("text") 348 | 349 | # Update the text of the buttons 350 | self.buttons[current_key].configure( 351 | text=self.buttons[next_key].cget("text") 352 | ) 353 | self.buttons[next_key].configure(text=current_text) 354 | 355 | # Clear the selection from the current option 356 | self.deselect(current_key) 357 | 358 | # Update the selection 359 | self.select(next_key) 360 | 361 | # Update the scrollbar position 362 | if self._parent_canvas.yview() != (0.0, 1.0): 363 | self._parent_canvas.yview("scroll", int(100 / 6), "units") 364 | -------------------------------------------------------------------------------- /tk_to_ctk/lists.py: -------------------------------------------------------------------------------- 1 | tkinter_widgets = [ 2 | "Button", 3 | "Canvas", 4 | "Checkbutton", 5 | "Entry", 6 | "Label", 7 | "Menubutton", 8 | "Message", 9 | "Radiobutton", 10 | "Scale", 11 | "Scrollbar", 12 | "Text", 13 | "Toplevel", 14 | "Treeview", 15 | "Frame", 16 | "Progressbar", 17 | "Separator", 18 | ] 19 | 20 | ctk_widgets = ["CTk" + x for x in tkinter_widgets] 21 | -------------------------------------------------------------------------------- /tk_to_ctk/slider.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from .util import parsetree 3 | from copy import deepcopy 4 | 5 | 6 | 7 | def remove_resolution_parameter_for_ctk_slider(filepath: str) -> None: 8 | """ 9 | Remove the 'resolution' parameter from all calls to ctk.CTkSlider or CTkSlider 10 | in the Python file specified by 'filepath'. 11 | 12 | Args: 13 | filepath (str): The path to the Python file. 14 | 15 | Returns: 16 | None 17 | """ 18 | with open(filepath, "r") as r: 19 | content = r.read() 20 | modified_code = remove_resolution_from_ctkslider(content) 21 | with open(filepath, "w") as w: 22 | w.write(modified_code) 23 | 24 | 25 | def remove_parameter_from_call(node: ast.Call, parameter: str) -> None: 26 | """ 27 | Remove the specified parameter from the given AST call node. 28 | 29 | Args: 30 | node (ast.Call): The AST call node. 31 | parameter (str): The name of the parameter to remove. 32 | 33 | Returns: 34 | None 35 | """ 36 | node.keywords = [kw for kw in node.keywords if kw.arg != parameter] 37 | 38 | 39 | def remove_parameter_from_initializer( 40 | tree: ast.AST, name: str, parameter: str 41 | ) -> ast.AST: 42 | """ 43 | Remove the specified parameter from the initializers with the given name 44 | in the AST tree. 45 | 46 | Args: 47 | tree (ast.AST): The AST tree. 48 | name (str): The name of the initializer. 49 | parameter (str): The name of the parameter to remove. 50 | 51 | Returns: 52 | ast.AST: The modified AST tree. 53 | """ 54 | for node in ast.walk(tree): 55 | if isinstance(node, ast.Call): 56 | string_name = ast.unparse(node.func) 57 | if string_name == name: 58 | remove_parameter_from_call(node, parameter) 59 | return tree 60 | 61 | 62 | def remove_parameter_from_initializers( 63 | tree: ast.AST, names: list[str], parameter: str 64 | ) -> ast.AST: 65 | """ 66 | Remove the specified parameter from the initializers with the given names 67 | in the AST tree. 68 | 69 | Args: 70 | tree (ast.AST): The AST tree. 71 | names (list[str]): The names of the initializers. 72 | parameter (str): The name of the parameter to remove. 73 | 74 | Returns: 75 | ast.AST: The modified AST tree. 76 | """ 77 | for node in ast.walk(tree): 78 | if isinstance(node, ast.Call): 79 | string_name = ast.unparse(node.func) 80 | if string_name in names: 81 | remove_parameter_from_call(node, parameter) 82 | return tree 83 | 84 | 85 | def remove_resolution_from_ctkslider(content: str) -> str: 86 | """ 87 | Remove the 'resolution' parameter from calls to ctk.CTkSlider or CTkSlider 88 | in the provided Python code. 89 | 90 | Args: 91 | content (str): The Python code content. 92 | 93 | Returns: 94 | str: The modified Python code without the 'resolution' parameter. 95 | """ 96 | tree = parsetree(content) 97 | slidernodes = [] 98 | for node in ast.walk(tree): 99 | if isinstance(node, ast.Call): 100 | string_name = ast.unparse(node.func) 101 | if string_name == "ctk.CTkSlider" or string_name == "CTkSlider": 102 | n = deepcopy(node) 103 | slidernodes.append(node) 104 | for node in slidernodes: 105 | remove_parameter_from_call(node, "resolution") 106 | return ast.unparse(tree) 107 | -------------------------------------------------------------------------------- /tk_to_ctk/templates.py: -------------------------------------------------------------------------------- 1 | help_template = f"""\ 2 | tk2ctk - Tkinter to CustomTkinter 3 | 4 | Description: \033[2m\033[3mtkinter to customtkinter file converter. \033[0m 5 | 6 | Usage: \033[2m\033[3mtk2ctk [file] [Options] \033[0m 7 | \033[2m\033[3mtk2ctk -m [file] [file2] [file3] ... \033[0m 8 | \033[2m\033[3mtk2ctk [file] -o [file] \033[0m 9 | 10 | Options: 11 | -h, --help \033[2m\033[3mShow this help message and exit\033[0m 12 | -o, --output \033[2m\033[3mDefine the output file\033[0m 13 | -v, --verbose \033[2m\033[3mOperate with higher verbosity level\033[0m 14 | -l, --listboxes \033[2m\033[3mConvert listboxes to custom listboxes\033[0m 15 | -m, --multiple \033[2m\033[3mConvert multiple target scripts\033[0m 16 | -e, --examples \033[2m\033[3mShow examples for flags and options\033[0m\n""" 17 | 18 | examples_template = f"""\ 19 | ┌─── Flag/Option ───── Example Usage ────────────────────────────┐ 20 | │ -h, --help → tk2ctk -h │ 21 | │ -o, --output → tk2ctk target.py -o newfile.py │ 22 | │ -v, --verbose → tk2ctk target.py -v │ 23 | │ -l, --listboxes → tk2ctk target.py -l │ 24 | │ -m, --multiple → tk2ctk -m target1.py file2.py pathway3.py │ 25 | │ -e, --examples → tk2ctk -e │ 26 | └──────────────────────────────────────────────────────────────────────┘\ 27 | """ 28 | 29 | 30 | def print_examples() -> None: 31 | print(examples_template) 32 | 33 | 34 | def print_help_screen() -> None: 35 | print(help_template) 36 | -------------------------------------------------------------------------------- /tk_to_ctk/test.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import filedialog 3 | import subprocess 4 | 5 | 6 | 7 | def search_file(): 8 | file_path = filedialog.askopenfilename(filetypes=[("Python files", "*.py")]) 9 | if file_path: 10 | with open(file_path, 'r') as file: 11 | content = file.read() 12 | text_box.delete(1.0, tk.END) 13 | text_box.insert(tk.END, content) 14 | 15 | def convert_file(): 16 | file_content = text_box.get(1.0, tk.END) 17 | # Perform file conversion here 18 | # For example, let's just print the content for demonstration 19 | print("Converted file content:") 20 | print(file_content) 21 | 22 | # Create the main window 23 | root = tk.Tk() 24 | root.title("Python File Converter") 25 | 26 | # Create a frame for buttons 27 | button_frame = tk.Frame(root) 28 | button_frame.pack(pady=10) 29 | 30 | # Create a Search button 31 | search_button = tk.Button(button_frame, text="Search File", command=search_file) 32 | search_button.pack(side=tk.LEFT, padx=5) 33 | 34 | # Create a Convert button 35 | convert_button = tk.Button(button_frame, text="Convert File", command=convert_file) 36 | convert_button.pack(side=tk.LEFT, padx=5) 37 | 38 | # Create a text box to display file content 39 | text_box = tk.Text(root, wrap="word", width=50, height=20) 40 | text_box.pack(pady=10) 41 | 42 | # Run the Tkinter event loop 43 | root.mainloop() -------------------------------------------------------------------------------- /tk_to_ctk/tk_to_ctk.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import subprocess 4 | import ast 5 | 6 | from .util import ( 7 | pip_str, 8 | get_listbox_source, 9 | has_listbox, 10 | classes_begin_index, 11 | print_warning, 12 | print_update, 13 | ) 14 | from .lists import tkinter_widgets, ctk_widgets 15 | from .templates import print_examples, print_help_screen 16 | 17 | # Ensure rich is installed here 18 | try: 19 | from rich.status import Status 20 | except: 21 | print(f"rich is not installed. Installing...") 22 | subprocess.run([pip_str, "install", "rich"], check=True) 23 | print(f"rich has been installed.") 24 | from rich.status import Status 25 | from rich.console import Console 26 | 27 | from .widget_replacer import WidgetReplacer 28 | from .tree import change_orient_to_orientation, change_textvariable_to_variable 29 | from .app_parser import get_parser 30 | from .util import parsetree 31 | from .call import CallArgumentNameChanger, CallArgumentRemover 32 | 33 | 34 | # Universal Keyword Translations from tkinter to customtkinter 35 | universal_kw_translation = { 36 | "fg": "fg_color", 37 | "orient": "orientation", 38 | "foreground": "fg_color", 39 | "background": "bg_color", 40 | "bg": "bg_color", 41 | "borderwidth": "border_width", 42 | "text_variable": "textvariable", 43 | "from": "from_", 44 | "labelanchor": "label_anchor" 45 | } 46 | 47 | # Keywords that are not translated at all from tkinter to customtkinter 48 | not_translated_kws = [ 49 | "selectmode", "blockcursor", "format", "sliderlength", "proxyrelief", "showhandle", "activestyle", "fg", "listvariable", "startline", "class", 50 | "show", "labelwidget", "direction", "root", "sliderrelief", "activebackground", "setgrid", "vcmd", "screenName", "endline", "tabs", "spacing3", 51 | "className", "default", "selectimage", "phase", "troughcolor", "bigincrement", "label", "tickinterval", "displaycolumns", "useTk", "readonlybackground", 52 | "buttonuprelief", "tearoffcommand", "overrelief", "insertunfocussed", "postcommand", "sashcursor", "cnf", "jump", "wraplength", "tabstyle", "sync", 53 | "container", "undo", "onvalue", "sashpad", "buttoncursor", "validatecommand", "columns", "maxundo", "opaqueresize", "colormap", "foreground", 54 | "disabledforeground", "buttonbackground", "proxyborderwidth", "sashwidth", "padding", "repeatdelay", "baseName", "digits", "activeborderwidth", 55 | "activerelief", "offrelief", "validate", "menu", "tristatevalue", "increment", "showvalue", "proxybackground", "justify", "spacing2", "sashrelief", 56 | "bitmap", "autoseparators", "style", "invcmd", "maximum", "type", "length", "screen", "resolution", "handlesize", "inactiveselectbackground", "aspect", 57 | "buttondownrelief", "disabledbackground", "exportselection", "invalidcommand", "handlepad", "tristateimage", "labelanchor", "spacing1", "visual", 58 | "indicatoron", "selectcolor", "exists", "tearoff", "elementborderwidth", "use", "activeforeground", "repeatinterval" 59 | ] 60 | # List of widgets available in customtkinter 61 | ctk_widget_names = [ 62 | "CTk", 63 | "CTkInputDialog", 64 | "CTkToplevel", 65 | "CTkButton", 66 | "CTkCheckBox", 67 | "CTkComboBox", 68 | "CTkEntry", 69 | "CTkFrame", 70 | "CTkLabel", 71 | "CTkOptionMenu", 72 | "CTkProgressBar", 73 | "CTkRadioButton", 74 | "CTkScrollableFrame", 75 | "CTkScrollbar", 76 | "CTkSegmentedButton", 77 | "CTkSlider", 78 | "CTkSwitch", 79 | "CTkTabview", 80 | "CTkFrames", 81 | "CTkTextbox", 82 | ] 83 | # list of widgets available in customtkinter when using different import styles 84 | ctk_qualified_widget_names = ["ctk." + x for x in ctk_widget_names] 85 | 86 | 87 | 88 | tk_widget_arguments = { 89 | "tk.Tk": ["screenName", "baseName", "className", "useTk", "sync", "use"], 90 | "ttk.Button": ["command", "default", "takefocus", "text", "textvariable", "underline", 91 | "width", "image", "compound", "padding", "state", "cursor", "style", "class"], 92 | "ttk.Checkbox": ["variable", "onvalue", "offvalue", "command", "takefocus", "text", "textvariable", 93 | "underline", "width", "image", "compound", "padding", "state", "cursor", "style", "class" ], 94 | "ttk.Combobox": ["height", "postcommand", "values", "exportselection", "font", "invalidcommand", "justify", 95 | "show", "state", "textvariable", "validate", "validatecommand", "width", "xscrollcommand", 96 | "foreground", "background", "takefocus", "cursor", "style", "class"], 97 | "ttk.Entry": ["exportselection", "font", "invalidcommand", "justify", "show", "state", "textvariable", 98 | "validate", "validatecommand", "width", "xscrollcommand", "foreground", "background", 99 | "takefocus", "cursor", "style"], 100 | "ttk.Label": ["background", "bg", "fg", "foreground", "font", "borderwidth", "relief", "anchor", "justify", 101 | "wraplength", "takefocus", "text", "textvariable", "underline", "width", "image", "compound", 102 | "padding", "state", "cursor", "style"], 103 | "ttk.LabeledScale": ["borderwidth", "padding", "relief", "width", "height", "takefocus", "cursor", "style"], 104 | "ttk.Labelframe": ["labelanchor", "text", "underline", "labelwidget", "borderwidth", "padding", "relief", 105 | "width", "height", "takefocus", "cursor", "style"], 106 | "ttk.Menubutton": ["menu", "direction", "takefocus", "text", "textvariable", "underline", "width", 107 | "image", "compound", "padding", "state", "cursor", "style"], 108 | "ttk.Notebook": ["height", "padding", "takefocus", "cursor", "style"], 109 | "ttk.Panedwindow": ["orient", "width", "height", "takefocus", "cursor", "style"], 110 | "ttk.Progressbar": ["orient", "length", "mode", "maximum", "value", "variable", "phase", "takefocus", "cursor", "style"], 111 | "ttk.Radiobutton": ["variable", "value", "command", "takefocus", "text", "textvariable", "underline", 112 | "width", "image", "compound", "padding", "state", "cursor", "style"], 113 | "ttk.Scale": ["orient", "length", "from", "to", "variable", "command", "takefocus", "cursor", "style"], 114 | "ttk.Scrollbar": ["command", "orient", "takefocus", "cursor", "style"], 115 | "ttk.Separator": ["orient", "takefocus", "cursor", "style"], 116 | "ttk.Spinbox": ["values", "from", "to", "increment", "format", "command", "wrap", "exportselection", "font", 117 | "invalidcommand", "justify", "show", "state", "textvariable", "validate", "validatecommand", 118 | "width", "xscrollcommand", "foreground", "background", "takefocus", "cursor", "style"], 119 | "ttk.Treeview": ["columns", "displaycolumns", "show", "selectmode", "height", "padding", "xscrollcommand", 120 | "yscrollcommand", "takefocus", "cursor", "style"], 121 | 122 | "tk.Canvas": ["background", "bd", "bg", "borderwidth", "closeenough", "confine", "cursor", "height", 123 | "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", 124 | "insertofftime", "insertontime", "insertwidth", "offset", "relief", "scrollregion", "selectbackground", 125 | "selectborderwidth", "selectforeground", "state", "takefocus", "width", "xscrollcommand", "xscrollincrement", 126 | "yscrollcommand", "yscrollincrement"], 127 | "tk.Listbox": ["activestyle", "background", "bd", "bg", "borderwidth", "cursor", "disabledforeground", 128 | "exportselection", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", 129 | "highlightthickness", "justify", "relief", "selectbackground", "selectborderwidth", "selectforeground", 130 | "selectmode", "setgrid", "state", "takefocus", "width", "xscrollcommand", "yscrollcommand", "listvariable"], 131 | "tk.Menu": ["activebackground", "activeborderwidth", "activeforeground", "background", "bd", "bg", "borderwidth", 132 | "cursor", "disabledforeground", "fg", "font", "foreground", "postcommand", "relief", "selectcolor", 133 | "takefocus", "tearoff", "tearoffcommand", "title", "type"], 134 | "tk.Text": ["autoseparators", "background", "bd", "bg", "blockcursor", "borderwidth", "cursor", "endline", 135 | "exportselection", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", 136 | "highlightthickness", "inactiveselectbackground", "insertbackground", "insertborderwidth", "insertofftime", 137 | "insertontime", "insertunfocussed", "insertwidth", "maxundo", "padx", "pady", "relief", "selectbackground", 138 | "selectborderwidth", "selectforeground", "setgrid", "spacing1", "spacing2", "spacing3", "startline", "state", 139 | "tabs", "tabstyle", "takefocus", "undo", "width", "wrap", "xscrollcommand", "yscrollcommand"], 140 | "tk.Toplevel": ["bd", "borderwidth", "class", "menu", "relief", "screen", "use", "background", "bg", "colormap", 141 | "container", "cursor", "height", "highlightbackground", "highlightcolor", "highlightthickness", 142 | "padx", "pady", "takefocus", "visual", "width"], 143 | "tk.Button": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", 144 | "command", "compound", "cursor", "default", "disabledforeground", "fg", "font", "foreground", "height", 145 | "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "overrelief", "padx", 146 | "pady", "relief", "repeatdelay", "repeatinterval", "state", "takefocus", "text", "textvariable", "underline", 147 | "width", "wraplength"], 148 | "tk.Checkbutton": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", "command", "compound", "cursor", "disabledforeground", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "indicatoron", "justify", "offrelief", "offvalue", "onvalue", "overrelief", "padx", "pady", "relief", "selectcolor", "selectimage", "state", "takefocus", "text", "textvariable", "tristateimage", "tristatevalue", "underline", "variable", "width", "wraplength"], 149 | "tk.Entry": ["background", "bd", "bg", "borderwidth", "cursor", "disabledbackground", "disabledforeground", "exportselection", "fg", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "invalidcommand", "invcmd", "justify", "readonlybackground", "relief", "selectbackground", "selectborderwidth", "selectforeground", "show", "state", "takefocus", "textvariable", "validate", "validatecommand", "vcmd", "width", "xscrollcommand"], 150 | "tk.Frame": ["bd", "borderwidth", "class", "relief", "background", "bg", "colormap", "container", "cursor", "height", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "takefocus", "visual", "width"], 151 | "tk.Label": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", "compound", "cursor", "disabledforeground", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "padx", "pady", "relief", "state", "takefocus", "text", "textvariable", "underline", "width", "wraplength"], 152 | "tk.Labelframe": ["bd", "borderwidth", "class", "fg", "font", "foreground", "labelanchor", "labelwidget", "relief", "text", "background", "bg", "colormap", "container", "cursor", "height", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "takefocus", "visual", "width"], 153 | "tk.Menubutton": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", "cursor", "direction", "disabledforeground", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "indicatoron", "justify", "menu", "padx", "pady", "relief", "compound", "state", "takefocus", "text", "textvariable", "underline", "width", "wraplength"], 154 | "tk.Message": ["anchor", "aspect", "background", "bd", "bg", "borderwidth", "cursor", "fg", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "justify", "padx", "pady", "relief", "takefocus", "text", "textvariable", "width"], 155 | "tk.OptionMenu": ["master", "variable", "value", "*", "**"], 156 | "tk.PanedWindow": ["background", "bd", "bg", "borderwidth", "cursor", "handlepad", "handlesize", "height", "opaqueresize", "orient", "proxybackground", "proxyborderwidth", "proxyrelief", "relief", "sashcursor", "sashpad", "sashrelief", "sashwidth", "showhandle", "width"], 157 | "tk.Radiobutton": ["activebackground", "activeforeground", "anchor", "background", "bd", "bg", "bitmap", "borderwidth", "command", "compound", "cursor", "disabledforeground", "fg", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "indicatoron", "justify", "offrelief", "overrelief", "padx", "pady", "relief", "selectcolor", "selectimage", "state", "takefocus", "text", "textvariable", "tristateimage", "tristatevalue", "underline", "value", "variable", "width", "wraplength"], 158 | "tk.Scale": ["activebackground", "background", "bigincrement", "bd", "bg", "borderwidth", "command", "cursor", "digits", "fg", "font", "foreground", "from", "highlightbackground", "highlightcolor", "highlightthickness", "label", "length", "orient", "relief", "repeatdelay", "repeatinterval", "resolution", "showvalue", "sliderlength", "sliderrelief", "state", "takefocus", "tickinterval", "to", "troughcolor", "variable", "width", ], 159 | "tk.Scrollbar": ["activebackground", "activerelief", "background", "bd", "bg", "borderwidth", "command", "cursor", "elementborderwidth", "highlightbackground", "highlightcolor", "highlightthickness", "jump", "orient", "relief", "repeatdelay", "repeatinterval", "takefocus", "troughcolor", "width"], 160 | "tk.Spinbox": ["activebackground", "background", "bd", "bg", "borderwidth", "buttonbackground", "buttoncursor", "buttondownrelief", "buttonuprelief", "command", "cursor", "disabledbackground", "disabledforeground", "exportselection", "fg", "font", "foreground", "format", "from", "highlightbackground", "highlightcolor", "highlightthickness", "increment", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "invalidcommand", "invcmd", "justify", "relief", "readonlybackground", "repeatdelay", "repeatinterval", "selectbackground", "selectborderwidth", "selectforeground", "state", "takefocus", "textvariable", "to", "validate", "validatecommand", "values", "vcmd", "width", "wrap", "xscrollcommand"], 161 | "tk.Variable": ["master", "value", "name"], 162 | "tk.BoolenVar": ["master", "value", "name"], 163 | "tk.DoubleVar": ["master", "value", "name"], 164 | "tk.IntVar": ["master", "value", "name"], 165 | "tk.StringVar": ["master", "value", "name"], 166 | "tk.BitmapImage": ["name", "cnf", "master", "**"], 167 | "tk.PhotoImage": ["name", "cnf", "master", "**"], 168 | "tk.filedialog.Directory": ["master", "**"], 169 | "tk.filedialog.Open": ["master", "**"], 170 | "tk.filedialog.SaveAs": ["master", "**"], 171 | "tk.colorchooser.Chooser": ["master", "**"], 172 | "ttk.Style": ["master"], 173 | "ttk.Font": ["root", "font", "name", "exists", "**"], 174 | } 175 | ctk_widget_arguments = { 176 | "ctk.CTkButton": ["master", "width", "height", "corner_radius", "border_width", "border_spacing", "fg_color", "hover_color", "border_color", "text_color", "text_color_disabled", "text", "font", "textvariable", "image", "state", "hover", "command", "compound", "anchor"], 177 | "ctk.CTk": ["fg_color"], 178 | "ctk.CTkInputDialog": ["title", "text", "fg_color", "button_hover_color", "button_text_color", "entry_fg_color", "entry_border_color", "entry_text_color" ], 179 | "ctk.CTkToplevel": ["fg_color"], 180 | "ctk.CTkCheckBox": ["master", "width", "height", "checkbox_width", "corner_radius", "border_width", "fg_color", "border_color", "text_color", "text", "textvariable", "font", "hover", "state", "variable", "offvalue" ], 181 | "ctk.CTkComboBox": ["master", "width", "height", "corner_radius", "border_width", "fg_color", "border_color", "button_color", "button_hover_color", "dropdown_fg_color", "dropdown_hover_color", "text_color", "font", "dropdown_font", "values", "hover", "state", "variable" ], 182 | "ctk.CTkEntry": ["master", "textvariable", "width", "height", "corner_radius", "fg_color", "text_color", "placeholder_text_color", "placeholder_text", "font", "state" ], 183 | "ctk.CTkFrame": ["master", "width", "height", "border_width", "fg_color", "border_color" ], 184 | "ctk.CTkLabel": ["master", "textvariable", "text", "width", "height", "corner_radius", "fg_color", "text_color", "font", "anchor", "compound", "padx", "pady" ], 185 | "ctk.CTkOptionMenu": ["master", "width", "height", "corner_radius", "fg_color", "button_color", "button_hover_color", "dropdown_fg_color", "dropdown_hover_color", "text_color", "font", "dropdown_font", "hover", "state", "variable", "values", "dynamic_resizing", "anchor" ], 186 | "ctk.CTkProgressBar": ["master", "width", "height", "border_width", "corner_radius", "fg_color", "border_color", "progress_color", "mode", "determinate_speed" ], 187 | "ctk.CTkRadioButton": ["master", "width", "height", "radiobutton_width", "radiobutton_height", "corner_radius", "border_width_unchecked", "border_width_checked", "fg_color", "border_color", "text_color", "text", "textvariable", "font", "hover", "state", "variable", "value" ], 188 | "ctk.CTkScrollableFrame": ["master", "width", "height", "corner_radius", "border_width", "fg_color", "border_color", "scrollbar_fg_color", "scrollbar_button_color", "scrollbar_button_hover_color", "label_fg_color", "label_text_color", "label_text", "label_font", "label_anchor" ], 189 | "ctk.CTkScrollbar": ["master", "width", "height", "corner_radius", "border_spacing", "fg_color", "button_color", "button_hover_color", "minimum_pixel_length", "hover" ], 190 | "ctk.CTkSegmentedButton": ["master", "width", "height", "corner_radius", "border_width", "fg_color", "selected_color", "selected_hover_color", "unselected_color", "unselected_hover_color", "text_color", "font", "values", "variable", "state", "dynamic_resizing" ], 191 | "ctk.CTkSlider": ["master", "variable", "width", "height", "border_width", "from_", "to", "fg_color", "progress_color", "border_color", "button_color", "button_hover_color", "state", "hover" ], 192 | "ctk.CTkSwitch": ["master", "width", "height", "switch_width", "switch_height", "corner_radius", "border_width", "fg_color", "border_color", "progress_color", "button_color", "button_hover_color", "text_color", "text", "textvariable", "font", "variable", "offvalue", "statemaster", "width", "height", "switch_width", "switch_height", "corner_radius", "border_width", "fg_color", "border_color", "progress_color", "button_color", "button_hover_color", "text_color", "text", "textvariable", "font", "variable", "offvalue", "state" ], 193 | "ctk.CTkTabview": ["master", "width", "height", "corner_radius", "border_width", "fg_color", "border_color", "segmented_button_fg_color", "segmented_button_selected_hover_color", "segmented_button_unselected_color", "text_color", "anchor", "state" ], 194 | "ctk.CTkTextbox": ["master", "width", "height", "corner_radius", "border_width", "border_spacing", "fg_color", "border_color", "text_color", "scrollbar_button_color", "scrollbar_button_hover_color", "font", "state", "wrap" ], 195 | "ctk.CTkFont": ["family", "size", "weight", "slant", "underline", "overstrike"], 196 | "ctk.CTkImage": ["light_image", "dark_image", "size"], 197 | "ctk.CTkCanvas": ["background", "bd", "bg", "borderwidth", "closeenough", "confine", "cursor", "height", "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "offset", "relief", "scrollregion", "selectbackground", "selectborderwidth", "selectforeground", "state", "takefocus", "width", "xscrollcommand", "xscrollincrement", "yscrollcommand", "yscrollincrement"], 198 | "ctk.Variable": ["master", "value", "name"], 199 | "ctk.BoolenVar": ["master", "value", "name"], 200 | "ctk.DoubleVar": ["master", "value", "name"], 201 | "ctk.IntVar": ["master", "value", "name"], 202 | "ctk.StringVar": ["master", "value", "name"], 203 | "ctk.filedialog.Directory": ["master", "**"], 204 | "ctk.filedialog.Open": ["master", "**"], 205 | "ctk.filedialog.SaveAs": ["master", "**"], 206 | "ctk.colorchooser.Chooser": ["master", "**"], 207 | "ctk.CTkVariable": ["master", "value", "name"], 208 | "ctk.CTkVariable": ["master", "value", "name"], 209 | "ctk.CTkBoolenVar": ["master", "value", "name"], 210 | "ctk.CTkDoubleVar": ["master", "value", "name"], 211 | "ctk.CTkIntVar": ["master", "value", "name"], 212 | "ctk.CTkStringVar": ["master", "value", "name"], 213 | "ctk.CTkPhotoImage": ["master", "light_image", "dark_image", "size"], 214 | } 215 | 216 | ctk_cant_have_these_keywords = { 217 | 'ctk.CTk': ['screenName', 'baseName', 'className', 'useTk', 'sync', 'use'], 218 | 'ctk.CTkButton': ['activebackground', 219 | 'activeforeground', 220 | 'bd', 221 | 'bitmap', 222 | 'cursor', 223 | 'default', 224 | 'disabledforeground', 225 | 'highlightbackground', 226 | 'highlightcolor', 227 | 'highlightthickness', 228 | 'justify', 229 | 'overrelief', 230 | 'padx', 231 | 'pady', 232 | 'relief', 233 | 'repeatdelay', 234 | 'repeatinterval', 235 | 'takefocus', 236 | 'underline', 237 | 'wraplength'], 238 | 'ctk.CTkCanvas': [], 239 | 'ctk.CTkCheckBox': ['activebackground', 240 | 'activeforeground', 241 | 'anchor', 242 | 'bd', 243 | 'bitmap', 244 | 'command', 245 | 'compound', 246 | 'cursor', 247 | 'disabledforeground', 248 | 'highlightbackground', 249 | 'highlightcolor', 250 | 'highlightthickness', 251 | 'image', 252 | 'indicatoron', 253 | 'justify', 254 | 'offrelief', 255 | 'onvalue', 256 | 'overrelief', 257 | 'padx', 258 | 'pady', 259 | 'relief', 260 | 'selectcolor', 261 | 'selectimage', 262 | 'takefocus', 263 | 'tristateimage', 264 | 'tristatevalue', 265 | 'underline', 266 | 'wraplength'], 267 | 'ctk.CTkComboBox': ['postcommand', 268 | 'exportselection', 269 | 'invalidcommand', 270 | 'justify', 271 | 'show', 272 | 'textvariable', 273 | 'validate', 274 | 'validatecommand', 275 | 'xscrollcommand', 276 | 'takefocus', 277 | 'cursor', 278 | 'style', 279 | 'class'], 280 | 'ctk.CTkDoubleVar': [], 281 | 'ctk.CTkEntry': ['bd', 282 | 'cursor', 283 | 'disabledbackground', 284 | 'disabledforeground', 285 | 'exportselection', 286 | 'highlightbackground', 287 | 'highlightcolor', 288 | 'highlightthickness', 289 | 'insertbackground', 290 | 'insertborderwidth', 291 | 'insertofftime', 292 | 'insertontime', 293 | 'insertwidth', 294 | 'invalidcommand', 295 | 'invcmd', 296 | 'justify', 297 | 'readonlybackground', 298 | 'relief', 299 | 'selectbackground', 300 | 'selectborderwidth', 301 | 'selectforeground', 302 | 'show', 303 | 'takefocus', 304 | 'validate', 305 | 'validatecommand', 306 | 'vcmd', 307 | 'xscrollcommand'], 308 | 'ctk.CTkFrame': ['bd', 309 | 'class', 310 | 'relief', 311 | 'colormap', 312 | 'container', 313 | 'cursor', 314 | 'highlightbackground', 315 | 'highlightcolor', 316 | 'highlightthickness', 317 | 'padx', 318 | 'pady', 319 | 'takefocus', 320 | 'visual'], 321 | 'ctk.CTkImage': ['name', 'cnf', 'master', '**'], 322 | 'ctk.CTkIntVar': [], 323 | 'ctk.CTkLabel': ['activebackground', 324 | 'activeforeground', 325 | 'bd', 326 | 'bitmap', 327 | 'cursor', 328 | 'disabledforeground', 329 | 'highlightbackground', 330 | 'highlightcolor', 331 | 'highlightthickness', 332 | 'image', 333 | 'justify', 334 | 'relief', 335 | 'state', 336 | 'takefocus', 337 | 'underline', 338 | 'wraplength'], 339 | 'ctk.CTkOptionMenu': ['value', '*', '**'], 340 | 'ctk.CTkPhotoImage': ['name', 'cnf', '**'], 341 | 'ctk.CTkProgressBar': ['length', 342 | 'maximum', 343 | 'value', 344 | 'variable', 345 | 'phase', 346 | 'takefocus', 347 | 'cursor', 348 | 'style'], 349 | 'ctk.CTkRadioButton': ['activebackground', 350 | 'activeforeground', 351 | 'anchor', 352 | 'bd', 353 | 'bitmap', 354 | 'command', 355 | 'compound', 356 | 'cursor', 357 | 'disabledforeground', 358 | 'highlightbackground', 359 | 'highlightcolor', 360 | 'highlightthickness', 361 | 'image', 362 | 'indicatoron', 363 | 'justify', 364 | 'offrelief', 365 | 'overrelief', 366 | 'padx', 367 | 'pady', 368 | 'relief', 369 | 'selectcolor', 370 | 'selectimage', 371 | 'takefocus', 372 | 'tristateimage', 373 | 'tristatevalue', 374 | 'underline', 375 | 'wraplength'], 376 | 'ctk.CTkScrollableFrame': ['text', 377 | 'underline', 378 | 'labelwidget', 379 | 'padding', 380 | 'relief', 381 | 'takefocus', 382 | 'cursor', 383 | 'style'], 384 | 'ctk.CTkScrollbar': ['activebackground', 385 | 'activerelief', 386 | 'bd', 387 | 'command', 388 | 'cursor', 389 | 'elementborderwidth', 390 | 'highlightbackground', 391 | 'highlightcolor', 392 | 'highlightthickness', 393 | 'jump', 394 | 'relief', 395 | 'repeatdelay', 396 | 'repeatinterval', 397 | 'takefocus', 398 | 'troughcolor'], 399 | 'ctk.CTkSlider': ['activebackground', 400 | 'bigincrement', 401 | 'bd', 402 | 'command', 403 | 'cursor', 404 | 'digits', 405 | 'font', 406 | 'highlightbackground', 407 | 'highlightcolor', 408 | 'highlightthickness', 409 | 'label', 410 | 'length', 411 | 'relief', 412 | 'repeatdelay', 413 | 'repeatinterval', 414 | 'resolution', 415 | 'showvalue', 416 | 'sliderlength', 417 | 'sliderrelief', 418 | 'takefocus', 419 | 'tickinterval', 420 | 'troughcolor'], 421 | 'ctk.CTkStringVar': [], 422 | 'ctk.CTkTabview': ['takefocus', 'cursor', 'style'], 423 | 'ctk.CTkTextbox': ['autoseparators', 424 | 'bd', 425 | 'blockcursor', 426 | 'cursor', 427 | 'endline', 428 | 'exportselection', 429 | 'highlightbackground', 430 | 'highlightcolor', 431 | 'highlightthickness', 432 | 'inactiveselectbackground', 433 | 'insertbackground', 434 | 'insertborderwidth', 435 | 'insertofftime', 436 | 'insertontime', 437 | 'insertunfocussed', 438 | 'insertwidth', 439 | 'maxundo', 440 | 'padx', 441 | 'pady', 442 | 'relief', 443 | 'selectbackground', 444 | 'selectborderwidth', 445 | 'selectforeground', 446 | 'setgrid', 447 | 'spacing1', 448 | 'spacing2', 449 | 'spacing3', 450 | 'startline', 451 | 'tabs', 452 | 'tabstyle', 453 | 'takefocus', 454 | 'undo', 455 | 'xscrollcommand', 456 | 'yscrollcommand'], 457 | 'ctk.CTkToplevel': ['bd', 458 | 'class', 459 | 'menu', 460 | 'relief', 461 | 'screen', 462 | 'use', 463 | 'colormap', 464 | 'container', 465 | 'cursor', 466 | 'height', 467 | 'highlightbackground', 468 | 'highlightcolor', 469 | 'highlightthickness', 470 | 'padx', 471 | 'pady', 472 | 'takefocus', 473 | 'visual', 474 | 'width'], 475 | 'ctk.CTkVariable': [] 476 | } 477 | all_unused_params = [] 478 | [all_unused_params.extend(value) for value in ctk_cant_have_these_keywords.values()] 479 | 480 | 481 | 482 | 483 | Gverbose = False 484 | 485 | ################################################## 486 | # FUNCTIONS 487 | ################################################## 488 | 489 | def verbose_print(string: str) -> None: 490 | global Gverbose 491 | if Gverbose: 492 | print(string) 493 | 494 | 495 | def fix_orient_and_textvar_calls(file_path: str) -> None: 496 | """ 497 | Description: 498 | Takes a file path and replaces all the 499 | 'orient="' -> orientation=" and 500 | "textvariable" to "variable" parameters 501 | for compatibility with customtkinter. 502 | Arguments: 503 | file_path (str): path to the file 504 | Returns: 505 | None 506 | """ 507 | with open(file_path, "r", encoding="utf-8", errors="ignore") as file: 508 | content = file.read() 509 | source = change_textvariable_to_variable(content) 510 | source = change_orient_to_orientation(source) 511 | with open(file_path, "w", encoding="utf-8") as file: 512 | file.write(source) 513 | 514 | 515 | def replace_bg_with_bg_color_in_file(file_path: str) -> None: 516 | """ 517 | Description: 518 | Takes a file path and replaces all the 'bg="red"' -> bg_color="red" 519 | parameters for compatibility with customtkinter. 520 | Arguments: 521 | file_path (str): path to the file 522 | Returns: 523 | None 524 | """ 525 | global Gverbose 526 | 527 | with open(file_path, "r", encoding="utf-8", errors="ignore") as file: 528 | content = file.read() 529 | cont = content.replace(", bg=", ", bg_color=").replace(", bg =", ", bg_color =") 530 | with open(file_path, "w", encoding="utf-8") as file: 531 | file.write(cont) 532 | 533 | 534 | def replace_fg_with_fg_color_in_file(file_path: str) -> None: 535 | """ 536 | Description: 537 | Takes a file path and replaces all the 'fg="red"' -> fg_color="red" 538 | parameters for compatibility with customtkinter. 539 | Arguments: 540 | file_path (str): path to the file 541 | Returns: 542 | None 543 | """ 544 | verbose_print(f"Reading {file_path}...") 545 | with open(file_path, "r", encoding="utf-8", errors="ignore") as file: 546 | content = file.read() 547 | verbose_print("Replacing fg in content...") 548 | cont = content.replace(", fg=", ", fg_color=").replace(", fg =", ", fg_color =") 549 | verbose_print(f"Writing content to file : {file_path}...") 550 | with open(file_path, "w", encoding="utf-8") as file: 551 | file.write(cont) 552 | 553 | 554 | def replace_meta_in_file(file_path: str) -> None: 555 | """ 556 | Description: 557 | Replace all the tk meta class or base classes 558 | with custom tkinter ones. 559 | Arguments: 560 | file_path (str): path to file to be replaced 561 | Returns: 562 | None 563 | """ 564 | with open(file_path, "r", encoding="utf-8", errors="ignore") as file: 565 | content = file.read() 566 | verbose_print(f"{file_path} read.") 567 | cont = content.replace(r"(tk.Tk):", r"(ctk.CTk):") 568 | verbose_print("meta and base tk classes replaced...") 569 | for index, widget in enumerate(tkinter_widgets): 570 | verbose_print(f"finding base class widget {widget}...") 571 | cont = cont.replace(f"(tk.{widget}):", f"(ctk.{ctk_widgets[index]}):") 572 | cont = cont.replace("(Tk):", "(ctk.CTk):") 573 | cont = cont.replace("Tk()", "ctk.CTk()") 574 | verbose_print(f"Writing new content to {file_path}") 575 | with open(file_path, "w", encoding="utf-8") as file: 576 | file.write(cont) 577 | 578 | 579 | def replace_config_with_configure(file_path: str) -> None: 580 | """ 581 | Description: 582 | Takes a file path and replaces all the widget.config() -> widget.configure() 583 | methods for compatibility with customtkinter. 584 | Arguments: 585 | file_path (str): path to the file 586 | Returns: 587 | None 588 | """ 589 | verbose_print(f"Reading {file_path}") 590 | with open(file_path, "r", encoding="utf-8", errors="ignore") as file: 591 | content = file.read() 592 | verbose_print(f"Read {file_path}") 593 | verbose_print(f"Substituting .config --> .configure") 594 | modified_content = re.sub(r"\.config\(", ".configure(", content) 595 | verbose_print(f"Writing {file_path}") 596 | with open(file_path, "w", encoding="utf-8") as file: 597 | file.write(modified_content) 598 | 599 | 600 | def find_errs(file_path: str) -> None: 601 | """ 602 | Description: 603 | Find errors in the tk-ctk psuedo code. Takes a python like code and 604 | ensures that all errors are corrected and converted. 605 | Arguments: 606 | file_path (str): path to file to find errors in 607 | Returns: 608 | None 609 | """ 610 | verbose_print(f"Reading lines: {file_path}") 611 | with open(file_path, "r", encoding="utf-8") as file: 612 | lines = file.readlines() 613 | retv = [] 614 | 615 | verbose_print("Substituting by line....") 616 | for line in lines: 617 | verbose_print("old line: \n" + line) 618 | l = re.sub(r"ttk.ctk.", r"ctk.", line) 619 | l2 = re.sub(r"tk.ctk.", r"ctk.", l) 620 | l3 = re.sub(r".CTkText", r".CTkTextbox", l2) 621 | l4 = re.sub(r".CTkRadiobutton", r".CTkRadioButton", l3) 622 | # CTkCheckButton 623 | l5 = re.sub(r".CTkCheckbutton", r".CTkCheckBox", l4) 624 | l6 = re.sub(r".CTkScale", r".CTkSlider", l5) 625 | l7 = l6 626 | verbose_print(f"new line: \n" + l7) 627 | retv.append(l7) 628 | 629 | verbose_print(f" Writing {file_path}") 630 | with open(file_path, "w", encoding="utf-8") as wfile: 631 | for l in retv: 632 | wfile.write(l) 633 | 634 | 635 | def rewrite_listboxes(filepath: str) -> None: 636 | """ 637 | Description: 638 | Add the listbox class for custom tkinter 639 | and convert all the tk.Listbox -> CTkListbox. 640 | Arguments: 641 | filepath (str): path to file to be rewritten 642 | Returns: 643 | None 644 | """ 645 | final_lines = [] 646 | # does the file even have listboxes? 647 | if has_listbox(filepath) == True: 648 | 649 | # get the source lines to add to for the ListBox class 650 | listbox_addition = get_listbox_source() 651 | # read the source lines 652 | with open(filepath, "r") as r: 653 | source_lines = r.readlines() 654 | # determine where the classes should begin 655 | class_index = classes_begin_index(source_lines) 656 | 657 | # inject the class at the class index 658 | for index, line in enumerate(source_lines): 659 | if index == class_index: 660 | for source_line in listbox_addition: 661 | final_lines.append(source_line) 662 | final_lines.append(line) 663 | 664 | # write the lines out 665 | with open(filepath, "w") as w: 666 | w.writelines(final_lines) 667 | # read the new content from the class injection 668 | with open(filepath, "r") as r: 669 | content = r.read() 670 | # substitute the listboxes for the CTkListbox 671 | content2 = re.sub(r"tk\.Listbox(?!\()", r" CTkListbox", content) 672 | content3 = re.sub( 673 | r"customtkinter\.ThemeManager\.", "ctk.ThemeManager.", content2 674 | ) 675 | # write the final content 676 | with open(filepath, "w") as w: 677 | w.write(content3) 678 | 679 | def remove_relief_from_CTkButton(content: str) -> str: 680 | tree = parsetree(content) 681 | for node in ast.walk(tree): 682 | if isinstance(node, ast.Call): 683 | string_name = ast.unparse(node.func) 684 | if string_name == "ctk.CTkButton" or string_name == "CTkButton": 685 | node.keywords = [kw for kw in node.keywords if kw.arg != "relief"] 686 | return ast.unparse(tree) 687 | 688 | def remove_parameter_from_calls_with_names(node_names:list[str], content: str, parameter: str) -> str: 689 | tree = parsetree(content) 690 | for node in ast.walk(tree): 691 | if isinstance(node, ast.Call): 692 | string_name = ast.unparse(node.func) 693 | for name in node_names: 694 | if name in string_name: 695 | node.keywords = [kw for kw in node.keywords if kw.arg != parameter] 696 | break 697 | 698 | return ast.unparse(tree) 699 | 700 | def change_parameter_in_calls_with_names(node_names:list[str], content: str, parameter: str, new_parameter: str) -> str: 701 | tree = parsetree(content) 702 | for node in ast.walk(tree): 703 | if isinstance(node, ast.Call): 704 | string_name = ast.unparse(node.func) 705 | for name in node_names: 706 | if name in string_name: 707 | node.keywords = [kw for kw in node.keywords if kw.arg != parameter] 708 | node.keywords.append(ast.keyword(arg=new_parameter, value=node.keywords[0].value)) 709 | break 710 | 711 | return ast.unparse(tree) 712 | 713 | def change_call_func_name(func_name:str, new_name:str, content: str) -> str: 714 | tree = parsetree(content) 715 | for node in ast.walk(tree): 716 | if isinstance(node, ast.Call): 717 | string_name = ast.unparse(node.func) 718 | if string_name == func_name: 719 | if isinstance(node.func, ast.Name): 720 | node.func.id = new_name 721 | elif isinstance(node.func, ast.Attribute): 722 | attr, value = new_name.split(".", 1) 723 | node.func.attr = attr 724 | node.func.value.id = value 725 | 726 | return ast.unparse(tree) 727 | 728 | def remove_parameter_for_call(parameter:str, call_name:str, content: str) -> str: 729 | tree = parsetree(content) 730 | for node in ast.walk(tree): 731 | if isinstance(node, ast.Call): 732 | string_name = ast.unparse(node.func) 733 | if string_name == call_name: 734 | node.keywords = [kw for kw in node.keywords if kw.arg != parameter] 735 | 736 | return ast.unparse(tree) 737 | 738 | 739 | def remove_keyword_from_line(line:str, keyword:str) -> str: 740 | start_kw = line.index(keyword) 741 | end_kw = start_kw + len(keyword) 742 | length = len(line) - 1 743 | c = end_kw 744 | while c < length: 745 | 746 | char = line[c] 747 | c+=1 748 | 749 | if char == ")": 750 | c+=1 751 | break 752 | 753 | elif char == ",": 754 | c+=1 755 | break 756 | 757 | elif char == "(": 758 | c+=1 759 | while char != ")": 760 | char = line[c] 761 | c+=1 762 | 763 | elif char == "'" or char == '"': 764 | 765 | save = char 766 | c+=1 767 | while char != save: 768 | char = line[c] 769 | c+=1 770 | 771 | elif char == "[": 772 | 773 | save = "]" 774 | c+=1 775 | while char != save: 776 | char = line[c] 777 | c+=1 778 | 779 | xline = str(line[:start_kw] + line[c:-1]).strip() 780 | if xline.endswith(","): 781 | return xline[:-1] + ")" 782 | 783 | if xline.endswith(")"): 784 | return xline 785 | else: 786 | return line[:start_kw] + line[c:-1] + ")" 787 | 788 | 789 | 790 | 791 | def make_custom_tkinter( 792 | input_file: str, 793 | output_filename: str, 794 | convert_listboxes: bool = False, 795 | verbose: str = False, 796 | ) -> None: 797 | """ 798 | Description: 799 | Create a customtkinter file from a tkinter file 800 | Arguments: 801 | input_file (str): the tkinter file to use 802 | output_filename (str): the desired output file 803 | Returns: 804 | None 805 | """ 806 | con = Console() 807 | with Status(f"Analyzing {input_file}...") as status: 808 | verbose_print("Creating instance of WidgetReplacer class...") 809 | wr = WidgetReplacer(input_file, output_filename) 810 | 811 | verbose_print("iterating through tkinter widgets to add ctk regex patterns...") 812 | for widget in tkinter_widgets: 813 | ctk_widget = f"ctk.CTk{widget}" 814 | verbose_print(widget) 815 | wr.add_findable(f" {widget}(", f" {ctk_widget}(") 816 | wr.add_findable(f" {widget}(", f" {ctk_widget}(") 817 | wr.add_findable(f"{widget}(", f"{ctk_widget}(") 818 | wr.add_findable(f"{widget}, ", "") 819 | wr.add_findable(f"{widget},", f"{ctk_widget},") 820 | wr.add_findable(f" = {widget}(", f" = {ctk_widget}(") 821 | wr.add_findable(f"={widget}(", f"={ctk_widget}(") 822 | wr.add_findable(f": {widget} ", f": {ctk_widget} ") 823 | wr.add_findable(f":{widget},", f":{ctk_widget},") 824 | wr.add_findable(f":{widget}", f":{ctk_widget}") 825 | status.update("tk.Widget -> ctk.CTkWidget") 826 | 827 | verbose_print("iterating through tkinter widgets to add tk regex patterns...") 828 | for widg in tkinter_widgets: 829 | widget = "tk." + widg 830 | verbose_print(widget) 831 | ctk_widget = f"ctk.CTk{widg}" 832 | wr.add_findable(f"{widget}(", f"{ctk_widget}(") 833 | wr.add_findable(f" {widget}(", f" {ctk_widget}(") 834 | wr.add_findable(f" {widget}(", f" {ctk_widget}(") 835 | wr.add_findable(f"{widget}, ", f"") 836 | wr.add_findable(f"{widget},", f"{ctk_widget},") 837 | wr.add_findable(f" = {widget}(", f" = {ctk_widget}(") 838 | wr.add_findable(f"={widget}(", f"={ctk_widget}(") 839 | wr.add_findable(f": {widget} ", f": {ctk_widget} ") 840 | wr.add_findable(f":{widget},", f":{ctk_widget},") 841 | wr.add_findable(f":{widget}", f":{ctk_widget}") 842 | status.update(" ttk.Widget -> ctk.CTkWidget") 843 | 844 | verbose_print("Iterating through ttk widgets for regex patterns...") 845 | for widg in tkinter_widgets: 846 | widget: str = "ttk." + widg 847 | ctk_widget: str = f"ctk.CTk{widg}" 848 | verbose_print(widget) 849 | wr.add_findable("{0}(".format(widget), "{0}(".format(ctk_widget)) 850 | wr.add_findable("{0}, ".format(widget), "") 851 | wr.add_findable(widget + ",", "{0},".format(ctk_widget)) 852 | wr.add_findable(" = {0}(".format(widget), " = {0}(".format(ctk_widget)) 853 | wr.add_findable("={0}(".format(widget), "={0}(".format(ctk_widget)) 854 | wr.add_findable(": {0} ".format(widget), ": {0} ".format(ctk_widget)) 855 | wr.add_findable(":{0},".format(widget), ":{0},".format(ctk_widget)) 856 | wr.add_findable(":{0}".format(widget), ":{0}".format(ctk_widget)) 857 | 858 | verbose_print("Replacing all widgets now...") 859 | status.update("Replacing all widgets...") 860 | wr.replace_widgets() 861 | 862 | verbose_print("Double checking widgets now...") 863 | status.update("Double checking constants...") 864 | wr.double_check() 865 | 866 | status.update("finding .config/.configure...") 867 | verbose_print("Finding .config/.configure to replace...") 868 | replace_config_with_configure(file_path=output_filename) 869 | 870 | status.update("finding bg/bg_color...") 871 | verbose_print("finding bg parameter to convert to bg_color...") 872 | replace_bg_with_bg_color_in_file(file_path=output_filename) 873 | 874 | status.update("finding fg/fg_color...") 875 | verbose_print("finding fg parameter to convert to fg_color") 876 | replace_fg_with_fg_color_in_file(file_path=output_filename) 877 | 878 | status.update(" finding meta class options...") 879 | verbose_print("finding meta class tkinter objects...") 880 | replace_meta_in_file(file_path=output_filename) 881 | 882 | status.update("Checking for errors...") 883 | verbose_print("checking for errors...") 884 | find_errs(file_path=output_filename) 885 | 886 | if convert_listboxes: 887 | status.update(" fixing the listboxes...") 888 | verbose_print("Converting listboxes as specified by listbox flag...") 889 | rewrite_listboxes(filepath=output_filename) 890 | 891 | ## have to be try statements because it uses ast.parse and ast.unparse 892 | #try: 893 | # status.update("Fixing textvariable and orient call parameters...") 894 | # fix_orient_and_textvar_calls(filepath=output_filename) 895 | #except: 896 | # verbose_print("Could not fix 'textvariable' and 'orient' call parameters") 897 | # 898 | #try: 899 | # status.update("Fixing resolution parameters for CTkSlider...") 900 | # remove_resolution_parameter_for_ctk_slider(filepath=output_filename) 901 | #except: 902 | # verbose_print("Could not remove resolution parameter from CTkSlider") 903 | 904 | 905 | with open(output_filename, "r", errors="ignore") as f: 906 | source = f.read() 907 | 908 | 909 | try: 910 | tree = ast.parse(source) 911 | except SyntaxError: 912 | verbose_print("SyntaxError") 913 | con.print("Cannot Parse file!") 914 | exit(1) 915 | 916 | con.print("Fixing call parameters...") 917 | call_argument_changer = CallArgumentNameChanger() 918 | call_argument_remover = CallArgumentRemover() 919 | status.update("Fixing call parameters...") 920 | 921 | # Iterate through all ctk widgets and change their call parameters 922 | for ctkwidget in ctk_widgets: 923 | # Add universal keyword translations 924 | for oldkw, newkw in universal_kw_translation.items(): 925 | call_argument_changer.add_argument_name_change(ctkwidget, oldkw, newkw) 926 | 927 | if ctk_cant_have_these_keywords.get(ctkwidget, None): 928 | # Add specific keyword removals for each widget 929 | for kw in ctk_cant_have_these_keywords[ctkwidget]: 930 | call_argument_remover.add_keyword_removal(ctkwidget, kw) 931 | 932 | tree = call_argument_changer.visit(tree) 933 | tree = call_argument_remover.visit(tree) 934 | 935 | 936 | 937 | print("Unparsing tree...") 938 | status.update("Unparsing tree...") 939 | try: 940 | source = ast.unparse(tree) 941 | except: 942 | verbose_print("Could not unparse tree") 943 | raise SyntaxError("Could not unparse tree") 944 | finally: 945 | source = source.replace("from customtkinter import \n", "") 946 | source = source.replace("ctk.CTkMessage(", "tk.Message(") 947 | source = source.replace("import tkinter as tk", "import tkinter as tk\n") 948 | 949 | with open(output_filename, "w", errors="ignore") as f: 950 | f.write(source) 951 | 952 | with open(output_filename, "r", errors="ignore") as f: 953 | source = f.read() 954 | 955 | # remove banned keywords 956 | lines = source.split("\n") 957 | new_lines = [] 958 | count = 0 959 | for line in lines: 960 | for ctkwidget in ctk_qualified_widget_names: 961 | if line.find(ctkwidget+"(") != -1: 962 | for bannedkw in ctk_cant_have_these_keywords[ctkwidget]: 963 | if bannedkw + "=" in line: 964 | count+=1 965 | line = remove_keyword_from_line(line, bannedkw) 966 | break 967 | 968 | new_lines.append(line) 969 | 970 | con.print(f" {count} keyword(s) removed") 971 | 972 | source = "\n".join(new_lines) 973 | 974 | con.print(f" {source.count('ctk.CTk')} widgets replaced!") 975 | 976 | with open(output_filename, "w") as f: 977 | f.write(source) 978 | 979 | verbose_print(output_filename) 980 | verbose_print("done.") 981 | 982 | 983 | def input_filename_to_output_filename(input_filename: str) -> str: 984 | return os.path.join( 985 | os.getcwd(), "customtkinter_" + os.path.basename(str(input_filename)) 986 | ) 987 | 988 | 989 | def main(): 990 | global Gverbose 991 | 992 | parser = get_parser() 993 | 994 | try: 995 | args = parser.parse_args() 996 | except: 997 | print_warning(" -m/--multiple flag expected at least one argument") 998 | return 999 | 1000 | Gverbose = args.Verbose 1001 | verbose_print("Checking Flags...") 1002 | 1003 | if args.Examples: 1004 | print_examples() 1005 | return 1006 | 1007 | if args.Target == None and args.Multiple == []: 1008 | print_help_screen() 1009 | print_warning(" You must specify a target file to convert....") 1010 | return 1011 | 1012 | if args.Help: 1013 | verbose_print("Help activated...") 1014 | parser.print_help() 1015 | 1016 | if args.Listboxes: 1017 | verbose_print("Listbox Converter Activated") 1018 | 1019 | if args.Output != None: 1020 | verbose_print(f"Output file specified: {args.Output}") 1021 | 1022 | elif args.Output == None and args.Target != None: 1023 | verbose_print("Output not specified, making one from input file") 1024 | args.Output = input_filename_to_output_filename(args.Target) 1025 | 1026 | if args.Multiple != []: 1027 | verbose_print("Multiple conversions underway...") 1028 | 1029 | if args.Output != None: 1030 | verbose_print( 1031 | "Multiple flag used with outfile flag, defaulting to name generator..." 1032 | ) 1033 | print_warning("Cant specify output file with multiple conversions") 1034 | 1035 | for index, item in enumerate(args.Multiple): 1036 | verbose_print(f"Conversion {index+1} : {item}") 1037 | if os.path.exists(item): 1038 | output = input_filename_to_output_filename(item) 1039 | make_custom_tkinter( 1040 | input_file=item, 1041 | output_filename=output, 1042 | convert_listboxes=args.Listboxes, 1043 | verbose=args.Verbose, 1044 | ) 1045 | print_update(f"Finished {output}") 1046 | else: 1047 | print_warning(f"Cant locate {item}") 1048 | return 1049 | else: 1050 | verbose_print("Single Target conversion underway...") 1051 | try: 1052 | if os.path.exists(str(args.Target)): 1053 | make_custom_tkinter( 1054 | input_file=args.Target, 1055 | output_filename=args.Output, 1056 | convert_listboxes=args.Listboxes, 1057 | verbose=args.Verbose, 1058 | ) 1059 | fn = args.Output if args.Output != None else args.Target 1060 | print_update(f"Finished {fn}") 1061 | return 1062 | except AttributeError: 1063 | 1064 | print_help_screen() 1065 | print_warning("Must specify a Target to convert...") 1066 | return 1067 | 1068 | except FileNotFoundError: 1069 | 1070 | trypath = os.path.join(os.getcwd(), os.path.basename(str(args.Target))) 1071 | if os.path.exists(trypath): 1072 | make_custom_tkinter( 1073 | input_file=trypath, 1074 | output_filename=args.Output, 1075 | convert_listboxes=args.Listboxes, 1076 | verbose=args.Verbose, 1077 | ) 1078 | 1079 | else: 1080 | print_warning(f"Could not find file {args.Target}") 1081 | return 1082 | 1083 | 1084 | if __name__ == "__main__": 1085 | main() 1086 | -------------------------------------------------------------------------------- /tk_to_ctk/tree.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from typing import List, Tuple 3 | from .util import parsetree 4 | 5 | 6 | def change_textvariable_to_variable(source_code: str) -> str: 7 | """Change occurrences of the parameter 'textvariable' to 'variable' in function calls and class instantiations. 8 | 9 | Args: 10 | source_code (str): The source code to modify. 11 | 12 | Returns: 13 | str: The modified source code with 'textvariable' replaced by 'variable'. 14 | """ 15 | 16 | class TextVariableVisitor(ast.NodeTransformer): 17 | """AST visitor to modify occurrences of 'textvariable' to 'variable'.""" 18 | 19 | def visit_Call(self, node: ast.Call) -> ast.Call: 20 | """Visit function calls and replace 'textvariable' keyword argument with 'variable'.""" 21 | if isinstance(node.func, ast.Name): 22 | if node.keywords: 23 | for keyword in node.keywords: 24 | if ( 25 | isinstance(keyword, ast.keyword) 26 | and keyword.arg == "textvariable" 27 | ): 28 | keyword.arg = "variable" 29 | return node 30 | 31 | def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: 32 | """Visit class definitions and replace 'textvariable' keyword argument with 'variable'.""" 33 | for child in node.body: 34 | if isinstance(child, ast.Expr) and isinstance(child.value, ast.Call): 35 | call = child.value 36 | if isinstance(call.func, ast.Name): 37 | if call.keywords: 38 | for keyword in call.keywords: 39 | if ( 40 | isinstance(keyword, ast.keyword) 41 | and keyword.arg == "textvariable" 42 | ): 43 | keyword.arg = "variable" 44 | return node 45 | 46 | # Parse the source code into an AST 47 | tree = parsetree(source_code) 48 | 49 | # Transform the AST with the TextVariableVisitor 50 | transformer = TextVariableVisitor() 51 | transformed_tree = transformer.visit(tree) 52 | 53 | # Convert the AST back to source code 54 | modified_source_code = ast.unparse(transformed_tree) 55 | 56 | return modified_source_code 57 | 58 | 59 | def change_orient_to_orientation(source_code: str) -> str: 60 | """Change occurrences of the parameter 'orient' to 'orientation' in function calls and class instantiations. 61 | 62 | Args: 63 | source_code (str): The source code to modify. 64 | 65 | Returns: 66 | str: The modified source code with 'orient' replaced by 'orientation'. 67 | """ 68 | 69 | class OrientVisitor(ast.NodeTransformer): 70 | """AST visitor to modify occurrences of 'orient' to 'orientation'.""" 71 | 72 | def visit_Call(self, node: ast.Call) -> ast.Call: 73 | """Visit function calls and replace 'orient' keyword argument with 'orientation'.""" 74 | if isinstance(node.func, ast.Name): 75 | if node.keywords: 76 | for keyword in node.keywords: 77 | if isinstance(keyword, ast.keyword) and keyword.arg == "orient": 78 | keyword.arg = "orientation" 79 | return node 80 | 81 | def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: 82 | """Visit class definitions and replace 'orient' keyword argument with 'orientation'.""" 83 | for child in node.body: 84 | if isinstance(child, ast.Expr) and isinstance(child.value, ast.Call): 85 | call = child.value 86 | if isinstance(call.func, ast.Name): 87 | if call.keywords: 88 | for keyword in call.keywords: 89 | if ( 90 | isinstance(keyword, ast.keyword) 91 | and keyword.arg == "orient" 92 | ): 93 | keyword.arg = "orientation" 94 | return node 95 | 96 | # Parse the source code into an AST 97 | tree = ast.parse(source_code) 98 | 99 | # Transform the AST with the OrientVisitor 100 | transformer = OrientVisitor() 101 | transformed_tree = transformer.visit(tree) 102 | 103 | # Convert the AST back to source code 104 | modified_source_code = ast.unparse(transformed_tree) 105 | 106 | return modified_source_code 107 | -------------------------------------------------------------------------------- /tk_to_ctk/util.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import ast 3 | import copy 4 | 5 | 6 | def point(thing: any) -> any: 7 | """Makes a reference to an object""" 8 | ref = thing 9 | return ref 10 | 11 | def parsetree(source: str) -> ast.AST: 12 | try: 13 | tree = ast.parse(source, type_comments=True) 14 | return tree 15 | except: 16 | tree = ast.parse(source) 17 | return tree 18 | finally: 19 | return ast.parse(source) 20 | 21 | 22 | 23 | def interface(thing: any) -> any: 24 | """Create a a copy of the object but reference nested objects""" 25 | replica = copy.copy(thing) 26 | return replica 27 | 28 | 29 | def clone(thing: any) -> any: 30 | """Create a copy of an object not as a reference""" 31 | newthing = copy.deepcopy(thing) 32 | return newthing 33 | 34 | 35 | def parsetree(source: str) -> ast.AST: 36 | try: 37 | tree = ast.parse(source, type_comments=True) 38 | except: 39 | tree = ast.parse(source) 40 | finally: 41 | tree = ast.parse(source) 42 | return tree 43 | 44 | 45 | def get_operating_system() -> str: 46 | """ 47 | Determine the operating system being used. 48 | 49 | Returns: 50 | str: A string indicating the operating system. Possible values are "Windows", "Linux", "macOS", or "Unknown". 51 | """ 52 | system: str = platform.system() 53 | if system == "Windows": 54 | return "Windows" 55 | elif system == "Linux": 56 | return "Linux" 57 | elif system == "Darwin": 58 | return "macOS" 59 | else: 60 | return "Unknown" 61 | 62 | 63 | pip_str = "pip" if get_operating_system() == "Windows" else "pip3" 64 | python_str = "python" if get_operating_system() == "Windows" else "python3" 65 | 66 | 67 | def classes_begin_index(lines: list[str]) -> int: 68 | 69 | # first try to put the CTkListBox as the first Class in the class area 70 | count = 0 71 | for line in lines: 72 | if line.startswith("class "): 73 | return count 74 | count += 1 75 | 76 | # if there is no classes defined in the script. Find the first non import line 77 | count = 0 78 | for line in lines: 79 | if line.startswith("import ") or line.startswith("from "): 80 | count += 1 81 | continue 82 | else: 83 | return count 84 | 85 | return count 86 | 87 | 88 | def get_listbox_source(): 89 | with open("listbox.py", "r") as file: 90 | lines = file.readlines()[1:] 91 | return lines 92 | 93 | 94 | def has_listbox(filepath: str) -> bool: 95 | with open(filepath, "r") as file: 96 | content = file.read() 97 | try: 98 | lbi = content.find("tk.Listbox(") 99 | return True 100 | except IndexError: 101 | return False 102 | 103 | 104 | def print_warning(string: str) -> None: 105 | print("\033[35m[WARNING]\033[0m:\033[31m" + string + "\033[0m") 106 | 107 | 108 | def print_update(string: str) -> None: 109 | if isinstance(string, list): 110 | string = "[" + ", ".join(string) + "]" 111 | print("\033[32m[UPDATE]\033[0m:\033[33m" + string + "\033[0m") 112 | -------------------------------------------------------------------------------- /tk_to_ctk/widget_replacer.py: -------------------------------------------------------------------------------- 1 | import re 2 | import ast 3 | 4 | 5 | 6 | tkinter_widgets = [ "Button", "Canvas", "Checkbutton", 7 | "Entry", "Label", "Menubutton", 8 | "Message", "Radiobutton", "Scale", 9 | "Scrollbar", "Text", "Toplevel", 10 | "Treeview", "Frame", "Progressbar", 11 | "Separator"] 12 | ctk_widgets = ["CTk" + x for x in tkinter_widgets] 13 | 14 | 15 | 16 | class WidgetReplacer: 17 | """ 18 | A class to replace widgets in a script and add custom constants. 19 | 20 | Attributes: 21 | source (str): The path to the source script file. 22 | output (str): The path to the output script file. 23 | findables (dict): A dictionary containing findable patterns and their replacements. 24 | constants (list): A list of existing tkinter constants found in the script. 25 | used_constants (list): A list of constants used in the script. 26 | """ 27 | 28 | tkinter_constants = [ 29 | "ACTIVE", 30 | "ALL", 31 | "ANCHOR", 32 | "ARC", 33 | "BASELINE", 34 | "BEVEL", 35 | "BOTH", 36 | "BOTTOM", 37 | "BROWSE", 38 | "BUTT", 39 | "CASCADE", 40 | "CENTER", 41 | "CHAR", 42 | "CHECKBUTTON", 43 | "CHORD", 44 | "COMMAND", 45 | "DISABLED", 46 | "E", 47 | "END", 48 | "EW", 49 | "EXCEPTION", 50 | "EXTENDED", 51 | "FALSE", 52 | "FIRST", 53 | "FLAT", 54 | "GROOVE", 55 | "HIDDEN", 56 | "HORIZONTAL", 57 | "INSERT", 58 | "INSIDE", 59 | "LAST", 60 | "LEFT", 61 | "MITER", 62 | "MULTIPLE", 63 | "N", 64 | "NE", 65 | "NO", 66 | "NONE", 67 | "NORMAL", 68 | "NS", 69 | "NSEW", 70 | "NW", 71 | "OFF", 72 | "ON", 73 | "OUTSIDE", 74 | "PAGES", 75 | "PIESLICE", 76 | "PROJECTING", 77 | "RADIOBUTTON", 78 | "RAISED", 79 | "READABLE", 80 | "RIDGE", 81 | "RIGHT", 82 | "ROUND", 83 | "S", 84 | "SCROLL", 85 | "SE", 86 | "SEL", 87 | "SEL_FIRST", 88 | "SEL_LAST", 89 | "SEPARATOR", 90 | "SINGLE", 91 | "SOLID", 92 | "SUNKEN", 93 | "SW", 94 | "Synchronous", 95 | "SystemButton", 96 | "TOP", 97 | "TRUE", 98 | "UNITS", 99 | "VERTICAL", 100 | "W", 101 | "WORD", 102 | "WRITABLE", 103 | "X", 104 | "Y", 105 | ] 106 | 107 | def __init__(self, source: str, output: str) -> None: 108 | """ 109 | Initialize the WidgetReplacer instance. 110 | 111 | Args: 112 | source (str): The path to the source script file. 113 | output (str): The path to the output script file. 114 | """ 115 | self.source: str = source 116 | self.output: str = output 117 | self.findables: dict[str, str] = {} 118 | self.constants: list[str] = [] 119 | self.used_constants: list[str] = [] 120 | 121 | def set_source(self, source: str): 122 | self.source = source 123 | 124 | def set_output(self, output: str): 125 | self.output = output 126 | 127 | def add_findable(self, original: str, replacement: str) -> None: 128 | """ 129 | Add a findable pattern and its replacement. 130 | 131 | Args: 132 | original (str): The original pattern to find. 133 | replacement (str): The replacement pattern. 134 | """ 135 | self.findables[re.escape(original)] = replacement 136 | 137 | def replace_widgets(self): 138 | """ 139 | Replace widgets in the script using findable patterns and replacements. 140 | """ 141 | with open(self.source, "r", encoding="utf-8", errors="ignore") as f: 142 | script_content = f.read() 143 | out = "" 144 | for onst in self.tkinter_constants: 145 | if re.search(onst, script_content): 146 | self.constants.append(onst) 147 | 148 | for original, replacement in self.findables.items(): 149 | pattern = r"\b{}\b".format(original) 150 | script_content = re.sub(pattern, replacement, script_content) 151 | 152 | with open(self.output, "w", errors="ignore") as f: 153 | f.write(script_content) 154 | 155 | def double_check(self): 156 | """ 157 | Double-check and add custom tkinter constants to the script. 158 | """ 159 | with open(self.output, "r") as f: 160 | script_content = f.readlines() 161 | 162 | out = "import customtkinter as ctk\nfrom customtkinter import " 163 | m = len(self.constants) - 1 164 | for index, constant in enumerate(self.constants): 165 | if index == m: 166 | out += f"{constant}" 167 | else: 168 | out += f"{constant}, " 169 | out += "\n" 170 | 171 | with open(self.output, "w") as f: 172 | f.write(out) 173 | for line in script_content: 174 | f.write(line) 175 | 176 | def add_constant(self, constant): 177 | """ 178 | Add a custom constant to the list of constants. 179 | 180 | Args: 181 | constant (str): The constant to add. 182 | """ 183 | self.constants.append(constant) 184 | 185 | 186 | --------------------------------------------------------------------------------