From e5463891e94d5da19e531bb33382e4d87d20b7aa Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 28 Sep 2008 00:00:49 +0900 Subject: [PATCH] initial commit from hg --- .gitignore | 7 + COPYING | 676 +++ CREDITS | 3 + ChangeLog.old | 6072 +++++++++++++++++++++++++ README | 38 + README.development | 5 + README.translating | 27 + anki | 21 + anki.bat | 2 + anki.desktop | 11 + ankiqt/__init__.py | 101 + ankiqt/config.py | 157 + ankiqt/icons_rc.py | 6537 +++++++++++++++++++++++++++ ankiqt/locale/ankiqt_cs_CZ.po | 2658 +++++++++++ ankiqt/locale/ankiqt_de_DE.po | 2281 ++++++++++ ankiqt/locale/ankiqt_es_ES.po | 2787 ++++++++++++ ankiqt/locale/ankiqt_fr_FR.po | 2817 ++++++++++++ ankiqt/locale/ankiqt_ja_JP.po | 2713 +++++++++++ ankiqt/locale/ankiqt_ko_KR.po | 2425 ++++++++++ ankiqt/locale/messages.pot | 2069 +++++++++ ankiqt/ui/__init__.py | 69 + ankiqt/ui/about.py | 21 + ankiqt/ui/addcards.py | 129 + ankiqt/ui/cardlist.py | 647 +++ ankiqt/ui/deckproperties.py | 188 + ankiqt/ui/displayproperties.py | 287 ++ ankiqt/ui/exporting.py | 63 + ankiqt/ui/facteditor.py | 560 +++ ankiqt/ui/graphs.py | 213 + ankiqt/ui/help.py | 177 + ankiqt/ui/importing.py | 202 + ankiqt/ui/lookup.py | 75 + ankiqt/ui/main.py | 1345 ++++++ ankiqt/ui/modelchooser.py | 210 + ankiqt/ui/modelproperties.py | 474 ++ ankiqt/ui/preferences.py | 195 + ankiqt/ui/status.py | 265 ++ ankiqt/ui/sync.py | 171 + ankiqt/ui/tagedit.py | 53 + ankiqt/ui/tray.py | 73 + ankiqt/ui/unsaved.py | 16 + ankiqt/ui/update.py | 155 + ankiqt/ui/utils.py | 67 + ankiqt/ui/view.py | 269 ++ ankiqtmac.py | 17 + designer/about.ui | 87 + designer/addcards.ui | 324 ++ designer/addmodel.ui | 140 + designer/cardlist.ui | 385 ++ designer/changemap.ui | 82 + designer/deckproperties.ui | 666 +++ designer/displayproperties.ui | 677 +++ designer/exporting.ui | 108 + designer/importing.ui | 278 ++ designer/infodialog.ui | 76 + designer/main.ui | 922 ++++ designer/modelproperties.ui | 782 ++++ designer/preferences.ui | 645 +++ designer/sort.ui | 97 + designer/syncdeck.ui | 97 + ez_setup.py | 228 + icons.qrc | 62 + icons/Anki_Add_Tag.png | Bin 0 -> 1553 bytes icons/Anki_Card.png | Bin 0 -> 1872 bytes icons/Anki_Del_Tag.png | Bin 0 -> 1773 bytes icons/Anki_Fact.png | Bin 0 -> 2110 bytes icons/anki.png | Bin 0 -> 1720 bytes icons/application-exit.png | Bin 0 -> 1760 bytes icons/colors.png | Bin 0 -> 2295 bytes icons/colorscm.png | Bin 0 -> 2452 bytes icons/configure.png | Bin 0 -> 3355 bytes icons/contents.png | Bin 0 -> 2467 bytes icons/contents2.png | Bin 0 -> 2157 bytes icons/document-export.png | Bin 0 -> 1291 bytes icons/document-import.png | Bin 0 -> 1244 bytes icons/document-new.png | Bin 0 -> 1410 bytes icons/document-open-recent.png | Bin 0 -> 1663 bytes icons/document-open.png | Bin 0 -> 1088 bytes icons/document-save-as.png | Bin 0 -> 2152 bytes icons/document-save.png | Bin 0 -> 1263 bytes icons/edit-find.png | Bin 0 -> 1512 bytes icons/edit-undo.png | Bin 0 -> 2020 bytes icons/edit.png | Bin 0 -> 1627 bytes icons/editdelete.png | Bin 0 -> 1368 bytes icons/fileclose.png | Bin 0 -> 1594 bytes icons/folder_image.png | Bin 0 -> 2586 bytes icons/folder_sound.png | Bin 0 -> 2762 bytes icons/format-stroke-color.png | Bin 0 -> 1703 bytes icons/games-solve.png | Bin 0 -> 2720 bytes icons/go-home.png | Bin 0 -> 1332 bytes icons/help-contents.png | Bin 0 -> 1599 bytes icons/help.png | Bin 0 -> 1587 bytes icons/image.png | Bin 0 -> 1892 bytes icons/kanji.png | Bin 0 -> 1495 bytes icons/kbugbuster.png | Bin 0 -> 1709 bytes icons/kexi.png | Bin 0 -> 1179 bytes icons/khtml_kget.png | Bin 0 -> 2122 bytes icons/kpersonalizer.png | Bin 0 -> 2208 bytes icons/list-add.png | Bin 0 -> 1279 bytes icons/math_matrix.png | Bin 0 -> 637 bytes icons/math_sqrt.png | Bin 0 -> 474 bytes icons/media-playback-pause.png | Bin 0 -> 1145 bytes icons/media-playback-start.png | Bin 0 -> 1177 bytes icons/media-playback-stop.png | Bin 0 -> 1165 bytes icons/multisynk.png | Bin 0 -> 4254 bytes icons/package_games_card.png | Bin 0 -> 2253 bytes icons/preferences-desktop-font.png | Bin 0 -> 1336 bytes icons/rating.png | Bin 0 -> 1504 bytes icons/speaker.png | Bin 0 -> 1980 bytes icons/spreadsheet.png | Bin 0 -> 1740 bytes icons/sqlitebrowser.png | Bin 0 -> 486 bytes icons/system-shutdown.png | Bin 0 -> 1753 bytes icons/tex.png | Bin 0 -> 2224 bytes icons/text-speak.png | Bin 0 -> 2527 bytes icons/text_bold.png | Bin 0 -> 1620 bytes icons/text_italic.png | Bin 0 -> 1378 bytes icons/text_under.png | Bin 0 -> 1549 bytes icons/view-pim-news.png | Bin 0 -> 1109 bytes icons/view_text.png | Bin 0 -> 952 bytes icons_rc.py | 6777 ++++++++++++++++++++++++++++ mac/anki.icns | Bin 0 -> 70779 bytes mac/make.sh | 17 + mac/setup.py | 80 + setup.py | 32 + tools/build_ui.sh | 46 + tools/translate.sh | 30 + 126 files changed, 49986 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 CREDITS create mode 100644 ChangeLog.old create mode 100644 README create mode 100644 README.development create mode 100644 README.translating create mode 100755 anki create mode 100644 anki.bat create mode 100644 anki.desktop create mode 100644 ankiqt/__init__.py create mode 100644 ankiqt/config.py create mode 100644 ankiqt/icons_rc.py create mode 100644 ankiqt/locale/ankiqt_cs_CZ.po create mode 100644 ankiqt/locale/ankiqt_de_DE.po create mode 100644 ankiqt/locale/ankiqt_es_ES.po create mode 100644 ankiqt/locale/ankiqt_fr_FR.po create mode 100644 ankiqt/locale/ankiqt_ja_JP.po create mode 100644 ankiqt/locale/ankiqt_ko_KR.po create mode 100644 ankiqt/locale/messages.pot create mode 100644 ankiqt/ui/__init__.py create mode 100644 ankiqt/ui/about.py create mode 100644 ankiqt/ui/addcards.py create mode 100644 ankiqt/ui/cardlist.py create mode 100644 ankiqt/ui/deckproperties.py create mode 100644 ankiqt/ui/displayproperties.py create mode 100644 ankiqt/ui/exporting.py create mode 100644 ankiqt/ui/facteditor.py create mode 100644 ankiqt/ui/graphs.py create mode 100644 ankiqt/ui/help.py create mode 100644 ankiqt/ui/importing.py create mode 100644 ankiqt/ui/lookup.py create mode 100644 ankiqt/ui/main.py create mode 100644 ankiqt/ui/modelchooser.py create mode 100644 ankiqt/ui/modelproperties.py create mode 100644 ankiqt/ui/preferences.py create mode 100644 ankiqt/ui/status.py create mode 100644 ankiqt/ui/sync.py create mode 100644 ankiqt/ui/tagedit.py create mode 100644 ankiqt/ui/tray.py create mode 100644 ankiqt/ui/unsaved.py create mode 100644 ankiqt/ui/update.py create mode 100644 ankiqt/ui/utils.py create mode 100644 ankiqt/ui/view.py create mode 100644 ankiqtmac.py create mode 100644 designer/about.ui create mode 100644 designer/addcards.ui create mode 100644 designer/addmodel.ui create mode 100644 designer/cardlist.ui create mode 100644 designer/changemap.ui create mode 100644 designer/deckproperties.ui create mode 100644 designer/displayproperties.ui create mode 100644 designer/exporting.ui create mode 100644 designer/importing.ui create mode 100644 designer/infodialog.ui create mode 100644 designer/main.ui create mode 100644 designer/modelproperties.ui create mode 100644 designer/preferences.ui create mode 100644 designer/sort.ui create mode 100644 designer/syncdeck.ui create mode 100644 ez_setup.py create mode 100644 icons.qrc create mode 100644 icons/Anki_Add_Tag.png create mode 100644 icons/Anki_Card.png create mode 100644 icons/Anki_Del_Tag.png create mode 100644 icons/Anki_Fact.png create mode 100644 icons/anki.png create mode 100644 icons/application-exit.png create mode 100644 icons/colors.png create mode 100644 icons/colorscm.png create mode 100644 icons/configure.png create mode 100644 icons/contents.png create mode 100644 icons/contents2.png create mode 100644 icons/document-export.png create mode 100644 icons/document-import.png create mode 100644 icons/document-new.png create mode 100644 icons/document-open-recent.png create mode 100644 icons/document-open.png create mode 100644 icons/document-save-as.png create mode 100644 icons/document-save.png create mode 100644 icons/edit-find.png create mode 100644 icons/edit-undo.png create mode 100644 icons/edit.png create mode 100644 icons/editdelete.png create mode 100644 icons/fileclose.png create mode 100644 icons/folder_image.png create mode 100644 icons/folder_sound.png create mode 100644 icons/format-stroke-color.png create mode 100644 icons/games-solve.png create mode 100644 icons/go-home.png create mode 100644 icons/help-contents.png create mode 100644 icons/help.png create mode 100644 icons/image.png create mode 100644 icons/kanji.png create mode 100644 icons/kbugbuster.png create mode 100644 icons/kexi.png create mode 100644 icons/khtml_kget.png create mode 100644 icons/kpersonalizer.png create mode 100644 icons/list-add.png create mode 100644 icons/math_matrix.png create mode 100644 icons/math_sqrt.png create mode 100644 icons/media-playback-pause.png create mode 100644 icons/media-playback-start.png create mode 100644 icons/media-playback-stop.png create mode 100644 icons/multisynk.png create mode 100644 icons/package_games_card.png create mode 100644 icons/preferences-desktop-font.png create mode 100644 icons/rating.png create mode 100644 icons/speaker.png create mode 100644 icons/spreadsheet.png create mode 100644 icons/sqlitebrowser.png create mode 100644 icons/system-shutdown.png create mode 100644 icons/tex.png create mode 100644 icons/text-speak.png create mode 100644 icons/text_bold.png create mode 100644 icons/text_italic.png create mode 100644 icons/text_under.png create mode 100644 icons/view-pim-news.png create mode 100644 icons/view_text.png create mode 100644 icons_rc.py create mode 100644 mac/anki.icns create mode 100755 mac/make.sh create mode 100644 mac/setup.py create mode 100644 setup.py create mode 100755 tools/build_ui.sh create mode 100755 tools/translate.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ecba33791 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.pyc +*~ +*.mo +*\# +build +dist +ankiqt/forms diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..443254047 --- /dev/null +++ b/COPYING @@ -0,0 +1,676 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/CREDITS b/CREDITS new file mode 100644 index 000000000..86e93093b --- /dev/null +++ b/CREDITS @@ -0,0 +1,3 @@ +Many people have contributed bug reports, patches and feature suggestions. +Thanks also to those who have donated or provided translations. Further detail +is available in the changelog. diff --git a/ChangeLog.old b/ChangeLog.old new file mode 100644 index 000000000..3dc3a4115 --- /dev/null +++ b/ChangeLog.old @@ -0,0 +1,6072 @@ +changeset: 812:f7e7fa383739 +tag: tip +user: Damien Elmes +date: Wed Sep 24 20:21:25 2008 +0900 +description: +reset timer when deck finished + + +changeset: 811:3866027bd65b +user: Damien Elmes +date: Tue Sep 23 03:22:01 2008 +0900 +description: +update german translations + + +changeset: 810:d75bc66e2383 +user: Damien Elmes +date: Mon Sep 22 19:19:37 2008 +0900 +description: +bump version number + + +changeset: 809:8403d7bb9e32 +user: Damien Elmes +date: Mon Sep 22 19:18:25 2008 +0900 +description: +don't focus config button + + +changeset: 808:b58ff100df95 +user: Damien Elmes +date: Mon Sep 22 18:07:35 2008 +0900 +description: +add config button shortcut, shrink deck properties + + +changeset: 807:7eef95b6ea70 +user: Damien Elmes +date: Mon Sep 22 17:41:55 2008 +0900 +description: +add shortcut to scheduling properties + + +changeset: 806:ec6c1f1cd1d7 +user: Damien Elmes +date: Mon Sep 15 16:19:53 2008 +0900 +description: +add stricter time difference check to sync + + +changeset: 805:98568347266b +user: Damien Elmes +date: Mon Sep 22 17:07:28 2008 +0900 +description: +update translations, make some extra fields translatable + + +changeset: 804:7e03e3e9bcee +user: Damien Elmes +date: Mon Sep 22 15:58:57 2008 +0900 +description: +update translations + + +changeset: 803:7446a54e3e2b +user: Damien Elmes +date: Mon Sep 22 00:36:56 2008 +0900 +description: +suppress welcome screen on no sync, on sync ensure 2.5+ seconds shown + + +changeset: 802:6bc9d401bb0c +user: Damien Elmes +date: Sun Sep 21 21:25:33 2008 +0900 +description: +update to new syncing code + + +changeset: 801:5cd940ec0e5b +user: Damien Elmes +date: Fri Sep 19 18:37:04 2008 +0900 +description: +optimizeMedia->rebuildMedia + + +changeset: 800:d7bfea4832ae +user: Damien Elmes +date: Fri Sep 19 15:52:29 2008 +0900 +description: +possible fix for window off screen bugs + + +changeset: 799:3d659fc0f023 +user: Damien Elmes +date: Fri Sep 19 11:42:33 2008 +0900 +description: +increase precision of initial intervals, fix mac menu bars + + +changeset: 798:5e121bf274a2 +user: Damien Elmes +date: Thu Sep 18 22:44:20 2008 +0900 +description: +link to changes on update + + +changeset: 797:d276ba511992 +user: Damien Elmes +date: Wed Sep 17 01:07:19 2008 +0900 +description: +hide audio, just like images + + +changeset: 796:a6110bb3d2ad +user: Damien Elmes +date: Mon Sep 15 05:12:03 2008 +0900 +description: +bump version number + + +changeset: 795:5a0aef4d8b4a +user: Damien Elmes +date: Mon Sep 15 02:12:16 2008 +0900 +description: +prefs dialog tweak + + +changeset: 794:164d195a23ea +user: Damien Elmes +date: Mon Sep 15 01:58:31 2008 +0900 +description: +fix timer bug, improve colours + + +changeset: 793:39d95e3e2636 +user: Damien Elmes +date: Mon Sep 15 00:59:56 2008 +0900 +description: +bump version + + +changeset: 792:b4e396f318ee +user: Damien Elmes +date: Sun Sep 14 23:52:14 2008 +0900 +description: +add new icons, revert add cards and font buttons + + +changeset: 791:487b12bb790a +user: Damien Elmes +date: Sun Sep 14 22:37:56 2008 +0900 +description: +add shortcuts to tooltips in fact editor + + +changeset: 790:0a4bccd3c008 +user: Damien Elmes +date: Sun Sep 14 22:22:16 2008 +0900 +description: +update translations + + +changeset: 789:76a01432b26c +user: Damien Elmes +date: Sun Sep 14 22:13:42 2008 +0900 +description: +add shortcut key for repeating audio + + +changeset: 788:9781fee21d27 +user: Damien Elmes +date: Sun Sep 14 22:04:53 2008 +0900 +description: +implement timer + + +changeset: 787:9fe8ad6c18bc +user: Damien Elmes +date: Sun Sep 14 15:20:15 2008 +0900 +description: +add media dir optimization support + + +changeset: 786:d222970fd673 +user: Damien Elmes +date: Fri Sep 12 22:30:34 2008 +0900 +description: +merge daily & global stats, remove mode indicator, remove distracted time + + +changeset: 785:617724d51e53 +user: Damien Elmes +date: Fri Sep 12 22:28:02 2008 +0900 +description: +add hooks to editor + + +changeset: 784:006188aeaf53 +user: Damien Elmes +date: Wed Sep 10 00:06:10 2008 +0900 +description: +update 'no problems found' message, don't warn about failed fixes + + +changeset: 783:f2e1209c1f2e +user: Damien Elmes +date: Sun Sep 07 01:04:26 2008 +0900 +description: +add debian diff + + +changeset: 782:64bbc71bf76e +user: Damien Elmes +date: Sun Sep 07 00:04:58 2008 +0900 +description: +update spanish translations + + +changeset: 781:c3fa61627da3 +user: Damien Elmes +date: Sat Sep 06 23:50:47 2008 +0900 +description: +bump version number + + +changeset: 780:b184386be21a +user: Damien Elmes +date: Sat Sep 06 23:44:36 2008 +0900 +description: +new resize/move that should work on all platforms, don't set c+w on mac + + +changeset: 779:b3ccd75b96f2 +user: Damien Elmes +date: Sat Sep 06 22:28:00 2008 +0900 +description: +change scheduling options + + +changeset: 778:ece55e98a4ea +user: Damien Elmes +date: Sat Sep 06 22:12:59 2008 +0900 +description: +ensure window is visible (based on patch by David Knaack) + + +changeset: 777:d1b3a6ab2c02 +user: Damien Elmes +date: Sat Sep 06 21:10:59 2008 +0900 +description: +add space in deck message + + +changeset: 776:36f2787b1fc7 +user: Damien Elmes +date: Sat Sep 06 20:38:28 2008 +0900 +description: +update priority url + + +changeset: 775:0ab9164b739b +user: Damien Elmes +date: Sat Sep 06 19:01:02 2008 +0900 +description: +add max new cards and new card scheduling options + + +changeset: 774:f28a50fae006 +user: Damien Elmes +date: Sat Sep 06 18:31:54 2008 +0900 +description: +remove debian dir + + +changeset: 773:7b21553ea9a2 +user: Damien Elmes +date: Sat Sep 06 18:28:12 2008 +0900 +description: +center deck finished message but left align it + + +changeset: 772:1eb6c7d80ae1 +user: Damien Elmes +date: Wed Sep 03 15:22:39 2008 +0900 +description: +add 'period' translation + + +changeset: 771:512b057f1dae +user: Damien Elmes +date: Wed Sep 03 15:00:59 2008 +0900 +description: +disable undo answer on deck close + + +changeset: 770:35a2f14d440d +user: Damien Elmes +date: Wed Sep 03 04:35:41 2008 +0900 +description: +bump version number + + +changeset: 769:efa4c5a348dc +user: Damien Elmes +date: Wed Sep 03 03:38:51 2008 +0900 +description: +updated german translations from [Bananeweizen at gmx dot de] + + +changeset: 768:9075d5825e29 +user: Damien Elmes +date: Tue Sep 02 16:14:09 2008 +0900 +description: +factor pruneHTML into libanki + + +changeset: 767:847d8f4cf083 +user: Damien Elmes +date: Mon Sep 01 23:40:03 2008 +0900 +description: +update translations + + +changeset: 766:dbf82a0cb361 +user: Damien Elmes +date: Mon Sep 01 23:39:46 2008 +0900 +description: +fix menus + + +changeset: 765:554a0025ef38 +user: Damien Elmes +date: Mon Sep 01 19:41:26 2008 +0900 +description: +bump version number + + +changeset: 764:c409cc86cd13 +user: Damien Elmes +date: Mon Sep 01 19:40:58 2008 +0900 +description: +strip leading/trailing whitespace + + +changeset: 763:0bddf38529bd +user: Damien Elmes +date: Mon Sep 01 18:32:52 2008 +0900 +description: +fix problems escaping html (causing extra spaces to be inserted) + + +changeset: 762:0920d0d6c6b4 +user: Damien Elmes +date: Mon Sep 01 17:50:26 2008 +0900 +description: +fix problem recording minimum spacing with periods + + +changeset: 761:44c72b39eeee +user: Damien Elmes +date: Mon Sep 01 12:23:15 2008 +0900 +description: +fix capitalization in menus + + +changeset: 760:937d1e207f64 +user: Damien Elmes +date: Mon Sep 01 00:32:55 2008 +0900 +description: +don't hide last card msg on fin, center msg, only stop double card if not due + + +changeset: 759:4c6b2407a30d +user: Damien Elmes +date: Mon Sep 01 00:17:31 2008 +0900 +description: +check card dupes via id, not object, reset current card on auto + + +changeset: 758:049cbcc348a0 +user: Damien Elmes +date: Mon Sep 01 00:10:03 2008 +0900 +description: +strip single empty paragraph + + +changeset: 757:3daa78ee8bd8 +user: Damien Elmes +date: Sun Aug 31 23:59:49 2008 +0900 +description: +change html escaping to preserve colours + + +changeset: 756:30266124390a +user: Damien Elmes +date: Sun Aug 31 22:56:45 2008 +0900 +description: +update spanish translations + + +changeset: 755:2f0e3f5a3bc4 +user: Damien Elmes +date: Sun Aug 31 22:49:48 2008 +0900 +description: +bump version number + + +changeset: 754:a700b3ec1478 +user: Damien Elmes +date: Sun Aug 31 22:49:35 2008 +0900 +description: +make about dialog translatable + + +changeset: 753:1a4141980049 +user: Damien Elmes +date: Sun Aug 31 22:49:18 2008 +0900 +description: +another patch from Nathanael Law: fix a bug & enable/disable move up/down buttons + + +changeset: 752:a693cfbcbbad +user: Damien Elmes +date: Sun Aug 31 22:48:48 2008 +0900 +description: +expunge card on answering, since we update db directly + + +changeset: 751:18bd8dac5361 +user: Damien Elmes +date: Sun Aug 31 21:29:15 2008 +0900 +description: +hide edit cards dialog first, to show we've received the event + + +changeset: 750:b72657d4ef56 +user: Damien Elmes +date: Sun Aug 31 17:22:11 2008 +0900 +description: +show full path of recent file in status bar +patch from Misha Penkov + + +changeset: 749:733382d7738a +user: Damien Elmes +date: Sun Aug 31 17:14:32 2008 +0900 +description: +add support for changing order of card models and field models +- thanks to Nathanael Law for the patch +- updated patch to mark facts/models modified so changes sync +- fixed tab order in model properties dialog + + +changeset: 748:0a43022bc866 +user: Damien Elmes +date: Fri Aug 29 21:21:10 2008 +0900 +description: +add comment about having same name + + +changeset: 747:e4439acc05ed +user: Damien Elmes +date: Fri Aug 29 21:10:06 2008 +0900 +description: +bump version number + + +changeset: 746:c190c5b3c560 +user: Damien Elmes +date: Fri Aug 29 20:42:48 2008 +0900 +description: +change to temp dir before downloading auto updater + + +changeset: 745:c195b694e298 +user: Damien Elmes +date: Fri Aug 29 20:25:08 2008 +0900 +description: +fix question/answer regeneration (thanks to nathaneal law) + + +changeset: 744:543f4cda569c +user: Damien Elmes +date: Thu Aug 28 17:44:33 2008 +0900 +description: +model merging + + +changeset: 743:725e383f8f41 +user: Damien Elmes +date: Mon Aug 25 19:06:43 2008 +0900 +description: +call set_menubar_icons() after app init + + +changeset: 742:394e8a22ce01 +user: Damien Elmes +date: Mon Aug 25 17:53:06 2008 +0900 +description: +bump version + + +changeset: 741:1006b32430e8 +user: Damien Elmes +date: Mon Aug 25 15:19:20 2008 +0900 +description: +update the stats whenever getQuestion is called + + +changeset: 740:c828fb3ef89b +user: Damien Elmes +date: Mon Aug 25 00:58:57 2008 +0900 +description: +work around an osx bug + + +changeset: 739:3985b6a1302d +user: Damien Elmes +date: Mon Aug 25 00:02:34 2008 +0900 +description: +change picture icon again + + +changeset: 738:b3df7d8ec7db +user: Damien Elmes +date: Mon Aug 25 00:02:27 2008 +0900 +description: +change i + + +changeset: 737:d968b7a2dbc6 +user: Damien Elmes +date: Sun Aug 24 23:59:27 2008 +0900 +description: +change icon + + +changeset: 736:55567faed309 +user: Damien Elmes +date: Sun Aug 24 23:52:32 2008 +0900 +description: +new icons + + +changeset: 735:4cf70cf64496 +user: Damien Elmes +date: Sun Aug 24 23:45:29 2008 +0900 +description: +new icons, improve integrity check + + +changeset: 734:89d91e0d3417 +user: Damien Elmes +date: Sun Aug 24 16:10:53 2008 +0900 +description: +disable advanced menu when deck not open + + +changeset: 733:bda2c7cf46b6 +user: Damien Elmes +date: Sun Aug 24 16:08:17 2008 +0900 +description: +integrity check, update handling + + +changeset: 732:b583fe72e197 +user: Damien Elmes +date: Sat Aug 23 13:25:19 2008 +0900 +description: +rename 'ready to add', as silly in editor + + +changeset: 731:f7e55fb507cd +user: Damien Elmes +date: Sat Aug 23 05:53:29 2008 +0900 +description: +create media dir when latex added + + +changeset: 730:fcde5237f8c9 +user: Damien Elmes +date: Sat Aug 23 00:32:02 2008 +0900 +description: +force info dialog in add cards to a fixed height + + +changeset: 729:547750d4f09b +user: Damien Elmes +date: Fri Aug 22 18:36:49 2008 +0900 +description: +bump version number + + +changeset: 728:9886f22c8158 +user: Damien Elmes +date: Fri Aug 22 18:36:10 2008 +0900 +description: +fix japanese reading shortcut + + +changeset: 727:1e9d3fa9f9aa +user: Damien Elmes +date: Fri Aug 22 17:23:36 2008 +0900 +description: +fix undo, update card count when refreshing page + + +changeset: 726:1e742db56ddd +user: Damien Elmes +date: Fri Aug 22 04:11:00 2008 +0900 +description: +increase size of about box + + +changeset: 725:fabe74dee1f6 +user: Damien Elmes +date: Fri Aug 22 03:38:54 2008 +0900 +description: +update translations + + +changeset: 724:07593ce4bd07 +user: Damien Elmes +date: Fri Aug 22 02:19:05 2008 +0900 +description: +bump version number + + +changeset: 723:d71740fc991a +user: Damien Elmes +date: Fri Aug 22 02:17:23 2008 +0900 +description: +off by one character movement introduced in qt4.4 + + +changeset: 722:9cebb7e911ab +user: Damien Elmes +date: Thu Aug 21 17:53:32 2008 +0900 +description: +add help for duplicate & missing fields in add cards dialog + + +changeset: 721:b8b13cc6a780 +user: Damien Elmes +date: Thu Aug 21 17:24:38 2008 +0900 +description: +don't change card model order in model selector, add shortcut keys + + +changeset: 720:31979af1c9ae +user: Damien Elmes +date: Thu Aug 21 01:50:06 2008 +0900 +description: +improve model properties and deck properties dialogs + + +changeset: 719:bc16d6f69d8a +user: Damien Elmes +date: Wed Aug 20 23:51:54 2008 +0900 +description: +factor finished message into library + + +changeset: 718:94efb87e22bb +user: Damien Elmes +date: Tue Aug 19 01:07:37 2008 +0900 +description: +since I can't keep CREDITS up to date, refer people to the changelog instead + + +changeset: 717:9483d27b19cf +user: Damien Elmes +date: Tue Aug 19 00:50:09 2008 +0900 +description: +make double click work with chinese models, too + + +changeset: 716:c69d7ba763dd +user: Damien Elmes +date: Tue Aug 19 00:43:37 2008 +0900 +description: +remove any pyc files before building mac version + + +changeset: 715:7fb77fee1092 +user: Damien Elmes +date: Mon Aug 18 23:47:51 2008 +0900 +description: +if sync fails on new, close deck + + +changeset: 714:62b007f52bc1 +user: Damien Elmes +date: Fri Aug 15 21:14:30 2008 +0900 +description: +update version + + +changeset: 713:9b5bce251851 +parent: 711:311dfe083e2c +parent: 712:30d6e6f86e10 +user: Damien Elmes +date: Thu Aug 14 15:17:50 2008 +0900 +description: +merge + + +changeset: 712:30d6e6f86e10 +parent: 708:9451eae5a208 +user: Damien Elmes +date: Thu Aug 14 15:17:20 2008 +0900 +description: +add local server for convenience + + +changeset: 711:311dfe083e2c +user: Damien Elmes +date: Thu Aug 14 15:17:05 2008 +0900 +description: +don't show same card twice, mark expired due, fix undo, improve congrats screen + + +changeset: 710:d6762d010ce6 +user: Damien Elmes +date: Sun Aug 03 16:10:19 2008 +0900 +description: +from hashbang from ankiqt/__init__.py + + +changeset: 709:89cc676bb1da +user: Damien Elmes +date: Sun Aug 03 16:08:19 2008 +0900 +description: +update for new libanki + + +changeset: 708:9451eae5a208 +user: Damien Elmes +date: Mon Jul 07 23:58:51 2008 +0900 +description: +default to tray off + + +changeset: 707:7de6b86acbfc +user: Damien Elmes +date: Mon Jul 07 23:57:02 2008 +0900 +description: +possible workaround for sync colours on dark background + + +changeset: 706:09497f5da843 +user: Damien Elmes +date: Mon Jul 07 23:46:18 2008 +0900 +description: +bump version number + + +changeset: 705:55d10f85edd1 +user: Damien Elmes +date: Mon Jul 07 23:43:20 2008 +0900 +description: +typo + + +changeset: 704:25f21a737e14 +user: Damien Elmes +date: Mon Jul 07 23:42:54 2008 +0900 +description: +catch eof error when loading config file + + +changeset: 703:9364b621e019 +user: Damien Elmes +date: Mon Jul 07 23:28:59 2008 +0900 +description: +update korean translations + + +changeset: 702:62631b225e67 +user: Damien Elmes +date: Sun Jul 06 17:55:28 2008 +0900 +description: +bump version number + + +changeset: 701:b01a614f7949 +user: Damien Elmes +date: Fri Jul 04 15:10:36 2008 +0900 +description: +when reporting off clock, show amount of time off in seconds + + +changeset: 700:c73fc95b6059 +user: Damien Elmes +date: Fri Jul 04 00:18:13 2008 +0900 +description: +update urls + + +changeset: 699:25e2f7bbfc99 +user: Damien Elmes +date: Fri Jul 04 00:03:36 2008 +0900 +description: +fix tray icon not disappearing on win32 + + +changeset: 698:813a438a049d +user: Damien Elmes +date: Thu Jul 03 17:03:54 2008 +0900 +description: +set priorities on cards when importing + + +changeset: 697:45072a766f80 +user: Damien Elmes +date: Thu Jul 03 16:25:46 2008 +0900 +description: +fix problems closing/reopening add cards dialog + + +changeset: 696:4ead28f37690 +user: Damien Elmes +date: Thu Jul 03 16:07:41 2008 +0900 +description: +fix 'ret' in tag editor on unknown tag, fix tag editor bug + + +changeset: 695:55be28f053d2 +user: Damien Elmes +date: Thu Jul 03 15:39:36 2008 +0900 +description: +remove reference to 'a week' as no longer applies + + +changeset: 694:e9a42c18f0ed +user: Damien Elmes +date: Wed Jul 02 18:31:28 2008 +0900 +description: +make all widgets on status bar permanent to work around bug + + +changeset: 693:9f028862d92e +user: Damien Elmes +date: Fri Jun 27 13:40:20 2008 +0900 +description: +hard code foreground text colour since we control background colour in facteditor + + +changeset: 692:f1339bd8841d +user: Damien Elmes +date: Fri Jun 27 13:32:46 2008 +0900 +description: +don't delete objects in their handler (setParent -> deleteLater) + + +changeset: 691:4ffd57664dae +user: Damien Elmes +date: Sat Jun 14 16:05:52 2008 +0900 +description: +over a week -> over a month + + +changeset: 690:65974539f177 +user: Damien Elmes +date: Fri Jun 13 19:07:55 2008 +0900 +description: +fix 'play last audio' + + +changeset: 689:020bda0f2977 +user: Damien Elmes +date: Thu Jun 12 17:33:31 2008 +0900 +description: +remove event filter from tray support - don't detect minimize + + +changeset: 688:336542f8b01c +user: Damien Elmes +date: Thu Jun 12 03:21:29 2008 +0900 +description: +don't force quit on incorrect time + + +changeset: 687:47734fd9517e +user: Damien Elmes +date: Thu Jun 05 18:16:05 2008 +0900 +description: +tray patch from richard, update desktop file + + +changeset: 686:c7740f4f6761 +user: Damien Elmes +date: Sat May 24 15:50:11 2008 +0900 +description: +updated korean translations + + +changeset: 685:2692f9ef9f2e +user: Damien Elmes +date: Sat May 24 15:29:09 2008 +0900 +description: +bump version number + + +changeset: 684:d370b0cd8ef6 +user: Damien Elmes +date: Sat May 24 15:26:35 2008 +0900 +description: +add option to show tray icon only on minimize + + +changeset: 683:e33ef06d693d +user: Damien Elmes +date: Sat May 24 15:06:07 2008 +0900 +description: +exit if clock is off by more than 60 seconds + + +changeset: 682:c56e58164b08 +user: Damien Elmes +date: Sat May 24 13:37:04 2008 +0900 +description: +disable recent deck shortcuts on mac + + +changeset: 681:8872d1a17734 +user: Damien Elmes +date: Sat May 24 13:23:04 2008 +0900 +description: +add version info to about dialog + + +changeset: 680:9976f572e7c9 +user: Damien Elmes +date: Sat May 24 13:12:53 2008 +0900 +description: +add reference to missing Qt in tray.py + + +changeset: 679:037049aa8e5e +user: Damien Elmes +date: Sat May 24 13:10:46 2008 +0900 +description: +add missing korean po file, add patch from richard for minimize to tray + + +changeset: 678:ed4f90dcdab5 +user: Damien Elmes +date: Sat May 24 13:04:02 2008 +0900 +description: +add Korean translation from Jin Eundeok + + +changeset: 677:2ed6aecb856a +user: Damien Elmes +date: Mon May 19 20:25:16 2008 +0900 +description: +if all last card content is suppressed, add a space for balance + + +changeset: 676:7a0426980b71 +user: Damien Elmes +date: Mon May 19 19:54:37 2008 +0900 +description: +add two point precision to average/distracted time, update model name on edit + + +changeset: 675:11da1d077915 +user: Damien Elmes +date: Mon May 19 19:10:28 2008 +0900 +description: +add tiff and svg image support + + +changeset: 674:3a83b5c9ae3d +user: Damien Elmes +date: Mon May 19 18:20:24 2008 +0900 +description: +import pkg_resources before doing anything else, to prevent mac io errors + + +changeset: 673:bd93fe328314 +user: Damien Elmes +date: Sun May 18 19:53:23 2008 +0900 +description: +add support for suppressing answer interval estimates, last card interval & content + + +changeset: 672:c4cdc06ebd3e +user: Damien Elmes +date: Sun May 18 19:21:17 2008 +0900 +description: +patch from richard colley: tray icon, show all cards, suppress update + + +changeset: 671:8c7cd6b8f61a +user: Damien Elmes +date: Sun May 18 16:23:12 2008 +0900 +description: +strip latex from last card display + + +changeset: 670:07a90606a9a0 +user: Damien Elmes +date: Sun May 18 16:09:25 2008 +0900 +description: +don't try refresh if the deck is closed during a sync + + +changeset: 669:c65079dfe269 +user: Damien Elmes +date: Sun May 18 16:04:56 2008 +0900 +description: +only warn about psyco on 32 bit chips + + +changeset: 668:3caab8f2e6df +user: Damien Elmes +date: Sun May 18 15:11:34 2008 +0900 +description: +add jpeg and gif to available images + + +changeset: 667:fdb6eb92050c +user: Damien Elmes +date: Sun May 18 15:10:10 2008 +0900 +description: +catch 'bad status line' error in update.py + + +changeset: 666:4df89ef2db62 +user: Damien Elmes +date: Sun May 18 15:08:19 2008 +0900 +description: +setup.py: gplv3->gplv3 + + +changeset: 665:f89db0814665 +user: Damien Elmes +date: Sun May 18 15:06:52 2008 +0900 +description: +fix 'successive' typo + + +changeset: 664:04493c084299 +user: Damien Elmes +date: Sun May 18 15:05:49 2008 +0900 +description: +patch from 'jtibbes', fix dialogs refusing to close + + +changeset: 663:e6e26fa97576 +user: Damien Elmes +date: Thu May 01 19:07:47 2008 +0900 +description: +patch from metellius: double click to choose kakasi string + + +changeset: 662:99a8e08c6255 +user: Damien Elmes +date: Thu Apr 03 12:44:02 2008 +0900 +description: +bump version number + + +changeset: 661:b49c59542b65 +user: Damien Elmes +date: Wed Apr 02 22:55:16 2008 +0900 +description: +fix typo in jp translations, add 'difficult' + + +changeset: 660:ae2d26dc45b5 +user: Damien Elmes +date: Wed Apr 02 22:42:50 2008 +0900 +description: +jp translation updates from ianl and wrightak + + +changeset: 659:ff2530e72c22 +user: Damien Elmes +date: Wed Apr 02 22:07:10 2008 +0900 +description: +bump version number + + +changeset: 658:256ab58f0e48 +user: Damien Elmes +date: Tue Apr 01 12:28:00 2008 +0900 +description: +delay + 1 should be enough + + +changeset: 657:814054d9d578 +user: Damien Elmes +date: Mon Mar 24 03:26:25 2008 +0900 +description: +bump version number + + +changeset: 656:18c91ef38463 +user: Damien Elmes +date: Mon Mar 24 03:06:46 2008 +0900 +description: +on close, hide any modeless dialogs + + +changeset: 655:170c56f6be7a +user: Damien Elmes +date: Mon Mar 24 01:34:25 2008 +0900 +description: +after deleting a field, update current card + + +changeset: 654:287e97f081c1 +user: Damien Elmes +date: Wed Mar 19 15:23:16 2008 +0900 +description: +gpl2 -> gpl3 + + +changeset: 653:6a5f1c10d625 +user: Damien Elmes +date: Wed Mar 19 15:20:56 2008 +0900 +description: +don't flush() in reset() as reset() flushes + + +changeset: 652:69da9bc66cd7 +user: Damien Elmes +date: Tue Mar 11 18:06:12 2008 +0900 +description: +fix sort by difficulty, ignore case in q/a sort + + +changeset: 651:75aacee84734 +user: Damien Elmes +date: Tue Mar 11 17:13:56 2008 +0900 +description: +fix deck finished screen refresh disabling undo + + +changeset: 650:ecce4a477d19 +user: Damien Elmes +date: Tue Mar 11 17:04:45 2008 +0900 +description: +if lastCard is defined, enable undo + + +changeset: 649:f05013983d43 +user: Damien Elmes +date: Mon Mar 10 19:48:48 2008 +0900 +description: +don't stop refresh timer if we don't have one + + +changeset: 648:301092e5bf34 +user: Damien Elmes +date: Sun Mar 09 03:42:33 2008 +0900 +description: +really fix updating + + +changeset: 647:1555d5fd5091 +user: Damien Elmes +date: Sun Mar 09 03:04:23 2008 +0900 +description: +don't multiple delay twice + + +changeset: 646:61876cfadd44 +user: Damien Elmes +date: Sun Mar 09 02:59:01 2008 +0900 +description: +ver=0.9.5.4 + + +changeset: 645:e81a50783ac9 +user: Damien Elmes +date: Sun Mar 09 01:22:39 2008 +0900 +description: +remove incomplete dutch translation + + +changeset: 644:41b7e8e50695 +user: Damien Elmes +date: Sat Mar 08 02:42:23 2008 +0900 +description: +favour text over html when pasting + + +changeset: 643:8d3cd1edcf6a +user: Damien Elmes +date: Sat Mar 08 01:50:31 2008 +0900 +description: +fix cards->facts in spanish translation + + +changeset: 642:c1bb1f525b2c +user: Damien Elmes +date: Sat Mar 08 00:21:51 2008 +0900 +description: +don't set sync name on failure, don't try to sync if no syncname + + +changeset: 641:25bf6a3ed6c0 +user: Damien Elmes +date: Wed Mar 05 11:16:43 2008 +0900 +description: +fix broken anki updater + + +changeset: 640:9aa9d11fee4c +user: Damien Elmes +date: Mon Mar 03 22:52:02 2008 +0900 +description: +import importing module rather than * + + +changeset: 639:0d66b751eded +user: Damien Elmes +date: Mon Mar 03 22:46:29 2008 +0900 +description: +ver=0.9.5.3 + + +changeset: 638:fcbed8ee3554 +user: Damien Elmes +date: Mon Mar 03 22:46:00 2008 +0900 +description: +update spanish translations + + +changeset: 637:66a505cd7f62 +user: Damien Elmes +date: Fri Feb 29 02:50:09 2008 +0900 +description: +handle duplicate model names + + +changeset: 636:33e20730643b +user: Damien Elmes +date: Fri Feb 29 02:04:10 2008 +0900 +description: +bump version number + + +changeset: 635:16acd561b764 +user: Damien Elmes +date: Fri Feb 29 00:36:57 2008 +0900 +description: +add missing 'welcome to anki' message to translations + + +changeset: 634:1faefbcaa59a +user: Damien Elmes +date: Fri Feb 29 00:12:40 2008 +0900 +description: +update ja translation + + +changeset: 633:269aea3b848f +user: Damien Elmes +date: Fri Feb 29 00:05:16 2008 +0900 +description: +bump version number, update french translation + + +changeset: 632:b7357ed5d95a +user: Damien Elmes +date: Thu Feb 28 03:28:35 2008 +0900 +description: +bump version number + + +changeset: 631:5fd11be30dbc +user: Damien Elmes +date: Thu Feb 28 02:52:41 2008 +0900 +description: +add saveas, coloured pending display + + +changeset: 630:67bb8ce2c1ce +user: Damien Elmes +date: Thu Feb 28 00:26:40 2008 +0900 +description: +cards->facts in model delete msg + + +changeset: 629:e55d9eebb7cc +user: Damien Elmes +date: Thu Feb 28 00:19:46 2008 +0900 +description: +don't order by card ordinal when sorting, add reset card/fact support + + +changeset: 628:c59c34f13b48 +user: Damien Elmes +date: Tue Feb 26 17:16:35 2008 +0900 +description: +add sorting by question/answer + + +changeset: 627:461973990205 +user: Damien Elmes +date: Mon Feb 25 19:02:10 2008 +0900 +description: +allow 'return' to update current search + + +changeset: 626:99f6db6b344c +user: Damien Elmes +date: Mon Feb 25 16:16:29 2008 +0900 +description: +add support for adding tags and duplicate tags to importing + + +changeset: 625:411d8b9ceb56 +user: Damien Elmes +date: Mon Feb 25 15:34:21 2008 +0900 +description: +run updater with full path + + +changeset: 624:429f3afd8f61 +user: Damien Elmes +date: Sat Feb 23 19:35:51 2008 +0900 +description: +set backup dir based on config dir + + +changeset: 623:a421e2b4a54d +user: Damien Elmes +date: Sat Feb 23 18:05:20 2008 +0900 +description: +update priorities explanations, languages + + +changeset: 622:217f56ab5043 +user: Damien Elmes +date: Sat Feb 23 18:03:19 2008 +0900 +description: +remove horizontal scrollbar from q/aformat, fix reporting of current card in stats + + +changeset: 621:cc8237d5cd7f +user: Damien Elmes +date: Sat Feb 23 15:54:25 2008 +0900 +description: +when no current card, show all cards in editor. change locking behaviour + + +changeset: 620:6c14fdfb5323 +user: Damien Elmes +date: Fri Feb 22 22:40:49 2008 +0900 +description: +get default dir from most recent file, not oldest file + + +changeset: 619:2928ed31eaa7 +user: Damien Elmes +date: Fri Feb 22 22:23:37 2008 +0900 +description: +if unable to load the same deck, load another + + +changeset: 618:f33307fa7f31 +user: Damien Elmes +date: Fri Feb 22 22:16:41 2008 +0900 +description: +lock the deck post-sync + + +changeset: 617:313c0f36541b +user: Damien Elmes +date: Fri Feb 22 21:52:30 2008 +0900 +description: +first time->completely forgot, fix bug in previous patch + + +changeset: 616:22b0f59cb617 +user: Damien Elmes +date: Fri Feb 22 21:48:44 2008 +0900 +description: +fixed 'file referenced before assignment' bug + + +changeset: 615:8645ade4917c +user: Damien Elmes +date: Thu Feb 21 01:09:12 2008 +0900 +description: +add more years to graphs + + +changeset: 614:2834fa64adc2 +user: Damien Elmes +date: Thu Feb 21 00:46:46 2008 +0900 +description: +work when updating both qformat and aformat + + +changeset: 613:56d1edd3e179 +user: Damien Elmes +date: Thu Feb 21 00:08:10 2008 +0900 +description: +fix ordering by card ordinal, mac fix/hack + + +changeset: 612:fcd2f16cfa6b +user: Damien Elmes +date: Wed Feb 20 22:22:33 2008 +0900 +description: +update pysqlite version, fix debian rules + + +changeset: 611:c8129b1081c5 +user: Damien Elmes +date: Wed Feb 20 21:39:26 2008 +0900 +description: +bump version number + + +changeset: 610:deca5ed0f9aa +user: Damien Elmes +date: Wed Feb 20 21:10:23 2008 +0900 +description: +operate on all facts, not just visible ones + + +changeset: 609:63027d29aa27 +user: Damien Elmes +date: Wed Feb 20 20:49:16 2008 +0900 +description: +set homedir to c:\anki if not writeable + + +changeset: 608:85d0975a91f2 +user: Damien Elmes +date: Wed Feb 20 03:26:08 2008 +0900 +description: +make sure to flush changes before refreshing card/fact + + +changeset: 607:1e3c0d610973 +user: Damien Elmes +date: Wed Feb 20 03:20:32 2008 +0900 +description: +reload prefs every time + + +changeset: 606:922ff4115386 +user: Damien Elmes +date: Tue Feb 19 15:54:54 2008 +0900 +description: +simplejson 0.7.3->1.7.3 + + +changeset: 605:4b6968ffe91b +user: Damien Elmes +date: Tue Feb 19 01:46:47 2008 +0900 +description: +store successive for undo + + +changeset: 604:225d9ad1d6ef +user: Damien Elmes +date: Mon Feb 18 20:15:11 2008 +0900 +description: +change status layout + + +changeset: 603:161b247a7b30 +user: Damien Elmes +date: Mon Feb 18 17:22:14 2008 +0900 +description: +when undoing, set reps back one + + +changeset: 602:4bb3c0c8e68e +user: Damien Elmes +date: Sun Feb 17 03:05:07 2008 +0900 +description: +fix synconclose + + +changeset: 601:583c9d0bb649 +user: Damien Elmes +date: Sat Feb 16 23:24:38 2008 +0900 +description: +bump version number + + +changeset: 600:53e54783949d +user: Damien Elmes +date: Sat Feb 16 23:18:21 2008 +0900 +description: +fix renaming + + +changeset: 599:0889ff31a330 +user: Damien Elmes +date: Sat Feb 16 21:19:40 2008 +0900 +description: +fix tags in add cards, unable to change language bug + + +changeset: 598:6b222c73c027 +user: Damien Elmes +date: Sat Feb 16 05:22:28 2008 +0900 +description: +update readme + + +changeset: 597:671f4a9a3ea7 +user: Damien Elmes +date: Sat Feb 16 05:21:26 2008 +0900 +description: +update deb reqs + + +changeset: 596:99d7befeb338 +user: Damien Elmes +date: Sat Feb 16 04:58:46 2008 +0900 +description: +fix lookup + + +changeset: 595:232e760b1356 +user: Damien Elmes +date: Sat Feb 16 04:46:58 2008 +0900 +description: +update translations + + +changeset: 594:946180997437 +user: Damien Elmes +date: Sat Feb 16 04:46:31 2008 +0900 +description: +bump version + + +changeset: 593:5e3e1e33199c +user: Damien Elmes +date: Sat Feb 16 04:38:49 2008 +0900 +description: +don't set modtime when finished, don't congratulate user on failure + + +changeset: 592:ea038a3dc1c4 +user: Damien Elmes +date: Sat Feb 16 00:19:34 2008 +0900 +description: +adjust stats layout, session->daily + + +changeset: 591:927d04b55fc9 +user: Damien Elmes +date: Fri Feb 15 15:07:45 2008 +0900 +description: +display old/new pending at all times, fix mac setup + + +changeset: 590:0f84a5ae76d8 +user: Damien Elmes +date: Wed Feb 13 23:40:30 2008 +0900 +description: +fix syncOnLoad/syncOnClose + + +changeset: 589:76ffc3879164 +user: Damien Elmes +date: Wed Feb 13 23:05:23 2008 +0900 +description: +sync updates, fix fact add/del on , undo fix + + +changeset: 588:215c01c1e153 +user: Damien Elmes +date: Wed Feb 13 02:39:32 2008 +0900 +description: +when a card model is changed, update q/a on every associated card + + +changeset: 587:6bb8ceb4e686 +user: Damien Elmes +date: Wed Feb 13 02:12:05 2008 +0900 +description: +fix ref to tagslist + + +changeset: 586:261e2f50ed95 +user: Damien Elmes +date: Wed Feb 13 01:44:49 2008 +0900 +description: +textChanged=True in fact editor mod, cards->facts in deckprops, fix lastSnc calc + + +changeset: 585:69fd92dcb176 +user: Damien Elmes +date: Mon Feb 11 23:54:07 2008 +0900 +description: +add initial spacing + + +changeset: 584:2fadce8bc54b +user: Damien Elmes +date: Mon Feb 11 18:06:01 2008 +0900 +description: +connect failed card max, bump version number + + +changeset: 583:e0ef97f61872 +user: Damien Elmes +date: Mon Feb 11 16:03:40 2008 +0900 +description: +cardlist fix, implement syncing, don't uniquify model names + + +changeset: 582:9c0d9b5a3bda +user: Damien Elmes +date: Mon Feb 04 23:59:51 2008 +0900 +description: +fix mac paths, make editor non-modal + + +changeset: 581:54a79e90ab29 +user: Damien Elmes +date: Mon Feb 04 19:42:50 2008 +0900 +description: +select text by default in cardlist + + +changeset: 580:459cf3c246ed +user: Damien Elmes +date: Mon Feb 04 19:36:50 2008 +0900 +description: +detect path on win32 better, try different column sizing in cardeditor + + +changeset: 579:4b26c2a0055d +user: Damien Elmes +date: Mon Feb 04 19:08:27 2008 +0900 +description: +bump version number + + +changeset: 578:bf424d1a7bab +user: Damien Elmes +date: Mon Feb 04 16:59:02 2008 +0900 +description: +copy file when using samples + + +changeset: 577:f7df0896cbc5 +user: Damien Elmes +date: Mon Feb 04 16:52:58 2008 +0900 +description: +backups done + + +changeset: 576:68ed2ae725c5 +user: Damien Elmes +date: Mon Feb 04 16:20:42 2008 +0900 +description: +fix autosave + + +changeset: 575:540437a24331 +user: Damien Elmes +date: Mon Feb 04 16:01:17 2008 +0900 +description: +remove saveas + + +changeset: 574:3ea83bc1ee59 +user: Damien Elmes +date: Mon Feb 04 15:20:23 2008 +0900 +description: +numeric fields, fix sorting, optimize sorting +- sorting now detects if a field is numeric and sorts in numeric order +- optimize query using index +- look for .anki, not .Anki etc + + +changeset: 573:54b7c2ea4c57 +user: Damien Elmes +date: Mon Feb 04 14:05:02 2008 +0900 +description: +improve save, remove redundant isReadOnly/hasPath checks + + +changeset: 572:e5361fa56134 +user: Damien Elmes +date: Mon Feb 04 14:01:39 2008 +0900 +description: +sort numerically if column is numeric, default to sorting by created + + +changeset: 571:b7d23db73c18 +user: Damien Elmes +date: Mon Feb 04 11:33:56 2008 +0900 +description: +update ui to reflect relative spacing, readadd spaced card count + + +changeset: 570:8393c8c8e921 +user: Damien Elmes +date: Sun Feb 03 22:30:28 2008 +0900 +description: +call alterShortcuts after loading mainWin + + +changeset: 569:c6b6924bf639 +user: Damien Elmes +date: Sun Feb 03 03:42:24 2008 +0900 +description: +update title bar on save, don't mark deck modified if no cards added + + +changeset: 568:1f19773bef16 +user: Damien Elmes +date: Sun Feb 03 03:28:37 2008 +0900 +description: +add file icons back, support changing order of card models + + +changeset: 567:84614f323ac2 +user: Damien Elmes +date: Sun Feb 03 02:26:21 2008 +0900 +description: +update import old anki file support + + +changeset: 566:22f535ebe13b +user: Damien Elmes +date: Sun Feb 03 02:23:53 2008 +0900 +description: +update importing to use new code + + +changeset: 565:532666c996d2 +user: Damien Elmes +date: Fri Feb 01 20:00:46 2008 +0900 +description: +fix save, graphs on empty, status on empty + + +changeset: 564:63d909a0f80a +user: Damien Elmes +date: Fri Feb 01 19:18:23 2008 +0900 +description: +add anchor handling to main app, make welcome screen more friendly + + +changeset: 563:e71c4b4e5ebe +user: Damien Elmes +date: Fri Feb 01 16:50:04 2008 +0900 +description: +remove glob locking, use file locking, create mediadir only when needed + + +changeset: 562:d6a24980253b +user: Damien Elmes +date: Fri Feb 01 15:51:46 2008 +0900 +description: +new answer prompts + + +changeset: 561:aa06776ac46d +user: Damien Elmes +date: Thu Jan 31 23:20:30 2008 +0900 +description: +orange after yellow in status bar + + +changeset: 560:a412c3ae5d62 +user: Damien Elmes +date: Thu Jan 31 23:18:38 2008 +0900 +description: +start of sync changes + + +changeset: 559:7174b7196aa9 +user: Damien Elmes +date: Wed Jan 30 15:14:40 2008 +0900 +description: +fix exporting + + +changeset: 558:4a54faed295a +user: Damien Elmes +date: Wed Jan 30 02:35:29 2008 +0900 +description: +fix displayproperties + + +changeset: 557:abc311cc107c +user: Damien Elmes +date: Wed Jan 30 02:25:05 2008 +0900 +description: +add hidden 'show suspended cards', make sure to reset in editor + + +changeset: 556:35fab336992b +user: Damien Elmes +date: Wed Jan 30 02:11:56 2008 +0900 +description: +add fact deletion again using bulk code + + +changeset: 555:868c96374041 +user: Damien Elmes +date: Wed Jan 30 02:04:57 2008 +0900 +description: +use cached quesion/answer, bulk tag/delete card functions, display queue msg + + +changeset: 554:c64bc72e02fb +user: Damien Elmes +date: Mon Jan 28 02:40:53 2008 +0900 +description: +add alex's icons + + +changeset: 553:5b5f40ee3f4d +user: Damien Elmes +date: Mon Jan 28 02:39:13 2008 +0900 +description: +don't refresh before flushing any changes + + +changeset: 552:74e46f0b5485 +user: Damien Elmes +date: Mon Jan 28 00:16:00 2008 +0900 +description: +display properties fixed + + +changeset: 551:1f0866e9c336 +user: Damien Elmes +date: Sun Jan 27 23:30:25 2008 +0900 +description: +undo/suspend/mark + + +changeset: 550:0570c936bbd2 +user: Damien Elmes +date: Sun Jan 27 23:00:25 2008 +0900 +description: +fix modelchooser/add cards + + +changeset: 549:284c838234b6 +user: Damien Elmes +date: Sun Jan 27 20:56:23 2008 +0900 +description: +implement deck properties, tweak modelproperties + + +changeset: 548:14f543543e1a +user: Damien Elmes +date: Sat Jan 26 18:07:18 2008 +0900 +description: +implement model properties, define 'spaced cards', add deck refresh support + + +changeset: 547:88d7e917b9c4 +user: Damien Elmes +date: Fri Jan 25 19:08:46 2008 +0900 +description: +editor: everything implemented + + +changeset: 546:39bb5c3088aa +user: Damien Elmes +date: Wed Jan 23 22:59:15 2008 +0900 +description: +most of cardlist reimplemented + + +changeset: 545:9a4be57046d9 +user: Damien Elmes +date: Fri Jan 18 20:21:43 2008 +0900 +description: +integrate new db code, remove preview in prefs, refactor state handling, more +- stats/sync/autosave changes + + +changeset: 544:750b6c95d6a3 +user: Damien Elmes +date: Thu Jan 03 23:03:34 2008 +0900 +description: +handle clearing of help better + + +changeset: 543:a6851fcab7d7 +user: Damien Elmes +date: Thu Jan 03 22:48:27 2008 +0900 +description: +save config file backup to correct dir + + +changeset: 542:4e6bb57831dd +user: Damien Elmes +date: Thu Jan 03 22:46:06 2008 +0900 +description: +fix missing 'medium priority' links + + +changeset: 541:ad27e34ff754 +user: Damien Elmes +date: Thu Jan 03 22:30:05 2008 +0900 +description: +update priorities on add/edit/suspend + + +changeset: 540:239502c75825 +user: Damien Elmes +date: Thu Jan 03 05:03:41 2008 +0900 +description: +bump version number + + +changeset: 539:d984f004421f +user: Damien Elmes +date: Thu Jan 03 02:14:18 2008 +0900 +description: +fix logic bug in autosync + + +changeset: 538:8214d76bc856 +user: Damien Elmes +date: Thu Jan 03 02:10:09 2008 +0900 +description: +preserve selection when losing focus + + +changeset: 537:d31d9ee0f11c +user: Damien Elmes +date: Thu Jan 03 02:07:17 2008 +0900 +description: +disable live update in card editor + + +changeset: 536:362c7dc397eb +user: Damien Elmes +date: Thu Jan 03 02:05:43 2008 +0900 +description: +write to a temp file when writing config + + +changeset: 535:ff850976acb8 +user: Damien Elmes +date: Thu Jan 03 02:01:13 2008 +0900 +description: +catch invalid deck open + + +changeset: 534:914df3502ee7 +user: Damien Elmes +date: Wed Jan 02 21:43:23 2008 +0900 +description: +add czech translation + + +changeset: 533:15e75d3e40e8 +user: Damien Elmes +date: Wed Jan 02 21:39:41 2008 +0900 +description: +repose.cx -> ichi2.net + + +changeset: 532:b8bd4000823e +user: Damien Elmes +date: Mon Dec 31 18:57:38 2007 +0900 +description: +make sure to update priorities in deck editor + + +changeset: 531:fa43f72dfd7d +user: Damien Elmes +date: Sat Dec 29 17:31:22 2007 +0900 +description: +don't include suspended cards in spaced cards number + + +changeset: 530:a7c49c697eca +user: Damien Elmes +date: Fri Dec 28 05:05:28 2007 +0900 +description: +check for updates after loading last deck + + +changeset: 529:9e8ab2dc7c9f +user: Damien Elmes +date: Thu Dec 27 20:21:38 2007 +0900 +description: +fix deb definition to build universal package + + +changeset: 528:031f5820d07b +user: Damien Elmes +date: Thu Dec 27 19:49:47 2007 +0900 +description: +fix card edit table wierdness on win32 + + +changeset: 527:b7867f355da1 +user: Damien Elmes +date: Thu Dec 27 18:01:31 2007 +0900 +description: +include unihan.pickle in mac build, don't set nodeck in view + + +changeset: 526:588535e67c24 +user: Damien Elmes +date: Thu Dec 27 17:28:44 2007 +0900 +description: +enable font colours on mac, move to plastique buttons + + +changeset: 525:70cbd5fdc584 +user: Damien Elmes +date: Thu Dec 27 17:08:51 2007 +0900 +description: +fix 'sync with existing deck' + + +changeset: 524:f83e1e3d8203 +user: Damien Elmes +date: Thu Dec 27 17:01:26 2007 +0900 +description: +allow undo on final card + + +changeset: 523:a1772428285a +user: Damien Elmes +date: Thu Dec 27 16:55:34 2007 +0900 +description: +link to forum + + +changeset: 522:770d0193179a +user: Damien Elmes +date: Thu Dec 27 16:51:08 2007 +0900 +description: +avoid initial state if loading a deck (fix flash of welcome screen) + + +changeset: 521:410f20d86248 +user: Damien Elmes +date: Thu Dec 27 16:38:39 2007 +0900 +description: +fix colour assigning + + +changeset: 520:1389afe90f82 +user: Damien Elmes +date: Thu Dec 27 16:11:12 2007 +0900 +description: +add collapsing/delay2 to deck properties + + +changeset: 519:f961c7bed0a8 +user: Damien Elmes +date: Thu Dec 27 15:49:20 2007 +0900 +description: +update final review message + + +changeset: 518:8171941c3649 +user: Damien Elmes +date: Thu Dec 27 15:44:24 2007 +0900 +description: +don't run psyco on powerpc, set library not add to avoid errors on osx + + +changeset: 517:5a07f1117619 +user: Damien Elmes +date: Wed Dec 26 04:39:58 2007 +0900 +description: +include plugins on mac, use different path + + +changeset: 516:c4b59898691a +user: Damien Elmes +date: Wed Dec 26 04:02:33 2007 +0900 +description: +fix window creep on osx by only restoring size + + +changeset: 515:a3a490bbc131 +user: Damien Elmes +date: Wed Dec 26 03:39:21 2007 +0900 +description: +fix deck/model properties by making modal and show() + + +changeset: 514:65d7017653a4 +user: Damien Elmes +date: Wed Dec 26 03:28:21 2007 +0900 +description: +make a bunch of dialogs proper windows + + +changeset: 513:ed5e6f8cca2a +user: Damien Elmes +date: Wed Dec 26 03:23:11 2007 +0900 +description: +mac requires taller buttons + + +changeset: 512:3e1144723d81 +user: Damien Elmes +date: Wed Dec 26 03:20:20 2007 +0900 +description: +don't give foreground button a parent so it stays hidden on mac + + +changeset: 511:ea3b77c8faf0 +user: Damien Elmes +date: Wed Dec 26 03:17:34 2007 +0900 +description: +change stretch factor on cardlist for mac, remove color sel on mac + + +changeset: 510:18d326614e61 +user: Damien Elmes +date: Wed Dec 26 02:28:30 2007 +0900 +description: +don't show icons in menu bars on mac + + +changeset: 509:331e68fd21c4 +user: Damien Elmes +date: Wed Dec 26 01:42:45 2007 +0900 +description: +merge ianl's ja updates + + +changeset: 508:ec260455758e +user: Damien Elmes +date: Wed Dec 26 01:35:43 2007 +0900 +description: +change plugin search path + + +changeset: 507:feb2e3f0da7e +user: Damien Elmes +date: Wed Dec 26 00:00:19 2007 +0900 +description: +add final review status indicator + + +changeset: 506:0246ef821221 +user: Damien Elmes +date: Tue Dec 25 23:40:43 2007 +0900 +description: +remove help on empty deck + + +changeset: 505:dd968d1ad256 +user: Damien Elmes +date: Tue Dec 25 23:37:30 2007 +0900 +description: +add anki.bat for win32 dev + + +changeset: 504:133710df601e +user: Damien Elmes +date: Tue Dec 25 23:34:23 2007 +0900 +description: +remove scroll to end support, fixes slow status bar update on win32 + + +changeset: 503:b1dab87a66db +user: Damien Elmes +date: Tue Dec 25 23:12:32 2007 +0900 +description: +update jp translations + + +changeset: 502:fac9ea7c0bbf +user: Damien Elmes +date: Tue Dec 25 22:14:49 2007 +0900 +description: +switch from full psyco to profiling + + +changeset: 501:555f40764c8f +user: Damien Elmes +date: Tue Dec 25 22:14:20 2007 +0900 +description: +set the scrollarea widget last, so that win32 updates properly + + +changeset: 500:cfb7ba1ba4cd +user: Damien Elmes +date: Tue Dec 25 05:23:10 2007 +0900 +description: +normalize path before updateRecentFiles() + + +changeset: 499:9077e476e5e1 +user: Damien Elmes +date: Tue Dec 25 04:24:55 2007 +0900 +description: +fix focus issues related to scrollarea + + +changeset: 498:3c993841d04f +user: Damien Elmes +date: Tue Dec 25 04:10:37 2007 +0900 +description: +fix missing latex code + + +changeset: 497:5a3f96a0e31c +user: Damien Elmes +date: Tue Dec 25 04:08:20 2007 +0900 +description: +put a scroll area around the fact editor + + +changeset: 496:99c7ec035760 +user: Damien Elmes +date: Sat Dec 22 05:37:43 2007 +0900 +description: +add final drill help + + +changeset: 495:e94a2f9d4c69 +user: Damien Elmes +date: Sat Dec 22 04:54:20 2007 +0900 +description: +default to card/field name, make sure card names are unique + + +changeset: 494:13dae040b66c +user: Damien Elmes +date: Sat Dec 22 04:38:29 2007 +0900 +description: +fix alc url + + +changeset: 493:4897fba66e2c +user: Damien Elmes +date: Sat Dec 22 04:27:40 2007 +0900 +description: +readd heisig, remove error label, fix loading add cards dialog on new deck + + +changeset: 492:816a8bba39a5 +user: Damien Elmes +date: Sat Dec 22 04:17:27 2007 +0900 +description: +fix bug in copying fields to new model in fact editor + + +changeset: 491:23fad02214e9 +user: Damien Elmes +date: Sat Dec 22 04:07:49 2007 +0900 +description: +correct wording on sync dialog + + +changeset: 490:100c668455f6 +user: Damien Elmes +date: Sat Dec 22 04:05:58 2007 +0900 +description: +use abspath from config file, clean up new file if initial sync fails + + +changeset: 489:30e7031d70f6 +user: Damien Elmes +date: Sat Dec 22 03:56:52 2007 +0900 +description: +new 'new file' dialog that supports loading an existing deck + + +changeset: 488:a72b9736ece5 +user: Damien Elmes +date: Sat Dec 22 02:47:54 2007 +0900 +description: +simplify 'insertion order' in model properties to make behaviour clearer + + +changeset: 487:5fab9752bbe8 +user: Damien Elmes +date: Sat Dec 22 02:21:38 2007 +0900 +description: +fix button3, remove redundant 'reposition' + + +changeset: 486:fc935606609e +user: Damien Elmes +date: Fri Dec 21 23:26:09 2007 +0900 +description: +fix 'add missing cards' bug, use only active cards, not all + + +changeset: 485:40cecb3b31e7 +user: Damien Elmes +date: Fri Dec 21 23:07:34 2007 +0900 +description: +new icons + + +changeset: 484:644e10b7e1ac +user: Damien Elmes +date: Thu Dec 06 19:59:41 2007 +0900 +description: +merge tall buttons code + + +changeset: 483:71f7cd7b9ff6 +user: Damien Elmes +date: Mon Dec 03 17:51:35 2007 +0900 +description: +use the main window instead of help screen in various place + + +changeset: 482:0c7a31c83e4b +user: Damien Elmes +date: Wed Nov 14 11:58:38 2007 +0900 +description: +med priority, latex buttons, font colour button, different focus out handling + + +changeset: 481:0289fe2a735f +user: Damien Elmes +date: Tue Nov 13 17:36:34 2007 +0900 +description: +refactor save/prompt/sync code. autosave on every type of close now + + +changeset: 480:2a323105d70f +user: Damien Elmes +date: Tue Nov 13 16:44:16 2007 +0900 +description: +as of qt4.3, use geometry() not frameGeometry() + + +changeset: 479:bd2f9f5ef39c +user: Damien Elmes +date: Tue Nov 13 16:37:33 2007 +0900 +description: +strip html from cards before searching + + +changeset: 478:99f6a629deae +user: Damien Elmes +date: Tue Nov 13 16:33:00 2007 +0900 +description: +updateViews(status!=noDeck) should not have a null deck + + +changeset: 477:a2f83a4da80b +user: Damien Elmes +date: Mon Nov 12 16:18:18 2007 +0900 +description: +latex support + + +changeset: 476:92f3c4e1f41a +user: Damien Elmes +date: Mon Nov 12 14:37:44 2007 +0900 +description: +support for compact ease button style + + +changeset: 475:842e41e16286 +user: Damien Elmes +date: Mon Nov 12 12:18:45 2007 +0900 +description: +add 'undo last card' menu item +this doesn't adjust the review count + + +changeset: 474:73f7af6306bb +user: Damien Elmes +date: Mon Nov 12 11:14:02 2007 +0900 +description: +improve matplotlib message + + +changeset: 473:8d37157b1cb3 +user: Damien Elmes +date: Mon Nov 12 11:01:06 2007 +0900 +description: +show fact count in addition to card count in deck properties + + +changeset: 472:190827da09ec +user: Damien Elmes +date: Wed Oct 31 13:34:49 2007 +0900 +description: +update french translations, fix some translation bugs + + +changeset: 471:7e3c9d494d30 +user: Damien Elmes +date: Sat Sep 08 19:03:14 2007 +0900 +description: +thinko in mydeck.anki + + +changeset: 470:070b2db9559e +user: Damien Elmes +date: Sat Sep 08 18:53:57 2007 +0900 +description: +fix a win98/me bug + + +changeset: 469:d765877ae9f0 +user: Damien Elmes +date: Fri Sep 07 23:50:15 2007 +0900 +description: +add debian files + + +changeset: 468:368c196c41a5 +user: Damien Elmes +date: Fri Sep 07 22:51:22 2007 +0900 +description: +update desktop entry + + +changeset: 467:cdf030590286 +user: Damien Elmes +date: Fri Sep 07 22:44:25 2007 +0900 +description: +merge french work from laurent steffan, update translations + + +changeset: 466:391cacbb81ee +user: Damien Elmes +date: Fri Sep 07 22:26:46 2007 +0900 +description: +toggle enabled in saveafteraddingnum + + +changeset: 465:3d93f94217a1 +user: Damien Elmes +date: Fri Sep 07 21:52:27 2007 +0900 +description: +add picture/audio tooltips + + +changeset: 464:b0bca2b2f6b2 +user: Damien Elmes +date: Fri Sep 07 21:09:17 2007 +0900 +description: +add "save after adding n items" support + + +changeset: 463:355a8d0927f2 +user: Damien Elmes +date: Fri Sep 07 20:56:29 2007 +0900 +description: +catch exceptions on deck load, 'export to' button, style tweaks + + +changeset: 462:08a4bc4bbfa6 +user: Damien Elmes +date: Thu Sep 06 05:21:51 2007 +0900 +description: +show card stats and not kanji stats on main bar + + +changeset: 461:c022cf6f3974 +user: Damien Elmes +date: Thu Sep 06 05:17:52 2007 +0900 +description: +add desktop file + + +changeset: 460:01a55a77c020 +user: Damien Elmes +date: Thu Sep 06 05:07:52 2007 +0900 +description: +hide models when importing anki documents + + +changeset: 459:a5acbd3ae9fd +user: Damien Elmes +date: Thu Sep 06 04:22:42 2007 +0900 +description: +support appending tags on import + + +changeset: 458:3a3bfd2a119e +user: Damien Elmes +date: Thu Sep 06 04:16:24 2007 +0900 +description: +add tags to importing, don't add cardmodel/model tags to editor + + +changeset: 457:67e6033b6ab3 +user: Damien Elmes +date: Thu Sep 06 03:46:10 2007 +0900 +description: +polish exporting, fix bug in tag completer + + +changeset: 456:87dce4660dd7 +user: Damien Elmes +date: Thu Sep 06 02:43:48 2007 +0900 +description: +refactor tag editor into new file, work on exporting + + +changeset: 455:f030540cbe95 +user: Damien Elmes +date: Thu Sep 06 01:53:49 2007 +0900 +description: +don't use custom colours when editing, only on quiz + + +changeset: 454:b18ba125e482 +user: Damien Elmes +date: Thu Sep 06 01:14:06 2007 +0900 +description: +fix bug in saving new file + + +changeset: 453:ed0567a02e3f +user: Damien Elmes +date: Thu Sep 06 01:12:17 2007 +0900 +description: +don't use native overwrite confirmation + + +changeset: 452:793bcd68ae0c +user: Damien Elmes +date: Thu Sep 06 00:56:43 2007 +0900 +description: +catch error in deck creation too + + +changeset: 451:d4527bd2aa66 +user: Damien Elmes +date: Thu Sep 06 00:50:59 2007 +0900 +description: +delete graphs window on close + + +changeset: 450:ace0595ec4b8 +user: Damien Elmes +date: Thu Sep 06 00:10:08 2007 +0900 +description: +saveas: check if a file exists after adding extension + + +changeset: 449:c28e8c0a83cf +user: Damien Elmes +date: Wed Sep 05 22:38:50 2007 +0900 +description: +provide path to save(), rename tools to utils for consistency + + +changeset: 448:214f6b5a548e +user: Damien Elmes +date: Wed Sep 05 21:23:56 2007 +0900 +description: +prevent images/sounds until saved, don't force user to assign name + + +changeset: 447:c65ef1798409 +user: Damien Elmes +date: Wed Sep 05 21:15:41 2007 +0900 +description: +open the default with a .anki extension, not .fc extension + + +changeset: 446:4aa5a25d2ad7 +user: Damien Elmes +date: Tue Sep 04 23:00:37 2007 +0900 +description: +fix bug in toggling font properties + + +changeset: 445:712721b11c1a +user: Damien Elmes +date: Sun Sep 02 03:46:50 2007 +0900 +description: +remove debug statement in tools, make sure sounds are enabled + + +changeset: 444:027e7afe8e77 +user: Damien Elmes +date: Fri Aug 31 20:37:35 2007 +0900 +description: +don't ask to close the window twice + + +changeset: 443:b15bc65b805b +user: Damien Elmes +date: Fri Aug 31 20:26:22 2007 +0900 +description: +sort tags in cardlist/add cards editor + + +changeset: 442:b71304829679 +user: Damien Elmes +date: Tue Aug 28 23:41:30 2007 +0900 +description: +same for makeDue + + +changeset: 441:5b50518fb0b0 +user: Damien Elmes +date: Tue Aug 28 23:40:56 2007 +0900 +description: +make reset progress output clearer + + +changeset: 440:2d05e24d26ff +user: Damien Elmes +date: Tue Aug 28 15:52:20 2007 +0900 +description: +fix preferences window opening + + +changeset: 439:013e1acb3d21 +user: Damien Elmes +date: Mon Aug 27 12:35:32 2007 +0900 +description: +disable fact editor buttons by default + + +changeset: 438:293661b8e4aa +user: Damien Elmes +date: Sat Aug 25 18:59:38 2007 +0900 +description: +scroll to end of text in main window + + +changeset: 437:7e511b3cb7f8 +user: Damien Elmes +date: Sat Aug 25 17:13:09 2007 +0900 +description: +remove debugging statement + + +changeset: 436:da16e0592cc8 +user: Damien Elmes +date: Sat Aug 25 17:12:36 2007 +0900 +description: +fix delete fact + + +changeset: 435:af428fd67fdb +user: Damien Elmes +date: Sat Aug 25 04:13:53 2007 +0900 +description: +typo in codetoindex + + +changeset: 434:4d75d2f18e66 +user: Damien Elmes +date: Sat Aug 25 02:56:46 2007 +0900 +description: +only reset interface lang if it's empty + + +changeset: 433:191cfd3ee557 +user: Damien Elmes +date: Sat Aug 25 00:41:15 2007 +0900 +description: +remember import location + + +changeset: 432:0b7396d3fc50 +user: Damien Elmes +date: Sat Aug 25 00:39:33 2007 +0900 +description: +remember last location for pictures/sound + + +changeset: 431:72f9c5738848 +user: Damien Elmes +date: Sat Aug 25 00:24:28 2007 +0900 +description: +add missing cards support, delete fact support + + +changeset: 430:f3167aa0d04f +user: Damien Elmes +date: Fri Aug 24 23:49:56 2007 +0900 +description: +update tags each time fact is changed + + +changeset: 429:265384edff2c +user: Damien Elmes +date: Fri Aug 24 23:47:49 2007 +0900 +description: +tag completion support + + +changeset: 428:58e6d236397a +user: Damien Elmes +date: Fri Aug 24 01:02:01 2007 +0900 +description: +set a default lang if none available + + +changeset: 427:f9647ba1c74b +user: Damien Elmes +date: Fri Aug 24 00:58:58 2007 +0900 +description: +fix for python2.4 on mac + + +changeset: 426:908f8c89093d +user: Damien Elmes +date: Thu Aug 23 22:54:58 2007 +0900 +description: +bump version number + + +changeset: 425:ec50da151592 +user: Damien Elmes +date: Thu Aug 23 22:49:55 2007 +0900 +description: +encode the path for saving as unicode + + +changeset: 424:5cd65848bbc4 +user: Damien Elmes +date: Thu Aug 23 06:04:53 2007 +0900 +description: +fix samples on mac, make cardlist a bit bigger + + +changeset: 423:406f78e2303c +user: Damien Elmes +date: Thu Aug 23 02:17:02 2007 +0900 +description: +remove fuzzy tag + + +changeset: 422:8237d9b77adf +user: Damien Elmes +date: Thu Aug 23 02:16:14 2007 +0900 +description: +update jp translation + + +changeset: 421:a26f5c1be19f +user: Damien Elmes +date: Thu Aug 23 02:14:49 2007 +0900 +description: +make wording a little clearer + + +changeset: 420:9342e0a9e746 +user: Damien Elmes +date: Thu Aug 23 02:11:30 2007 +0900 +description: +add missing dutch file + + +changeset: 419:5420eab9b9d4 +user: Damien Elmes +date: Thu Aug 23 02:11:00 2007 +0900 +description: +beginnings of dutch support, update translations + + +changeset: 418:4d9b40fa247c +user: Damien Elmes +date: Thu Aug 23 01:34:05 2007 +0900 +description: +don't set up a specific timer if there is no earlier card + + +changeset: 417:2a168259745c +user: Damien Elmes +date: Thu Aug 23 01:25:06 2007 +0900 +description: +display spacing, delays etc in minutes or days, not seconds + + +changeset: 416:586cd9a79891 +user: Damien Elmes +date: Thu Aug 23 01:19:30 2007 +0900 +description: +make sync prompt a little clearer + + +changeset: 415:23e05536f833 +user: Damien Elmes +date: Thu Aug 23 01:18:55 2007 +0900 +description: +pick correct save dir, load new deck if old not avail, update model docs + + +changeset: 414:20690b29f65b +user: Damien Elmes +date: Thu Aug 23 01:01:16 2007 +0900 +description: +load a new deck instead of an empty window + + +changeset: 413:534a91329a30 +user: Damien Elmes +date: Thu Aug 23 00:59:05 2007 +0900 +description: +ensure a deck always has a path, use defaultdir for saving + + +changeset: 412:13218b679a96 +user: Damien Elmes +date: Thu Aug 23 00:34:51 2007 +0900 +description: +more stats on cardlist, add sorting by card ease + + +changeset: 411:c6d873f3dc55 +user: Damien Elmes +date: Wed Aug 22 23:24:50 2007 +0900 +description: +bump version + + +changeset: 410:fb62af1ae45d +user: Damien Elmes +date: Wed Aug 22 23:24:02 2007 +0900 +description: +sound support + + +changeset: 409:892bbe789cff +user: Damien Elmes +date: Wed Aug 22 20:59:09 2007 +0900 +description: +add image support, move to pyqt4.3 ui + + +changeset: 408:dd346aac0a31 +user: Damien Elmes +date: Wed Aug 22 15:55:05 2007 +0900 +description: +make sure to update modtime when changing card/fact tags + + +changeset: 407:5a0535dcbfae +user: Damien Elmes +date: Sat Aug 18 23:40:58 2007 +0900 +description: +don't display confirmation message twice in addcards + + +changeset: 406:3e9643261e96 +user: Damien Elmes +date: Sat Aug 18 23:40:45 2007 +0900 +description: +fix sync freeze bug + + +changeset: 405:9e4cbbb6aa46 +user: Damien Elmes +date: Sat Aug 18 04:14:21 2007 +0900 +description: +new url for documentation + + +changeset: 404:e0b74b9b7f60 +user: Damien Elmes +date: Sat Aug 18 01:34:29 2007 +0900 +description: +simplify language handling and accomodation autodetection code + + +changeset: 403:dbc9fa403096 +user: Damien Elmes +date: Sat Aug 18 01:17:08 2007 +0900 +description: +ask user if they want to close add cards if data has been input + + +changeset: 402:3229198c259f +user: Damien Elmes +date: Sat Aug 18 00:26:07 2007 +0900 +description: +card tags -> card specific tags + + +changeset: 401:14a9151f7ad8 +user: Damien Elmes +date: Sat Aug 18 00:17:34 2007 +0900 +description: +save card tags properly + + +changeset: 400:812f07a15160 +user: Damien Elmes +date: Fri Aug 17 23:57:08 2007 +0900 +description: +pull up saveas dialog on new file when trying to sync + + +changeset: 399:cab4b16da139 +user: Damien Elmes +date: Fri Aug 17 22:04:38 2007 +0900 +description: +set shownFinished on closing deck + + +changeset: 398:ee09fa6916c0 +user: Damien Elmes +date: Fri Aug 17 21:20:04 2007 +0900 +description: +convert old recent deck paths to unicode + + +changeset: 397:177872003718 +user: Damien Elmes +date: Fri Aug 17 20:38:12 2007 +0900 +description: +change description of ease 2 + + +changeset: 396:2bebd56b1862 +user: Damien Elmes +date: Thu Aug 16 03:32:55 2007 +0900 +description: +fix kanji stats + + +changeset: 395:0928bca9200e +user: Damien Elmes +date: Wed Aug 15 23:46:46 2007 +0900 +description: +delay in hours, win98 support, mac sync fix + + +changeset: 394:bdb67a6ee213 +user: Damien Elmes +date: Wed Aug 15 03:49:57 2007 +0900 +description: +mention translators + + +changeset: 393:ce926f5b34e5 +user: Damien Elmes +date: Wed Aug 15 03:47:49 2007 +0900 +description: +update development readme, fix setuptools (icons_rc) + + +changeset: 392:530333fcdc4a +user: Damien Elmes +date: Wed Aug 15 03:38:46 2007 +0900 +description: +update readme + + +changeset: 391:c6299137ab65 +user: Damien Elmes +date: Wed Aug 15 03:31:13 2007 +0900 +description: +add missing jp trans, fix samples on win32 + + +changeset: 390:c44fd5535b76 +user: Damien Elmes +date: Wed Aug 15 02:54:27 2007 +0900 +description: +allow libanki as a subdir, too + + +changeset: 389:1c7decdafffe +user: Damien Elmes +date: Wed Aug 15 02:48:00 2007 +0900 +description: +move icons location in build_ui, fix presentation bugs on osx in prefs + + +changeset: 388:4ba7b5deef75 +user: Damien Elmes +date: Wed Aug 15 02:41:30 2007 +0900 +description: +move search for kakasi to support code + + +changeset: 387:cb29bcbf0222 +user: Damien Elmes +date: Wed Aug 15 02:11:17 2007 +0900 +description: +update help message + + +changeset: 386:492e42eb9133 +user: Damien Elmes +date: Wed Aug 15 01:47:54 2007 +0900 +description: +use fully qualified package names in ankiqt + + +changeset: 385:195e54a8adb9 +user: Damien Elmes +date: Wed Aug 15 00:14:59 2007 +0900 +description: +one more es fix + + +changeset: 384:69e1293bdb26 +user: Damien Elmes +date: Wed Aug 15 00:13:18 2007 +0900 +description: +more spanish fixes from pcsl + + +changeset: 383:6b1842c94311 +user: Damien Elmes +date: Wed Aug 15 00:07:33 2007 +0900 +description: +fix typo in spanish translation + + +changeset: 382:ef8687286a35 +user: Damien Elmes +date: Tue Aug 14 23:19:19 2007 +0900 +description: +add spanish support + + +changeset: 381:0aac2e3d6261 +user: Damien Elmes +date: Tue Aug 14 23:17:42 2007 +0900 +description: +add spanish translation + + +changeset: 380:27ce0941f81d +user: Damien Elmes +date: Tue Aug 14 07:20:20 2007 +0900 +description: +more work on getting the mac to build again + + +changeset: 379:7993303b3381 +user: Damien Elmes +date: Tue Aug 14 05:55:02 2007 +0900 +description: +keep the 'next time' at a small font, tweak prefs dialog + + +changeset: 378:88190c8b8560 +parent: 376:611a9a55e2f4 +parent: 377:49a3a9cab6a8 +user: Damien Elmes +date: Tue Aug 14 05:33:54 2007 +0900 +description: +merge with other box + + +changeset: 377:49a3a9cab6a8 +parent: 375:98d005d15ae3 +user: Damien Elmes +date: Tue Aug 14 05:14:59 2007 +0900 +description: +update translations + + +changeset: 376:611a9a55e2f4 +user: Damien Elmes +date: Tue Aug 14 05:33:23 2007 +0900 +description: +adjustable card editor font, remove unclickable labels, more translations + + +changeset: 375:98d005d15ae3 +user: Damien Elmes +date: Tue Aug 14 04:47:25 2007 +0900 +description: +tweak to work with py2exe again + + +changeset: 374:e957f6b915f0 +user: Damien Elmes +date: Tue Aug 14 00:25:19 2007 +0900 +description: +add german translation from frostschutz + + +changeset: 373:877c462b1344 +user: Damien Elmes +date: Tue Aug 14 00:24:35 2007 +0900 +description: +update french and german translations + + +changeset: 372:d7c6ebe4922d +user: Damien Elmes +date: Mon Aug 13 19:34:14 2007 +0900 +description: +accept multiple tags when adding/removing + + +changeset: 371:aa7e390faa6d +user: Damien Elmes +date: Mon Aug 13 15:57:35 2007 +0900 +description: +require corresponding libanki version + + +changeset: 370:82f33d4bd13e +user: Damien Elmes +date: Mon Aug 13 10:54:22 2007 +0900 +description: +don't zip; bundle locale in egg + + +changeset: 369:053c7e7ec9fc +user: Damien Elmes +date: Mon Aug 13 10:22:14 2007 +0900 +description: +add ianl's translation work, fix reference to create account + + +changeset: 368:cb33ecc043dd +user: Damien Elmes +date: Mon Aug 13 09:56:55 2007 +0900 +description: +tab order in preferences + + +changeset: 367:9de9f904d752 +user: Damien Elmes +date: Mon Aug 13 09:55:49 2007 +0900 +description: +bug in displayprops, bugs in translation, missing reset progress hook + + +changeset: 366:c2a3c17d1bbc +user: Damien Elmes +date: Mon Aug 13 08:07:07 2007 +0900 +description: +fr->de + + +changeset: 365:269d63c402a4 +user: Damien Elmes +date: Mon Aug 13 07:58:19 2007 +0900 +description: +add missing idx + + +changeset: 364:7bbcbc9d123f +user: Damien Elmes +date: Mon Aug 13 07:35:45 2007 +0900 +description: +typo + + +changeset: 363:4dc36ef23f90 +user: Damien Elmes +date: Mon Aug 13 07:32:04 2007 +0900 +description: +update translation docs + + +changeset: 362:6d7d286d9970 +user: Damien Elmes +date: Mon Aug 13 07:01:54 2007 +0900 +description: +add german and french support + + +changeset: 361:d2a0ab4f9439 +user: Damien Elmes +date: Mon Aug 13 06:18:07 2007 +0900 +description: +require the same libanki version, look for development version + + +changeset: 360:b7b5c26bac6f +user: Damien Elmes +date: Mon Aug 13 05:03:50 2007 +0900 +description: +typo in help + + +changeset: 359:bd1b3ac8fd4f +user: Damien Elmes +date: Mon Aug 13 05:02:50 2007 +0900 +description: +update install docs + + +changeset: 358:85b1ea398857 +user: Damien Elmes +date: Mon Aug 13 04:59:49 2007 +0900 +description: +refactor for packaging in setuptools + + +changeset: 357:56a112de7d22 +user: Damien Elmes +date: Mon Aug 13 04:14:28 2007 +0900 +description: +make toolbar display customizable + + +changeset: 356:b972765487aa +user: Damien Elmes +date: Mon Aug 13 04:03:50 2007 +0900 +description: +remove useless horizontal scrollbar + + +changeset: 355:2ec838779daf +user: Damien Elmes +date: Mon Aug 13 04:00:48 2007 +0900 +description: +convert

into two br tags + + +changeset: 354:1465277f3644 +user: Damien Elmes +date: Mon Aug 13 04:00:12 2007 +0900 +description: +replace verbose qt paragraph formatting with simple br tags + + +changeset: 353:86e628f85a2c +user: Damien Elmes +date: Mon Aug 13 03:39:07 2007 +0900 +description: +adjust formatting buttons on focus in/out, remove selection + + +changeset: 352:ec066b636368 +user: Damien Elmes +date: Mon Aug 13 03:12:15 2007 +0900 +description: +instead of inserting text (which includes images), strip the html + + +changeset: 351:d59591eaae95 +user: Damien Elmes +date: Mon Aug 13 02:55:59 2007 +0900 +description: +strip html from incoming text + + +changeset: 350:8f01b5c3995d +user: Damien Elmes +date: Sun Aug 12 22:56:50 2007 +0900 +description: +add note about translating + + +changeset: 349:5ec222996326 +user: Damien Elmes +date: Sun Aug 12 22:36:42 2007 +0900 +description: +document inheritence in display properties better + + +changeset: 348:16da1b6a10f7 +user: Damien Elmes +date: Sun Aug 12 22:10:06 2007 +0900 +description: +fix bug in importing dialog, doc updates + + +changeset: 347:2ac7774083ed +user: Damien Elmes +date: Sun Aug 12 06:24:27 2007 +0900 +description: +when updating fields, make sure to use html + + +changeset: 346:192c9d3d6be9 +user: Damien Elmes +date: Sun Aug 12 06:19:46 2007 +0900 +description: +redraw on font change, to prevent sticking + + +changeset: 345:db83a251147a +user: Damien Elmes +date: Sun Aug 12 05:24:42 2007 +0900 +description: +make updating stay on screen + + +changeset: 344:7af3fc95ae26 +user: Damien Elmes +date: Sun Aug 12 05:21:02 2007 +0900 +description: +update help + + +changeset: 343:68f2c6a863cf +user: Damien Elmes +date: Sun Aug 12 05:19:24 2007 +0900 +description: +bump version number + + +changeset: 342:c63d0ea37856 +user: Damien Elmes +date: Sun Aug 12 05:19:09 2007 +0900 +description: +fix display properties + + +changeset: 341:fc74e896c4da +user: Damien Elmes +date: Sun Aug 12 05:17:18 2007 +0900 +description: +fix layout in importing + + +changeset: 340:e06e88cd417a +user: Damien Elmes +date: Sun Aug 12 05:14:34 2007 +0900 +description: +allow editing of active cards in add cards dialog + + +changeset: 339:b80bf76e5bf4 +user: Damien Elmes +date: Sun Aug 12 04:07:50 2007 +0900 +description: +remove silly-looking close button from edit window + + +changeset: 338:86a11af953cf +user: Damien Elmes +date: Sun Aug 12 04:00:32 2007 +0900 +description: +update help + + +changeset: 337:fa3c69b3c387 +user: Damien Elmes +date: Sun Aug 12 03:54:37 2007 +0900 +description: +remove redundant font button from addcards + + +changeset: 336:0e1fcd20450f +user: Damien Elmes +date: Sun Aug 12 03:49:58 2007 +0900 +description: +bold/italic/underline + + +changeset: 335:095717cf2d0f +user: Damien Elmes +date: Sat Aug 11 21:14:11 2007 +0900 +description: +restore RET event filter (we'll see how it goes..) + + +changeset: 334:41153041387a +user: Damien Elmes +date: Sat Aug 11 19:39:39 2007 +0900 +description: +fix qt 4.3 bugs + + +changeset: 333:64cb07d65387 +user: Damien Elmes +date: Sat Aug 11 02:55:28 2007 +0900 +description: +deck properties should expand + + +changeset: 332:35a6721d4b48 +user: Damien Elmes +date: Fri Aug 10 23:30:21 2007 +0900 +description: +silly mistake in drawing the deck finished message fixed + + +changeset: 331:059eca315002 +user: Damien Elmes +date: Fri Aug 10 21:44:43 2007 +0900 +description: +need slightly bigger spacing & margins for mac osx + + +changeset: 330:71eb6fbf521f +user: Damien Elmes +date: Fri Aug 10 21:28:33 2007 +0900 +description: +more translation fixes + + +changeset: 329:dc6fb6d71747 +user: Damien Elmes +date: Fri Aug 10 21:16:05 2007 +0900 +description: +update some of the translations + + +changeset: 328:951c056099e7 +user: Damien Elmes +date: Fri Aug 10 21:06:05 2007 +0900 +description: +adjust button spacing, make 'how well did you remember?' smaller + + +changeset: 327:50d9280a9a13 +user: Damien Elmes +date: Fri Aug 10 20:58:24 2007 +0900 +description: +fix dynamic updating of _ strings + + +changeset: 326:ab8f11bb30b3 +user: Damien Elmes +date: Fri Aug 10 20:44:40 2007 +0900 +description: +change wording + + +changeset: 325:439ff0c4e399 +user: Damien Elmes +date: Fri Aug 10 20:38:02 2007 +0900 +description: +only warn about dll files on win32 + + +changeset: 324:207eb512c1d2 +user: Damien Elmes +date: Fri Aug 10 20:30:17 2007 +0900 +description: +add an extra icon + + +changeset: 323:1f52d4b7dac4 +user: Damien Elmes +date: Fri Aug 10 20:27:02 2007 +0900 +description: +bump version + + +changeset: 322:1f2532bdff0f +user: Damien Elmes +date: Fri Aug 10 20:22:18 2007 +0900 +description: +batch add/delete tags, reset progress, make due + + +changeset: 321:23c453c4520f +user: Damien Elmes +date: Fri Aug 10 19:16:04 2007 +0900 +description: +don't default to close on RET in cardlist + + +changeset: 320:a0806b5c4e0a +user: Damien Elmes +date: Fri Aug 10 19:13:31 2007 +0900 +description: +the main window has to have focus or you can't copy on win32, fix yellow + + +changeset: 319:7da4b504d14f +user: Damien Elmes +date: Fri Aug 10 18:56:52 2007 +0900 +description: +sorting, card editor tweaks + + +changeset: 318:72188f29b524 +user: Damien Elmes +date: Fri Aug 10 18:16:03 2007 +0900 +description: +show spaced cards in status bar too, correct algorithm + + +changeset: 317:f300b9161a79 +user: Damien Elmes +date: Fri Aug 10 18:05:10 2007 +0900 +description: +show suspended/waiting in deck finished message + + +changeset: 316:9f11d36690ae +user: Damien Elmes +date: Fri Aug 10 17:36:08 2007 +0900 +description: +load last question as soon as it's expired + + +changeset: 315:3816dd800133 +user: Damien Elmes +date: Fri Aug 10 14:53:21 2007 +0900 +description: +prevent closing if failure halfway through sync + + +changeset: 314:0f58b22be50b +user: Damien Elmes +date: Fri Aug 10 14:48:50 2007 +0900 +description: +report errors halfway through sync + + +changeset: 313:781ca44bf003 +user: Damien Elmes +date: Fri Aug 10 00:22:48 2007 +0900 +description: +move to 'initial' in sync, as deck may already have been closed + + +changeset: 312:aa492cb0c4a2 +user: Damien Elmes +date: Thu Aug 09 04:48:18 2007 +0900 +description: +use different samples path on windows, hide .fc files + + +changeset: 311:b0c8537f4467 +user: Damien Elmes +date: Thu Aug 09 03:26:09 2007 +0900 +description: +try and handle old non-unicode recentDeckPaths + + +changeset: 310:fcba0a38e002 +user: Damien Elmes +date: Thu Aug 09 03:23:34 2007 +0900 +description: +save the file after upgrading to update recentdeckpaths + + +changeset: 309:3bffbb6bfe89 +user: Damien Elmes +date: Thu Aug 09 03:14:41 2007 +0900 +description: +change config file location back to ~/.anki + + +changeset: 308:9ed15e8202c7 +user: Damien Elmes +date: Thu Aug 09 03:14:04 2007 +0900 +description: +change sample deck path on mac to reflect release + + +changeset: 307:4c868be79209 +user: Damien Elmes +date: Thu Aug 09 03:10:42 2007 +0900 +description: +app version -> 0.3.0 + + +changeset: 306:e0f71a60c867 +user: Damien Elmes +date: Wed Aug 08 19:47:04 2007 +0900 +description: +refactor importing code into libanki + + +changeset: 305:9e6b5ee93ad7 +user: Damien Elmes +date: Mon Aug 06 06:28:20 2007 +0900 +description: +stop window oscillating by increasing help size, highPri->lowPri + + +changeset: 304:142190eba8ff +user: Damien Elmes +date: Sun Aug 05 02:58:23 2007 +0900 +description: +add missing files + + +changeset: 303:4f04b38980d7 +user: Damien Elmes +date: Sun Aug 05 02:49:49 2007 +0900 +description: +redundant br in cardlist + + +changeset: 302:cf0ca1c2d387 +user: Damien Elmes +date: Sun Aug 05 02:46:59 2007 +0900 +description: +do numeric sort if possible + + +changeset: 301:4292e8db6665 +user: Damien Elmes +date: Sun Aug 05 02:39:35 2007 +0900 +description: +cardlist tweaks: new sorting method, clarify tags + + +changeset: 300:3ea45a19f74e +user: Damien Elmes +date: Sat Aug 04 22:20:37 2007 +0900 +description: +change upgrade message + + +changeset: 299:1467219f39d9 +user: Damien Elmes +date: Sat Aug 04 15:08:28 2007 +0900 +description: +show time info in stats too + + +changeset: 298:9db26a6e277e +user: Damien Elmes +date: Sat Aug 04 05:34:07 2007 +0900 +description: +question/answer->expression/meaning in lookup + + +changeset: 297:567f70a615d3 +user: Damien Elmes +date: Sat Aug 04 04:46:18 2007 +0900 +description: +strip \ns too in cardlist + + +changeset: 296:affab22d7aae +user: Damien Elmes +date: Sat Aug 04 03:16:27 2007 +0900 +description: +fix warning about psyco on mac + + +changeset: 295:8304d6d47c83 +user: Damien Elmes +date: Sat Aug 04 01:18:05 2007 +0900 +description: +implement font substitution + + +changeset: 294:f8fe1944c6ac +user: Damien Elmes +date: Sat Aug 04 00:39:59 2007 +0900 +description: +display auto default buttons + + +changeset: 293:414bea7e1de3 +user: Damien Elmes +date: Fri Aug 03 19:27:21 2007 +0900 +description: +suspend card -> suspend fact + + +changeset: 292:4a9fc4d45e01 +user: Damien Elmes +date: Fri Aug 03 18:53:00 2007 +0900 +description: +work around windows sometimes reporting an indefinite number of pending evts + + +changeset: 291:2ff4ac1d4f08 +user: Damien Elmes +date: Fri Aug 03 18:35:14 2007 +0900 +description: +don't pass the language catalog name to gettext as a unicode string + + +changeset: 290:be1cca3e012b +user: Damien Elmes +date: Thu Aug 02 04:49:24 2007 +0900 +description: +when recording recent files, make sure to write them in unicode + + +changeset: 289:ae21e1088d40 +user: Damien Elmes +date: Thu Aug 02 03:32:34 2007 +0900 +description: +check facts on add start, don't mark viewed cards modified in editor + + +changeset: 288:3d8896a88928 +user: Damien Elmes +date: Wed Aug 01 12:14:35 2007 +0900 +description: +fix references to field and cardmodel in modelprops + + +changeset: 287:70c51ffd756b +user: Damien Elmes +date: Wed Aug 01 12:08:30 2007 +0900 +description: +encode new deck name & cmd args + + +changeset: 286:1bc35cceac84 +user: Damien Elmes +date: Wed Aug 01 11:55:00 2007 +0900 +description: +don't mark invalid fields until they're edited + + +changeset: 285:9bdf6c71494d +user: Damien Elmes +date: Wed Aug 01 09:39:35 2007 +0900 +description: +updates for libanki changes + + +changeset: 284:49bde442ad65 +user: Damien Elmes +date: Tue Jul 31 06:07:12 2007 +0900 +description: +strip spaces in editor, upgrade mac decks, warn about missing dll + + +changeset: 283:43af18352d4c +user: Damien Elmes +date: Tue Jul 31 03:59:27 2007 +0900 +description: +use full psyco + + +changeset: 282:d5995e759fa6 +user: Damien Elmes +date: Tue Jul 31 03:41:13 2007 +0900 +description: +use psyco for big speedups, don't clobber files when upgrading from .fc + + +changeset: 281:a76c459a7ca4 +user: Damien Elmes +date: Tue Jul 31 02:49:58 2007 +0900 +description: +update titlebar on save + + +changeset: 280:6055dcead896 +parent: 278:1b8ffad57cbd +parent: 279:ffc59ba3f603 +user: Damien Elmes +date: Tue Jul 31 02:24:02 2007 +0900 +description: +merge + + +changeset: 279:ffc59ba3f603 +parent: 275:df5cf5b49e3b +user: Damien Elmes +date: Mon Jul 30 23:57:10 2007 +0900 +description: +decode the config dir as the file system encoding + + +changeset: 278:1b8ffad57cbd +user: Damien Elmes +date: Tue Jul 31 02:19:15 2007 +0900 +description: +catch old deck errors + + +changeset: 277:5fef7bb7fec3 +user: Damien Elmes +date: Tue Jul 31 02:04:39 2007 +0900 +description: +use same tags as last add + + +changeset: 276:9613928c8b9f +user: Damien Elmes +date: Tue Jul 31 00:59:52 2007 +0900 +description: +add facts metadata to sync + + +changeset: 275:df5cf5b49e3b +user: Damien Elmes +date: Sun Jul 29 20:25:57 2007 +0900 +description: +fix sync error check + + +changeset: 274:d77a8971defa +user: Damien Elmes +date: Sun Jul 29 15:12:47 2007 +0900 +description: +update translations + + +changeset: 273:b84ab33580f7 +user: Damien Elmes +date: Sat Jul 28 04:04:30 2007 +0900 +description: +missing sync code, bugfix + + +changeset: 272:d18ed3533521 +user: Damien Elmes +date: Sat Jul 28 01:11:03 2007 +0900 +description: +enable syncing (to localhost), disable main until finished, name chooser + + +changeset: 271:7b52c0735027 +user: Damien Elmes +date: Fri Jul 27 21:13:53 2007 +0900 +description: +modify title bar format + + +changeset: 270:db8f089e638d +user: Damien Elmes +date: Fri Jul 27 20:48:15 2007 +0900 +description: +work on syncing, delete deck name as its redundant + + +changeset: 269:16e8ac744bd6 +user: Damien Elmes +date: Fri Jul 27 15:35:42 2007 +0900 +description: +add a spliter to cardlist + + +changeset: 268:ab09dbe41eef +user: Damien Elmes +date: Fri Jul 27 03:59:27 2007 +0900 +description: +don't make button3 default + + +changeset: 267:b301c1f37b7f +user: Damien Elmes +date: Thu Jul 26 00:13:00 2007 +0900 +description: +postponed->suspended, set no field if user hits esc on addfield in importing + + +changeset: 266:aba3db119c3c +user: Damien Elmes +date: Wed Jul 25 23:12:48 2007 +0900 +description: +mac hacks: change toolbar style + + +changeset: 265:c994b4c14d11 +user: Damien Elmes +date: Wed Jul 25 22:46:53 2007 +0900 +description: +use field features to define kakasi src/dst, rm 2nd display properties link + + +changeset: 264:8d542a382b0a +user: Damien Elmes +date: Wed Jul 25 22:36:19 2007 +0900 +description: +catch ret in add models, send accepted + + +changeset: 263:6860ae408e31 +user: Damien Elmes +date: Wed Jul 25 11:43:02 2007 +0900 +description: +don't lowercase file, just extension + + +changeset: 262:4782c7afee0b +user: Damien Elmes +date: Wed Jul 25 00:42:09 2007 +0900 +description: +if the current tag has been deleted, use idx 0 + + +changeset: 261:100022d60250 +user: Damien Elmes +date: Tue Jul 24 02:04:14 2007 +0900 +description: +default to 'english' when importing old decks that are not japanese + + +changeset: 260:433b6b4f19ea +user: Damien Elmes +date: Tue Jul 24 00:29:17 2007 +0900 +description: +fix import bug, prompt for model on new file + + +changeset: 259:fd1c43f17bba +user: Damien Elmes +date: Mon Jul 23 23:29:10 2007 +0900 +description: +report real delay not 10 minutes + + +changeset: 258:d96fa8eb2298 +user: Damien Elmes +date: Mon Jul 23 23:18:30 2007 +0900 +description: +filter out (invalid) images from pastes + + +changeset: 257:e891349d498d +user: Damien Elmes +date: Mon Jul 23 12:42:08 2007 +0900 +description: +fix showWarning calls, handle missing fields more gracefully when upgrading + + +changeset: 256:4dbaa5fe1378 +user: Damien Elmes +date: Mon Jul 23 11:54:25 2007 +0900 +description: +use different config file temporarily + + +changeset: 255:4d12225b5bfa +user: Damien Elmes +date: Mon Jul 23 11:49:38 2007 +0900 +description: +update cards after field rename since they may have changed, strip + + +changeset: 254:fdc172283931 +user: Damien Elmes +date: Mon Jul 23 11:14:53 2007 +0900 +description: +disable mark/suspend if there's no current card + + +changeset: 253:0d7f50179d71 +user: Damien Elmes +date: Mon Jul 23 02:14:15 2007 +0900 +description: +load the default model if no card is available + + +changeset: 252:18474dbc3e00 +user: Damien Elmes +date: Mon Jul 23 02:13:07 2007 +0900 +description: +make displayproperties bigger for the mac + + +changeset: 251:0f0fe251b176 +user: Damien Elmes +date: Mon Jul 23 00:26:54 2007 +0900 +description: +another mac sample change + + +changeset: 250:3855a24554a3 +user: Damien Elmes +date: Mon Jul 23 00:25:30 2007 +0900 +description: +mac samples bug + + +changeset: 249:8c67e6ee6b96 +user: Damien Elmes +date: Mon Jul 23 00:22:39 2007 +0900 +description: +bug in saveas + + +changeset: 248:137682844e59 +user: Damien Elmes +date: Sun Jul 22 23:38:54 2007 +0900 +description: +use .anki instead of .fc + + +changeset: 247:9feb9368bd10 +user: Damien Elmes +date: Sun Jul 22 22:35:55 2007 +0900 +description: +fix off-by-one in loadRecent + + +changeset: 246:803b406d520c +user: Damien Elmes +date: Sun Jul 22 22:33:42 2007 +0900 +description: +fix lookup (isJapaneseText) + + +changeset: 245:14bdee2478f5 +user: Damien Elmes +date: Sun Jul 22 21:19:39 2007 +0900 +description: +fix bug in modelchooser + + +changeset: 244:6bdf78695d23 +user: Damien Elmes +date: Sun Jul 22 20:58:19 2007 +0900 +description: +if all cards are deleted, show the correct screen + + +changeset: 243:e17d9a38327f +user: Damien Elmes +date: Sun Jul 22 20:46:08 2007 +0900 +description: +update mac build script + + +changeset: 242:869ed9968726 +user: Damien Elmes +date: Sun Jul 22 20:44:36 2007 +0900 +description: +use new style fact when importing + + +changeset: 241:e365a9df577b +user: Damien Elmes +date: Sun Jul 22 20:15:55 2007 +0900 +description: +reposition support, last card tweaks + + +changeset: 240:08223ce8e2ed +user: Damien Elmes +date: Sun Jul 22 02:36:43 2007 +0900 +description: +tagged->marked, change help + + +changeset: 239:45e4f89c9771 +user: Damien Elmes +date: Sun Jul 22 02:24:29 2007 +0900 +description: +no 'added mark', more deckrelated actions, bug in modelproperties + + +changeset: 238:7979f29ef2ee +user: Damien Elmes +date: Sat Jul 21 17:15:34 2007 +0900 +description: +bug in suspendedtags, fix tab order on a bunch of dialogs + + +changeset: 237:6d2b1b818742 +user: Damien Elmes +date: Sat Jul 21 02:59:30 2007 +0900 +description: +bug in suspended/mistaken differentiation + + +changeset: 236:bd9346836fdd +user: Damien Elmes +date: Sat Jul 21 02:55:14 2007 +0900 +description: +change wording for suspended/marked, remove simple status text + + +changeset: 235:344295ec5e67 +user: Damien Elmes +date: Sat Jul 21 01:24:44 2007 +0900 +description: +use a unicode middle dot to delinierate fields + + +changeset: 234:cb83d41a65b4 +user: Damien Elmes +date: Sat Jul 21 01:19:14 2007 +0900 +description: +priorities, postponing, tagging, statustips, icons, flickering, 2 bugs + + +changeset: 233:500adc240b60 +user: Damien Elmes +date: Fri Jul 20 21:23:44 2007 +0900 +description: +improve tag display in cardlist, add tooltips + + +changeset: 232:73aee372cb33 +user: Damien Elmes +date: Fri Jul 20 18:25:43 2007 +0900 +description: +displayproperties size, css bug, model properties change + + +changeset: 231:42fdfd832b60 +user: Damien Elmes +date: Fri Jul 20 12:57:47 2007 +0900 +description: +make prefs non-modal + + +changeset: 230:8892549fde1c +user: Damien Elmes +date: Fri Jul 20 12:49:22 2007 +0900 +description: +support left/right alignment, close all dialogs on exit + + +changeset: 229:c17e58a8fcaa +user: Damien Elmes +date: Thu Jul 19 05:02:59 2007 +0900 +description: +fact fields section in displayproperties expand + + +changeset: 228:c91dc1e20ac4 +user: Damien Elmes +date: Thu Jul 19 01:30:45 2007 +0900 +description: +mark cards/fields as modified in displayproperties + + +changeset: 227:61a5e72211be +user: Damien Elmes +date: Thu Jul 19 01:25:10 2007 +0900 +description: +update modified properly in modelproperties + + +changeset: 226:869c076fe16e +user: Damien Elmes +date: Thu Jul 19 00:18:34 2007 +0900 +description: +add samples menu option + + +changeset: 225:a7c829fb850f +user: Damien Elmes +date: Thu Jul 19 00:14:45 2007 +0900 +description: +update saving code, add recent files menu + + +changeset: 224:62ca2d63f2ad +user: Damien Elmes +date: Wed Jul 18 22:27:29 2007 +0900 +description: +update tags in editor in real time + + +changeset: 223:ff99f675327d +user: Damien Elmes +date: Wed Jul 18 22:17:37 2007 +0900 +description: +updates from refactor, prevent cards/models/fields from being empty/dupe + + +changeset: 222:bfe4b124e29e +user: Damien Elmes +date: Wed Jul 18 00:15:19 2007 +0900 +description: +only downcase file portion when saving + + +changeset: 221:e7cef0026f3c +user: Damien Elmes +date: Tue Jul 17 04:06:45 2007 +0900 +description: +fix esc on display and graphs + + +changeset: 220:10e742d155ec +user: Damien Elmes +date: Tue Jul 17 03:41:20 2007 +0900 +description: +make add cards and edit deck full, non-modal windows + + +changeset: 219:4122aadaf78a +user: Damien Elmes +date: Tue Jul 17 02:30:12 2007 +0900 +description: +tweaks to displaypreferences, make graphs and displayeditor non-modal + + +changeset: 218:062a6431da73 +user: Damien Elmes +date: Tue Jul 17 01:24:10 2007 +0900 +description: +small bug fixes, make fonts non-modal and preview toggle + + +changeset: 217:3568f142edb5 +user: Damien Elmes +date: Mon Jul 16 17:56:50 2007 +0900 +description: +mac tweaks, and fix a centering bug on pyqt4.3 + + +changeset: 216:db1cf00283a9 +user: Damien Elmes +date: Mon Jul 16 17:02:56 2007 +0900 +description: +change window size in a way that works on osx + + +changeset: 215:7d66d45b1bb7 +user: Damien Elmes +date: Mon Jul 16 16:55:29 2007 +0900 +description: +show empty fields differently to dupe fields + + +changeset: 214:b299f1efeaeb +user: Damien Elmes +date: Sun Jul 15 04:50:48 2007 +0900 +description: +fix bug in fact editor: convert to unicode before comparison + + +changeset: 213:16351c50f99d +user: Damien Elmes +date: Sun Jul 15 04:13:07 2007 +0900 +description: +add support for hiding the question when showing answer + + +changeset: 212:9573f889aa83 +user: Damien Elmes +date: Sun Jul 15 04:01:28 2007 +0900 +description: +allow import on no deck, improve upgrading & notice + + +changeset: 211:50c999a072a1 +user: Damien Elmes +date: Sat Jul 14 13:36:45 2007 +0900 +description: +add cardlist, delete icon + + +changeset: 210:e77a193cdd99 +user: Damien Elmes +date: Sat Jul 14 13:36:10 2007 +0900 +description: +revert to gpl2+, as we're waiting on qt + + +changeset: 209:08b18663fcd2 +user: Damien Elmes +date: Fri Jul 13 17:22:22 2007 +0900 +description: +disable search group too, and remove old redundant validity check + + +changeset: 208:9bc926768b63 +user: Damien Elmes +date: Fri Jul 13 17:14:46 2007 +0900 +description: +live update of errors in cardlist/addcards + + +changeset: 207:525ffb751f61 +user: Damien Elmes +date: Thu Jul 12 22:55:05 2007 +0900 +description: +start work on invalid indicator + + +changeset: 206:6e7f1a9d03f6 +user: Damien Elmes +date: Thu Jul 12 03:09:16 2007 +0900 +description: +hack up the new deck editor & add tag support, improve fact editor + + +changeset: 205:cf7105b74d16 +user: Damien Elmes +date: Wed Jul 11 18:38:16 2007 +0900 +description: +more add card tweaks, add tags, shortcuts to model + + +changeset: 204:4b85ccd9d918 +user: Damien Elmes +date: Wed Jul 11 11:56:38 2007 +0900 +description: +polish 'add cards' dialog a bit + + +changeset: 203:f87e703b2ae7 +user: Damien Elmes +date: Tue Jul 10 23:24:03 2007 +0900 +description: +work on add cards, fact editor, kakasi integration, more + + +changeset: 202:c60eab940740 +user: Damien Elmes +date: Tue Jul 10 16:39:25 2007 +0900 +description: +fix bug with card colours, start work on card editor + + +changeset: 201:f3e15cf0a4cf +user: Damien Elmes +date: Mon Jul 09 23:21:13 2007 +0900 +description: +move to gpl v3 + + +changeset: 200:23a5590d20f3 +user: Damien Elmes +date: Mon Jul 09 23:16:08 2007 +0900 +description: +add map changing support in importing, tidy up a few gui elements + + +changeset: 199:8e2b09b363fa +user: Damien Elmes +date: Sat Jul 07 17:26:20 2007 +0900 +description: +mostly finish displayproperties dialog, use interface font again, get html q/a + + +changeset: 198:3baef7303b4b +user: Damien Elmes +date: Thu Jul 05 13:03:59 2007 +0900 +description: +toolbar, css, progress buttons, copy deck stats on import, display editor + + +changeset: 197:3465d72828d6 +user: Damien Elmes +date: Thu Jul 05 00:05:56 2007 +0900 +description: +add buttons for showing/answering, start work on display properties + + +changeset: 196:6cb0dd76c50c +user: Damien Elmes +date: Mon Jul 02 19:15:58 2007 +0900 +description: +update preferences, deck properties, models, start work on addcards + + +changeset: 195:78e3a73089a2 +user: Damien Elmes +date: Sun Jul 01 02:42:09 2007 +0900 +description: +rename add->addcards, add add model & tools + + +changeset: 194:1cdca67fcb6e +user: Damien Elmes +date: Sun Jul 01 02:40:22 2007 +0900 +description: +deck properties and model properties mostly working + + +changeset: 193:7ee0349a20d7 +user: Damien Elmes +date: Sat Jun 30 22:18:17 2007 +0900 +description: +more importing changes + + +changeset: 192:f4cacaa73ff8 +user: Damien Elmes +date: Thu Jun 28 04:44:25 2007 +0900 +description: +start work on importing dialog, model chooser + + +changeset: 191:9f4ae859cc3f +user: Damien Elmes +date: Mon Jun 25 03:10:12 2007 +0900 +description: +fix missing colon in copyright + + +changeset: 190:6b0dd76bd69e +user: Damien Elmes +date: Mon Jun 25 03:09:12 2007 +0900 +description: +more deck/model property changes + + +changeset: 189:d445970f1f5e +user: Damien Elmes +date: Sun Jun 24 05:20:10 2007 +0900 +description: +work on model editor dialog + + +changeset: 188:6fb09d9e9920 +user: Damien Elmes +date: Sun Jun 24 03:22:31 2007 +0900 +description: +add deckproperties, reduce copyright statement size, misc changes + + +changeset: 187:33d796e81962 +user: Damien Elmes +date: Sun Jun 24 01:23:48 2007 +0900 +description: +update for changes in libanki, add deckprefs dialog definition + + +changeset: 186:d510369cf8a5 +user: Damien Elmes +date: Sun Jun 10 00:23:26 2007 +0900 +description: +fix altering of shortcuts for mac + + +changeset: 185:541be5c7898a +user: Damien Elmes +date: Sun Jun 10 00:15:09 2007 +0900 +description: +allow the user to bypass the lock file, rather than making them delete it + + +changeset: 184:644ea16060e0 +user: Damien Elmes +date: Sat Jun 09 22:30:36 2007 +0900 +description: +fix card stats on empty deck bug + + +changeset: 183:81c293b8f51b +user: Damien Elmes +date: Sat Jun 09 21:45:34 2007 +0900 +description: +fix bug in font assignment, bump to 0.2.8 + + +changeset: 182:e261adf56f84 +user: Damien Elmes +date: Sat Jun 09 20:46:49 2007 +0900 +description: +use helvetica not arial on mac + + +changeset: 181:60d5a6063726 +user: Damien Elmes +date: Sat Jun 09 20:45:42 2007 +0900 +description: +use c-d, not c-a on osx + + +changeset: 180:7a51cb129bc3 +user: Damien Elmes +date: Sat Jun 09 20:41:04 2007 +0900 +description: +add lock file, 'command' on macosx + + +changeset: 179:fa8973a939e0 +user: Damien Elmes +date: Sat Jun 09 20:14:55 2007 +0900 +description: +die gracefully when there's a problem with the config file + + +changeset: 178:7a775b943e0a +user: Damien Elmes +date: Sat Jun 09 20:08:17 2007 +0900 +description: +don't overwrite old deck on open, go through the usual close procedures +- ensures the deck will be synced when switching files +- means the deck will be automatically saved without prompting if +'saveOnClose' is enabled + + +changeset: 177:c2543981a3f4 +user: Damien Elmes +date: Sat Jun 09 20:01:47 2007 +0900 +description: +allow focus redirection in help area even when not main window + + +changeset: 176:b65fad02e99f +user: Damien Elmes +date: Sat Jun 09 19:55:44 2007 +0900 +description: +add cumulative due graph, fix bug showing card stats on finished deck + + +changeset: 175:9cb53b457a66 +user: Damien Elmes +date: Sat Jun 09 19:13:53 2007 +0900 +description: +add cards: allow rich text, html, newlines, more +- the widgets will accept rich text now, pasted from other locations +- html is supported, and newlines +- strip newlines when speaking to kakasi, which is line-based +- change 'commit' key to control+enter +- remove some of the whitespace tidying code, which is no longer necessary +- update help + + +changeset: 174:9b4803888777 +user: Damien Elmes +date: Sat Jun 09 18:27:52 2007 +0900 +description: +use new sync protocol + + +changeset: 173:7f55a58ebae9 +user: Damien Elmes +date: Sat Jun 09 09:32:47 2007 +0900 +description: +ignore all files in plugins/ except .py files + + +changeset: 172:3f6edde2d659 +user: Damien Elmes +date: Sat Jun 09 09:27:19 2007 +0900 +description: +add support for plugins + + +changeset: 171:251c2651756b +user: Damien Elmes +date: Sat Jun 09 09:21:25 2007 +0900 +description: +add plugins/backups dir, newDeckStorage -> DeckStorage + + +changeset: 170:ce1feb0b64d5 +user: Damien Elmes +date: Sat Jun 09 05:06:15 2007 +0900 +description: +add mnemosyne import support + + +changeset: 169:70f956a11e7f +user: Damien Elmes +date: Sat Jun 09 03:45:57 2007 +0900 +description: +add improved text file import handling + + +changeset: 168:9e8b7b059bdc +user: Damien Elmes +date: Tue Jun 05 01:03:53 2007 +0900 +description: +copy the old config, don't move it (across drives) + + +changeset: 167:d0aa0aa97392 +user: Damien Elmes +date: Mon Jun 04 17:38:26 2007 +0900 +description: +make sure to toggle addReverse in config both ways + + +changeset: 166:a35f74911370 +user: Damien Elmes +date: Mon Jun 04 17:33:13 2007 +0900 +description: +config file to .anki/config.db, improve user customization support +users can now use a file ~/.anki/config.py to tweak the behaviour of anki + + +changeset: 165:acca095b277e +user: Damien Elmes +date: Sat Jun 02 16:18:21 2007 +0900 +description: +bump version + + +changeset: 164:c42e418e31a0 +user: Damien Elmes +date: Sat Jun 02 08:36:36 2007 +0900 +description: +add credits file + + +changeset: 163:8677f05896d8 +user: Damien Elmes +date: Sat Jun 02 01:54:08 2007 +0900 +description: +remove debugging info + + +changeset: 162:a2373e29a433 +user: Damien Elmes +date: Sat Jun 02 01:53:00 2007 +0900 +description: +fix default prefs page + + +changeset: 161:c385f39d93d2 +user: Damien Elmes +date: Sat Jun 02 01:52:27 2007 +0900 +description: +use different url generation for win32 + + +changeset: 160:ddc3bb0bb043 +user: Damien Elmes +date: Sat Jun 02 01:13:08 2007 +0900 +description: +flush output before generating stats + + +changeset: 159:37c1a5080f0c +user: Damien Elmes +date: Sat Jun 02 01:09:29 2007 +0900 +description: +add status message for kanji stats + + +changeset: 158:10e76674ee0f +user: Damien Elmes +date: Sat Jun 02 01:07:57 2007 +0900 +description: +add cgi module to mac build + + +changeset: 157:a511e275e906 +user: Damien Elmes +date: Sat Jun 02 00:55:06 2007 +0900 +description: +fix mac kakasi path (again), prevent leading space + + +changeset: 156:9c383d9fc1bc +user: Damien Elmes +date: Sat Jun 02 00:20:28 2007 +0900 +description: +fix sizes on osx, remove debugging info + + +changeset: 155:73cb0cf6b291 +user: Damien Elmes +date: Fri Jun 01 23:57:56 2007 +0900 +description: +bump version number, don't display seconds when showing next card + + +changeset: 154:97ba6addf1c0 +user: Damien Elmes +date: Fri Jun 01 23:22:23 2007 +0900 +description: +select the previous card when opening edit dialog + + +changeset: 153:0995bbd13e79 +user: Damien Elmes +date: Fri Jun 01 22:43:34 2007 +0900 +description: +add 'don't generate reverse card entries' option to the add card dialog + + +changeset: 152:28e41be09db0 +user: Damien Elmes +date: Fri Jun 01 22:16:25 2007 +0900 +description: +change the path to kakasi on the mac + + +changeset: 151:8f538034c077 +user: Damien Elmes +date: Fri Jun 01 22:04:59 2007 +0900 +description: +mac cp doesn't support -a + + +changeset: 150:5f999c709637 +user: Damien Elmes +date: Fri Jun 01 22:03:09 2007 +0900 +description: +set up kakasi in the build dir on mac + + +changeset: 149:59f13a0b2b21 +user: Damien Elmes +date: Fri Jun 01 22:01:13 2007 +0900 +description: +set a default font on matplotlib + + +changeset: 148:9e88c5ef3293 +user: Damien Elmes +date: Wed May 23 23:59:15 2007 +0900 +description: +add eases graph + + +changeset: 147:650a99a81572 +user: Damien Elmes +date: Tue May 22 03:25:24 2007 +0900 +description: +use new kanji report + + +changeset: 146:2d5b4473fbbb +user: "frostschutz " +date: Sat May 19 19:53:40 2007 +0200 +description: +make (foo) in '(foo) bar' actually show up + + +changeset: 145:56df29eed2eb +user: Damien Elmes +date: Sun May 20 00:19:23 2007 +0900 +description: +load user customizations before view, etc + + +changeset: 144:a895fe361a59 +user: Damien Elmes +date: Sat May 19 23:33:13 2007 +0900 +description: +copy and paste bug + + +changeset: 143:67b82487a696 +user: Damien Elmes +date: Sat May 19 23:30:34 2007 +0900 +description: +add hooks in addCards, user config file + + +changeset: 142:c69e57fba360 +user: Damien Elmes +date: Sat May 19 22:57:12 2007 +0900 +description: +update help + + +changeset: 141:f4ac85e67a06 +user: Damien Elmes +date: Sat May 19 22:50:10 2007 +0900 +description: +update pending# every minute + + +changeset: 140:cdbfd4141806 +user: Damien Elmes +date: Sat May 19 22:44:21 2007 +0900 +description: +add graph of first answer + + +changeset: 139:f252384ebfe1 +user: Damien Elmes +date: Sat May 19 00:20:53 2007 +0900 +description: +tweak answer card help once again + + +changeset: 138:0b23cde4c19d +user: Damien Elmes +date: Fri May 18 21:51:06 2007 +0900 +description: +age in english + + +changeset: 137:4541d46b3a59 +user: Damien Elmes +date: Fri May 18 21:19:13 2007 +0900 +description: +update translations, bump to 0.2.6.3 + + +changeset: 136:6e2110636739 +user: Damien Elmes +date: Fri May 18 20:54:00 2007 +0900 +description: +show different help messages for cards in the initial state + + +changeset: 135:c39e8b6a638e +user: Damien Elmes +date: Fri May 18 19:25:38 2007 +0900 +description: +make the help window arial 12pt + + +changeset: 134:859bf6e90fb6 +user: Damien Elmes +date: Fri May 18 17:16:21 2007 +0900 +description: +prefs window, change example text + + +changeset: 133:e85b05dbb237 +user: Damien Elmes +date: Fri May 18 16:02:33 2007 +0900 +description: +more osx related tweaks + + +changeset: 132:6ad8da1ef509 +user: Damien Elmes +date: Fri May 18 06:38:42 2007 +0900 +description: +tweak mac build script + + +changeset: 131:9271cf3773a5 +user: Damien Elmes +date: Fri May 18 06:37:45 2007 +0900 +description: +copy sample decks out of mac archive + + +changeset: 130:104de8aa60a6 +user: Damien Elmes +date: Thu May 17 04:47:08 2007 +0900 +description: +get platform, too + + +changeset: 129:aa8d41244bb0 +user: Damien Elmes +date: Thu May 17 04:42:37 2007 +0900 +description: +slightly expand prefs dialog + + +changeset: 128:0f46c4f073ce +user: Damien Elmes +date: Thu May 17 04:33:41 2007 +0900 +description: +not deck -> deck is None + + +changeset: 127:44988161aed5 +user: Damien Elmes +date: Thu May 17 04:24:39 2007 +0900 +description: +don't do that, the problem was elsewhere + + +changeset: 126:04de408b3d81 +user: Damien Elmes +date: Thu May 17 04:23:09 2007 +0900 +description: +force recompile on mac build (fixes stale problems) + + +changeset: 125:f50ed70bfc28 +user: Damien Elmes +date: Thu May 17 04:18:28 2007 +0900 +description: +return True if save was successful, bump to 0.2.6 + + +changeset: 124:6b865f69389a +user: Damien Elmes +date: Thu May 17 04:14:29 2007 +0900 +description: +tweak read-only check + + +changeset: 123:0f6ac7683a75 +user: Damien Elmes +date: Thu May 17 04:04:40 2007 +0900 +description: +more read-only handling + + +changeset: 122:9b8929959581 +user: Damien Elmes +date: Thu May 17 03:40:17 2007 +0900 +description: +add mac build script + + +changeset: 121:2a42e089b9b3 +user: Damien Elmes +date: Thu May 17 03:22:45 2007 +0900 +description: +default to sync off again + + +changeset: 120:877d57416124 +user: Damien Elmes +date: Thu May 17 03:19:06 2007 +0900 +description: +enable save on save + + +changeset: 119:cf8ee60a7a94 +user: Damien Elmes +date: Thu May 17 03:17:57 2007 +0900 +description: +disable sync on closed deck, don't die on stat(noFile) + + +changeset: 118:0dcaf806985d +user: Damien Elmes +date: Thu May 17 03:04:38 2007 +0900 +description: +disable save menu on read-only deck + + +changeset: 117:c12ca4376bd3 +user: Damien Elmes +date: Thu May 17 02:56:44 2007 +0900 +description: +graph status, fix save bug, osx package updates + + +changeset: 116:c2a08e3ba0a5 +parent: 115:e1693e2a0e72 +parent: 114:f5c74c75b8dd +user: Damien Elmes +date: Thu May 17 01:45:48 2007 +0900 +description: +merge from other comp + + +changeset: 115:e1693e2a0e72 +parent: 112:95b18641289d +user: Damien Elmes +date: Thu May 17 01:41:00 2007 +0900 +description: +updates to mac build script + + +changeset: 114:f5c74c75b8dd +user: Damien Elmes +date: Wed May 16 17:07:31 2007 +0900 +description: +adjust graph window size again + + +changeset: 113:3102f79132e1 +parent: 110:b2224a14bd74 +user: Damien Elmes +date: Wed May 16 16:43:39 2007 +0900 +description: +import graphs in onGraphStats + + +changeset: 112:95b18641289d +user: Damien Elmes +date: Wed May 16 23:19:08 2007 +0900 +description: +osx: different graph window size, use geometry() not framegeo..() + + +changeset: 111:d1aefe158e8d +user: Damien Elmes +date: Wed May 16 22:48:03 2007 +0900 +description: +import re in ankiqt.py + + +changeset: 110:b2224a14bd74 +user: Damien Elmes +date: Tue May 15 04:16:41 2007 +0900 +description: +bump version number, don't die on no deck + + +changeset: 109:57e121db9a76 +user: Damien Elmes +date: Tue May 15 04:10:45 2007 +0900 +description: +tweak updating code + + +changeset: 108:6262e2694fe8 +user: Damien Elmes +date: Tue May 15 03:30:53 2007 +0900 +description: +point "start here" to the help pages on the web + + +changeset: 107:92c7a12a663d +user: Damien Elmes +date: Tue May 15 03:21:42 2007 +0900 +description: +add some explanations to the graphs + + +changeset: 106:ef9dfdac9e8e +user: Damien Elmes +date: Tue May 15 02:41:45 2007 +0900 +description: +give more information when importing + + +changeset: 105:7c4919e5904e +user: Damien Elmes +date: Tue May 15 00:23:30 2007 +0900 +description: +always add graph link (speed up deck stats) + + +changeset: 104:f45d4917614d +user: Damien Elmes +date: Tue May 15 00:04:58 2007 +0900 +description: +increase default size, make added cards use same direction + + +changeset: 103:f8a7138acfd0 +user: Damien Elmes +date: Tue May 15 00:01:06 2007 +0900 +description: +add adjustable graphs + + +changeset: 102:b00fc31d9e35 +user: Damien Elmes +date: Mon May 14 08:59:03 2007 +0900 +description: +mention problem with qt4.1 + + +changeset: 101:bf4d48b52ec0 +user: Damien Elmes +date: Sun May 13 23:06:21 2007 +0900 +description: +account for libanki refactor + + +changeset: 100:39ba315a1525 +user: Damien Elmes +date: Sun May 13 18:48:58 2007 +0900 +description: +display '4 days' in prefs preview (closer to reality) + + +changeset: 99:0d0cc137104a +user: Damien Elmes +date: Sun May 13 05:26:56 2007 +0900 +description: +ensure currentCard != lastCard + + +changeset: 98:bc8c88b9c655 +user: Damien Elmes +date: Sun May 13 05:08:06 2007 +0900 +description: +add card stats, beginnings of hook support, keys for misc text in help + + +changeset: 97:aeeaa02bcb65 +user: Damien Elmes +date: Sun May 13 03:40:01 2007 +0900 +description: +bump to 0.2.5-pre, report retention for old cards in update + + +changeset: 96:aa889ebc3455 +user: Damien Elmes +date: Fri May 11 01:38:35 2007 +0900 +description: +let library deal with deviation + + +changeset: 95:fbbd86b1dbc5 +user: Damien Elmes +date: Fri May 11 00:32:38 2007 +0900 +description: +add deviation to prefs window, too + + +changeset: 94:dbee68b86a7b +user: Damien Elmes +date: Fri May 11 00:22:22 2007 +0900 +description: +allow reading of .anki.css + + +changeset: 93:7daf4bea0fe4 +user: Damien Elmes +date: Mon May 07 17:09:34 2007 +0900 +description: +report the real schedule date, not an estimate pre-deviation + + +changeset: 92:9b583cbe6eeb +user: Damien Elmes +date: Mon May 07 01:05:56 2007 +0900 +description: +show deleted cards even if edited, don't show < 60 seconds in deckFinished + + +changeset: 91:b7e7b5ecca03 +user: Damien Elmes +date: Wed May 02 20:29:35 2007 +0900 +description: +reset pending cards on deck edit + + +changeset: 90:523d02455b19 +user: Damien Elmes +date: Wed May 02 20:18:21 2007 +0900 +description: +translation updates + + +changeset: 89:542585e46bb0 +user: Damien Elmes +date: Wed May 02 19:47:30 2007 +0900 +description: +fix non-wrapping tooltips, bump version to 0.2.4 + + +changeset: 88:99e8fc3f1845 +user: Damien Elmes +date: Wed May 02 18:51:14 2007 +0900 +description: +use new stats format when checking if we should autosave + + +changeset: 87:e2e464156366 +user: Damien Elmes +date: Wed May 02 18:35:41 2007 +0900 +description: +link in new graphs from libanki + + +changeset: 86:78b167452a2a +user: Damien Elmes +date: Wed May 02 17:42:37 2007 +0900 +description: +update to new stats api, improve status bar + + +changeset: 85:90e1f68711a1 +user: Damien Elmes +date: Wed May 02 05:45:46 2007 +0900 +description: +save after every 30, not 10 + + +changeset: 84:13fb077f0cce +user: Damien Elmes +date: Wed May 02 05:30:07 2007 +0900 +description: +fix reference to the deck menu in help + + +changeset: 83:8c6ec5456ee6 +user: Damien Elmes +date: Wed May 02 05:27:29 2007 +0900 +description: +don't accept RT in add cards, strip newline from pasted text + + +changeset: 82:103ec8d3ef17 +user: Damien Elmes +date: Wed May 02 04:32:12 2007 +0900 +description: +tweak key handling + + +changeset: 81:60e6a7014230 +user: Damien Elmes +date: Wed May 02 04:02:43 2007 +0900 +description: +allow deck arg both before and after options + + +changeset: 80:ba5362f41fb9 +user: Damien Elmes +date: Wed May 02 02:57:28 2007 +0900 +description: +add status bar widgets, fix bug w/ finished help, add ~ outside win32 + + +changeset: 79:71e82d66ca29 +user: Damien Elmes +date: Mon Apr 30 23:52:13 2007 +0900 +description: +prevent deck finished window from overwriting deck statistics, etc + + +changeset: 78:18924197529f +user: Damien Elmes +date: Mon Apr 30 20:52:35 2007 +0900 +description: +report socket timeouts when syncing + + +changeset: 77:b8fb5bc5c5e7 +user: Damien Elmes +date: Mon Apr 30 20:45:23 2007 +0900 +description: +fix bug when reporting failed file load + + +changeset: 76:4b1a39b0708d +user: Damien Elmes +date: Mon Apr 30 20:43:58 2007 +0900 +description: +fix another key handler assuming the key is a string + + +changeset: 75:456c9149973b +user: Damien Elmes +date: Sat Apr 28 15:10:01 2007 +0900 +description: +set focus back to main window after triggering graphs + + +changeset: 74:cbb0d7745bbc +user: Damien Elmes +date: Sat Apr 28 00:43:25 2007 +0900 +description: +add room for x axis label in graphs + + +changeset: 73:246a55ef47ff +user: Damien Elmes +date: Fri Apr 27 01:33:17 2007 +0900 +description: +add welcome message + + +changeset: 72:858ff864eb9f +user: Damien Elmes +date: Fri Apr 27 01:13:27 2007 +0900 +description: +bump version to 0.2.3 + + +changeset: 71:4885925f424f +user: Damien Elmes +date: Fri Apr 27 01:08:54 2007 +0900 +description: +add support for exporting decks + + +changeset: 70:ee5eb6fd7188 +user: iroiro +date: Wed Apr 25 18:36:10 2007 +0300 +description: +Fix for the title bar bug + + +changeset: 69:7fbd85b7f09c +user: Damien Elmes +date: Thu Apr 26 01:50:08 2007 +0900 +description: +show new cards as new in edit dialog + + +changeset: 68:5cd39a30584d +user: Damien Elmes +date: Thu Apr 26 01:43:50 2007 +0900 +description: +prevent errors due to lack of data for graphs + + +changeset: 67:be71ea3c1611 +user: Damien Elmes +date: Thu Apr 26 00:22:54 2007 +0900 +description: +allow hiragana prompts in question for beginners + + +changeset: 66:e8adb5e1337d +user: Damien Elmes +date: Wed Apr 25 21:18:58 2007 +0900 +description: +tidy up iroiro's patch, correct stealth removal of space after question ;-) + + +changeset: 65:65cb3ecefbd5 +user: iroiro +date: Tue Apr 24 23:44:27 2007 +0300 +description: +fixed font ' issue; using \n" + t) + t = "

" + c.htmlAnswer + "
" + self.dialog.answer.setText( + "\n" + t) + self.main.updateViews(self.main.state) + + def reject(self): + ui.dialogs.close("DisplayProperties") + QDialog.reject(self) diff --git a/ankiqt/ui/exporting.py b/ankiqt/ui/exporting.py new file mode 100644 index 000000000..9732d4c1e --- /dev/null +++ b/ankiqt/ui/exporting.py @@ -0,0 +1,63 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import anki, ankiqt +from anki.exporting import exporters +from anki.utils import parseTags +from ankiqt import ui + +class ExportDialog(QDialog): + + def __init__(self, parent): + QDialog.__init__(self, parent) + self.parent = parent + self.deck = parent.deck + self.dialog = ankiqt.forms.exporting.Ui_ExportDialog() + self.dialog.setupUi(self) + self.exporter = None + self.setup() + self.exec_() + + def setup(self): + self.dialog.format.insertItems( + 0, QStringList(list(zip(*exporters())[0]))) + self.connect(self.dialog.format, SIGNAL("activated(int)"), + self.exporterChanged) + self.exporterChanged(0) + # fragile + self.tags = ui.tagedit.TagEdit(self) + self.tags.setDeck(self.deck) + self.dialog.gridlayout.addWidget(self.tags,1,1) + self.setTabOrder(self.dialog.format, + self.tags) + self.setTabOrder(self.tags, + self.dialog.includeScheduling) + # save button + b = QPushButton(_("Export to...")) + self.dialog.buttonBox.addButton(b, QDialogButtonBox.AcceptRole) + + def exporterChanged(self, idx): + self.exporter = exporters()[idx][1](self.deck) + if hasattr(self.exporter, "includeSchedulingInfo"): + self.dialog.includeScheduling.show() + else: + self.dialog.includeScheduling.hide() + if hasattr(self.exporter, "includeTags"): + self.dialog.includeTags.show() + else: + self.dialog.includeTags.hide() + + def accept(self): + file = ui.utils.getSaveFile(self, _("Choose file to export to"), "export", + self.exporter.key, self.exporter.ext) + if file: + self.exporter.includeSchedulingInfo = ( + self.dialog.includeScheduling.isChecked()) + self.exporter.includeTags = ( + self.dialog.includeTags.isChecked()) + self.exporter.limitTags = parseTags(unicode(self.tags.text())) + self.exporter.exportInto(file) + self.parent.setStatus(_("%d exported.") % self.exporter.count) + QDialog.accept(self) diff --git a/ankiqt/ui/facteditor.py b/ankiqt/ui/facteditor.py new file mode 100644 index 000000000..0b4513300 --- /dev/null +++ b/ankiqt/ui/facteditor.py @@ -0,0 +1,560 @@ +# -*- coding: utf-8 -*- +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import re, os, sys +from anki.utils import parseTags, stripHTML, tidyHTML +import anki.sound +from ankiqt import ui + +class FactEditor(object): + """An editor for new/existing facts. + + The fact is updated as it is edited. + Extra widgets can be added to 'fieldsGrid' to represent card-specific + information, etc.""" + + def __init__(self, parent, widget, deck=None): + self.widget = widget + self.parent = parent + self.deck = deck + self.fact = None + self.fontChanged = False + self.setupFields() + self.checkTimer = None + self.onChange = None + self.onFactValid = None + self.onFactInvalid = None + self.lastFocusedEdit = None + + def setFact(self, fact, noFocus=False, check=False): + "Make FACT the current fact." + self.fact = fact + self.factState = None + if self.needToRedraw(): + self.drawFields(noFocus, check) + else: + self.updateFields(check) + if not noFocus: + # update focus to first field + self.fields[self.fact.fields[0].name][1].setFocus() + self.fontChanged = False + + def setupFields(self): + self.fields = {} + # top level vbox + self.fieldsBox = QVBoxLayout(self.widget) + self.fieldsBox.setMargin(5) + self.fieldsBox.setSpacing(3) + # icons + self.iconsBox = QHBoxLayout() + self.fieldsBox.addLayout(self.iconsBox) + # scrollarea + self.fieldsScroll = QScrollArea() + self.fieldsScroll.setWidgetResizable(True) + self.fieldsScroll.setLineWidth(0) + self.fieldsScroll.setFrameStyle(0) + self.fieldsScroll.setFocusPolicy(Qt.NoFocus) + self.fieldsBox.addWidget(self.fieldsScroll) + self.iconsBox.setMargin(0) + self.iconsBox.addItem(QSpacerItem(20,20, QSizePolicy.Expanding)) + # button styles for mac + self.plastiqueStyle = QStyleFactory.create("plastique") + # bold + self.bold = QPushButton() + self.bold.setCheckable(True) + self.bold.connect(self.bold, SIGNAL("toggled(bool)"), self.toggleBold) + self.bold.setIcon(QIcon(":/icons/text_bold.png")) + self.bold.setToolTip(_("Bold text (Ctrl+b)")) + self.bold.setShortcut(_("Ctrl+b")) + self.bold.setFocusPolicy(Qt.NoFocus) + self.bold.setEnabled(False) + self.iconsBox.addWidget(self.bold) + self.bold.setStyle(self.plastiqueStyle) + # italic + self.italic = QPushButton(self.widget) + self.italic.setCheckable(True) + self.italic.connect(self.italic, SIGNAL("toggled(bool)"), self.toggleItalic) + self.italic.setIcon(QIcon(":/icons/text_italic.png")) + self.italic.setToolTip(_("Italic text (Ctrl+i)")) + self.italic.setShortcut(_("Ctrl+i")) + self.italic.setFocusPolicy(Qt.NoFocus) + self.italic.setEnabled(False) + self.iconsBox.addWidget(self.italic) + self.italic.setStyle(self.plastiqueStyle) + # underline + self.underline = QPushButton(self.widget) + self.underline.setCheckable(True) + self.underline.connect(self.underline, SIGNAL("toggled(bool)"), self.toggleUnderline) + self.underline.setIcon(QIcon(":/icons/text_under.png")) + self.underline.setToolTip(_("Underline text (Ctrl+u)")) + self.underline.setShortcut(_("Ctrl+u")) + self.underline.setFocusPolicy(Qt.NoFocus) + self.underline.setEnabled(False) + self.iconsBox.addWidget(self.underline) + self.underline.setStyle(self.plastiqueStyle) + # foreground color - not working on mac + self.foreground = QPushButton() + self.foreground.connect(self.foreground, SIGNAL("clicked()"), self.selectForeground) + self.foreground.setToolTip(_("Foreground colour (Ctrl+r)")) + self.foreground.setShortcut(_("Ctrl+r")) + self.foreground.setFocusPolicy(Qt.NoFocus) + self.foreground.setEnabled(False) + self.foreground.setFixedWidth(30) + self.foregroundFrame = QFrame() + self.foregroundFrame.setAutoFillBackground(True) + hbox = QHBoxLayout() + hbox.addWidget(self.foregroundFrame) + hbox.setMargin(5) + self.foreground.setLayout(hbox) + self.iconsBox.addWidget(self.foreground) + self.foreground.setStyle(self.plastiqueStyle) + # pictures + spc = QSpacerItem(10,10) + self.iconsBox.addItem(spc) + self.addPicture = QPushButton(self.widget) + self.addPicture.connect(self.addPicture, SIGNAL("clicked()"), self.onAddPicture) + self.addPicture.setFocusPolicy(Qt.NoFocus) + self.addPicture.setShortcut(_("Ctrl+p")) + self.addPicture.setIcon(QIcon(":/icons/colors.png")) + self.addPicture.setEnabled(False) + self.addPicture.setToolTip(_("Add a picture (Ctrl+p)")) + self.iconsBox.addWidget(self.addPicture) + self.addPicture.setStyle(self.plastiqueStyle) + # sounds + self.addSound = QPushButton(self.widget) + self.addSound.connect(self.addSound, SIGNAL("clicked()"), self.onAddSound) + self.addSound.setFocusPolicy(Qt.NoFocus) + self.addSound.setShortcut(_("Ctrl+s")) + self.addSound.setEnabled(False) + self.addSound.setIcon(QIcon(":/icons/text-speak.png")) + self.addSound.setToolTip(_("Add audio (Ctrl+s)")) + self.iconsBox.addWidget(self.addSound) + self.addSound.setStyle(self.plastiqueStyle) + # latex + spc = QSpacerItem(10,10) + self.iconsBox.addItem(spc) + self.latex = QPushButton(self.widget) + self.latex.connect(self.latex, SIGNAL("clicked()"), self.insertLatex) + self.latex.setToolTip(_("Latex (Ctrl+l)")) + self.latex.setShortcut(_("Ctrl+l")) + self.latex.setIcon(QIcon(":/icons/tex.png")) + self.latex.setFocusPolicy(Qt.NoFocus) + self.latex.setEnabled(False) + self.iconsBox.addWidget(self.latex) + self.latex.setStyle(self.plastiqueStyle) + # latex eqn + self.latexEqn = QPushButton(self.widget) + self.latexEqn.connect(self.latexEqn, SIGNAL("clicked()"), self.insertLatexEqn) + self.latexEqn.setToolTip(_("Latex equation (Ctrl+e)")) + self.latexEqn.setShortcut(_("Ctrl+e")) + self.latexEqn.setIcon(QIcon(":/icons/math_sqrt.png")) + self.latexEqn.setFocusPolicy(Qt.NoFocus) + self.latexEqn.setEnabled(False) + self.iconsBox.addWidget(self.latexEqn) + self.latexEqn.setStyle(self.plastiqueStyle) + # latex math env + self.latexMathEnv = QPushButton(self.widget) + self.latexMathEnv.connect(self.latexMathEnv, SIGNAL("clicked()"), + self.insertLatexMathEnv) + self.latexMathEnv.setToolTip(_("Latex math environment (Ctrl+m)")) + self.latexMathEnv.setShortcut(_("Ctrl+m")) + self.latexMathEnv.setIcon(QIcon(":/icons/math_matrix.png")) + self.latexMathEnv.setFocusPolicy(Qt.NoFocus) + self.latexMathEnv.setEnabled(False) + self.iconsBox.addWidget(self.latexMathEnv) + self.latexMathEnv.setStyle(self.plastiqueStyle) + + self.fieldsFrame = None + self.widget.setLayout(self.fieldsBox) + self.updatingFields = False + + def _makeGrid(self): + "Rebuild the grid to avoid trigging QT bugs." + self.fieldsFrame = QFrame() + self.fieldsFrame.setFrameStyle(0) + self.fieldsFrame.setLineWidth(0) + self.fieldsGrid = QGridLayout(self.fieldsFrame) + self.fieldsFrame.setLayout(self.fieldsGrid) + self.fieldsGrid.setMargin(0) + + def drawFields(self, noFocus=False, check=False): + self.parent.setUpdatesEnabled(False) + self._makeGrid() + # add entries for each field + fields = self.fact.fields + self.fields = {} + n = 0 + first = None + for field in fields: + # label + l = QLabel(field.name) + self.fieldsGrid.addWidget(l, n, 0) + # edit widget + w = FactEdit(self) + w.setTabChangesFocus(True) + w.setAcceptRichText(True) + w.setMinimumSize(20, 60) + w.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + w.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.fieldsGrid.addWidget(w, n, 1) + self.fields[field.name] = (field, w) + # catch changes + w.connect(w, SIGNAL("textChanged()"), + lambda f=field, w=w: self.fieldChanged(f, w)) + w.connect(w, SIGNAL("currentCharFormatChanged(QTextCharFormat)"), + lambda w=w: self.formatChanged(w)) + n += 1 + # tags + self.fieldsGrid.addWidget(QLabel(_("Tags")), n, 0) + self.tags = ui.tagedit.TagEdit(self.parent) + self.tags.connect(self.tags, SIGNAL("textChanged(QString)"), + self.onTagChange) + # update available tags + self.tags.setDeck(self.deck) + self.fieldsGrid.addWidget(self.tags, n, 1) + # status warning + n += 1 + self.warning = QLabel() + self.warning.setFixedHeight(20) + self.warning.setOpenExternalLinks(True) + self.fieldsGrid.addWidget(self.warning, n, 1) + # update fields + self.updateFields(check) + self.parent.setUpdatesEnabled(True) + self.fieldsScroll.setWidget(self.fieldsFrame) + + def needToRedraw(self): + if len(self.fact.fields) != len(self.fields): + return True + for field in self.fact.fields: + if field.name not in self.fields: + return True + return self.fontChanged + + def updateFields(self, check=True, font=True): + "Update field text (if changed) and font/colours." + self.updatingFields = True + # text + for (name, (field, w)) in self.fields.items(): + new = self.fact[name] + old = tidyHTML(unicode(w.toHtml())) + # only update if something has changed, to preserve the cursor + if new != old: + w.setHtml(new) + if font: + # apply fonts + font = QFont() + # family + family = (field.fieldModel.editFontFamily or + field.fieldModel.quizFontFamily) + if family: + font.setFamily(family) + # size + size = (field.fieldModel.editFontSize or + field.fieldModel.quizFontSize) + if size: + font.setPixelSize(size) + w.setFont(font) + self.tags.blockSignals(True) + self.tags.setText(self.fact.tags) + self.tags.blockSignals(False) + self.updatingFields = False + if check: + self.checkValid() + + def fieldChanged(self, field, widget): + if self.updatingFields: + return + value = tidyHTML(unicode(widget.toHtml())) + if value and not value.strip(): + widget.setText("") + value = u"" + self.fact[field.name] = value + self.fact.setModified(textChanged=True) + self.deck.setModified() + self.fact.onKeyPress(field, value) + # the keypress handler may have changed something, so update all + self.updateFields(font=False) + if self.onChange: + self.onChange(field) + self.scheduleCheck() + self.formatChanged(None) + + def scheduleCheck(self): + if not self.deck: + return + interval = 200 + if self.checkTimer: + self.checkTimer.setInterval(interval) + else: + self.checkTimer = QTimer(self.parent) + self.checkTimer.setSingleShot(True) + self.checkTimer.start(interval) + self.parent.connect(self.checkTimer, SIGNAL("timeout()"), + self.checkValid) + + def checkValid(self): + empty = [] + dupe = [] + for field in self.fact.fields: + p = QPalette() + p.setColor(QPalette.Text, QColor("#000000")) + if not self.fact.fieldValid(field): + empty.append(field) + p.setColor(QPalette.Base, QColor("#ffffcc")) + self.fields[field.name][1].setPalette(p) + elif not self.fact.fieldUnique(field, self.deck.s): + dupe.append(field) + p.setColor(QPalette.Base, QColor("#ffcccc")) + self.fields[field.name][1].setPalette(p) + else: + p.setColor(QPalette.Base, QColor("#ffffff")) + self.fields[field.name][1].setPalette(p) + self.checkTimer = None + # call relevant hooks + invalid = len(empty+dupe) + if self.factState != "valid" and not invalid: + if self.onFactValid: + self.onFactValid(self.fact) + self.factState = "valid" + elif self.factState != "invalid" and invalid: + if self.onFactInvalid: + self.onFactInvalid(self.fact) + self.factState = "invalid" + if invalid: + self.warning.setText(_( + "Some fields are " + "missing" + " or not " + "unique.")) + else: + self.warning.setText(_("All fields valid")) + + def onTagChange(self, text): + if not self.updatingFields: + self.fact.tags = unicode(text) + if self.onChange: + self.onChange(None) + + def focusField(self, fieldName): + self.fields[fieldName][1].setFocus() + + def formatChanged(self, fmt): + w = self.focusedEdit() + if not w or w.textCursor().hasSelection(): + return + else: + self.bold.setChecked(w.fontWeight() == QFont.Bold) + self.italic.setChecked(w.fontItalic()) + self.underline.setChecked(w.fontUnderline()) + self.foregroundFrame.setPalette(QPalette(w.textColor())) + + def resetFormatButtons(self): + self.bold.setChecked(False) + self.italic.setChecked(False) + self.underline.setChecked(False) + + def enableButtons(self, val=True): + self.bold.setEnabled(val) + self.italic.setEnabled(val) + self.underline.setEnabled(val) + self.foreground.setEnabled(val) + self.addPicture.setEnabled(val) + self.addSound.setEnabled(val) + self.latex.setEnabled(val) + self.latexEqn.setEnabled(val) + self.latexMathEnv.setEnabled(val) + + def disableButtons(self): + self.enableButtons(False) + + def focusedEdit(self): + for (name, (field, w)) in self.fields.items(): + if w.hasFocus(): + return w + return None + + def toggleBold(self, bool): + w = self.focusedEdit() + if w: + self.fontChanged = True + w.setFontWeight(bool and QFont.Bold or QFont.Normal) + + def toggleItalic(self, bool): + w = self.focusedEdit() + if w: + self.fontChanged = True + w.setFontItalic(bool) + + def toggleUnderline(self, bool): + w = self.focusedEdit() + if w: + self.fontChanged = True + w.setFontUnderline(bool) + + def selectForeground(self): + w = self.focusedEdit() + if w: + # we lose the selection when we open the colour dialog on win32, + # so we need to save it + cursor = w.textCursor() + new = QColorDialog.getColor(w.textColor(), self.parent) + if new.isValid(): + w.setTextCursor(cursor) + self.foregroundFrame.setPalette(QPalette(new)) + w.setTextColor(new) + # now we clear the selection + cursor.clearSelection() + w.setTextCursor(cursor) + self.fontChanged = True + + def insertLatex(self): + w = self.focusedEdit() + if w: + self.deck.mediaDir(create=True) + w.insertHtml("[latex][/latex]") + w.moveCursor(QTextCursor.PreviousWord) + w.moveCursor(QTextCursor.PreviousCharacter) + w.moveCursor(QTextCursor.PreviousCharacter) + + def insertLatexEqn(self): + w = self.focusedEdit() + if w: + self.deck.mediaDir(create=True) + w.insertHtml("[$][/$]") + w.moveCursor(QTextCursor.PreviousWord) + w.moveCursor(QTextCursor.PreviousCharacter) + w.moveCursor(QTextCursor.PreviousCharacter) + + def insertLatexMathEnv(self): + w = self.focusedEdit() + if w: + self.deck.mediaDir(create=True) + w.insertHtml("[$$][/$$]") + w.moveCursor(QTextCursor.PreviousWord) + w.moveCursor(QTextCursor.PreviousCharacter) + w.moveCursor(QTextCursor.PreviousCharacter) + + def fieldsAreBlank(self): + for (field, widget) in self.fields.values(): + value = tidyHTML(unicode(widget.toHtml())) + if value: + return False + return True + + def onAddPicture(self): + if not self.hasMediaDir(): + return + # get this before we open the dialog + w = self.focusedEdit() + key = _("Images (*.jpg *.png *.gif *.tiff *.svg *.tif *.jpeg)") + file = ui.utils.getFile(self.parent, _("Add an image"), "picture", key) + if not file: + return + path = self.deck.addMedia(file) + w.insertHtml('' % path) + + def onAddSound(self): + if not self.hasMediaDir(): + return + # get this before we open the dialog + w = self.focusedEdit() + key = _("Sounds (*.mp3 *.ogg *.wav)") + file = ui.utils.getFile(self.parent, _("Add audio"), "audio", key) + if not file: + return + anki.sound.play(file) + path = self.deck.addMedia(file) + w.insertHtml('[sound:%s]' % path) + + def hasMediaDir(self): + if self.deck.mediaDir(create=True): + return True + ui.utils.showInfo("Please save your deck first.", self.parent) + return False + +class FactEdit(QTextEdit): + + def __init__(self, parent, *args): + QTextEdit.__init__(self, *args) + self.parent = parent + + def insertFromMimeData(self, source): + if source.hasText(): + self.insertPlainText(source.text()) + elif source.hasHtml(): + self.insertHtml(self.simplyHTML(unicode(source.html()))) + + def simplifyHTML(self, html): + "Remove all style information and P tags." + html = re.sub("\n", " ", html) + html = re.sub("
", "\n", html) + html = re.sub("

", "\n\n", html) + html = re.sub('', "", html) + html = stripHTML(html) + html = html.replace("\n", "
") + html = re.sub("\s\s+", " ", html).strip() + return html + + def focusOutEvent(self, evt): + QTextEdit.focusOutEvent(self, evt) + self.parent.lastFocusedEdit = self + self.parent.resetFormatButtons() + self.parent.disableButtons() + + # this shouldn't be necessary if/when we move away from kakasi + def mouseDoubleClickEvent(self, evt): + r = QRegExp("\\{(.*[|,].*)\\}") + r.setMinimal(True) + + mouseposition = self.textCursor().position() + + blockoffset = 0 + result = r.indexIn(self.toPlainText(), 0) + + found = "" + + while result != -1: + if mouseposition > result and mouseposition < result + r.matchedLength(): + mouseposition -= result + 1 + frompos = 0 + topos = 0 + + string = r.cap(1) + offset = 0 + bits = re.split("[|,]", unicode(string)) + for index in range(0, len(bits)): + offset += len(bits[index]) + 1 + if mouseposition < offset: + found = bits[index] + break + break + + blockoffset= result + r.matchedLength() + result = r.indexIn(self.toPlainText(), blockoffset) + + if found == "": + QTextEdit.mouseDoubleClickEvent(self,evt) + return + self.setPlainText(self.toPlainText().replace(result, r.matchedLength(), found)) + + def focusInEvent(self, evt): + if (self.parent.lastFocusedEdit and + self.parent.lastFocusedEdit is not self): + # remove selection from previous widget + try: + cur = self.parent.lastFocusedEdit.textCursor() + cur.clearSelection() + self.parent.lastFocusedEdit.setTextCursor(cur) + except RuntimeError: + # old widget was deleted + pass + self.lastFocusedEdit = None + QTextEdit.focusInEvent(self, evt) + self.parent.formatChanged(None) + self.parent.enableButtons() diff --git a/ankiqt/ui/graphs.py b/ankiqt/ui/graphs.py new file mode 100644 index 000000000..b58664840 --- /dev/null +++ b/ankiqt/ui/graphs.py @@ -0,0 +1,213 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import sys +import anki, anki.graphs, anki.utils +from ankiqt import ui + +from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib import rc +rc('font', **{'sans-serif': 'Arial', + 'serif': 'Arial', + 'size': 20.0}) +rc('legend', fontsize=14.0) + +class AnkiFigureCanvas (FigureCanvas): + def __init__(self, fig, parent=None): + self.fig = fig + FigureCanvas.__init__(self, self.fig) + self.setParent(parent) + + self.fig.subplots_adjust(left=0.08, right=0.96, bottom=0.15, top=0.95) + + FigureCanvas.setSizePolicy(self, + QSizePolicy.Expanding, + QSizePolicy.Expanding) + FigureCanvas.updateGeometry(self) + + def sizeHint(self): + w, h = self.get_width_height() + return QSize(w, h) + + def minimumSizeHint(self): + return QSize(10, 10) + + # bug in matplotlib + def keyReleaseEvent(self, evt): + evt.ignore() + + def keyPressEvent(self, evt): + evt.ignore() + +class AdjustableFigure(QWidget): + + def __init__(self, figureFunc, range=None): + QWidget.__init__(self) + self.vbox = QVBoxLayout() + self.vbox.setSpacing(2) + self.range = range + self.figureFunc = figureFunc + self.setLayout(self.vbox) + self.updateTimer = None + self.hbox = QHBoxLayout() + self.hbox.addSpacing(10) + self.hbox.addStretch(1) + + def addWidget(self, widget): + self.vbox.addWidget(widget) + + def addFigure(self): + if self.range is None: + self.figureCanvas = AnkiFigureCanvas(self.figureFunc()) + else: + self.figureCanvas = AnkiFigureCanvas(self.figureFunc(self.range)) + self.addWidget(self.figureCanvas) + + def updateFigure(self): + self.updateTimer = None + self.setUpdatesEnabled(False) + idx = self.vbox.indexOf(self.figureCanvas) + self.vbox.removeWidget(self.figureCanvas) + self.figureCanvas.deleteLater() + self.figureCanvas = AnkiFigureCanvas(self.figureFunc(self.range)) + self.vbox.insertWidget(idx, self.figureCanvas) + self.setUpdatesEnabled(True) + + def addSlider(self, label, choices): + self.choices = choices + self.labelText = label + self.label = QLabel() + self.label.setFixedWidth(110) + self.updateLabel() + self.slider = QScrollBar(Qt.Horizontal) + self.slider.setFixedWidth(150) + self.slider.setRange(0, len(choices) - 1) + self.slider.setValue(choices.index(self.range)) + self.slider.setFocusPolicy(Qt.TabFocus) + self.hbox.addWidget(self.label) + self.hbox.addWidget(self.slider) + self.connect(self.slider, SIGNAL("valueChanged(int)"), + self.sliderChanged) + + def updateLabel(self): + self.label.setText("%s: %s" % (self.labelText, + anki.utils.fmtTimeSpan(self.range*86400))) + + def sliderChanged(self, index): + self.range = self.choices[index] + self.updateLabel() + self.scheduleUpdate() + + def scheduleUpdate(self): + if not self.updateTimer: + self.updateTimer = QTimer(self) + self.updateTimer.setSingleShot(True) + self.updateTimer.start(200) + self.connect(self.updateTimer, SIGNAL("timeout()"), + self.updateFigure) + else: + self.updateTimer.setInterval(200) + + def addExplanation(self, text): + self.explanation = QLabel(text) + self.hbox.insertWidget(1, self.explanation) + self.vbox.addLayout(self.hbox) + +class IntervalGraph(QDialog): + + def __init__(self, parent, *args): + QDialog.__init__(self, parent, Qt.Window) + ui.dialogs.open("Graphs", self) + self.setAttribute(Qt.WA_DeleteOnClose) + + def reject(self): + ui.dialogs.close("Graphs") + QDialog.reject(self) + +def intervalGraph(parent, deck): + dg = anki.graphs.DeckGraphs(deck) + # dialog setup + d = IntervalGraph(parent) + d.setWindowTitle(_("Deck graphs")) + if sys.platform.startswith("darwin"): + d.setMinimumSize(740, 680) + else: + d.setMinimumSize(670, 715) + scroll = QScrollArea(d) + topBox = QVBoxLayout(d) + topBox.addWidget(scroll) + frame = QWidget(scroll) + vbox = QVBoxLayout(frame) + vbox.setMargin(0) + vbox.setSpacing(0) + frame.setLayout(vbox) + + range = [7, 30, 90, 180, 365, 730, 1095, 1460, 1825] + + # views + nextDue = AdjustableFigure(dg.nextDue, 30) + nextDue.addWidget(QLabel(_("

Due cards

"))) + nextDue.addFigure() + nextDue.addSlider(_("Period"), range) + nextDue.addExplanation(_("The number of cards due each day over the " + "period.\n" + "Today is 0; cards less than zero are overdue.")) + + vbox.addWidget(nextDue) + + cumDue = AdjustableFigure(dg.cumulativeDue, 30) + cumDue.addWidget(QLabel(_("

Cumulative view of due cards

"))) + cumDue.addFigure() + cumDue.addSlider(_("Period"), range) + cumDue.addExplanation(_("The number of cards due each day, assuming " + "no study.")) + + vbox.addWidget(cumDue) + + interval = AdjustableFigure(dg.intervalPeriod, 30) + interval.addWidget(QLabel(_("

Card intervals

"))) + interval.addFigure() + interval.addSlider(_("Period"), range) + interval.addExplanation(_("The number of cards scheduled for a given " + "number of days.")) + vbox.addWidget(interval) + + added = AdjustableFigure(dg.addedRecently, 30) + added.addWidget(QLabel(_("

Added cards

"))) + added.addFigure() + added.addSlider(_("Period"), range) + added.addExplanation(_("The number of cards added on a given day.")) + vbox.addWidget(added) + + answered = AdjustableFigure(lambda *args: apply( + dg.addedRecently, args + ('firstAnswered',)), 30) + answered.addWidget(QLabel(_("

First answered

"))) + answered.addFigure() + answered.addSlider(_("Period"), range) + answered.addExplanation(_("The number of cards first answered on a " + "given day.\nThis will be different to " + "'added cards' if you are\nusing a " + "pre-made deck.")) + vbox.addWidget(answered) + + eases = AdjustableFigure(dg.easeBars) + eases.addWidget(QLabel(_("

Card ease

"))) + eases.addFigure() + eases.addExplanation(_("The amount of times you answered a card at " + "each ease level.")) + vbox.addWidget(eases) + + scroll.setWidget(frame) + buttonBox = QDialogButtonBox(d) + buttonBox.setOrientation(Qt.Horizontal) + close = buttonBox.addButton(QDialogButtonBox.Close) + close.setDefault(True) + + d.connect(buttonBox, SIGNAL("rejected()"), d.close) + + topBox.addWidget(buttonBox) + + d.show() + diff --git a/ankiqt/ui/help.py b/ankiqt/ui/help.py new file mode 100644 index 000000000..f67414fde --- /dev/null +++ b/ankiqt/ui/help.py @@ -0,0 +1,177 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +import sys +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import ankiqt.forms + +# Hideable help area widget +########################################################################## + +class HelpArea(object): + + helpAreaWidth = 300 + minAppWidth = 550 + + def __init__(self, helpFrame, config, mainWindow=None, focus=None): + self.helpFrame = helpFrame + self.widget = helpFrame.findChild(QTextBrowser) + self.mainWindow = mainWindow + if mainWindow: + self.focus=mainWindow + else: + self.focus=focus + self.config = config + self.widget.connect(self.widget, SIGNAL("anchorClicked(QUrl)"), + self.anchorClicked) + self.hide() + self.data = HelpData() + + def getMinAppWidth(self): + if self.config['easeButtonStyle'] == 'compact': + return self.minAppWidth - 150 + return self.minAppWidth + + def show(self): + "Show the help area." + if self.mainWindow: + self.mainWindow.setMinimumWidth( + self.getMinAppWidth()+self.helpAreaWidth) + self.helpFrame.show() + self.widget.show() + + def hide(self): + self.currentKey = None + self.helpFrame.hide() + self.widget.hide() + if self.mainWindow: + self.mainWindow.setMinimumWidth(self.getMinAppWidth()) + # force resize + g = self.mainWindow.geometry() + if g.width() < self.getMinAppWidth(): + self.mainWindow.setGeometry(QRect(g.left(), + g.top(), + self.getMinAppWidth(), + g.height())) + self.mainWindow.runHook("helpChanged") + + def showKey(self, key, noFlush=False, dict=False): + "Look up KEY in DATA and show." + text = self.data[key] + # accomodate some quirks in QTextEdit's html interpreter + text = text.strip() + if dict: + text = text % dict + self.showText(text, noFlush, key=key) + + def showHideableKey(self, key, dict=False): + "Look up a hideable KEY in DATA and show." + if self.config.get("hide:" + key, False): + # user requested not to see this key. if previous key was help, we + # need to hide it + if self.currentKey in self.data: + self.hide() + return + self.showKey(key, noFlush=True, dict=dict) + self.addRemover(key) + self.flush() + + def showText(self, text, noFlush=False, py={}, key="misc"): + self.show() + self.buffer = text + self.addHider() + self.handlers = py + if not noFlush: + self.flush() + self.currentKey = key + if self.mainWindow: + self.mainWindow.runHook("helpChanged") + + def flush(self): + if sys.platform.startswith("darwin"): + font = "helvetica" + else: + font = "arial" + # qt seems to ignore font-size on elements like h1 + style = ("\n") % font + self.widget.setHtml(style + '
' + + self.buffer + '
') + + def addRemover(self, key): + self.buffer += (" / " + + _("Don't show this again.") + + "") % key + + def addHider(self): + self.buffer += _("

Hide this") + + def anchorClicked(self, url): + # prevent the link being handled + self.widget.setSource(QUrl("")) + addr = unicode(url.toString()) + if addr.startswith("hide:"): + if len(addr) > 5: + # hide for good + self.config[addr] = True + self.hide() + elif addr.startswith("py:"): + key = addr[3:] + if key in self.handlers: + self.handlers[key]() + else: + # open in browser + QDesktopServices.openUrl(QUrl(url)) + if self.focus: + self.focus.setFocus() + +# Text strings +########################################################################## + +class HelpData(dict): + + def __init__(self): + self['learn'] = _(""" +

Learning new cards

Anki is currently in 'learning mode'. +

+As an alternative to using the mouse, spacebar and the number keys are +available. +

+More information +""") + + self['review'] = _(""" +

Reviewing

You are currently looking at a card you have seen before. +Unlike new cards, it's important to try and review previously seen cards as +promptly as possible, in order to ensure your previous effort spent +remembering the cards is not wasted.

At the bottom of the main window, the +"Remaining" figure indicates how many previously reviewed words are waiting +for you today. Once this number reaches 0, you can close Anki, or continue +studying new cards.""") + + self['finalReview'] = _("""

Final review

You are now being +shown cards that are due soon (in the next 5 hours by default). This includes +any cards you failed recently. You can answer them now, or come back later - +it's up to you.""") + + self['add'] = _(""" +

Adding cards

+Please enter some things you want to learn. +

Shortcuts

+ + + + + + + + + +
Tab change between fields.
Ctrl+Enter add the current card.
Esc close the dialog.
Ctrl+B bold
Ctrl+I italic
Ctrl+U underline
Alt+1 enable/disable card model 1
Alt+2 enable/disable card model 2
+ +

Cards

Depending on the language you selected, more than one card may +be generated. This allows you to practice both Production (trying to produce +the target idea/phrase yourself), and Recognition (quickly recognizing and +understanding the target idea/phrase). To change which cards are automatically +generated, click the rightmost button at the top.""") diff --git a/ankiqt/ui/importing.py b/ankiqt/ui/importing.py new file mode 100644 index 000000000..b9b169973 --- /dev/null +++ b/ankiqt/ui/importing.py @@ -0,0 +1,202 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +import os, copy, time +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import anki +import anki.importing as importing +from anki.errors import * +import ankiqt.forms +from ankiqt import ui + +class ChangeMap(QDialog): + def __init__(self, parent, model, current): + QDialog.__init__(self, parent) + self.parent = parent + self.model = model + self.dialog = ankiqt.forms.changemap.Ui_ChangeMap() + self.dialog.setupUi(self) + n = 0 + for field in self.model.fieldModels: + item = QListWidgetItem(_("Map to %s") % field.name) + self.dialog.fields.addItem(item) + if current == field.name: + self.dialog.fields.setCurrentRow(n) + n += 1 + self.dialog.fields.addItem(QListWidgetItem(_("Discard field"))) + if current is None: + self.dialog.fields.setCurrentRow(n) + self.field = None + + def getField(self): + self.exec_() + return self.field + + def accept(self): + row = self.dialog.fields.currentRow() + if row < len(self.model.fieldModels): + self.field = self.model.fieldModels[row] + else: + self.field = None + QDialog.accept(self) + +class ImportDialog(QDialog): + + def __init__(self, parent): + QDialog.__init__(self, parent) + self.parent = parent + self.dialog = ankiqt.forms.importing.Ui_ImportDialog() + self.dialog.setupUi(self) + self.setupMappingFrame() + self.setupOptions() + self.exec_() + + def setupOptions(self): + self.file = None + self.model = self.parent.deck.currentModel + self.modelChooser = ui.modelchooser.ModelChooser(self, + self.parent, + self.parent.deck, + self.modelChanged) + self.importerChanged(0) + self.connect(self.dialog.type, SIGNAL("activated(int)"), + self.importerChanged) + self.dialog.type.insertItems(0, QStringList(list(zip(*importing.Importers)[0]))) + self.connect(self.dialog.file, SIGNAL("clicked()"), + self.changeFile) + self.dialog.modelArea.setLayout(self.modelChooser) + self.connect(self.dialog.importButton, SIGNAL("clicked()"), + self.doImport) + self.maybePreview() + + def importerChanged(self, idx): + self.importerFunc = zip(*importing.Importers)[1][idx] + if self.importerFunc.needMapper: + self.modelChooser.show() + self.dialog.tagDuplicates.show() + else: + self.modelChooser.hide() + self.dialog.tagDuplicates.hide() + self.dialog.file.setText(_("Choose file...")) + self.file = None + self.maybePreview() + + def changeFile(self): + key = zip(*importing.Importers)[0][self.dialog.type.currentIndex()] + file = ui.utils.getFile(self, _("Import file"), "import", key) + if not file: + return + self.file = unicode(file) + self.dialog.file.setText(os.path.basename(self.file)) + self.maybePreview() + + def maybePreview(self): + if self.file and self.model: + self.dialog.status.setText("") + self.showMapping() + else: + self.hideMapping() + + def modelChanged(self, model): + self.model = model + self.maybePreview() + + def doImport(self): + self.dialog.status.setText(_("Importing. Anki will freeze for a while..")) + t = time.time() + while self.parent.app.hasPendingEvents(): + self.parent.app.processEvents() + if time.time() - t > 1: + # windows sometimes has pending events permanently? + break + self.importer.mapping = self.mapping + self.importer.tagsToAdd = unicode(self.dialog.tags.text()) + self.importer.tagDuplicates = self.dialog.tagDuplicates.isChecked() + try: + self.importer.doImport() + except ImportFormatError, e: + msg = _("Importing failed.\n") + msg += e.data['info'] + self.dialog.status.setText(msg) + return + except DeckWrongFormatError, e: + msg = _("Import failed: %s") % `e.data` + self.dialog.status.setText(msg) + return + txt = ( + _("Importing complete. %(num)d cards imported from %(file)s.\n") % + {"num": self.importer.total, "file": os.path.basename(self.file)}) + txt += _("Click the close button or import another file.\n\n") + if self.importer.log: + txt += _("Log of import:\n") + "\n".join(self.importer.log) + self.dialog.status.setText(txt) + self.file = None + self.maybePreview() + self.parent.deck.updateAllPriorities() + self.parent.rebuildQueue() + + def setupMappingFrame(self): + # qt seems to have a bug with adding/removing from a grid, so we add + # to a separate object and add/remove that instead + self.mapbox = QVBoxLayout(self.dialog.mappingArea) + self.mapwidget = None + + def hideMapping(self): + self.dialog.mappingGroup.hide() + + def showMapping(self, keepMapping=False): + # first, check that we can read the file + try: + self.importer = self.importerFunc(self.parent.deck, self.file) + if not keepMapping: + self.mapping = self.importer.mapping + except ImportFormatError, e: + self.dialog.status.setText( + _("Unable to read file.\n\n%(info)s") % { + 'type': e.data.get('type', ""), + 'info': e.data.get('info', ""), + }) + self.file = None + self.maybePreview() + return + self.dialog.mappingGroup.show() + if self.importer.fields(): + self.dialog.mappingArea.show() + else: + self.dialog.mappingArea.hide() + return + # set up the mapping grid + if self.mapwidget: + self.mapbox.removeWidget(self.mapwidget) + self.mapwidget.deleteLater() + self.mapwidget = QWidget() + self.mapbox.addWidget(self.mapwidget) + self.grid = QGridLayout(self.mapwidget) + self.mapwidget.setLayout(self.grid) + self.grid.setMargin(6) + self.grid.setSpacing(12) + fields = self.importer.fields() + for num in range(len(self.mapping)): + text = _("Field %d of file is:") % (num + 1) + self.grid.addWidget(QLabel(text), num, 0) + if self.mapping[num]: + text = _("mapped to %s") % self.mapping[num].name + else: + text = _("") + self.grid.addWidget(QLabel(text), num, 1) + button = QPushButton(_("Change")) + self.grid.addWidget(button, num, 2) + self.connect(button, SIGNAL("clicked()"), + lambda s=self,n=num: s.changeMappingNum(n)) + + def changeMappingNum(self, n): + f = ChangeMap(self.parent, self.model, self.mapping[n]).getField() + try: + # make sure we don't have it twice + index = self.mapping.index(f) + self.mapping[index] = None + except ValueError: + pass + self.mapping[n] = f + self.showMapping(keepMapping=True) diff --git a/ankiqt/ui/lookup.py b/ankiqt/ui/lookup.py new file mode 100644 index 000000000..0a93dd56a --- /dev/null +++ b/ankiqt/ui/lookup.py @@ -0,0 +1,75 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import urllib, re +import anki + +# Tools - looking up words in the dictionary +########################################################################## + +class Lookup(object): + + def __init__(self, main): + self.main = main + + def selection(self, function): + "Get the selected text and look it up with FUNCTION." + text = unicode(self.main.mainWin.mainText.textCursor().selectedText()) + if "\n" in text: + self.main.setStatus(_("Can't look up a selection with a newline.")) + return + text = text.strip() + if not text: + self.main.setStatus(_("Empty selection.")) + return + function(text) + + def edictKanji(self, text): + self.edict(text, True) + + def edict(self, text, kanji=False): + "Look up TEXT with edict." + if kanji: + x="M" + else: + x="U" + baseUrl="http://www.csse.monash.edu.au/~jwb/cgi-bin/wwwjdic.cgi?1M" + x + if isJapaneseText(text): + baseUrl += "J" + else: + baseUrl += "E" + url = baseUrl + urllib.quote(text.encode("utf-8")) + self.main.setStatus(_("Looking %s up on edict..") % text) + qurl = QUrl() + qurl.setEncodedUrl(url) + QDesktopServices.openUrl(qurl) + + def alc(self, text): + "Look up TEXT with ALC." + newText = urllib.quote(text.encode("utf-8")) + url = ( + "http://eow.alc.co.jp/" + + newText + + "/UTF-8/?ref=sa") + self.main.setStatus(_("Looking %s up on ALC..") % text) + qurl = QUrl() + qurl.setEncodedUrl(url) + QDesktopServices.openUrl(qurl) + +def isJapaneseText(text): + "True if 70% of text is a Japanese character." + total = len(text) + if total == 0: + return True + jp = 0 + en = 0 + for c in text: + if ord(c) >= 0x2E00 and ord(c) <= 0x9FFF: + jp += 1 + if re.match("[A-Za-z]", c): + en += 1 + if not jp: + return False + return ((jp + 1) / float(en + 1)) >= 1.0 diff --git a/ankiqt/ui/main.py b/ankiqt/ui/main.py new file mode 100644 index 000000000..900ba00ce --- /dev/null +++ b/ankiqt/ui/main.py @@ -0,0 +1,1345 @@ +# Copyright: Damien Elmes +# -*- coding: utf-8 -*- +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * + +# fixme: sample files read only, need to copy + +import os, sys, re, types, gettext, stat, traceback +import copy, shutil, time + +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from anki import DeckStorage +from anki.errors import * +from anki.sound import hasSound, playFromText +from anki.utils import addTags, deleteTags +from anki.media import rebuildMediaDir +import anki.lang +import ankiqt +ui = ankiqt.ui +config = ankiqt.config + +class AnkiQt(QMainWindow): + def __init__(self, app, config, args): + QMainWindow.__init__(self) + if sys.platform.startswith("darwin"): + qt_mac_set_menubar_icons(False) + ankiqt.mw = self + self.app = app + self.config = config + self.deck = None + self.views = [] + self.setLang() + self.setupFonts() + self.setupBackupDir() + self.setupHooks() + self.loadUserCustomisations() + self.mainWin = ankiqt.forms.main.Ui_MainWindow() + self.mainWin.setupUi(self) + self.alterShortcuts() + self.help = ui.help.HelpArea(self.mainWin.helpFrame, self.config, self) + self.trayIcon = ui.tray.AnkiTrayIcon( self ) + self.connectMenuActions() + self.resize(self.config['mainWindowSize']) + self.move(self.config['mainWindowPos']) + self.maybeMoveWindow() + self.bodyView = ui.view.View(self, self.mainWin.mainText, + self.mainWin.mainTextFrame) + self.addView(self.bodyView) + self.statusView = ui.status.StatusView(self) + self.addView(self.statusView) + self.setupButtons() + self.setupAnchors() + if not self.config['showToolbar']: + self.removeToolBar(self.mainWin.toolBar) + self.mainWin.toolBar.hide() + self.show() + if sys.platform.startswith("darwin"): + self.setUnifiedTitleAndToolBarOnMac(True) + pass + # load deck + try: + self.maybeLoadLastDeck(args) + finally: + self.setEnabled(True) + # the focus is not set while disabled, so fetch card again + self.moveToState("auto") + # run after-init hook + try: + self.runHook('init') + except: + print _("Error running initHook. Broken plugin?") + print traceback.print_exc() + # check for updates + self.setupAutoUpdate() + + def maybeMoveWindow(self): + # If the window is positioned off the screen, move it back into view + moveWin = False + if (self.pos().x() > (self.app.desktop().width() - 200) or + self.pos().x() < 0): + moveWin = True + newX = self.app.desktop().width() - self.size().width() + else: + newX = self.pos().x() + if (self.pos().y() > (self.app.desktop().height() - 200) or + self.pos().y() < 0): + moveWin = True + newY = self.app.desktop().height() - self.size().height() + else: + newY = self.pos().y() + if moveWin: + self.move( newX, newY ) + + # State machine + ########################################################################## + + def addView(self, view): + self.views.append(view) + + def updateViews(self, status): + if self.deck is None and status != "noDeck": + raise "updateViews() called with no deck. status=%s" % status + for view in self.views: + view.setState(status) + + def pauseViews(self): + if getattr(self, 'viewsBackup', None): + return + self.viewsBackup = self.views + self.views = [] + + def restoreViews(self): + self.views = self.viewsBackup + self.viewsBackup = None + + def reset(self): + if self.deck: + self.deck.refresh() + self.deck.updateAllPriorities() + self.rebuildQueue() + + def rebuildQueue(self): + # qt on mac is misbehaving + mac = sys.platform.startswith("darwin") + if not mac: self.setEnabled(False) + self.mainWin.mainText.clear() + self.mainWin.mainText.setHtml(_("

Building revision queue..

")) + self.app.processEvents() + self.deck.rebuildQueue() + if not mac: self.setEnabled(True) + self.moveToState("initial") + + def moveToState(self, state): + if state == "initial": + # reset current card and load again + self.currentCard = None + self.lastCard = None + if self.deck: + self.mainWin.menu_Lookup.setEnabled(True) + self.enableDeckMenuItems() + self.updateRecentFilesMenu() + self.updateViews(state) + return self.moveToState("getQuestion") + else: + return self.moveToState("noDeck") + elif state == "auto": + self.currentCard = None + if self.deck: + return self.moveToState("getQuestion") + else: + return self.moveToState("noDeck") + # save the new & last state + self.lastState = getattr(self, "state", None) + self.state = state + self.updateTitleBar() + if state == "noDeck": + self.help.hide() + self.currentCard = None + self.lastCard = None + self.disableDeckMenuItems() + self.resetButtons() + # hide all deck-associated dialogs + ui.dialogs.closeAll() + elif state == "getQuestion": + self.deck._countsDirty = True + if self.deck.cardCount() == 0: + return self.moveToState("deckEmpty") + else: + if not self.currentCard: + self.currentCard = self.deck.getCard() + if self.currentCard: + if self.lastCard: + if self.lastCard.id == self.currentCard.id: + if self.currentCard.combinedDue > time.time(): + # if the same card is being shown and it's not + # due yet, give up + return self.moveToState("deckFinished") + self.enableCardMenuItems() + return self.moveToState("showQuestion") + else: + return self.moveToState("deckFinished") + elif state == "deckEmpty": + self.resetButtons() + self.disableCardMenuItems() + self.mainWin.menu_Lookup.setEnabled(False) + elif state == "deckFinished": + self.deck.s.flush() + self.resetButtons() + self.mainWin.menu_Lookup.setEnabled(False) + self.disableCardMenuItems() + self.startRefreshTimer() + self.bodyView.setState(state) + elif state == "showQuestion": + if self.deck.mediaDir(): + os.chdir(self.deck.mediaDir()) + self.resetButtons() + self.showAnswerButton() + self.updateMarkAction() + self.runHook('showQuestion') + elif state == "showAnswer": + self.currentCard.stopTimer() + self.resetButtons() + self.showEaseButtons() + self.enableCardMenuItems() + self.updateViews(state) + + def keyPressEvent(self, evt): + "Show answer on RET or register answer." + if self.state == "showQuestion": + if evt.key() in (Qt.Key_Enter, + Qt.Key_Return): + evt.accept() + return self.moveToState("showAnswer") + elif self.state == "showAnswer": + key = unicode(evt.text()) + if key and key >= "0" and key <= "4": + # user entered a quality setting + num=int(key) + evt.accept() + return self.cardAnswered(num) + evt.ignore() + + def cardAnswered(self, quality): + "Reschedule current card and move back to getQuestion state." + # copy card for undo + self.lastCardBackup = copy.copy(self.currentCard) + # remove card from session before updating it + self.deck.s.expunge(self.currentCard) + self.deck.answerCard(self.currentCard, quality) + self.lastScheduledTime = anki.utils.fmtTimeSpan( + self.currentCard.due - time.time()) + self.lastQuality = quality + self.lastCard = self.currentCard + self.currentCard = None + if self.config['saveAfterAnswer']: + num = self.config['saveAfterAnswerNum'] + stats = self.deck.getStats() + if stats['gTotal'] % num == 0: + self.saveDeck() + self.moveToState("getQuestion") + + def startRefreshTimer(self): + "Update the screen once a minute until next card is displayed." + if getattr(self, 'refreshTimer', None): + return + self.refreshTimer = QTimer(self) + self.refreshTimer.start(60000) + self.connect(self.refreshTimer, SIGNAL("timeout()"), self.refreshStatus) + # start another time to refresh exactly after we've finished + next = self.deck.earliestTime() + if next: + delay = next - time.time() + if delay > 86400: + return + if delay < 0: + sys.stderr.write("earliest time returned negative value\n") + return + t = QTimer(self) + t.setSingleShot(True) + self.connect(t, SIGNAL("timeout()"), self.refreshStatus) + t.start((delay+1)*1000) + + def refreshStatus(self): + "If triggered when the deck is finished, reset state." + if self.state == "deckFinished": + # don't try refresh if the deck is closed during a sync + if self.deck: + self.deck.markExpiredCardsDue() + self.moveToState("getQuestion") + if self.state != "deckFinished": + if self.refreshTimer: + self.refreshTimer.stop() + self.refreshTimer = None + + # Buttons + ########################################################################## + + def setupButtons(self): + self.outerButtonBox = QHBoxLayout(self.mainWin.buttonWidget) + self.outerButtonBox.setMargin(3) + self.outerButtonBox.setSpacing(0) + self.innerButtonWidget = None + + def resetButtons(self): + # this round-about process is trying to work around a bug in qt + if self.lastState == self.state: + return + if self.innerButtonWidget: + self.outerButtonBox.removeWidget(self.innerButtonWidget) + self.innerButtonWidget.deleteLater() + self.innerButtonWidget = QWidget() + self.outerButtonBox.addWidget(self.innerButtonWidget) + self.buttonBox = QVBoxLayout(self.innerButtonWidget) + self.buttonBox.setSpacing(3) + self.buttonBox.setMargin(3) + if self.config['easeButtonHeight'] == "tall": + self.easeButtonHeight = 50 + else: + if sys.platform.startswith("darwin"): + self.easeButtonHeight = 35 + else: + self.easeButtonHeight = 25 + + def showAnswerButton(self): + if self.lastState == self.state: + return + button = QPushButton(_("Show answer")) + button.setFixedHeight(self.easeButtonHeight) + self.buttonBox.addWidget(button) + button.setFocus() + button.setDefault(True) + self.connect(button, SIGNAL("clicked()"), + lambda: self.moveToState("showAnswer")) + + def getSpacer(self, hpolicy=QSizePolicy.Preferred): + return QSpacerItem(20, 20, + hpolicy, + QSizePolicy.Preferred) + + def showEaseButtons(self): + # if the state hasn't changed, do nothing + if self.lastState == self.state: + return + # gather next intervals + nextInts = {} + for i in range(5): + s=self.deck.nextIntervalStr(self.currentCard, i) + nextInts["ease%d" % i] = s + text = ( + (_("Completely forgot"), ""), + (_("Made a mistake"), ""), + (_("Difficult"), + _("Next in %(ease2)s")), + (_("About right"), + _("Next in %(ease3)s")), + (_("Easy"), + _("Next in %(ease4)s"))) + # button grid + grid = QGridLayout() + grid.setSpacing(3) + if self.config['easeButtonStyle'] == 'standard': + button3 = self.showStandardEaseButtons(grid, nextInts, text) + else: + button3 = self.showCompactEaseButtons(grid, nextInts) + self.buttonBox.addLayout(grid) + button3.setFocus() + + def showStandardEaseButtons(self, grid, nextInts, text): + # show 'how well?' message + hbox = QHBoxLayout() + hbox.addItem(self.getSpacer(QSizePolicy.Expanding)) + label = QLabel(self.withInterfaceFont( + _("How well did you remember?"))) + hbox.addWidget(label) + hbox.addItem(self.getSpacer(QSizePolicy.Expanding)) + self.buttonBox.addLayout(hbox) + # populate buttons + button3 = None + for i in range(5): + button = QPushButton(str(i)) + button.setFixedWidth(100) + button.setFixedHeight(self.easeButtonHeight) + if i == 3: + button3 = button + grid.addItem(self.getSpacer(QSizePolicy.Expanding), i, 0) + grid.addWidget(button, i, 1) + grid.addItem(self.getSpacer(), i, 2) + grid.addWidget(QLabel(self.withInterfaceFont(text[i][0])), i, 3) + grid.addItem(self.getSpacer(), i, 4) + if not self.config['suppressEstimates']: + grid.addWidget(QLabel(self.withInterfaceFont( + text[i][1] % nextInts)), i, 5) + grid.addItem(self.getSpacer(QSizePolicy.Expanding), i, 6) + self.connect(button, SIGNAL("clicked()"), + lambda i=i: self.cardAnswered(i)) + return button3 + + def showCompactEaseButtons(self, grid, nextInts): + text = ( + (_("%(ease0)s")), + (_("%(ease1)s")), + (_("%(ease2)s")), + (_("%(ease3)s")), + (_("%(ease4)s"))) + button3 = None + for i in range(5): + button = QPushButton(str(i)) + button.setFixedHeight(self.easeButtonHeight) + #button.setFixedWidth(70) + if i == 3: + button3 = button + grid.addWidget(button, 0, (i*2)+1) + if not self.config['suppressEstimates']: + label = QLabel(self.withInterfaceFont(text[i] % nextInts)) + label.setAlignment(Qt.AlignHCenter) + grid.addWidget(label, 1, (i*2)+1) + self.connect(button, SIGNAL("clicked()"), + lambda i=i: self.cardAnswered(i)) + return button3 + + def withInterfaceFont(self, text): + family = self.config["interfaceFontFamily"] + size = self.config["interfaceFontSize"] + colour = self.config["interfaceColour"] + css = ('.interface {font-family: "%s"; font-size: %spx; color: %s}\n' % + (family, size, colour)) + css = "\n" + text = css + '' + text + "" + return text + + # Hooks + ########################################################################## + + def setupHooks(self): + self.hooks = {} + + def addHook(self, hookName, func): + if not self.hooks.get(hookName, None): + self.hooks[hookName] = [] + if func not in self.hooks[hookName]: + self.hooks[hookName].append(func) + + def removeHook(self, hookName, func): + hook = self.hooks.get(hookName, []) + if func in hook: + hook.remove(func) + + def runHook(self, hookName, *args): + hook = self.hooks.get(hookName, None) + if hook: + for func in hook: + func(*args) + + # Deck loading & saving: backend + ########################################################################## + + def setupBackupDir(self): + anki.deck.backupDir = os.path.join( + self.config.configPath, "backups") + + def loadDeck(self, deckPath, sync=True): + "Load a deck and update the user interface. Maybe sync." + # return None if error should be reported + # return 0 to fail with no error + # return True on success + try: + self.pauseViews() + if not self.saveAndClose(): + return 0 + finally: + self.restoreViews() + if not os.path.exists(deckPath): + return + try: + self.deck = DeckStorage.Deck(deckPath, rebuild=False) + except (IOError, ImportError): + return + except DeckWrongFormatError, e: + self.importOldDeck(deckPath) + if not self.deck: + return + except DeckAccessError, e: + if e.data.get('type') == 'inuse': + ui.utils.showWarning(_("Unable to load the same deck twice.")) + return 0 + return + self.updateRecentFiles(self.deck.path) + if sync and self.config['syncOnLoad']: + self.syncDeck(False) + else: + try: + self.rebuildQueue() + except: + ui.utils.showWarning(_( + "Error building queue. Attempting recovery..")) + self.onCheckDB() + # try again + self.rebuildQueue() + return True + + def importOldDeck(self, deckPath): + from anki.importing.anki03 import Anki03Importer + # back up the old file + newPath = re.sub("\.anki$", ".anki-v3", deckPath) + while os.path.exists(newPath): + newPath += "-1" + os.rename(deckPath, newPath) + try: + self.deck = DeckStorage.Deck(deckPath) + imp = Anki03Importer(self.deck, newPath) + imp.doImport() + except DeckWrongFormatError, e: + ui.utils.showWarning(_( + "An error occurred while upgrading:\n%s") % `e.data`) + return + self.rebuildQueue() + + def maybeLoadLastDeck(self, args): + "Open the last deck if possible." + # try a command line argument if available + try: + if args: + f = unicode(args[0], sys.getfilesystemencoding()) + return self.loadDeck(f) + except: + sys.stderr.write("Error loading last deck.\n") + traceback.print_exc() + self.deck = None + return self.moveToState("initial") + # try recent deck paths + for path in self.config['recentDeckPaths']: + try: + r = self.loadDeck(path) + if r == 0: + # in use + continue + return r + except: + sys.stderr.write("Error loading last deck.\n") + traceback.print_exc() + self.deck = None + return self.moveToState("initial") + return self.moveToState("initial") + + def getDefaultDir(self, save=False): + "Try and get default dir from most recently opened file." + defaultDir = "" + if self.config['recentDeckPaths']: + latest = self.config['recentDeckPaths'][0] + defaultDir = os.path.dirname(latest) + else: + if save: + defaultDir = unicode(os.path.expanduser("~/"), + sys.getfilesystemencoding()) + else: + samples = self.getSamplesDir() + if samples: + return samples + return defaultDir + + def getSamplesDir(self): + path = os.path.join(ankiqt.runningDir, "libanki") + if not os.path.exists(path): + path = os.path.join( + os.path.join(ankiqt.runningDir, ".."), "libanki") + if not os.path.exists(path): + path = ankiqt.runningDir + if sys.platform.startswith("win32"): + path = os.path.split( + os.path.split(ankiqt.runningDir)[0])[0] + elif sys.platform.startswith("darwin"): + path = ankiqt.runningDir + "/../../.." + else: + path = os.path.join(path, "anki") + path = os.path.join(path, "samples") + path = os.path.normpath(path) + if os.path.exists(path): + if sys.platform.startswith("darwin"): + return self.openMacSamplesDir(path) + return path + return "" + + def openMacSamplesDir(self, path): + # some versions of macosx don't allow the open dialog to point inside + # a .App file, it seems - so we copy the files onto the desktop. + newDir = os.path.expanduser("~/Documents/Anki 0.3 Sample Decks") + import shutil + if os.path.exists(newDir): + files = os.listdir(path) + for file in files: + loc = os.path.join(path, file) + if not os.path.exists(os.path.join(newDir, file)): + shutil.copy2(loc, newDir) + return newDir + shutil.copytree(path, newDir) + return newDir + + def updateRecentFiles(self, path): + "Add the current deck to the list of recent files." + path = os.path.normpath(path) + if path in self.config['recentDeckPaths']: + self.config['recentDeckPaths'].remove(path) + self.config['recentDeckPaths'].insert(0, path) + del self.config['recentDeckPaths'][4:] + self.config.save() + self.updateRecentFilesMenu() + + def updateRecentFilesMenu(self): + if not self.config['recentDeckPaths']: + self.mainWin.menuOpenRecent.setEnabled(False) + return + self.mainWin.menuOpenRecent.setEnabled(True) + self.mainWin.menuOpenRecent.clear() + n = 1 + for file in self.config['recentDeckPaths']: + a = QAction(self) + if not sys.platform.startswith("darwin"): + a.setShortcut(_("Alt+%d" % n)) + a.setText(os.path.basename(file)) + a.setStatusTip(os.path.abspath(file)) + self.connect(a, SIGNAL("triggered()"), + lambda n=n: self.loadRecent(n-1)) + self.mainWin.menuOpenRecent.addAction(a) + n += 1 + + def loadRecent(self, n): + self.loadDeck(self.config['recentDeckPaths'][n]) + + # New files, loading & saving + ########################################################################## + + def saveAndClose(self, exit=False): + "(Auto)save and close. Prompt if necessary. True if okay to proceed." + if self.deck is not None: + # sync (saving automatically) + if self.config['syncOnClose'] and self.deck.syncName: + self.syncDeck(False, reload=False) + while self.deckPath: + self.app.processEvents() + time.sleep(0.1) + return True + # save + if self.deck.modifiedSinceSave(): + if self.config['saveOnClose'] or self.config['syncOnClose']: + self.saveDeck() + else: + res = ui.unsaved.ask(self) + if res == ui.unsaved.save: + self.saveDeck() + elif res == ui.unsaved.cancel: + return False + elif res == ui.unsaved.discard: + pass + # close + self.deck.rollback() + self.deck = None + if not exit: + self.moveToState("noDeck") + return True + + def onNew(self): + if not self.saveAndClose(): return + self.deck = DeckStorage.Deck() + m = ui.modelchooser.AddModel(self, online=True).getModel() + if m: + if m != "online": + self.deck.addModel(m) + self.saveDeck() + self.moveToState("initial") + return + # ensure all changes come to us + self.deck.syncName = None + self.deck.modified = 0 + self.deck.lastLoaded = self.deck.modified + self.deck.s.flush() + self.deck.s.commit() + if self.syncDeck(onlyMerge=True): + return + self.deck = None + self.moveToState("initial") + + def onOpen(self, samples=False): + key = _("Deck files (*.anki)") + if samples: defaultDir = self.getSamplesDir() + else: defaultDir = self.getDefaultDir() + file = QFileDialog.getOpenFileName(self, _("Open deck"), + defaultDir, key) + file = unicode(file) + if not file: + return False + if samples: + # we need to copy into a writeable location + new = DeckStorage.newDeckPath() + shutil.copyfile(file, new) + file = new + ret = self.loadDeck(file) + if not ret: + if ret is None: + ui.utils.showWarning(_("Unable to load file.")) + self.deck = None + return False + else: + self.updateRecentFiles(file) + return True + + def onOpenSamples(self): + self.onOpen(samples=True) + + def onSave(self): + if self.deck.modifiedSinceSave(): + self.saveDeck() + else: + self.setStatus(_("Deck is not modified.")) + + self.updateTitleBar() + + def onSaveAs(self): + "Prompt for a file name, then save." + title = _("Save deck") + dir = os.path.dirname(self.deck.path) + file = QFileDialog.getSaveFileName(self, title, + dir, + _("Deck files (*.anki)"), + None, + QFileDialog.DontConfirmOverwrite) + file = unicode(file) + if not file: + return + if not file.lower().endswith(".anki"): + file += ".anki" + if os.path.exists(file): + # check for existence after extension + if not ui.utils.askUser( + "This file exists. Are you sure you want to overwrite it?"): + return + self.deck = self.deck.saveAs(file) + self.updateTitleBar() + self.moveToState("initial") + + def saveDeck(self): + self.setStatus(_("Saving..")) + self.deck.save() + self.updateRecentFiles(self.deck.path) + self.updateTitleBar() + self.setStatus(_("Saving..done")) + + # Opening and closing the app + ########################################################################## + + def prepareForExit(self): + "Save config and window geometry." + self.runHook('quit') + self.help.hide() + self.config['mainWindowPos'] = self.pos() + self.config['mainWindowSize'] = self.size() + # save config + try: + self.config.save() + except (IOError, OSError), e: + ui.utils.showWarning(_("Anki was unable to save your " + "configuration file:\n%s" % e)) + + def closeEvent(self, event): + "User hit the X button, etc." + if not self.saveAndClose(exit=True): + event.ignore() + else: + self.prepareForExit() + event.accept() + self.app.quit() + + # Anchor clicks + ########################################################################## + + def onWelcomeAnchor(self, str): + if str == "new": + self.onNew() + elif str == "sample": + self.onOpenSamples() + elif str == "open": + self.onOpen() + + def setupAnchors(self): + self.anchorPrefixes = { + 'welcome': self.onWelcomeAnchor, + } + self.connect(self.mainWin.mainText, + SIGNAL("anchorClicked(QUrl)"), + self.anchorClicked) + + def anchorClicked(self, url): + # prevent the link being handled + self.mainWin.mainText.setSource(QUrl("")) + addr = unicode(url.toString()) + fields = addr.split(":") + if len(fields) > 1 and fields[0] in self.anchorPrefixes: + self.anchorPrefixes[fields[0]](*fields[1:]) + else: + # open in browser + QDesktopServices.openUrl(QUrl(url)) + + # Tools - looking up words in the dictionary + ########################################################################## + + def initLookup(self): + if not getattr(self, "lookup", None): + self.lookup = ui.lookup.Lookup(self) + + def onLookupExpression(self): + self.initLookup() + try: + self.lookup.alc(self.currentCard.fact['Expression']) + except KeyError: + self.setStatus(_("No expression in current card.")) + + def onLookupMeaning(self): + self.initLookup() + try: + self.lookup.alc(self.currentCard.fact['Meaning']) + except KeyError: + self.setStatus(_("No meaning in current card.")) + + def onLookupEdictSelection(self): + self.initLookup() + self.lookup.selection(self.lookup.edict) + + def onLookupEdictKanjiSelection(self): + self.initLookup() + self.lookup.selection(self.lookup.edictKanji) + + def onLookupAlcSelection(self): + self.initLookup() + self.lookup.selection(self.lookup.alc) + + # Tools - statistics + ########################################################################## + + def onKanjiStats(self): + rep = anki.stats.KanjiStats(self.deck).report() + rep += _("Missing Kanji
") + self.help.showText(rep, py={"miss": self.onMissingStats}) + + def onMissingStats(self): + ks = anki.stats.KanjiStats(self.deck) + ks.genKanjiSets() + self.help.showText(ks.missingReport()) + + def onDeckStats(self): + txt = anki.stats.DeckStats(self.deck).report() + self.help.showText(txt) + + def onCardStats(self): + self.addHook("showQuestion", self.onCardStats) + self.addHook("helpChanged", self.removeCardStatsHook) + txt = "" + if self.currentCard: + txt += _("

Current card

") + txt += anki.stats.CardStats(self.deck, self.currentCard).report() + if self.lastCard and self.lastCard != self.currentCard: + txt += _("

Last card

") + txt += anki.stats.CardStats(self.deck, self.lastCard).report() + if not txt: + txt = _("No current card or last card.") + self.help.showText(txt, key="cardStats") + + def removeCardStatsHook(self): + "Remove the update hook if the help menu was changed." + if self.help.currentKey != "cardStats": + self.removeHook("showQuestion", self.onCardStats) + + def onShowGraph(self): + self.setStatus(_("Loading graphs (may take time)..")) + self.app.processEvents() + import anki.graphs + if anki.graphs.graphsAvailable(): + try: + ui.dialogs.get("Graphs", self, self.deck) + except (ImportError, ValueError): + if sys.platform.startswith("win32"): + ui.utils.showInfo( + _("To display graphs, Anki needs a .dll file which\n" + "you don't have. Please install:\n") + + "http://www.dll-files.com/dllindex/dll-files.shtml?msvcp71") + else: + ui.utils.showInfo(_( + "Your version of Matplotlib is broken.\n" + "Please see http://repose.ath.cx/tracker/anki/issue102")) + else: + ui.utils.showInfo(_("Please install python-matplotlib to access graphs.")) + + def onKanjiOccur(self): + self.setStatus(_("Generating report (may take time)..")) + self.app.processEvents() + import tempfile + (fd, name) = tempfile.mkstemp(suffix=".html") + f = os.fdopen(fd, 'w') + ko = anki.stats.KanjiOccurStats(self.deck) + ko.reportFile(f) + f.close() + if sys.platform == "win32": + url = "file:///" + else: + url = "file://" + url += os.path.abspath(name) + QDesktopServices.openUrl(QUrl(url)) + + # Marking, suspending and undoing + ########################################################################## + + def onMark(self, toggled): + if self.currentCard.hasTag("Marked"): + self.currentCard.tags = deleteTags("Marked", self.currentCard.tags) + else: + self.currentCard.tags = addTags("Marked", self.currentCard.tags) + self.currentCard.setModified() + self.deck.setModified() + + def onSuspend(self): + self.currentCard.tags = addTags("Suspended", self.currentCard.tags) + self.deck.updatePriority(self.currentCard) + self.currentCard.setModified() + self.deck.setModified() + self.lastScheduledTime = None + self.moveToState("initial") + + def onUndoAnswer(self): + # quick and dirty undo for now + self.currentCard = None + self.deck.s.flush() + self.lastCardBackup.toDB(self.deck.s) + self.reset() + + # Other menu operations + ########################################################################## + + def onAddCard(self): + ui.dialogs.get("AddCards", self) + + def onEditDeck(self): + ui.dialogs.get("CardList", self) + + def onDeckProperties(self): + self.deckProperties = ui.deckproperties.DeckProperties(self) + + def onModelProperties(self): + if self.currentCard: + model = self.currentCard.fact.model + else: + model = self.deck.currentModel + ui.modelproperties.ModelProperties(self, model) + + def onDisplayProperties(self): + ui.dialogs.get("DisplayProperties", self) + + def onPrefs(self): + ui.preferences.Preferences(self, self.config) + + def onReportBug(self): + QDesktopServices.openUrl(QUrl(ankiqt.appIssueTracker)) + + def onForum(self): + QDesktopServices.openUrl(QUrl(ankiqt.appForum)) + + def onAbout(self): + ui.about.show(self) + + # Importing & exporting + ########################################################################## + + def onImport(self): + if self.deck is None: + self.onNew() + if self.deck is not None: + ui.importing.ImportDialog(self) + + def onExport(self): + ui.exporting.ExportDialog(self) + + # Language handling + ########################################################################## + + def setLang(self): + "Set the user interface language." + languageDir=os.path.join(ankiqt.modDir, "locale") + self.languageTrans = gettext.translation('ankiqt', languageDir, + languages=[self.config["interfaceLang"]], + fallback=True) + self.installTranslation() + if getattr(self, 'mainWin', None): + self.mainWin.retranslateUi(self) + self.alterShortcuts() + anki.lang.setLang(self.config["interfaceLang"]) + self.updateTitleBar() + + def getTranslation(self, text): + return self.languageTrans.ugettext(text) + + def installTranslation(self): + import __builtin__ + __builtin__.__dict__['_'] = self.getTranslation + + # Syncing + ########################################################################## + + def syncDeck(self, interactive=True, create=False, onlyMerge=False, reload=True): + "Synchronise a deck with the server." + # vet input + u=self.config['syncUsername'] + p=self.config['syncPassword'] + if not u or not p: + msg = _("Not syncing, username or password unset.") + if interactive: + ui.utils.showWarning(msg) + return + if self.deck is None and self.deckPath is None: + # qt on linux incorrectly accepts shortcuts for disabled actions + return + if self.deck: + # save first, so we can rollback on failure + self.deck.save() + self.deck.close() + self.deckPath = self.deck.path + self.syncName = self.deck.syncName or self.deck.name() + self.lastSync = self.deck.lastSync + self.deck = None + self.loadAfterSync = reload + # bug triggered by preferences dialog - underlying c++ widgets are not + # garbage collected until the middle of the child thread + import gc; gc.collect() + self.bodyView.clearWindow() + self.bodyView.flush() + self.syncThread = ui.sync.Sync(self, u, p, interactive, create, onlyMerge) + self.connect(self.syncThread, SIGNAL("setStatus"), self.setSyncStatus) + self.connect(self.syncThread, SIGNAL("showWarning"), ui.utils.showWarning) + self.connect(self.syncThread, SIGNAL("moveToState"), self.moveToState) + self.connect(self.syncThread, SIGNAL("noMatchingDeck"), self.selectSyncDeck) + self.connect(self.syncThread, SIGNAL("syncClockOff"), self.syncClockOff) + self.connect(self.syncThread, SIGNAL("cleanNewDeck"), self.cleanNewDeck) + self.connect(self.syncThread, SIGNAL("syncFinished"), self.syncFinished) + self.syncThread.start() + self.setEnabled(False) + while not self.syncThread.isFinished(): + self.app.processEvents() + self.syncThread.wait(100) + self.setEnabled(True) + return self.syncThread.ok + + def syncFinished(self): + "Reopen after sync finished." + if self.loadAfterSync: + self.loadDeck(self.deckPath, sync=False) + self.deck.syncName = self.syncName + self.deck.s.flush() + self.deck.s.commit() + else: + self.moveToState("noDeck") + self.deckPath = None + + def selectSyncDeck(self, decks, create=True): + name = ui.sync.DeckChooser(self, decks, create).getName() + self.syncName = name + if name: + if name == self.syncName: + self.syncDeck(create=True) + else: + self.syncDeck() + else: + if not create: + # called via 'new' - close + self.cleanNewDeck() + else: + self.syncFinished() + + def cleanNewDeck(self): + "Unload a new deck if an initial sync failed." + self.deck = None + self.moveToState("initial") + + def setSyncStatus(self, text, *args): + self.setStatus(text, *args) + self.mainWin.mainText.append("" + text + "") + + def syncClockOff(self, diff): + ui.utils.showWarning( + _("Your computer clock is not set to the correct time.\n" + "It is off by %d seconds.\n\n" + "Since this can cause many problems with syncing,\n" + "syncing is disabled until you fix the problem.") + % diff) + self.syncFinished() + + # Menu, title bar & status + ########################################################################## + + deckRelatedMenuItems = ( + "Save", + "Close", + "Addcards", + "Editdeck", + "Syncdeck", + "DisplayProperties", + "DeckProperties", + "ModelProperties", + "UndoAnswer", + "Export", + "MarkCard", + "Graphs", + "Dstats", + "Kstats", + "Cstats", + ) + + deckRelatedMenus = ( + "Tools", + "Advanced", + ) + + def connectMenuActions(self): + self.connect(self.mainWin.actionNew, SIGNAL("triggered()"), self.onNew) + self.connect(self.mainWin.actionOpen, SIGNAL("triggered()"), self.onOpen) + self.connect(self.mainWin.actionOpenSamples, SIGNAL("triggered()"), self.onOpenSamples) + self.connect(self.mainWin.actionSave, SIGNAL("triggered()"), self.onSave) + self.connect(self.mainWin.actionSaveAs, SIGNAL("triggered()"), self.onSaveAs) + self.connect(self.mainWin.actionClose, SIGNAL("triggered()"), self.saveAndClose) + self.connect(self.mainWin.actionExit, SIGNAL("triggered()"), self, SLOT("close()")) + self.connect(self.mainWin.actionSyncdeck, SIGNAL("triggered()"), self.syncDeck) + self.connect(self.mainWin.actionDeckProperties, SIGNAL("triggered()"), self.onDeckProperties) + self.connect(self.mainWin.actionDisplayProperties, SIGNAL("triggered()"),self.onDisplayProperties) + self.connect(self.mainWin.actionAddcards, SIGNAL("triggered()"), self.onAddCard) + self.connect(self.mainWin.actionEditdeck, SIGNAL("triggered()"), self.onEditDeck) + self.connect(self.mainWin.actionPreferences, SIGNAL("triggered()"), self.onPrefs) + self.connect(self.mainWin.actionLookup_es, SIGNAL("triggered()"), self.onLookupEdictSelection) + self.connect(self.mainWin.actionLookup_esk, SIGNAL("triggered()"), self.onLookupEdictKanjiSelection) + self.connect(self.mainWin.actionLookup_expr, SIGNAL("triggered()"), self.onLookupExpression) + self.connect(self.mainWin.actionLookup_mean, SIGNAL("triggered()"), self.onLookupMeaning) + self.connect(self.mainWin.actionLookup_as, SIGNAL("triggered()"), self.onLookupAlcSelection) + self.connect(self.mainWin.actionDstats, SIGNAL("triggered()"), self.onDeckStats) + self.connect(self.mainWin.actionKstats, SIGNAL("triggered()"), self.onKanjiStats) + self.connect(self.mainWin.actionCstats, SIGNAL("triggered()"), self.onCardStats) + self.connect(self.mainWin.actionGraphs, SIGNAL("triggered()"), self.onShowGraph) + self.connect(self.mainWin.actionAbout, SIGNAL("triggered()"), self.onAbout) + self.connect(self.mainWin.actionReportbug, SIGNAL("triggered()"), self.onReportBug) + self.connect(self.mainWin.actionForum, SIGNAL("triggered()"), self.onForum) + self.connect(self.mainWin.actionStarthere, SIGNAL("triggered()"), self.onStartHere) + self.connect(self.mainWin.actionImport, SIGNAL("triggered()"), self.onImport) + self.connect(self.mainWin.actionExport, SIGNAL("triggered()"), self.onExport) + self.connect(self.mainWin.actionMarkCard, SIGNAL("toggled(bool)"), self.onMark) + self.connect(self.mainWin.actionSuspendCard, SIGNAL("triggered()"), self.onSuspend) + self.connect(self.mainWin.actionModelProperties, SIGNAL("triggered()"), self.onModelProperties) + self.connect(self.mainWin.actionRepeatQuestionAudio, SIGNAL("triggered()"), self.onRepeatQuestion) + self.connect(self.mainWin.actionRepeatAnswerAudio, SIGNAL("triggered()"), self.onRepeatAnswer) + self.connect(self.mainWin.actionRepeatAudio, SIGNAL("triggered()"), self.onRepeatAudio) + self.connect(self.mainWin.actionUndoAnswer, SIGNAL("triggered()"), self.onUndoAnswer) + self.connect(self.mainWin.actionCheckDatabaseIntegrity, SIGNAL("triggered()"), self.onCheckDB) + self.connect(self.mainWin.actionOptimizeDatabase, SIGNAL("triggered()"), self.onOptimizeDB) + self.connect(self.mainWin.actionMergeModels, SIGNAL("triggered()"), self.onMergeModels) + self.connect(self.mainWin.actionCheckMediaDatabase, SIGNAL("triggered()"), self.onCheckMediaDB) + + def enableDeckMenuItems(self, enabled=True): + "setEnabled deck-related items." + for item in self.deckRelatedMenus: + getattr(self.mainWin, "menu" + item).setEnabled(enabled) + for item in self.deckRelatedMenuItems: + getattr(self.mainWin, "action" + item).setEnabled(enabled) + + def disableDeckMenuItems(self): + "Disable deck-related items." + self.enableDeckMenuItems(enabled=False) + + def updateTitleBar(self): + "Display the current deck and card count in the titlebar." + title=ankiqt.appName + " " + ankiqt.appVersion + if self.deck != None: + deckpath = self.deck.name() + if self.deck.modifiedSinceSave(): + deckpath += "*" + title = _("%(path)s (%(facts)d facts, %(cards)d cards)" + " - %(title)s") % { + "path": deckpath, + "title": title, + "cards": self.deck.cardCount(), + "facts": self.deck.factCount(), + } + self.setWindowTitle(title) + + def setStatus(self, text, timeout=3000): + self.mainWin.statusbar.showMessage(text, timeout) + + def onStartHere(self): + QDesktopServices.openUrl(QUrl(ankiqt.appHelpSite)) + + def alterShortcuts(self): + if sys.platform.startswith("darwin"): + self.mainWin.actionAddcards.setShortcut(_("Ctrl+D")) + self.mainWin.actionClose.setShortcut("") + + def updateMarkAction(self): + self.mainWin.actionMarkCard.blockSignals(True) + if self.currentCard.hasTag("Marked"): + self.mainWin.actionMarkCard.setChecked(True) + else: + self.mainWin.actionMarkCard.setChecked(False) + self.mainWin.actionMarkCard.blockSignals(False) + + def disableCardMenuItems(self): + self.mainWin.actionUndoAnswer.setEnabled(not not self.lastCard) + self.mainWin.actionMarkCard.setEnabled(False) + self.mainWin.actionSuspendCard.setEnabled(False) + self.mainWin.actionRepeatQuestionAudio.setEnabled(False) + self.mainWin.actionRepeatAnswerAudio.setEnabled(False) + self.mainWin.actionRepeatAudio.setEnabled(False) + + def enableCardMenuItems(self): + self.mainWin.actionUndoAnswer.setEnabled(not not self.lastCard) + self.mainWin.actionMarkCard.setEnabled(True) + self.mainWin.actionSuspendCard.setEnabled(True) + self.mainWin.actionRepeatQuestionAudio.setEnabled( + hasSound(self.currentCard.question)) + self.mainWin.actionRepeatAnswerAudio.setEnabled( + hasSound(self.currentCard.answer) and self.state != "getQuestion") + self.mainWin.actionRepeatAudio.setEnabled( + self.mainWin.actionRepeatQuestionAudio.isEnabled() or + self.mainWin.actionRepeatAnswerAudio.isEnabled()) + + # Auto update + ########################################################################## + + def setupAutoUpdate(self): + self.autoUpdate = ui.update.LatestVersionFinder(self) + self.connect(self.autoUpdate, SIGNAL("newVerAvail"), self.newVerAvail) + self.connect(self.autoUpdate, SIGNAL("clockIsOff"), self.clockIsOff) + self.autoUpdate.start() + + def newVerAvail(self, version): + if self.config['suppressUpdate'] < version['latestVersion']: + ui.update.askAndUpdate(self, version) + + def clockIsOff(self, diff): + if diff < 0: + ret = _("late") + else: + ret = _("early") + ui.utils.showWarning( + _("Your computer clock is not set to the correct time.\n" + "It is %(sec)d seconds %(type)s.\n" + " Please ensure it is set correctly and then restart Anki.") + % { "sec": abs(diff), + "type": ret } + ) + + # User customisations + ########################################################################## + + def loadUserCustomisations(self): + # look for config file + dir = self.config.configPath + file = os.path.join(dir, "custom.py") + plugdir = os.path.join(dir, "plugins") + sys.path.insert(0, dir) + if os.path.exists(file): + try: + import custom + except: + print "Error in custom.py" + print traceback.print_exc() + sys.path.insert(0, plugdir) + import glob + plugins = [f.replace(".py", "") for f in os.listdir(plugdir) \ + if f.endswith(".py")] + plugins.sort() + for plugin in plugins: + try: + __import__(plugin) + except: + print "Error in %s.py" % plugin + print traceback.print_exc() + + # Font localisation + ########################################################################## + + def setupFonts(self): + for (s, p) in anki.fonts.substitutions(): + QFont.insertSubstitution(s, p) + + # Sounds + ########################################################################## + + def onRepeatQuestion(self): + playFromText(self.currentCard.question) + + def onRepeatAnswer(self): + playFromText(self.currentCard.answer) + + def onRepeatAudio(self): + playFromText(self.currentCard.question) + if self.state != "showQuestion": + playFromText(self.currentCard.answer) + + # Advanced features + ########################################################################## + + def onCheckDB(self): + "True if no problems" + ret = self.deck.fixIntegrity() + if ret == "ok": + ret = _("""\ +No problems found. Some data structures have been rebuilt in case +they were causing problems. On the next sync, all cards will be +sent to the server.""") + ui.utils.showInfo(ret) + ret = True + else: + ret = _("Problems found:\n%s") % ret + ui.utils.showWarning(ret) + ret = False + self.rebuildQueue() + return ret + + def onOptimizeDB(self): + size = self.deck.optimize() + ui.utils.showInfo("Database optimized.\nShrunk by %d bytes" % size) + + def onMergeModels(self): + ret = self.deck.canMergeModels() + if ret[0] == "ok": + if not ret[1]: + ui.utils.showInfo(_( + "No models found to merge. If you want to merge models,\n" + "all models must have the same name.")) + return + if ui.utils.askUser(_( + "Would you like to merge models that have the same name?")): + self.deck.mergeModels(ret[1]) + ui.utils.showInfo(_("Merge complete.")) + else: + ui.utils.showWarning(_("""%s. +Anki can only merge models if they have exactly +the same field count and card count.""") % ret[1]) + + def onCheckMediaDB(self): + mb = QMessageBox(self) + mb.setText(_("""\ +Would you like to remove unused files from the media directory, and +tag or delete references to missing files?""")) + bTag = QPushButton("Tag facts missing media") + mb.addButton(bTag, QMessageBox.RejectRole) + bDelete = QPushButton("Delete references to missing media") + mb.addButton(bDelete, QMessageBox.RejectRole) + bCancel = QPushButton("Cancel") + mb.addButton(bCancel, QMessageBox.RejectRole) + mb.exec_() + if mb.clickedButton() == bTag: + (missing, unused) = rebuildMediaDir(self.deck, False) + elif mb.clickedButton() == bDelete: + (missing, unused) = rebuildMediaDir(self.deck, True) + else: + return + ui.utils.showInfo(_( + "%(a)d missing references.\n" + "%(b)d unused files removed.") % { + 'a': missing, + 'b': unused}) diff --git a/ankiqt/ui/modelchooser.py b/ankiqt/ui/modelchooser.py new file mode 100644 index 000000000..3ef091ee3 --- /dev/null +++ b/ankiqt/ui/modelchooser.py @@ -0,0 +1,210 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import anki +from anki import stdmodels +from anki.models import * +from ankiqt import ui +import ankiqt.forms + +class ModelChooser(QHBoxLayout): + + def __init__(self, parent, main, deck, onChangeFunc, cards=True): + QHBoxLayout.__init__(self) + self.parent = parent + self.main = main + self.deck = deck + self.onChangeFunc = onChangeFunc + self.setMargin(0) + self.setSpacing(6) + self.shortcuts = [] + label = QLabel(_("Model:")) + self.addWidget(label) + self.models = QComboBox() + s = QShortcut(QKeySequence(_("Alt+M")), self.parent) + s.connect(s, SIGNAL("activated()"), + lambda: self.models.showPopup()) + self.drawModels() + sizePolicy = QSizePolicy( + QSizePolicy.Policy(7), + QSizePolicy.Policy(0)) + self.models.setSizePolicy(sizePolicy) + self.addWidget(self.models) + self.add = QPushButton() + self.add.setIcon(QIcon(":/icons/list-add.png")) + self.add.setToolTip(_("Add a new model")) + self.add.setAutoDefault(False) + self.addWidget(self.add) + self.connect(self.add, SIGNAL("clicked()"), self.onAdd) + self.edit = QPushButton() + self.edit.setIcon(QIcon(":/icons/edit.png")) + self.edit.setShortcut(_("Alt+E")) + self.edit.setToolTip(_("Edit the current model")) + self.edit.setAutoDefault(False) + self.addWidget(self.edit) + self.connect(self.edit, SIGNAL("clicked()"), self.onEdit) + self.connect(self.models, SIGNAL("activated(int)"), self.onChange) + self.handleCards = False + if cards: + self.handleCards = True + label = QLabel(_("Cards:")) + self.addWidget(label) + self.cards = QPushButton() + self.connect(self.cards, SIGNAL("clicked()"), self.onCardChange) + s = QShortcut(QKeySequence(_("Alt+C")), self.parent) + s.connect(s, SIGNAL("activated()"), + self.onCardChange) + self.addWidget(self.cards) + self.drawCardModels() + + def show(self): + for i in range(self.count()): + self.itemAt(i).widget().show() + + def hide(self): + for i in range(self.count()): + self.itemAt(i).widget().hide() + + def onEdit(self): + idx = self.models.currentIndex() + model = self.deck.models[idx] + ui.modelproperties.ModelProperties(self.parent, model, self.main, + onFinish=self.onModelEdited) + self.drawModels() + self.changed(model) + + def onModelEdited(self): + self.drawModels() + + def onAdd(self): + model = AddModel(self.parent, self.main).getModel() + if model: + self.deck.addModel(model) + self.drawModels() + self.changed(model) + self.deck.setModified() + + def onChange(self, idx): + model = self.deck.models[idx] + self.deck.currentModel = model + self.changed(model) + self.deck.setModified() + + def changed(self, model): + self.deck.addModel(model) + self.onChangeFunc(model) + self.drawCardModels() + + def drawModels(self): + self.models.clear() + self.models.addItems(QStringList( + [m.name for m in self.deck.models])) + idx = self.deck.models.index(self.deck.currentModel) + self.models.setCurrentIndex(idx) + + def drawCardModels(self): + if not self.handleCards: + return + # remove any shortcuts + for s in self.shortcuts: + s.setEnabled(False) + self.shortcuts = [] + m = self.deck.currentModel + txt = ", ".join([c.name for c in m.cardModels if c.active]) + if len(txt) > 30: + txt = txt[0:30] + "..." + self.cards.setText(txt) + n = 1 + for c in m.cardModels: + s = QShortcut(QKeySequence("Alt+%d" % n), self.parent) + self.parent.connect(s, SIGNAL("activated()"), + lambda c=c: self.toggleCard(c)) + self.shortcuts.append(s) + n += 1 + + def onCardChange(self): + m = QMenu(self.parent) + m.setTitle("menu") + model = self.deck.currentModel + for card in model.cardModels: + action = QAction(self.parent) + action.setCheckable(True) + if card.active: + action.setChecked(True) + action.setText(card.name) + self.connect(action, SIGNAL("toggled(bool)"), + lambda b, a=action, c=card: \ + self.cardChangeTriggered(b,a,c)) + m.addAction(action) + m.exec_(self.cards.mapToGlobal(QPoint(0,0))) + + def cardChangeTriggered(self, bool, action, card): + if bool: + card.active = True + elif self.canDisableModel(): + card.active = False + self.drawCardModels() + + def canDisableModel(self): + active = 0 + model = self.deck.currentModel + for c in model.cardModels: + if c.active: + active += 1 + if active > 1: + return True + return False + + def toggleCard(self, card): + if not card.active: + card.active = True + elif self.canDisableModel(): + card.active = False + self.drawCardModels() + +class AddModel(QDialog): + + def __init__(self, parent, main=None, online=False): + QDialog.__init__(self, parent) + self.parent = parent + if not main: + main = parent + self.main = main + self.model = None + self.dialog = ankiqt.forms.addmodel.Ui_AddModel() + self.dialog.setupUi(self) + self.models = {} + for name in ( + "Japanese", + "English", + "Cantonese", + "Mandarin", + "Heisig"): + # hard code the order so that most common come first + m = stdmodels.byName(name) + item = QListWidgetItem(m.name) + self.dialog.models.addItem(item) + self.models[m.name] = m + self.dialog.models.setCurrentRow(0) + # the list widget will swallow the enter key + s = QShortcut(QKeySequence("Return"), self) + self.connect(s, SIGNAL("activated()"), self.accept) + if not online: + self.dialog.loadOnline.setShown(False) + + def getModel(self): + self.exec_() + return self.model + + def accept(self): + if self.dialog.createTemplate.isChecked(): + self.model = self.models[ + unicode(self.dialog.models.currentItem().text())] + elif self.dialog.createBasic.isChecked(): + self.model = stdmodels.byName("Basic") + else: + self.model = "online" + QDialog.accept(self) + diff --git a/ankiqt/ui/modelproperties.py b/ankiqt/ui/modelproperties.py new file mode 100644 index 000000000..10ba678d9 --- /dev/null +++ b/ankiqt/ui/modelproperties.py @@ -0,0 +1,474 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import sys, re +import ankiqt.forms +import anki +from anki.models import FieldModel, CardModel +from ankiqt import ui + +class ModelProperties(QDialog): + + def __init__(self, parent, model, main=None, onFinish=None): + QDialog.__init__(self, parent, Qt.Window) + if not main: + main = parent + self.parent = main + self.deck = main.deck + self.origModTime = self.deck.modified + self.m = model + self.onFinish = onFinish + self.dialog = ankiqt.forms.modelproperties.Ui_ModelProperties() + self.dialog.setupUi(self) + self.setupFields() + self.setupCards() + self.readData() + self.show() + + def readData(self): + # properties section + self.dialog.name.setText(self.m.name) + self.dialog.description.setText(self.m.description) + self.dialog.tags.setText(self.m.tags) + self.dialog.decorators.setText(self.m.features) + self.dialog.spacing.setText(str(self.m.spacing)) + self.dialog.initialSpacing.setText(str(self.m.initialSpacing/60)) + + # Fields + ########################################################################## + + def setupFields(self): + self.fieldOrdinalUpdatedIds = [] + self.ignoreFieldUpdate = False + self.currentField = None + self.updateFields() + self.readCurrentField() + self.connect(self.dialog.fieldList, SIGNAL("currentRowChanged(int)"), + self.fieldRowChanged) + self.connect(self.dialog.tabWidget, SIGNAL("currentChanged(int)"), + self.fieldRowChanged) + self.connect(self.dialog.fieldAdd, SIGNAL("clicked()"), + self.addField) + self.connect(self.dialog.fieldDelete, SIGNAL("clicked()"), + self.deleteField) + self.connect(self.dialog.fieldUp, SIGNAL("clicked()"), + self.moveFieldUp) + self.connect(self.dialog.fieldDown, SIGNAL("clicked()"), + self.moveFieldDown) + + def updateFields(self, row = None): + oldRow = self.dialog.fieldList.currentRow() + if oldRow == -1: + oldRow = 0 + self.dialog.fieldList.clear() + n = 1 + for field in self.m.fieldModels: + label = _("Field %(num)d: %(name)s [%(cards)s non-empty]") % { + 'num': n, + 'name': field.name, + 'cards': self.deck.fieldModelUseCount(field) + } + item = QListWidgetItem(label) + self.dialog.fieldList.addItem(item) + n += 1 + count = self.dialog.fieldList.count() + if row != None: + self.dialog.fieldList.setCurrentRow(row) + else: + while (count > 0 and oldRow > (count - 1)): + oldRow -= 1 + self.dialog.fieldList.setCurrentRow(oldRow) + self.enableFieldMoveButtons() + + def fieldRowChanged(self): + if self.ignoreFieldUpdate: + return + self.saveCurrentField() + self.readCurrentField() + + def readCurrentField(self): + if not len(self.m.fieldModels): + self.dialog.fieldEditBox.hide() + self.dialog.fieldUp.setEnabled(False) + self.dialog.fieldDown.setEnabled(False) + return + else: + self.dialog.fieldEditBox.show() + self.currentField = self.m.fieldModels[self.dialog.fieldList.currentRow()] + field = self.currentField + self.dialog.fieldName.setText(field.name) + self.dialog.fieldDescription.setText(field.description) + self.dialog.fieldUnique.setChecked(field.unique) + self.dialog.fieldRequired.setChecked(field.required) + self.dialog.fieldFeatures.setText(field.features) + self.dialog.numeric.setChecked(field.numeric) + + def enableFieldMoveButtons(self): + row = self.dialog.fieldList.currentRow() + if row < 1: + self.dialog.fieldUp.setEnabled(False) + else: + self.dialog.fieldUp.setEnabled(True) + if row == -1 or row >= (self.dialog.fieldList.count() - 1): + self.dialog.fieldDown.setEnabled(False) + else: + self.dialog.fieldDown.setEnabled(True) + + def saveCurrentField(self): + if not self.currentField: + return + field = self.currentField + name = unicode(self.dialog.fieldName.text()).strip() + # renames + if not name: + name = _("Field %d") % (self.m.fieldModels.index(field) + 1) + if name != field.name: + self.deck.renameFieldModel(self.m, field, name) + # the card models will have been updated + self.readCurrentCard() + self.updateField(field, 'description', + unicode(self.dialog.fieldDescription.toPlainText())) + self.updateField(field, 'features', + unicode(self.dialog.fieldFeatures.text())) + # unique, required, numeric + self.updateField(field, 'unique', + self.dialog.fieldUnique.checkState() == Qt.Checked) + self.updateField(field, 'required', + self.dialog.fieldRequired.checkState() == Qt.Checked) + self.updateField(field, 'numeric', + self.dialog.numeric.checkState() == Qt.Checked) + self.ignoreFieldUpdate = True + self.updateFields() + self.ignoreFieldUpdate = False + + def addField(self): + f = FieldModel() + f.name = _("Field %d") % (len(self.m.fieldModels) + 1) + self.deck.addFieldModel(self.m, f) + self.updateFields() + self.dialog.fieldList.setCurrentRow(len(self.m.fieldModels)-1) + self.dialog.fieldName.setFocus() + self.dialog.fieldName.selectAll() + + def deleteField(self): + row = self.dialog.fieldList.currentRow() + if row == -1: + return + if len(self.m.fieldModels) < 2: + ui.utils.showInfo( + _("Please add a new field first.")) + return + field = self.m.fieldModels[row] + count = self.deck.fieldModelUseCount(field) + if count: + if not ui.utils.askUser( + _("This field is used by %d cards. If you delete it,\n" + "all information in this field will be lost.\n" + "\nReally delete this field?") % count, + parent=self): + return + self.deck.deleteFieldModel(self.m, field) + self.currentField = None + self.updateFields() + # need to update q/a format + self.readCurrentCard() + + def moveFieldUp(self): + row = self.dialog.fieldList.currentRow() + if row == -1: + return + if row == 0: + return + field = self.m.fieldModels[row] + tField = self.m.fieldModels[row - 1] + self.m.fieldModels.remove(field) + self.m.fieldModels.insert(row - 1, field) + if field.id not in self.fieldOrdinalUpdatedIds: + self.fieldOrdinalUpdatedIds.append(field.id) + if tField.id not in self.fieldOrdinalUpdatedIds: + self.fieldOrdinalUpdatedIds.append(tField.id) + self.ignoreFieldUpdate = True + self.updateFields(row - 1) + self.ignoreFieldUpdate = False + + def moveFieldDown(self): + row = self.dialog.fieldList.currentRow() + if row == -1: + return + if row == len(self.m.fieldModels) - 1: + return + field = self.m.fieldModels[row] + tField = self.m.fieldModels[row + 1] + self.m.fieldModels.remove(field) + self.m.fieldModels.insert(row + 1, field) + if field.id not in self.fieldOrdinalUpdatedIds: + self.fieldOrdinalUpdatedIds.append(field.id) + if tField.id not in self.fieldOrdinalUpdatedIds: + self.fieldOrdinalUpdatedIds.append(tField.id) + self.ignoreFieldUpdate = True + self.updateFields(row + 1) + self.ignoreFieldUpdate = False + + # Cards + ########################################################################## + + def setupCards(self): + self.cardOrdinalUpdatedIds = [] + self.ignoreCardUpdate = False + self.currentCard = None + self.updateCards() + self.readCurrentCard() + self.connect(self.dialog.cardList, SIGNAL("currentRowChanged(int)"), + self.cardRowChanged) + self.connect(self.dialog.cardAdd, SIGNAL("clicked()"), + self.addCard) + self.connect(self.dialog.cardDelete, SIGNAL("clicked()"), + self.deleteCard) + self.connect(self.dialog.cardToggle, SIGNAL("clicked()"), + self.toggleCard) + self.connect(self.dialog.cardUp, SIGNAL("clicked()"), + self.moveCardUp) + self.connect(self.dialog.cardDown, SIGNAL("clicked()"), + self.moveCardDown) + + def updateCards(self, row = None): + oldRow = self.dialog.cardList.currentRow() + if oldRow == -1: + oldRow = 0 + self.dialog.cardList.clear() + n = 1 + for card in self.m.cardModels: + if card.active: + status="" + else: + status=_("; disabled") + label = _("Card %(num)d (%(name)s): used %(cards)d times%(status)s") % { + 'num': n, + 'name': card.name, + 'status': status, + 'cards': self.deck.cardModelUseCount(card), + } + item = QListWidgetItem(label) + self.dialog.cardList.addItem(item) + n += 1 + count = self.dialog.cardList.count() + if row != None: + self.dialog.cardList.setCurrentRow(row) + else: + while (count > 0 and oldRow > (count - 1)): + oldRow -= 1 + self.dialog.cardList.setCurrentRow(oldRow) + self.enableCardMoveButtons() + + def cardRowChanged(self): + if self.ignoreCardUpdate: + return + self.saveCurrentCard() + self.readCurrentCard() + + def readCurrentCard(self): + if not len(self.m.cardModels): + self.dialog.cardEditBox.hide() + self.dialog.cardToggle.setEnabled(False) + self.dialog.cardDelete.setEnabled(False) + self.dialog.cardUp.setEnabled(False) + self.dialog.cardDown.setEnabled(False) + return + else: + self.dialog.cardEditBox.show() + self.dialog.cardToggle.setEnabled(True) + self.dialog.cardDelete.setEnabled(True) + self.currentCard = self.m.cardModels[self.dialog.cardList.currentRow()] + card = self.currentCard + self.dialog.cardName.setText(card.name) + self.dialog.cardDescription.setText(card.description) + self.dialog.cardQuestion.setPlainText(card.qformat.replace("
", "\n")) + self.dialog.cardAnswer.setPlainText(card.aformat.replace("
", "\n")) + if card.questionInAnswer: + self.dialog.questionInAnswer.setCheckState(Qt.Checked) + else: + self.dialog.questionInAnswer.setCheckState(Qt.Unchecked) + self.updateToggleButtonText(card) + + def enableCardMoveButtons(self): + row = self.dialog.cardList.currentRow() + if row < 1: + self.dialog.cardUp.setEnabled(False) + else: + self.dialog.cardUp.setEnabled(True) + if row == -1 or row >= (self.dialog.cardList.count() - 1): + self.dialog.cardDown.setEnabled(False) + else: + self.dialog.cardDown.setEnabled(True) + + def updateToggleButtonText(self, card): + if card.active: + self.dialog.cardToggle.setText(_("Disa&ble")) + else: + self.dialog.cardToggle.setText(_("Ena&ble")) + + def saveCurrentCard(self): + if not self.currentCard: + return + card = self.currentCard + newname = unicode(self.dialog.cardName.text()) + if not newname: + newname = _("Card %d") % (self.m.cardModels.index(card) + 1) + self.updateField(card, 'name', newname) + self.updateField(card, 'description', unicode( + self.dialog.cardDescription.toPlainText())) + s = unicode(self.dialog.cardQuestion.toPlainText()).strip() + s = s.replace("\n", "
") + changed = self.updateField(card, 'qformat', s) + s = unicode(self.dialog.cardAnswer.toPlainText()).strip() + s = s.replace("\n", "
") + changed2 = self.updateField(card, 'aformat', s) + changed = changed or changed2 + self.updateField(card, 'questionInAnswer', self.dialog.questionInAnswer.isChecked()) + if changed: + # need to generate all question/answers for this card + self.deck.updateCardsFromModel(self.currentCard) + self.ignoreCardUpdate = True + self.updateCards() + self.ignoreCardUpdate = False + + def updateField(self, obj, field, value): + if getattr(obj, field) != value: + setattr(obj, field, value) + self.m.setModified() + self.deck.setModified() + return True + return False + + def addCard(self): + cards = len(self.m.cardModels) + name = _("Card %d") % (cards+1) + cm = CardModel(name=name) + self.m.addCardModel(cm) + self.updateCards() + self.dialog.cardList.setCurrentRow(len(self.m.cardModels)-1) + self.dialog.cardName.setFocus() + self.dialog.cardName.selectAll() + + def deleteCard(self): + row = self.dialog.cardList.currentRow() + if row == -1: + return + if len (self.m.cardModels) < 2: + ui.utils.showWarning( + _("Please add a new card first."), + parent=self) + return + card = self.m.cardModels[row] + count = self.deck.cardModelUseCount(card) + if count: + if not ui.utils.askUser( + _("This model is used by %d cards. If you delete it,\n" + "all the cards will be deleted too. If you just\n" + "want to prevent the creation of future cards with\n" + "this model, please use the 'disable' button\n" + "instead.\n\nReally delete these cards?") % count, + parent=self): + return + self.deck.deleteCardModel(self.m, card) + self.updateCards() + + def toggleCard(self): + row = self.dialog.cardList.currentRow() + if row == -1: + return + card = self.m.cardModels[row] + active = 0 + for c in self.m.cardModels: + if c.active: + active += 1 + if active < 2 and card.active: + ui.utils.showWarning( + _("Please enable a different model first."), + parent=self) + return + card.active = not card.active + self.updateToggleButtonText(card) + self.updateCards() + self.m.setModified() + self.deck.setModified() + + def moveCardUp(self): + row = self.dialog.cardList.currentRow() + if row == -1: + return + if row == 0: + return + card = self.m.cardModels[row] + tCard = self.m.cardModels[row - 1] + self.m.cardModels.remove(card) + self.m.cardModels.insert(row - 1, card) + if card.id not in self.cardOrdinalUpdatedIds: + self.cardOrdinalUpdatedIds.append(card.id) + if tCard.id not in self.cardOrdinalUpdatedIds: + self.cardOrdinalUpdatedIds.append(tCard.id) + self.ignoreCardUpdate = True + self.updateCards(row - 1) + self.ignoreCardUpdate = False + + def moveCardDown(self): + row = self.dialog.cardList.currentRow() + if row == -1: + return + if row == len(self.m.cardModels) - 1: + return + card = self.m.cardModels[row] + tCard = self.m.cardModels[row + 1] + self.m.cardModels.remove(card) + self.m.cardModels.insert(row + 1, card) + if card.id not in self.cardOrdinalUpdatedIds: + self.cardOrdinalUpdatedIds.append(card.id) + if tCard.id not in self.cardOrdinalUpdatedIds: + self.cardOrdinalUpdatedIds.append(tCard.id) + self.ignoreCardUpdate = True + self.updateCards(row + 1) + self.ignoreCardUpdate = False + + # Cleanup + ########################################################################## + + def reject(self): + "Save user settings on close." + # update properties + mname = unicode(self.dialog.name.text()) + if not mname: + mname = _("Model") + self.updateField(self.m, 'name', mname) + self.updateField(self.m, 'description', + unicode(self.dialog.description.toPlainText())) + self.updateField(self.m, 'tags', + unicode(self.dialog.tags.text())) + self.updateField(self.m, 'features', + unicode(self.dialog.decorators.text())) + try: + self.updateField(self.m, 'spacing', + float(self.dialog.spacing.text())) + self.updateField(self.m, 'initialSpacing', + float(self.dialog.initialSpacing.text())*60) + except ValueError: + pass + # before field, or it's overwritten + self.saveCurrentCard() + self.saveCurrentField() + # rebuild ordinals if changed + if len(self.fieldOrdinalUpdatedIds) > 0: + self.deck.rebuildFieldOrdinals(self.m.id, self.fieldOrdinalUpdatedIds) + self.m.setModified() + self.deck.setModified() + if len(self.cardOrdinalUpdatedIds) > 0: + self.deck.rebuildCardOrdinals(self.cardOrdinalUpdatedIds) + self.m.setModified() + self.deck.setModified() + # if changed, reset deck + if self.origModTime != self.deck.modified: + self.parent.reset() + if self.onFinish: + self.onFinish() + QDialog.reject(self) diff --git a/ankiqt/ui/preferences.py b/ankiqt/ui/preferences.py new file mode 100644 index 000000000..4e2282612 --- /dev/null +++ b/ankiqt/ui/preferences.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +import copy, sys +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import anki, anki.utils +from anki.facts import Fact +from anki.stdmodels import JapaneseModel +from ankiqt import ui +import ankiqt.forms + +class Preferences(QDialog): + + def __init__(self, parent, config): + QDialog.__init__(self, parent) + self.origConfig = config + self.parent = parent + self.config = copy.copy(self.origConfig) + self.origInterfaceLang = self.config['interfaceLang'] + self.dialog = ankiqt.forms.preferences.Ui_Preferences() + self.dialog.setupUi(self) + self.supportedLanguages = [ + (_("English"), "en_US"), + (_("Czech"), "cs_CZ"), + (_("French"), "fr_FR"), + (_("German"), "de_DE"), + (_("Japanese"), "ja_JP"), + (_("Korean"), "ko_KR"), + (_("Spanish"), "es_ES"), + ] + self.setupLang() + self.setupFont() + self.setupColour() + self.setupSync() + self.setupSave() + self.setupAdvanced() + self.show() + + def accept(self): + self.updateSync() + self.updateSave() + self.updateAdvanced() + self.config['interfaceLang'] = self.origConfig['interfaceLang'] + self.origConfig.update(self.config) + self.origConfig.save() + self.parent.setLang() + self.parent.moveToState("auto") + self.done(0) + + def reject(self): + self.origConfig['interfaceLang'] = self.origInterfaceLang + self.parent.setLang() + self.done(0) + + def setupLang(self): + # interface lang + for (lang, code) in self.supportedLanguages: + self.dialog.interfaceLang.addItem(lang) + self.connect(self.dialog.interfaceLang, + SIGNAL("currentIndexChanged(QString)"), + self.interfaceLangChanged) + self.dialog.interfaceLang.setCurrentIndex(self.codeToIndex(self.config['interfaceLang'])) + + def interfaceLangChanged(self): + self.origConfig['interfaceLang'] = ( + self.supportedLanguages[self.dialog.interfaceLang.currentIndex()])[1] + self.parent.setLang() + self.dialog.retranslateUi(self) + + fonts = ( + "interface", + "lastCard", + "edit", + ) + + def loadCurrentFonts(self): + for font in self.fonts: + # family init + getattr(self.dialog, font + "Family").setCurrentFont(QFont( + self.config[font + "FontFamily"])) + # size init + getattr(self.dialog, font + "Size").setValue( + self.config[font + "FontSize"]) + + def setupFont(self): + self.loadCurrentFonts() + for font in self.fonts: + # family change + family = font + "Family" + chngFunc = lambda qfont, type=font: self.familyChanged(qfont, type) + self.connect(getattr(self.dialog, family), + SIGNAL("currentFontChanged(QFont)"), + chngFunc) + + # size change + size = font + "Size" + chngFunc = lambda size, type=font: self.sizeChanged(size, type) + self.connect(getattr(self.dialog, size), + SIGNAL("valueChanged(int)"), + chngFunc) + + def familyChanged(self, qfont, type): + self.config[type + "FontFamily"] = unicode(qfont.family()) + getattr(self.dialog, type + "Family").setFocus() + + def sizeChanged(self, size, type): + self.config[type + "FontSize"] = size + getattr(self.dialog, type + "Size").setFocus() + + def setupColour(self): + if sys.platform.startswith("darwin"): + # mac widgets don't show colours + self.plastiqueStyle = QStyleFactory.create("plastique") + for c in ("interface", "lastCard", "background"): + colour = c + "Colour" + button = getattr(self.dialog, colour) + if sys.platform.startswith("darwin"): + button.setStyle(self.plastiqueStyle) + button.setPalette(QPalette(QColor( + self.config[colour]))) + self.connect(button, SIGNAL("clicked()"), + lambda b=button, t=c, : self.colourClicked(b, t)) + + def colourClicked(self, button, type): + new = QColorDialog.getColor(button.palette().window().color(), self) + if new.isValid(): + self.config[type + "Colour"] = str(new.name()) + button.setPalette(QPalette(new)) + + def setupSync(self): + self.dialog.syncOnOpen.setChecked(self.config['syncOnLoad']) + self.dialog.syncOnClose.setChecked(self.config['syncOnClose']) + self.dialog.syncUser.setText(self.config['syncUsername']) + self.dialog.syncPass.setText(self.config['syncPassword']) + + def updateSync(self): + self.config['syncOnLoad'] = self.dialog.syncOnOpen.isChecked() + self.config['syncOnClose'] = self.dialog.syncOnClose.isChecked() + self.config['syncUsername'] = unicode(self.dialog.syncUser.text()) + self.config['syncPassword'] = unicode(self.dialog.syncPass.text()) + + def setupSave(self): + self.dialog.saveAfterEveryNum.setValue(self.config['saveAfterAnswerNum']) + self.dialog.saveAfterEvery.setChecked(self.config['saveAfterAnswer']) + self.dialog.saveAfterAdding.setChecked(self.config['saveAfterAdding']) + self.dialog.saveAfterAddingNum.setValue(self.config['saveAfterAddingNum']) + self.dialog.saveWhenClosing.setChecked(self.config['saveOnClose']) + + def updateSave(self): + self.config['saveAfterAnswer'] = self.dialog.saveAfterEvery.isChecked() + self.config['saveAfterAnswerNum'] = self.dialog.saveAfterEveryNum.value() + self.config['saveAfterAdding'] = self.dialog.saveAfterAdding.isChecked() + self.config['saveAfterAddingNum'] = self.dialog.saveAfterAddingNum.value() + self.config['saveOnClose'] = self.dialog.saveWhenClosing.isChecked() + + def setupAdvanced(self): + self.dialog.showToolbar.setChecked(self.config['showToolbar']) + self.dialog.compactEaseButtons.setChecked( + self.config['easeButtonStyle'] != 'standard') + self.dialog.tallButtons.setChecked( + self.config['easeButtonHeight'] != 'standard') + self.dialog.suppressEstimates.setChecked(self.config['suppressEstimates']) + self.dialog.suppressLastCardInterval.setChecked(self.config['suppressLastCardInterval']) + self.dialog.suppressLastCardContent.setChecked(self.config['suppressLastCardContent']) + self.dialog.showTray.setChecked(self.config['showTray']) + self.dialog.showTimer.setChecked(self.config['showTimer']) + self.dialog.editCurrentOnly.setChecked(self.config['editCurrentOnly']) + + def updateAdvanced(self): + self.config['showToolbar'] = self.dialog.showToolbar.isChecked() + if self.dialog.compactEaseButtons.isChecked(): + self.config['easeButtonStyle'] = 'compact' + else: + self.config['easeButtonStyle'] = 'standard' + if self.dialog.tallButtons.isChecked(): + self.config['easeButtonHeight'] = 'tall' + else: + self.config['easeButtonHeight'] = 'standard' + self.config['suppressLastCardInterval'] = self.dialog.suppressLastCardInterval.isChecked() + self.config['suppressLastCardContent'] = self.dialog.suppressLastCardContent.isChecked() + self.config['showTray'] = self.dialog.showTray.isChecked() + self.config['showTimer'] = self.dialog.showTimer.isChecked() + self.config['suppressEstimates'] = self.dialog.suppressEstimates.isChecked() + self.config['editCurrentOnly'] = self.dialog.editCurrentOnly.isChecked() + + def codeToIndex(self, code): + n = 0 + for (lang, type) in self.supportedLanguages: + if code == type: + return n + n += 1 + # default to english + return self.codeToIndex("en_US") diff --git a/ankiqt/ui/status.py b/ankiqt/ui/status.py new file mode 100644 index 000000000..33a511c5a --- /dev/null +++ b/ankiqt/ui/status.py @@ -0,0 +1,265 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import anki +import sys, time +from ankiqt import ui + +class QClickableLabel(QLabel): + url = "http://ichi2.net/anki/wiki/TheTimerAndShortQuestions" + def mouseReleaseEvent(self, evt): + QDesktopServices.openUrl(QUrl(self.url)) + +# Status bar +########################################################################## + +class StatusView(object): + "Manage the status bar as we transition through various states." + + warnTime = 10 + + def __init__(self, parent): + self.main = parent + self.statusbar = parent.mainWin.statusbar + self.shown = [] + self.hideBorders() + self.setState("noDeck") + self.thinkingTimer = QTimer(parent) + self.thinkingTimer.start(1000) + self.timer = None + parent.connect(self.thinkingTimer, SIGNAL("timeout()"), + self.drawTimer) + + # State control + ########################################################################## + + def setState(self, state): + "Change to STATE, and update the display." + self.state = state + if self.state == "initial": + self.showDeckStatus() + self.updateProgressGoal() + elif self.state == "noDeck": + self.hideDeckStatus() + elif self.state in ("showQuestion", + "deckFinished", + "deckEmpty"): + self.redraw() + + # Setup and teardown + ########################################################################## + + def vertSep(self): + spacer = QFrame() + spacer.setFrameStyle(QFrame.VLine) + spacer.setFrameShadow(QFrame.Plain) + return spacer + + def showDeckStatus(self): + if self.shown: + return + progressBarSize = (50, 8) + # small spacer + self.initialSpace = QWidget() + self.addWidget(self.initialSpace, 1) + # remaining & eta + self.remText = QLabel() + self.addWidget(self.remText, 0) + self.addWidget(self.vertSep(), 0) + self.etaText = QLabel() + self.etaText.setToolTip(_( + "

Estimated time

" + "This is how long it will take to complete the current mode " + "at your current pace.")) + self.addWidget(self.etaText, 0) + # progress&retention + self.addWidget(self.vertSep(), 0) + vbox = QVBoxLayout() + vbox.setSpacing(0) + vbox.setContentsMargins(0,0,0,0) + self.progressBar = QProgressBar() + self.progressBar.setFixedSize(*progressBarSize) + self.progressBar.setMaximum(100) + self.progressBar.setTextVisible(False) + vbox.addWidget(self.progressBar, 0) + self.retentionBar = QProgressBar() + self.retentionBar.setFixedSize(*progressBarSize) + self.retentionBar.setMaximum(100) + self.retentionBar.setTextVisible(False) + vbox.addWidget(self.retentionBar, 0) + self.combinedBar = QWidget() + self.combinedBar.setLayout(vbox) + self.addWidget(self.combinedBar, 0) + # timer + self.addWidget(self.vertSep(), 0) + self.timer = QClickableLabel() + self.timer.setText("00:00") + if sys.platform.startswith("darwin"): + self.timer.setFixedWidth(40) + self.addWidget(self.timer) + # mac + if sys.platform.startswith("darwin"): + # we don't want non-coloured, throbbing widgets + self.plastiqueStyle = QStyleFactory.create("plastique") + self.progressBar.setStyle(self.plastiqueStyle) + self.retentionBar.setStyle(self.plastiqueStyle) + self.optionsButton = QPushButton() + self.optionsButton.setIcon(QIcon(":/icons/configure.png")) + self.optionsButton.setFixedSize(20, 20) + self.optionsButton.setFocusPolicy(Qt.NoFocus) + self.optionsButton.setToolTip(_( + "Click this button to customize\n" + "the way Anki shows you cards.")) + self.main.connect(self.optionsButton, + SIGNAL("clicked()"), + self.onConfigure) + self.addWidget(self.optionsButton) + self.redraw() + + def addWidget(self, w, stretch=0, perm=True): + if perm: + self.statusbar.addPermanentWidget(w, stretch) + else: + self.statusbar.addWidget(w, stretch) + self.shown.append(w) + + def hideDeckStatus(self): + for w in self.shown: + self.statusbar.removeWidget(w) + w.setParent(None) + self.shown = [] + + def hideBorders(self): + "Remove the ugly borders QT places on status bar widgets." + self.statusbar.setStyleSheet("::item { border: 0; }") + + def updateProgressGoal(self): + return + stats = self.main.deck.sched.getStats() + self.totalPending = stats['pending'] + + # Updating + ########################################################################## + + def redraw(self): + p = QPalette() + stats = self.main.deck.getStats() + remStr = _("Remaining: ") + if self.state == "deckFinished": + remStr += "0" + elif self.state == "deckEmpty": + remStr += "0" + else: + # remaining string, bolded depending on current card + if not self.main.currentCard: + remStr += "%(failed1)s + %(successive1)s + %(new1)s" + else: + q = self.main.deck.queueForCard(self.main.currentCard) + if q == "failed": + remStr += "%(failed1)s  %(successive1)s  %(new1)s" + elif q == "rev": + remStr += "%(failed1)s  %(successive1)s  %(new1)s" + else: + remStr += "%(failed1)s  %(successive1)s  %(new1)s" + stats['failed1'] = '%s' % stats['failed'] + stats['successive1'] = '%s' % stats['successive'] + stats['new1'] = '%s' % stats['new'] + self.remText.setText(remStr % stats) + stats['suspended'] = self.main.deck.suspendedCardCount() + stats['spaced'] = self.main.deck.spacedCardCount() + self.remText.setToolTip(_( + "

Remaining cards

" + "The number of cards left to answer." + "

There are %(failed)d failed cards due soon.
" + "There are %(successive)d cards awaiting review.
" + "There are %(new)d new cards.
" + "There are %(spaced)d spaced cards.
" + "There are %(suspended)d suspended cards.") % stats) + # eta + self.etaText.setText(_("ETA: %(timeLeft)s") % stats) + # retention & progress bars + p.setColor(QPalette.Base, QColor("black")) + p.setColor(QPalette.Button, QColor("black")) + self.setProgressColour(p, stats['gMatureYes%']) + self.retentionBar.setPalette(p) + self.retentionBar.setValue(stats['gMatureYes%']) + self.setProgressColour(p, stats['dYesTotal%']) + self.progressBar.setPalette(p) + self.progressBar.setValue(stats['dYesTotal%']) + # tooltips + stats['avgTime'] = anki.utils.fmtTimeSpan(stats['dAverageTime'], point=2) + stats['revTime'] = anki.utils.fmtTimeSpan(stats['dReviewTime'], point=2) + tip = _("""

Performance

+The top bar shows your performance today. The bottom bar shows your
+performance on cards scheduled for 21 days or more. The bottom bar should
+generally be between 80-95%% - lower and you're forgetting mature cards
+too often, higher and you're spending too much time reviewing. +

Reviews today

+Correct today: %(dYesTotal%)0.1f%% +(%(dYesTotal)d of %(dTotal)d)
+Average time per answer: %(avgTime)s
+Total review time: %(revTime)s""") % stats + stats['avgTime'] = anki.utils.fmtTimeSpan(stats['gAverageTime'], point=2) + stats['revTime'] = anki.utils.fmtTimeSpan(stats['gReviewTime'], point=2) + tip += _("""

All Reviews

+Correct over a month: %(gMatureYes%)0.1f%% +(%(gMatureYes)d of %(gMatureTotal)d)
+Average time per answer: %(avgTime)s
+Total review time: %(revTime)s
+Correct under a month: %(gYoungYes%)0.1f%% +(%(gYoungYes)d of %(gYoungTotal)d)
+Correct first time: %(gNewYes%)0.1f%% +(%(gNewYes)d of %(gNewTotal)d)
+Total correct: %(gYesTotal%)0.1f%% +(%(gYesTotal)d of %(gTotal)d)""") % stats + self.combinedBar.setToolTip(tip) + if self.main.config['showTimer']: + self.timer.setVisible(True) + self.drawTimer() + self.timer.setToolTip(_(""" +

Time

+Anki tracks how long you spend looking at a card.
+This time is used to calculate the ETA, but not used
+for scheduling.

+You should aim to answer each question within
+10 seconds. Click the timer to learn more.""")) + else: + self.timer.setVisible(False) + + def setProgressColour(self, palette, perc): + if perc == 0: + palette.setColor(QPalette.Highlight, QColor("black")) + elif perc < 50: + palette.setColor(QPalette.Highlight, QColor("#ee0000")) + elif perc < 65: + palette.setColor(QPalette.Highlight, QColor("#ee7700")) + elif perc < 75: + palette.setColor(QPalette.Highlight, QColor("#eeee00")) + else: + palette.setColor(QPalette.Highlight, QColor("#00ee00")) + + def drawTimer(self): + if not self.main.config['showTimer']: + return + if not self.timer: + return + if self.main.state in "showQuestion": + t = self.main.currentCard.thinkingTime() + if t < 60: + if t < StatusView.warnTime: + col="#000000" + else: + col="#FF3300" + self.timer.setText('00:%02d' % (col, t)) + else: + self.timer.setText('01:00') + elif self.main.state == "showAnswer": + pass + else: + self.timer.setText("00:00") + + def onConfigure(self): + self.main.deckProperties = ui.deckproperties.DeckProperties(self.main) + self.main.deckProperties.dialog.qtabwidget.setCurrentIndex(2) diff --git a/ankiqt/ui/sync.py b/ankiqt/ui/sync.py new file mode 100644 index 000000000..da3131e43 --- /dev/null +++ b/ankiqt/ui/sync.py @@ -0,0 +1,171 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import os, types, socket, time +import ankiqt +import anki +from anki.sync import SyncClient, HttpSyncServerProxy +from anki.errors import * +from anki import DeckStorage +import ankiqt.forms + +# Synchronising a deck with a public server +########################################################################## + +class Sync(QThread): + + def __init__(self, parent, user, pwd, interactive, create, onlyMerge): + QThread.__init__(self) + self.parent = parent + self.interactive = interactive + self.user = user + self.pwd = pwd + self.create = create + self.ok = True + self.onlyMerge = onlyMerge + + def setStatus(self, msg, timeout=5000): + self.emit(SIGNAL("setStatus"), msg, timeout) + + def run(self): + self.syncDeck() + + def error(self, error): + self.setStatus(self.getErrorMessage(error)) + if self.onlyMerge: + # new file needs cleaning up + self.emit(SIGNAL("cleanNewDeck")) + else: + self.emit(SIGNAL("syncFinished")) + + def getErrorMessage(self, error): + if error.data.get('status') == "invalidUserPass": + msg=_("Please double-check your username/password.") + elif error.data.get('status') == "oldVersion": + msg=_("The sync protocol has changed. Please upgrade.") + elif error.data.get('type') == "noResponse": + msg=_("Server is down or operation failed.") + else: + msg=_("Unknown error: %s") % `error.data` + return msg + + def connect(self, *args): + # connect, check auth + proxy = HttpSyncServerProxy(self.user, self.pwd) + proxy.connect("ankiqt-" + ankiqt.appVersion) + return proxy + + def syncDeck(self): + self.setStatus(_("Connecting.."), 0) + try: + proxy = self.connect() + except SyncError, e: + return self.error(e) + # exists on server? + if not proxy.hasDeck(self.parent.syncName): + if self.create: + try: + proxy.createDeck(self.parent.syncName) + except SyncError, e: + return self.error(e) + else: + self.emit(SIGNAL("noMatchingDeck"), proxy.decks.keys(), + not self.onlyMerge) + self.setStatus("") + return + timediff = abs(proxy.timestamp - time.time()) + if timediff > 60: + self.emit(SIGNAL("syncClockOff"), timediff) + return + # reconnect + try: + self.deck = DeckStorage.Deck(self.parent.deckPath, rebuild=False, + backup=False, lock=True) + client = SyncClient(self.deck) + client.setServer(proxy) + proxy.deckName = self.parent.syncName + # need to do anything? + if not client.prepareSync(): + self.setStatus(_("Sync: nothing to do")) + self.deck.close() + self.emit(SIGNAL("syncFinished")) + return + start = time.time() + # summary + self.setStatus(_("Fetching summary from server.."), 0) + sums = client.summaries() + # diff + self.setStatus(_("Determining differences.."), 0) + payload = client.genPayload(sums) + # send payload + pr = client.payloadChangeReport(payload) + self.setStatus("
" + pr + "
", 0) + self.setStatus(_("Sending payload..."), 0) + res = client.server.applyPayload(payload) + # apply reply + self.setStatus(_("Applying reply.."), 0) + client.applyPayloadReply(res) + # finished. save deck, preserving mod time + self.setStatus(_("Sync complete.")) + self.deck.lastLoaded = self.deck.modified + self.deck.s.flush() + self.deck.s.commit() + # close and send signal to main thread + self.deck.close() + taken = time.time() - start + if taken < 2.5: + time.sleep(2.5 - taken) + self.emit(SIGNAL("syncFinished")) + except Exception, e: + self.deck.close() + # cheap hack to ensure message is displayed + err = `getattr(e, 'data', None) or e` + self.setStatus(_("Syncing failed: %(a)s") % { + 'a': err}) + time.sleep(3) + self.emit(SIGNAL("syncFinished")) + +# Choosing a deck to sync to +########################################################################## + +class DeckChooser(QDialog): + + def __init__(self, parent, decks, create): + QDialog.__init__(self, parent) + self.parent = parent + self.decks = decks + self.dialog = ankiqt.forms.syncdeck.Ui_DeckChooser() + self.dialog.setupUi(self) + self.create = create + if self.create: + self.dialog.decks.addItem(QListWidgetItem( + _("Create '%s' on server") % self.parent.syncName)) + self.decks.sort() + for name in decks: + name = os.path.splitext(name)[0] + item = QListWidgetItem(_("Merge with '%s' on server") % name) + self.dialog.decks.addItem(item) + self.dialog.decks.setCurrentRow(0) + # the list widget will swallow the enter key + s = QShortcut(QKeySequence("Return"), self) + self.connect(s, SIGNAL("activated()"), self.accept) + self.name = None + + def getName(self): + self.exec_() + return self.name + + def accept(self): + idx = self.dialog.decks.currentRow() + if self.create: + offset = 1 + else: + offset = 0 + if idx == 0 and self.create: + self.name = self.parent.syncName + else: + self.name = self.decks[self.dialog.decks.currentRow() - offset] + self.close() + diff --git a/ankiqt/ui/tagedit.py b/ankiqt/ui/tagedit.py new file mode 100644 index 000000000..e652d5b57 --- /dev/null +++ b/ankiqt/ui/tagedit.py @@ -0,0 +1,53 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +from anki.utils import parseTags + +class TagEdit(QLineEdit): + + def __init__(self, parent, *args): + QLineEdit.__init__(self, parent, *args) + self.model = QStringListModel() + self.completer = TagCompleter(self.model, parent) + self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) + self.completer.setCaseSensitivity(Qt.CaseInsensitive) + self.setCompleter(self.completer) + + def setDeck(self, deck): + "Set the current deck, updating list of available tags." + self.deck = deck + tags = self.deck.allTags() + self.model.setStringList( + QStringList(sorted(tags))) + + def keyPressEvent(self, evt): + if evt.key() in (Qt.Key_Enter, + Qt.Key_Return): + evt.accept() + cur = self.completer.currentCompletion() + if cur and not str(cur).strip().endswith(","): + self.setText(self.completer.currentCompletion()) + else: + self.completer.popup().close() + else: + QLineEdit.keyPressEvent(self, evt) + +class TagCompleter(QCompleter): + + def __init__(self, *args): + QCompleter.__init__(self, *args) + self.tags = [] + + def splitPath(self, str): + self.tags = parseTags(unicode(str)) + if self.tags: + return QStringList(self.tags[-1]) + return QStringList("") + + def pathFromIndex(self, idx): + ret = QCompleter.pathFromIndex(self, idx) + self.tags = self.tags[0:-1] + self.tags.append(unicode(ret)) + return ", ".join(self.tags) diff --git a/ankiqt/ui/tray.py b/ankiqt/ui/tray.py new file mode 100644 index 000000000..f1a9969f9 --- /dev/null +++ b/ankiqt/ui/tray.py @@ -0,0 +1,73 @@ +# Copyright: Richard Colley +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4 import QtGui, QtCore +Qt = QtCore.Qt + +class AnkiTrayIcon( QtCore.QObject ): + """ + Enable minimize to tray + """ + + def __init__( self, mw ): + QtCore.QObject.__init__( self, mw ) + self.mw = mw + self.anki_visible = True + if (QtGui.QSystemTrayIcon.isSystemTrayAvailable() and + mw.config['showTray']): + self.ti = QtGui.QSystemTrayIcon( mw ) + if self.ti: + self.mw.addHook("quit", self.onQuit) + self.ti.setIcon( QtGui.QIcon(":/icons/anki.png") ) + self.ti.setToolTip( "Anki" ) + + # hook signls, and Anki state changes + mw.addView( self ) + mw.connect(self.ti, QtCore.SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), lambda i: self.activated(i)) + mw.connect(self.ti, QtCore.SIGNAL("messageClicked()"), lambda : self.messageClicked()) + self.ti.show() + + def showMain( self ): + self.mw.showNormal() + self.mw.activateWindow() + self.mw.raise_() + self.anki_visible = True + + def hideMain( self ): + self.mw.hide() + self.anki_visible = False + + def activated( self, reason ): + if self.anki_visible: + self.hideMain() + else: + self.showMain() + + def messageClicked( self ): + if not self.anki_visible: + self.showMain() + + def setToolTip( self, message ): + self.ti.setToolTip( message ) + + def showMessage( self, message ): + if self.ti.supportsMessages(): + self.ti.showMessage( "Anki", message ) + + def setState( self, state ): + if state == "showQuestion": + if not self.anki_visible: + self.showMessage( "A new card is available for review, click this message to display Anki" ) + self.setToolTip( "Anki - displaying question" ) + elif state == "showAnswer": + self.setToolTip( "Anki - displaying answer" ) + elif state == "noDeck": + self.setToolTip( "Anki - no deck" ) + elif state == "deckFinished": + if self.mw and self.mw.deck: + self.setToolTip( "Anki - next card in " + self.mw.deck.earliestTimeStr() ) + else: + self.setToolTip( "Anki" ) + + def onQuit(self): + self.ti.deleteLater() diff --git a/ankiqt/ui/unsaved.py b/ankiqt/ui/unsaved.py new file mode 100644 index 000000000..d7439e509 --- /dev/null +++ b/ankiqt/ui/unsaved.py @@ -0,0 +1,16 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +import ankiqt.forms + +save = QMessageBox.Save +discard = QMessageBox.Discard +cancel = QMessageBox.Cancel +def ask(parent): + return QMessageBox.question( + parent, "Anki", + _("""

Unsaved changes

There are unsaved + changes. Would you like to save them, discard your + changes, or cancel?"""), + save | discard | cancel) diff --git a/ankiqt/ui/update.py b/ankiqt/ui/update.py new file mode 100644 index 000000000..06eaf05f0 --- /dev/null +++ b/ankiqt/ui/update.py @@ -0,0 +1,155 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtCore import * +from PyQt4.QtGui import * +import urllib, urllib2, os, sys, time, httplib +import anki, anki.utils, anki.lang, anki.stats +import ankiqt +import simplejson +import tempfile + +#baseUrl = "http://localhost:5000/update/" +baseUrl = "http://anki.ichi2.net/update/" + +# when requesting latest version number, gather their version, deck size and +# average retention ratio for future development +# FIXME: add ability to disable in prefs, warn about anonymous sent info +class LatestVersionFinder(QThread): + + def __init__(self, main): + QThread.__init__(self) + self.main = main + self.config = main.config + # calculate stats before we start a new thread + if self.main.deck != None: + deckSize = self.main.deck.cardCount() + stats = anki.stats.globalStats(self.main.deck.s) + deckRecall = "%0.2f" % ( + (stats.matureEase3 + stats.matureEase4) / + float(stats.matureEase0 + + stats.matureEase1 + + stats.matureEase2 + + stats.matureEase3 + + stats.matureEase4 + 0.000001) * 100) + pending = "(%d, %d)" % (self.main.deck.seenCardCount(), + self.main.deck.newCardCount()) + ct = self.main.deck.created + if ct: + ol = anki.lang.getLang() + anki.lang.setLang("en") + age = anki.utils.fmtTimeSpan(abs( + time.time() - ct)) + anki.lang.setLang(ol) + else: + age = "" + plat=sys.platform + pver=sys.version + else: + deckSize = "nodeck" + deckRecall = "" + pending = "" + age = "" + plat="" + pver="" + d = {"ver": ankiqt.appVersion, + "size": deckSize, + "rec": deckRecall, + "pend": pending, + "age": age, + "pver": pver, + "plat": plat,} + self.stats = d + + def run(self): + if not self.config['checkForUpdates']: + return + d = self.stats + d['proto'] = 2 + d = urllib.urlencode(d) + try: + f = urllib2.urlopen(baseUrl + "getQtVersion", d) + except (urllib2.URLError, httplib.BadStatusLine): + return + resp = f.read() + if not resp: + return + resp = simplejson.loads(resp) + if resp['latestVersion'] > ankiqt.appVersion: + self.emit(SIGNAL("newVerAvail"), resp) + diff = resp['currentTime'] - time.time() + if abs(diff) > 300: + self.emit(SIGNAL("clockIsOff"), diff) + +class Updater(QThread): + + filename = "anki-update.exe" + # FIXME: get when checking version number + chunkSize = 428027 + percChange = 5 + + def __init__(self): + QThread.__init__(self) + + def setStatus(self, msg, timeout=0): + self.emit(SIGNAL("statusChanged"), msg, timeout) + + def run(self): + dir = tempfile.mkdtemp(prefix="anki-update") + os.chdir(dir) + filename = os.path.abspath(self.filename) + try: + f = urllib2.urlopen(baseUrl + "getQt") + except urllib2.URLError: + self.setStatus(_("Unable to reach server")) + return + try: + newfile = open(filename, "wb") + except: + self.setStatus(_("Unable to open file")) + return + perc = 0 + while 1: + self.setStatus( + _("Downloading anki updater - %d%% complete.") % perc) + resp = f.read(self.chunkSize) + if not resp: + break + newfile.write(resp) + perc += self.percChange + if perc > 99: + perc = 99 + newfile.close() + self.setStatus(_("Updating..")) + os.chdir(os.path.dirname(filename)) + os.system(os.path.basename(filename)) + self.setStatus(_("Update complete. Please restart Anki.")) + os.unlink(filename) + +def askAndUpdate(parent, version=None): + version = version['latestVersion'] + baseStr = ( + _("""

Anki updated

Anki %s has been released.
+The release notes are +here. +

""") % + version) + msg = QMessageBox(parent) + msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + msg.setIcon(QMessageBox.Information) + msg.setText(baseStr + _("Would you like to download it now?")) + button = QPushButton(_("Ignore this update")) + msg.addButton(button, QMessageBox.RejectRole) + ret = msg.exec_() + if msg.clickedButton() == button: + # ignore this update + parent.config['suppressUpdate'] = version + elif ret == QMessageBox.Yes: + if sys.platform == "win32": + parent.autoUpdate = Updater() + parent.connect(parent.autoUpdate, + SIGNAL("statusChanged"), + parent.setStatus) + parent.autoUpdate.start() + else: + QDesktopServices.openUrl(QUrl(ankiqt.appWebsite)) diff --git a/ankiqt/ui/utils.py b/ankiqt/ui/utils.py new file mode 100644 index 000000000..807201b51 --- /dev/null +++ b/ankiqt/ui/utils.py @@ -0,0 +1,67 @@ +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * + +import re, os +import ankiqt + +def showWarning(text, parent=None): + "Show a small warning with an OK button." + if not parent: + parent = ankiqt.mw + QMessageBox.warning(parent, "Anki", text) + +def showInfo(text, parent=None): + "Show a small info window with an OK button." + if not parent: + parent = ankiqt.mw + QMessageBox.information(parent, "Anki", text) + +def askUser(text, parent=None): + "Show a yes/no question. Return true if yes." + if not parent: + parent = ankiqt.mw + r = QMessageBox.question(parent, "Anki", text, + QMessageBox.Yes | QMessageBox.No) + return r == QMessageBox.Yes + +def getText(prompt, parent=None): + if not parent: + parent = ankiqt.mw + (text, ok) = QInputDialog.getText(parent, "Anki", prompt) + if not ok: + return None + return unicode(text) + +def getFile(parent, title, dir, key): + "Ask the user for a file. Use DIR as config variable." + dirkey = dir+"Directory" + file = unicode(QFileDialog.getOpenFileName( + parent, title, ankiqt.mw.config.get(dirkey, ""), key)) + if file: + dir = os.path.dirname(file) + ankiqt.mw.config[dirkey] = dir + return file + +def getSaveFile(parent, title, dir, key, ext): + "Ask the user for a file to save. Use DIR as config variable." + dirkey = dir+"Directory" + file = unicode(QFileDialog.getSaveFileName( + parent, title, ankiqt.mw.config.get(dirkey, ""), key, + None, QFileDialog.DontConfirmOverwrite)) + if file: + # add extension + if not file.lower().endswith(ext): + file += ext + # save new default + dir = os.path.dirname(file) + ankiqt.mw.config[dirkey] = dir + # check if it exists + if os.path.exists(file): + if not askUser( + _("This file exists. Are you sure you want to overwrite it?"), + parent): + return None + return file diff --git a/ankiqt/ui/view.py b/ankiqt/ui/view.py new file mode 100644 index 000000000..a43e60437 --- /dev/null +++ b/ankiqt/ui/view.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# Copyright: Damien Elmes +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import anki, anki.utils +from anki.sound import playFromText, stripSounds +from anki.latex import renderLatex, stripLatex +from anki.utils import stripHTML +import types, time +from ankiqt import ui + +# Views - define the way a user is prompted for questions, etc +########################################################################## + +class View(object): + "Handle the main window update as we transition through various states." + + def __init__(self, parent, body, frame=None): + self.main = parent + self.body = body + self.frame = frame + + # State control + ########################################################################## + + def setState(self, state): + "Change to STATE, and update the display." + self.oldState = getattr(self, 'state', None) + self.state = state + if self.state == "initial": + self.shownLearnHelp = False + self.shownReviewHelp = False + self.shownFinalHelp = False + return + elif self.state == "noDeck": + self.clearWindow() + self.drawNoDeckMessage() + self.flush() + return + self.redisplay() + + def redisplay(self): + "Idempotently display the current state (prompt for question, etc)" + if self.state == "noDeck": + return + self.clearWindow() + self.setBackgroundColour() + self.maybeHelp() + if self.main.deck.cardCount(): + if not self.main.lastCard or ( + self.main.config['suppressLastCardContent'] and + self.main.config['suppressLastCardInterval']): + self.buffer += "
" + else: + self.drawTopSection() + if self.state == "showQuestion": + self.drawQuestion() + elif self.state == "showAnswer": + if not self.main.currentCard.cardModel.questionInAnswer: + self.drawQuestion(nosound=True) + self.drawAnswer() + elif self.state == "deckEmpty": + self.drawDeckEmptyMessage() + elif self.state == "deckFinished": + self.drawDeckFinishedMessage() + self.flush() + + def addStyles(self): + # card styles + s = "" + return s + + def clearWindow(self): + self.body.setHtml("") + self.buffer = "" + + # Font properties & output + ########################################################################## + + def flush(self): + "Write the current HTML buffer to the screen." + self.body.setHtml(self.addStyles() + '
' + self.buffer + "
") + + def write(self, text): + if type(text) != types.UnicodeType: + text = unicode(text, "utf-8") + self.buffer += text + + def setBackgroundColour(self): + p = QPalette() + p.setColor(QPalette.Base, QColor(self.main.config['backgroundColour'])) + self.body.setPalette(p) + if self.frame: + p.setColor(QPalette.Background, QColor(self.main.config['backgroundColour'])) + self.frame.setPalette(p) + + # Question and answer + ########################################################################## + + def drawQuestion(self, nosound=False): + "Show the question." + q = self.main.currentCard.htmlQuestion + q = renderLatex(self.main.deck, q) + self.write(stripSounds(q)) + if self.state != self.oldState and not nosound: + playFromText(q) + + def drawAnswer(self): + "Show the answer." + a = self.main.currentCard.htmlAnswer + a = renderLatex(self.main.deck, a) + self.write(stripSounds(a)) + if self.state != self.oldState: + playFromText(a) + + # Top section + ########################################################################## + + def drawTopSection(self): + "Show previous card, next scheduled time, and stats." + self.buffer += "
" + self.drawLastCard() + self.buffer += "
" + + def drawLastCard(self): + "Show the last card if not the current one, and next time." + if self.main.lastCard: + if not self.main.config['suppressLastCardContent']: + if (self.state == "deckFinished" or + self.main.currentCard.id != self.main.lastCard.id): + q = self.main.lastCard.question.replace("
", " ") + q = stripHTML(q) + if len(q) > 50: + q = q[:50] + "..." + a = self.main.lastCard.answer.replace("
", " ") + a = stripHTML(a) + if len(a) > 50: + a = a[:50] + "..." + s = "%s
%s" % (q, a) + s = stripLatex(s) + self.write('%s
' % s) + if not self.main.config['suppressLastCardInterval']: + if self.main.lastQuality > 1: + msg = _("Well done! This card will appear again in " + "%(next)s.") % \ + {"next":self.main.lastScheduledTime} + else: + msg = _("This card will appear again in less than " + "%(next)s.") % \ + {"next":self.main.lastScheduledTime} + self.write(msg) + + # Help + ########################################################################## + + def maybeHelp(self): + return + stats = self.main.deck.sched.getStats() + if not stats['pending']: + self.main.help.hide() + elif (self.main.currentCard and + self.main.currentCard.nextTime > time.time()): + if not self.shownFinalHelp: + self.shownFinalHelp = True + self.main.help.showHideableKey("finalReview") + elif stats['learnMode']: + if not self.shownLearnHelp: + if stats['pending'] != 0: + self.shownLearnHelp = True + self.main.help.showHideableKey("learn") + else: + if not self.shownReviewHelp: + self.shownReviewHelp = True + self.main.help.showHideableKey("review") + + # Welcome/empty/finished deck messages + ########################################################################## + + def drawNoDeckMessage(self): + self.write(_("""

Welcome to Anki!

+

+ + + + +
+Anki is a tool which will help you remember things as quickly and easily as +possible. Anki works by asking you questions. After answering a question, +Anki will ask how well you remembered. If you made a mistake or had difficulty +remembering, Anki will show you the question again after a short amount of +time. If you answered the question easily, Anki will wait for a number of days +before asking you again. Each time you successfully remember something, the +time before you see it again will get bigger. +
+ +

+ + + + + + + + + + + + + + + + +
+ +

Open a sample deck

+ +

Open an existing deck

+ + +

Create a new deck

+

+ + + + +
+

Adding material

+There are three ways to add material to Anki: typing it in yourself, using a +pre-made Anki deck, or importing word lists that you find on the internet. +

+ +For language learning, it's a good idea to add material yourself, from sources +like a textbook or a TV show. By adding words that you see or hear in context, +you also learn how they are used. While it may be tempting to use a big, +pre-made vocabulary list to save time, learning words and grammar in context +will ensure you can use them naturally. +

+ +So if you're learning a language, consider adding material you want to learn +into Anki by yourself. Initially the time required to type in material may +seem daunting, but it's a small amount of time compared to the time you'll +save by not forgetting. +

+""")) + + def drawDeckEmptyMessage(self): + "Tell the user the deck is empty." + self.write(_(""" +

Empty deck

The current deck has no cards in it. Please select 'Add +card' from the Edit menu.""")) + + def drawDeckFinishedMessage(self): + "Tell the user the deck is finished." + self.write("
" + + self.main.deck.deckFinishedMsg() + + "
") diff --git a/ankiqtmac.py b/ankiqtmac.py new file mode 100644 index 000000000..b7b0089ff --- /dev/null +++ b/ankiqtmac.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# +# hack because py2app barfs on a try block +# + +import pkg_resources + +import os, sys + +modDir=os.path.dirname(os.path.abspath(__file__)) +runningDir=os.path.split(modDir)[0] +# set up paths for local development +sys.path.insert(0, os.path.join(runningDir, "libanki")) +sys.path.insert(0, os.path.join(os.path.join(runningDir, ".."), "libanki")) + +import ankiqt +ankiqt.run() diff --git a/designer/about.ui b/designer/about.ui new file mode 100644 index 000000000..870d56239 --- /dev/null +++ b/designer/about.ui @@ -0,0 +1,87 @@ + + About + + + + 0 + 0 + 224 + 310 + + + + + 0 + 0 + + + + About Anki + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + About + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + About + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/addcards.ui b/designer/addcards.ui new file mode 100644 index 000000000..705611e17 --- /dev/null +++ b/designer/addcards.ui @@ -0,0 +1,324 @@ + + AddCards + + + + 0 + 0 + 921 + 553 + + + + Anki - Add Cards + + + + 0 + + + 3 + + + + + 6 + + + 3 + + + + + 3 + + + 3 + + + + + + + + + + + 5 + 7 + 0 + 0 + + + + Fields + + + + + + + Status + + + + 0 + + + 0 + + + + + false + + + + 7 + 5 + 0 + 0 + + + + + 16777215 + 50 + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::NoButton + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 250 + 230 + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 250 + 230 + + + + + + + + + 255 + 250 + 230 + + + + + + + 255 + 250 + 230 + + + + + + + + true + + + QFrame::NoFrame + + + QFrame::Raised + + + + 9 + + + 6 + + + + + true + + + + 1 + 7 + 0 + 0 + + + + + 200 + 0 + + + + + 300 + 16777215 + + + + + + + + + 255 + 250 + 230 + + + + + + + + + 255 + 250 + 230 + + + + + + + + + 207 + 207 + 207 + + + + + + + + QFrame::NoFrame + + + QFrame::Sunken + + + Qt::ScrollBarAlwaysOff + + + + + + + + + + + + + buttonBox + + + + + + + buttonBox + rejected() + AddCards + reject() + + + 301 + -1 + + + 286 + 274 + + + + + diff --git a/designer/addmodel.ui b/designer/addmodel.ui new file mode 100644 index 000000000..c3377163e --- /dev/null +++ b/designer/addmodel.ui @@ -0,0 +1,140 @@ + + AddModel + + + + 0 + 0 + 388 + 363 + + + + Anki + + + + + + <h1>What would you like to study?</h1> + + + Qt::AlignCenter + + + true + + + + + + + + + + + + + A pre-made quiz style. + + + true + + + + + + + + 12 + + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + + A simple front-to-back quiz style. + + + + + + + An existing online deck. + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AddModel + accept() + + + 266 + 353 + + + 157 + 274 + + + + + buttonBox + rejected() + AddModel + reject() + + + 334 + 353 + + + 286 + 274 + + + + + createTemplate + toggled(bool) + models + setEnabled(bool) + + + 217 + 71 + + + 213 + 138 + + + + + diff --git a/designer/cardlist.ui b/designer/cardlist.ui new file mode 100644 index 000000000..8389202a3 --- /dev/null +++ b/designer/cardlist.ui @@ -0,0 +1,385 @@ + + EditDeck + + + Qt::NonModal + + + + 0 + 0 + 710 + 655 + + + + Anki - Edit Deck + + + :/icons/view_text.png + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 400 + 0 + + + + &Search + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + + + + + + + 150 + 0 + + + + + + + + + + + S&ort + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + + + + + Actions on selected.. + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + Facts.. + + + :/icons/Anki_Fact.png + + + false + + + + + + + Cards.. + + + :/icons/Anki_Card.png + + + false + + + + + + + + + + + + Qt::Vertical + + + + + 0 + 4 + + + + Qt::ActionsContextMenu + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + QAbstractItemView::SelectRows + + + + + + 0 + 2 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + 6 + + + + + + 2 + 2 + + + + + 0 + 300 + + + + Current Card + + + + + + + + 7 + 2 + + + + Current &Fact + + + + + + + + + + + + + :/icons/editdelete.png + + + Toggle Delete + + + + + :/icons/Anki_Add_Tag.png + + + Add Tag.. + + + + + :/icons/Anki_Add_Tag.png + + + Add Tag.. + + + + + :/icons/Anki_Del_Tag.png + + + Delete Tag.. + + + + + :/icons/Anki_Del_Tag.png + + + Delete Tag.. + + + + + :/icons/Anki_Card.png + + + Add Missing Active Cards + + + + + :/icons/editdelete.png + + + Toggle Delete + + + + + Reset Progress + + + + + Reset Progress + + + + + fieldsArea + + + + + + diff --git a/designer/changemap.ui b/designer/changemap.ui new file mode 100644 index 000000000..02ab05969 --- /dev/null +++ b/designer/changemap.ui @@ -0,0 +1,82 @@ + + ChangeMap + + + + 0 + 0 + 391 + 360 + + + + Change field mapping + + + + 9 + + + 6 + + + + + <h1>Available fields</h1>Please choose which field you would like to import into. If you select "Discard field", all data from this field will be lost. + + + true + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ChangeMap + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ChangeMap + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/deckproperties.ui b/designer/deckproperties.ui new file mode 100644 index 000000000..b5b662500 --- /dev/null +++ b/designer/deckproperties.ui @@ -0,0 +1,666 @@ + + DeckProperties + + + Qt::ApplicationModal + + + + 0 + 0 + 529 + 593 + + + + Edit deck properties + + + + + + 0 + + + + Description && Synchronisation + + + + 6 + + + 6 + + + + + <h1>Deck description</h1> + + + + + + + + 0 + 100 + + + + true + + + + + + + <h1>Synchronisation</h1>Synchronisation lets you use your deck on multiple computers at the same time. You can also use it to study on the web, for when you're not at home. You can also study on your mobile phone if your phone has internet access. + + + false + + + true + + + + + + + Synchronize this deck with name: + + + + + + + false + + + + + + + + Priorities && Suspending + + + + 6 + + + 6 + + + + + <h1>Priorities</h1>A comma-separated list of tags to prioritize or deprioritize<a href="http://ichi2.net/anki/wiki/CardDisplayOrderAndPriorities">?</a> + + + true + + + + + + + <h2>Very high priority</h2> + + + true + + + + + + + + + + <h2>High priority</h2> + + + true + + + + + + + + + + <h2>Low priority</h2> + + + true + + + + + + + + + + <h2>Suspended</h2> + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Scheduling + + + + + + + 0 + 0 + + + + <h1>New cards</h1> + + + true + + + + + + + + + <b>Number of new cards per day + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + Initial times + + + + + + + 0 + + + 6 + + + + + + + + Min + + + + + + + Min + + + + + + + + + + <b>Easy</b> + + + + + + + <b>Medium</b> + + + + + + + Max + + + + + + + + + + + + + Max + + + + + + + + + + + + + Min + + + + + + + Max + + + + + + + + 220 + 0 + + + + <b>Hard</b> + + + + + + + + + + 0 + 0 + + + + <h1>Delay on mistake</h1>The time until Anki shows you a card you got wrong. The default is 10 minutes. + + + + + + + 0 + + + 6 + + + + + <b>Totally forgot (0)</b> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <b>Made a mistake (1) on a young<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-3b268fd372f260751f5e9020a79d54a4f68e1829">?</a> card + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + <b>Made a mistake (1) on a mature<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-3b268fd372f260751f5e9020a79d54a4f68e1829">?</a> card</b> + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + <h1>Failed card limit & collapsing</h1> + + + + + + + + + <b>Maximum failed cards<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-e081bb818555df4a9fd06e16e685835a95303e0c">?</a></b> + + + true + + + + + + + + + + <b>Show failed cards early<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-c34f83f4acaf3d9a8f59dcff02dd6abec930ebaf">?</a></b> + + + true + + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + Models + + + + 6 + + + 6 + + + + + <h1>Models</h1>Models define what sort of information you're studying, and the way to show it to you. Models range from a simple representation of a single flashcard with a "front" and "back" side, to more complicated domain specific models: a model for Russian verbs may define two verb forms, and test you on both of them. + + + true + + + + + + + + + + 6 + + + 0 + + + + + &Add + + + + + + + &Edit + + + + + + + &Delete + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + qtabwidget + deckDescription + doSync + syncName + highPriority + medPriority + lowPriority + postponing + newCardsPerDay + newCardOrder + newCardScheduling + hardMin + hardMax + midMin + midMax + easyMin + easyMax + delay0 + delay1 + delay2 + failedCardMax + collapse + modelsList + modelsAdd + modelsEdit + modelsDelete + buttonBox + + + + + buttonBox + accepted() + DeckProperties + accept() + + + 266 + 539 + + + 157 + 274 + + + + + buttonBox + rejected() + DeckProperties + reject() + + + 334 + 539 + + + 286 + 274 + + + + + doSync + toggled(bool) + syncName + setEnabled(bool) + + + 58 + 445 + + + 327 + 477 + + + + + diff --git a/designer/displayproperties.ui b/designer/displayproperties.ui new file mode 100644 index 000000000..2c833b9de --- /dev/null +++ b/designer/displayproperties.ui @@ -0,0 +1,677 @@ + + DisplayProperties + + + + 0 + 0 + 794 + 700 + + + + + 5 + 3 + 0 + 0 + + + + + 0 + 700 + + + + Fonts and colours + + + :/icons/fonts.png + + + + 9 + + + 6 + + + + + 0 + + + 12 + + + + + + 0 + 5 + 0 + 0 + + + + + 330 + 16 + + + + + 330 + 16777215 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 6 + + + + + 0 + + + 7 + + + + + 0 + + + 6 + + + + + + + + + + 0 + + + 6 + + + + + + 0 + 5 + 0 + 0 + + + + Card: + + + + + + + + + + + + Card properties + + + + 3 + + + 6 + + + + + 0 + + + 5 + + + + + Question font + + + + + + + + + + false + + + + + + + Answer size + + + + + + + Answer colour + + + + + + + Answer font + + + + + + + + + + Question alignment + + + + + + + Question size + + + + + + + 300 + + + + + + + + + + + + + false + + + + + + + 300 + + + + + + + Question colour + + + + + + + Answer alignment + + + + + + + + + + + 5 + 5 + 0 + 0 + + + + + + + + + + + + + Fields + + + + 3 + + + 6 + + + + + + 5 + 3 + 0 + 0 + + + + + 0 + 60 + + + + + + + + <b>When quizzing/adding/editing</b> + + + + + + + 0 + + + 5 + + + + + + 1 + 1 + 0 + 0 + + + + + 0 + 25 + + + + Use custom size + + + + + + + + 0 + 25 + + + + + + + + + 1 + 1 + 0 + 0 + + + + + 0 + 25 + + + + Use custom font + + + + + + + 300 + + + 5 + + + + + + + + 1 + 1 + 0 + 0 + + + + + 0 + 25 + + + + Use custom colour + + + + + + + true + + + + + + false + + + + + + + + + <b>When adding/editing</b> + + + + + + + 0 + + + 5 + + + + + 300 + + + 5 + + + + + + + + 1 + 0 + 0 + 0 + + + + + 0 + 25 + + + + Use custom size + + + + + + + + 1 + 0 + 0 + 0 + + + + + + + + + 1 + 0 + 0 + 0 + + + + + 0 + 25 + + + + Use custom font + + + + + + + + + + + + Show preview + + + true + + + false + + + + + + + + + + + + Preview + + + + 6 + + + 6 + + + + + + 7 + 3 + 0 + 0 + + + + + 400 + 0 + + + + true + + + + + + + + 7 + 3 + 0 + 0 + + + + true + + + + + + + + + + + + cardList + questionFont + questionSize + questionColour + questionAlign + answerFont + answerSize + answerColour + answerAlign + fieldList + useFamily + fontFamily + useSize + fontSize + useColour + fontColour + useFamilyEdit + fontFamilyEdit + useSizeEdit + fontSizeEdit + preview + question + answer + + + + + + + useFamily + toggled(bool) + fontFamily + setShown(bool) + + + 98 + 448 + + + 197 + 447 + + + + + useSize + toggled(bool) + fontSize + setShown(bool) + + + 136 + 471 + + + 233 + 468 + + + + + useColour + toggled(bool) + fontColour + setShown(bool) + + + 109 + 501 + + + 261 + 499 + + + + + useFamilyEdit + toggled(bool) + fontFamilyEdit + setShown(bool) + + + 118 + 538 + + + 258 + 543 + + + + + useSizeEdit + toggled(bool) + fontSizeEdit + setShown(bool) + + + 106 + 569 + + + 165 + 567 + + + + + diff --git a/designer/exporting.ui b/designer/exporting.ui new file mode 100644 index 000000000..2a1838222 --- /dev/null +++ b/designer/exporting.ui @@ -0,0 +1,108 @@ + + ExportDialog + + + + 0 + 0 + 295 + 154 + + + + Export + + + + + + + + + 100 + 0 + + + + <b>Export format</b>: + + + + + + + + + + <b>Limit to tags</b>: + + + + + + + + + + + Include scheduling information + + + + + + + Include tags + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel + + + + + + + + + buttonBox + accepted() + ExportDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ExportDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/importing.ui b/designer/importing.ui new file mode 100644 index 000000000..12743c976 --- /dev/null +++ b/designer/importing.ui @@ -0,0 +1,278 @@ + + ImportDialog + + + + 0 + 0 + 477 + 484 + + + + Import + + + + + + Import options + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + 6 + + + + + <b>File to import</b>: + + + + + + + Choose &file... + + + true + + + + + + + <b>Type of file</b>: + + + + + + + + 0 + 0 + + + + + + + + Tags to append: + + + + + + + + + + + + Tag facts with duplicate fields instead of deleting + + + + + + + + + + + + + + 0 + 0 + + + + Field mapping + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 6 + + + 6 + + + + + &Import + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + + + + + Status + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + true + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + type + file + tags + tagDuplicates + importButton + status + buttonBox + + + + + buttonBox + accepted() + ImportDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ImportDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/infodialog.ui b/designer/infodialog.ui new file mode 100644 index 000000000..1ac2a5a36 --- /dev/null +++ b/designer/infodialog.ui @@ -0,0 +1,76 @@ + + InfoDialog + + + + 0 + 0 + 409 + 284 + + + + Dialog + + + + 9 + + + 6 + + + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + InfoDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + InfoDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/main.ui b/designer/main.ui new file mode 100644 index 000000000..2da19502f --- /dev/null +++ b/designer/main.ui @@ -0,0 +1,922 @@ + + MainWindow + + + + 0 + 0 + 555 + 292 + + + + + 0 + 0 + + + + Anki + + + + :/icons/anki.png:/icons/anki.png + + + + + 0 + 57 + 555 + 212 + + + + + 1 + 1 + + + + true + + + + 0 + + + 0 + + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + + true + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 5 + + + + + + 0 + 0 + + + + + 0 + 100 + + + + Qt::ClickFocus + + + QFrame::NoFrame + + + false + + + true + + + Qt::TextBrowserInteraction + + + false + + + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 250 + 230 + + + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 250 + 230 + + + + + + + + + 255 + 250 + 230 + + + + + + + 207 + 207 + 207 + + + + + + + + true + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 9 + + + + + true + + + + 0 + 0 + + + + + 200 + 0 + + + + + 300 + 16777215 + + + + + + + + + 255 + 250 + 230 + + + + + + + + + 255 + 250 + 230 + + + + + + + + + 207 + 207 + 207 + + + + + + + + QFrame::NoFrame + + + QFrame::Sunken + + + Qt::ScrollBarAlwaysOff + + + + + + + + + + + + + + + 0 + 0 + 555 + 23 + + + + + &Help + + + + + + + + + + + + &Edit + + + + + + + + + + + + + + + + &Deck + + + + Open &Recent + + + + :/icons/document-open-recent.png:/icons/document-open-recent.png + + + + + + + + + + + + + + + + + + + + &Tools + + + + &Lookup + + + + :/icons/edit-find.png:/icons/edit-find.png + + + + + + + + + + + + + + + + + + + + + + + + + &Advanced + + + + + + + + + + + + + + + + + + 0 + 269 + 555 + 23 + + + + + + true + + + + 0 + 23 + 555 + 34 + + + + Qt::Horizontal + + + TopToolBarArea + + + false + + + + + + + + + + + + + + + + + + + + + :/icons/application-exit.png:/icons/application-exit.png + + + E&xit + + + Ctrl+Q + + + + + + :/icons/document-new.png:/icons/document-new.png + + + &New.. + + + Ctrl+N + + + + + + :/icons/document-open.png:/icons/document-open.png + + + &Open.. + + + Ctrl+O + + + + + + :/icons/fileclose.png:/icons/fileclose.png + + + &Close + + + Ctrl+W + + + + + + :/icons/document-save.png:/icons/document-save.png + + + &Save + + + Ctrl+S + + + + + + :/icons/multisynk.png:/icons/multisynk.png + + + Save and S&ync + + + Ctrl+Y + + + + + + :/icons/list-add.png:/icons/list-add.png + + + &Add Facts.. + + + Add Cards + + + Ctrl+A + + + + + + :/icons/view_text.png:/icons/view_text.png + + + &Edit Facts.. + + + Ctrl+E + + + + + + :/icons/configure.png:/icons/configure.png + + + &Preferences.. + + + Application-wide preferences. + + + Ctrl+P + + + QAction::PreferencesRole + + + + + ...&expression on ALC + + + Ctrl+1 + + + + + ...&meaning on ALC + + + Ctrl+2 + + + + + ...&selection on ALC + + + Ctrl+3 + + + + + ...&word selection on Edict + + + Ctrl+4 + + + + + ...&kanji selection on Edict + + + Ctrl+5 + + + + + + :/icons/kanji.png:/icons/kanji.png + + + &Kanji Statistics + + + + + + :/icons/spreadsheet.png:/icons/spreadsheet.png + + + &Deck Statistics + + + + + + :/icons/go-home.png:/icons/go-home.png + + + &Start Here.. + + + + + + :/icons/kbugbuster.png:/icons/kbugbuster.png + + + &Report Bug.. + + + Open the bug tracker. + + + + + + :/icons/anki.png:/icons/anki.png + + + &About.. + + + QAction::AboutRole + + + + + + :/icons/package_games_card.png:/icons/package_games_card.png + + + &Card Statistics + + + + + + :/icons/contents.png:/icons/contents.png + + + &Deck Properties.. + + + Customize syncing, scheduling, priorities and models. + + + + + + :/icons/document-import.png:/icons/document-import.png + + + &Import.. + + + + + + :/icons/colorscm.png:/icons/colorscm.png + + + &Graphs.. + + + + + + :/icons/document-export.png:/icons/document-export.png + + + Expor&t... + + + + + + :/icons/preferences-desktop-font.png:/icons/preferences-desktop-font.png + + + Disp&lay Properties.. + + + Customize fonts, colours and alignment. + + + + + Open Sa&mple... + + + + + true + + + + :/icons/rating.png:/icons/rating.png + + + &Mark Card + + + + + + :/icons/media-playback-pause.png:/icons/media-playback-pause.png + + + &Suspend fact + + + Stop reviewing this card until it's unsuspended in the editor. + + + + + + :/icons/kpersonalizer.png:/icons/kpersonalizer.png + + + &Model Properties.. + + + Customize card layout, fields, etc. + + + + + Repeat &Question Audio + + + + + Repeat &Answer Audio + + + + + false + + + + :/icons/media-playback-start.png:/icons/media-playback-start.png + + + Repeat las&t audio + + + Ctrl+R + + + + + false + + + + :/icons/edit-undo.png:/icons/edit-undo.png + + + &Undo last answer + + + + + + :/icons/view-pim-news.png:/icons/view-pim-news.png + + + &Forum.. + + + + + + :/icons/document-save-as.png:/icons/document-save-as.png + + + Save &As.. + + + + + + :/icons/sqlitebrowser.png:/icons/sqlitebrowser.png + + + Check Database Integrity + + + + + + :/icons/games-solve.png:/icons/games-solve.png + + + Optimize Database + + + + + + :/icons/khtml_kget.png:/icons/khtml_kget.png + + + Merge Models.. + + + + + + :/icons/text-speak.png:/icons/text-speak.png + + + Check Media Database.. + + + + + + + + diff --git a/designer/modelproperties.ui b/designer/modelproperties.ui new file mode 100644 index 000000000..cd4cb83ec --- /dev/null +++ b/designer/modelproperties.ui @@ -0,0 +1,782 @@ + + ModelProperties + + + Qt::ApplicationModal + + + + 0 + 0 + 544 + 589 + + + + Model Properties + + + + + + 0 + + + + + 0 + 0 + 522 + 515 + + + + Model properties + + + + 6 + + + 9 + + + + + <h1>Model properties</h1> + + + + + + + 0 + + + 6 + + + + + <b>Name</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + <b>Description</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + true + + + + + + + <b>Features<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-f57a8f871fc97ceb2f6daa43528fd640ee63b4f4">?</a></b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + <b>Tags<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-09efbe5fd6809ae0d90543adf92014b8eb9ef1bf">?</a></b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + <b>Card spacing<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-59a81e35b6afb23930005e943068945214d194b3">?</a></b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + Minimum spacing<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-4ee3a58e2fdd61da8ef81984213862d3bc0ed4bd">?</a> + + + true + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Minimum interval multiplier<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-51074b785bb6049b44fce9f18fca198100c4d2f7">?</a> + + + true + + + + + + + + + + + + + + + + + + + 0 + 0 + 522 + 515 + + + + Fields + + + + 6 + + + 9 + + + + + <h1>Fields</h1> + + + + + + + A flashcard is made from a number of fields, like "meaning", "notes", etc. + + + + + + + 6 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 60 + + + + + + + + 6 + + + 0 + + + + + &Add + + + false + + + + + + + &Delete + + + false + + + + + + + Move selected field up + + + Move &Up + + + false + + + + + + + Move selected field down + + + Move Dow&n + + + false + + + + + + + + + + + Field properties + + + + 6 + + + 9 + + + + + 0 + + + 6 + + + + + <b>Unique? + + + + + + + + + + Prevent me from entering the same thing in this field twice + + + + + + + <b>Name</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + <b>Description</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + <b>Required?</b> + + + + + + + Prevent new cards from being added if this field is blank + + + + + + + <b>Features<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-f57a8f871fc97ceb2f6daa43528fd640ee63b4f4">?</a></b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + true + + + + + + + 0 + + + 0 + + + + + + + + + + <b>Numeric?</b> + + + + + + + Sort this field using numeric order instead of string order + + + + + + + + + + + + + + 0 + 0 + 522 + 515 + + + + Cards + + + + 6 + + + 9 + + + + + + 0 + 0 + + + + <h1>Card models</h1> + + + + + + + One or more cards are generated for each piece of information you enter into Anki. Here you can control how many cards are generated, and what they look like. Spacing is the amount of time before showing a different card for the same piece of information. + + + true + + + + + + + 6 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 60 + + + + + + + + 6 + + + 0 + + + + + &Add + + + + + + + + + + + + + + &Delete + + + + + + + Move selected card model up + + + Move &Up + + + + + + + Move selected card model down + + + Move Dow&n + + + + + + + + + + + Edit card + + + + 6 + + + 9 + + + + + 0 + + + 6 + + + + + + 0 + 0 + + + + + 16777215 + 60 + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + + + <b>Description</b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + <b>Question hiding</b> + + + + + + + Hide the question when showing answer + + + + + + + + 0 + 0 + + + + + 16777215 + 60 + + + + Qt::ScrollBarAlwaysOff + + + true + + + false + + + + + + + <b>Answer format<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-b8916ff117aa0da4a414ce9b9b9be4a232eab2f4">?</a></b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 60 + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + + + <b>Name/tag<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-09efbe5fd6809ae0d90543adf92014b8eb9ef1bf">?</a></b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + <b>Question format<a href="http://ichi2.net/anki/wiki/Key_Terms_and_Concepts#head-b8916ff117aa0da4a414ce9b9b9be4a232eab2f4">?</a></b> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + tabWidget + name + tags + description + initialSpacing + spacing + decorators + fieldList + fieldAdd + fieldDelete + fieldUp + fieldDown + fieldName + fieldDescription + fieldUnique + fieldRequired + numeric + fieldFeatures + cardList + cardAdd + cardToggle + cardDelete + cardUp + cardDown + cardName + cardDescription + cardQuestion + cardAnswer + questionInAnswer + buttonBox + + + + + buttonBox + accepted() + ModelProperties + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ModelProperties + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/preferences.ui b/designer/preferences.ui new file mode 100644 index 000000000..f7d0fd9cc --- /dev/null +++ b/designer/preferences.ui @@ -0,0 +1,645 @@ + + Preferences + + + + 0 + 0 + 532 + 531 + + + + Preferences + + + + + + 0 + + + + + 0 + 0 + 510 + 449 + + + + Language, Fonts and Colours + + + + + + 6 + + + 0 + + + + + <h1>Interface language</h1>The language for the user interface: dialogs, menus, etc. + + + false + + + + + + + + 300 + 0 + + + + + + + + + 0 + 0 + + + + <h1>Standard fonts</h1>See 'display properties' for deck specific font preferences. + + + false + + + + + + + 0 + + + 6 + + + + + 300 + + + + + + + Last card + + + + + + + + 170 + 16777215 + + + + + + + + + 170 + 16777215 + + + + + + + + Interface + + + + + + + 300 + + + + + + + Card editor + + + + + + + + 170 + 16777215 + + + + + + + + 6 + + + 20 + + + + + + + + + + 0 + 0 + + + + <h1>Standard colours</h1>These colours are used for all decks. + + + false + + + + + + + 0 + + + 6 + + + + + + + + + + + + Interface colour + + + + + + + + + + + + + + Last card colour + + + + + + + Background colour + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + 0 + 0 + 453 + 443 + + + + Autosave and Synchronisation + + + + 6 + + + 9 + + + + + 6 + + + 0 + + + + + 12 + + + 0 + + + + + <h1>Autosaving</h1>Anki can save your progress automatically. + + + + + + + 0 + + + 6 + + + + + Save after answering + + + true + + + + + + + + + + Save when closing + + + true + + + + + + + cards + + + + + + + Save after adding + + + true + + + + + + + + + + facts + + + + + + + + + <h1>Synchronisation</h1>Synchronisation enables you to access your deck from the web and your mobile phone. You can <a href="http://anki.ichi2.net/">create a free account</a>. + + + true + + + true + + + + + + + 0 + + + 6 + + + + + Password + + + + + + + Username + + + + + + + + + + Sync on close + + + true + + + + + + + QLineEdit::Password + + + + + + + Sync on open + + + true + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + 0 + 453 + 443 + + + + Advanced Settings + + + + + + + + Show toolbar on startup + + + + + + + Use compact answer button style + + + + + + + <h1>Advanced settings</h1> + + + + + + + Tall buttons (for touchscreen) + + + + + + + Start Edit Deck with only current card selected + + + true + + + + + + + Hide next interval when showing answer buttons + + + + + + + Hide interval of last card + + + + + + + Hide question/answer of last card + + + + + + + Show tray icon + + + + + + + Show timer + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + tabWidget + interfaceLang + interfaceFamily + interfaceSize + lastCardFamily + lastCardSize + editFamily + editSize + interfaceColour + lastCardColour + backgroundColour + saveWhenClosing + saveAfterEvery + saveAfterEveryNum + saveAfterAdding + saveAfterAddingNum + syncUser + syncPass + syncOnOpen + syncOnClose + buttonBox + + + + + buttonBox + accepted() + Preferences + accept() + + + 266 + 476 + + + 157 + 274 + + + + + buttonBox + rejected() + Preferences + reject() + + + 334 + 476 + + + 286 + 274 + + + + + saveAfterEvery + toggled(bool) + saveAfterEveryNum + setEnabled(bool) + + + 94 + 157 + + + 235 + 160 + + + + + saveAfterAdding + toggled(bool) + saveAfterAddingNum + setEnabled(bool) + + + 80 + 178 + + + 174 + 176 + + + + + diff --git a/designer/sort.ui b/designer/sort.ui new file mode 100644 index 000000000..efdedb7b4 --- /dev/null +++ b/designer/sort.ui @@ -0,0 +1,97 @@ + + Sort + + + + 0 + 0 + 425 + 358 + + + + Anki + + + + 9 + + + 20 + + + + + Please choose a field to sort by. + + + Qt::AlignCenter + + + true + + + + + + + + 12 + + + + QAbstractItemView::NoEditTriggers + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Sort + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Sort + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/designer/syncdeck.ui b/designer/syncdeck.ui new file mode 100644 index 000000000..9d6ee6732 --- /dev/null +++ b/designer/syncdeck.ui @@ -0,0 +1,97 @@ + + DeckChooser + + + + 0 + 0 + 484 + 320 + + + + Anki + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + <h1>Where should we synchronize to?</h1> + + + Qt::AlignCenter + + + + + + + + 12 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + DeckChooser + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DeckChooser + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ez_setup.py b/ez_setup.py new file mode 100644 index 000000000..38c09c624 --- /dev/null +++ b/ez_setup.py @@ -0,0 +1,228 @@ +#!python +"""Bootstrap setuptools installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from ez_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import sys +DEFAULT_VERSION = "0.6c5" +DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3] + +md5_data = { + 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', + 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', + 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', + 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', + 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', + 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', + 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', + 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', + 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', + 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', + 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', + 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', + 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', + 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', + 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', + 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', + 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', + 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', + 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', + 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', + 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', +} + +import sys, os + +def _validate_md5(egg_name, data): + if egg_name in md5_data: + from md5 import md5 + digest = md5(data).hexdigest() + if digest != md5_data[egg_name]: + print >>sys.stderr, ( + "md5 validation of %s failed! (Possible download problem?)" + % egg_name + ) + sys.exit(2) + return data + + +def use_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, + download_delay=15 +): + """Automatically find/download setuptools and make it available on sys.path + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end with + a '/'). `to_dir` is the directory where setuptools will be downloaded, if + it is not already available. If `download_delay` is specified, it should + be the number of seconds that will be paused before initiating a download, + should one be required. If an older version of setuptools is installed, + this routine will print a message to ``sys.stderr`` and raise SystemExit in + an attempt to abort the calling script. + """ + try: + import setuptools + if setuptools.__version__ == '0.0.1': + print >>sys.stderr, ( + "You have an obsolete version of setuptools installed. Please\n" + "remove it from your system entirely before rerunning this script." + ) + sys.exit(2) + except ImportError: + egg = download_setuptools(version, download_base, to_dir, download_delay) + sys.path.insert(0, egg) + import setuptools; setuptools.bootstrap_install_from = egg + + import pkg_resources + try: + pkg_resources.require("setuptools>="+version) + + except pkg_resources.VersionConflict, e: + # XXX could we install in a subprocess here? + print >>sys.stderr, ( + "The required version of setuptools (>=%s) is not available, and\n" + "can't be installed while this script is running. Please install\n" + " a more recent version first.\n\n(Currently using %r)" + ) % (version, e.args[0]) + sys.exit(2) + +def download_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, + delay = 15 +): + """Download setuptools from a specified location and return its filename + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download attempt. + """ + import urllib2, shutil + egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) + url = download_base + egg_name + saveto = os.path.join(to_dir, egg_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + from distutils import log + if delay: + log.warn(""" +--------------------------------------------------------------------------- +This script requires setuptools version %s to run (even to display +help). I will attempt to download it for you (from +%s), but +you may need to enable firewall access for this script first. +I will start the download in %d seconds. + +(Note: if this machine does not have network access, please obtain the file + + %s + +and place it in this directory before rerunning this script.) +---------------------------------------------------------------------------""", + version, download_base, delay, url + ); from time import sleep; sleep(delay) + log.warn("Downloading %s", url) + src = urllib2.urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = _validate_md5(egg_name, src.read()) + dst = open(saveto,"wb"); dst.write(data) + finally: + if src: src.close() + if dst: dst.close() + return os.path.realpath(saveto) + +def main(argv, version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + + try: + import setuptools + except ImportError: + egg = None + try: + egg = download_setuptools(version, delay=0) + sys.path.insert(0,egg) + from setuptools.command.easy_install import main + return main(list(argv)+[egg]) # we're done here + finally: + if egg and os.path.exists(egg): + os.unlink(egg) + else: + if setuptools.__version__ == '0.0.1': + # tell the user to uninstall obsolete version + use_setuptools(version) + + req = "setuptools>="+version + import pkg_resources + try: + pkg_resources.require(req) + except pkg_resources.VersionConflict: + try: + from setuptools.command.easy_install import main + except ImportError: + from easy_install import main + main(list(argv)+[download_setuptools(delay=0)]) + sys.exit(0) # try to force an exit + else: + if argv: + from setuptools.command.easy_install import main + main(argv) + else: + print "Setuptools version",version,"or greater has been installed." + print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' + + + +def update_md5(filenames): + """Update our built-in md5 registry""" + + import re + from md5 import md5 + + for name in filenames: + base = os.path.basename(name) + f = open(name,'rb') + md5_data[base] = md5(f.read()).hexdigest() + f.close() + + data = [" %r: %r,\n" % it for it in md5_data.items()] + data.sort() + repl = "".join(data) + + import inspect + srcfile = inspect.getsourcefile(sys.modules[__name__]) + f = open(srcfile, 'rb'); src = f.read(); f.close() + + match = re.search("\nmd5_data = {\n([^}]+)}", src) + if not match: + print >>sys.stderr, "Internal error!" + sys.exit(2) + + src = src[:match.start(1)] + repl + src[match.end(1):] + f = open(srcfile,'w') + f.write(src) + f.close() + + +if __name__=='__main__': + if len(sys.argv)>2 and sys.argv[1]=='--md5update': + update_md5(sys.argv[2:]) + else: + main(sys.argv[1:]) + + + + + diff --git a/icons.qrc b/icons.qrc new file mode 100644 index 000000000..972249a54 --- /dev/null +++ b/icons.qrc @@ -0,0 +1,62 @@ + + + icons/document-open-recent.png + icons/khtml_kget.png + icons/edit-find.png + icons/colors.png + icons/anki.png + icons/Anki_Add_Tag.png + icons/Anki_Card.png + icons/Anki_Del_Tag.png + icons/Anki_Fact.png + icons/application-exit.png + icons/colorscm.png + icons/configure.png + icons/contents.png + icons/contents2.png + icons/document-export.png + icons/document-import.png + icons/document-new.png + icons/document-open-recent.png + icons/document-open.png + icons/document-save-as.png + icons/document-save.png + icons/edit-undo.png + icons/edit.png + icons/editdelete.png + icons/fileclose.png + icons/folder_image.png + icons/folder_sound.png + icons/format-stroke-color.png + icons/games-solve.png + icons/go-home.png + icons/help-contents.png + icons/help.png + icons/image.png + icons/kanji.png + icons/kbugbuster.png + icons/kexi.png + icons/kpersonalizer.png + icons/list-add.png + icons/math_matrix.png + icons/math_sqrt.png + icons/media-playback-pause.png + icons/media-playback-start.png + icons/media-playback-stop.png + icons/multisynk.png + icons/package_games_card.png + icons/preferences-desktop-font.png + icons/rating.png + icons/speaker.png + icons/spreadsheet.png + icons/sqlitebrowser.png + icons/system-shutdown.png + icons/tex.png + icons/text-speak.png + icons/text_bold.png + icons/text_italic.png + icons/text_under.png + icons/view-pim-news.png + icons/view_text.png + + diff --git a/icons/Anki_Add_Tag.png b/icons/Anki_Add_Tag.png new file mode 100644 index 0000000000000000000000000000000000000000..221c0a2f909a5c5dfe4c404562cbdf2f118590f5 GIT binary patch literal 1553 zcmV+s2JZQZP)jcNH8&$;= zo{W{0X459&Llgp0Hb@9nY>*HOx`%*-1Y%Q4sv@FDh@w_ht%yn@HC6=;$tF@ zOu7}+0s;u{ZY7mG6|QgB`~FV=nM`dO_!8(Cxcz{Y1_MN-S?zyx3(|i9kjd0K&=-JX zU`K!&Gx5kHX*xPm5d&INjW86F2*av)K7?LZAI3xocV3=76u3Dq}QGMl=8w?H2tp~_tYCY&E@O@A>h^<>=^!GO~ zFwn@39jS^yBwQZY^V44p^Xi+oQ7IzP8W2eQ`sbfx@1DkW01yCH1;}LT`oK;DyOK#q zCKHKvch_O&HVCh6C8N0NSzZ>~cGVLE0Z7bEEHXElCk*3AiFHwsVt|VPGMV~5urslk z)gL~*l_N)5sjZDyLb_hmeWd-+cRo!+a~(3zJFi~lo!5qtVkLlbx(q2A-)Axp#6iE0 z#a#XK&v)?XqYn^<#>I$7&$QFKw}I*DN%HvxJm`zbr039~ z2U%WroILq=ve}hc&@VyZpk`JYG^)dwYsxGQrui6J)b1fKCh! ze)uDR{{C%qGc(ISIe&hfC!cJuq#_JMBqNzhvVCU@X0b|LS}HR3&j}{SXPKOsCZAtq zwYbXs!XnGd9`7B_S@}hzz*g3CA+jhnK{Ceb$|^Ep&NcvC8yU&>6^q9H{o8r@FScAY*1c&)?3x&)DcJ<9DWM{ZuotM1Y)_yOU#fEC)ashL{QG zHv*i=FKGZp}}t|Ze}z$#n{!Jz?=|9bQn5JBAp5<{?!OXyG@&YmhawOBT1i?I12izM3W@i z+LHA4*3r{bM{TXYD?OV>W^MW+fXM#VTceyl{lVq{YfD#G+Dm#0I4W^OQYu=UY1-Oq zkc1gX)$m5Pa%&rWByr+BnB zANS9wx3|6l)KU^tY2Y!Yjz*tUIY9KXE&M-SfF*m00000NkvXXu0mjf D{LSQ9 literal 0 HcmV?d00001 diff --git a/icons/Anki_Card.png b/icons/Anki_Card.png new file mode 100644 index 0000000000000000000000000000000000000000..9df8f509b1587a295eda81a3d41e77dd71ee5c6e GIT binary patch literal 1872 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 P00000NkvXXu0mjfLfTKu literal 0 HcmV?d00001 diff --git a/icons/Anki_Fact.png b/icons/Anki_Fact.png new file mode 100644 index 0000000000000000000000000000000000000000..1870c5a7ec65f81b00c45324106ad8f1f4397f9a GIT binary patch literal 2110 zcmV-E2*LM>P)7$hx%J_r?!Q{p;tnkI1`b`s~> z_uBV$?mg$8$KHD}sEQDAo10R8X-~G6{`Kv(mbUgLW=5}aN`)=j^Y1mWx?4!=E~l8? z4FN1~^2x4iyrc4X7xfr+hl@e3u98!8Cf76Jg2 zV$-g)O!HdZtY5ehx35=!kscm=-dnDy{~180l6%Rb-;6%EXL59UME3P%#Lkfs^cQlJ zO(bCn3*9h4%lC0{`35hqHq7G5YwF4yr_LMAPpNJ&_n85NWj#@v*!IH2BX>D>Ol*-4 z?A}FNN_i3lXjae$ATUS}Bnc1%>g^7nSXktl_b;jskDiJe3(HR%rC+{n06U)^O%${L zoch6igo4faB1|2Dr zptI%ch*gBpj}iI-nOGws1VqZh*wzAmbZ8oa2nhpPUvILnHDUmf0VPYe96Y#l@9LZL z&jNVvrZ#U_{$lot!uWucX`)z%h{Fg76s%B_qZlbo$U>sucJSoa@5S-6S74aYsJ9Wg zUFtMFblM)eQ2>^jvfKNlUCchwQwyBFLMbtr9~DVqNl8da!eBB5b^mB4%X^FbP1yat3mvPG6xE)mx2E1yCLTp8TD|kdnzF z;9HYp`0;}WD2QUTye^t;A8xw~uN&gvUE_FXb^%}7+>i2F6B)}!LE4BS4K^`MY@mrj zf+#tVgTGY02f$xG6@X*4Ke;K-Obo3R{dw*J+@^;g9-PM3fdXz+8vrv7?VEtx_A$L{ z6uC?iK^Wty?;gU^$~xY=a23ZdT_qhytPMyIb7C;Z9IO4e1OQMJNt;+3gn>ajj&b7L zA{G}{@%Vjv@QwX>S^QjvwOHw`TF~naj{x!!WT{jBw2b1f9GkfXt+4f@&K&>SN1hij)Rv=D-jIQH-ra zeW=!4q!KnBxPJ-*{dpAfX?$@0I(~ojIL<7tpb>^>o0tO_C_!K)IaupJX3{e^o%g>t zVz;vt*4&1PnT`4=LV6lkFo3UOM9k0(s0fH!k&f2gV;kk#5Wq^Oo>}|*LQVP5Y%G?x z#6Vvj2ltG_?fCfhD{te~w`TFq`{yydseq>2M!*^|0oo!q(t;5dj1jCuq1WHNShLcp zXEw^U;Z+>B+7%(?)3v#kKREYp6LErQbaN4N3rqOzoA0sP@JRsh&Ifq(&@T9a4`mTd zQZSPQSp)+MDzdn7X4ORX&eKMzO3wfQV3ax{r1fHMYw<}Zk+)wvb%oyg@CuvAz(g@8 zEsMw|RB8=$!w@C`7AYGR5flSD6x^+JmHWZf7md$e>*qh^c6QOeNO;EwH8t$@!0a~ zr!TZ?q3TA0BSGMl1g9)Ej=*}yL!d&y5g#Ro4h#4bzN#Uo8);O4G#~~^z=0SzW{<-t%ggN zE@5qLjV;UKL?XedREkrn6f-l=&CQwF*;xh0GfWHd-@yxJDBJv#bWWf$;rvg)YKIE`}>j4=OKhZxm?E4 zqes2VmoNW}nSa@{DKp>J%IEWa0GmjL`cm&*;@uHS7{;6IhgWOhm^ o|L8c5+pFzWrM6tO^n$IZibS~xy8^TXDg}vbjg1loC!`5&>?Xk!NgQIz zNUvrzK05C+zxTs)EcO`Z&Yg=JV-m&~#~8zQ(^}JN-xvenYps_yH#f@@6BDM@`>ov^ zpFe-z)mo1kV=goUXsrQW>$vGTUu(T+jJeg@+xyA2Yu8L$0ApiguGadRZQEzNySqg= z97ZX%{dBK8ImQ@#-=|b6Q7jgzR4RWKLi}uee0;gtmudwdgcu8l!)KGpq;MQ(FPV4! z&2bz;p%Bq%lxNSLec=24ec(5*_}cFL^5x5MrPPH~DkWmE7@<&zP$-0L+bE@Wn3Qsd zg~MUe=`jLTmkswMxA1?r!$)-%qhv zq*|?V`t)fA4j<;P*RL}-H-|9>*L86m=jfgQN+}0$B9TZ-$BlKpUJv#h$KityFY@nl z9Y<*XS*%e&1PT~q8cIgn0l*j&oPN9G###swtk>4oSSfqlnypdwHAPSJqhnD91_qd( zo(`T@YG=xcRsc#VY}V9Mx#U`5h|4m<#HLVHJ^X>8Ka}4WHOmR!3J#G0AKZkAk&*O-WY?FlBuaF z!r?HTot-2S36jYqN+~Lp3KJ6(luD%_pBm2J7@(m99%$Z9JyRB=!~A{F*i5I!omWn zREmLt0b;RO0P$8K7#<#Gu`2jisYZUa#-}-tOP4M&J3AXxM4?dN-o1Ogc=3XTg#`+Q z0;yDrbUOWJ07@w=%OaUf($&?)@bED2eSL`Qa~rJJ3=!MnSNB)YC*!Edw-MIX<>KOZ zqqwfi)vH%A#<01$Nj{$^pU-b~%yt1B$Kk|@6P!AA3gLF}WkK_&nGL>p?$I5VgoH)4 zX87e7t9ZVl>TAyQ@8jHnV0wCbhk`3BD?EDisC|G)B*M_p5Gz)UzkONZ`O6w!%^-#0 zorqwcZQ*^Tgy$QM?sxh42VIQ+{V!xPnV@qv0Hl)tw1Y)4Pc{O zE)$Ezf-Y#=Hmx96x?Mi0A6+Dq&?&UHdPe-@8XHmqTmK?9L3o^dBm6u8-JkQJS zP`XtG0?zXC@(=s^`mA_7ju3*Cl@*>oeaeFe4=5Ikfdk93&{|V2mwEZ}C6;CFu+`O7 zZr;3E^*ry_z(4i^0LWGr4Upy;pjxex&1SiC=MIHJ z;ZxvapxV~QDc}NQzy%-yIK27rfkogJ@Cne~$LY2ae@lQRpxnwi!2bXz^3A?ad&T1b O0000p00006VoOIv0RI600RN!9r;`8x25m`1 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 literal 0 HcmV?d00001 diff --git a/icons/configure.png b/icons/configure.png new file mode 100644 index 0000000000000000000000000000000000000000..17905c7348fdf46c4055a3e5261cfd4d7796f0cc GIT binary patch literal 3355 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 literal 0 HcmV?d00001 diff --git a/icons/contents2.png b/icons/contents2.png new file mode 100644 index 0000000000000000000000000000000000000000..6b00ccd58153d382588ea9d2d09523df79425a64 GIT binary patch literal 2157 zcmW-je>jx+7Qo+mXUq&N;1FlBR>oMR>P*NRLn><3^RA^ALo0X^T+v~^Eu}^=Q$<3y@6I1 z_7(sDSOo_Kgqt*dMTmIQn{o2wyh-7bxO=z&@c8NIzDSnIre^Mp%={*epP3z(o(OOg z(&7{8!AIkg62lYY5>6bypST?W2*%(5?*5$T@k>UdFP6w;u~y=M9qx$Hr5E@ znh6oRBWtZa;gk|)Pe}=@MtyU{DWw{`kcIHy@R0<5qVCgc)tO@PGf3+L(@{8XT}9g* zJ4XC5_HM9KN(pwF(yz zEALc5TmE&{*R95i%+ZjMDU?mkV#Hi8PcVns%~FeGaAReaO%i#{B>zE0ab(wEKk@M= zS_t}fRz`*ACsBU7Idi*rUZ_|xJpJeGgN@WzL#$$NVEPCdFyXD=&E@)F5~w|H&VFN1 zN>c$8l1{cvyq5!W7U9Z5vmT?Cpb%0m!`Wrr9zB`2C!GLd-BGqtF-vOLjdMp(FdSQ z7E9fcn}kb==bQW~DwX7oTd1@%k&y@Xm*s<(A^NR9WH7i$dc@{)&E*NPl-(4azkJ9-?E){EWN?(+#nY6^J_DKStJwvPfwZ4+phO6H7Et}<4mHO@APZ@*!#ai-Q3=w0SU7|>}w{tj;*jWSs zN4B)EhQYK~?T2;d>6^D$s)oL*$E#Q@u4niaTrZ#AEoF0a7b!hPJ>i?5D~P^>f6dS+ z8EMl@s3^i&33)tT5P&&9R9yk#>$3QC@y}*9Lr9-MwIXDGp1CMEr}ePa)!UBXy86rndILD^L<1H zD`?d^L=PU^WC}0Da?G_0eb!Juhctfpu!DP=3IgFjAum3(ve$mQIPG z3*YMCR@BR2W*3yYJ@r_il*MJ3EjUd+$QI{&w(E4I6C^1kZ*kE<^`hLD8q;A=();_m zB=lYdvZ1q6I&ps=l2Pt65h8vWC^Q#6C(}{sHuT(r@{dLt#OZjAZAmd07O@cD3m+j# zEEnuJ`FV!$U|0>6zsgO@nOh`0HL{eBP?NJGr+pJDjkS*0Gdha*zPq$aBAZrmR-o7&4-Er;%2(TayFV)leh)P@`AN zu=xgqOyiI|;UVeVmJ&+(^ZjuQjvPUd0?p1Y%yOS!my&p7%omExlhwYwD4rO6AIn*! zs9tA}zv?0I{ywdi83|~J$I`OVsAypPxAd1RJ_Z!6s>?3JoxdJ)+T(sVUGt8>X6|Gk zLgM(ja$M?WO!GK~{L=o$Ql%gi9b3QB*66dS_KikMsFhFdKUWBC!T}gOAd3BnY+yA1 R$8;|M!8`W`{N{%g{2v|(zt8{x literal 0 HcmV?d00001 diff --git a/icons/document-export.png b/icons/document-export.png new file mode 100644 index 0000000000000000000000000000000000000000..999a415b4e61ce8b3c91edad6ab587ad67c5f7ed GIT binary patch literal 1291 zcmcK4jWe5f902gg+sQK4l+2adkg3&CU7M~~Rof7q?8+j-qz&RVTB9VXFjkRX-`sRo zb{#PnM%7D%B}FB@ASl*)>ot{ljq?^%BPzsO_Dg#E6ZX09@B4eX=XriF&vVaRT+p|c za632zL6+D63?9^u`T{cn8B>|p11b}mFO~px5w)z+x_u$)spO-+10Umy^)w6wGWPxORBp-3bWi^c8j?HwJR z1`nLnGcz-@v$J#a3-b$$ z3yUC>i%JknN?>V8xwO0lEWfw1yaM9Q3h>5Y)!?mx%3y6xwWeMJz*d2EgLeiS2H^684I66Kk1cHo=jZI9=%+28zmWYEN+t}J2 z{=~rv<$T2Db5}R_V<*sGp7uQVl`rNz_PZcF@k&+XQ zSJaHdBbLXWdCEB9SSxNQyB=OeNKU#U$F4gCo}|6qm_##Nh+Ff{e)E~DrG-zd+nVm^ zs%h@#Pg_rryD`tnh(nHW&Nd2uXnzA2$_YS`NSIu2jS+g*zwFwj^v6g+s!P9{>N>42 z`=PI6=*^hoio}sNAuD*N6PC%Z#h2%YWrn+6gru!ueP-M{Hwto02R6mOGBZO%P+yoG z4Yg14cuLEl&cy_pBJNt%pztK&_NN(V8Oq%1e+GNpNw;>Tw3-+7XpQEx>G*UxXKo=@ zf%+z|UnscjWpR%L_bE=0L{bWNvlUEHa6+Ljk?Duh$=9vpdWcz+7lY&=wjBoPX;jzC zQ>D#l>-vZ$7K5&8Dt~$6+Vn=I_@l&PAKZZ7pDW%jB2V@ysLO^LdB$i<-3Ye`l=Pxx zUzg2EkF+-@SEdO&ekthXdWv7ul_@7#uo0pMB2(_Aj2y$}=rG8*cw5T-z4DT%QTgEu zW=e!VI&t&$OX=-B=Z2UXwP(7F!+R;!@b0P9*|M(u`}BqlrK;EI{6g44c7Fck*)xCt z0p~l73L|uY+~Y}jH=TQx mcSu`j$BhrW;^IWL=$!-h3Q@L$Qv0L+M}zeb!Zi4jS^omhw~#IX literal 0 HcmV?d00001 diff --git a/icons/document-import.png b/icons/document-import.png new file mode 100644 index 0000000000000000000000000000000000000000..6680f2416ae06f6cc0206c059f143d52282f4767 GIT binary patch literal 1244 zcmc)Ki8C8m7y$4HDvnIG)VWsNwzb8^>R7rhla$yxik-TmqC=)*yKc#p23e}K-CZ4w zYqmCmQI(;MuC6K~!B)hXilCB66C&cOT5;`5`bX?<-uJzE-+VJ~=FK;+BsAC`u4kbK zfk5Dx08|**5vqV`f{db;4uVZH!w(YygTdy6Ap&sJO$~_30M%?&Xx!zJ*`R2iiH^(+ zPf5xo;L;Nz1Onmwt)G)KZsJlC&!?p0YZX2g5C~K!^lAheG#?)ygZ#gUh>E%%8y$(o zM#tTViI0!P#gj-R96l)lkI&D~FDNK@$DbEVm$$aI^78T~x$k&$^Pr);yu7-)x~8V) zNxce%LZMQrjg5^>O-;?s%`Gi0Z8Ta3@ccQQPVapA@~^J0?(XiMo}S*`-u`|DgE26` zWHFgc7BHwbG&D3k{1zA)VYAtzqnuF=XABr0AD@_*n4FxNnwp-T=5o0+yw7HNvnqU* zIbdF80eJs@5fBIjLZMJ35-lw)iN)d%fJE{^B9%y&rORNgEU(B`0GVt>wkng!R#oJy zalyvor2<+bW&ix zxdLHWCn~!12)(nHj$$h8jql}~dYaN?q+ADEPR9kZULBM+BXYpRZk3d>F1&C`y?rt6 zt=0+CIw;OFDyNU4%-(-pt6{LgGpPFE2JNy-p5LCMVIqs+QD1?ztF#=kcRlMUdZ-*; zadK{G!$sKZv0=rTHu?!`9U;!*;92<@^3^BZ=D#9GUOg4s85Q`O{SFtu+6e8r{$sn6 z*uwO<<{Pu$s)fB|d@#lewNb zXJ&WYI2k>eOb(&+gT>zaaG3vJYwdMLlTz|uB6`I#D%g!d3nrB~DMMqNBp0ZnOKy?_ zP(XtMLWp{xo`v8?V>i1fXI*P;t82rH8)&F#00D#3gNyne^fg=_y!@UxEPmiu+U55$ zV=zk!t3?&zz8ut zHqODVA%s4`Mn$I zmG~F?d8mh2;s&=9;)!wcB$E%wl0cUb11iB;X}r0{5c8V=kP=O5kOc&`aSFf%`0*BL0rm{2%jv-5^MDU% zgzDphfExlHFo)rn9TI>ydju6h7)isJ4$dYZ2{=hCqyj2lqHwzP8S#v%It=;M6gJhy9Zqi1zoP8V*|P5o#+8)F;k*x7LmxCF#yj*^#<65_>7 ztXZ>$#>Ph4?zIsLg^0)FszN@WkGfzT&CShpZ0Yz5h{_xQ#amE_StcwzYe@YQ*4EY% z2n1NQYSnY^IGs*hE*F527cXwWnmLpbwLVkT$cd@U?J(yQD2kW9qzNl3*}@C+7_bV% ze4-Zdf@n6IeQpI14yQc>!ZMDtC}Av<$tcluI*nl%PtGa;;%PGmB<)tfwrHN5kP_8( zT}?nF63MLz^?MbSRmPY5J9~isk_MFc9D*fKS(~OwDwQG}9%f`@7>~!x(we0>9ZuP~ zZ{P9LU!VF+N|`NXps0B178DHRGFMp-4Gpn={W>xkQ<>R&pzHKECysxl#BE(dn0Mx_ z+2_kYK0bll?U@lj&~xP6$=;*y=4apb6xc`IdR6{dEXMToG{InSM!ffMce_R0UInn% z3QG&pWC54kB|E$JpZ@CTv0|CqA48kwR0VRO${whxGe6(A|Jb*x=Xr7a2GnO%O@gK8 zr9L{s;Q)t*;c&+ec71W=yHo16%Y}T3LpcNMSvU!x-y&|;fXXHWg!mu%7iW_sYDk^+ QD*ylh07*qoM6N<$f;iQC6#xJL literal 0 HcmV?d00001 diff --git a/icons/document-open-recent.png b/icons/document-open-recent.png new file mode 100644 index 0000000000000000000000000000000000000000..2d16f792842936be53dd028b02e6376f10531fae GIT binary patch literal 1663 zcmV-_27vjAP)IcN-BYV4Lf@r(D+sArJ@}pT_S+*F~t>9^c*fH}=2!F86Ht7(4D5 zz%Gmekm$daqvx-3;@l-3`uuvHed56#)TWQM5^z_!TzREhts*pm5KE#dV3CM&-R1sW zf8niJ#djX~H1}-oW^LBQ)IvfP;z*b(d%W`Yd+d7tb*husUT5~L?TkD(51>aSAa_1;Cm^(_s$5E{MFNo(v5`EPltsx(_5&$Ba*DJO z5QZV1=Mh>KXj-G)6+s|zJ&(FwXJljqUDvTJ3n2ua=izxCnh+3+foTB5^~mYeg1i~3 zk%l!LCKWHBFz2vowT_e$83X|Q;e{h4;&HmW*V5J5MJkoTFfCqudyc9nL1!8WTh~A< zH-tdbG~)3%o7cuDJ07RUW~n+7LePXp+={bi)f(2U?k1DYVp$ftZcz6_UcER=#dX0g zjsmXMYLJT-#n>#!bUMwB^#*}1I66AXnM*}v2!>&jNoVNj>?D`VkxV8rO%sLS^w=y% z-<-tP1-x_e&p<8G0d7LxbRd;Vv2{}~Te_;`UB%B&{EJf;OL$?7L?THhohFe?Vwfg& z056YD^VCb1D0mV|m;cU%7ta9omPOc3gkcyA3=HtiTmH?pF^>!NI6pjkg?oo`e0HdV z{;njkSiod?p4Z+fa_nM}JUS{U7C8LlryCoRzicyd9Osnhc?-6{c zv;D5m(Ruznr!Jo3OlmE8Iv6JbKw7wL3TN0FjB@wK*KxmH{wFXF1d*GOh?<5GZQ8t= zrpdZ>>&Rxa3=Li73sLkT0rh&Fnb{fM zo1W%NJ9pyyK85i@c;LYAf3kb`&wjm>96(UM;qu*;q{yzz5<$|VZ7=|Ga z9z5`)N7|6L?Sc=2j;=2E*?qrx?5Usbe!3lb6t+nzL*MtqAP5#hN*PKiL#5QRkV=KN z>xI+Bx$xY`Xz{CGd*~ZL{QbPiF2+FP1yy`N1FDTacldw7e*i{)&4IOA@09=m002ov JPDHLkV1kd27n}e9 literal 0 HcmV?d00001 diff --git a/icons/document-open.png b/icons/document-open.png new file mode 100644 index 0000000000000000000000000000000000000000..7422ad3338bb73f8a30502c3eb830be527c06c98 GIT binary patch literal 1088 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 literal 0 HcmV?d00001 diff --git a/icons/edit-find.png b/icons/edit-find.png new file mode 100644 index 0000000000000000000000000000000000000000..1e2b2e0b321e25a0309982896ae15300670bf336 GIT binary patch literal 1512 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 Me@j>1_&}}yALD7)0RR91 literal 0 HcmV?d00001 diff --git a/icons/editdelete.png b/icons/editdelete.png new file mode 100644 index 0000000000000000000000000000000000000000..945d221eeaa3cf0ffabbf288d7da7541adf05ddf GIT binary patch literal 1368 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 literal 0 HcmV?d00001 diff --git a/icons/folder_image.png b/icons/folder_image.png new file mode 100644 index 0000000000000000000000000000000000000000..a856a401cdba8e10e9dd9c27a0343b45d2b15ca5 GIT binary patch literal 2586 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 Md!N@G4`$x~0mCvE7ytkO literal 0 HcmV?d00001 diff --git a/icons/format-stroke-color.png b/icons/format-stroke-color.png new file mode 100644 index 0000000000000000000000000000000000000000..05c34b9a4b9cfe2d23f50de00081ad58f2ddc9c9 GIT binary patch literal 1703 zcmV;Y23YxtP)$eLt_i-|xK=ilT^6C}i2$*;NukdcfQCzUhL<<#M*azyGC~GiMI7 zSgbAx41+hPO+YLbw>mjFJ@)hS)B5`QK6iC>?ccIx%W!UPZsfEGSXo(hI668Ga=F~0 zgoFg@*s){Uwzf7ZDJkhULI``@!I4)N5Pti+B%*yI3BBaZT#F!$@d8p`DIj$fp`^yz zXaR)J1^2SDvL3Xywo={Q-Bdfq9o1w0}&bmP7%R14FK)Ov2~)?1Vj~!a1Dco zV4^fPN`U?)kw~O!wVLYe>>Lq^L=?(FLFxvjHWWfjfbQNi=D6 zGg^SAprBw^OiT>L%srcN}@J56c~rujvbYAgM%M0k;_N8 zzP?&o!h_5Gn;zA`z`zT*ILp`$N9aMIVN!_Rm{fq&)p@ZtZ@wUwNJb)s!Xdg0v;>F4 z>57Vq(&DMvY^*>LsgsWvQ0&RJyFrp(n6J$l?~`6*lhL>adC0a9UL5l|KrJr z%d6%AZ!p#i`tW$5(eW8r#E>|sR4TQ2U)_)IgxcU_6~u zQd0iF-4B4V$#8RX>xaZMFE6jN^w=38Ae~HLwSj=l@B)ws%g)Xo!YKCZ`@zM<=fX0CRMwG$>3_#?r{&78L4(Dz#&uKV2J$BCo0%<^FfS49A0VfOu zju_bwvEV7*6TJ}7E=}c+eyNJ0YPY98A%x5@5J;FnAV*(|Lk5 zdS(sP)+D07Ivh`>Cxve_5SR;&GQy7lhZhIX%?HFFXsVsOEJt>r3116|iK6({PdFWW>8-4_J^ijfQIYIsj|0Y zYdN!Ro=hrma!|hX_&~m*sdts4_GXo0_vP)1t>^aSuT$49uQ>E^Vd389;+*ZDm!wyn zU6EA&O=)7`#g!R~FUs=s)a4a9hs!r+9w^_DvAeuhwzd3dT26EPv|7A&K^smXKv0VwvMO~u2#kbzdOu02b zE&X;xN@jOhLSA=>u%IU(rlcnzs5K_0%za|?18N=&{B6@Gv@b)e*qV$zSuu2B98z7002ovPDHLkV1iIJ8(#nb literal 0 HcmV?d00001 diff --git a/icons/games-solve.png b/icons/games-solve.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed4c865411eff156028fea302bd030be8bb1b9f GIT binary patch literal 2720 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 literal 0 HcmV?d00001 diff --git a/icons/help.png b/icons/help.png new file mode 100644 index 0000000000000000000000000000000000000000..93bf094a114d46d3f2c0236badc2914c04fd2e89 GIT binary patch literal 1587 zcmV-32F&@1P)BgLpAOuSSlFw|J$wBwB zEXyO%Z`_%%4B#waB8ay-tqzWWd4SvFRzgTDDY$$xZRQ5@Kp*f0-~wQ%3Pi&Y^uviq z^F+fz0Hi4M-d!EL1jRD(a+NqRfAg?)X*y#rd@cfOk6VV2ECO*&g;*HAd$LM=N@e;9 zAE38r2lxrFo)LVwCV1!8F4xvZ4dF7sJZilH%+Bm|_lDL3JYEMk^bpKGUE%wuG)8nk z0GU0(+J;TV6$q&`?Vf#7XZD23?1@p{{=nsrH%ff66^jeO+6}{+J(V*PeF?O@x$(qd zhaY_1Pkbr>z{ZZt){Xwz z_e2MQ2DlN0b8|tC468U*NM$P&iw@uU+ECB)Y>>LToM+;w&Y`Hv>u1KO$rM*^j~T!s zFx_gSb_uw+9)j1NcZr5n3JzrRqA&PCBKJ4*6dZ7C3TK`>%<*v>A&I|t$Xe)1p#G^{ zP!)J?s>0;BhEoNn3U?k9JHR)yaBT(NS%K@TkT129_|{kTrhPQ5^75$wejf}SC#E#=4ow*!ZgUh)1#Tu`x4!;a0sfw-P%ODkop1okmHj=Aj%Ymlr96Nk zBxk!3FjWPnC#ryoU7{&Le>;JX*J)d~OP&YTx1BaUzviv2ZPobKs*4^Aps64PW?K-h zK4wiKh=)Tk6jLdd+udo00?p^L3!iR6)os_=-AQx&^s%PF{zyY@D(eA8%~`}cnZpKSU&0yGu|vLGe< z1R8}Uq}3?be3WaQk^S*{k5pRO=11QfB^uiQqV2Ne*H^1RutPuS2Pr{#m`hq~Q86Ml zie=xxl`&Ah$B$n<)&c&D-&a`A1-sgW10I&w1}I((hWs7i0!gYnhaVM)MzRVb9J4`=Sx zeubwdB5iBGe5=OYOaQIV36`sX6aY)$t9t;FjRfP?z^BRU5LT9sr5C%DH4)HIxFY zEQs4B7bDicQ~TZfF*fp0EO|8{FhFy+nRRi!1ZH(B$CrWm^&QnXGTeVNyYLxCz)wiJ zj1TE#?fwk@v!MVoSI26v1t-&HPDm~lD=OLD0^tBX#V7Lo?1z3{oY8st8^iqcRShG` zLv6M)d7QEY#HG2)xm?$Npx!_xK6((NRLtVn=q`%O<45(j_M;&0y8^iPDo~@ zBzNWZBVjWK9Xl z;@c}@@x%9R&U~f7=;&BeXU~Seez0%Tk`&3L9`NUnhDaA40Yst~_(Sgn_3m~0UENwp z6(bjZb0cCL)AP(s>@qeSBoqu0jd^iUEaq_>hpkMB8}|%0_KpDLyns9Z`7S1n@j~8Q z_-D+z2%KHt)y?%=dc0mvTMA1IWTcVd65e+%F86xt|BC>?w+m(txCmTa411ST;B>R9 lHFG=3Mur=7C|~sT{C|>@vbt?~LxunV002ovPDHLkV1n+Z^l$(G literal 0 HcmV?d00001 diff --git a/icons/image.png b/icons/image.png new file mode 100644 index 0000000000000000000000000000000000000000..d6a9bc31b67b60782d3bf612dc2827e9efe379a4 GIT binary patch literal 1892 zcmXAqc~n!^7RJxba1+8!h=P*HU?K>-Di8>jAZii@2_l+;R<0_jr*15`mlkOBg^4X=Of^R0E(+G~C5_wBRK7t$R8 zblNH!0DvAS@e463XQ`;L@l46gd~Xz!BuRJ*0H)?k1%Yoav5c3bSaGlzfO}`M=07>N z)Nx1>5)44*S^zKw0CTueUjcBG2f%9-fQ=;pSSEgXuyHE@aBrZWcvpIFXJwJV z+|KS;L0__DMf+KlfPqMHvZt>1WgUC_?h;nIfXzN!H`~r>Xm6O%w@sK`$ zbTFG`aC7JH8}B-V=JuK}MeCbgi1C>V!>&Yg^T#|Dx4RFIoTVUXF{;<4B86?;B6(%( zp$O7RzR$O0KYsm7m~;W{APw>5aYeu2+Yy|7LG=k00#Jk8dM9J!Z3M?^7fl;D{&l!J zUvmmKJwkP&^N7KoyN5~MNbzyeYwav9%w^@n1#Y4t(Z3Bj?a?tY5&a`kaBB__fqNWB z7Zb?74}EPgi7IVgc5nG7)%0SWRXyru;QzWpN80~gd!y7hiP*+9(>r!cfF__;chFUFuisqf*datx#Fas81 z`}KO#H;qWQ8391KgV<`}ao&TBsl0)OxhcfbWgoaPEJP+JClLmNfu2xV8QwnkhEr2h zD|X^nXDy2J4T`uVHPQS{T^*9x_j%Ogt}f*$1Yy8Ora5ip$;wqK*&ZGqtS3*OYV&fe z-_6e?^5`Z$`L5zfPJm1%%kib^*Gcm+xuv~f%T4mwbbNu8;Kk?j6(uDs#mSR2osMG0G8jJm#zY%*{f9Iy znnuFn+jL6-~R7sq9>9k-a;gMw>C8EsL~1du|I@gXghvq zTkloJDZW~vs_=IS6#)*W-Uw`TS%e@*j+<0Iq#umv9{|J4hZB*$9sx@rsN*N4$h3-8 z*BQI+Vx8oCI?XFBEzN=gNX(N&G{eS5=L62Si~hB^I6s42c>y7Ns-8#Aen^6eO$W(v z@WUzO2e%H*{dWa97cW{R*gD-8y`p@Ye>K3E>%b|(kyXQ3P1?A-L?XfHlofZ%qKA;~ zyZfSMk^9YF4#>U{+(4IsipP)=IK9E1=&Ie_m)_TVzx=ntQL0S~2*+_;(UpG}~yP5Xz%Rvo+;`LrsrZ2E` z)x?waqLOhjmz+$>6qAYe5O zj%}UKzKhi*Tl9orpk9)z@*}pUa!k>LsMfPKxtCH80cqQdQ>;zXpLbYb5bZO?z%F1J=V`c(KnJCu- z0`=Ps`jL_HigvSVYViS42cdGmcnhWMVoOWQkLMhMlVRF^`Av!uYr=oGY=drdiOHl2 z8DU$=^z<~RbwAc=dG;xR#5BE=DO6vxD&G9r-n}n&T-o%D{XChu6h?w}M5w97B?({ok?RU|ts zr1tX0qqpp&f4bM+Con}Q1%%ep)Mc-!r#u~`;}_~l)H6ghKHWmHpIaOT5fG5Y{jKiU T<$F!W9}xul@9?|pgUbH~wOkdb literal 0 HcmV?d00001 diff --git a/icons/kanji.png b/icons/kanji.png new file mode 100644 index 0000000000000000000000000000000000000000..77c5a26f24a21a1a2b896168a43a5bafb321479e GIT binary patch literal 1495 zcmV;|1t|K7P)G;J*9L zJ@?%6zvrBL?uGxE6oA&&){%{sl|Ls^nxx(@wO zXsduf!r&cWTtElXi%mB)%`l!`W?qhy93?Q(AXzWlGBO)rJuol`?hPRqG zsVD|sS>zS7nl50qnK9Wi#(%Oy(~qDC{Js&ql?yg3i)oM+Na+iUh%SgM#C+jj&w5Kl za9D&@b_Iu3!J!ZmwOs zR{YsFY%b3O>(;GfXlRIw7cX-7@L{6SXjb;<(IaYVY8V+A0pRJ=r>tMUKFb4CR#vil z_39;!i!A1kky=|@--v2z9- zA3r{_BmiC4B^Hb2DZ1Tm86F(GRn3p<7m^9U z!Gj0cy?ZyEot=3T%gV~|cszJK9!g6~*|1>)9*+l?%T=^dBiOuC(ACw&z`#JBQXmju z)22-Tgu`LpzJ1Htvu6t)*KY#gcDr%A-KMdsswz4v%gecY_b!c%jbHD9 z-12+={CR9P8}04wjE#+%ls|s_NGg>wjThsAWh)^-Bog7)ty`QqbB16r$gyL`va-p^ zN&5QwXliO=#flXsy}`jjcI?=(Y$XJ+TCK|ixO(*}RaI4tj*fEq@@11O8jVs~T1s7A z9Zsi{l`B`8B1ohhr{&t_HysuJySm1xpN1v*GqkUJzlRDfQpI=o;`a;U0oe6my7oHcIxZv zDK9T46bkY1;X@LM1iG%HswyQVC5w+zOQv@GhpMHe@~)`;Df%oxp{Q+=FKi+fp9vVR8&;3cI{e9N=itjQbeOs x!r?IU^YhD=*?2t84Q?BA8iq7DN{gO5vCY)0y>mQ z)D02iOs2%mq0@yaS+dQM83P$QBb#m-W`fz&jYNUCI3FzX z=*#-C(Jf$XtC=xRY;5cX^rhh>ATbyW>r^V0KZ~)EWeP>PiLp|f&6cdw>0Bv{&7IEJ z!VHzlhDc3C1Xf{AL^uhk1wrV<;st!CGHJw~(n8}&hdt_Fk6`L9&QYGkcmp7{+wIF8 z4##dT+OQEY8jZ0&pRWyznwqxf9sX#ga3?EZC51vxYL%Q)y-~D!p}2cfjqojO9hsS# z?VNPj2t-9iMSH#8wOi}#2YS1P)23P@Ii1cBqNAfpuh-L(A~F5(i_)adB~>F%%wd*oQUYtb|gCudG6$-d<`l_ZC=fRG22vl&oPi(wj&l zye`TQIw?2FP6b9Iy{0$bFPAAl0a^D`6X0Ql^NI;--SxvFO>ZX?)8CkKBVJ9vADT*I zGXpdx7@*eAUZ8*Mdb-LO-izooQ}&LHvYy6q%y`ss9L#wn&izD-#d5P%q`iC|3txiy zHQ+jOp6B;dYHBKFWMoh#+8aUw-B78i3-2$=6trKYIhT@>asq3lJP`pNa+J}@C@|G- zxBJs~WV(&Z9PzZtV!`g<9J#)`>@9kuq=b@^k_dvM;b?EArPIx5dx&78+fBP-T@Nax z+Ep4bO6YHRr36CVeL(UyTRPXUK4=8HXrc*eAG|uIsKP3|C76iJ#b0>9d*+Nqi zJv1{gifXemsb_2!-F6CeS|+FD`7gkc!bLQ0uYo2RtQ%MgZb*@zVQ{ zkiODn{>$U>P!`mFh38+braSosbY;Q#h4+f|+zHE}pK6M3fF&6fXMC zGyK=STNFP#V0%&#T35vjx1Ym;U*eo!hIyA*LN^_;bSZHdH76v{nVcNz$$p-?l6}+- zZEzuO07(InYEAK9#2UsSpMo^TTtB+qy=NmLs5LT@@^Q@K%x0QtFj5=Z-DV4Q_)}>+ zLLY;EItXyP2y>H2TjQL35e5y zQjAI&dY6Er)QM#~VRI@wyT`_u`DyBap=CtcwKk208X-Vl^C417M2MG4rL+sv&!IX- zqYt5l-r0Z)7(WZ$v;~44T!c?UK-u8)9ci}P=^#uO!7JqYc_SkuF?j{zl|TW}&O1kVrL zxz%drYlw$JK#%fy1g)Zo$;$l>M<~xZ*x@=%n-Ax&Si?`*K{(iH{QjQJ?d{z-<5t$K(7HGLRkr^Gxv{q8E~v7}tO&00000NkvXXu0mjf DNC^_> literal 0 HcmV?d00001 diff --git a/icons/kexi.png b/icons/kexi.png new file mode 100644 index 0000000000000000000000000000000000000000..35980860e8368e63be4d3713f80faa4ae5a75210 GIT binary patch literal 1179 zcmV;M1Z4Y(P)kdg00002b3#c}2nbc| zMg#x=010qNS#tmY19kua19ky@)q>0b000?uMObuGZ)S9NVRB^vcXxL#X>MzCV_|S* zE^l&Yo9;Xs000CUNklZ1`9RT3xKQrsi#<88mO=2fb6VgHg3KX^U0tYA( zLW-bLC4>+Mj&R}w@Co<;oZtlF04P!^LPDJ2L=lh!C*Ke`c-t5cF{O8B> zqyPW_KuSOa0000f001fdqI&Bq<;%}ss;?H8`|HfN;z{q};)nUc@BjA0HUa<;LI@$; zUf90%;O&EP9Zj^6ql;&;8 z3JpT%ge6&+O@=@D{LdnQ6yPr_tH0a$CWT7jSSLwHAOx}%Nh@RX&wJnd`Uq0sud5gC zow-0#I!4k{NGd&rS}Sxqt>h#!4u`{=Z|qZ$iuzZJ7izWEL>9rDGlL7Rro z*|Q`8fDoVq0O8nzr{)DzFjIK!Mb!Fw0001J%=MK%5&~3^X5EQhozgXnPTQ6wr2^2& z0&9+peQeW`JsUHq;8fYD^c=Wv-6gB~9-4bylJIxjluaBNI*`<^QbGl}x8R0{O0`yv zWh=J)Ly1Nx?73pDlg)8O<5^EoA?BtkH*MN8v@AADkI6%BZpqF;fc8pR8Tkl z-j2ERK4ZmQ|FYspqfnrgg@GO6OUCy7SI@*!0~MUkd%dw`@7we#H|)6R1=*2fEL&FD zGj!22PYu<23QH|i;KUUJGo{+tkr$n_Z%5?>4(-a;e8HYpr_mX>VmN>bW}Z8Z$Bv9O zC~X_bI#4L>TEoOpc3rZi0ToR0p!~!cub4aVz=5$tEwYfbvYFbZ1#cL6&rDWis9Y`HOm3Q-I;G)kpITRNrF7Om>*E6h#J z4fITmO=Zd4>$V4i3T83&lA%_u)=8GUXI~aVl9X0mcdYZ=$eK0V<^dJrlLwcsKXTs7 zWyQ6| z6o4-eUj5DGH>JxiT6f+Dc6B--DHP86v>jVEeH=S~xbyvYr$7bp?lisi=4a3UX!4!B zekO&nPL_qF#8~5_xVQEG?cJZhb3Xzg1pr8i@7_53gVoQSyi}a)uT+c7pEsk~ZoPSU t@9@{Zy-x%HqyPW_DS-q6hyVb9{{y@Q0k>ztOZET&002ovPDHLkV1gC5Al(1} literal 0 HcmV?d00001 diff --git a/icons/khtml_kget.png b/icons/khtml_kget.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb66552860667991b768b8fc1105a89ca388c4e GIT binary patch literal 2122 zcmV-Q2(|Z#P)TTunkXbn3?!&8 zCW;u5koY2r#0S)bsL=-moFLhCK|)rf6B9+4on2V=dthgGzI(c7db<05)yMH+W(Q_x zh=9E5Nh-G~?=axV`3T?4}K&E#=Rf7zW73=cchKc zjoYzFfp6aTiz__W`>SQ{NHa|9ri>PJ`sAdajsy13_f+We%NK!0k z8C--YwF6X7H1N`a*U%O0fa5wWgh&P9w#GvI^q$||lj1FbDbEGi^|8;~ZYr0qxox#rG?dU6XgF(oQMq*M(E0Gr=@?Lge%$(Czl1EXwF` zy5LJ6vJISgWfuDno0z_wzCA89_9Yt4K$97^TytgX9QGt;WacN=j+fvHGwaH|y zg|!ya@i29=j>UKe7hG^Y28!KaBaxFnl175@mnZP*p;O3lFW6e}+HpOr0t3XB_rQq8 zWS5g+a*{VGaM5^E7-L*!1_?skUcvTD2s0n)f4X2>p;Rq=uIgSG& zL4{Hmj_MLL3m{HF#hz+KHT!@>u_-YaU4B5gR8&3Yf})0&L9L4UiFZ{XYrNK-h{z& zFIWqtj>7yx701S>@!FA7xVZnrU@({o03%5ivQ!vAydeli2#2NH1lI0?4MA9MCY(aO zDhWq?-{tOd0lnKw=q?wqts}(HP(Sp%|F%JR`Gr>@%zG#VIfQ|WAm>5~z``y_&z&S8 z6ELf#R=j>2Zcw1KLRO?APPKlVfXh0|C9jyz5i>y>4Xq9Ox;wGBFuy4XZ@TIR7_*uu ztYu_MBTh0{wwjs79U&tFwE}@8Z0M~44j+ase`qPuS@8GeX7rrW_QuYRk{kFQ5fO|r zP|9GazZcAmz5D(K0J!^0w?P>LtqqjYNHc|26mdIBtvKYH&>ma)s9&y zZR1o;gn|E36lYCMVjj{g*5i7NN@ZPvO}SvWAUNp01m$wUE9G+wQIeJ!e4Znv$on2b z-y_G7aMxPSaglRf_&EmvIEpi#uPxa#bM-^66#q1qTiTfEOlQZJR+gs@OupR0?CjQC z003iSWA?h8x3tOHDMvb`Fz|OTt+c0`?fBz!)qH<3420*oLI^=;bDCvFk|uVg8Jqe_ ztD0%^N>5jDy4i}`nbvb8c_K43FMpfGeYiv(9f_UhC@WnlNPD?otPr zi}|T2PPWh08bex}Zewj|ElWXUq!eu-P%i|*-0)yuwG}6-*=n~{rsrH&o-hz68+EmK zY)>=8IW^*z1qm=}mkuq&3#Bj47CHk&NuqpDCLLiu?(Z&7d!Fk7>@c%nCNNtt!)j|y zoMf3JL{kWwXSS~^VY~qU_Y%{8H_yBdrLP$~&(KH`@Jl_x(M&9Yf_X z?}eRSF4*Sg+_Dg&AQ?V0Ng_7PNQ~9(BvVW6Sk)Uzv{ap|wSM_PC3{a*@wwwZdgqvO=mcYz`$WUIy!pxt@0o3-&1jQ*5E^lC;$Ke07*qoM6N<$f?s^` AiU0rr literal 0 HcmV?d00001 diff --git a/icons/kpersonalizer.png b/icons/kpersonalizer.png new file mode 100644 index 0000000000000000000000000000000000000000..cc3e9869898a71fe79273231abf41873febe4391 GIT binary patch literal 2208 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 literal 0 HcmV?d00001 diff --git a/icons/math_matrix.png b/icons/math_matrix.png new file mode 100644 index 0000000000000000000000000000000000000000..e30804f0315eb39a4779d4d251becdf13ffc1c53 GIT binary patch literal 637 zcmV-@0)qXCP)&xw;p}VP?3@41?)>xSpVuW+Al_EsV#Sq@_g-<{8E9H$t&9e~ z=$HM}PrGw6e=*$0BP$3k z`gffefYDI;&$ndP+;W>jZ6)`DPtU|}MN;*#GIr&+|48F+wT9fl_g;RMMrUX4IQ|)E>8JLy3MEXQi^4@4 z3sTIQ)IhU)RV8UGZmMwZS!WV`)QBy|<*W+lp8;3p;pzGQf6YGwA5@$7h=Jz*KS~Fh-YExj+-dFsyC%43%Wa|?ai7f zVLni#T-m8k*iu~4cq_Ww?ka=s)b-9lBTWryYAS)N?e1R{eHPoN!T!vAEnxjK(0r}? z|B?oZ)-r!)oLvbY=XwlKgBl3PpMv3``1BV5y`%@g?HBTAbAK4mz>Uu2V!<7<_Dc8% X>utjlxbhGp00000NkvXXu0mjfXLKfN literal 0 HcmV?d00001 diff --git a/icons/math_sqrt.png b/icons/math_sqrt.png new file mode 100644 index 0000000000000000000000000000000000000000..69df3f61e86702fb8f6384c73a3a4fbbb38d5b4b GIT binary patch literal 474 zcmV<00VV#4P)RCt{2l|hb#AP_}=nTf^?iCa&@ z(Kwo?aoY(yO-#)~u|Y~kMdN0ELOKzO|EMZx;9oC`t_B(yeht7kH_#b}==kSq@Yh^l zjP3vcMGnAq^EuW)Qzk&J^!W_j)qGBuGmz7Kw*W}>Z`+mu_`{btcV`dq=ko!=tIYtQ zlyZjT>u1ME$ueO8}s{t|JI*?fQ*~f<1zFz!?~rWPNw)Pt!DE97ic+UC$hzVa-5p zcaP(^H75Y5s%j60R)8C>mXr|&<`X9%P`D@2Ppbr!0ZH4m`zn0%BX|4k`!AFx2Dn>g zSsu<-F`vebuTG>qT1T`da9=q{Jx^@P4nmV3wfi7Krpcv Q$^ZZW07*qoM6N<$f^x~m1poj5 literal 0 HcmV?d00001 diff --git a/icons/media-playback-pause.png b/icons/media-playback-pause.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7d128ee8728707a6193eb41af32b4e28890533 GIT binary patch literal 1145 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 literal 0 HcmV?d00001 diff --git a/icons/media-playback-stop.png b/icons/media-playback-stop.png new file mode 100644 index 0000000000000000000000000000000000000000..650874f67f0ccb1b39591de96691b61f3c3bf98d GIT binary patch literal 1165 zcmV;81akX{P)kdg00002b3#c}2nbc| zMg#x=010qNS#tmY19kua19ky@)q>0b000?uMObuGZ)S9NVRB^vcXxL#X>MzCV_|S* zE^l&Yo9;Xs000CGNklrkmC6Jy9WesR^^K8P&yuG_6BOq91w;O z0FIbYMal#}U|XkRx6FwT6Tku#*>#;1HyPf+y_n^2g($1BtyaSfaR;8k6ZA~sI(gSC zc%vyhSu{W$FX5Pt^Vi%%4psu)qlr?gpwL-XKAv1R_HEJ~1I0WF4?Jpm~W5GYEj ztH#jhkPumvF9GJYitJ75H}MpnMT6lX9Y15XR54~N2ULy4d;0$f``E+=_o>u3cZcqz ztB)Df#=OsD0F^qVjg&%)$pL|--KDEznj~;+%&Pn%Kky?%Ctk&nW;j|!4e%6}nAfZ3nxTO315g7c|Bk-L zfaG<;pbU5l8X*cRv9L2JXw=|y1iD;xnjS1dKkxTfK@nT`F9Ew5D9F>4bh~(rnSG8{ z9IwEKi+b)Ufs3v5z@c9-(K+Snz+2!?KjqVA*sc&7?)`d7C_O6|h0Pt^l*|)UbjczO zpHqs6{c1tr{jM{Ly^B^h(GM`fHiqD-BOIzeI|&rCUIitq#~4@XO$B|~3_nj_UbE*a z_zsUE(wSO(Of&e;r{TG7?{D~uDlKGfEwz9w%aCkX=Os-r_7})Orh+paP4EeXiDW3t zT2Q`-u^N!^S6smWBcueERN)jwd!i#FYW(a`Q(s>b2 zBl1;=3cRN8@g?$5rZwm&>RT`nX;=;&DABsmLiTnzs1o-Ne$v fC9`oMo%a0)Lk#jiOZi|800000NkvXXu0mjfdhQk9 literal 0 HcmV?d00001 diff --git a/icons/multisynk.png b/icons/multisynk.png new file mode 100644 index 0000000000000000000000000000000000000000..e5262bda1ea17681d4ee595ef3f3ea3d1f7e0ff2 GIT binary patch literal 4254 zcmbVPYj9mv8C~anAGx_V&z3ye1X9|X^pO@qkqV@3Y{OGgI+Q9nFr(whIK2Ld{!wRC za2y{vqx^9kKmi%jLY3*X3Rs$AXaQSm3Z1q|$jhY7P44sDd!DX+@-Rs==(yW`_sKc? z?C;y_TkG53dGd32?2JWfA^>8YyLQ~gu|f2Z&fmwLcp}R|f8f@gJK&3dzx~S#PxDUr z;az(k1EQs(OEbuNi8+k-1E>FkZA7Q(f-AI!cs$`Z&Kov|GVkB z5mppclQmV7ASu3Ti1j4+9(;+8`JVXixsKz)b8HWe<-49EsgmdB9bXt$|8MJjal?kK z`Bc70LtINzAxU(D{qp8vc&xX^DwugGq837>2|Lx-|H%&Mx+-yD#kO7N@&%{w)UCP> z2Y>l396oXeX2FK0DNtkyvMdJ^R1QW+!%O?Raq!U3Vv?r1z9QM7>MDm0OwXBKYT@s! zal)&Ak^}i%2C-NeH(kF9#iEP6Y2nPdQ49`G1W1%6qIltz!}##Ro9%iyq&t$Uc&Z}l ziNu`2B|~6Yj!!3C+qMg;Dr<_WY7xT-Po^@-xi<-V(JBNx7KYQ=0tSaB=8TsoBG`A} z4dkT3-KwEUlBW2wtNB_S9<*K8 zCWgz}e3A*e&BmM5hi<5J)Icg@qUp-z7;9V|;uZuItWqF-M-+h@vW}t}MlGp52eivJylhA--S4+N*2PvhhZ&xpK=r-o@-Q zOc`;>^GkfkHEek5n%%p(1-7=%8>PzHDn%;V3L#cDw_fv|k%2x;B`=|n%d+Q@%35T6 z6ahCFbAiHvgFV=DcLzMi{JTGX1zWe&;MdRm9yeUy7Ld{-Dy39OS!Erpyj`0d8>m%E z!ri_^nt(tXsuwu0tg%*cindCXYOVG4Pe&t>O>r8Fl|&HIHTaqV%?M%JwwqB|S&4W& z&Rs_E@~eNvRK~|Ie)S?A`T8y_Usg@{Q+V$AF5JKOE)4%`5;-O+(X?lmdM!f8T*EYmH2r098R1{;`W_wbRx;Q77>%7hvL%3k@Ho) zia}pdMtw=Lm71m%s%u%{=4kxw(r83mv!Xs6FR!R0QE=$(W2kRv#P*I3lFTK;rzJS6 zgwE})Shu=5Af%?NFboZvs$%oT4S4myix?W$(b}>S$*Ej`L^vFQnax#DF?9vmIY@Hb zf`qD+FLaP#rB(6LgzLF+Go8j56+lO@p>-3dhLi1+{YM{>vXg(AXC$%-Oz&1GIhjy0>Y9F=@8#Kb5mSH$9va;zwe#fo&mPE=Ij zofGe&w7yY=xB4&${RDE4tF#S?8iM63HwtRXmj(^ZAZEahR5r)Et z$Fm{gsgh#Il!W9t+!l|qm|?V4mnD2^Tj%NN#1dujY-*cboOB`wh}=TCv)~B`jk=#48zn3X zI@>nkwXRo?O=nOe)dR zqeMGLq>D;ZQ=^`@tl>)&$u+g*6<5ScN=TG23fUZ5+dE(l4WRqvDO|yGfW9IW3Q=wb zj=cOl22Y>F%9f1*FT{_K=i!5g(9?4QmnNpLea9|XEW|9w1nUr_rjj^ztmi(hIyU55 zc8)>G%aS5HVM9Oe!vEc=a~J#qcNN5y48L&leeB%xB_u!YM^AS*kw_y$G@gCx`)FFd z7Q;is0TKd@+1_`dFOcG`x7-5eG{mDS>Z;;szN!H?wXB7iOs6e^?#NP#WHDvkb{xfX zi{&NJu7R=fu2bjEi~5l$aHrIB-#`A2#~*nZ`Qd(i*xQTqZ@rGke)tn?yL%7#YQPJ@ z4G|X6+pgV=O-w}OCCcMrtgNZVhSg2DVdHwP>!L83H650uJasHZ(_C>JGbhVMmXrCi zSoDea&YrL3!dtFi|0y&yx56^BDCTq6d+%Nxe*I6l_o<&Un`KZ!sTOB;E_fj1D*Ce8 z8nA*QpY%C(h|q{Gv$HDpM!OCW{pqI44&MfB7ce2v3EA*NSV(_s}&=*;Dc%-!*xFlrOh`SP1ZV%C|(<5G?U18eEhNBfVw^U}#9e-0|0BJ+T27Xv7n#O9hnKy=~AVuSPl7SESzfdk?}7oFg) z9A+bnNvUA6-@mi{fpuHA1@G~QU`!cTLM*;~MB_m+hroPe0`~cTi|hY5urMTxYeGNE z5}OEM@Al7}X}|4G>+Nz5B;foW4ORW7r-P-%}I}cYswW?Q4?(Dc@N7uHmeDB|0 C#BQqq literal 0 HcmV?d00001 diff --git a/icons/package_games_card.png b/icons/package_games_card.png new file mode 100644 index 0000000000000000000000000000000000000000..bae6de6142ac534047bc45616dbf27a7e9964b7d GIT binary patch literal 2253 zcmW+&YgCe17e4SJpuB*FD2OFmrll5YmbVnl(k@eWSF7>$f;Q!px714EMG`anlo^|J z`pio?@nvS#G^I5einp-Rbeb+xD(Ylj8Ys0O35UKPXFuz#z4lqp+536cIr(9mL&#<} zW&i;4`q1EQMr40oM7%Mk9y>N|1Y82|8y*1HI#-#T8Z+*hX=}sNwjYj9%ZN_l176(W z7(RRb_t6RbZT#rCqsb5W{s0i8)(7);WWJ2i&&>rYxL6d#*l(~Pk&3IUs|%!3DP|!K zZDkk?U%E89sRYTueW7PkYO1&mS%7`QJ(9@%Y<(kwdwuTOxA%N!hS~}sb^sSx{0){Y zxjgvt<;$G`0RgaGpdc=D_M@BP-tkY2a{r?(a-~wqUbqm7pVjHy>#tvLo{w%8+fpu+ zERQne78mz@nEj;Q4X`Jj2^>$)gUQLs?t2?ka#in#J7B$D4=-K16t1hQgJWW1;7yx0 z!K{^uAP>#S!Sbf2XJ(F~@k;Qsv7C4XLYp68T77$ad(+RIJNMDi(eYMGOAAsF85!xJ z(P-pPTe6((?VFQ^+sSxi(lSc%oHHP{ot=1gO~^GwNxiwCc|M!Xt^}uJ4_0 zWQTaML#mGB*=RU=+3$DR?Poi~K-?0~tr3JbE{Fqmj~SL-A?j^!E3jtPUcK6!Z!r=p zwUM@URuOusEo@-Ub>)`aBP^L0_gNv=ykkM)2G>q}nmX1SJK`c$BURWKD3zp8De^?^ zS+UZRk~|q94W7ag0nWTn5mH2{SbhAX9pp#oVaZ*mXPx3TYtlG+!oe6^hrM&?4;OK7 zfK+?Jl*UL&Ns*5uRVE*NU~wyV42LrgZ)`Y^YW=u8AiGF(o)z;~#XuN^XA(*bNGKnolC>#}D8RVbX5h*Zr-@RBPs zCZkEPZsyDV`|#;!-5#9s*WL1l>JriW2m2&*W&Y;ks>}1^IL@mXES9*7NFtH&M)~2> z)6?tyLn)W=K1N!^P5n)&qdo8TtU|3Y!_!?Mp*y(43ntM~DwFZgJ`=ywd;tr}sG~w% z`RCy|@1snECP{DqA^uAzQ2Zuza^ev8Zdp2h1Jd&9UPwN zUgnok7~OT&4W`rS@ReOxV+IEY(LBpg@YHDhdQscYJ$#rXx7v^7^ zaMILfMd0R&fNb+K*;3L1>9%%O0)tWBPl(@&+|hnE{lPIplrkXj~dD03v&F>4m!)>IHXFM=3RGp#edBZ$UpUYs$(AZWivZ!nz6# z1s^zB$p!IR>P^csN~INQW*-)F0z;$GlE%!GHTC;*9D`7h;|EIj7w)~A{*IPHL}9(l zFSu!+9*jDQcVTcq7`7Fp*>Zfs8$9)Aw?ifK>sM$>=%s?vQs&K@HyN#MZOp{PLg|9k->V zQ=fmT9GV%;z*o)mF%7~@1>1RPJLl+!I0?V&tmq=n(yLM`;;&(Rz3w8rIf6tU5k?|> zt3#E#f%^?%paN!Oi{vtUTEW+XBnBtqO~){qFJRozOLAhk*PId zG*mrZ>HL;AHu(0*#X%*paoN{XN9IvWDTO(azM5d7+kQqxWbg0E%hCxA4 zvRq7{7D5x#t2%S>t_Q!&z5kEOjvA=wxFC>FAN4Vtd?jGbkkg_X?n-R7F7LlZD)CRN zpy6wFu7Nw_@Ib6c=$z^~s9E7L$t9gl1s zejNLME#4by%+h^mUUj%16%$`r)bGU<{-};(8s>Cl%7M&K(F&-K&bmjPTZ3-K?u8Ix?`(oo+DJfBHGvxm;l@iaN@4{c|PDpP^miFDhDR$ zaA$7w@+Y5Oc?PQo?!{`!wSf&U!kHuJRR;?T3p@gqR5dk~S5;P`_Fi7}S5I_QFCrVQ zU)d$1cxw$`X3fLa&>AhwZkX?qiIwSyTA~{3lU1PfN6V3VkDLSk=J?BEABKcCRC&wc ze~*ZX^<~EmdfgGJ4GLPu0kX@VI60;_vHImFk Yo~i3a-yJbG{zt(2wVQ+Mf{>hl0UtitegFUf literal 0 HcmV?d00001 diff --git a/icons/preferences-desktop-font.png b/icons/preferences-desktop-font.png new file mode 100644 index 0000000000000000000000000000000000000000..9ace98c25bceb585d5dfb2a5dc99f7a3da047ce5 GIT binary patch literal 1336 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 F{trwwq+tL6 literal 0 HcmV?d00001 diff --git a/icons/sqlitebrowser.png b/icons/sqlitebrowser.png new file mode 100644 index 0000000000000000000000000000000000000000..87be207dd0de7e1c7dfa44f14496b260dc786ce6 GIT binary patch literal 486 zcmV@P)004R=004l4008;_004mK004C`008P>0026d000+nm#LZ50004T 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 literal 0 HcmV?d00001 diff --git a/icons/text-speak.png b/icons/text-speak.png new file mode 100644 index 0000000000000000000000000000000000000000..15299b138cc49e7211ae9b7a1283203d0b2177d3 GIT binary patch literal 2527 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 literal 0 HcmV?d00001 diff --git a/icons/text_bold.png b/icons/text_bold.png new file mode 100644 index 0000000000000000000000000000000000000000..7984923fe76ca943a1b8b9ec29b538217a62c629 GIT binary patch literal 1620 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 k%W7fj=c}LHYbeZr0WKR literal 0 HcmV?d00001 diff --git a/icons/text_under.png b/icons/text_under.png new file mode 100644 index 0000000000000000000000000000000000000000..e9ac03b963c88e619467501dda84998a334cd813 GIT binary patch literal 1549 zcmV+o2J-odP)iDmH!vXKA_EGvg7gd|cBxkCPdk8psSg`yk? z7ZNEDMIz#%NG2!{L@W!CFBS(If~*k9kE~I^yPoyVOjiywJ=;Ay29mZ$?w#)G`s%B% zs=JAqq1xpyceU*K?D23*tG8DO?gMigG8~lQ!`dSg`bqDYs?wvGb;r6ld8hm4Ctppi zE1RSM%ua0UExx__wPndcV}XqTyl!lR|DG%Et}N$Hyz|AvqX@v0+gtjIZpH$PLzhnOOdUA+=%!`)Ltub81z4YgTKYlM9spnfL;!Qp*Bb+1 z4yJ@-%&~Fr1^gEeAXa_=$y&gK*Yw-1TfM{d=Vak1p)z^k8&!Gzw>=BN9d?Sw>-DHsZ=56Zw1|V`F1Db?OwBmX=T~7IF6M zS!A=>XyIb9i1G1p9653Xot>Syas4_9MUzni-~&b)TY8=*WtrYA%Yu{=?d|QzX0r|B zuInP3&0_oZ?I>>AWDbT*2#`?|ETc&1O^Gb*GXT%?VB0n_nT*L2f-p%bK?t-#L`F8K zc|>FsEldBQC2k$Kwul0dPN$>AlwMrdg&>0G`^e>T5PEhPb%6|fbrH(Cp$Kh^7-mK$ zlYwp9(Qg2dz8`t)`#v(63`mIjhf0XdAUy;!9s;f}n$(yXwrwMoN*V8c1t4UkwY9aO znAI6gVjwcEPlTxh073|4GMNYfA&5Of<3t1@L4e0xc+OU9F7R0tzqX_9A0Z64%5JJHB{lIw4`>ClZ*tU&BhYpz< zP~L}POwI!J#8^=C2|*o5r_*p<7YOMm4ZyW)*U-_?fws1`jWs|Ngj($f#(^P@Sam}{ zeSLjMr_;#i^EiM0JSycf2*BdvBF>yS10e(k1_mIM9zsp%Ac43Bj1vw=@mBwJJsdoE z5Z_+Bh|1a;q?GvP!UZ^v0|0Ow2M6}=$H>Tt5th2w8ok;{E3p#lAQS`Y-LYfGu&}s@ z`}gl-Zf*|Swr<7X&=9)2yV2CtWEf8h1AzilNolAv5KlqX^L!tjot+pR9Sx|~C|2`c z1t4OeVKyettgLqYYZ6Qi=8rwU%^BdL?vd;YQo(blM$*1ibvmuzJQJ!W`yshm#>FrS7=b z@lZ5`Agm`+O04~Q0(kO0z(BRud|J3w>vt88rsqGQ^4Bj|TU(3HbH49Kv587W!oh{r zQB8ZKlz|gr-j*~o0GJs@*L7%Ep(#*PR-J5)7-j4qT$r_0HBa_+B00000NkvXXu0mjf3s2g} literal 0 HcmV?d00001 diff --git a/icons/view-pim-news.png b/icons/view-pim-news.png new file mode 100644 index 0000000000000000000000000000000000000000..c603c3ac0c83c8b6cb7fadc2bb4b7077a0b57b6b GIT binary patch literal 1109 zcmV-b1giUqP)`{6hRQK?&+D?&9aY>H4(u}K*&YJi-+933f|)n z@W57zu9ce<__P!f2TalDAy(I-vS%PfXD;5ObSI7V2%hvp!TPqzR=foZklpaHYS~#Gd&q@=-un*NI87NL(vCj z-x!9&A+;KgfU9YgX<6yi){+J?p-t1#FqPDbj*7a)!Ki)A&viSOE%*u*I>mt1ZPike z2-BRMlISY>wJzWj^)rph@9D`Y`$KRWnKd2xe3H|hPXGRF01sMkD_RvbeEFK+PXHNc z9Rb!Dy5Y`<_GC(XVn8r8w^%AD7rQY(_`s%L0=5jwGVV0kE`IasDHVmCe)hR?PfnF zBsS_t<^4Tjz6X2>*xLykI&vYhEHnC|DEy{s2pP{)`K z>kuw;Tu|QEm*DR14#tp}A*aI{r$ZAu9Y+8pg(J|R9-*->4^PhNxpxDO0Q`Ig@Hj=o z+(&@ZVGeK^UAQBXltWI4GvW<_L_P1FO}_X8;Fuh$I}r;fN04 z<^yzGo$u9Y2t(lL=t#_FGwzLu9Yi2)Qym?(qtVE``^A6}e|2@$7y_rKr|Y^uVe6cnq<=RaU;3a-&<{`>{RUgBBeyOodQ$hKg6OQ*e-IEF_})7>>s@nq4aAXxZ)XoHwQTLRuu=wseyKo6 b6KQ?}5B4RFxRbZ^00000NkvXXu0mjfs4oJE literal 0 HcmV?d00001 diff --git a/icons/view_text.png b/icons/view_text.png new file mode 100644 index 0000000000000000000000000000000000000000..ea06f93b16568a60df17c58867068ba172357565 GIT binary patch literal 952 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 $init +echo "__all__ = [" >> $init + +echo "Generating forms.." +for i in designer/*.ui +do + base=$(echo $i | perl -pe 's/\.ui//; s%designer/%%;') + py=$(echo $i | perl -pe 's/\.ui/.py/; s%designer%ankiqt/forms%;') + echo " * "$py + $pyuic $i -o $py + echo " \"$base\"," >> $init + echo "import $base" >> $temp + # munge the output to use gettext + perl -pi.bak -e 's/QtGui.QApplication.translate\(".*?", /_(/; s/, None, QtGui.*/))/' $py + # remove the 'created' time, to avoid flooding the version control system + perl -pi.bak -e 's/^# Created:.*$//' $py + rm $py.bak +done +echo "]" >> $init +cat $temp >> $init +rm $temp + +echo "Building resources.." +$pyrcc icons.qrc -o icons_rc.py diff --git a/tools/translate.sh b/tools/translate.sh new file mode 100755 index 000000000..6609b3e0c --- /dev/null +++ b/tools/translate.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# +# build translations +# + +if [ ! -d "locale" ] +then + echo "Please run this from the anki module directory" + exit +fi + +allPyFiles=ankiqt.files +echo "Generating translations.." +for i in *.py ui/*.py forms/*.py +do + echo $i >> $allPyFiles +done + +xgettext -s --no-wrap --files-from=$allPyFiles --output=locale/messages.pot +for file in locale/*.po +do + echo -n $file + msgmerge -s --no-wrap $file locale/messages.pot > $file.new && mv $file.new $file + outdir=$(echo $file | \ + perl -pe 's%locale/ankiqt_(.*)\.po%locale/\1/LC_MESSAGES%') + outfile="$outdir/ankiqt.mo" + mkdir -p $outdir + msgfmt $file --output-file=$outfile +done +rm $allPyFiles