kabikaboo-1.7/000755 001750 001750 00000000000 11314170755 011507 5ustar00000000 000000 kabikaboo-1.7/COPYING-DOCS000644 001750 001750 00000043275 11312705425 013277 0ustar00000000 000000 GNU Free Documentation License Version 1.1, March 2000 Copyright (C) 2000 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 0. PREAMBLE The purpose of this License is to make a manual, textbook, or other written document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (For example, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, whose contents can be viewed and edited directly and straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup has been designed to thwart or discourage subsequent modification by readers is not Transparent. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML designed for human modification. Opaque formats include PostScript, PDF, proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies of the Document numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a publicly-accessible computer-network location containing a complete Transparent copy of the Document, free of added material, which the general network-using public has access to download anonymously at no charge using public-standard network protocols. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has less than five). C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License. I. Preserve the section entitled "History", and its title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. In any section entitled "Acknowledgements" or "Dedications", preserve the section's title, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section as "Endorsements" or to conflict in title with any Invariant Section. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections entitled "History" in the various original documents, forming one section entitled "History"; likewise combine any sections entitled "Acknowledgements", and any sections entitled "Dedications". You must delete all sections entitled "Endorsements." 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, does not as a whole count as a Modified Version of the Document, provided no compilation copyright is claimed for the compilation. Such a compilation is called an "aggregate", and this License does not apply to the other self-contained works thus compiled with the Document, on account of their being thus compiled, if they are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one quarter of the entire aggregate, the Document's Cover Texts may be placed on covers that surround only the Document within the aggregate. Otherwise they must appear on covers around the whole aggregate. 8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License provided that you also include the original English version of this License. In case of a disagreement between the translation and the original English version of this License, the original English version will prevail. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation 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. See http://www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. A copy of the license is included in the section entitled "GNU Free Documentation License". If you have no Invariant Sections, write "with no Invariant Sections" instead of saying which ones are invariant. If you have no Front-Cover Texts, write "no Front-Cover Texts" instead of "Front-Cover Texts being LIST"; likewise for Back-Cover Texts. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. kabikaboo-1.7/help/000755 001750 001750 00000000000 11313262762 012436 5ustar00000000 000000 kabikaboo-1.7/help/C/000755 001750 001750 00000000000 11314161053 012610 5ustar00000000 000000 kabikaboo-1.7/help/C/kabikaboo.xml000644 001750 001750 00000153323 11313263102 015260 0ustar00000000 000000 ]>
Kabikaboo Manual 2009 Dave Kerr 2009 Jeremy Bicha 2007 GNOME Documentation Project 2002 2003 2004 Sun Microsystems 2000 Eric Baudais Kabikaboo Dave Kerr Jeremy Bicha Joachim Noreiko GNOME Documentation Project GNOME Hal Canary Added the Shortcut Keys Table Sun Java Desktop System Documentation Team Sun Microsystems
gdocteam@sun.com
Eric Baudais GNOME Documentation Project
baudais@okstate.edu
gedit V1.0 2000 Eric Baudais baudais@okstate.edu GNOME Documentation Project gedit Manual V2.0 March 2002 Sun GNOME Documentation Team GNOME Documentation Project gedit Manual V2.1 June 2002 Sun GNOME Documentation Team GNOME Documentation Project gedit Manual V2.2 August 2002 Sun GNOME Documentation Team GNOME Documentation Project gedit Manual V2.3 September 2002 Sun GNOME Documentation Team GNOME Documentation Project gedit Manual V2.4 January 2003 Sun GNOME Documentation Team GNOME Documentation Project gedit Manual V2.5 March 2003 Sun GNOME Documentation Team GNOME Documentation Project gedit Manual V2.6 September 2003 Sun GNOME Documentation Team GNOME Documentation Project gedit Manual V2.7 March 2004 Sun GNOME Documentation Team GNOME Documentation Project gedit Manual V2.8 August 2004 Sun Java Desktop System Documentation Team GNOME Documentation Project gedit Manual V2.9 May 2009 GNOME Documentation Team GNOME Documentation Project Kabikaboo Manual v0.1 December 2009 Jeremy Bicha jeremy@bicha.net GNOME Documentation Project This manual describes version &appversion; of &app;. Feedback To report a bug or make a suggestion regarding the &app; application or this manual, follow the directions at the Kabikaboo Report a Bug website. &app; is a tree-based text editor, designed to help you plan a book or complex project. Kabikaboo's user interface is designed to manage maneuverability inside a massive tree. Each node can be View or Edit, and each can have any arrangement of children, and the nodes can be moved around freely. Kabikaboo can be used to plan a series of books, technical manuals, software projects or anything that would benefit from tree-based text organization.
kabikaboo recursive writing assistant Introduction The &app; application aims to make the author's life easier by providing a way to edit and organize a collection of related text files. Getting Started Starting &app; You can start &app; in the following ways: Applications menu Choose OfficeKabikaboo. Command line Execute the following command: kabikaboo The &app; Window When you start &app;, the following window is displayed:
&app; Window Shows Kabikaboo main window.
The &app; window contains the following elements: Menubar The menus on the menubar contain all of the commands you need to work with files in &app;. Toolbar The toolbar contains a subset of the commands that you can access from the menubar. Display area The display area contains the text of the file that you are editing. Statusbar The statusbar displays information about current &app; activity and contextual information about the menu items. Side Pane The side pane displays a heirarchy or tree representing the sections of the open document. Click the View or Edit buttons at the bottom of the side pane to switch between view-only and edit modes. When you right-click in the &app; window, the application displays a popup menu. The popup menu contains the most common text editing commands. Like other applications, actions in &app; can be performed in several ways: with the menu, with the toolbar, or with shortcut keys. Shortcuts keys common to all applications are listed in the User Guide.
Working with Files Creating a New Document To create a new document, choose FileNew. The application displays a new blank document in the &app; window. Opening a File To open a file, choose FileOpen... to display the Open File dialog. Select the file that you want to open, then click Open. The file is displayed in the &app; window. The application records the paths and filenames of the five most recent files that you edited and displays the files as menu items on the FileOpen Recent menu. You can only open one file at a time in Kabikaboo. This is intentional as it would likely be confusing to attempt to edit multiple sections of multiple documents in the same window. Saving a File You can save files in the following ways: To save changes to an existing file, choose FileSave. To save a new file or to save an existing file under a new filename, choose File Save As... . Enter a name for the file in the Save As dialog, then click Save. To save a file under a new filename but keep the file with the existing filename open, choose File Save Copy... . Enter a name for the file in the Save Copy dialog, then click Save. To save a file with an automatically generated three-digit version number, choose File Save Version . Saving and Loading Preferences Autosave Select the Autosave option to have your file automatically saved every 5 minutes. If your work is interrupted for any reason you should be able to pick up where you left off with minimal difficulty. The time between automatic saves can be configured in EditPreferences. Open Last File on Startup Select the Open Last File on Startup option if you want the file you were working on when you last exited the application to be automatically opened for you when you start the application. Save File on Exit Select the Save File on Exit option to have Kabikaboo automatically save the file you are working on when you exit the application. Working with Text Editing Text You can edit the text of a file in the following ways: Type new text from the keyboard. The blinking insertion cursor marks the point where new text appears. To change this, use the arrow keys on the keyboard or click with the mouse. To copy the selected text to the clipboard, choose Edit Copy . To delete the selected text from the file and move the selected text to the clipboard, choose Edit Cut . To insert the contents of the clipboard at the cursor position, choose Edit Paste . You must cut or copy text before you can paste text into the file, either from &app; or another application. Undoing and Redoing Changes To undo a change you have made, choose Edit Undo . To reverse this action, choose Edit Redo . Shortcut Keys Use shortcut keys to perform common tasks more quickly than with the mouse and menus. The following tables list all of &app;'s shortcut keys. For more on shortcut keys, see the Desktop User Guide. Tabs Shortcuts for tabs: Shortcut Key Command Ctrl + Tab Switches to the next tab to the right. Shift + Ctrl + Tab Switches to the next tab to the leftt. Ctrl + W Close tab. --> Files Shortcuts for working with files: Shortcut Key Command Ctrl + N Create a new document. Ctrl + O Open a document. Ctrl + S Save the current document to disk. Ctrl + Shift + S Save the current document with a new filename. Ctrl + Q Quit Kabikaboo. Edit Shortcuts for editing documents: Shortcut Key Command Ctrl + Z Undo the last action. Ctrl + Shift + Z Redo the last undone action . Ctrl + X Cut the selected text or region and place it on the clipboard. Ctrl + C Copy the selected text or region onto the clipboard. Ctrl + V Paste the contents of the clipboard. Ctrl + A Select all. Ctrl + Left Move the cursor to the beginning of the previous word. Ctrl + Right Move the cursor to the end of the next word. Ctrl + Up Move the cursor to the beginning of the previous paragraph. Ctrl + Down Move the cursor to the end of the next paragraph. Help Shortcuts for help: Shortcut Key Command F1 Open &app;'s user manual. Preferences To configure &app;, choose Edit Preferences . Changes are applied immediately unless otherwise noted. To close the Preferences dialog, click Close. Tools Document Statistics shows the number of lines, words, and characters in the document. Document Statistics The Document Statistics tool calculates and displays various statistical figures to assist in tracking your writing progress. Choose Tools Document Statistics to display the Document Statistics dialog. The Document Statistics dialog displays the following information about the file: Starting Word Count. Current Word Count. Words This Session. Time of Session. Words per Minute. Words in Node. To close the Document Statistics dialog, click Close. By default, &app; only updates statistics when the Document Statistics window is open. If you would prefer for statistics to be updated in the background, select the Calculate Statistics in the Background option in Edit Preferences . Spell Checker The Spell Checker checks whether the words used can be found in the dictionary.To use the Spell checker, perform the following steps: Choose Tools Autocheck Spelling . To unset the automatic spell check, choose Tools Autocheck Spelling again. Unknown spellings are underlined with a red squiggly line. Right-click on an unknown spelling: To replace the unknown spelling with another spelling in the list, select the replacement spelling. To add the unknown spelling to your personal dictionary, select Add to Dictionary . To ignore all occurrences of the unknown spelling, so that they are no longer flagged as unknown but are not added to your personal dictionary, select Ignore All . The unknown word is ignored in the current &app; session only. To change the language used for the spell checker, right click in the text area and select the Languages menu. Select the appropriate language from the list. The spell checker is turned off by default since it slows down loading files. This may be particularly noticable with large files.
kabikaboo-1.7/help/C/figures/000755 001750 001750 00000000000 11312707171 014261 5ustar00000000 000000 kabikaboo-1.7/help/C/figures/kabikaboo_window.png000644 001750 001750 00000110770 11312707105 020303 0ustar00000000 000000 ‰PNG  IHDR(B¤Žt.sBITÛáOàtEXtSoftwaregnome-screenshotï¿> IDATxœìÝwxSUð÷Þ¬6݃®tA颋–]V){«ÈTe)CDE”¥ ‚Ÿ2UÙ{¶ì -¥P(¥{ï™usÏ÷GÚ¶Iš´¥-ðþž<ÐÜqÎ{Ï=INÎ9÷†"„€šï|!„B¨1œúã[õ§”ªá5xÚF­¼'¿Þ£9¢B!„z mÛI’ûèø¯ó•O+^ƒ§ý0sêÈ,¨hÎèB!„jÉ/®`¬žs9´…°…¤`kn›·mW¶½('ÿÀÁïÌœ:%=¯\Ï´B!„šL~qEAIùƒ›gõܾ]§¾Öæ&ê-§fO!·¨üʹÃÇÏ=øÛ´e—\ Æd/„B¡fWP"Î+*‹»}®ïà7¼]¬êÜþQjáÙãû|:„€µ¹q IŒZyÕÿíEã'ͨÊëL!„žaËâ®ÝO‘I›ö¡®F”7®_¡WCvAÙÍ Çzõé`mªç.÷ždÆÝ>ש×{kÓ’dæ•Þ8ýWù{»zE±â¤;Ññ妭{…Ø (§݊N/%@[¸vp2ãhÜ«,îNæc‡˜AÚñcŸ§xtvQkÕX›ì`‘S uc­éÌ!ôÊ (kS«ocÅÎÚ,€¢*›7-!UÃKsB–ù×Úp(€#WpÄ 2s†¥ÂQŽ‚Ð –.³éÀÛéB¨a%IQO§]m~kò¶L‡Â‡ŒÛ’å=”ûàÓõ÷Ájüœþ£my¡}·m/eÔ3Qëºù/èbŠûGÏþðÐ6ÀÓ’Vê“ÅÖ´ «¶Ÿ{qÑÿ_zGÌw Ú0ÝËÖÖó­6qËò|–hYµ®@æo‹#޽H}Ž—Jßž]TŸ½x½ö6:RèÛ³‹ªí¥4kõ¡¾=»ÔÈBë/™\¡¾tõNAïÌ'tè<޼”wc ¯Ã$-p“ò' V^ÆïšéÜŠ³5vD½H[ólå6Ô7þçÔk73‘$eXuë¾(Ì¡•úüz¡Ð”•åSå³ì˜ÔLO’r%¶|to°¶µcs3áYã†(ˆ*ëv.ÖyWÎ/»mæëcNÒÜzf‘’áhkPñuõo[ú8åv™×@SÚµµ‰Oë*é±Ä‚®}µB¨ÅRͯ*—È ÝQÙJÑ–‚ÆÕÖ™‚Ž}k¤ ¢mr=­àš3”1kî4ÝŠµ¤\!,9Y(a®ƒ…É' CûýB-ˆú±‚ä r×^ö‚Ð1}»Ïü’ByYægXøýî` ì“k÷Ž%JŒ½?è Tët Êë«¿ÔxVãmÂ6ħûÅ›1V"6£ÞYè³VÃ*‚ïZ½`xœÊïJ –€›·£ulÜ©Cphhš’+ØÚ)¨Ôxª¾°ÎnÞŽ®1C_9ßK@íTtÞN‚T-'Ï6 U×uW½£á[B/,õ°8÷Ïm‰…SÃFÚõß—ùë̯‰¬©Ÿ…)È3wòPAûx‡ÔÞÓ!ÀÙ1"6¬Þò3(ÈÏ¥ªç¢ú3=òæmßN#ì>šê¿tãÃL3bìZ¯,h43/llÀ2܇Zõ„ïb lJbyqžÖU3‡šýþ¡–ª5̧ëšD–€²™DU óÕNAIcNŸnß½§ŠA}r½²íÕ!8°v *º^ÃÈYÃZRQªÜ†+­`d@IÅ|…^½ØjÜM‚•üóÛyÎôÞÃZ÷™Ð—ýóìÖÌ‚"°²ä9½?*àºÌvP_C"­ü~üÚ£Œ,x ¿™!keÏÏ)QÏæÙ_âÜ¿¶_k5»k7+/'U,Øü$ƒW¯,ìlóâw=ö˜åÅ =l[_±ÀZhyñ»ŒÌAÛ*'¾k!ôb¡UÛ ¾&ªfhiX«­õ¦ 0kõ¡à åª5¦ ¢õv„°4K¹\ÁÈÅ?‰˜°,Ëd CQryQ«î4‡Ë²,Eá| „^L5GèLÛ83»~½È™Ñs°°ïÛ}˜ß/ýpÂxV?'ç@O£„øç¸ÓÂ-j¤‘qõ„U»ñí@VråÔ_3Üœé̜깨=£©‚ [ïÛ}èïá°tœøÓcW0žch®BŽ™ý½ά ÛÁÁÙZòŠÇ÷ã?žVáÚFÄ-Ó¶ª×a#ô‚©}®Ú$ªÔ¸ WÕ}ç9… @e AþÚ–ÔHáÙåo5¦dÕÌ™EVeH!¬‚°•wŸä߯³|QEÓ4EWŸ‰zÑiö£ÇécQ€—5”¤=NΕß¶»•83%«TJ(¾©½IYj¶hó¶þnæP‘ú0!WFö.¶²ÜÌB‰‚X:º¸µ2VÞ«r­“—¯€ªþ”Tä<ŽÏ® À1wöim.ÏIM®GÀJ 333 Ê$ 4ÏÄÂF$jeÊ­kBè…babtòèÞ1c'~«B/eoSVA™þ·“È)(…Z÷oÞT8A½Úw*,ë™B!„P“¡iާ_ĉý\;>ŸÇ¢ûQPTú4æbÿ¡£d +“3-$ ÒŸÆjã…B!ÔìÊ*$B#~ßÁoœ=¾/M¿]ú~C*c*ªîªÕRPÑy; „B¡æV.–Z[˜ {m¬žÛKåL¹XÚÒR ^!„jùò‹Ê^ô”Ÿ>fyN^l`(!„BH7³<.\5¶ ?°ß !„B¨±)ûºÒÓR7ÿ‰÷>E!„j"ØðB!„j"ÕnäŒC!„B‹Rû™ìñB!„j"ØðB!„j"Õ^Ä òÔ­á.Ý×?‘Õø»É”F¼ÓÆ{Æå²çž{³]© §E‘Åÿê¶9Eþ䢮ÎÒ’«P³ÇÖì „Ðs¦µá¥)<8ÂÁÎ^íÑnÞM©ÐsÀ›ƒ}ͿӬvvö=6$ȵmO«EÂ$ÿæÚëIZ7Öb÷—±‡òXõeâÛŸÙuý!ŽynGªQåá»ßŸ­¨\TvaЇèÍS%M€æxª"ÿð©›®*êÞñåÒ ÒUT£¡ö5_)vövöÁ_GK#y‘ƒ#]Þ‰(}¶¨üÒ4OûAg³Ú÷B¡W‰~“ë !@q»|ûß·„@ñmÛY ¬éUeDùgê•-træÔHœTmMYõP‹ÔBÒ_¿)¡ðáö³ÙCÆ8p*•Ýýã@ŽÏGo´Xy«ÒoÈq鉀äì²_î ü¦½1Tý–¦!¥*œ†‡¬<ÝVYÕÅ„•ÆŸøá㥶»õKo ªî½k$ ;ƒ±r©âyäÒ ÒUT£õgN–² ŽZ6nAÚÔ¿6mEPFöž|}“oÄoU jI=[ 5õÆ}÷@¡–Í€NŽ¥‡``P``P`` “MÛÖÏ­ÇÏO5|q—$üfLç6ŽöŽÞaü\¯îõì‚ý=mù[xmÝÄ®®Žö¾ƒæíMªü $FIé™wz~õP·¸›³ƒ£½Ïý³³é59œcÛÉL¦jIÉÍßNîÊcª©†£#EÇßòþò®ØìG88x~p¾ øä„¶ŸÞzôæ}¦ô,Ú±ô@Fí¢c‹ïnžÙÏÇÑÑÞ¡u§±ßO¯ŒKsáhØ`s—¶^^^>þ]†¸`„uɃØ|Fg0Z–?£Èü¦[À”]O¥ ²®»kØš}ëÞéææàèÜñí wŠ‹ïm™ÚÛËÞÁÑïµ ”!Kmýhh/{G{QÀ€Y;bÊ(wï¹ô·/_ëàîà¶>^¦1Rñh×'ƒ]ìí}{LÞö¤æO—ê¢o ј‹ö*z­¼AQµñ¯|´µâQ&.íƒ\ËÒ¿>'¥aET·:«¢üɺî®=oZ0²“‡«KÀðÏ¥6J× BµÏaôŒ-Šüü¹—<>Ù}áÖõ3ë‡å¯{kÆ™12¥ÈÚÿáÄ• =Öœ¼vóßiÜKÏ•Õø†lÖï‹ßúò}_MËÊÌŽûIÿ´)Ën“™Fýq,Mù6O ¯ì8SÑé½ANÕ:µkпuÁ•«™r€ò‡§SFÒ¨È$)€8þ\ ñïçidè±Ò¶ý¿˜ázíû ·jü0”"ëàGc]÷þâÀÅkg~y½lÛ¤±?Þ—h/œF>lEÊù½ í:w°çé Fór&çô—#ß=仿èÿÞj#ùÃ_×>ûþXäïÚßùîíwÞ]p+dñ¾ GÖ…'¯ÿhu´€•ÑÞVþwõÖÍK{?÷»óõÛßÝ*¯LQþxó¿‚9Gg¤œžÑ†§)&eׇŸm½pÿµ»woÙ¶`€³À;=kH]¹Ô¬¢ó•õ>)uß0úVEyü–}– OÆ&$\_Õöć×>jH !„^Lú5!Ò“ãÛ:T.ðXp>r–qÕð!Ê!$B!Lö‰ÕŒ¦[<Ö‡γ¿Ÿu°ÿŽˆÜ1ãíõ ªVvæoÎùØ/çßúï›7M\¿ZuíèˆP=÷j9†…i‡w†ÛŽüs⤹ž<6ïÂöóŠ?…ÛÑ„0uÝ›otìi÷í騢éŽ÷-Þü0èì©YroùÕ«…­'ù›2JI À÷œ´høoï/Ý3ýÀ$kÕQÉÓm<Ë}s÷ò‰Í)ð˜·aÁ©î?o‰þèGÍ…óî?ª-lV ÷'}Íì(žMcÙýè4ÚsŽ©r™Ð+Ì›Ð4ë œø†ÓÎ]{ŸÌ\è•vûenŸm½ljt j;:FàÌß}úQIHÜå²v †÷KØ~8¦x˜ôlª]¯N­ô/f5´Uïù³½ÂÖ¬½úÆRÕBYFt*å1½™²'‚çЩ³mqT|‰˜h.œF;ÝV^ÙÅ„(Ê3oïZ¼èÝYmÏm}ÓR{0—+‚@þ`í¸iô€_Ï®á¨^.~a €Ž×˜VOç\U€céåoU“€ /„ÐKÂ9^Vmü•üÛ¶âk}?&„pBÖG¥eef¨Wçû™Zv¾"Åá©B¦¸¼Æ)5ò7Ö=åß¿–ežÜqÛdÀ{]-k¥–£ãƒÐg€·øöù˜[gÒ[ p émýøtTôé8AHowA=âµ™ðõ°oé®ÄêÓ\4”=¥­pët(çxyû‡M\¼l9µa_Š\k0º–sº…·)»ðï™”H4]=EEsiµ½6ÿÄÜIkÓû¯¸Ÿž‘‘°o”%çYw ‡ËQËRS.ÆíæŒ>ÿç¼¢ÂÈÆuëµ ²ÈÀkíô©!†æÒð¨40ô¤4f0Õ^¹þm­*~µ¿vj ¹BÓß!ô2Ðû>^W«ïTõ7ß¹cxxàZžBWruÑ”Ï)PDRï¤I•O¥Q‰’Ú‘P4DÁÖ+WžÇ¨ ÞÙ¶ù÷«aÇSÇÑQ–}œ³NìÚýжWµ‘K`reçΫïþÞÆR•˜uùäãÀG?ýYÈ*çÔÞ…$D>,Un(˺y#Ï¢][3–Ây§ƒ% .,WhFórŠںǢ=›‡>þâY{Sej9¨6©=©Ñdi7beSßïãamDƒª¬¾‡æ\ÇÜ«÷ØYK~9xzS÷¼C»b* ,ˆºkˆÖ\tTцF¥Š€]5DëI©;xƒ£Ð°HWUT €I¾|¿P¹‘,óæíB+?wÃb@¡FkëQp†Îmvî“©+Þyš–šuîå_ïxlðÅQŠ¢§±÷Tbâ2*h§ÁÓºçïXöo¼˜IÂ+¶&ÖÞkîâÄM¿tåqV^AQyíõºñ\‡½ãŸÿ÷‚Õì_;@X{GÇ…v1{²ïÒÓ…Æ^}=2OœÎrîÓÞª!…Ìu½h¼àìæ+beÏ×qÐŒ¾ò=Ÿ.úëæÓä'×Ìúþ¡ÇÛS…\-…Óh§£4íÉãÇÅEß¹tÑb»°~î­ÁhY®*E»+ö¯ïv}î› Oæ0º²­^ví\é'Ç/gÉTÄïY¼ö¦•š¹Hb7·ñÀÕ¸Ô¬ì¤Û'N¾á6n¬Æ+uQÛ~ü¿.ú÷÷4 ÌûyÀåÜ.¡"ž¦tôg2û³n»>¹Z9â±¹áï¼… ¾Ù³.=ÞÛöËÇ~Fà ¹pët\ýlH˜òo¡K·‰kYÔÅ´£eù³S®èµµûʧ ›:Ædç¾Å]õ ‚ã0òÇ•W¦.èæ5ßÜÊ9ôÝYC®ý¨{ ¬Z._8ò³N¬š´<¥„€À©óØ·L÷4üÜÔUCh¦\ª —U«¢ÿüÜQ©ÓZC´,Wkùj¾±è[y>Îv;6£ç—Ù ëàw7ýù±_}ÇêB¨å¡fÍœ¾~ã/ùy •âeÛ¡æ#òSßþ‡']81Ùå9|)D¡æ! =-uóo¿éõææ$r®½0#=­‘ãÒ™ÝsÍQG¦Ï;_ƒ"&ZRɼšýÅÒŒ'¥¥U]„jôjx5ñ;f³¼A·œO…–‰RK‹§…{^,Ú´¨`B¨eÒïª!Ô¸³Ï? øf„zi=‡Ÿ B!„Bš`B!„PÁ/„B¡&R­Ç+)1¡¹â@!„z)ù¶{v#èj ¯î=Ú:„B¡—šòn©JÕ^ÿÆB!„ÒÎñB!„j"xU#B!„PÁ/„B¡&‚ /„B¡&Rý·q¨!„B蹩>Ç«¹¢@!„zpëÞ¡ANNîοÿ‰ŠŽ...)-+31zy¶í:°?ccãæŽ!„h¬¡FB€¢!šè¨;]C{Š[ÌíÄZZ<*÷2#ÿwcLïüK cXs‡ÓP )g±X|6òüácÇž$<õöòìÞË®•-Ë’¼ü¼””ôÍÛwlØô?_oïAû÷ ë-0„BÍ«†ûýè½ÝËFñyœF‰©¥xêÏœù nß¾«{›!íZýCÃCÚy÷‹?&¦?Ⱥôóù髯4Aµ´9þúÅsûöÝ}{Ðô³ E !„–%„°,Ë*Šó—o5ÖÑ=ȺdPxëöí»ýÂBË*­êEå …âNT”a±x w££¿[¹ªG·N|>ÏÉÉ^*‘îÝ»ÿišòŠòàà`{;{€/4vëÒáàá“æfnÝœûÝŸÍ 2ìhB¡FÒ 9^ñ©s<9jd»§nL]~}<š¦é6îk¤ 6oáü¢ÂBš¦­m­W~»²Éòí|ò쥺¶iß(yM ^¶òÌX˜ÞySíµÊ†‡C+X–¦hªQ†«ë|&²Žž¶!Ï·}³õ÷?;†˜››ñy|‡ó(>^&• yMÓí|}ù<¾‚UH$b©T*•J% a(ŠeÙî„"„ziÔg¨QÁ’O:íàhÛ££×“¬2FA LÂ$d—µiÛ:1§¼µIcÇùBúèÓyÑÑ1unèÿók J9&ëü¯7gÀ´N›z×X[RRbk× X¡éÂÒRKKKƒ¯AߣòÿyaGÑ@Eåæçµ âñ„°@€%¬B®°´°ÉåD¾€Çç›MŠKŠó ,,,ŸÇÀ+B!¤§ú4¼¾Ûv¡X̾Ö'(!³¤L̨–ËåLi¹”¦)…ÂX,•gä•z¹Ú4^¨-]ÂGVÀá¸~u“gÛZ¹$::fäÐþUSΕ3Î+g + ÕwîÝ74¯¿¢¾T]Õ¸rP͉eEÅ…Ž"'9«àðx…ù... 9®èè˜×‡ iZÙ‰¨œ@¦:Õq»pMµ‹j!„PýÐ𺛾ì+cGvq“Su !ɈIÎoï-bŠÉKoY4ÌCdÕàP5+-+]±¼Ú=#!Eñùü·ßyG}B!äçžSM,À¡÷†áš(ËÊJML a…€…&ee¥/ßÛc®|Ÿxë¸÷?:°ë·£—ŽŸÚ!8ÈÍÍÕÂÂÜH °¶² $R™ŒQ¤gd'%§Tˆ%."‡lkb‚B5›j /†a´m÷$­pêò#ðϾ‹ïŒïÇ0¬îñÃJ¤ÌÅË÷åŒbÔ‚ÿYö†‡³¾m/ahDIOOŠ¢”Q©þ`YVõwå@˜‰´KAbvVBhÑM0s`fî‚…÷btÍß h÷Ó÷Ü—_P`fj*“Ë@&—™™šæXYY×;Á¹ >»«cƒÀ¿ußWÝ]ÂÌA´èfÖúÁ`xië½1¹}vAqNÄÈ·¦ðlø<îö±@?-_y|³URŽUJ6G*“ñù<‡cldÄ¡AÀã8´r´´´JHL©G@!„B½Hß/g;³s›&@øÌR™œa««áÅ¡)±Œ‘3 å^ª›{5:3S³Ï¿X¨¾ÄÜÌ|Ê”÷)ŠÚ²esIi UììZ=§0”Ò¢Ÿ8}~•cíª|z/æ~ïîY¶êölå}ÞU¢cÔ/£ûÙ¶Ü™ ÷·ï•••yäøÕÚaÆËdRɤÖ66GŽ޼Q¹jð0GƒòºÛ»{'Še½©lǪŽëòõ(õí9Ö®v³Ž¦­èV¿CÓeå׎¾ФA£&Éi–6râ †Q0rV*)ÍËÎxú8%-%‘µ~?5ÒRÓZÙÙeff”—•+X_À§Ô~W!„jzú6¼Œø\#~åÆb#¯«áÅÒ”X*§(01æsèçxÿòï–×X2kî,GQTyEù/?ÿòü²®ÁumN%~ç/ßÒ±K€_»úåõ÷ýoþ|;ýAÖ¥ ç§gáàà8lð°ÓgÆï`ï¨P0 Iñ_çãåë7? +;óïw÷ïÓÏÐVWÕQÜÔ½A%\w×Õ™†fT'FV~ñÀ8++E—°!LÙ?¬ì©Bú€@q(Ê„kmæîÔÖ»KnŽÿ™ÃÛùl8!4ÀãñŒŒæææÔXŸ‘±T.“3òºRYãÅl‡ÃårY–m®ž·qþ‹Wœ»âôØqþ‹U úô ÿsçï2©ÌÅɦi'w™TöçÎßûô wpph¾xGHŸ……£o³¼¾¥b›¢È/¢rò -’Ó %róA*#òŠóöNî„)âÐrºj”hš€‚‚‚f=„B¯´ú|õ/—Èd ÃÖÕãU!•WÍ jÊiõMv§òy_|y7êžîmÚ®Y¹¬áyùÙõZ;èníåööö½{ôÞý÷_ãÇMh瘗Ÿ»ûï¿z÷èmooßðLÕ5åÁª˜X¸ôæüÞ‘e9~!Ã*Š"Í»R´aårIºLò´ (ÅÆJ. å\.…úîP`gg׈Q!„Bú3ì·³³³@*S(–­kc‰”aY6';ÛÔÝÙ° Ò´u(ÉECfv¶µ•™……÷¯oà§¢³“hÝOk !–@Q ÿŒÕ3…öQ÷êøMÃöò™›saëݹ0%x]»V=kü&‘™…EÇŽGîÚÓÌÂB&—+›\‡%„Ãá4¼ðy¬žsx&=_ß{ñà¤sGNõÒY¡ LZ/â ÌSÓndŸØßx7k›N1w’‰qWîÂìR32ííì¸\.6»B5† ©ì¿‰ºaÀM$2E÷/ÌŸ· Ió«²zEc«éöÏý%•W5FN_ÒçŒú*å9²±¶6x¸²ëyüBNSlm.¿óàÍWÏ9úßi‰„M|˜8pðXK[Üñ2©¿Å–B¡—5kæôõ)ÈÏF.oîx2Œrr›¸¢øì®ÞÖn@ |zlB€¢hŠæph.—Ëçóù|¾rr[“]åŠB)qy<HOKÝüÛox'IôbS¤ò,l_Ÿ¡ë%B¡–À°¡F„B!To†ÝÇ !„BÕ[}~2!„BÕ6¼B!„šHõ9^8ÔˆB!ôÜ`B!„P©Öã•ß\q „B½”|Ûù«þ®Öð:j×äÁ Ô.%«°éãyp8´³Åë½|B¼›;„zÁà/„*Å%çí8Ý¥ƒßÞ¶4ÍiîpZ.–e3sò·RÈÄÚ4w8!ô"©Ñðj®0j~»NÝ °´´)—} þÕdnnÕ¡}à¡«±á…BÁ/„*eäù˜—JÍÈ‹A 4ÏÌ-nî(BèSã'ƒ°á…^]Åf%Ų,Ûܼ0( Ë !„ Ó†iAØOÿ¸Ç›âdÕÞÅ)^סÞ]“â7Ü—*´mchšiÇ¡áÛ™C›;ŠÉØ››;„zÁ4úP#'`°ßhiâ²s¥R ¸žÛ¼ëM.œ~z:[¡9u¢ìh#¤fölNjal!Ã*×hÞF C·G€KaY¬0úãâ}BÈ@ÏaŽ—ªÅCñ|;{LôdΞHŒÈÕÒêRåJj5’ˆüÉÝÔ'º·14M„´ãÒ@p¦£!°á…B†ª9Ô˜wk÷÷ïëÞgü‚-®>µ­%@€P<¿Ð¶ÜeÇ&^ÌWö"Pö¾.£üÌ]Í8@˜ô§Ù{¯æeÈ•;-0í7À¡—–”_»–r$Q¦ ù݆ût{úè§ie«!@(®g'wÚHK¾U†¥ @ñ…¡Ýœû¶1[‘wôRVl9íËÑ«ƒSÙŒÀó®/6¼BÈ@ÕÞ8 !.ÞÆ-ØlaiAª‹ˆˆ°°´·`³‹w¢pøí{xNp“>òôBž‚­ZAÉ+ ?üû`ÍñÌ [§w; y•£ˆ”}€½MJòÿÆm@:„µîcEUuV‘g#@ÅõéÒöwñÞ#I7‹YƒÓn`/áv’Gã¾?͵}{ ½#M´.G/¯Ú/.MË–<¯‡$n¹Ÿi‡Ÿ“ä5–Ë’7u4 øþ±LÇ6†¦Ù$.M5Á›B½Lj6¼!.^°òµ×GEFFªVEFF¾öú¨Á¬tñÒÙê"¸"ç×=§=½T juBé²n¤•ç–J³3ó_+á»XÚÑ•»0™é”æ–Šã¢SNºøq•­-õIŠçÚv‚Kù¿‡“ï”°õHS ´èåLî\L½ž%ÎË/:u>3ËÂ&Ô†¢´,oÔzÔ²Ô~1pi`ŸÕ¸º°ù{ú™:U¬|Êäžž,4ïöÕ……îLk/¥„ÞCÇð3£tlchšM‡BÈP5n'QÉÙ»ã V¾öú¨û÷†……)[]ƒ>XéìݱÎa%%iBóÁVw#òóžÝ‰2u²{£‹];knåMÁ™"#ˆ‚,q…²[‹•'ç(L¬W 0*”C{ P²sJô³A@ÃÒš ­@r¡P¡¼Ô‘©(Os\,9°°³¥õú`µ]ÔÓÖa_ÿVmU­mô C¿¸6¼BÈPÕÞ8åŒBõpðî7yùÈ×G-^¼xäë£úM^îା¶‡‚€¼ {ó¡ôlg×=,LÙÊå ×lt?‹¤Äï¿óɯ·>;”_A@Qµ Q°ª–aåŒB@X¶*YRœYœË3 q©oš @µhY¢PÈ´-×ãxññ‚>j¿844Ô@‘¦íÿ0ìÍ]Žßž9¸°£9ÅŠc7NîíkoddldâÚmò¯QŬjEî¹¥C=ÍŒŒ….as÷$IYBdÉ¿t¶lÿÃc™z²,!òÜÓó;X¹ŽÛñXlpš,QÞÜðn'cc##kŸ¡_J–ËëñÀ9^!d( C*"Ï~ï-[²dI¿÷–‰»ªQ߇äÌœ9ÿZ|yb×L?!õl¹qдOÞí×¾­«‹OwV~? üø¾Gbå*ô\¶vj÷¶®ÞýçmúÚÿÉÖÍÑÏ7+·yÚY}'žìüÛ¹_Ç»óê‘fIÚ¡u'yãY;¹»§Gà°E[¾ò‹ßòËÝ2FËrƒŽZõÀ†Bªî;×syÆõ˜<¢ì®*OOÛp„š;Ìk÷Ó•‚]çÍ'÷ú¾·¢¼´äÒíBNª™)$ónf~›¶_öäÑ’ò‹§Èe ]•Žjö BHÁãÄuܶŸð‘y¸?ÍÀ4AvûÌ#Ó^î#_[–½åTzš@ërô áДêÞ»z (hÛ?$óÔ¦åÿùýmj•"÷üÚ¹Ÿm:“[Y‰„Ë„p×½—Ÿ­¼Å®cg›ü{OJY{Ru³@vÕˆwèÁ¿_ûù ·*ÃÒÌK¹LyÎö7U¾¬¹Ž]»µ*ºó¸¨‚h^΄ 9õ,1„Bhô¨Êoï»~[-ÉÒ”äo7%+Ÿ”>LXñ0AµîD”òñ‰¿nœÈ> ž’BrþŸç+ÿVmŠœØGŸÅV>É6(M")8qªfÜÚ–£W—„Ñóz @ÀÈkÚßÛBß0³ÿTAÄoc\ylÞ‘é£Wå}¸óú¡ž6BÉå|F—±,¶ê.w,T .V>Õrpœzô7:±ëxÒÐÉm ^i ” °ÏbÖ¼œÕ·½Y£ÄB¤Ž¡F„^Vµ_ †5m^•Gm\ ly(·§Ì;vðÀ\Ë¿'œw"GAxö~nôãC2äÀ–?úë‹U1@©RéÅEŸn½ò45îÌ3—Æ´yor€ðYRªd9CÖœø­Ç•éƒ?>šÉ€Áiš‰†Î ÛõѼ×âcŽ,ÿà»XÏÉÓÛsµ,Ç9^!Ô4jÜÇ«áC½¨84P„è7o‰P*·'À±ìñÍá½ÒoŒ,ØrÙ›×^zgvÓ,sk×^S>qiKˆ2e~»9ó\¼üI¦Â¦ÃûÛöÎ0& E” Uî!|ç79Qönø;#L÷œXÞÇÀ4ÁøÍ‡r>žóUÿ…`ä6u×öÏŒ(Nš—×륷“@!CQ³fN_¿ñ—‚ü<øâ¯ÇÍBMdù¯Kvì>ðáûerY³Äó"Úüû¿“Æ¿ÖÜQ „Ћ!=-uóo¿Õ}U#B¯.Möûê «!„ ÕèW5"ô¢âÒÊë±1¡/œã…B†ªÖðúmnwmÛQ…Í2ô2Q¯«ãÐ@öxçx!„¡¸uo؆^\EaE×öx!„¡ô}ã¤(A/9. XË ‚ /„2”¾oœØã…^z8pf(,1„2Tã÷xEFFÖ3„švë K !„ Õø=^}úôi´¶“¶c@ë°O™ÆI!„B¨9é;¹Þ «G½gÏž°°0}£ …‡ßðŸ~£ò™À!äõW,žào¦Ï¾Lòæa<9ÕM߃AH ©TÒÜ!¼`Ž9ÔÜ! „PKDÓ´¹……§—·m+;õåÏåªÆ={öÜöŠÛmåÁ夹Q»¾^ðÙ[àqyM'ýóD¨˜™[”–7w/Œ ïNnîB¨…’ˆÅI‰ ±11AÁ!–VVªåÏ¥Ç+,,¬m/Ž™³‡——xù|½êîñ‘gŽ'H:ÙªoÀ–DmûbÁº±…Äȵ×{߬þlHrî½^‹ãXê¶Àrô¾›ëºõÌ!Ô ØHE!llmýâÆv í¡Zø¼®jTµ½ê3ß‹˜ð@!UTËR‘}hÖØonx/Ü{þò©#˶Oykm¬Ä,|û…Å>|Ÿo®$§§§Çb« !„B-‚@`$41).*R_øïãöá‡öéÓ§¢¢Â€Ý˜Â{¯ÙaØË] ¾8ãøÿÎqG­Y>¡K[w¿Aó6,ðIøsÛ=CRF!„j*EQŲ¬úBÃæ£+ÕÿÕ±qddäÆ#""„B}º¡ˆôÔ‘òo“€IV ´¡!MµZ–•JyLõ5U6¹ö:Ùß{R¢1èB!„ša /eKKý_m"## œãEq».Ûû]gÚØÆÙÅ΄Pó6zÝð>B!„zQ<—9^†·º8ænÞ¾¾¾Þî•­®êø¢`’p>®Lù”ɾy3Ï·­9 4‡R½!„B¨z.s¼êÑêª×qÐôpùóíºù4ùÁÉ5³¿óx{r ¸æ.ŽÜôKWeå•ã­VB!Ôb=—¯FoupìGlø{I‡Ë^ëÚÆ~ãI[þúØÏÌCç}Ü9~Ñ€AÝ¿¼³íB!ÔR5þ}¼""" nuQVÃ÷§ ¯½œë<éT⤪g´yðûO¿¿±æÎ&3ÿ¼>Ó°B!„š\ã÷x5~_B!„ÐKá9ÞÇ !„B©{.¿ÕˆÐKL.—[ÛØÖ½B¡—]=~9 {¼B!„šÈóú­F„B!Töx!„B5}^!„B¨p¨!„B¨‰àP#B!„PÁ/„Z&mÇ€ÖaŸ¾ü¿7*²¾—{ÿm©5T½´mchš/´f¬:²nQµEƒ~¿Ç+22²ž± ôbc’~íëôÙmqåóÒ Ó¼E¢>›+?ØìG¸xN9WÚlÖ…IÞÜ·ußß’ ø#…‡_wnó^dåA)òÏ/wk=xÕÕ"ÖÐÜiaÛþ£ùš¶”ÞuRxøu‘Š«ŸwWÎ7aS õª–êYר|šYÿ¾á*u_'«ÇÞuS*­{÷Ò3ã]]Æœj¹/bÔò5~WŸ>}³íõR~‘E/'®C—îvù×.§+?œ¥‰1¬µQê¹èBe¤üÁ©‡à3ÀϤ9ƒ|ž˜œÓ‹^{ûO³÷þ;¿›¥ÁWîÐÖ=æ¯^nÇy±ÕÅí¶êhDĹS~æzgýû³öf*?V.×ÔLmœ©Gµdå K­Y7îib2Nì¸kî똱çŸ8‰á»·Ä:ƒPžË¯Ñ£GÞö"âÄcßOÒ±­H$‰|BG}¼)"MÚ¤œ ün„^yF}B„Iç¢ Y`²o^)é0c’k©G’„ˆ»b÷¾!Ö´$éðⱡž"‘HÔ®ïÔM× k–IÒá¥oõð‰D"÷࡟ÍbØ’¨- ðw‰DÝÆwBùQ*²¾—{ßµûÖO õ‰Ü»¼³ñnqɽ­3Â}D"QÐ+/T%_G¾¥çÞëµ8N·$ÔM$ùͽ^¡%G˜Ì£Ÿxo¯ýçûÿšbFHm›="ÔG$‰\ƒÏùý~Ù³4›iÍøNn"‘KЈ/§ÊAû°‘"ÿüâ¾í?ø;QjpšÚ Mûò8fÎ^^Þ~O[8Ñ•y|+M¦½$5œ5ñÝ/‚[?ë );?Å'ð“ʳÖç»Í_êÔVäÚwý½û»?ì&‰Dn~aïoO©“ø¿¾n]–ÅJ«ªGɹ©¾Þïž($uŸVýªe@ÁÏêñ¬Ü\™u­ZQZsD8|ÕΕo÷ðu¹´´à@мò¤]þñínmD"‘ÿü>ÑÛoöÕr µ&åðŸµqdÙö)o­­ì$ÇýúS|ïïŸÝ·4èîòw'½÷ù­à¯ÿ;whmXò†Ù?Þ[TW¾fáÛ/,öáû|s%9===v]Ž«“¥îŸ;lÚ1%‡þ˜¨÷aå´×[Ëÿ½týzä¿ ýî|óî²[UŸ½ò¸MëžØy=â÷ø{f¼ýÓC©¦t˜œ3_½>ùˆÏ÷‡6k-08Mm…¦«05Q”§\øçh Ï»“3_[IjÒÞÕÉÉÉɵ]ï)ÛžHõ,*Ôœ”u^ý '† IDAT±sÇVBHIq‘î‡ÎWŠìñºž¢ßD‹IùµCB—Ýž˜è7p[ŠœÍ;4Ú­ÍÄ“ùðýþaU-‘?Yß»ÍÐÝY yêöþî½N¤ïêê=õL!«–´ »—œyËÕeÌ©Mé¡WQi©É;wlU~ÊÜ‹º3kæôçuUcXX˜þý^Læ¹¢éžs&x©/æZ´2¡äq¿|ÓiñÞˆc¿SüóéÂc¹¬î/¾Ï¾·MkM4n¦á»©iÍ/7F:¾JêÿU½RxÎÝ»Úd]º‘]‘zé¶"¨—›‘™_?—Ô3÷KËŸ¹¯ðèÏK½ž NZ×·uåœm·^+ã%…i…êãkÒÔ[IÄk@ …zU’eD¥RaUóˆ¹ö:Ù?|R¢àŠ‚Ý•=33žcû6ÊÍ8ffTEA…¤)uç[ƒÎÕQÐ:ÿq×…¤òZï·\Ñ€qírï,R|ã¯ËÐkBgsJÏÓZwµ4¥ªÅc^+/{¾òOŽ©‘´°‚•¥G§Q¡m*3jê)ÐP*1*ë1¹—- œ0ÒìÒö‹ù¬–Ò¨³ˆê.CõGÃhH€˜›k:„ôôïãöá‡öéÓ§¢¢ŽqyÖƒ,p l­õʳ>KWL ó÷ :wanô™'bcÿI líììÙuü7KÂ+Ή¯)0úÕœ0G>Íš˜hÚL‘uìÇý’aë7Íèîìî6zbGnõL9'yo]zÿµgï=IIKüß(KŽA_à8ÝÂ[—^Üs6E5 Y¯4µš>›rÌݼ}Û…¾¹è×%A±kÎVT’Tõ4 aŸ…KÑܪódÜnö¨s¿Úß©0ò‡ñÝÃ>;_óªP®cÿqírï}$.º¾ë Õ{|Gs ô=­uWËñ‚¢ÕQÿS\zûÃÙEÇßõs‰D"Á¿¥•^Ü~.G¡¥4ê,¢ºË°Žú£×pŒ,ML,ŒñG_PýV{”Í/õuˆŒŒÜ¸qcDD„PØÀq®(¤µP™ÏÊQ(+¬`uñUûÞ¦y3= 5èü*iÈWUôJ1õè­ˆ9pðP’cX pïÈÜ8xðtšEç^®|K§ÖwèZ¾Ž¹—ŽîÔãS÷ŠÕ+_ìBÎÇ•)Ÿ2Ù7oæYø¶5×ó5¬O¾4‡²†çH›~¸sÏÇŽÇ>z}þ±,@ž~ó,pêû}ÚÚs(&ÿÁâgý0LòÕ‡•‹LÎ;V~µ¿xÑVÝý»yhü—£æìKSNÙ64Mk͇`dxarG~2ÜèâúÝéšKRãYÚØJåU'LÑÓôr-g€ká6nÖ’ÿ8½)4ïÐîû5'¸rû½å—{ä¿k‘]ᄽÕÑ\™©>§µîj©sçjµB|§@›påiå!H’®Ä׺PŠ]Ûqší»úð™*Ç·OqŠúýd†ò-VSiÔUDš6¨¼Îú£ß±‡|wéòŠ8Û ÕŸa /R5… ê|ŒŒŒ=zôž={ÂÂÂêL–çÐβb5O¿¬öm( ©ã‹Ë³ïm úέ뫤A_UÑ«„cÛ¡·Kñ©M¹º)È„ÞýÚ¤îÞúØßÛ8Cç6;÷éÔ•ïÎãíÉú~è“/pÍ]¹é—®<ÊÊ+(*70GÚ¬Ã'»vÏ´ØûÁ‹Îæ*€k×Ε~rüJ¤"~ÏÒuÔ;dW¾]ôç¤ôøÈŸþðÀý­·ý5 rqì¬ØûS·ë¿ùùÉ NÓLË!pëQ˜”y×é\¶oËè­±$5ž5†ïÒ£›ÅÃ?öÆUR‘p`ÅϱDÃ×=Iìæe\KÍÎNºsâÄcÆÞÇ®VcˆãþV@Þß‹–žç…¿b¦ÿi…º«¥.Õk…>}s½ZòǼå‡î§¦=8öÃç»3€ªÞŸÈæ_Ü~zNâ[%0lÜHûûI)ÕTu‘æ ªOé¬?z{Åe'|{KÚéé¹Ìñ2¨Õ\Çð1ÌÅ »W»°‰)ÎÕöÝPß/.Ú6ÓüÝ´ú—}¿J‚ßÃÐ+„ïÚ«“¾x[åœEÚª}7ðhFЖa+m}ßüÔÂá=»tíõæü] f.5nzE[õYµ㛲Ý3…v>wÇ}1ûþ^ÒáÁ²×z†öŸ±ßxÒ–¿>ö3Òfúä æ¡ó>î¿h@‡ €î_Þ–š#mÙmá?¾ÏûkÒèo/·¹ze¿„ݼ}ƒzÍ<8kˆÕ³á|žÏŒY.G¦÷ê6a‹xÔÆ?çú ´¤É½¶vßrŸÓÆ|w±€r00Mm…V¯ÂøLœT°oS”ÿ2M%©ñ¬I§…ë§oâÝÆ·Ç¬«!3{išRA øÙ'V½×¯kHH÷1?e þqË4ÏÚS8öá‚*’rLú6S¹§ê®–ºT¯ú¼½qFmÜ1Ûáôœ]»¼¹¾dôüîBŸ«–"çÜöËT—Ñ!fj{ñ=†²{´s_"¥¡4ê,"-T þ®¹Žú£Ç±W(ŠþÏÞ}Ç5q>pà " *à@ÜŠ qoýY­£ÖY«¶Z«¶Új«¶V[k±vh]­­{T­[@ëÞ[Tpn@öHÈýþ•‘„á|Þ/_¾ ¹{îyî.ä›ç¹çrùر>]ãYƬF3æ3fÒ%žû±³‹“G·Oo?rîâ¹£{VÌáßlü¡ÄŒÉM‹neN*I:ù±{ÿÝqÚ;«zºÕxgóm¤K¼ò÷°šNNîCƒãO†z¼¼ÁÅÒ£÷ŒõvªÞ7"nœ ^ýçžÛéÑÎUª X~þ΃¨˜MzLÐx§*=fl<~+ôÔž?¾þtéåÔ›H>¿à«Ÿ6¼tëîÝëÇþé]±Å—ÓÌi;žÂ™Õ §´ï›Vj½PÿÌBàåö¢Ìj4½¯ë1…mÍ1«wÿ:ØñxÀÈžíÛ´ï1:àpñÞ3&Ô5Ôõoa샯 ‹éÿlšýÃMŠI%Mû¨ E‡zÕŠÀ FœÛ2süOÞÚªóè\ŠŘÑ#æÿõPQÆÞÁàr …‰Ù+88ØÌÔ<ç|VÛ¶l0hh|\¬ñ5‘W !¤ØÓ¼÷ç©û©Bwóöùì ­Ë¼ð Êó-#..6xÏîŽ]º !"#Â.X`ê+Áô/RmŠ’M?ßrêóç] àETˆ÷ñ@V…uçzä@€Lèñ =^2á›>dÂP#€Lj =^2)ø¯ààà|Öà•Vð=^­Zµ"{äV(×xõîÝÛ¼ì%Ålî©ÎÁoþ5­E¥½¹°µ[ë7 ¬@óÊ5^kÖ¬1;{ …ªá—ë¶=±ã÷7*Z˜³>ðâÒ„´pm»8²—E©Ê5¼Ÿ¨YÅAyã×Ö.f\HÍ\@Š áå1h{Œ$„H¹±yú¾UÕjµºzë?‰I™§µÿ·Ëglæå¢v®Þ᣷4">pH‹é!i!Ÿûº¨ÕêãŽ$&]^1¡sµZ­v©á7|IXšéÕô“¢6tÏÙo›¡þô³)ú×QÚViÛ«ƒ×k&½ºÌZøU’ë—¢“û¥¨¤†NȬçhcážÃÚˆuÓ~ºÑqé¡sOÿÔôµ,ÏH1›{V¬<$8>ã×ô¨½Óý]Ü:~{è‘ÎÜÙ—! ŽÊÄåÌÕø${­Y³ÆÏÏÏìz !„ʹcßjßü¾áêÄ5­…⎭اó h\J¡{´wJ¯ñgÛÏXþU“òi—ÖN;p´ãÞ彄šË‹×tYºáÜvá«ßéôáä¦M—w_²oz{¿•oïá¢B{sQçÉUg®Ÿßª¢Å£kgÎ$XñÂ3S”l=wç¶øt!Dò™oúOŽ|ûÏ€NJ!”Åʹ(Vé]GY¦Ù‡ß53qf- 6C'¤‘µpÏamTØ‹Ê-T*cgl©û»>í5lu™ñë–­kgö=Äyâ™â}¼üüüÞ}÷ÝV­Z%%%™°¸”ºs€çÓN¯±‡Uêv}=ïnÚp%U!Åûû?Ñl@£’Šôû;æüSlļé}Uuv©Ñîýoßu:ºlïÃŒÏ.ݧŽlâ¨Rsë<¼…êìîÐä[J Q¹4óóq-_ÎÙ³I—7Ú:[šÛ8 U ·Ì>Ûšî¥-Å+zÕÊø­Z¹b !„.þÌïïøy¨Õê̾ØìÃR^±f-¬ÔË‹ÇvóõT«ÕêJµ;¾¿ì|‚$DfWqëÖ öuW«]½5ÿTlÜÙE£ü=ÕjuíÿÍÜþd1¿Ï™Ü³QÍj®n>ݦl×äÞz{£³ÓÅxË·²Z­®ÙiÒ†›;µõV/Wu’V<•{¡‹;ýû{íjVT«ÕîMú}µ=2WÅ ïR=Nè-Pz´ýMzSO§!t÷×ö¨¨öxg_¼BÄîèáóÑñ¬‹ôTÒжLÙ¥RÊÍ_ôoæ¡V«Õ®u:Oú÷®ÖèáÖwíX]Ôþïû5pQ«k?^ÞиáÓÇsµ1>û*zÛ•ç¹­wçK1›{ºwYv?uÿOµúiçVκÝùwR·!ëÊ}¼á¯÷3SWµ:=jïôÖî>o¯¼žjv™úO'cã¥f^Üψ_Yÿ7"88xþüùAAA¶¶¶¦”í¯í&yÛ•º]ßê÷7o¸’*¤Ø£-4,¡©·Ž„%ߘÛÚ-3¤¹´˜y5%&"F+„–ŽÕÊYeiñš}±Ô˜¤œ}ÉÖUû¼éufBãÆÝÞþxÎßûn$r‹2È@òˬs ¦¯ Úº oúª “·>Èvfjo­;9Ðí£õNœ<¼ã÷‰m+éˆÍsa è4Êjý¿^½ÿÈ‘àÕ“kœœ6hÆñÇ—ViB~ûñjËY›÷¬ÿ¢ö©¯ òññ:Ÿ­ ÜôƒßÍycçœ}4W¯/õѶ3W®ø¦ÊŽ1oýx)5Û†3z£¸[|øàι¢Ž^'{NH¿·qì[³ÂšÍÚzðЊK¿ Ê|[Ò[=;ÿ%û¦{ZyN;x322òÂÜF¶FZ‘!×*Ö÷6ycÚQÉëöØ9¿{Â’aý¸}ø×ð.ÕwàÒõ¨x­fטC‡ïj…H¼´ó²¢XêÙà©B$‡ž—júW)f¬’¶¶eÊ.ÕÅMî1r•èõã¶Glùap-[ÉøáÖwž?ϽÖn^ð‘ ?†X­50çq×/Wmò>Uò~!èÝù©¥»nÝ<°¬u³¥!‘‘×–øééôJ ß0®Ë;[Ý?ßôÇHï'c…ÑjíýÝŸöºÅsÖ¦ŸûºY›]¦þÓÉ@K \¶€—ɘÑ#%IŠzø êáÉ0!„‘gs/äààdÒ:ºèM=Ônƒƒâr=£ _ÚÑ¥þ´3IÑÛUó+I’”xøýêÎWßKϹtÚÕ›»´YtK“ñkÒÉ}Üû47ø»úÿvCó´ÜG!A+>{§«“Sýƒcr…W]Æ9Ÿõßò¥‹$IŠ‹}düŸñWŠ$I’”p`”‡ºÃŸ·µH»úcs'‘A±:I’$)v÷ jÕß;˜ i—´umùS˜F’RÎLkP¹ûЧëäbÎÂy—&Iq{†xÖÿìTòãê Îx¦^œÙÄÉ}Ðöh$I’æú¯þ.~óÃ4™‹U};ãU(IÚ;+»ºøL:‘”¥bÚ;+»¹ûκ”ú¸Î¡-+w^q7ë LskI{gâ3~M<:ÉÇÉsÌÁƒÕËýúÕ»X¶}•mÍ­%í+V³/.c÷k®ÿ꯮9þHbÞ{ÌÀ3X`ʹ/¹÷Úø@—rfz“Æc¿ë[«ý¢›MØO-]Û- ÏÑ€í2°-Sv©6ryçJ#vÇèôí¢œ;ÊÐq4º¼kÿÍÒW»sŽã.ü9{M8U’ò:u ͤ“|ÜúìÌý>"é¢7õprrrrrj5ç|RîçóÙêŒwœßÿ3¾IåFïoŠÔ{†æ]fœ™rÞâ¹Ëó-#"üæò¥‹2ÞeΞ>9fôÈB™ÕülWw=¥ªÐ¦[Öþë …_ÿú%„ÂÚ¹›Ùt8Êäë"•J!é²,®*éá×wÌç¿nÜõ³ïÃM+ÎçŽ šJ]×Í6ãS¶eé ¶i9úbÍêˆÍsa ¤?<0¢mmµZ­V{ܧ}\½:®ývåí,+øTÎè°°+o§HŠNÊìaQU¬ãšÙƒmQªZÍÒÑ®eè3Ú)íöéEU?ÏÌ‹Ÿm«ùUËìÓ0R½¬L\,ÛÃî~/ˆV•kÐÀ!öRh\Ö^#ûTÏ3X µkK«ÝWâï;”X½k—®.‘»ÏÇFŸ /Û¼¾cÞ—ÔêÙ–)»45üø ©Z;ï’Ùû†Œn=ÇQgly—&^¥2Þ.TeëÖ-“ã¸çƒ¡vYäun›r4õQ7ÿÚ¶—ÍùçF¶±ËglµæÒÜ~£vÔúaÃw]Tù+3:B‹’óÙR¼è ë>^ùH]é®]8ûĹÛI:!„Eyÿþµ®œúÅ^Kÿþu3z-ÊwžØÛ.pˆ™ÿœ¼v:ðÏo>[vÅðÔDU ç ªÈý/ß}ý(1áÂÂó7 ¿wïÆÉíÛ¯hËy–µ2«ª€ùJ‹'/7…Bäú(cS}ìÆÓË&´uŠ žÝ¯©ß¤½Ff\å¹°¾tQÛÇ™Ùö‡=gCoED^YÛ«”Å“jd©žB‘í7!²V6]“þø)]›ë-@ÒIuæž ÌâÀDÏl/0I¥…å“¡´ÌøÙhõž2q±\ôüËöÁ]jèÀé-ÐÖ£­Gò‰½çNì‰tmW«RÝe®ì>sfwˆu–®Ö&TR϶LÙ¥úßQ¹£”Ï›_†ÚeÒ !¯£©uÕ!‹×MvšÐs܆ˆÇ¹5¿§Ó囸»Åÿ·fÏ­'Ãù*ÓP‹òÓR¼à ë>^æ÷uIÚ£ŸöêøD‡Þs2Ʋ-Êù¨tã~ñ6ýê<µW–òûfÓ¢á%vNîÚ¼Qã¯øw˜s)#m)á;ñƒ†W§¶«W»VÓ)§Ò­îmÿvH›Æuë6íóãÝŽs~§*W×ã`VGlž çZ@yìbš÷ˆá­ªØÛX(´Q/=2ûNÚ›/d¾jï?Sº†[Ö‰õ¦ôF[©k«¥ðS‘™Ÿ”ÒnŸ¾ž" ! W/[µi­È¶Š•ºŽ³¶7$óÎNÚ{ÇŽ=,éU¥DÎ?&ïÃ*K×nUñîö•+/9´ô)cS©™t诿¥x´ñÈ}©k®žx}LÙ¥ÖÎõ]WvžÍúgÚèŽÒs­î]þÐ¥ÇËß?y2:Çq7Ì`µËè0õhê«Lqïw—¯ù ÂÖ÷z~¸õ®Vˆ<÷RÞ­V–n:uõÂÎW§ôz}DÆ¥ïæ–YÆY‹Šå¿¥x¡|WPPÙ©KQºë†ÈÎ}× ã•E…¾ÿDDF^ü1Û}Y¬œ;LZ|)22222ôĶß?ô/«–UÆî»±k¨sf‡¯M¯O…þÕÚNEqïѹyansïA›…DDFFF^;¶afïª6|‚Àó–bNGlž ë]@U¶z%e趃wµBJººæ‹¹óñÙYsxÆ”eGnD^ š7ñû·þkf½`Ú”Þh•SÇ·›F-ýzõÕdIJ [7sÑŒÇ U/{µÂ”Vd_ETè0Ò_³vâÔ¿]»yqÇ÷cg…¸ê- ™µÿU† ´Tû6² ]¿_[§™³¥°©ÖÆýÎö]÷*¶ò)ëmöJÊÀ¦ìR‹òÆ÷°Þ2öÝ€çnFÞ<¿wÍòÀ;ÂèŽÊ}íŒ.ŸvðË©½y5xþ„Ù]swƒ ·ÑP»ò<Fv¾ ”võÆÿ½btÉuoÿoêžé†Ï:3ZmQ¶Ý7ë~lräƒ×?Þq_käL6P¦niê…€Ýß]aö'¼ ¾ÇëÙ¯ëŠ ¥µ±y.¬w‹òÝ¿›Ù&ì£&^µ[ŒÞë=¦SiSoä÷„¥çè±.[Gµhè÷æâ”×çÿ9®Fö4Sz£-Ê÷œ·d„˜ß¹†g­¦#‚jôµU ! W/{u SZ‘m•©åºÍ[ùy½‹3z4÷m;jƒÍàßÿú F±l+˜µÿ……á‹Uiím%T5ÛTµBQÒ»»ö|Õz Ë^ICwÝ1e—*K·úvÃü×ÓVŒêàÛз븥ç“F·žã˜Çò£Æ8oÙ¢¡ß€ß“{å>îeocÖ®+íÊû@Ùù&Q–j2yÕŸÃ-ÿÜûËÿb ¤Õ*uÖí¹ý>_ý­0wOj‘Á–¦Ç‡8~îv 3ò_NŠ1£GÌÿ%:ê¡¢Œ½ƒÁå ³F\Æ9ŸÕ¶-› k|EFcä•ò*Ó„´n³yðÛžt*ãeÄq Nžoqq±Á{vwìÒM¾pÁ‚B¹Æ ¹âëUa}W#€We•±ûnŒ}ÞµÀ³â8Ï=^2ᆠ2áâz™0Ô z¼dRð=^ÁÁÁù¬ À+­à{¼Zµjõâf/mÄÒvn~ó¯ñ W@~…rWïÞ½ÍË^RÌæž+ Ž7cÓho.líÖzÁÍ̤¥´­Ò¶W¯\_0Pø å¯5kÖ˜½ä¡,ÓìÃï&û—µxÞEP¡ôxùùùHöÒÅþý½v5+ªÕj÷&ý¾Ú©Éx\J¹±ù‹þÍ<ÔjµÚµNçIÿÞÕŠÔË‹ÇvóõT«ÕêJµ;¾¿ì|‚$D|àÓCÒB>÷uQ«Õ5ƉÏ6Ô¨¿|Mh@ Wÿo—ÏØÌËEí\½ÃGoi„RÒå:×qQ«Õj—~ׄ¥=Kã@‘SX³ {¥ßÛ4æiG=&¯Û{`çüî K†õÿáBŠº˜ É=F®½~ÜvàÈ-? ®e+ ¡Ó(«õÿzõþ#G‚WO®qrÚ Ç…ÿ’}Ó=­<§¼yan#ÊBÍåÅk¬Fo8w#lß´r›?œ¼õN{kÅØÉn­?pâäá¿Ol[ÑŠKÀ+M­V?ï*¼jLý®F…B‘ìõî»ï¶jÕ*11ÑÖÖÖÜšiooû5PÕëï¯4²Sˆ*ç}´«ÙO‹Ï¾7«âÖ9Rº,þy\ëR !DEךB!j~/ãQ±ß´Ïwý;iËÕi | ·ÏPùß×B‡îSG6qT áÖyx‹é£v‡&w(£riæçãZÞB”/çìinƒ@QgÞWe 8fý߈àààùóçå#u !ÒnŸW¸û=¾^U®A‡ØK¡qIáÇoHÕÚy—̾ùô‡F´­í¢V«ÕjÏ;âb"ãŒÎ]4T~ºBX:V+g•±œÅköÅRc’tÖUû¼éufBãÆÝÞþxÎßûn$rc3<šÐ€®m‡?ëÌ\#3|öó¨1öPâ3n(@ºG‡f÷®[I­V»Û› ×V êåöDÖ׳ì‹*ó‚WF§WÖÿ îÝ»÷š5küüüž¡zz²Þ¸§‹Ú>~ÈÜȶ?ì9z+"òÊÚ^¥,Lé 3X¾B™õ)IBØT»ñtಠmb‚g÷kê7iï# M@‘¡ ÿ«O=Šú IDATÕ*¯ÿyóÉßQ]ìÖ­Ôô«SÄt½rL:. ¤˜Í=ÕjµûÀ÷Ó3JøoD5—7vØœîÌMdªäÙ°Ã[S¼«)¨ò ±óA±nÚO7:.=tîâ韚¾ö\ê`¦ìB­V«½?:žú 3ëèÿóÓ¬&žã©V«ëL=òìU†< å¯I]Vê:ÎRØÞÌ6Ú{ÇŽ=,éU¥„s}WÅ•gc³VHyìbš÷ˆá­ªØÛX(´Q/=z| ¥tz’¡òíUI¿¾c>ÿu㮟}nZq>9ß­Ã+HåüÆìÏ|Nñ᪌ÏÇRÜáo>XYlXÀ8ŸâEáz@Fó’~‘¹æ !Rgþz®ÿ~(TM¾ý7(hÏÎWý0¶¥*è³Þ-ýyýež¤ {`Q¹eƒJeJÚÙd¿„äE>ñª†_®Û–iûêqÕmžqf½Rؽvmå†ÐÌc)ÅX¶Wã`Y`† ë>^ùH]é®]8ûÄ…ˆâmGúkÖNœú÷±k7/îø~ì¬÷C½m-ÊwßÃzËØwvž»yóüÞ5ËÕ+)C·¼«RÒÕ5_̽˜Ùs¥*á\A¹ÿà廣%fý£ªÐAoù†ª—raáŒù…„ß»wãäöíW´å<ËZ™Õ@¼òT.ý¾ŸZëÄç­‹ÔJñGgûÛrØT ]”{òlò©Oê¸õÙù¸›#aï0OïñG“²gx"­.þÌïïøy¨Õê'³n ÍÒ5eCBèb¼å[Y­V×ì4iÃÍT}­Ó3›XеÕW ?íÕ ŠºRëyW5)76O÷ªZ­VWo=âç#1éB'' ¡õ'´7oïÒàó³?ÛKv÷ô¶ë‘dnós×<ëÞÐS”ôhû›õ2ºt÷×ö¨¨öxg_¼BÄîèáóÑñdƒ¢s²°óÚìѲÿÜNÏý¤ž=`¦õl£¢{µjž5|št|óãE;Ö+¹wÚÔm÷uϽÇ×èÞkýÃú€Á¾îjµk£·æŸŠ;»h”¿§Z­®ý¿™û£·LïÕ¿£ô™5‹ÙÜӽ˲û©û‡xªÕ•‡Çç:|©†§¨›TÏLúë ÷å–×¹úø@”ª\Ã;S-O'[ááE“ T–hôV³GëÖ^IBÝÃ}ËŽ•ëݽ²E^å:9Mûû€‚UX÷ñ2¿¯KÒý´WÇ'ÚÛW¬ë¼•Ÿ×»8£Gsß¶£6Ø þý¯jBYºÕ·濞¶bT߆¾]Ç-=Ÿ¬°(ßý»™mÂ>jâáU»Åè½Þc:•ÎüLTÂwâ ¯NmW¯v­¦SNdýeQ®›Þò PZ[ÝÛþí6ëÖmÚçÇ»çüþNU>f •Ë€9SªýlÒòͳÆ-S šûsàXC“gó`x"­&ä—YçL_´uAßôU&o} 3:K7/é÷6Ž}kVX³Y[Z1Âbé—A ¹^ïúg !4W~_c=fÓÅðÛß.s`J¯ñÜÇ->|pçÜNÑG¯¿“nê¤ãböê_ýéî­Øyp¨+.d¼1è¢÷/ÝgÑfPãRŠü4?KÍßqÚ‰¢¿(Åk5Û¸Æ:|W+D⥗ÅRÏßH"94ð¼TÓ¿JÆß=¢soXéÐfòÈJGfÿt"ǵJ:½{@gÚ¦ SØÕö¶gêÁµgâÍœ-nlï…üöãÕ–³6ïYÿEíS_<äããu>[¸é¿›óÆÎ9›l°9évTñœçÓOÁŠÒ]7„nXÖºÙÒÈÈkKüìr¾·‹o5•뜴ú_nÆš–/&¨(Õð­VÉ›V\H"ý~à²3.ovs±4©“NNÈaÌè‘’$E=|õðd˜ÂȳY™¸$ðeœóYÿ-_ºH’¤¸ØGÆÿ¥H’”¶ k%''§úŒÓÜZÒ¾bõ1ûât’$I’æú¯þêšã$JI'?öqí½#.søà¡µ>8’˜µœ”3ÓTî¾â¶6{éWlîä12(6£ÄØÝƒªUï`‚ÙÒ„/iëÚò§0$in-iïì=ñP|Æ"‰G'ù8yŽ9˜u³ÚÈå+yŒØ£Ë]™Ñû2J×ÞYÙÍÝwÖ¥ÔÌg5¡-+w^q7=ûþ‰Û3ijþg§’%Isc¿«ÿo74¦¯ž~ÿŸ~Uj;/IRú½5½*מx8!£ f5?{ͳ2XTʹ/¹÷Úø@—rfz“Æc¿ë[«ý¢›MØO-]Û- ×dî ïfÅø}#d—tb’[Ÿ™UÉ~ø 2Óê™ýðg¯ƒ—›Içª.zS§¬šýp%-Ëë.ëkÐä+y=xÿØGõê|p0Aski'Ïîݺ±¬³Ï”SÉ’‘r ísþ999é9 x,Ï·Œˆð›Ë—.Êx—9{úä˜Ñ#M½„dr׳]M¼ô,+÷›Ø~v¿£G5°K;q:\á>"ûäÙ³¡qéµò.ǺjŸ7½–MhÜøïvmüZuü_÷殙׊©ÔuÝl3~´,]Á6-¯o+„JÛ .*q_ï6L.P¡(^s@Wë>_kùõZ£Ú”Wm7¡œÙç(…x/ ¨R¨TJ……¥*óMLßäYEöG%I—ûåeS}ìÆÓí÷oÛ¼kv¿ïð÷®™-‹ !J e–’ž¼2ó»!I¥…å“"•––fLwV(U—–t’E¹Ç7õ.›m}]Ô¿ã‡Ìþûž•MÜÊØ¤Ûp°¾yžVϱ¹R†´³öÇ¡®·þ8W¡÷¬êOÚÌm~–šçZOßC¶m=’¿ß{îĹH×îµ*ÕnQfÎî3gRC¬ëŒsµ~Zh® Ñú©Üúú¿_|¹b𤧋Ú:S6m„6*$,ÑZíVÚÂ`ëô1¾÷žœ„ E¶ßÄãvjŽ&T˜±£ Éqø 5Ê„z湡Ü/7“ÎU!_ãe÷äwC³%M.P!Šy¾Ñ³DÏ?=U†Ÿ£2ëp©¹ûÜ”¿(h…uçzÂÈäY¥Mi[‘“ù‘SûèZd¢¾OŸ&O¤}– Y©k«¥ðS‘™—î§Ý>}=%çëÝZßlâ\Ë4p!›G娀‰“Ž ­ž“]ÝA]ŠXø×Ò?®¸öíUÅêY›Ÿkošï¬,]»UÅ»ÛW®¼äÐÒ§ŒM¥f>Ò¡¿þ:”âÑÆ#?·*T”hüÁµ¯|·/:Ï=ðl›–âÿ¾0¤XÓ×k¿fÖlqaþÞËÊÔše{†&¡—Ÿ)êÏVó›–3 ´rïõF…°“IM7+-8™]±g;ÄÈŸB™Õ ƒÁɳVÎÍš”¼ôǺ$IJ ÛøÍO¤œ/1³&Ò>ˆTNßnµôëÕW“%)%lÝÌE7r•¯w6±6ç2'ö¶ œ0bæ?'¯E„‡üó›Ï–]IS™6éX2°z®ºØÔЫÌÁ٠ê èîjùÌÍ7uO a©ömdº~¿¶N3gKaS­ûí»îUlåS:_oñBUéõ©}­÷,<”,ÝùØtz|DØ•+—/9´cù·o·}q¬ßô/:8* µNÿñ5ïee¤9ö‡ÁIèyìG3§¨-ˤ:˜Ý´¼˜[ ¥ûÈ-ΛçW"û1»bqê…€Ýß]Q„n²'z¼€Âdpòlñ“FØüÞÉ£²W³1‡êŽn‘ë–ŠæM¤}† ‹ò=ç-!æw®áY«éˆ Z#}msýeÐ7›8×2¥ü¾Ù´hx‰“»6oÔ¸Åëþfç\Jiê¤ãý«çn«uµÞýÜ…¢î Î_,ñ,Í7uO Q¬Jko+¡ªÙ¦ªŠ’ÞmÜ…°oä«Î÷üæ×êŽù¨‰JÉè4Ó’öФέZù·éÔgìi~Ÿ¯Þ»t ›•‘Öé?¾æï½¬Œ4G¿ìçƒé÷40sŠºQ¦ÕÁì¦åÅìUÅK•)aûujn9†qz|؉ãçnçêûFPŒ=2`þ/ÑQ…eì .Ç5^xµdœóYmÛ²iÀ ¡ñq±ÆWÔh4F^)CÒÑšö¿>õàª^¦\ ÿÔjuddäó®Å‹+Ï·Œ¸¸Øà=»;vé&„ˆŒ_¸`©×Àó'ibî_]ûí†ÿŸýI]^:; (,‰Þoлֵç÷s[•æÂS/n'àåñZËEaŒzx‰qq=Ð ¼ \ÁßN"888Ÿux¥|W«V­äÈ^ZÃßñðB*”¨öîÝÛüì%%_ß:kD§úUÔjµZíéÛ냟ƒ"RMŠ{Ú› [»µ^p“^d…rך5kÌÍ^É—~îëÿö¢;Þ£æ­Û¶kûÆùc'®ûì‡S¿!åYé4¾ȪPz¼üüüÌË^Ú›O˜y¢êäÖÌÖ±±wõZ Z÷ûpÁŽ-“kK>õI·>;ã3—LØ;ÌÓ{üѬ·ŽÒbzHZÈç¾.jµºÆ¸#QVÑ„´pmõÕÂO{5¨¢®ÔzÞUMÊÍÓßð­ªV«ÕÕ[øùHLºé0SaÍj4+{iï®:£lþþl_ò *éXÜ„úÙù/Ù7ÝÓÊsÚÁ›‘‘‘æ62ö ]š+¿¯±³ébøío—90¥×øîã–>¸sn§è€£×ß!z€ÂRˆ_’íçç÷î»ï¶jÕ*))ïÜÒܽxWTðv3ëkÀòÉ®ó§ïûU°Rª¬ø§ØˆyÓû4ªêìR£Ýûß¾ëttÙÞ‡ @€BbÞWeÜF5ëÿFž?~PP­m¾¾%¾P¨Ôµ+Ù!„H½u$,ùÆñÖns³<í£嬞OÝÀ+μ¯ Êý¿!ÁÁÁ½{÷^³fŸŸ_ž…[–¯^^¬9w=Aª^&g§—Bd{D’tyŽy[E¡T=îä“t’E¹Ç7õækv€, %s˜•º„ª þ}¼µÿÍ[q%5ëÃÚØ‰:¥Mi[‘“”1¨}t-21÷` ÒB)$ÝãÇMZEX;7p!›G1¶äQ(ÁˬÔ%„ªJýgT;äëî}¦,Ùqôü¥óÇWÎ~§}—™çR¬œ›5)yéu!I’”¶ñ›Ÿ.H¹®S•p® ŠÜðò݇ѵ¦¬"„EùÎ{ÛN1óŸ“×"ÂÃNþùÍgË®¤=SË +”àe^êB…mÍ1«wÿ:ØñxÀÈžíÛ´ï1:àpñÞ3&ÔµÅLaó{'Ê^ÍÆª;º…žKðKøNü áÕ©íêÕ®Õtʉ$SVBYÊï›M‹†—Ø9¹kóF[¼þáßavÎ¥v…E1fôÈ€ù¿DG=B”±wxöƒƒƒÍL]ÀsqÎgµm˦ƒ†ÆÇÅ_Q£ÑÈ+ð²Ëó-#..6xÏîŽ]º !"#Â.XPð<¤.½Y Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBðBÑõßÞ [ÛâY P¼PtµïØ%Ç*Õó®ðÜ$%%æø€BEŠ.†2#x¡èb¨ 3†Qt1Ô=^(ºjÈŒà…¢‹¡F€ÌjDÑÅP#@fôx¡èb¨ 3‚Š.†2c¨EC™Ñã…¢‹¡F€Ì^(ºjÈŒ¡F] 5dFŠ.†2#x¡èb¨ 3†Qt1Ô=^(ºjÈŒà…¢‹¡F€ÌjDÑÅP#@fôx¡èb¨ 3‚Š.†2c¨EC™Ñã…¢‹¡F€Ì^(ºjÈŒ¡F] 5dFŠ.†2#x¡èb¨ 3†Qt1Ô=^(ºjÈŒà…¢‹¡F€ÌjDÑÅP#@fôx¡èb¨ 3‚Š.†2c¨EC™Ñã…¢‹¡F€Ì^(ºjÈŒ¡F] 5dFŠ.†2#x¡èb¨ 3†Qt1Ô=^(ºjÈŒà…¢‹¡F€ÌjDÑÅP#@fôx¡èb¨ 3‚Š.†2c¨EC™Ñã ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈ„à ‚€L^2!xÈD•õ—訇ϫ¯“d„‘¡F^ 5>“d„‘¡ÆÁP#À, 5Êía$rðR`¨@&/™¼dBð Á @&/™¼dBð Á @&¯B|â£êŽöŽMÂ4y>k|áüm¼^†i®4u´wp´oýÇít!„.öð7þåíÕ]:›(=ïú€— ßÕh"]ì±ï^ï9çŒVX7žòïŠw½‹+òY’MݯOG|) …Êʲ@«^pôx™Bwâ‡>ÝgŸÔëoYñ¾Ïk ‘~wË„Nõ=*Ú;8Ú;8–­êûú§ëB“³uƒéâÏ.ÙÒÍÁÑÞµÙ°ßNÇë„B$Ÿüħ¢“ºbËù×r¦Ý\9¸ªƒ£½C•~ËBSó½ !¤¤+k?ëë[­¼½ƒ£½³OûQó<Ôæù(Tôx™àúâ7»_º®Võ&mZ5®®B!Òã¯9]¢FÓÚe¬®9z5è—‘ÿKw9üu}ÛÇë…þ0bJ1{‡bB$\Þ8¥Wºúðâ.ކ£®öÁ®{¼÷o´xÍï›-‹ßªb­¹šÏMhÃÿÜvl`’ {ç Éá‘Ç×LïvâþöÀé ŠE|êµüöáÓÐãe‚¸K×5B8¿½vÕøú%ï1K—á[®…Ù¼ìÇY³Zºh˜Z¹yÕ”,+V¿çò¥+»Æ» !â6Ïý÷vºá›ÝûÍ?n‰bM?ßüÇpOEþ7‘zaÁÌÀ$!¬[üxâ©3¶ŒP !®-üzWT²á§tz« ÁËdáënO}ò«.öÄÏoÖqªX¥V½ú [ô[)„"eµséÚÝ«¸BaëÕµ£³BÜ<~+UrwÛ?!éB=|õòÑ53¯Ëç&t.»-„µúµ«h)%j÷hë „Ð\>|ýžÁ§n¦À>ƼLàÒëuO¥Q›ßëôÞºðŒ€’phúð9ÁZ+ŸÁ_Ìûíçoú: !„$e½+Cw‘«Út+3=ó&Œ<ɨ"ÏÁË¥|§nX3ÚS!Äýõït~c¤F¤? y$„(ÖzÊ´Qýÿ×¾š.9÷z7¶l¾œ$II—6o B—zÎÖ7ReìÜ‘BÄí|¿ç'»¦‹üoBY²z'!„8»rW¤VHñg6î~(„°ôhìVÖàS.Vϸ—@^^&±(Óbú?«Þ®"„¸³zx—ñÿ>(Q½Ž£"eÇ'Lûâý¾CWÇèYíêw­<ª{Vk;'La×yl' ƒ›P–j4uÕÏ]J qkÙ›ý~<¨,“ßM«1b²¿­©{ÇÖ«Q¯N. "„•ßþ¤­½­á§8(l¼ÛšÊ¾ՌÍuBDü=¨Ëô¨·Mmïj­»ºñ÷µá ¦Œ÷ʽJ•q ¾ê䘖"DñªÝ¾X;¯‹£áÜ%„P¨*¾þóš¼-„öÔ7ÿ±â~í)Ëò· •sÿ¥»~ݺZÅÛ‘‰Åœê½>mÓÖO¼¦0ö(dŠ1£GÌÿ%:êáó® ðœmÛ²iÀ ¡ñq±ÆÓh4eìä©àE–ç[F\\lðžÝ»tBDF„/\°€/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð‰ÁK’¤”䔸Gq¢cccbâSSÓ$I*€Z$ t­6ê`¢Ð†/òS7 Ó@¡Bx ºz HQ™¸\ZjZ\lüå‹×®‡FE=B888º»W©Q³–mñâ*•r¤è]=†ÎöXµÏïën©wyeñªzwªn§Bhoþêﻼï Ñ®ú.œ:ÿÝÅi¢Í/gWuwxJ“OjÐ>pàþÿÞ~Z=3™¼ÒRÓîß»·k×vDze»÷ì央(„ˆŒß´aýÚN»•¯P>==ÝÀÚ Uã¯7ÌhP<óW¥­ÚÙàV•eZLžÛÂÌ6lÛ o&F/Þs¿Ëå-2J8µlã}¯1¯W)VÆ«€ªŠ ¼ûn$IJˆO ÚݰQã·su«leeeeeåVÙ}ÐÐáõ4 ܳÓÒÊZ¡P*Á¢”{­ÚOÔªê`¥B}hN¿úNŽöÕÚŽ_{#5cѧcyñ»Þl2åbڥϔwp´¯:ƬV=Kí[kmyä÷í·µ‰;ºäßGµ‡ts±Ì1Ô˜r}ã§½êUr´wpto>4àptºb¶¾áRëã“)BݽU܆Ç !Ä£í}]«p4Ù¬†€WIÞÁ+5%54ôrùòš·­9¢[IDATl•œœeEA\¤¯¯Îz­³]ƒÁÝÎÿ±ázšBµoQpz³ámÊYd[(ýÞöYë‹þåË~«Ur©ÙáƒïÇ©/z ,Û°e¹;OÇJ©×Ï–ì=Ö7qÿÑ»ZÍíCUnWËÎ`¿ xåå¼ÒuÒÇ÷+8©…[·n•$éɬÀ­[· !*Tpºyó†…ÊÂ@ Uão6ïÙ˜ñ/pó”Ú6i·OG(«ù{¾–±„­G+O«‚L$Y묗ñ:Ûú¼õºúêò5¡©"ýÞ®ET­‡·´Ï±§Ro M¾þ]suf ¬ÐäË+)Ñ1k·Vu­/퉻{tBÍîݺ»Fî<sj×­²-–5u2xår\µzõjC *!ã/»,$IB(TVO²ŒÒÒ²@' ¾Þ,ç‚ú.æÕ¿Ÿë/ÿ}éýwN.>^¼Ã*ßÒ¹—”$‹zóÏní[.GÕu^í=’fŸ=~6²rÏÚ.>~efï8u*5¤X½‰nÖæµ¼RòÎ;*•£cÙ;·#…:uÊúTƯwnGº¸ºét†f5êa¥®]QÜ<‘–ñkêíS×Rr5*-”B’t¦«·ÎzåUg«ª½ßò¼»~ÑæUKÏ–î6¤¾]®%¬V7ŒÊU?e)Ÿ6Îw·þõ×E¿:el\ZÔ•üñçdö^¶ùh xeä¼,­,=<½< „˜:uêàÁƒííííííŸbQ¡×/ŽmªT®Ö`èïÑÍlsפdóÉ_™ìWË«ZÃÉfµ*ks?kB…P©;k Lnýûyꉨ,í?{ÛŸï”Ü6±cÃ:uuÿgh‰JѪX•v>VÂÒ»}5!%}Úº áиiE+³^5Š1£GÌÿ%:ê¡‘…´ZmLtÌŽÿÚÙ•héç_ѹ’""üÖÞ À„„ø·† S*ýGÏÅËXg<_Û¶l0hh|\¬ñÅ4M{yªx‘åù–¼gwÇ.Ý„‘á ,0i–J¥*_¡üÐáïœ:q|Çö­wïÜB”¯àäííS¿Aô´Ô0Á¼Œu¯6Soožž®Óé|êÖkа±ÒÂB¡:N«Ñ$%%Ì÷d‚—±ÎàfÆ}¥$IÒ¤¥iŒ\õâyë ^U̳ Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼dBð Á @&/™¼d¢zÞ^2–––ñq±Ï»€—=^2!xÈ„à ‚€L^2!xÈ„à ‚€L^@&¥R™’œü¼kxE¤§k …R™-k¼€L%K•¾uózJJ²$IÏ».€—›V«MNNNIN.YªTÖÇùÊ “§Wõ3§NzV¯nW¢„B¡xÞÕ¼Ä EBBü…³çjש›õq‚©TéÒ5jÕºzår\l¬N§{ÞռĔJe‰’%kÔò.UºtÖÇ ^2¹tñ|cßæ±b2~~ÞÕA¥J—Îñ" ƒWõšÏXÁë9hìÛüyW˜-£åY¼žƒg?làeĬF™¼^ iWç4V·\®Íñ¸6|‘ŸºI@˜ÆÈ2æ– òËhð’¢7vq´wp~cý½ô̇öq-ÿ¿qµýÌMdþ+[¹N«~“î¿“VPåíÍ_[¨›ý|CSà%KÑ»”uz3(>ã×ô¨ O›—sjóÕÁ³'Ú)‹WíлSu;ò5/‚<ß‘B¤ìþêç³…xCo…Ê÷»]öïÞ±aÞ8Õî{øö_z-µð6øÑÞÛ>¹óKì>Ü´þßÒfç'e™“ç~Ú¦¬EaÔ ˜)Ï·r‹þÃ[Ä,ž¾>2=÷“)×7~Ú«^%G{G÷æCG§ )fë.µ>>™"„ÐÝ[ÕÙÁÁmhp¼B<ÚÞ×µúGõ$8‹•ªzxxÕ¬Û´ó OÿÜ4¢TДÉÿÞ× !t±'{§UUGG{纯¾5"³/LJ¹¾ñ³> \í+Ôh?aÓmò‰ª«{lÜ—4ÐÍsÌᤌ!³æ³×ÎÐ@íàX¡NÿO<Š=³px³ÊöŽ]fì‹zÜ2=ÍÉqkúõ_ömT¹‚£c•Ö¬¿•&âw½ÙdÊÅ´KŸ5(ïàh_uÌ¡$³w}Þ4·7OèøæêòŸþ»zBýJ!„H Y8ºC½ÊöŽöå¼üG/>›ðôëéQûf¾îSÞÁÑѳÓGÃÓDö¡Æ¬Ò£‚>m¡öò×µT³Ë4|P =2ä݇¢th7õ]—C3Ž%dB4©û˜ÿªL\}àÔ‰ ù]£æ¼ñΚÛ:»Zí*G8xG#DâÅ—ÅÒN^O"ùêž³R­¶U‹å±=E‰†ïŒ¬žz`õ©8‘~wè^Ÿñœ²ùàñàßz&ü>°÷÷çR„ÐEï™ØyØ ÑççÝÇOÝ1oH­âÆ¿ä%íâ/s®´ú~Ç›gøœø¢ÿ›oNªÜ®–]ÞßÄ¢rðªZ<-òÚ£ÔÈÚmÙçÇo5®êV³Óä_>®ºtáé¤ô;ÿÎZ›Úýçß&vªíæìæíßwpk'K£e–h=cÖð5½˜ø–kÔÙã¿å_˳A¯GyEÿwð¶Æ`s2.«rè9ý=_GKE±ÊÝFøYžÙy¥°¿JYJ üðÃu%'ý³lDuÛ,»ÌÆ{ØûükWvv®ÖdÀ—3Z'íüçÊãpcÝlú¬¡M*;{´÷ã'5–-9§§–šÈÍ: Ý]þ¶Ÿú¸Xå£ÌxEkàñBÝM¼°vïØöÚkv94éª!e¿Éã]éñr†š“±~¶¢%at`³ XW{{ùæ)Uö¼ßéÝuá/ÑÒEmóæw‘íçýwùÖýnmê]ÚÂøk|ÛTŽß·j×Í'ÃÏ\f&C!˜ïšM ñ ñ94užœ¢¤ï‡ú\þáÛàèÌ^k熕ÅÅ £ruþ(Kù´q¾»õ¯¿.:øÕ)cãÒ¢®tà?${´÷2áJ()îèo¿],Ö¬O;+ug),èbæÅeš»G<,Y£jIç†nŠËÛO?Ê–”¶elEâãÎ(mLXD¢ùÁps QZ(…$^×—òµÚcWmœèôïÈÎã·ÜÑ !„&âÈyMíQ#ZWµ·±Phž¿ótôW{óÀÅØÌÆß?q"ºtM·×r^‹2ͧm\Òåò¤n£×f^)on™öÎúJ1‹;YŠ”'#ŒùjB¡rycÚë]¿HÎx#¶¨Ðerß»ßòÕ†ãaáá¡§v/ùò“E—Ó„V›6±»ºfŸ¦^‹JVÂÆ£m•;[wÜunS§”þÍ¥ÇݺzùrÈ…Ó¶ýñÕàÖÝ>òÿêëNŽJ•S§÷Ú¤­÷ñŸGÂn\Øöíèo.Vü¶­E…Îö²úgô;s¶Ÿ½qãlÐÊ¥»ok¬*µhZòÂ’5“$))tÝ—?ž3¯/È`s ì;ç ªˆ}Bî<ŒŠI,œ»Œ*K4øpíš1¥Vé:y׃t¡*[ÃEúïþ;!%]Yõé÷²v(¥îŸ>ié‘ëW|ÿëóno ö¶ÑWïrfmþ¹é¡÷º¸ížV˜]¦ƒbè`ÊŽàEõd„ñY†…B¼VoÜÇM-Åãa6eiÿÙÛþ|§ä¶‰Ö©Û¨Ûø?CKTʈVŪ´ó±–Þí«Ù¡(éÓÖ]‡ÆM+Zé-WҜضi³¦-Ûö5gOšÿ×þ5¸²µ¢|Ï_Ö~]ÿüç×kùö:›a¬žP«˜Ê2m¾ß² fùðÖõ}txoÉùd…Bo4õçQ¶ ÚºVtk8ê@ý1~¯™3Ìe¤9ú•l>ybã+“ýjyUk8ùXa]D®,ÝtÊú•#T¼ÙmÚÞGe{þø]»Ð \ݽòy¿K™§Ã±V^cÆUÚ4¼‘OÓÞ ’ßX°rBMC㺖ÿ7oËìê[‡ýoÚÞhE3Ë4tP =@Qòd„QïP£bÌè‘ó‰Žzø<êV„\ºx¾±os¾€WÒîÛzôꓟû‡Œ"#Â.XÀ8Ï*÷£Þ¡FSîîcžôlåþ!+z¼ò)÷Æ‚šÕ€lŒŒ02ÔPŒŒ02Ôøï:Àÿ7 FÁ(# çºlhaÊIEND®B`‚kabikaboo-1.7/help/C/legal.xml000644 001750 001750 00000007476 11311565434 014444 0ustar00000000 000000 Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License (GFDL), Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. You can find a copy of the GFDL at this link or in the file COPYING-DOCS distributed with this manual. This manual is part of a collection of GNOME manuals distributed under the GFDL. If you want to distribute this manual separately from the collection, you can do so by adding a copy of the license to the manual, as described in section 6 of the license. Many of the names used by companies to distinguish their products and services are claimed as trademarks. Where those names appear in any GNOME documentation, and the members of the GNOME Documentation Project are made aware of those trademarks, then the names are in capital letters or initial capital letters. DOCUMENT AND MODIFIED VERSIONS OF THE DOCUMENT ARE PROVIDED UNDER THE TERMS OF THE GNU FREE DOCUMENTATION LICENSE WITH THE FURTHER UNDERSTANDING THAT: DOCUMENT IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE DOCUMENT OR MODIFIED VERSION OF THE DOCUMENT IS FREE OF DEFECTS MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY, ACCURACY, AND PERFORMANCE OF THE DOCUMENT OR MODIFIED VERSION OF THE DOCUMENT IS WITH YOU. SHOULD ANY DOCUMENT OR MODIFIED VERSION PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL WRITER, AUTHOR OR ANY CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY DOCUMENT OR MODIFIED VERSION OF THE DOCUMENT IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER; AND UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL THE AUTHOR, INITIAL WRITER, ANY CONTRIBUTOR, OR ANY DISTRIBUTOR OF THE DOCUMENT OR MODIFIED VERSION OF THE DOCUMENT, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER DAMAGES OR LOSSES ARISING OUT OF OR RELATING TO USE OF THE DOCUMENT AND MODIFIED VERSIONS OF THE DOCUMENT, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. kabikaboo-1.7/man/000755 001750 001750 00000000000 11306741153 012257 5ustar00000000 000000 kabikaboo-1.7/man/kabikaboo.1000644 001750 001750 00000003075 11306741153 014270 0ustar00000000 000000 .\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH KABIKABOO 1 "JUNE 17 2009" Linux "User Manuals" .SH NAME kabikaboo \- recursive writing assistant .SH SYNOPSIS .B kabikaboo [FILE]... .SH DESCRIPTION .B kabikaboo is a tree branching text organizer. It is meant to be used to help aid in the writing of novels or other large documents. A recursive viewing feature allows one to see any section of the tree. You can see an overview of all the children or grandchildren of any given node in the tree, for reference. Each text node can have any arrangement of children, and the nodes can be moved around freely. A tabbed notebook allows users to keep track of the most recently opened nodes, easing movement within a large tree. This tree-based note pad could be used for any purposes - not only for novels. It could be used to plan a technical manual, todo lists, anything that would benefit from tree-based text organization. Kabikaboo is not meant to actually write a document - you should use AbiWord/OpenOffice/LaTeX or some other editor for that purpose.. Free and open source, created with Python, PyGTK, Geany, Glade. SH OPTIONS No options available. .SH FILES .I ~/.kabikaboo/settings.txt .RS The user's saved settings file. Includes last opened file, last directory, window size, viewing preferencs, and more. It is autogenerated upon running. .SH BUGS If you find a bug, please run kabikaboo from command line, reproduce the bug, and report the output errors to: https://bugs.launchpad.net/kabikaboo .SH AUTHOR Dave Kerr .SH "SEE ALSO" .BR abiword (1) kabikaboo-1.7/kabikaboo.desktop000644 001750 001750 00000000357 11312745020 015020 0ustar00000000 000000 [Desktop Entry] Exec=kabikaboo Icon=/usr/share/kabikaboo/kabikaboo.png Terminal=false Type=Application Categories=GNOME;GTK;Office; StartupNotify=false Name=Kabikaboo GenericName=Writing Assistant Comment=Manage large collections of notes kabikaboo-1.7/kabikaboo.png000644 001750 001750 00000015073 11306520755 014145 0ustar00000000 000000 ‰PNG  IHDR€€Ã>aËsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÙ1—¦¾µ»IDATxÚík[çyßï{.8vØ]’")Q¤iRwʲÕZÎ¥µÈÚÛµœºµ‰$Ó®3Ó~I2“~ÈLowÒ̸™ôCë¶n;MÇ”"'c'Ó‹Û*Ž,É‘5r§íø¦(¯Ë½qï‹Åý\Þ§p€Å‚À.€Ý%iùüg8À9xñþÿïÿyÞç=„ $H A‚ $H A‚ $H A‚ $H A‚ $H A‚ $H A‚ üäAÝÎåóîʘgŒð¥ˆ”R>¨šc骑Z ë”j#˜þ{ ]ï"¼vÞù‚ ÿ°Æ&4¤uEHè#Fˆ‚ø¯_1„õzCŒ~€1!ƉBEDõ*b„°VEDˆªUŒD ‘šˆ¨îcB?»3×ø#ãÍ~‘Î.ÙÒoÊX!úµÛ"€7~™ ?°¿ ü,€“Nó¾gÏ“=r$êXéÔ‰êmd³«6å ¶=”•BY)ŒçbpX-‚jjµ:A`káÀ¨ÏxªÈ˜[%?bp¬Í>”øA!H£—Qñëï‰KaB1`¢:&¢Ð1ßGŒi<"¢0DÂc Ư7^ ê †¨^ bH"Ac"L½ ¸Þø\¿†Da[§èI¸¹¯÷]/?Ãq?°_Hçó<úìy¼ñC=Èߞ؛ÝàfgX-Û\Û¸‹É£’Îdq]Û¶Ig²h­9®J)*• ¥R‰(Šð}Ÿ¥ÕUV«U¢¥k®%¥âíê<­êÓaµ‰™«?`í~o[~öUß>ï>j0=|ˆ3¿ø,N&&ìò=7F0hŒE$Ä#Íb$Ænл3 ùƒ'9~ü=AЕí]×%—Ë122B†„aÈÄÄa²²<ÉÔÊ€Ôh–3Ÿù8ÙÉüöäwì[¯Žâ;ÇveÝs³³»÷8¶moKZ;ñÍÏi>VJ±¸¸H:=º«¶„aÈÚÚ­‚Ùt€&ÃÏ DºŒz‘XªmÚ×}Ê77s+W‰^º¼Af¦•ôùJäés/„ßÝ·YÀ+Ÿw0&z 8Ìqæ3#5’m¯¿Â(f £ÜûþG†¶îååePŠñññÖhÞn´wßýa²ººÊý÷ß·ë0%Òn5&º-Ö«ö€ßá9ÂvÓ¾¹™®^›À«–ÉN_ižS”’/|øùèôÃãPxõ¼ý3óMP“¹£‡xø©ãx©­s|Ù®¦ßø;[ÁÍŸ&Úºçææ¸÷Þ{[£±“ N» Àu]Ö×שTÊÜu×Ñ]‡‘Q»€2A\ÀÚ¬´ÊÄ­L°sÔ·—šÛkû›}677ǵ©†Õ;A‘©K¨Xô‚úµ'/„/öËåÀxísÖ§EøC pàä=<øñ¡m«w§[M_"fK÷rø‘3C[÷ÜÜZk&&& 3mçèFv/'p‡ÅÅE²™ Ùlv×adÜ+n:!m. 6‡yk¹if }ÖœÌÍßàÚõùù¡Oîò;(5µôÛg/ÿj>íÁÈwþŽˆüÀ8òð{8}ö¦™p‡ÅÓæƒ®&Q£3š;0´uÏÎÎòÐCaYAì8Ú»=VJµBɃ‡ö$ŒËo &ܺ^Ðt€fÜo®3Ä¥_‰ÛqÓ´/>|îÆSÓ ¥0dlêªUR—¯œ½ý£At_P¯}Îþ¢ˆüfs߉>Àñ¿øÀÖd¯=îo±ÏîVz½r’{O¿ohëž™™Áq&''ñ}cLO’·;ã8Ôj5 …§OŸÞu©U6?\j8b[§}½`ë°m6 ›nÐ÷ço¬05Ó˜Êk1>}]¯5ßøõÅjø«Ã„óðʓد·þ­~¹™åÞ÷äc~èÞîÄö7®¡Æ$?qhhëžžžæÑG Š¢-ïéÇÚ³ùb±H†äóù]‡‘»€«ª £k6y¤µdÜözkÁ«}LþÂ×gW⽆üôetµÚlâ·VƒÏ>ù ¢=Àÿü,Y¥ì?Dø€åØ<ø±Ç™…áŒÌqæSÂÍx{BþÅÕè‘ÓÜsìØÐÖ=11ÁOgÔhùc``üØAþıœ½[5þÁÊýœxàð®¬[kM&“i\?VÈqCj–²ïQ =*GŤ)ª4ÚÉ’J’Îäð²yRéQR^ÏË`Û6¥R‰µµ5ªÕ*…B×uI§Ód³Ùm¿nadc}‰œ³‚RQÛ\¾ÛD¨Óï7ù¥3‹­ÌúJg}ÿ©sÏûoí-f_9o=ü^ó"Ž»î?ÆýgCYzÏÈ¿ºq”J4Æõ«o³¾2ËÈhžtfl(ëÖZø>¶YçØøï(%Œ"#Ô}C±*”à õ0C½0B°1JE¨<5ƉTš X[[A#Ù a¢”BkÝZÙë;Œ”æ9d¯láYغõšgÌ/o%?].o,äÅW¦}úÜóÁwÙCد~Öþ àKÍõØûOqògÞó»ŽÜàÓïy™¢Ÿ¦d©®ŽPY£ óCYw¹¸JXžGgÖ0¦a©–¥ÐZáØÏ5ŒŸÐ„„¦ŒoVð#ßx”CB}„¢Ÿæ UÀµ„¬¿ÂÆÜ,k7òˆ=`)®â—æÁn´ÅÐZuåºeûmû;ÉOUKŒ\¿ÒÑŸ-šþÊŸ˜—÷úûÕóöoÿ Yx8õóp÷£'ÙØÚ0ê”Ié*#Ö:ÕÀ¦8C[÷úÊ,AyžbX`A+2žÍhÆnu¼mëØââLÔ€8çüHS tÜ–…F[‚F{Ê‘ÇF—¶x™±›ÃÈò,~iŽ¢^gH»Ù´ÒÛzæ–kL/V[‚pêUF§.·ÂÃ÷o32¿/œ¿Þ|òÐÇçà©»ÙOh­ð´à9y"7³ôoÝ¡# KÝ —9¨V™‘´Mʵz/@Ж(2„‘Pó#ŠU¡d¨ûêõê룔TŽÐ'ÔcDªÑ–ÚúEr²B.#dÓ®­onK—Ð9òÐ'wõb«¾ÿö’peÍàZûÇÝ\ÔÉ8ÍÁ¿ðT¼€Åęc wxnv%Œ~¬»¥¨U“"çP¦ÆÑqÛÖ{*RG)lK‘r ¹È'ˆB)áG+Ô#_ÒÔÂ•У¦8àÎ2>ZápÎòÔfVôd~¹ÆÌBµõ²Æ®^lÕ÷‹uÃÛ˲¯²•ŽȲÛ°oá"2½ÅÔ‡uw¡lÿ:ÈÖX¤DBDBD*ˆ6§rBÛcÝFzïÌo~¹ÆôÂæÈ×QÈøô%Tи²ä kÕý%×WÝ|‡nl»9­ ã•Ã0¾š¦ã9m¢1áæóÖ{w×i›kó»=So´È?@‹atê*®ïû‘̯VÌ‘ý§ÿÞÚ¿ÿ6›äÆ‹ÎøÐv!ËìÊBºÍØ¥ÿu®î÷Œõùˆadú*vµÜªï/eJàoòS)€=s!gX @5ÝÅ„›î?–-ûÃF²ï“–kIï‘£ºE)#s×q6Zõýš ü”ý/nÕû-Ìÿà-´mã¤ÒXž‡íyX©vÊÃrRX)Ëõ°ÜÆßÒíŸ8Y -Pî&± ¨¶Ò-HÛ-]ÒÈDDLãbX0E¬¯/q}áâ—ÈÞ˜Æ]ÛRßÿäÙ?`ùÂS·î«ÚñÜ'eü*Óo¾Ù÷Žçag28);íáxi¬tÇkˆÆöÒØ^ËMa¥RXn ÛM¡S.–“B;î+þ}ÒLJµ ÍA±¶®dWçq—[õ}1ÖÙ³/†3·ú«Ú¿‰â7€ÉA j5‚ZêЃLã¤3Ø^ 73‚í¥°½4NÚÃr¢²SiìT Ja»^,¤¦ ¥PZßñfÒm˜Z]ÂkÕ÷ FÔSç~¿~év´Ï~ò…ðw€ßP¯?CÞX^ÞXQN"ÉYZrbȉÒ9-’C‘$‡Òygà¥Å:82.‡h…2Õ#]ø—g_¿t§ô×»¯´8ûUj@ ê Ýsá)ý~”<Âÿi]‹7~÷MùDóo>ËXÝ%gB7'Úä,‘S¬Ÿ{!ú/wÒwM°OøÐ‹làOÇ»¾s'¶S'Týt#@"€‰$H A"€‰$H A"€‰$H A"€ïV¼›®P{ø>Ip“*ýzB÷ãÖ¥æï{Ý|ë¿liS7I"€‰Rýw¶5 ájX»}~—ÿÑMu ©Û‰@îqØ·‰ìHß–,Ó¸ o;ØÏEzŸßêƒ\¹“Ä`ßbÂUŸ¤«†es´©>‰ß ¤‡uû =àhï*¹…·Þ¤†E¿!`PPC&€Òöý ý @zæ=›9†ì¥Sì¤k’6ˆ ì&¨=ýƒÚ²ÕÏï »té•c´}Õqì®Ä°ô“t©>E0 ôvd{n?Ø9Bwá½^“Fö@¨»€ê“ ЗDXUЉö,-ù,v€Ú'¸)‰ë‘ÐÅ¢w$»ÛˆE¡uBFÀ@©£oöDzä«âUç¦;þn·¹4~¾.wy]þ¡G]ðêu^þýïñ¿q xñ9››Õñ¸Ûó~¶žçØa„vný~w+þ.£ßºbþƒoø@t×Q=âòæå+À›_=¶ü}Ï·§ ŒÕjˆ‘ßì'Aê·þ”×A>?·€€ÆÍ›ºí¹¢ñ[rƒÎ,Ô£f§xÞËõ¤¾”Žçṅûâ[¬½ø–<ïsŸÆ/¹x}Xß!”½ÀÀsõÈïÜghÜ„×$Ö°ùCÍ÷Dñþ€ÍÜîœ 8:¶MÞ¶ j€°Ò)¾ íqó»ÕÚö5û$ˆá÷U»¸v£=„ýS}Sî‹z‡}vëÄnö×Oî±H—Îì·:cÿvÄtfòõ.ßMÚDa:ÚÇ6D«½t€Û é „mò5@Òª¶!c»ýû]i¤Í · =ƒŠó¶ W\Úí>é“$éq¬Úiª·Mü—åîÛWô,\ôñ~°Â½P¸ Sa6ÜaÞßM@ƒ¼6È9ië@èwôì$‚AÈ•>“mb½ì¢&°í*žR7·] Gî~ŠdOgloºjÇßZϘ%{/ŠA]… z7ÓLjܭHyϾä;ž\Ikš¦†p–AɆôaƒd‡ô_Ú°Óh”}ÇÀ¹Á°I`óC,ßÓðXÊnú#Ñl™ïÇç6mB–ì~Fø Ÿ] ¾[@õ@¿dɈã–ÌT[…Êý_—åoÿÜ1ý+ZÉÏE_Y‘—ÿèϹ ŒÅÅŠ0þk†${Øzÿ¾]¦PÒ£ÏûÀ^‹‚[-€fÉÖúÚ[,}í-óOcA4K²ºc®Þ>:vKö°£}Ï. ÓªUµk/Õ„;ä{-Š=Á00q‰²9²m¶.L4Ë–Í×£]V‡íûápæKæ›÷Œ©§gÔ…:ÑrE^2måÚh°ç„ߊN泇íÃ’>ìõ.E(pŸ~€§'x´fpÞœaê¾ÇÖ:}ÍÒî ®p[H¸“°WÄïå÷³ÛrŸæf·²u±¦Îæ åqðO’†mû­ø>í×´_Ü {?E7–$H A‚ $H A‚ $H A‚ $H A‚ î<üÆÀD;ç½0®IEND®B`‚kabikaboo-1.7/COPYING000644 001750 001750 00000001140 11306540177 012536 0ustar00000000 000000 Kabikaboo 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 2 of the License, or (at your option) any later version. Kabikaboo 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 Kabikaboo. If not, see http://www.gnu.org/licenses/. kabikaboo-1.7/src/000755 001750 001750 00000000000 11314161033 012263 5ustar00000000 000000 kabikaboo-1.7/src/file.py000644 001750 001750 00000063550 11313563132 013572 0ustar00000000 000000 # Kabikaboo file handler # # Novel Writing Assistance Software # Copyleft (c) 2009 # Created by David Kerr # Naturally Intelligent Inc. # # Free and Open Source # Licensed under GPL v2 or later # No Warranty from document import current_version from document import KabikabooDocument from ConfigParser import * # pickle file handling (awesome) import pickle import os.path import re class KabikabooFile: # initialize def __init__(self): home = os.path.expanduser('~') self.application_name = 'Kabikaboo' self.show_application_name = True self.recovery_file = os.path.join(home, '.kabikaboo', 'recovery.txt') self.settings_file = os.path.join(home, '.kabikaboo', 'settings.txt') self.working_file = '' self.last_directory = os.path.join(home, 'Documents') self.last_import_directory = os.path.join(home, 'Documents') self.last_export_directory = os.path.join(home, 'Documents') self.history = [] self.max_history = 5 self.different = False self.window_maximized = False self.remember_position = False self.window_x = -1 self.window_y = -1 self.diff_x = 0 self.diff_y = 0 self.window_width = 800 self.window_height = 550 self.attribute_panel_height = 150 self.tree_width = 250 self.tool_text = False self.show_tabs = True self.show_node_path = False self.show_node_path_status = True self.homog_tabs = False self.show_tab_arrows = True self.show_bullets = False self.show_attributes = True self.show_statusbar = True self.show_toolbar_tree = True self.show_toolbar_text = False self.show_file_status = True self.show_directory_status = True self.move_on_new = False self.sample_data = True self.tab_bullets = False self.calculate_statistics = False self.fullscreen = False self.spellcheck = False # can hurt app speed, even on the sample data self.tree_toolbar_intree = False # loading, etc self.load_file_message = '' self.opened_attempts = 0 self.max_open_attempts = 2 self.upgraded = False self.proper_shutdown = True self.autoopen = True self.save_on_exit = False self.autosave = False self.autosave_interval = 5 self.autosave_version = False # load settings (must do this last, after setting default values) self.load_settings(self.settings_file) # load recovery self.load_recovery() if not self.proper_shutdown: print 'Improper shutdown detected...' self.save_recovery(False) # settings self.check_settings_dir() def check_settings_dir(self): home = os.path.expanduser('~') if not os.path.isdir(os.path.join(home, '.kabikaboo')): os.mkdir(os.path.join(home, '.kabikaboo')) def new(self, document): document.new_document() self.working_file = '' def open(self, document): if self.autoopen and self.working_file != '': return self.load_last_saved() else: return False, document def close(self, document): if self.save_on_exit and self.working_file != '': self.save_last_opened(document) def load_last_saved(self): return self.load_from_file_pickle(self.working_file) def save(self, document): result = False if self.save_to_file_pickle(self.working_file, document): result = True return result def save_last_saved(self, document): result = False if self.save_to_file_pickle(self.working_file, document): result = True return result def save_last_opened(self, document): result = False if self.save_to_file_pickle(self.working_file, document): result = True return result # autonumbered save based on document title def save_version(self, document): result = False title = document.title.replace(' ', '') if title == '' or len(title)<1: title = 'Unnamed' version = 1 # need to check all files first and find the last version number # user may have deleted or moved earlier versions existing_files = os.listdir(self.last_directory) for existing_file in existing_files: # find end of DocumentTitle- index1 = existing_file.find(title + '-') + len(title + '-') # find start of .kaboo index2 = existing_file.find('.kaboo') # get text between if index1>0 and index2>0 and index2>index1: try: old_version = int(existing_file[index1:index2]) if old_version >= version: version = old_version + 1 except ValueError: pass if version < 1: version = 1 # now we have our best guess at a file name filename = title + '-' + str(version).zfill(2) + '.kaboo' # double check for duplicates while os.path.isfile(filename): version = version + 1 filename = title + '-' + str(version).zfill(2) + '.kaboo' # finally, save our file final_file = self.last_directory + filename if self.save_to_file_pickle(final_file, document): self.working_file = final_file result = True # return the version for status message return result, version # save to .kaboo file def save_to_file(self, filename, document): result = False self.last_directory = os.path.dirname(filename) + os.sep if self.save_to_file_pickle(filename, document): result = True return result # save copy to .kaboo file def save_copy_to_file(self, filename, document): result = False current_working_file = self.working_file if self.save_to_file_pickle(filename, document): result = True self.working_file = current_working_file return result # load from .kaboo file def load_from_file(self, filename): result = False result, document = self.load_from_file_pickle(filename) self.last_directory = os.path.dirname(filename) + os.sep if result: result = True return result, document # load from pickle.kaboo file def load_from_file_pickle(self, filename): result = False self.load_file_message = '' self.upgraded = False if os.path.isfile(filename): input = open(filename) if input: try: document = pickle.load(input) except: print 'Invalid file.' return result, None old_document = None # test document version 1.1 (when version was added) try: document.version except AttributeError: # this is 1.0 or earlier, upgrade to 1.1 self.load_file_message += "Old file version detected (1.0)...\n" if not self.upgraded: old_document = document document.version = current_version() document.tab_max = 5 document.tabs = [] document.version = '1.1' self.load_file_message += "Upgrading to version 1.1 (added tabs)...\n" self.upgraded = True # 1.1 to 1.1.1 upgrade if document.version == '1.1': self.load_file_message += "Old file version detected (1.1)...\n" if not self.upgraded: old_document = document document.tab_max = 7 document.version = '1.1.1' self.load_file_message += "Upgrading to version 1.1.1 (extra tabs)...\n" self.upgraded = True # 1.2 if document.version == '1.1' or document.version == '1.1.1' or document.version <= 1.1: self.load_file_message += "Old file version detected (1.1.1)...\n" if not self.upgraded: old_document = document document.add_bulleting() document.version = '1.2' self.load_file_message += "Upgrading to version 1.2 (added bulleting)...\n" self.upgraded = True # 1.4 if document.version == '1.2': document.version = '1.4' # 1.5 if document.version == '1.4': self.load_file_message += "Old file version detected (1.4)...\n" if not self.upgraded: old_document = document document.bookmarks = [] document.bookmark_max = 10 document.show_titles_in_view = True document.show_titles_in_export = True document.version = '1.5' self.load_file_message += "Upgrading to version 1.5 (added bookmarks)...\n" self.upgraded = True # 1.7 # 1.7 test one upgrade_17_needed = False if document.version == '1.5' or document.version == '1.6': upgrade_17_needed = True if not upgrade_17_needed: # test for visits in root node try: if document.visits_all == 1: pass except AttributeError: upgrade_17_needed = True if upgrade_17_needed: self.load_file_message += "Old file version detected (1.5/1.6)...\n" if not self.upgraded: old_document = document document.add_visits_data() document.version = '1.7' self.load_file_message += "Upgrading to version 1.7 (added recently visited)...\n" self.upgraded = True # show warning in console (also passed to GUI) if self.upgraded: self.load_file_message = "Kabikaboo Upgrade Notice\n\nYour file was saved with an older version of Kabikaboo.\n" + self.load_file_message self.load_file_message += "Upgraded file: %s" % filename + "\n" backupfile = filename + '.backup~' if self.save_to_file_pickle(backupfile, old_document): self.load_file_message += "\nSaved copy of original for backup: %s" % backupfile + "\n" self.add_to_history(backupfile) self.load_file_message += "\nThe upgraded changes are not permanent until saved.\n" self.load_file_message += "It is recommended to save your document with a new versioned filename.\n" # set variables self.working_file = filename self.add_to_history(filename) print self.load_file_message result = True input.close return result, document # save to pickle.kaboo file def save_to_file_pickle(self, filename, document): result = False output = open(filename, 'w') if output: pickle.dump(document, output) output.close self.working_file = filename self.add_to_history(filename) result = True return result # add to file list history # returns True if new, False if already in def add_to_history(self, filename): result = True filename = filename.replace('//', '/') # is this file already at the top of history? if so skip if len(self.history) > 0 and self.history[0] == filename: return False # look through history for duplicate for search in self.history: # move to front if search == filename: self.history.remove(search) result = False # insert at front self.history.insert(0, filename) self.check_history() self.save_settings_default() return result # check history for too many files def check_history(self): # only store max histories while len(self.history) > self.max_history: del self.history[len(self.history)-1] def load_settings(self, filename): result = False config = ConfigParser() if os.path.isfile(filename): try: config.read(filename) except: print "Can't read settings file: " + filename return False # Application Settings if config.has_option('kabikaboo', 'application_name'): self.application_name = config.get('kabikaboo', 'application_name') if config.has_option('kabikaboo', 'last_file'): self.working_file = config.get('kabikaboo', 'last_file') if config.has_option('kabikaboo', 'last_directory'): self.last_directory = config.get('kabikaboo', 'last_directory') if config.has_option('kabikaboo', 'autoopen'): self.autoopen = config.getboolean('kabikaboo', 'autoopen') if config.has_option('kabikaboo', 'save_on_exit'): self.save_on_exit = config.getboolean('kabikaboo', 'save_on_exit') if config.has_option('kabikaboo', 'autosave'): self.autosave = config.getboolean('kabikaboo', 'autosave') if config.has_option('kabikaboo', 'autosave_interval'): self.autosave_interval = config.getfloat('kabikaboo', 'autosave_interval') if config.has_option('kabikaboo', 'autosave_version'): self.autosave_version = config.getboolean('kabikaboo', 'autosave_version') if config.has_option('kabikaboo', 'calculate_statistics'): self.calculate_statistics = config.getboolean('kabikaboo', 'calculate_statistics') # Export Settings if config.has_option('kabikaboo-export', 'last_export_directory'): self.last_export_directory = config.get('kabikaboo-export', 'last_export_directory') if config.has_option('kabikaboo-export', 'last_import_directory'): self.last_import_directory = config.get('kabikaboo-export', 'last_import_directory') # GUI Settings if config.has_option('kabikaboo-gui', 'show_application_name'): self.show_application_name = config.getboolean('kabikaboo-gui', 'show_application_name') if config.has_option('kabikaboo-gui', 'window_maximized'): self.window_maximized = config.getboolean('kabikaboo-gui', 'window_maximized') if config.has_option('kabikaboo-gui', 'remember_position'): self.remember_position = config.getboolean('kabikaboo-gui', 'remember_position') if config.has_option('kabikaboo-gui', 'window_x'): self.window_x = config.getint('kabikaboo-gui', 'window_x') if config.has_option('kabikaboo-gui', 'window_y'): self.window_y = config.getint('kabikaboo-gui', 'window_y') if config.has_option('kabikaboo-gui', 'diff_x'): self.diff_x = config.getint('kabikaboo-gui', 'diff_x') if config.has_option('kabikaboo-gui', 'diff_y'): self.diff_y = config.getint('kabikaboo-gui', 'diff_y') if config.has_option('kabikaboo-gui', 'window_width'): self.window_width = config.getint('kabikaboo-gui', 'window_width') if config.has_option('kabikaboo-gui', 'window_height'): self.window_height = config.getint('kabikaboo-gui', 'window_height') if config.has_option('kabikaboo-gui', 'tree_width'): self.tree_width = config.getint('kabikaboo-gui', 'tree_width') if config.has_option('kabikaboo-gui', 'attribute_panel_height'): self.attribute_panel_height = config.getint('kabikaboo-gui', 'attribute_panel_height') if config.has_option('kabikaboo-gui', 'tool_text'): self.tool_text = config.getboolean('kabikaboo-gui', 'tool_text') if config.has_option('kabikaboo-gui', 'show_tabs'): self.show_tabs = config.getboolean('kabikaboo-gui', 'show_tabs') if config.has_option('kabikaboo-gui', 'show_node_path'): self.show_node_path = config.getboolean('kabikaboo-gui', 'show_node_path') if config.has_option('kabikaboo-gui', 'show_node_path_status'): self.show_node_path_status = config.getboolean('kabikaboo-gui', 'show_node_path_status') if config.has_option('kabikaboo-gui', 'homog_tabs'): self.homog_tabs = config.getboolean('kabikaboo-gui', 'homog_tabs') if config.has_option('kabikaboo-gui', 'show_tab_arrows'): self.show_tab_arrows = config.getboolean('kabikaboo-gui', 'show_tab_arrows') if config.has_option('kabikaboo-gui', 'show_bullets'): self.show_bullets = config.getboolean('kabikaboo-gui', 'show_bullets') if config.has_option('kabikaboo-gui', 'show_attributes'): self.show_attributes = config.getboolean('kabikaboo-gui', 'show_attributes') if config.has_option('kabikaboo-gui', 'show_statusbar'): self.show_statusbar = config.getboolean('kabikaboo-gui', 'show_statusbar') if config.has_option('kabikaboo-gui', 'show_toolbar_tree'): self.show_toolbar_tree = config.getboolean('kabikaboo-gui', 'show_toolbar_tree') if config.has_option('kabikaboo-gui', 'show_toolbar_text'): self.show_toolbar_text = config.getboolean('kabikaboo-gui', 'show_toolbar_text') if config.has_option('kabikaboo-gui', 'show_file_status'): self.show_file_status = config.getboolean('kabikaboo-gui', 'show_file_status') if config.has_option('kabikaboo-gui', 'move_on_new'): self.move_on_new = config.getboolean('kabikaboo-gui', 'move_on_new') if config.has_option('kabikaboo-gui', 'sample_data'): self.sample_data = config.getboolean('kabikaboo-gui', 'sample_data') if config.has_option('kabikaboo-gui', 'tab_bullets'): self.tab_bullets = config.getboolean('kabikaboo-gui', 'tab_bullets') if config.has_option('kabikaboo-gui', 'fullscreen'): self.fullscreen = config.getboolean('kabikaboo-gui', 'fullscreen') if config.has_option('kabikaboo-gui', 'show_directory_status'): self.show_directory_status = config.getboolean('kabikaboo-gui', 'show_directory_status') if config.has_option('kabikaboo-gui', 'spellcheck'): self.spellcheck = config.getboolean('kabikaboo-gui', 'spellcheck') if config.has_option('kabikaboo-gui', 'tree_toolbar_intree'): self.tree_toolbar_intree = config.getboolean('kabikaboo-gui', 'tree_toolbar_intree') # History Settings if config.has_option('kabikaboo-history', 'max_history'): self.max_history = config.getint('kabikaboo-history', 'max_history') # History List for i in range(0, self.max_history): history = 'history-%d' % i if config.has_option('kabikaboo-history', history): self.history.append(config.get('kabikaboo-history', history)) result = True return result def save_settings_default(self): self.save_settings(self.settings_file) def save_settings(self, filename): result = False config = ConfigParser() config.add_section('kabikaboo') config.set('kabikaboo', 'application_name', self.application_name) config.set('kabikaboo', 'last_file', self.working_file) config.set('kabikaboo', 'last_directory', self.last_directory) config.set('kabikaboo', 'autoopen', self.autoopen) config.set('kabikaboo', 'save_on_exit', self.save_on_exit) config.set('kabikaboo', 'autosave', self.autosave) config.set('kabikaboo', 'autosave_interval', self.autosave_interval) config.set('kabikaboo', 'autosave_version', self.autosave_version) config.set('kabikaboo', 'calculate_statistics', self.calculate_statistics) config.add_section('kabikaboo-export') config.set('kabikaboo-export', 'last_export_directory', self.last_export_directory) config.set('kabikaboo-export', 'last_import_directory', self.last_import_directory) config.add_section('kabikaboo-gui') config.set('kabikaboo-gui', 'show_application_name', self.show_application_name) config.set('kabikaboo-gui', 'window_maximized', self.window_maximized) config.set('kabikaboo-gui', 'remember_position', self.remember_position) config.set('kabikaboo-gui', 'window_x', self.window_x) config.set('kabikaboo-gui', 'window_y', self.window_y) config.set('kabikaboo-gui', 'diff_x', self.diff_x) config.set('kabikaboo-gui', 'diff_y', self.diff_y) config.set('kabikaboo-gui', 'window_width', self.window_width) config.set('kabikaboo-gui', 'window_height', self.window_height) config.set('kabikaboo-gui', 'tree_width', self.tree_width) config.set('kabikaboo-gui', 'attribute_panel_height', self.attribute_panel_height) config.set('kabikaboo-gui', 'tool_text', self.tool_text) config.set('kabikaboo-gui', 'show_tabs', self.show_tabs) config.set('kabikaboo-gui', 'show_node_path', self.show_node_path) config.set('kabikaboo-gui', 'show_node_path_status', self.show_node_path_status) config.set('kabikaboo-gui', 'homog_tabs', self.homog_tabs) config.set('kabikaboo-gui', 'show_tab_arrows', self.show_tab_arrows) config.set('kabikaboo-gui', 'show_bullets', self.show_bullets) config.set('kabikaboo-gui', 'show_attributes', self.show_attributes) config.set('kabikaboo-gui', 'show_statusbar', self.show_statusbar) config.set('kabikaboo-gui', 'show_toolbar_tree', self.show_toolbar_tree) config.set('kabikaboo-gui', 'show_toolbar_text', self.show_toolbar_text) config.set('kabikaboo-gui', 'show_file_status', self.show_file_status) config.set('kabikaboo-gui', 'show_directory_status', self.show_directory_status) config.set('kabikaboo-gui', 'move_on_new', self.move_on_new) config.set('kabikaboo-gui', 'sample_data', self.sample_data) config.set('kabikaboo-gui', 'tab_bullets', self.tab_bullets) config.set('kabikaboo-gui', 'fullscreen', self.fullscreen) config.set('kabikaboo-gui', 'spellcheck', self.spellcheck) config.set('kabikaboo-gui', 'tree_toolbar_intree', self.tree_toolbar_intree) config.add_section('kabikaboo-history') config.set('kabikaboo-history', 'max_history', self.max_history) for index, file in enumerate(self.history): config.set('kabikaboo-history', 'history-%d' % index, file) try: output = open(filename, 'w') config.write(output) result = True except: print "Can't write settings file: " + filename return result def export_to_text_file(self, document, node, filename, recurse=True, show_titles=True): try: output = open(filename, 'w') except: print "Can't write text file: " + filename return False output.write(node.export_text('', recurse, show_titles)) self.last_export_directory = os.path.dirname(filename) + os.sep self.save_settings_default() return True def export_to_html_file(self, document, node, filename, recurse=True, show_titles=True): try: output = open(filename, 'w') except: print "Can't write HTML file: " + filename return False output.write(node.html_text(recurse, show_titles)) self.last_export_directory = os.path.dirname(filename) + os.sep self.save_settings_default() return True def load_recovery(self): result = False config = ConfigParser() if os.path.isfile(self.recovery_file): try: config.read(self.recovery_file) except: print "Can't read recovery file: " + self.recovery_file return False if config.has_option('kabikaboo-recovery', 'closed'): self.proper_shutdown = config.getboolean('kabikaboo-recovery', 'closed') if config.has_option('kabikaboo-recovery', 'opened-attempts'): self.opened_attempts = config.getint('kabikaboo-recovery', 'opened-attempts') result = True return result def save_recovery(self, closed): result = False config = ConfigParser() config.add_section('kabikaboo-recovery') config.set('kabikaboo-recovery', 'closed', closed) if closed: self.opened_attempts = 0 config.set('kabikaboo-recovery', 'opened-attempts', self.opened_attempts) try: output = open(self.recovery_file, 'w') config.write(output) result = True except: print "Can't write recovery file: " + self.recovery_file return result # import a text file def import_text_file(self, filename, node): result = False file = open(filename) if file: while 1: line = file.readline() if not line: break node.text += line result = True return result kabikaboo-1.7/src/document.py000644 001750 001750 00000071057 11313720701 014467 0ustar00000000 000000 # Kabikaboo document # # Novel Writing Assistance Software # Copyleft (c) 2009 # Created by David Kerr # Naturally Intelligent Inc. # # Free and Open Source # Licensed under GPL v2 or later # No Warranty def current_version(): return '1.7' # a list of node children class KabikabooNodeList: # init def __init__(self, owner): self.parent = owner self.list = [] # create a node, simple wrapper5 def create_node(self, owner, id): new_node = KabikabooNode(owner, id) self.list.append(new_node) return new_node # insert a node, simple wrapper def insert_node(self, owner, id, index): new_node = KabikabooNode(owner, id) self.list.insert(index, new_node) return new_node # delete a node, simple wrapper def delete_node(self, node): self.list.remove(node) # find index of node def index_of_node(self, node): index = self.list.index(node) return index # clear nodes def clear_nodes(self): self.list = [] # swap two nodes def swap(self, node, swap_node): result = False index1 = self.list.index(node) index2 = self.list.index(swap_node) if index1 != -1 and index2 != -1: self.list.remove(node) self.list.insert(index2, node) return result # this is a text node in a Kabikaboo document # each node has a list of 0..N children nodes class KabikabooNode: BULLETING_NONE = 0 BULLETING_NUMBER = 1 BULLETING_ALPHA_LOWER = 2 BULLETING_ALPHA = 3 BULLETING_ALPHA_UPPER = 3 BULLETING_ROMAN_LOWER = 4 BULLETING_ROMAN_UPPER = 5 BULLETING_DASH = 6 BULLETING_DOT = 7 # init def __init__(self, owner, id): self.id = id self.parent = owner self.children = KabikabooNodeList(self) self.title = 'Kabikaboo' self.text = '' self.view = False self.all = True self.bulleting = KabikabooNode.BULLETING_NONE self.visits_all = 0 self.visits_session = 0 # get title def get_title(self): return self.get_title_bullet() + self.title # get title bullet prefix def get_title_bullet(self): bullet = '' # int_to_roman from http://code.activestate.com/recipes/81611/ def int_to_roman(input): if type(input) != type(1): raise TypeError, "expected integer, got %s" % type(input) if not 0 < input < 4000: raise ValueError, "Argument must be between 1 and 3999" ints = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) nums = ('M', 'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I') result = "" for i in range(len(ints)): count = int(input / ints[i]) result += nums[i] * count input -= ints[i] * count return result if self.parent and self.parent.bulleting != KabikabooNode.BULLETING_NONE: if self.parent.bulleting == KabikabooNode.BULLETING_NUMBER: bullet = str(self.parent.children.index_of_node(self)+1) + '. ' if self.parent.bulleting == KabikabooNode.BULLETING_ALPHA_UPPER: index = self.parent.children.index_of_node(self) myrep = int(index / 26) mystring = '' mychar = chr(index%26+65) bullet = chr((self.parent.children.index_of_node(self))%26+65) + '. ' if self.parent.bulleting == KabikabooNode.BULLETING_ALPHA_LOWER: bullet = chr((self.parent.children.index_of_node(self))%26+97) + '. ' if self.parent.bulleting == KabikabooNode.BULLETING_ROMAN_UPPER: bullet = int_to_roman(self.parent.children.index_of_node(self)+1) + '. ' if self.parent.bulleting == KabikabooNode.BULLETING_ROMAN_LOWER: bullet = int_to_roman(self.parent.children.index_of_node(self)+1).lower() + '. ' return bullet # set title def set_title(self, title): self.title = title # get text def get_text(self): return self.text # set text def set_text(self, text): self.text = text # add a child def add_child(self, id): node = self.children.create_node(self, id) node.parent = self return node # add a child def add_child(self, id, title, text): node = self.children.create_node(self, id) node.parent = self node.title = title node.text = text return node # insert a child def insert_child(self, id, index, title, text): node = self.children.insert_node(self, id, index) node.parent = self node.title = title node.text = text return node # return children def get_children(self): return self.children # has children def has_children(self): result = False if len(self.children.list) > 0: result = True return result #export text file def export_text(self, title, recurse, show_titles): text = '' if title: title = title + ' -> ' + self.get_title() else: title = self.get_title() if show_titles: text = title text += '\n' text += '-' * 2 * len(title) text += '\n' if(self.text): text += self.text text += '\n' text += '\n' if recurse: for child in self.children.list: text = text + child.export_text(title, recurse, show_titles) return text # view markuped text recursion def html_text(self, recurse, show_titles): text = '\n' text += '\n' text += '' + self.get_title() + '\n' text += '\n' text += '\n' text += self.html_text_node('', recurse, show_titles) text += '\n' text += '\n' return text # view markuped text recursion def html_text_node(self, title, recurse, show_titles): text = '' if show_titles: text = '' + self.get_title() + '
\n' if title: title += ' -> ' + self.get_title() text += '' + title + '
\n' else: title = self.get_title() text += '-' * 2 * len(title) text += '

\n' if(self.text): text += self.text.replace('\n', '
') text += '
\n' text += '
\n' if recurse: for child in self.children.list: text = text + child.html_text_node(title, recurse, show_titles) return text # get text to display def get_view_text(self, show_titles): if not self.view: return self.text else: return self.export_text('', self.all, show_titles) # get spaced title def get_spaced_title(self, length): return self.title.center(length, ' ') # get spaced title with bullets def get_spaced_bullet_title(self, length): title = self.get_title() return title.center(length, ' ') # get recursive title with path def get_recursive_title(self, title = '', separator = ' -> '): title = self.get_title() if self.parent: title = self.parent.get_recursive_title(title, separator) + separator + title return title # get title with parent def get_title_with_parent(self): title = self.title if self.parent: title = self.parent.title + ' -> ' + title return title # get bulleting def get_bulleting(self): return self.bulleting # set bulleting def set_bulleting(self, bulleting): self.bulleting = bulleting # add bulleting def add_bulleting(self): self.bulleting = KabikabooNode.BULLETING_NONE for child in self.children.list: child.add_bulleting() # simple word count def word_count(self, recurse=True): words = self.text.split(None) word_count = len(words) if recurse: for child in self.children.list: word_count += child.word_count() return word_count # visible word count def visible_word_count(self, recurse=True): if not self.view: return self.word_count(recurse) else: text = self.export_text('', self.all, False) words = text.split(None) return len(words) # is this node splittable? def is_splittable(self): result = True if self.view: result = False if self.text == '': result = False return result # is this node unifiable? def is_unifiable(self): result = False if self.has_children(): result = True return result # copy the children's text into this node def absorb_children_text(self, include_titles): for child in self.children.list: child.absorb_children_text(include_titles) if child.text != '' and child.title != '' and self.text != '': self.text += "\n\n" if include_titles and child.title != '': self.text += child.title self.text += "\n" if child.text != '': self.text += child.text # clear session visits def clear_session_visits(self): self.visits_session = 0 for child in self.children.list: child.clear_session_visits() # add visit counters (for upgrade) def add_visit_counters(self): self.visits_all = 0 self.visits_session = 0 for child in self.children.list: child.add_visit_counters() # create a sorted visit list def add_to_session_visit_list(self, list, max): added = False if self.visits_session > 0: count = 0 for node in list: if self.visits_session > node.visits_session: list.insert(count, self) added = True break count = count + 1 if not added and count < max: list.append(self) added = True if added: while len(list) > max: del list[len(list)-1] for child in self.children.list: child.add_to_session_visit_list(list, max) # create a sorted visit list def add_to_all_visit_list(self, list, max): added = False if self.visits_all > 0: count = 0 for node in list: if self.visits_all > node.visits_all: list.insert(count, self) added = True break count = count + 1 if not added and count < max: list.append(self) added = True if added: while len(list) > max: del list[len(list)-1] for child in self.children.list: child.add_to_all_visit_list(list, max) # top level node class KabikabooDocument(KabikabooNode): # initialize def __init__(self): # unique id counter self.id = 0 self.unique_id = 0 # inherited init, using counter for id KabikabooNode.__init__(self, self.id, self.unique_id) # initialize data self.title = 'New Document' self.table = {} self.text = '' self.view = True # version 1.1 self.tabs = [] self.tab_max = 7 self.version = current_version() # version 1.5 self.bookmarks = [] self.bookmark_max = 10 self.show_titles_in_view = True self.show_titles_in_export = True # version 1.7 (recently visited nodes) self.visited = [] self.visited_max = 10 # add self to the table reference list self.table[self.id] = self # add node (always use this instead of node.add_child) def add_node(self, node, title, text): # increment the unique id counter self.unique_id = self.unique_id + 1 # add the node into the tree new = node.add_child(self.unique_id, title, text) # also add this to the table, for reference by id self.table[new.id] = new # send result back return new # add node, and guess the title using the text def add_node_guess_title(self, node, text): # guess the title (get the first five words) title = '' lines = text.splitlines() goodline = '' for line in lines: if line != '' and line != ' ' and line != '\n': goodline = line break if goodline: sentence = goodline.split(' ') words = 0 for word in sentence: if word != '' and word != ' ': if words >= 1: title += ' ' title += word words += 1 if words >= 5: title += ' ...' break if title == '': title = 'split' # get the new node new = self.add_node(node, title, text) # send result back return new # add node before another node, on the same level def add_node_before(self, node, title, text): index = self.find_index_of_node(node) new = None if index != -1: # increment the unique id counter self.unique_id = self.unique_id + 1 # add the node into the tree new = node.parent.insert_child(self.unique_id, index, title, text) # also add this to the table, for reference by id self.table[new.id] = new return new #add node before another node, on the same level def add_node_after(self, node, title, text): index = self.find_index_of_node(node) new = None if index != -1: # move this index down one (after) index = index + 1 # increment the unique id counter self.unique_id = self.unique_id + 1 # add the node into the tree new = node.parent.insert_child(self.unique_id, index, title, text) # also add this to the table, for reference by id self.table[new.id] = new return new # find a node by id def valid_id(self, id): result = False if self.table[id] != None: result = True return result # find index of node def find_index_of_node(self, node): index = -1 if(node.parent): index = node.parent.children.index_of_node(node) return index # first node? def is_a_first_node(self, node): index = -1 if(node.parent): index = node.parent.children.index_of_node(node) return index == 0 # last node? def is_a_last_node(self, node): index = -1 if(node.parent): index = node.parent.children.index_of_node(node) return index == len(node.parent.children.list) - 1 # can move up? def can_move_up(self, node): result = False if not self.is_a_first_node(node) and not node.id == self.id: result = True return result # can move down? def can_move_down(self, node): result = False if not self.is_a_last_node(node) and not node.id == self.id: result = True return result # can move left? def can_move_left(self, node): result = False if node.parent: if node.parent != self: result = True return result # can move right? def can_move_right(self, node): result = False if node != self: if not self.is_a_first_node(node): result = True return result # get move down node def get_move_down_node(self, node): return self.get_node_after(node) # get move up node def get_move_up_node(self, node): return self.get_node_before(node) # move down def move_node_down(self, node): result = False if self.can_move_down(node): swap_node = self.get_move_down_node(node) node.parent.children.swap(node, swap_node) result = True return result # move up def move_node_up(self, node): result = False if self.can_move_up(node): swap_node = self.get_move_up_node(node) node.parent.children.swap(node, swap_node) result = True return result # move left (to parents node after) def move_node_left(self, node): result = False #if not node.id == self.id and not node.parent == self.id: if self.can_move_left(node): new_index = self.find_index_of_node(node.parent) + 1 new_parent = node.parent.parent node.parent.children.delete_node(node) new_parent.children.list.insert(new_index, node) node.parent = new_parent result = True return result # move right (to end of node befores children) def move_node_right(self, node): result = False if self.can_move_right(node): new_parent = self.get_node_before(node) node.parent.children.delete_node(node) new_parent.children.list.append(node) node.parent = new_parent result = True return result # return node after def get_node_after(self, node): return node.parent.children.list[node.parent.children.index_of_node(node) + 1] # return node before def get_node_before(self, node): return node.parent.children.list[node.parent.children.index_of_node(node) - 1] # find a node by id def fetch_node_by_id(self, id): if id in self.table: node = self.table[id] else: node = None return node # remove a node def remove_node(self, node): result = False # we can't delete the document node if node.id != self.id: # remove children for child in node.children.list: self.remove_node(child) # remove node from table self.table[node.id] = None # remove the node from the parents list node.parent.children.delete_node(node) # remove from tabs if in there self.remove_tab(node) # remove from bookmarks if in there self.remove_bookmark(node) # remove from visited if in there self.remove_visited(node) # success result = True return result # remove children def remove_children(self, node): result = False # remove children for child in reversed(node.children.list): self.remove_children(child) # we can't delete the document node if child.id != self.id: self.remove_node(child) # success result = True return result # clear this document def new_document(self): self.children.clear_nodes() self.table = {} self.id = 0 self.unique_id = 0 self.table[self.id] = self self.title = 'New Document' self.text = '' self.tabs = [] self.tab_max = 7 self.version = current_version() # populate for testing def generate_default_data(self): self.title = 'Kabikaboo' self.text = 'Welcome to Kabikaboo Recursive Writing Assistant!\nThis is sample data to show the capabilities of Kabikaboo.\nYou should create your own New Document from the File Menu.' self.view = False # check max tab, drop oldest # populate for testing def generate_test_data(self): self.generate_default_data() self.tab_max = 5 self.add_tab(self) node = self.add_node(self, 'Plot', 'Plot Text ' * 32) node.bulleting = KabikabooNode.BULLETING_ALPHA_UPPER node.view = True node_act = self.add_node(node, 'Act One', ('Act One Text ' * 24 + '\n') * 4) self.add_bookmark(node_act) node_act = self.add_node(node, 'Act Two', ('Act Two Text ' * 48 + '\n') * 8) self.add_bookmark(node_act) node_act = self.add_node(node, 'Act Three', ('Act Three Text ' * 32 + '\n') * 2) self.add_bookmark(node_act) node2 = self.add_node(self, 'Characters', '') node2.view = True node2.bulleting = KabikabooNode.BULLETING_NUMBER node = self.add_node(node2, 'Primary', '') node.view = True node.bulleting = KabikabooNode.BULLETING_ROMAN_UPPER node3 = self.add_node(node, 'Protagonist', 'Protagonist Description ' * 32) self.add_bookmark(node3) node3.bulleting = KabikabooNode.BULLETING_ALPHA_LOWER self.add_node(node3, 'Background', 'Background Information ' * 16) self.add_node(node3, 'Quotes', 'Memorable Quotes ' * 16) self.add_node(node3, 'Research', 'Research Area ' * 16) node3 = self.add_node(node, 'Antagonist', 'Antagonist Description ' * 32) node3.bulleting = KabikabooNode.BULLETING_ALPHA_LOWER self.add_node(node3, 'Background', 'Background Information ' * 16) self.add_node(node3, 'Quotes', 'Memorable Quotes ' * 16) self.add_node(node3, 'Research', 'Research Area ' * 16) node = self.add_node(node2, 'Secondary', '') node.view = True node.bulleting = KabikabooNode.BULLETING_ROMAN_LOWER self.add_node(node, 'Mom', 'Mom Description ' * 24) self.add_node(node, 'Dad', 'Dad Description ' * 24) node = self.add_node(self, 'Events', 'Events Text ' * 16) node.view = True self.add_node(node, 'Past', 'Past Text ' * 96) self.add_node(node, 'Present', 'Present Text ' * 96) self.add_node(node, 'Future', 'Future Text ' * 96) node = self.add_node(self, 'Locations', 'Location Text ' * 16) node.view = True self.add_node(node, 'Starting Location', 'Starting Location Text ' * 64) self.add_node(node, 'Middle Location', 'Middle Location Text ' * 64) self.add_node(node, 'Final Location', 'Final Location Text ' * 64) node = self.add_node(self, 'Culture', 'Culture Text ' * 16) node.view = True self.add_node(node, 'Art', 'Art Text ' * 128) self.add_node(node, 'Music', 'Music Text ' * 128) self.add_node(node, 'Norms', 'Norms Text ' * 128) # TABS (list of recently viewed nodes) # add tab def add_tab(self, node): added = False changed = False moved = False duplicate = False overflow = False previousIndex = -1 newIndex = -1 # check if the tab is already in the list, remove it (will be readded at front) if node.id in self.tabs: duplicate = True if self.tabs.index(node.id) > 0: previousIndex = self.tabs.index(node.id) self.tabs.remove(node.id) self.tabs.insert(0, node.id) newIndex = 0 moved = True changed = True # add the tab if not duplicate: self.tabs.insert(0, node.id) added = True changed = True if self.check_tab_overflow(): changed = True overflow = True return added, moved, changed, duplicate, overflow, previousIndex, newIndex # check tab overflow def check_tab_overflow(self): result = False while(len(self.tabs) > self.tab_max): self.tabs.remove(self.tabs[len(self.tabs)-1]) result = True return result # remove tab def remove_tab(self, node): if node.id in self.tabs: self.tabs.remove(node.id) # remove tab def remove_last_tab(self): if len(self.tabs) > 0: node_id = self.tabs[len(self.tabs)-1] if node_id: self.tabs.remove(node_id) # add bookmark def add_bookmark(self, node): added = False changed = False moved = False duplicate = False overflow = False previousIndex = -1 newIndex = -1 # check if the bookmark is already in the list, remove it (will be readded at front) if node.id in self.bookmarks: duplicate = True if self.bookmarks.index(node.id) > 0: previousIndex = self.bookmarks.index(node.id) self.bookmarks.remove(node.id) self.bookmarks.insert(0, node.id) newIndex = 0 moved = True changed = True # add the bookmark if not duplicate: self.bookmarks.insert(0, node.id) added = True changed = True if self.check_bookmark_overflow(): changed = True overflow = True return added, moved, changed, duplicate, overflow, previousIndex, newIndex # remove bookmark def remove_bookmark(self, node): if node.id in self.bookmarks: self.bookmarks.remove(node.id) # check for bookmark overflow def check_bookmark_overflow(self): result = False while(len(self.bookmarks) > self.bookmark_max): self.bookmarks.remove(self.bookmarks[len(self.bookmarks)-1]) result = True return result # check node in bookmarks def is_node_bookmarked(self, node): result = False for node_id in self.bookmarks: if node_id == node.id: result = True return result # add visited def add_visited(self, node): added = False changed = False moved = False duplicate = False overflow = False previousIndex = -1 newIndex = -1 # check if the visited is already in the list, remove it (will be readded at front) if node.id in self.visited: duplicate = True if self.visited.index(node.id) > 0: previousIndex = self.visited.index(node.id) self.visited.remove(node.id) self.visited.insert(0, node.id) newIndex = 0 moved = True changed = True # add the visited if not duplicate: self.visited.insert(0, node.id) added = True changed = True if self.check_visited_overflow(): changed = True overflow = True return added, moved, changed, duplicate, overflow, previousIndex, newIndex # remove visited def remove_visited(self, node): if node.id in self.visited: self.visited.remove(node.id) # check for visited overflow def check_visited_overflow(self): result = False while(len(self.visited) > self.visited_max): self.visited.remove(self.visited[len(self.visited)-1]) result = True return result # visit a node def visit(self, node): if node: node.visits_all = node.visits_all + 1 node.visits_session = node.visits_session + 1 (added, moved, changed, duplicate, overflow, previousIndex, newIndex) = self.add_visited(node) return changed return False # clear visits def clear_recent_visits(self): self.visited = [] # for upgrading to v1.7 def add_visits_data(self): self.visited = [] self.visited_max = 10 self.add_visit_counters() # get the sorted list of session visits def get_visited_sessions_list(self): visited = [] self.add_to_session_visit_list(visited, self.visited_max) return visited # get the sorted list of all visits def get_visited_all_list(self): visited = [] self.add_to_all_visit_list(visited, self.visited_max) return visited # should call this after loading a document from file def post_load(self): # clear visits this session # this cant be done on save, because then it would be cleared from users session # should really be stored in application but oh well self.clear_session_visits() # add first node in tabs to session visit if len(self.tabs) > 0: node_id = self.tabs[0] node = self.fetch_node_by_id(node_id) if node: # session node.visits_session = 1 # all if node.visits_all == 0: node.visits_all = 1 # recent self.add_visited(node) kabikaboo-1.7/src/statistics.py000644 001750 001750 00000013273 11311267207 015044 0ustar00000000 000000 # Kabikaboo statistics window # # Novel Writing Assistance Software # Copyleft (c) 2009 # Created by David Glen Kerr # Naturally Intelligent Inc. # # Free and Open Source # Licensed under GPL v2 or later # No Warranty from file import KabikabooFile #from kabikaboo import KabikabooMainWindow import os import sys import time import gtk import pango import pygtk import glib class KabikabooStatisticsWindow: # init main window def __init__(self): # create gtk builder self.builder = gtk.Builder() # load interface if os.path.isfile(os.path.join("ui", "statistics.glade")): self.builder.add_from_file(os.path.join("ui", "statistics.glade")) elif os.path.isfile(os.path.join("..", "ui", "statistics.glade")): self.builder.add_from_file(os.path.join("..", "ui", "statistics.glade")) elif os.path.isfile("/usr/share/kabikaboo/ui/statistics.glade"): self.builder.add_from_file("/usr/share/kabikaboo/ui/statistics.glade") # find main window self.window = self.builder.get_object("window_statistics") # pointers, must be set self.file = None self.kabikaboo = None self.document = None # application icon if os.path.isfile("kabikaboo.png"): gtk.window_set_default_icon_from_file("kabikaboo.png") elif os.path.isfile(os.path.join("..", "kabikaboo.png")): gtk.window_set_default_icon_from_file(os.path.join("..", "kabikaboo.png")) elif os.path.isfile("/usr/src/kabikaboo/kabikaboo.png"): gtk.window.set_default_icon_from_file("/usr/src/kabikaboo/kabikaboo.png") # data self.start_word_count = 0 self.session_word_count = 0 self.words_per_minute = 0 self.start_time = time.time() self.session_time = time.time() # connect gui callbacks self.connect_gui() def set_data(self, kabikaboo, file, document): self.file = file self.kabikaboo = kabikaboo self.document = document # stats from document if not self.file.calculate_statistics: self.start_word_count = self.document.word_count() else: self.start_time = self.kabikaboo.start_time if self.kabikaboo.start_word_count == -1: self.kabikaboo.start_word_count = self.document.word_count() self.start_word_count = self.kabikaboo.start_word_count self.update_title() # display self.display_statistics() # timer glib.timeout_add(250, self.display_statistics) self.set_window_title() def set_window_title(self): if self.file.show_application_name and self.file.application_name != '': self.window.set_title(self.file.application_name + ' - Statistics') else: self.window.set_title(self.document.title + ' - Statistics') def update_title(self): # now calculate them (potentially) label_header = self.builder.get_object("label_header") label_header.set_markup('' + self.document.get_title() + ' - Statistics') def new_data(self, document): self.document = document # stats from document self.start_time = self.kabikaboo.start_time if not self.file.calculate_statistics: self.start_word_count = self.document.word_count() else: if self.kabikaboo.start_word_count == -1: self.kabikaboo.start_word_count = self.document.word_count() self.start_word_count = self.kabikaboo.start_word_count def connect_gui(self): #close button close_button = self.builder.get_object("button_close") close_button.connect("clicked", self.closed_button_callback) #labels self.label_last_word_count_number = self.builder.get_object("label_last_word_count_number") self.label_current_word_count_number = self.builder.get_object("label_current_word_count_number") self.label_session_word_count_number = self.builder.get_object("label_session_word_count_number") self.label_session_time_number = self.builder.get_object("label_session_time_number") self.label_wpm_number = self.builder.get_object("label_wpm_number") self.label_node_words_number = self.builder.get_object("label_node_words_number") # close def closed_button_callback(self, data=None): self.window.hide() # calculate statistics and save in document def display_statistics(self): # derive stats self.current_word_count = self.document.word_count() self.session_word_count = self.current_word_count - self.start_word_count self.session_time = time.time() - self.start_time self.words_per_minute = self.session_word_count / max(self.session_time/60, 1) # display self.label_last_word_count_number.set_text("%d" % self.start_word_count) self.label_current_word_count_number.set_text("%d" % self.current_word_count) self.label_session_word_count_number.set_text("%d" % self.session_word_count) if self.session_time < 60*60: self.label_session_time_number.set_text(time.strftime("%M:%S", time.gmtime(self.session_time))) else: self.label_session_time_number.set_text(time.strftime("%H:%M:%S", time.gmtime(self.session_time))) self.label_wpm_number.set_text("%d" % self.words_per_minute) if self.kabikaboo.editor_node: self.label_node_words_number.set_text("%d" % self.kabikaboo.editor_node.visible_word_count(False)) else: self.label_node_words_number.set_text("n/a") # return true for timer to repeat return True kabikaboo-1.7/src/kabikaboo.py000755 001750 001750 00000435135 11313720306 014600 0ustar00000000 000000 #!/usr/bin/env python # -*- coding: utf-8 -*- # # Kabikaboo # # Recursive Writing Assistance Software # Created by # David Glen Kerr # Jeremy Bicha # # Licensed under GPL v2 or later # Free and Open Source # Copyleft (c) 2009 # No Warranty # Import Kabikaboo codebase from document import * from file import * from settings import * from statistics import * # external imports import commands import os import string import sys import time # dependency: apt-get install python-gtk2 import gobject import gtk import pango import pygtk # dependency: apt-get install python-gtksourceview2 import gtksourceview2 # dependency: apt-get install python-gtkspell import gtkspell # dependency : apt-get install python-gnome2 import gnome # KabikabooMainWindow # # Special thanks to Flint James Kerr # creator of the explosion sound "kabik-kaboo!" # class KabikabooMainWindow: # Init Step 1: init main window def __init__(self): # create document holder self.document = KabikabooDocument() # create file handler self.file = KabikabooFile() # Init Step 2: initialize gui interface def initialize_interface(self): # buffer self.copying_buffer = True # tell it we are positioning the window self.positioning = True # create window self.build_window() # find components in window self.find_window_components() # check for duplicate instance of kabikaboo if not self.check_for_duplicate_instance(): # open last file self.open_file_on_startup() # initialize variables self.initialize_variables() # create notebook self.create_notebook() # create treeview self.create_treeview() # size up component sizes self.set_gui_sizes() # application icon self.load_application_icon() # connect signals self.builder.connect_signals(self) # tree toolbar self.create_toolbar_tree() # text toolbar self.create_toolbar_text() # update the status bar self.update_status_bar() # menu items self.create_menu_items() # attributes callbacks self.connect_attributes_panel() # history self.create_history_gui() # bookmarks self.create_bookmarks_gui() # visited self.create_visited_gui() # autosave self.autosave_id = -1 self.check_autosave() # keep open self.keep_open = True # done positioning window self.positioning = False # Init Step 3: populate gui interface def populate_interface(self): # update everything self.set_window_title() self.match_tree_to_document() self.refresh_tree() self.update_status_bar() self.apply_settings() self.match_notebook_to_document() self.update_tool_buttons() self.update_settings_window() self.update_history() self.update_bookmarks() # save these for post_window_show(): #self.update_attributes() #self.update_node_path() # Init Step 4: show the window def window_show(self): self.window.show() # Init Step 5: do after the window is shown def post_window_show(self): # position window if self.file.remember_position and not self.file.window_maximized: if self.file.window_x != -1 and self.file.window_y != -1: self.positioning = True self.window.set_position(gtk.WIN_POS_NONE) self.window.move(self.file.window_x - self.file.diff_x, self.file.window_y - self.file.diff_y) self.positioning = False if self.file.fullscreen: self.window.fullscreen() self.open_first_notebook_tab() # need to update these because they get offcentered self.update_node_path() self.update_attributes() # one-time call to set up variables and pointers def initialize_variables(self): # find text view self.current_textview = None # search result self.found = None # keep a reference to the viewd textview, its handy self.current_textview = None # keep a reference to the viewd buffer, its handy self.current_buffer = None # create a reference to editing iterator self.editor_iter = None # create a reference to editing node self.editor_node = None # pages self.current_page = None self.last_page = None # internal flags self.updating_attributes = False self.changing_cursor = False self.switching_page = False # settings window init self.settings = None # statistics window self.statistics = None # calculate statistics on startup? self.start_time = time.time() self.start_word_count = -1 if self.file.calculate_statistics: self.start_word_count = self.document.word_count() # one time call to build the window def build_window(self): # create gtk builder self.builder = gtk.Builder() # load interface if os.path.isfile(os.path.join("ui", "main.glade")): self.builder.add_from_file(os.path.join("ui", "main.glade")) elif os.path.isfile(os.path.join("..", "ui", "main.glade")): self.builder.add_from_file(os.path.join("..", "ui", "main.glade")) elif os.path.isfile("/usr/share/kabikaboo/ui/main.glade"): self.builder.add_from_file("/usr/share/kabikaboo/ui/main.glade") # find main window self.window = self.builder.get_object("window_main") # window callbacks self.window.connect("delete-event", self.on_window_delete) self.window.connect("key-press-event", self.window_key_press_callback) self.window.connect('window-state-event', self.on_window_state_change) self.window.connect('size-allocate', self.on_window_size_allocate) self.window.connect('configure-event', self.on_window_move) # one time call to store pointers of window components def find_window_components(self): # store main horizontal panel splitter self.hpaned_main = self.builder.get_object("hpaned_main") # store left vertical panel splitter self.vpaned_left = self.builder.get_object("vpaned_left") # store attributes bullet area self.fixed_node_bullet = self.builder.get_object("fixed_node_bullet") # remember some labels self.label_title = self.builder.get_object("label_title") self.label_status_filename = self.builder.get_object("label_status_filename") self.label_node_path = self.builder.get_object("label_node_path") # grab status bar self.statusbar = self.builder.get_object("statusbar") # fixed attributes box self.attributes_area = self.builder.get_object("vpaned_attributes") # main menu self.main_menu = self.builder.get_object("menubar") self.menu_action_group = self.builder.get_object("actiongroup_menu") # one time call to create the treeview def create_treeview(self): # find tree self.treeview = self.builder.get_object("treeview") self.treeview.connect("key-press-event", self.treeview_key_press_callback) self.treeview.connect("button-press-event", self.treeview_button_press_callback) # create a treestore to hold the title and id data self.treestore = gtk.TreeStore(str, int) self.treeview.set_model(self.treestore) # create a visual expander column for titles self.treecolumn = gtk.TreeViewColumn('Titles') self.treeview.append_column(self.treecolumn) # hide the header, we dont need to see it self.treeview.set_headers_visible(False) # show tree lines - looks nice self.treeview.set_property('enable-tree-lines', True) # dont allow user to drag and drop tree nodes yet self.treeview.set_property('reorderable', False) # create a cell renderer to display title text on tree nodes self.cell = gtk.CellRendererText() self.cell.set_property('editable', True) self.cell.connect('edited', self.cell_changed_callback) # not sure what pack_start does self.treecolumn.pack_start(self.cell, True) # not sure what this does self.treecolumn.add_attribute(self.cell, 'text', 0) # when the user picks a new tree node, call this self.treeview.connect("cursor-changed", self.cursor_changed_callback) # one time call to create the notebook def create_notebook(self): # note book self.notebook = self.builder.get_object("notebook_tabs") # note book tabs list of node pointers self.book_node_ids = [] # note book tabs list of iter pointers self.book_iters = [] # note book tab textviews (one on each page) self.book_textviews = [] self.book_page = -1 # tabbed book node id list self.book_node_ids = [] # note book tabs list of iter pointers self.book_iters = [] # note book tab textviews (one on each page) self.book_textviews = [] # no page selected yet self.book_page = -1 # get the notebook self.notebook.set_scrollable(True) self.notebook.set_property('enable-popup', True) self.notebook.connect("switch-page", self.notebook_switch_page_callback) # set sizes def set_gui_sizes(self): # resize window self.window.set_default_size(self.file.window_width, self.file.window_height) # maximize if self.file.window_maximized: self.window.maximize() # resize treeview if self.file.tree_width > -1: self.hpaned_main.set_position(self.file.tree_width) # set vertical panel if self.file.attribute_panel_height > -1: self.vpaned_left.set_position(self.file.window_height - self.file.attribute_panel_height) # resize node title self.label_title.set_property("width-request", self.hpaned_main.get_position()) # destroy window action def on_window_main_destroy(self, widget, data=None): if self.keep_open: self.keep_open = self.close_window_query() if not self.keep_open: self.file.tree_width = self.hpaned_main.get_position() self.file.attribute_panel_height = self.file.window_height - self.vpaned_left.get_position() self.file.close(self.document) gtk.main_quit() # destroy window action def on_window_delete(self, widget, data=None): self.keep_open = self.close_window_query() return self.keep_open # ok to delete query def close_window_query(self): if self.file.different and not self.file.save_on_exit and self.file.working_file!='': dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, "Your changes are not saved.\nQuit anyways?") response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_NO: return True else: return False else: return False # set the title of the window def set_window_title(self): title = self.document.title if self.file.show_application_name and self.file.application_name != '': title = self.file.application_name if self.document.title != self.file.application_name: title = self.document.title + ' - ' + self.file.application_name if self.file.different: title = '*' + title self.window.set_title(title) if self.statistics: self.statistics.update_title() # update titles on all windows def update_window_titles(self): self.set_window_title() if self.settings: self.settings.set_window_title() if self.statistics: self.statistics.set_window_title() # populate the status bar def update_status_bar(self, message=''): self.label_status_filename.set_label('') status = '' if self.file.show_file_status: if self.file.working_file != '': if self.file.show_directory_status: status = self.file.working_file + ' ' else: status = os.path.basename(self.file.working_file) + ' ' else: status = 'New Document ' if self.file.different: status = status + '(changed)' if message: status = status + ' - ' + message self.label_status_filename.set_label(status) # load and set the application icon def load_application_icon(self): if os.path.isfile("kabikaboo.png"): gtk.window_set_default_icon_from_file("kabikaboo.png") elif os.path.isfile(os.path.join("..", "kabikaboo.png")): gtk.window_set_default_icon_from_file(os.path.join("..", "kabikaboo.png")) elif os.path.isfile("/usr/src/kabikaboo/kabikaboo.png"): gtk.window.set_default_icon_from_file("/usr/src/kabikaboo/kabikaboo.png") elif os.path.isfile("/usr/share/kabikaboo/kabikaboo.png"): self.window.set_icon_from_file("/usr/share/kabikaboo/kabikaboo.png") # detect some keys def window_key_press_callback(self, window, event, data=None): keyval = event.keyval name = gtk.gdk.keyval_name(keyval) mod = gtk.accelerator_get_label(keyval,event.state) #print '%s - %d' % (mod, keyval) # on some systems it adds Mod2+ into the string mod = string.replace(mod, 'Mod2+', '') # ctrl+tab if mod == 'Ctrl+Tab': self.next_notebook_page() self.notebook.grab_focus() # shift+ctrl+tab elif mod == 'Shift+Ctrl+ISO Left Tab' or mod == 'Shift+Ctrl+ISO Right Tab': self.previous_notebook_page() self.notebook.grab_focus() # alt+home elif mod == 'Alt+Home': self.default_view() self.treeview.grab_focus() # F2 (edit node title) elif mod == 'F2': self.edit_node_title() # detect textview keys def textview_key_press_callback(self, window, event, data=None): keyval = event.keyval name = gtk.gdk.keyval_name(keyval) mod = gtk.accelerator_get_label(keyval,event.state) #print '%s - %d' % (mod, keyval) # on some systems it adds Mod2+ into the string mod = string.replace(mod, 'Mod2+', '') # Ctrl+W (close tab) if mod == 'Ctrl+W': self.close_current_notebook_page() '''# italic elif mod == 'Ctrl+I': self.italic_callback(window) # bold elif mod == 'Ctrl+B': self.bold_callback(window) # underline elif mod == 'Ctrl+U': self.underline_callback(window)''' # detect treeview keys def treeview_key_press_callback(self, window, event, data=None): keyval = event.keyval name = gtk.gdk.keyval_name(keyval) mod = gtk.accelerator_get_label(keyval,event.state) #print '%s - %d' % (mod, keyval) # on some systems it adds Mod2+ into the string mod = string.replace(mod, 'Mod2+', '') # delete (delete node) if mod == 'Delete': self.remove_node_callback() # ctrl+delete (delete children) elif mod == 'Ctrl+Delete': self.remove_children_callback() # ctrl+up (move up) elif mod == 'Ctrl+Up': self.move_node_up_callback() # ctrl+down (move down) elif mod == 'Ctrl+Down': self.move_node_down_callback() # ctrl+left (move left) elif mod == 'Ctrl+Left': self.move_node_left_callback() # ctrl+right (move right) elif mod == 'Ctrl+Right': self.move_node_right_callback() # ctrl+e (edit node) elif mod == 'Ctrl+E': self.set_node_edit() # ctrl+v (view node) elif mod == 'Ctrl+V': self.set_node_view() # ctrl+g (view grandchildren) elif mod == 'Ctrl+G': self.flip_node_grandchildren() # ctrl+b (add before) elif mod == 'Ctrl+B': self.new_node_before_callback() # ctrl+a (add after) elif mod == 'Ctrl+A': self.new_node_after_callback() # ctrl+c (add child) elif mod == 'Ctrl+C': self.new_child_node_callback() # right (expand) elif mod == 'Right': self.expand_row_callback() # left (collapse) elif mod == 'Left': self.collapse_row_callback() # ctrl++ (expand children) elif mod == 'Ctrl+=' or mod == 'Ctrl+X': self.expand_all_children_callback() # home (delete children) elif mod == 'Home': self.default_view() # F2 (edit node title) elif mod == 'F2': self.edit_node_title() # callback for changed text in textview def text_changed_callback(self, textbuffer, data=None): # do we have a node selected? if self.editor_node and self.editor_iter and not self.editor_node.view: if not self.copying_buffer: # store the text from textview's buffer into our document text = textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter()) self.editor_node.set_text(text) self.bump() # callback for user beginning to edit a row of treeview # note: doesnt work, gtk stops us from modifying the cell def cell_editing_callback(self, cell, editable, path, data=None): # do we have a node selected? if self.editor_node and self.editor_iter: self.treestore.set(self.editor_iter, 0, self.editor_node.title) # callback for user editing row of treeview def cell_changed_callback(self, cell, path, new_text, data=None): # do we have a node selected? if self.editor_node and self.editor_iter: # compare new text with bulleting bullet = self.editor_node.get_title_bullet() if len(bullet) > 0: if new_text[0:len(bullet)] == bullet: # strip out the bulleting new_text = new_text[len(bullet):len(new_text)] # store the new cell title into our document if new_text != self.editor_node.title: self.editor_node.title = new_text # also update the visual treeview with the new title self.treestore.set_value(self.editor_iter, 0, self.editor_node.get_title()) # check if this is the document if self.editor_node == self.document: self.set_window_title() if self.editor_node.view: self.update_textview_safe() self.update_node_path() self.update_notebook() self.update_bookmarks() self.update_attributes() self.bump() # callback for user selecting row of treeview def cursor_changed_callback(self, treeview, data=None): if not self.changing_cursor: # grab the selection selection = treeview.get_selection() # get the iterator from the selection (model, iter) = selection.get_selected() if iter: # grab the id from the second column id = self.treestore.get_value(iter, 1) # find the node in our document matching this id node = self.document.fetch_node_by_id(id) if node: # store the currently selected iterator and node self.editor_iter = iter self.editor_node = node # copy the document's node text to the textview buffer self.add_to_notebook(node, iter) self.update_textview() self.node_visit(node) else: self.editor_node = None self.editor_iter = None self.set_textbuffer('') else: self.editor_node = None self.editor_iter = None self.set_textbuffer('') self.update_node_path() self.update_attributes() self.update_tool_buttons() # update the node path label # using statusbar.pop instead of a simpler, more powerful label because I can't # figure out how to get a label to display the long names common for node paths def update_node_path(self): # top node path if self.file.show_node_path: if self.book_page >= 0 and self.editor_node: self.label_node_path.set_label(''+self.editor_node.get_recursive_title()+'') else: self.label_node_path.set_label('') self.label_node_path.set_property('visible', True) else: self.label_node_path.set_property('visible', False) # bottom node path in status bar if self.file.show_node_path_status: self.statusbar.pop(0) status = '' if self.book_page >= 0 and self.editor_node: status = ' ' + self.editor_node.get_recursive_title() else: status = '' self.statusbar.push(0, status) # update the text in the textview def update_textview(self): if self.current_textview: if self.editor_node and self.editor_iter: if not self.editor_node.view: noundo = False if len(self.current_buffer.get_text(self.current_buffer.get_start_iter(), self.current_buffer.get_end_iter())) == 0: noundo = True if noundo: self.current_buffer.begin_not_undoable_action() self.set_textbuffer(self.editor_node.get_text()) if noundo: self.current_buffer.end_not_undoable_action() self.current_textview.set_editable(True) self.current_textview.set_cursor_visible(True) else: #self.current_buffer.set_property("redo", False) #self.current_buffer.set_property("canundo", False) self.current_buffer.begin_not_undoable_action() self.fancy_text(self.editor_node) self.current_buffer.end_not_undoable_action() self.current_textview.set_editable(False) self.current_textview.set_cursor_visible(False) else: self.current_buffer.begin_not_undoable_action() self.set_textbuffer('') self.current_buffer.end_not_undoable_action() self.current_textview.set_editable(False) self.current_textview.set_cursor_visible(False) # update the text in the textview (only if it is a view node) def update_textview_safe(self): if self.current_textview: if self.editor_node and self.editor_iter: if self.editor_node.view: noundo = False if len(self.current_buffer.get_text(self.current_buffer.get_start_iter(), self.current_buffer.get_end_iter())) == 0: noundo = True if noundo: self.current_buffer.begin_not_undoable_action() self.fancy_text(self.editor_node) if noundo: self.current_buffer.end_not_undoable_action() self.current_textview.set_editable(False) self.current_textview.set_cursor_visible(False) if not self.editor_node.view: self.current_textview.set_editable(True) self.current_textview.set_cursor_visible(True) else: self.current_textview.set_editable(False) self.current_textview.set_cursor_visible(False) else: self.current_buffer.begin_not_undoable_action() self.set_textbuffer('') self.current_buffer.end_not_undoable_action() self.current_textview.set_editable(False) self.current_textview.set_cursor_visible(False) # tree toolbar init def create_toolbar_tree(self): self.toolbar_tree = self.builder.get_object("toolbar_tree") self.toolbutton_default_view = self.builder.get_object("toolbutton_default_view") self.toolbutton_default_view.connect('clicked', self.default_view_callback) # separator self.toolbutton_sep2 = self.builder.get_object("toolbutton_sep2") # add child toolbutton self.toolbutton_add_child = self.builder.get_object("toolbutton_add_child") self.toolbutton_add_child.connect('clicked', self.new_child_node_callback) # add before toolbutton self.toolbutton_add_before = self.builder.get_object("toolbutton_add_before") self.toolbutton_add_before.connect('clicked', self.new_node_before_callback) # add after toolbutton self.toolbutton_add_after = self.builder.get_object("toolbutton_add_after") self.toolbutton_add_after.connect('clicked', self.new_node_after_callback) # separator self.toolbutton_sep3 = self.builder.get_object("toolbutton_sep3") # move up toolbutton self.toolbutton_move_up = self.builder.get_object("toolbutton_move_up") self.toolbutton_move_up.connect('clicked', self.move_node_up_callback) # move down toolbutton self.toolbutton_move_down = self.builder.get_object("toolbutton_move_down") self.toolbutton_move_down.connect('clicked', self.move_node_down_callback) # move left toolbutton self.toolbutton_move_left = self.builder.get_object("toolbutton_move_left") self.toolbutton_move_left.connect('clicked', self.move_node_left_callback) # move right toolbutton self.toolbutton_move_right = self.builder.get_object("toolbutton_move_right") self.toolbutton_move_right.connect('clicked', self.move_node_right_callback) # expand toolbutton self.toolbutton_expand = self.builder.get_object("toolbutton_expand") self.toolbutton_expand.connect('clicked', self.expand_row_callback) # expand children toolbutton self.toolbutton_expand_children = self.builder.get_object("toolbutton_expand_children") self.toolbutton_expand_children.connect('clicked', self.expand_all_children_callback) # collapse toolbutton self.toolbutton_collapse = self.builder.get_object("toolbutton_collapse") self.toolbutton_collapse.connect('clicked', self.collapse_row_callback) # remove toolbutton self.toolbutton_remove = self.builder.get_object("toolbutton_remove") self.toolbutton_remove.connect('clicked', self.remove_node_callback) # remove children toolbutton self.toolbutton_remove_children = self.builder.get_object("toolbutton_remove_children") self.toolbutton_remove_children.connect('clicked', self.remove_children_callback) # move the tree toolbar? if self.file.tree_toolbar_intree: #default is out of tree self.treebar_swap() # place the treebar in one of two places # BUG: requires restart when called from settings because buttons stop working # works fine from application startup tho... def treebar_swap(self): if self.file.tree_toolbar_intree: # in with the tree vbox_tree = self.builder.get_object("vbox_tree") self.toolbar_tree.hide() self.toolbar_tree.reparent(vbox_tree) vbox_tree.reorder_child(self.toolbar_tree, 0) vbox_tree.set_child_packing(self.toolbar_tree, False, False, 0, gtk.PACK_START) self.toolbar_tree.show() else: # up across top self.toolbar_tree.hide() hpaned_toolbar = self.builder.get_object("hpaned_toolbar") self.toolbar_tree.reparent(hpaned_toolbar) self.toolbar_tree.show() # set whether the tool buttons are enabled or not def update_tool_buttons(self): # later, we can add a settings option to customize these: tool_enabled = "visible" # TODO: user may want sensitive or visible here menu_enabled = "sensitive" # enable/disable buttons # NOTHING SELECTED if not self.editor_node or not self.editor_iter: self.toolbutton_expand.set_property(tool_enabled, False) self.toolbutton_collapse.set_property(tool_enabled, False) self.toolbutton_expand_children.set_property(tool_enabled, False) self.toolbutton_add_child.set_property(tool_enabled, False) self.toolbutton_add_after.set_property(tool_enabled, False) self.toolbutton_add_before.set_property(tool_enabled, False) self.toolbutton_remove.set_property(tool_enabled, False) self.toolbutton_remove_children.set_property(tool_enabled, False) self.toolbutton_move_up.set_property(tool_enabled, False) self.toolbutton_move_down.set_property(tool_enabled, False) self.toolbutton_move_left.set_property(tool_enabled, False) self.toolbutton_move_right.set_property(tool_enabled, False) self.toolbutton_sep2.set_property(tool_enabled, False) self.toolbutton_sep3.set_property(tool_enabled, False) self.export_node_menu_item.set_property(menu_enabled, False) self.export_node_children_menu_item.set_property(menu_enabled, False) self.export_html_node_menu_item.set_property(menu_enabled, False) self.export_html_node_children_menu_item.set_property(menu_enabled, False) self.import_text_menu_item.set_property(menu_enabled, False) self.split_one_menu_item.set_property(menu_enabled, False) self.split_two_menu_item.set_property(menu_enabled, False) self.unify_with_menu_item.set_property(menu_enabled, False) self.unify_without_menu_item.set_property(menu_enabled, False) self.toolbar_text.set_property('visible', False) self.undo_menu_item.set_property(menu_enabled, False) self.redo_menu_item.set_property(menu_enabled, False) self.cut_menu_item.set_property(menu_enabled, False) self.copy_menu_item.set_property(menu_enabled, False) self.paste_menu_item.set_property(menu_enabled, False) # NODE SELECTED else: # we have a node selected # DOCUMENT ROOT NODE SELECTED if self.editor_node == self.document: # main document node is selected self.export_node_menu_item.set_property(menu_enabled, False) self.export_node_children_menu_item.set_property(menu_enabled, False) self.export_html_node_menu_item.set_property(menu_enabled, False) self.export_html_node_children_menu_item.set_property(menu_enabled, False) self.toolbutton_expand.set_property(tool_enabled, True) self.toolbutton_collapse.set_property(tool_enabled, True) self.toolbutton_expand_children.set_property(tool_enabled, True) self.toolbutton_add_child.set_property(tool_enabled, True) self.toolbutton_add_after.set_property(tool_enabled, False) self.toolbutton_add_before.set_property(tool_enabled, False) self.toolbutton_remove.set_property(tool_enabled, False) self.toolbutton_remove_children.set_property(tool_enabled, self.editor_node.has_children()) self.toolbutton_move_up.set_property(tool_enabled, False) self.toolbutton_move_down.set_property(tool_enabled, False) self.toolbutton_move_left.set_property(tool_enabled, False) self.toolbutton_move_right.set_property(tool_enabled, False) self.toolbutton_sep2.set_property(tool_enabled, False) self.toolbutton_sep3.set_property(tool_enabled, True) self.import_text_menu_item.set_property(menu_enabled, True) self.split_one_menu_item.set_property(menu_enabled, self.editor_node.is_splittable()) self.split_two_menu_item.set_property(menu_enabled, self.editor_node.is_splittable()) self.unify_with_menu_item.set_property(menu_enabled, self.editor_node.is_unifiable()) self.unify_without_menu_item.set_property(menu_enabled, self.editor_node.is_unifiable()) # CHILD NODE SELECTED else: # a child node of the document is selected self.export_node_menu_item.set_property(menu_enabled, True) self.export_node_children_menu_item.set_property(menu_enabled, self.editor_node.has_children()) self.export_html_node_menu_item.set_property(menu_enabled, True) self.export_html_node_children_menu_item.set_property(menu_enabled, self.editor_node.has_children()) self.split_one_menu_item.set_property(menu_enabled, self.editor_node.is_splittable()) self.split_two_menu_item.set_property(menu_enabled, self.editor_node.is_splittable()) self.unify_with_menu_item.set_property(menu_enabled, self.editor_node.is_unifiable()) self.unify_without_menu_item.set_property(menu_enabled, self.editor_node.is_unifiable()) # expand button self.toolbutton_expand.set_property(tool_enabled, self.editor_node.has_children()) # collapse button self.toolbutton_collapse.set_property(tool_enabled, self.editor_node.has_children()) # show all children button self.toolbutton_expand_children.set_property(tool_enabled, self.editor_node.has_children()) self.toolbutton_add_child.set_property(tool_enabled, True) self.toolbutton_add_after.set_property(tool_enabled, True) self.toolbutton_add_before.set_property(tool_enabled, True) self.toolbutton_remove.set_property(tool_enabled, True) # remove children button self.toolbutton_remove_children.set_property(tool_enabled, self.editor_node.has_children()) # move up button self.toolbutton_move_up.set_property(tool_enabled, not self.document.is_a_first_node(self.editor_node)) # move down button self.toolbutton_move_down.set_property(tool_enabled, not self.document.is_a_last_node(self.editor_node)) # move left button self.toolbutton_move_left.set_property(tool_enabled, self.document.can_move_left(self.editor_node)) # move right button self.toolbutton_move_right.set_property(tool_enabled, self.document.can_move_right(self.editor_node)) self.import_text_menu_item.set_property(menu_enabled, True) self.toolbutton_sep2.set_property(tool_enabled, True) self.toolbutton_sep3.set_property(tool_enabled, False) # ANY TYPE OF NODE # text tool bar if not self.editor_node.view and self.file.show_toolbar_text: self.toolbar_text.set_property('visible', True) else: self.toolbar_text.set_property('visible', False) # cut n paste etc self.undo_menu_item.set_property(menu_enabled, not self.editor_node.view) self.redo_menu_item.set_property(menu_enabled, not self.editor_node.view) self.cut_menu_item.set_property(menu_enabled, not self.editor_node.view) self.copy_menu_item.set_property(menu_enabled, not self.editor_node.view) self.paste_menu_item.set_property(menu_enabled, not self.editor_node.view) # notebook related buttons if len(self.book_node_ids) > 0: self.close_current_notebook_page_menu_item.set_property(menu_enabled, True) self.add_bookmark_menu_item.set_property(menu_enabled, not self.is_current_tab_a_bookmark()) self.remove_bookmark_menu_item.set_property(menu_enabled, self.is_current_tab_a_bookmark()) else: self.close_current_notebook_page_menu_item.set_property(menu_enabled, False) self.add_bookmark_menu_item.set_property(menu_enabled, False) self.remove_bookmark_menu_item.set_property(menu_enabled, False) self.toolbar_tree.queue_draw() # top menu init def create_menu_items(self): menu_item = self.builder.get_object("imagemenuitem_new") menu_item.connect("activate", self.new_document_callback) menu_item = self.builder.get_object("imagemenuitem_open") menu_item.connect("activate", self.open_document_callback) menu_item = self.builder.get_object("imagemenuitem_save") menu_item.connect("activate", self.save_document_callback) menu_item = self.builder.get_object("imagemenuitem_saveas") menu_item.connect("activate", self.save_as_document_callback) menu_item = self.builder.get_object("menuitem_save_copy") menu_item.connect("activate", self.save_copy_callback) menu_item = self.builder.get_object("menuitem_save_version") menu_item.connect("activate", self.save_version_callback) menu_item = self.builder.get_object("imagemenuitem_quit") menu_item.connect("activate", self.on_window_main_destroy) self.undo_menu_item = self.builder.get_object("imagemenuitem_undo") self.undo_menu_item.connect("activate", self.undo_callback) self.redo_menu_item = self.builder.get_object("imagemenuitem_redo") self.redo_menu_item.connect("activate", self.redo_callback) self.cut_menu_item = self.builder.get_object("imagemenuitem_cut") self.cut_menu_item.connect("activate", self.cut_callback) self.copy_menu_item = self.builder.get_object("imagemenuitem_copy") self.copy_menu_item.connect("activate", self.copy_callback) self.paste_menu_item = self.builder.get_object("imagemenuitem_paste") self.paste_menu_item.connect("activate", self.paste_callback) menu_item = self.builder.get_object("imagemenuitem_fullscreen") menu_item.connect("activate", self.fullscreen_switch) self.show_statusbar_menu_item = self.builder.get_object("checkmenuitem_statusbar") self.show_statusbar_menu_item.connect("toggled", self.show_statusbar_callback) self.show_toolbar_tree_menu_item = self.builder.get_object("checkmenuitem_toolbar_tree") self.show_toolbar_tree_menu_item.connect("toggled", self.show_toolbar_tree_callback) menu_item = self.builder.get_object("checkmenuitem_toolbar_text") menu_item.connect("toggled", self.show_toolbar_text_callback) self.show_attributes_menu_item = self.builder.get_object("checkmenuitem_show_attributes") self.show_attributes_menu_item.connect("toggled", self.show_attributes_callback) menu_item = self.builder.get_object("checkmenuitem_spellcheck") menu_item.connect("toggled", self.checkbutton_spellcheck_callback) menu_item = self.builder.get_object("imagemenuitem_help") menu_item.connect("activate", self.help_callback) menu_item = self.builder.get_object("imagemenuitem_help_online") menu_item.connect("activate", lambda *a: gtk.show_uri(None, "http://answers.launchpad.net/kabikaboo", gtk.gdk.CURRENT_TIME)) menu_item = self.builder.get_object("imagemenuitem_translate") menu_item.connect("activate", lambda *a: gtk.show_uri(None, "https://translations.launchpad.net/kabikaboo", gtk.gdk.CURRENT_TIME)) menu_item = self.builder.get_object("imagemenuitem_report_problem") menu_item.connect("activate", lambda *a: gtk.show_uri(None, "https://bugs.launchpad.net/kabikaboo/+filebug", gtk.gdk.CURRENT_TIME)) menu_item = self.builder.get_object("imagemenuitem_about") menu_item.connect("activate", self.about_callback) menu_item = self.builder.get_object("imagemenuitem_preferences") menu_item.connect("activate", self.settings_callback) menu_item = self.builder.get_object("menuitem_export_document") menu_item.connect("activate", self.export_document_callback) menu_item = self.builder.get_object("menuitem_stats") menu_item.connect("activate", self.statistics_callback) # close tab callback self.close_current_notebook_page_menu_item = self.builder.get_object("imagemenuitem_close_current_page") self.close_current_notebook_page_menu_item.connect("activate", self.close_current_notebook_page_callback) # export buttons self.export_node_menu_item = self.builder.get_object("menuitem_export_node") self.export_node_menu_item.connect("activate", self.export_node_callback) self.export_node_children_menu_item = self.builder.get_object("menuitem_export_node_children") self.export_node_children_menu_item.connect("activate", self.export_node_children_callback) self.import_text_menu_item = self.builder.get_object("menuitem_import_text") self.import_text_menu_item.connect("activate", self.import_text_callback) menu_item = self.builder.get_object("menuitem_export_html_document") menu_item.connect("activate", self.export_html_document_callback) self.export_html_node_menu_item = self.builder.get_object("menuitem_export_html_node") self.export_html_node_menu_item.connect("activate", self.export_html_node_callback) self.export_html_node_children_menu_item = self.builder.get_object("menuitem_export_html_node_children") self.export_html_node_children_menu_item.connect("activate", self.export_html_node_children_callback) self.autoopen_menu_item = self.builder.get_object("menuitem_autoopen") self.autoopen_menu_item.connect("toggled", self.autoopen_menu_item_toggle) self.save_on_exit_menu_item = self.builder.get_object("menuitem_saveonexit") self.save_on_exit_menu_item.connect("toggled", self.save_on_exit_menu_item_toggle) self.autosave_menu_item = self.builder.get_object("checkmenuitem_autosave") self.autosave_menu_item.connect("toggled", self.autosave_menu_item_toggle) self.spellcheck_menu_item = self.builder.get_object("checkmenuitem_spellcheck") self.spellcheck_menu_item.connect("toggled", self.spellcheck_menu_item_toggle) self.split_one_menu_item = self.builder.get_object("menuitem_split_one") self.split_one_menu_item.connect("activate", self.split_node_one_callback) self.split_two_menu_item = self.builder.get_object("menuitem_split_two") self.split_two_menu_item.connect("activate", self.split_node_two_callback) self.unify_with_menu_item = self.builder.get_object("menuitem_unify_with") self.unify_with_menu_item.connect("activate", self.unify_with_callback) self.unify_without_menu_item = self.builder.get_object("menuitem_unify_without") self.unify_without_menu_item.connect("activate", self.unify_without_callback) # attributes init def connect_attributes_panel(self): self.radiobutton_edit = self.builder.get_object("radiobutton_edit") self.radiobutton_view = self.builder.get_object("radiobutton_view") self.checkbutton_children = self.builder.get_object("checkbutton_children") self.radiobutton_bulleting_none = self.builder.get_object("radiobutton_bulleting_none") self.radiobutton_bulleting_number = self.builder.get_object("radiobutton_bulleting_number") self.radiobutton_bulleting_alpha_upper = self.builder.get_object("radiobutton_bulleting_alpha_upper") self.radiobutton_bulleting_alpha_lower = self.builder.get_object("radiobutton_bulleting_alpha_lower") self.radiobutton_bulleting_roman_upper = self.builder.get_object("radiobutton_bulleting_roman_upper") self.radiobutton_bulleting_roman_lower = self.builder.get_object("radiobutton_bulleting_roman_lower") self.vpaned_left.connect('size-allocate', self.on_vpaned_left_size_allocate) self.radiobutton_edit.connect("toggled", self.radiobutton_edit_callback, "radiobutton_view") self.radiobutton_view.connect("toggled", self.radiobutton_view_callback, "radiobutton_edit") self.checkbutton_children.connect("toggled", self.checkbutton_children_callback) self.radiobutton_bulleting_none.connect("toggled", self.radiobutton_bulleting_none_callback, "radiobutton_bulleting_none") self.radiobutton_bulleting_number.connect("toggled", self.radiobutton_bulleting_number_callback, "radiobutton_bulleting_number") self.radiobutton_bulleting_alpha_upper.connect("toggled", self.radiobutton_bulleting_alpha_upper_callback, "radiobutton_bulleting_alpha_upper") self.radiobutton_bulleting_alpha_lower.connect("toggled", self.radiobutton_bulleting_alpha_lower_callback, "radiobutton_bulleting_alpha_lower") self.radiobutton_bulleting_roman_upper.connect("toggled", self.radiobutton_bulleting_roman_upper_callback, "radiobutton_bulleting_roman_upper") self.radiobutton_bulleting_roman_lower.connect("toggled", self.radiobutton_bulleting_roman_lower_callback, "radiobutton_bulleting_roman_lower") # callback for undo, redo, cut, copy and paste def cut_callback(self, data=None): self.current_buffer.cut_clipboard(gtk.Clipboard(), True) def copy_callback(self, data=None): self.current_buffer.copy_clipboard(gtk.Clipboard()) def paste_callback(self, data=None): self.current_buffer.paste_clipboard(gtk.Clipboard(), None, True) def undo_callback(self, menuitem, data=None): if self.current_buffer.can_undo(): self.current_buffer.undo() def redo_callback(self, menuitem, data=None): if self.current_buffer.can_redo(): self.current_buffer.redo() # callback for help # gnome-help will looks for help file in /usr/share/gnome/help/kabikaboo/ # so the default English help will be /usr/share/gnome/help/kabikaboo/C/kabikaboo.xml def help_callback(self, data=None): if os.path.isdir('/usr/share/gnome/help/kabikaboo/'): props = { gnome.PARAM_APP_DATADIR : '/usr/share' } gnome.program_init('kabikaboo', '1.7', properties=props) gnome.help_display('kabikaboo') else: helpError='yelp requires help files to be stored in a very specific location.\n\ Try sudo cp -R help/ /usr/share/gnome/help/kabikaboo/ and restart Kabikaboo.' print helpError helpDialog = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE, message_format=helpError) response = helpDialog.run() if response == gtk.RESPONSE_CLOSE: helpDialog.destroy() # callback for new child node def new_child_node_callback(self, data=None): if self.editor_node and self.editor_iter: new = self.document.add_node(self.editor_node, 'new', '') new_iter = self.add_document_node_to_tree(new, self.editor_iter) if not self.row_expanded(self.editor_iter): self.expand_row(self.editor_iter) self.bump() self.update_tool_buttons() self.update_textview_safe() self.update_attributes() self.update_tree_node_titles(self.editor_node) if self.file.move_on_new: self.editor_node = new self.editor_iter = new_iter path = self.treeview.get_model().get_path(new_iter) self.treeview.expand_to_path(path) self.treeview.scroll_to_cell(path) self.treeview.set_cursor(path) self.treeview.grab_focus() self.node_visit(new) # callback for move node def move_node_up_callback(self, data=None): if self.editor_node and self.editor_iter: if self.document.can_move_up(self.editor_node): move_node = self.document.get_move_up_node(self.editor_node) self.find_iter_by_id(move_node.id) if self.found: self.treestore.swap(self.editor_iter, self.found) self.document.move_node_up(self.editor_node) self.bump() self.update_tool_buttons() self.update_textview_safe() self.update_notebook_iters() self.update_node_path() self.update_tree_node_titles(self.editor_node) self.update_bookmarks() self.update_notebook() # callback for move node def move_node_down_callback(self, data=None): if self.editor_node and self.editor_iter: if self.document.can_move_down(self.editor_node): move_node = self.document.get_move_down_node(self.editor_node) self.find_iter_by_id(move_node.id) if self.found: self.treestore.swap(self.editor_iter, self.found) self.document.move_node_down(self.editor_node) self.bump() self.update_tool_buttons() self.update_textview_safe() self.update_notebook_iters() self.update_node_path() self.update_tree_node_titles(self.editor_node) self.update_bookmarks() self.update_notebook() # callback for move node def move_node_left_callback(self, data=None): if self.editor_node and self.editor_iter: if self.document.can_move_left(self.editor_node): self.find_parent_iter_of(self.editor_node) sibling_iter = self.found self.find_parent_iter_of(self.editor_node.parent) parent_iter = self.found new_iter = self.insert_after_node_in_tree(self.editor_node, parent_iter, sibling_iter) old_iter = self.editor_iter self.document.move_node_left(self.editor_node) self.editor_iter = new_iter path = self.treestore.get_path(self.editor_iter) self.treeview.set_cursor(path) self.treestore.remove(old_iter) self.bump() self.update_tool_buttons() self.update_textview_safe() self.update_notebook_iters() self.update_node_path() self.update_tree_node_titles(self.editor_node) self.update_bookmarks() self.update_notebook() # callback for move node def move_node_right_callback(self, data=None): if self.editor_node and self.editor_iter: if self.document.can_move_right(self.editor_node): new_parent_node = self.document.get_node_before(self.editor_node) self.find_iter_by_id(new_parent_node.id) new_parent_iter = self.found old_node = self.editor_node new_iter = self.add_document_node_to_tree(old_node, new_parent_iter) old_iter = self.editor_iter self.document.move_node_right(self.editor_node) self.editor_iter = new_iter path = self.treestore.get_path(self.editor_iter) if not self.row_expanded(new_parent_iter): self.expand_row(new_parent_iter) self.treeview.set_cursor(path) self.treestore.remove(old_iter) self.bump() self.update_tool_buttons() self.update_textview_safe() self.update_notebook_iters() self.update_node_path() self.update_tree_node_titles(self.editor_node) self.update_bookmarks() self.update_notebook() # callback for new def new_document_callback(self, data=None): allow_new = True if self.file.different: allow_new = False dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, 'You have not saved your current document.\nContinue?') response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: allow_new = True if allow_new: self.book_node_ids = [] # note book tabs list of iter pointers self.book_iters = [] # note book tab textviews (one on each page) self.book_textviews = [] self.book_page = -1 # pointers self.current_page = None self.last_page = None self.current_textview = None self.editor_node = None self.editor_iter = None self.file.new(self.document) self.populate_interface() self.default_view() self.document.post_load() self.unbump() self.start_time = time.time() if self.file.calculate_statistics: self.start_word_count = self.document.word_count() if self.statistics: self.statistics.new_data(self.document) # callback for open def open_document_callback(self, data=None): chooser = gtk.FileChooserDialog(title="Open Kabikaboo Document", parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) filter = gtk.FileFilter() filter.set_name("Kaboo documents") # might be useful to add in the future # filter.add_mime_type("text/kaboo") filter.add_pattern("*.kaboo") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) if self.file.working_file != '': chooser.set_current_folder(self.file.last_directory) elif self.file.last_directory != '': chooser.set_current_folder(self.file.last_directory) else: home = os.path.expanduser('~') chooser.set_folder_name(os.path.join(home, 'Documents')) response = chooser.run() if response == gtk.RESPONSE_OK: self.open_file(chooser.get_filename(), chooser) chooser.destroy() elif response == gtk.RESPONSE_CANCEL: chooser.destroy() # file handler def open_file(self, filename, dialog=None): result, new_document = self.file.load_from_file(filename) # open succeeded if result: self.document = new_document # update gui self.populate_interface() self.open_first_notebook_tab() self.update_history() # upgrade? if not self.file.upgraded: self.unbump() if dialog: dialog.destroy() if self.file.upgraded: dialog2 = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, self.file.load_file_message) response = dialog2.run() dialog2.destroy() # run some post loading logic self.document.post_load() # mark the current time self.start_time = time.time() # calculate statistics on startup (if enabled) if self.file.calculate_statistics: self.start_word_count = self.document.word_count() if self.statistics: self.statistics.new_data(self.document) # open failed else: dialog2 = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, 'Error opening file.') response = dialog2.run() dialog2.destroy() return result # opens the last file, maybe def open_file_on_startup(self): # file open flag file_opened = False # check for passed argument (filename) if len(sys.argv) >=2: file_opened, opened_document = self.file.load_from_file(sys.argv[1]) if file_opened: self.document = opened_document # after we load do this self.document.post_load() else: print 'Failed to open file from command line: %s' % sys.argv[1] # try to open file from settings file else: allow_open_last_file = True # detect program crash if not self.file.proper_shutdown: if self.file.opened_attempts >= self.file.max_open_attempts: allow_open_last_file = False print 'Not opening last file (%d failed attempts)...' % self.file.opened_attempts '''message = "Program crash detected on last run.\nNot loading last opened document." dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "Kabikaboo Crash Alert\n\n" + message) response = dialog.run() dialog.destroy()''' # open the file if no previous crash if allow_open_last_file: # open last file file_opened, opened_document = self.file.open(self.document) self.file.opened_attempts += 1 self.file.save_recovery(False) if file_opened: self.document = opened_document # do post loading logic self.document.post_load() # warn about upgrades if self.file.upgraded: dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, self.file.load_file_message) response = dialog.run() dialog.destroy() # if no file, use default data if not file_opened: if self.file.sample_data: self.document.generate_test_data() else: self.file.new(self.document) self.document.post_load() self.file.working_file = '' # callback for save def save_document_callback(self, data=None): if self.file.working_file != '': self.file.save(self.document) else: self.save_as_document_callback(data) self.unbump() self.update_history() self.update_status_bar() # callback for save version def save_version_callback(self, data=None): self.save_version() # save a new version def save_version(self): result, version = self.file.save_version(self.document) self.unbump() self.update_history() self.update_status_bar('Version %d saved.' % version) # callback for save as def save_as_document_callback(self, data=None): chooser = gtk.FileChooserDialog(title="Save Kabikaboo Document - " + self.document.title, parent=None, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) filter = gtk.FileFilter() filter.set_name("Kaboo documents") filter.add_pattern("*.kaboo") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) if self.file.working_file != '': chooser.set_current_folder(self.file.last_directory) chooser.set_current_name(self.file.working_file) elif self.file.last_directory != '': chooser.set_current_folder(self.file.last_directory) chooser.set_current_name(self.document.title + '.kaboo') else: home = os.path.expanduser('~') chooser.set_current_folder(os.path.join(home, 'Documents')) chooser.set_current_name(self.document.title + '.kaboo') response = chooser.run() if response == gtk.RESPONSE_OK: self.file.save_to_file(chooser.get_filename(), self.document) self.unbump() self.update_status_bar() self.update_history() self.open_file(chooser.get_filename(), chooser) chooser.destroy() elif response == gtk.RESPONSE_CANCEL: chooser.destroy() # callback for save copy def save_copy_callback(self, data=None): chooser = gtk.FileChooserDialog(title="Save Copy of Kabikaboo Document - " + self.document.title, parent=None, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) filter = gtk.FileFilter() filter.set_name("Kaboo documents") filter.add_pattern("*.kaboo") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) if self.file.working_file != '': chooser.set_current_folder(self.file.last_directory) chooser.set_current_name(self.file.working_file) elif self.file.last_directory != '': chooser.set_current_folder(self.file.last_directory) chooser.set_current_name(self.document.title + '.kaboo') else: home = os.path.expanduser('~') chooser.set_current_folder(os.path.join(home, 'Documents')) chooser.set_current_name(self.document.title + '.kaboo') response = chooser.run() if response == gtk.RESPONSE_OK: self.file.save_to_file(chooser.get_filename(), self.document) self.unbump() self.update_status_bar() self.update_history() self.open_file(chooser.get_filename(), chooser) chooser.destroy() elif response == gtk.RESPONSE_CANCEL: chooser.destroy() # callback for new node before def new_node_before_callback(self, data=None): if self.editor_node and self.editor_iter: parent = self.find_parent_iter_of(self.editor_node) new = self.document.add_node_before(self.editor_node, 'new', '') if new: new_iter = self.insert_before_node_in_tree(new, parent, self.editor_iter) self.bump() self.update_tool_buttons() self.update_textview_safe() self.update_tree_node_titles(self.editor_node) if self.file.move_on_new: self.editor_node = new self.editor_iter = new_iter path = self.treeview.get_model().get_path(new_iter) self.treeview.expand_to_path(path) self.treeview.scroll_to_cell(path) self.treeview.set_cursor(path) self.treeview.grab_focus() self.node_visit(new) # callback for new node after def new_node_after_callback(self, data=None): if self.editor_node and self.editor_iter: parent = self.find_parent_iter_of(self.editor_node) new = self.document.add_node_after(self.editor_node, 'new', '') if new: new_iter = self.insert_after_node_in_tree(new, parent, self.editor_iter) self.bump() self.update_tool_buttons() self.update_textview_safe() self.update_tree_node_titles(self.editor_node) if self.file.move_on_new: self.editor_node = new self.editor_iter = new_iter path = self.treeview.get_model().get_path(new_iter) self.treeview.expand_to_path(path) self.treeview.scroll_to_cell(path) self.treeview.set_cursor(path) self.treeview.grab_focus() self.node_visit(new) # callback for remove node def remove_node_callback(self, data=None): if self.editor_node and self.editor_iter: message = "Remove '"+self.editor_node.title+"'" if self.editor_node.has_children(): message += "\nand all of its children?" else: message += "?" dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, message) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # try and delete the node if self.document.remove_node(self.editor_node): parent = self.editor_node.parent self.prune_dead_nodes() self.editor_node = None self.editor_iter = None self.treeview.get_selection().unselect_all() self.bump() self.update_tool_buttons() self.update_attributes() self.update_notebook() self.update_bookmarks() self.update_node_path() self.update_tree_node_titles(parent) self.update_textview_safe() # callback for remove children of a node, but not the node def remove_children_callback(self, data=None): if self.editor_node and self.editor_iter: message = "Remove children of\n'"+self.editor_node.title+"'?" dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, message) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: # try and delete the node self.remove_children() # remove children of a node, but not the node def remove_children(self, data=None): if self.editor_node and self.editor_iter: # try and delete the node if self.document.remove_children(self.editor_node): self.prune_dead_nodes() self.bump() self.update_tool_buttons() self.update_textview_safe() self.update_notebook() self.update_bookmarks() self.update_attributes() # callback for remove node def expand_all_nodes_callback(self, data=None): self.expand_all_nodes() self.update_tool_buttons() # expand all children of a node def expand_all_children_callback(self, data=None): if self.editor_iter: self.expand_all_children(self.editor_iter) self.update_tool_buttons() # collapse row def expand_row_callback(self, data=None): if self.editor_iter: self.expand_row(self.editor_iter) self.update_tool_buttons() # collapse row def collapse_row_callback(self, data=None): if self.editor_iter: self.collapse_row(self.editor_iter) self.update_tool_buttons() # callback refreshing the tree def refresh_tree_callback(self, data=None): self.refresh_tree() # callback for showing the default document view def default_view_callback(self, data=None): self.default_view() # recursively add document nodes to treeview def add_document_node_to_tree(self, node, top): iter = self.treestore.append(top, [node.get_title(), node.id]) for child in node.children.list: self.add_document_node_to_tree(child, iter) return iter # recursively add document nodes to treeview def insert_before_node_in_tree(self, node, parent, sibling): iter = self.treestore.insert_before(parent, sibling, [node.get_title(), node.id]) for child in node.children.list: self.add_document_node_to_tree(child, iter) return iter # recursively add document nodes to treeview def insert_after_node_in_tree(self, node, parent, sibling): iter = self.treestore.insert_after(parent, sibling, [node.get_title(), node.id]) for child in node.children.list: self.add_document_node_to_tree(child, iter) return iter # add the first node form the document to the tree, then recurse def match_tree_to_document(self): self.add_document_node_to_tree(self.document, None) self.expand_document_node() # refresh the tree def refresh_tree(self): self.treestore.clear() self.add_document_node_to_tree(self.document, None) self.expand_document_node() self.editor_node = None self.editor_iter = None self.treeview.get_selection().unselect_all() self.update_textview() self.update_tool_buttons() self.update_attributes() self.update_node_path() self.update_tree_node_titles(self.document) # expand the first node def default_view(self): iter = self.treestore.get_iter_first() path = self.treestore.get_path(iter) self.treeview.collapse_row(path) self.treeview.expand_row(path, False) self.editor_node = self.document self.editor_iter = iter selection = self.treeview.get_selection() selection.select_iter(iter) self.add_to_notebook(self.editor_node, self.editor_iter) self.open_first_notebook_tab() self.update_tool_buttons() self.update_attributes() self.update_node_path() self.update_textview() self.node_visit(self.editor_node) # expand the first node def expand_document_node(self): iter = self.treestore.get_iter_first() path = self.treestore.get_path(iter) self.treeview.expand_row(path, False) # expand all nodes def expand_all_nodes(self): self.treeview.expand_all() # expand all nodes def expand_row(self, iter): path = self.treestore.get_path(iter) self.treeview.collapse_row(path) self.treeview.expand_row(path, False) # expand all nodes def expand_all_children(self, iter): path = self.treestore.get_path(iter) self.treeview.expand_row(path, True) # is this row expanded? def row_expanded(self, iter): path = self.treestore.get_path(iter) return self.treeview.row_expanded(path) # collapse a row def collapse_row(self, iter): path = self.treestore.get_path(iter) self.treeview.collapse_row(path) # collapse all nodes def collapse_all_nodes(self): self.treeview.collapse_all() expand_document_node() # find parent iter def find_parent_iter_of(self, node): iter = None if node.parent: self.find_iter_by_id(node.parent.id) iter = self.found return iter # find iter in tree, using id column def find_iter_by_id(self, id): found = None # callback on treestore.foreach def foreach_iter(model, path, iter, user_data): if iter: # find id this_id = model.get_value(iter, 1) # match parent id? if this_id == id: found = iter self.found = iter return found # loop through entire tree, finding dead nodes self.treestore.foreach(foreach_iter, None) # success? return found # find and place a new node in the tree (we dont know where) def place_new_node_in_tree(self, node): # parent parent = None parent_iter = None if node.parent: # callback on treestore.foreach def foreach_iter(model, path, iter, user_data): if iter: # find id id = self.treestore.get_value(iter, 1) # match parent id? if id == node.parent.id: parent_iter = iter # loop through entire tree, finding dead nodes self.treestore.foreach(foreach_iter, None) # success? self.add_document_node_to_tree(node, parent_iter) # check for deleted nodes def prune_dead_nodes(self): # make a list of dead nondes dead = [] # callback on treestore.foreach def foreach_iter(model, path, iter, user_data): if iter: # find id id = self.treestore.get_value(iter, 1) # check id if it is in document if not self.document.valid_id(id): # not valid id, add it to dead list dead.append(iter) # loop through entire tree, finding dead nodes self.treestore.foreach(foreach_iter, None) # loop backwards thru dead list to remove nodes from treestore for node in reversed(dead): self.treestore.remove(node) # update the tree node titles, possibly just a subset def update_tree_node_titles(self, node): # parent if node: node_iter = self.find_iter_by_id(node.id) # callback on treestore.foreach def foreach_iter(model, path, iter, user_data): if iter: # find id this_id = model.get_value(iter, 1) treenode = self.document.fetch_node_by_id(this_id) # can maybe optimize this by only updating "node"'s children if treenode: self.treestore.set_value(iter, 0, treenode.get_title()) # loop through entire tree, finding dead nodes self.treestore.foreach(foreach_iter, node) # bump the file to changed def bump(self): if self.file.working_file!='': self.file.different = True self.update_status_bar() self.set_window_title() # bump the file to unchanged def unbump(self): self.file.different = False self.update_status_bar() self.set_window_title() # maximize event def on_window_state_change(self, window, event): if event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED: self.file.window_maximized = True else: self.file.window_maximized = False # store the window size def on_window_size_allocate(self, window, requisition, userdate=None): self.file.window_width = requisition.width if not self.file.window_height == requisition.height: self.file.window_height = requisition.height self.vpaned_left.set_position(self.file.window_height - 180) # store the window position def on_window_move(self, window, properties, userdata=None): if not self.positioning: self.file.window_x = properties.x self.file.window_y = properties.y # callback for tree/text slider change def on_vpaned_left_size_allocate(self, widget, requisition, userdate=None): # resize node title label self.label_title.set_property("width-request", self.hpaned_main.get_position()) def autoopen_menu_item_toggle(self, toggle, data=None): if not editor.building_gui: self.file.autoopen = self.autoopen_menu_item.get_active() self.file.save_settings_default() self.update_settings_window() def save_on_exit_menu_item_toggle(self, toggle, data=None): if not editor.building_gui: self.file.save_on_exit = self.save_on_exit_menu_item.get_active() self.file.save_settings_default() self.update_settings_window() def autosave_menu_item_toggle(self, toggle, data=None): if not editor.building_gui: self.file.autosave = self.autosave_menu_item.get_active() self.file.save_settings_default() self.update_settings_window() self.check_autosave() def export_document_callback(self, data=None): self.export_to_text(self.document, True) def export_node_callback(self, data=None): if self.editor_node: self.export_to_text(self.editor_node, False) def export_node_children_callback(self, data=None): if self.editor_node: self.export_to_text(self.editor_node, True) def export_to_text(self, node, recurse=True): chooser = gtk.FileChooserDialog(title='Export Kabikaboo Text Document - ' + self.document.title, parent=None, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) filter = gtk.FileFilter() filter.set_name('.txt documents') filter.add_pattern('*.txt') chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name('Kaboo documents') filter.add_pattern('*.kaboo') chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name('All files') filter.add_pattern('*') chooser.add_filter(filter) if self.file.last_export_directory != '': chooser.set_current_folder(self.file.last_export_directory) else: home = os.path.expanduser('~') chooser.set_current_folder(os.path.join(home, 'Documents')) chooser.set_current_name(self.document.title + '.txt') response = chooser.run() if response == gtk.RESPONSE_OK: self.file.export_to_text_file(self.document, node, chooser.get_filename(), recurse, self.document.show_titles_in_export) chooser.destroy() elif response == gtk.RESPONSE_CANCEL: chooser.destroy() def export_html_document_callback(self, data=None): self.export_to_html(self.document, True) def export_html_node_callback(self, data=None): if self.editor_node: self.export_to_html(self.editor_node, False) def export_html_node_children_callback(self, data=None): if self.editor_node: self.export_to_html(self.editor_node, True) def export_to_html(self, node, recurse=True): chooser = gtk.FileChooserDialog(title='Export Kabikaboo HTML Document - ' + self.document.title, parent=None, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) filter = gtk.FileFilter() filter.set_name('HTML documents') filter.add_pattern('*.html') filter.add_pattern('*.htm') chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name('Kaboo documents') filter.add_pattern('*.kaboo') chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name('All files') filter.add_pattern('*') chooser.add_filter(filter) if self.file.last_export_directory != '': chooser.set_current_folder(self.file.last_export_directory) else: home = os.path.expanduser('~') chooser.set_current_folder(os.path.join(home, 'Documents')) chooser.set_current_name(self.document.title + '.html') response = chooser.run() if response == gtk.RESPONSE_OK: self.file.export_to_html_file(self.document, node, chooser.get_filename(), recurse, self.document.show_titles_in_export) chooser.destroy() elif response == gtk.RESPONSE_CANCEL: chooser.destroy() def radiobutton_edit_callback(self, button, data=None): self.set_node_edit() self.update_tool_buttons() def radiobutton_view_callback(self, button, data=None): self.set_node_view() self.update_tool_buttons() def set_node_edit(self): if self.editor_node and not self.updating_attributes: if self.editor_node.view != False: self.editor_node.view = False self.bump() self.update_attributes() self.current_buffer.begin_not_undoable_action() self.update_textview() self.current_buffer.end_not_undoable_action() def set_node_view(self): if self.editor_node and not self.updating_attributes: if self.editor_node.view != True: self.editor_node.view = True self.bump() self.update_attributes() self.current_buffer.begin_not_undoable_action() self.update_textview() self.current_buffer.end_not_undoable_action() def flip_node_grandchildren(self): if self.editor_node and self.editor_node.view and not self.updating_attributes: self.editor_node.all = not self.editor_node.all self.bump() self.update_attributes() self.update_textview() def checkbutton_children_callback(self, toggle, data=None): if self.editor_node and not self.updating_attributes: if self.editor_node.all != self.checkbutton_children.get_active(): self.editor_node.all = self.checkbutton_children.get_active() self.bump() self.update_attributes() self.update_textview() def radiobutton_bulleting_none_callback(self, button, data=None): if self.editor_node and not self.updating_attributes: self.editor_node.bulleting = self.editor_node.BULLETING_NONE self.bump() self.update_attributes() if self.editor_node.view: self.update_textview() self.update_tree_node_titles(self.editor_node) self.update_notebook() def radiobutton_bulleting_number_callback(self, button, data=None): if self.editor_node and not self.updating_attributes: self.editor_node.bulleting = self.editor_node.BULLETING_NUMBER self.bump() self.update_attributes() if self.editor_node.view: self.update_textview() self.update_tree_node_titles(self.editor_node) self.update_notebook() def radiobutton_bulleting_alpha_upper_callback(self, button, data=None): if self.editor_node and not self.updating_attributes: self.editor_node.bulleting = self.editor_node.BULLETING_ALPHA_UPPER self.bump() self.update_attributes() if self.editor_node.view: self.update_textview() self.update_tree_node_titles(self.editor_node) self.update_notebook() def radiobutton_bulleting_alpha_lower_callback(self, button, data=None): if self.editor_node and not self.updating_attributes: self.editor_node.bulleting = self.editor_node.BULLETING_ALPHA_LOWER self.bump() self.update_attributes() if self.editor_node.view: self.update_textview() self.update_tree_node_titles(self.editor_node) self.update_notebook() def radiobutton_bulleting_roman_upper_callback(self, button, data=None): if self.editor_node and not self.updating_attributes: self.editor_node.bulleting = self.editor_node.BULLETING_ROMAN_UPPER self.bump() self.update_attributes() if self.editor_node.view: self.update_textview() self.update_tree_node_titles(self.editor_node) self.update_notebook() def radiobutton_bulleting_roman_lower_callback(self, button, data=None): if self.editor_node and not self.updating_attributes: self.editor_node.bulleting = self.editor_node.BULLETING_ROMAN_LOWER self.bump() self.update_attributes() if self.editor_node.view: self.update_textview() self.update_tree_node_titles(self.editor_node) self.update_notebook() # update the attributes pane def update_attributes(self): if self.file.show_attributes: if self.editor_node and self.editor_iter and not self.updating_attributes: self.updating_attributes = True self.label_title.set_label(''+self.editor_node.title+'') self.label_title.set_tooltip_text(self.editor_node.get_recursive_title()) if not self.editor_node.view: self.radiobutton_edit.set_active(True) self.checkbutton_children.set_property("visible", False) else: if self.editor_node.all: self.checkbutton_children.set_active(True) else: self.checkbutton_children.set_active(False) self.radiobutton_view.set_active(True) self.checkbutton_children.set_property("visible", True) if self.editor_node.bulleting == self.editor_node.BULLETING_NONE: self.radiobutton_bulleting_none.set_active(True) if self.editor_node.bulleting == self.editor_node.BULLETING_NUMBER: self.radiobutton_bulleting_number.set_active(True) if self.editor_node.bulleting == self.editor_node.BULLETING_ALPHA_UPPER: self.radiobutton_bulleting_alpha_upper.set_active(True) if self.editor_node.bulleting == self.editor_node.BULLETING_ALPHA_LOWER: self.radiobutton_bulleting_alpha_lower.set_active(True) if self.editor_node.bulleting == self.editor_node.BULLETING_ROMAN_UPPER: self.radiobutton_bulleting_roman_upper.set_active(True) if self.editor_node.bulleting == self.editor_node.BULLETING_ROMAN_LOWER: self.radiobutton_bulleting_roman_lower.set_active(True) if self.editor_node.has_children() and self.file.show_bullets: self.fixed_node_bullet.set_property("visible", True) else: self.fixed_node_bullet.set_property("visible", False) self.attributes_area.set_property("visible", True) self.updating_attributes = False else: self.attributes_area.set_property("visible", False) else: self.attributes_area.set_property("visible", False) def scroll_textview_callback(self, scrolled_window, scrollType, horizontal, data=None): if self.current_textview: self.current_textview.grab_focus() # add node to notebook at beginning of it (dont call this directly, use add_to_notebook instead) def prepend_to_notebook(self, node, iter): scrolled_window = gtk.ScrolledWindow() scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.set_border_width(10) textbuffer = gtksourceview2.Buffer() textview = gtksourceview2.View(textbuffer) scrolled_window.add(textview) if self.file.tab_bullets: tab_title = node.get_spaced_bullet_title(14) else: tab_title = node.get_spaced_title(14) label = gtk.Label(tab_title) label.set_tooltip_text(node.get_recursive_title()) scrolled_window.show() scrolled_window.connect("scroll-child", self.scroll_textview_callback) textview.show() # word wrap textview.set_wrap_mode(gtk.WRAP_WORD) # text view margins textview.set_right_margin(8) textview.set_left_margin(8) textview.set_pixels_above_lines(5) textview.set_pixels_below_lines(5) textview.set_pixels_inside_wrap(0) # text buffer tags textbuffer.create_tag("normal", weight=pango.WEIGHT_NORMAL, family="verdana") textbuffer.create_tag("bold", weight=pango.WEIGHT_BOLD) textbuffer.create_tag("italic", style=pango.STYLE_ITALIC) textbuffer.create_tag("underline", underline=pango.UNDERLINE_SINGLE) textbuffer.create_tag("small", scale=pango.SCALE_SMALL, weight=pango.SCALE_SMALL, family="verdana") label.show() self.notebook.prepend_page(scrolled_window, label) self.book_node_ids.insert(0, node.id) self.book_iters.insert(0, iter) self.book_textviews.insert(0, textview) #update the text self.current_textview = textview self.current_buffer = textbuffer self.editor_iter = iter self.editor_node = node #gtkspell if(self.file.spellcheck): gtkspell.Spell(textview, lang=None) self.update_textview() # add node to notebook def add_to_notebook(self, node, iter): if self.document.tab_max < 1: return (added, moved, changed, duplicate, overflow, previousIndex, newIndex) = self.document.add_tab(node) if changed: if added: self.changing_cursor = True self.prepend_to_notebook(node, iter) self.notebook_switch_node(node) self.changing_cursor = False if moved: self.changing_cursor = True if previousIndex >= 0: book_textview = self.book_textviews[previousIndex] book_iter = self.book_iters[previousIndex] book_node_id = self.book_node_ids[previousIndex] del self.book_textviews[previousIndex] del self.book_iters[previousIndex] del self.book_node_ids[previousIndex] self.book_textviews.insert(0, book_textview) self.book_iters.insert(0, book_iter) self.book_node_ids.insert(0, book_node_id) self.book_page = 0 # change the gui notebook if previousIndex >= 0: page = self.notebook.get_nth_page(previousIndex) if page>-1: self.notebook.reorder_child(page, 0) self.notebook_switch_node(node) self.changing_cursor = False if overflow: self.check_notebook() #self.verify_notebook() def close_notebook_page(self, node): if node.id in self.book_node_ids: self.changing_cursor = True self.switching_page = True index = self.book_node_ids.index(node.id) if(len(self.book_node_ids) > 1): new_index = index + 1 if(new_index >= len(self.book_node_ids)): new_index = 0 self.current_textview = self.book_textviews[new_index] self.current_buffer = self.current_textview.get_buffer() self.editor_iter = self.book_iters[new_index] self.editor_node = self.document.fetch_node_by_id(self.book_node_ids[new_index]) path = self.treeview.get_model().get_path(self.editor_iter) self.treeview.expand_to_path(path) self.treeview.set_cursor(path) # when the user changes the text, call this self.current_buffer.connect("changed", self.text_changed_callback) self.current_textview.connect("populate-popup", self.textview_populate_popup_callback) self.current_textview.connect('key-press-event', self.textview_key_press_callback) # key press on textview self.update_attributes() self.update_tool_buttons() self.update_node_path() #self.update_textview() self.notebook.set_current_page(new_index) # visit this node self.node_visit(self.editor_node) #self.blank_hidden_tabs(self.editor_node) self.book_page = index # remove node from list self.document.remove_tab(node) self.book_textviews.remove(self.book_textviews[index]) self.book_iters.remove(self.book_iters[index]) self.book_node_ids.remove(self.book_node_ids[index]) self.notebook.remove_page(index) self.changing_cursor = False self.switching_page = False # was that the last page? if len(self.book_node_ids) == 0: self.book_page = -1 def close_current_notebook_page(self): if self.editor_node: self.close_notebook_page(self.editor_node) self.update_tool_buttons() self.update_node_path() def close_current_notebook_page_callback(self, data=None): self.close_current_notebook_page() def next_notebook_page(self): #self.verify_notebook() if not self.switching_page and len(self.document.tabs) > 0: page_num = self.book_page + 1 if page_num >= len(self.document.tabs): page_num = 0 node = self.document.fetch_node_by_id(self.book_node_ids[page_num]) self.notebook_switch_node(node) self.node_visit(node) def previous_notebook_page(self): #self.verify_notebook() if not self.switching_page and len(self.document.tabs) > 0: page_num = self.book_page - 1 if page_num < 0: page_num = len(self.document.tabs) - 1 node = self.document.fetch_node_by_id(self.book_node_ids[page_num]) self.notebook_switch_node(node) self.node_visit(node) # called when user selects a new tab def notebook_switch_page_callback(self, notebook, page, page_num): if not self.switching_page: if page_num >= 0 and self.book_page != page_num and page_num < len(self.book_textviews): self.changing_cursor = True self.current_textview = self.book_textviews[page_num] self.current_buffer = self.current_textview.get_buffer() self.editor_iter = self.book_iters[page_num] self.editor_node = self.document.fetch_node_by_id(self.book_node_ids[page_num]) path = self.treeview.get_model().get_path(self.editor_iter) self.treeview.expand_to_path(path) self.treeview.set_cursor(path) # when the user changes the text, call this self.current_buffer.connect("changed", self.text_changed_callback) self.current_textview.connect("populate-popup", self.textview_populate_popup_callback) self.current_textview.connect('key-press-event', self.textview_key_press_callback) # todo: hook into context menu via "populate-popup" callback self.book_page = page_num #self.blank_hidden_tabs(self.editor_node) self.update_attributes() self.update_tool_buttons() self.update_node_path() #self.update_textview() self.node_visit(self.editor_node) self.changing_cursor = False # force the notebook to switch to a node (which must be already in the notebook) # usually happens from ctrl+tab or treeview def notebook_switch_node(self, node): result = False if node.id in self.book_node_ids: self.changing_cursor = True self.switching_page = True index = self.book_node_ids.index(node.id) self.current_textview = self.book_textviews[index] self.current_buffer = self.current_textview.get_buffer() self.editor_iter = self.book_iters[index] self.editor_node = self.document.fetch_node_by_id(self.book_node_ids[index]) path = self.treeview.get_model().get_path(self.editor_iter) self.treeview.expand_to_path(path) self.treeview.set_cursor(path) # when the user changes the text, call this self.current_buffer.connect("changed", self.text_changed_callback) self.current_textview.connect("populate-popup", self.textview_populate_popup_callback) self.current_textview.connect('key-press-event', self.textview_key_press_callback) self.book_page = index # key press on textview self.update_attributes() self.update_tool_buttons() self.update_node_path() #self.update_textview() self.notebook.set_current_page(index) #self.blank_hidden_tabs(self.editor_node) self.changing_cursor = False self.switching_page = False result = True return result # check the notebook for too many tabs def check_notebook(self): while(len(self.book_node_ids) > self.document.tab_max): self.book_textviews.remove(self.book_textviews[len(self.book_textviews)-1]) self.book_iters.remove(self.book_iters[len(self.book_iters)-1]) self.book_node_ids.remove(self.book_node_ids[len(self.book_node_ids)-1]) self.notebook.remove_page(-1) # verify that the notebook is correct def verify_notebook(self): mismatch = False if len(self.document.tabs) != len(self.book_node_ids): print 'book_node_ids out of sync %d ' % len(self.document.tabs) print 'book_node_ids out of sync %d ' % len(self.book_node_ids) mismatch = True if len(self.document.tabs) != len(self.book_textviews): print 'book_textviews out of sync %d ' % len(self.document.tabs) print 'book_textviews out of sync %d ' % len(self.book_textviews) mismatch = True if len(self.document.tabs) != len(self.book_iters): print 'book_iters out of sync %d ' % len(self.document.tabs) print 'book_iters out of sync %d ' % len(self.book_iters) mismatch = True if not mismatch: for index, node_id in enumerate(self.document.tabs): if node_id != self.book_node_ids[index]: print 'book mismatch at index %d' % index # remove any notebook pages, and rebuild all tabs def match_notebook_to_document(self): while self.notebook.get_n_pages() > 0: self.notebook.remove_page(0) if len(self.document.tabs)>0: self.document.tabs.reverse() node = None unfound = False missing = [] for node_id in self.document.tabs: search = self.document.fetch_node_by_id(node_id) if search: node = search self.find_iter_by_id(node_id) if self.found != None: iter = self.found self.changing_cursor = True self.switching_page = True self.prepend_to_notebook(node, iter) self.changing_cursor = False self.switching_page = False else: print 'Error: NoteBook Tab not found!' print node_id else: # user had less than tab_max tabs open unfound = True print 'Tab node unfound in document (id = %d)' % node_id missing.append(node_id) self.document.tabs.reverse() # remove any unfound nodes (not ideal, but prevents corruption) if unfound: for node_id in missing: self.document.tabs.remove(node_id) #self.verify_notebook() # opens the first tab def open_first_notebook_tab(self): self.document.tabs.reverse() node = None for node_id in self.document.tabs: search = self.document.fetch_node_by_id(node_id) if search: node = search self.document.tabs.reverse() # take the last node and "click it" if node: self.notebook_switch_node(node) # detect and update any changes in the notebook def update_notebook(self): # check for deleted nodes deleted_nodes = [] for node_id in self.book_node_ids: if not self.document.fetch_node_by_id(node_id): deleted_nodes.append(node_id) # remove deleted nodes for node_id in deleted_nodes: index = self.book_node_ids.index(node_id) del self.book_node_ids[index] del self.book_iters[index] del self.book_textviews[index] self.notebook.remove_page(index) if len(self.book_node_ids) > 0: if self.book_page == index: self.notebook_switch_node(self.document.fetch_node_by_id(self.book_node_ids[0])) else: self.editor_iter = None self.editor_node = None self.current_textview = None self.current_buffer = None # check for changed labels for index, node_id in enumerate(self.book_node_ids): node = self.document.fetch_node_by_id(node_id) page = self.notebook.get_nth_page(index) label = self.notebook.get_tab_label(page) if self.file.tab_bullets: tab_title = node.get_spaced_bullet_title(14) else: tab_title = node.get_spaced_title(14) if label.get_text() != tab_title: label.set_text(tab_title) tab_tip = node.get_recursive_title() if label.get_tooltip_text() != tab_tip: label.set_tooltip_text(tab_tip) # if the treeview changes, we should adjust the notebook iter pointers def update_notebook_iters(self): # check for moved iters for index, node_id in enumerate(self.book_node_ids): self.find_iter_by_id(node_id) if self.found != None: self.book_iters[index] = self.found # this could be used to optimize the notebook (not used currently) # in the future, this should be optional def blank_hidden_tabs(self, shown_node): # blank view tabs (for speed) for index, node_id in enumerate(self.book_node_ids): if node_id != shown_node.id: node = self.document.fetch_node_by_id(node_id) # dont blank edit pages, because of undo/redo if node.view: textview = self.book_textviews[index] buffer = textview.get_buffer() self.copying_buffer = True buffer.set_text('') self.copying_buffer = False # copy text into the textview's buffer # needs wrapper due to callbacks being sprung during set_text def set_textbuffer(self, text): if self.current_textview: self.copying_buffer = True self.current_buffer.set_text(text) self.copying_buffer = False # add highlighting etc def fancy_text(self, node): self.set_textbuffer('') iter = self.current_buffer.get_start_iter() self.current_buffer.insert_with_tags_by_name(iter, '\n', "normal") self.fancy_text_node(iter, node, '', node.all, True) # fancy text def fancy_text_node(self, iter, node, title, recurse, first): text = '' if self.document.show_titles_in_view: self.current_buffer.insert_with_tags_by_name(iter, node.get_title(), "bold") if title: title = title + ' -> ' + node.get_title() self.current_buffer.insert_with_tags_by_name(iter, '\n', "normal") self.current_buffer.insert_with_tags_by_name(iter, title, "small") else: title = node.get_title() text = '\n' text += '-' * 2 * len(title) text += '\n' if(node.text): text += node.text text += '\n\n' self.current_buffer.insert_with_tags_by_name(iter, text, "normal") if recurse or first: for child in node.children.list: self.fancy_text_node(iter, child, title, recurse, False) # callback for about def about_callback(self, data=None): authors = [ "David Glen Kerr", "Jeremy Bicha", ] about = gobject.new( gtk.AboutDialog, name='Kabikaboo', version=current_version(), copyright='© 2008-2009 David Kerr and Jeremy Bicha\n' + 'Open source; free for all to use and modify.', comments="Recursive Writing Assistance Software") about.set_program_name('Kabikaboo') about.set_transient_for(self.window) about.set_website('https://launchpad.net/kabikaboo') about.set_logo(None) about.set_wrap_license(True) license='''Kabikaboo 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 2 of the License, or (at your option) \ any later version.\n Kabikaboo 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.\n You should have received a copy of the GNU General Public License along with \ Kabikaboo. If not, see http://www.gnu.org/licenses/.''' about.set_license(license) response = about.run() about.hide() # callback for settings window def settings_callback(self, data=None): new_settings = True if self.settings: if self.settings.window and self.settings.window.get_property('visible'): self.settings.window.deiconify() self.settings.window.show() self.settings.window.set_keep_above(False) new_settings = False if new_settings: self.settings = KabikabooSettingsWindow() self.settings.set_data(self, self.file, self.document) self.settings.populate_settings() self.settings.window.show() # callback from settings menu def update_settings(self): self.apply_settings() # apply most settings to kabikaboo's main window def apply_settings(self): # tool bar - text on buttons if self.file.tool_text: self.toolbar_tree.set_style(gtk.TOOLBAR_BOTH) else: self.toolbar_tree.set_style(gtk.TOOLBAR_ICONS) if self.file.show_toolbar_tree: self.toolbar_tree.show() else: self.toolbar_tree.hide() # text tool bar if self.file.show_toolbar_text: self.toolbar_text.show() else: self.toolbar_text.hide() # status bar if self.file.show_statusbar: self.statusbar.show() else: self.statusbar.hide() # attributes if self.file.show_attributes: self.attributes_area.show() self.update_attributes() else: self.attributes_area.hide() # status self.update_status_bar() # tab arrows self.notebook.set_scrollable(self.file.show_tab_arrows) # homog tabs self.notebook.set_property('homogeneous', self.file.homog_tabs) # show tabs self.notebook.set_show_tabs(self.file.show_tabs) # autosave / autoopen self.autoopen_menu_item.set_active(self.file.autoopen) self.save_on_exit_menu_item.set_active(self.file.save_on_exit) self.autosave_menu_item.set_active(self.file.autosave) # spellcheck self.spellcheck_menu_item.set_active(self.file.spellcheck) # view self.show_attributes_menu_item.set_active(self.file.show_attributes) self.show_statusbar_menu_item.set_active(self.file.show_statusbar) self.show_toolbar_tree_menu_item.set_active(self.file.show_toolbar_tree) # update the settings window, if open def update_settings_window(self): if self.settings: self.settings.populate_settings() ''' HISTORY ''' # history init def create_history_gui(self): self.history_action = gtk.Action('recentmenu', 'Open _Recent', None, None) self.history_menu = self.builder.get_object("recentmenu") # update the file history list def update_history(self): # empty the history submenu def history_foreach(child): self.history_menu.remove(child) self.history_menu.foreach(history_foreach) # add files to history menu for file in self.file.history: menu_item = self.history_action.create_menu_item() try: menu_item.set_property('label', file) except TypeError: #print 'Your GTK library does not support labelling new menu items.' #print 'Please upgrade to version 2.16 or later.' menu_item.set_property('name', file) menu_item.connect('activate', self.history_callback) self.history_menu.append(menu_item) # history menu item click def history_callback(self, item, data=None): if item: file = '' try: file = item.get_property('label') except TypeError: file = item.get_property('name') allow_new = True if self.file.different: allow_new = False dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, 'You have not saved your current document.\nContinue?') response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_YES: allow_new = True if allow_new: self.open_file(file) ''' BOOKMARKS ''' # bookmarks init def create_bookmarks_gui(self): self.bookmarks_action = gtk.Action('Bookmarks', '_Bookmarks', None, None) self.bookmarks_menu = self.builder.get_object("bookmarksmenu") # add bookmark self.add_bookmark_menu_item = self.bookmarks_action.create_menu_item() try: self.add_bookmark_menu_item.set_property('label', '_Add Bookmark') except TypeError: print 'Your GTK library does not support labelling new menu items.' print 'Please upgrade to version 2.16 or later.' self.add_bookmark_menu_item.set_property('name', '_Add Bookmark') self.add_bookmark_menu_item.connect("activate", self.add_bookmark_callback) self.bookmarks_menu.append(self.add_bookmark_menu_item) # remove bookmark self.remove_bookmark_menu_item = self.bookmarks_action.create_menu_item() try: self.remove_bookmark_menu_item.set_property('label', '_Remove Bookmark') except TypeError: self.remove_bookmark_menu_item.set_property('name', '_Remove Bookmark') self.remove_bookmark_menu_item.connect("activate", self.remove_bookmark_callback) self.bookmarks_menu.append(self.remove_bookmark_menu_item) # update all bookmarks def update_bookmarks(self): # empty the bookmarks submenu def bookmarks_remove(child): label = child.get_property('label') if label != '_Add Bookmark' and label != '_Remove Bookmark': self.bookmarks_menu.remove(child) self.bookmarks_menu.foreach(bookmarks_remove) # add a separator seperator = gtk.SeparatorMenuItem() self.bookmarks_menu.append(seperator) seperator.set_property('visible', True) # now build the list count = 0 for node_id in self.document.bookmarks: node = self.document.fetch_node_by_id(node_id) if node: count += 1 menu_item = self.bookmarks_action.create_menu_item() try: menu_item.set_property('label', '(_%d) %s' % (count, node.get_title_with_parent())) menu_item.set_property('name', node.id) except TypeError: menu_item.set_property('name', node.id) menu_item.connect('activate', self.bookmark_selection_callback) self.bookmarks_menu.append(menu_item) # add bookmark callback def add_bookmark_callback(self, data=None): if self.editor_node: self.add_bookmark(self.editor_node) self.update_bookmarks() self.update_tool_buttons() # remove bookmark callback def remove_bookmark_callback(self, data=None): if self.editor_node: self.remove_bookmark(self.editor_node) self.update_bookmarks() self.update_tool_buttons() # add bookmark def add_bookmark(self, node): if node: self.document.add_bookmark(node) # remove bookmark def remove_bookmark(self, node): if node: self.document.remove_bookmark(node) # user selects a bookmark from list def bookmark_selection_callback(self, item, data=None): if item: bookmark_id = int(item.get_property('name')) if bookmark_id >= 0: node = self.document.fetch_node_by_id(bookmark_id) if node: if not self.notebook_switch_node(node): self.find_iter_by_id(node.id) if self.found: self.add_to_notebook(node, self.found) self.node_visit(node) else: self.node_visit(node) # is the current tab a bookmark def is_current_tab_a_bookmark(self): result = False if self.editor_node: result = self.document.is_node_bookmarked(self.editor_node) return result # click and edit the node title (F2) def edit_node_title(self): if self.editor_iter: path = self.treeview.get_model().get_path(self.editor_iter) self.treeview.expand_to_path(path) self.treeview.scroll_to_cell(path) #self.treeview.set_cursor(path, self.treeview.get_column(0), True) self.treeview.grab_focus() #e = gtk.gdk.Event(gtk.gdk.KEY_PRESS) #e.state = 0 #e.keyval = 65293 #e.hardware_keycode = 65293 #e.window = self.window.get_property('frame') #e.time = 0 #self.window.emit("key-press-event", e) # textview popup def textview_populate_popup_callback(self, textview, menu, userdata=None): if self.editor_node: if not self.editor_node.view and self.current_buffer.get_has_selection(): separator = gtk.SeparatorMenuItem() menu.append(separator) menu_item = gtk.ImageMenuItem(gtk.STOCK_ADD) menu_item.set_property('label', 'Move Selected Text to New Child') menu_item.connect("activate", self.nodify_selected_text_callback) menu.append(menu_item) menu.show_all() # takes a chunk of text and pushes it into a new child node def nodify_selected_text_callback(self, widget, userdata=None): if not self.editor_node.view and self.current_buffer.get_has_selection(): selection = self.current_buffer.get_selection_bounds() start = selection[0] end = selection[1] text = self.current_buffer.get_text(start, end) new = self.document.add_node_guess_title(self.editor_node, text) new_iter = self.add_document_node_to_tree(new, self.editor_iter) self.current_buffer.delete(start, end) if not self.row_expanded(self.editor_iter): self.expand_row(self.editor_iter) self.bump() self.update_tool_buttons() self.update_textview() self.update_attributes() self.update_tree_node_titles(self.editor_node) # treeview area popup def create_treeview_popup_menu(self): if self.editor_node: popup_menu = gtk.Menu() # common node functions menu_item = gtk.ImageMenuItem(gtk.STOCK_CONVERT) menu_item.set_property('label', 'Add _Child') menu_item.connect("activate", self.new_child_node_callback) popup_menu.append(menu_item) # document-node functions if self.editor_node != self.document: menu_item = gtk.ImageMenuItem(gtk.STOCK_ADD) menu_item.connect("activate", self.new_node_before_callback) menu_item.set_property('label', 'Add _Before') popup_menu.append(menu_item) menu_item = gtk.ImageMenuItem(gtk.STOCK_ADD) menu_item.set_property('label', 'Add _After') menu_item.connect("activate", self.new_node_after_callback) popup_menu.append(menu_item) if not self.document.is_a_first_node(self.editor_node): menu_item = gtk.ImageMenuItem(gtk.STOCK_GO_UP) menu_item.set_property('label', 'Move _Up') menu_item.connect("activate", self.move_node_up_callback) popup_menu.append(menu_item) if not self.document.is_a_last_node(self.editor_node): menu_item = gtk.ImageMenuItem(gtk.STOCK_GO_DOWN) menu_item.set_property('label', 'Move _Down') menu_item.connect("activate", self.move_node_down_callback) popup_menu.append(menu_item) if self.document.can_move_left(self.editor_node): menu_item = gtk.ImageMenuItem(gtk.STOCK_GO_BACK) menu_item.set_property('label', 'Move _Left') menu_item.connect("activate", self.move_node_left_callback) popup_menu.append(menu_item) if self.document.can_move_right(self.editor_node): menu_item = gtk.ImageMenuItem(gtk.STOCK_GO_FORWARD) menu_item.set_property('label', 'Move _Right') menu_item.connect("activate", self.move_node_right_callback) popup_menu.append(menu_item) if self.document.is_node_bookmarked(self.editor_node): menu_item = gtk.ImageMenuItem(gtk.STOCK_REMOVE) menu_item.set_property('label', 'Remove _Bookmark') menu_item.connect("activate", self.remove_bookmark_callback) popup_menu.append(menu_item) else: menu_item = gtk.ImageMenuItem(gtk.STOCK_ADD) menu_item.set_property('label', 'Add _Bookmark') menu_item.connect("activate", self.add_bookmark_callback) popup_menu.append(menu_item) if self.editor_node.has_children(): menu_item = gtk.ImageMenuItem(gtk.STOCK_FULLSCREEN) menu_item.set_property('label', 'E_xpand All') menu_item.connect("activate", self.expand_all_children_callback) popup_menu.append(menu_item) if self.editor_node != self.document: menu_item = gtk.ImageMenuItem(gtk.STOCK_DELETE) menu_item.set_property('label', 'Remove') menu_item.connect("activate", self.remove_node_callback) popup_menu.append(menu_item) if self.editor_node.has_children(): menu_item = gtk.ImageMenuItem(gtk.STOCK_UNDELETE) menu_item.set_property('label', 'Remove Children') menu_item.connect("activate", self.remove_children_callback) popup_menu.append(menu_item) return popup_menu else: return False # treeview mouse button press def treeview_button_press_callback(self, widget, event): # right click if event.button == 3: x = int(event.x) y = int(event.y) time = event.time path_info = self.treeview.get_path_at_pos(x, y) if path_info is not None: path, col, cellx, celly = path_info self.treeview.grab_focus() self.treeview.set_cursor(path, col, 0) if self.editor_node: menu = self.create_treeview_popup_menu() menu.show_all() menu.popup( None, None, None, event.button, time) return True #checkbutton_statusbar def show_statusbar_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkmenuitem_statusbar") self.file.show_statusbar = menu_item.get_active() self.file.save_settings_default() self.update_settings() #checkmenuitem_toolbar_tree def show_toolbar_tree_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkmenuitem_toolbar_tree") self.file.show_toolbar_tree = menu_item.get_active() self.file.save_settings_default() self.update_settings() #checkmenuitem_toolbar_text def show_toolbar_text_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkmenuitem_toolbar_text") self.file.show_toolbar_text = menu_item.get_active() self.file.save_settings_default() self.update_settings() #checkmenuitem_spellcheck def checkbutton_spellcheck_callback(self, toggled, data=None): if not editor.building_gui: menu_item = self.builder.get_object("checkmenuitem_spellcheck") self.file.spellcheck = menu_item.get_active() self.notify_spellcheck_change() #checkmenuitem_show_attributes def show_attributes_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkmenuitem_show_attributes") self.file.show_attributes = menu_item.get_active() self.file.save_settings_default() self.update_settings() # callback for statistics window def statistics_callback(self, data=None): new_statistics = True if self.statistics: if self.statistics.window and self.statistics.window.get_property('visible'): self.statistics.window.deiconify() self.statistics.window.show() self.statistics.window.set_keep_above(True) self.statistics.window.set_keep_above(False) new_statistics = False if new_statistics: self.statistics = KabikabooStatisticsWindow() self.statistics.set_data(self, self.file, self.document) self.statistics.window.show() # callback for import def import_text_callback(self, data=None): chooser = gtk.FileChooserDialog(title="Import Text File", parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) chooser.set_default_response(gtk.RESPONSE_OK) filter = gtk.FileFilter() filter.set_name(".txt documents") filter.add_pattern("*.txt") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("Kaboo documents") filter.add_pattern("*.kaboo") chooser.add_filter(filter) filter = gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) if self.file.last_import_directory != '': chooser.set_current_folder(self.file.last_import_directory) else: home = os.path.expanduser('~') chooser.set_filename(os.path.join(home, 'Documents')) response = chooser.run() if response == gtk.RESPONSE_OK: filename = chooser.get_filename() self.import_text_file(filename, chooser) chooser.destroy() elif response == gtk.RESPONSE_CANCEL: chooser.destroy() # file handler def import_text_file(self, filename, dialog=None): result = self.file.import_text_file(filename, self.editor_node) if result: self.update_textview() else: dialog2 = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, 'Error importing file.') response = dialog2.run() dialog2.destroy() return result # split node every one line break def split_node_one_callback(self, widget, userdata=None): if self.editor_node and self.editor_node.is_splittable(): text = self.editor_node.text split = text.splitlines() for line in split: if line != '': new = self.document.add_node_guess_title(self.editor_node, line) new_iter = self.add_document_node_to_tree(new, self.editor_iter) if not self.row_expanded(self.editor_iter): self.expand_row(self.editor_iter) self.editor_node.set_text('') self.editor_node.view = True self.bump() self.update_tool_buttons() self.update_textview() self.update_tree_node_titles(self.editor_node) self.update_attributes() # split node every two line breaks def split_node_two_callback(self, widget, userdata=None): if self.editor_node and self.editor_node.is_splittable(): text = self.editor_node.text split = text.split("\n\n") for line in split: if line != '': new = self.document.add_node_guess_title(self.editor_node, line) new_iter = self.add_document_node_to_tree(new, self.editor_iter) if not self.row_expanded(self.editor_iter): self.expand_row(self.editor_iter) self.editor_node.set_text('') self.editor_node.view = True self.bump() self.update_tool_buttons() self.update_textview() self.update_tree_node_titles(self.editor_node) self.update_attributes() # copy children text with titles into parent def unify_with_callback(self, widget, userdata=None): if self.editor_node and self.editor_node.is_unifiable(): self.editor_node.view = False self.editor_node.absorb_children_text(True) self.remove_children() self.bump() self.update_tool_buttons() self.update_textview() self.update_attributes() # copy children text without titles into parent def unify_without_callback(self, widget, userdata=None): if self.editor_node and self.editor_node.is_unifiable(): self.editor_node.view = False self.editor_node.absorb_children_text(False) self.remove_children() self.bump() self.update_tool_buttons() self.update_textview() self.update_attributes() # fullscreen def fullscreen_switch(self, widget=None): if not self.file.fullscreen: self.window.fullscreen() self.file.fullscreen = True else: self.window.unfullscreen() self.file.fullscreen = False ''' TEXT EDITING (FONTS, TAGS) ''' # text toolbar init def create_toolbar_text(self): self.toolbar_text = self.builder.get_object("toolbar_text") self.toolbar_text.set_style(gtk.TOOLBAR_ICONS) # bold self.bold_toolbutton = gtk.ToolButton(gtk.STOCK_BOLD) self.bold_toolbutton.set_label('Bold') self.bold_toolbutton.set_tooltip_text('Bold (Ctrl+B)') self.bold_toolbutton.connect('clicked', self.bold_callback) self.bold_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.bold_toolbutton, -1) # italic self.italic_toolbutton = gtk.ToolButton(gtk.STOCK_ITALIC) self.italic_toolbutton.set_label('Italic') self.italic_toolbutton.set_tooltip_text('Italic (Ctrl+I)') self.italic_toolbutton.connect('clicked', self.italic_callback) self.italic_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.italic_toolbutton, -1) # underline self.underline_toolbutton = gtk.ToolButton(gtk.STOCK_UNDERLINE) self.underline_toolbutton.set_label('Underline') self.underline_toolbutton.set_tooltip_text('Underline (Ctrl+U)') self.underline_toolbutton.connect('clicked', self.underline_callback) self.underline_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.underline_toolbutton, -1) # font self.font_toolbutton = gtk.ToolButton(gtk.STOCK_SELECT_FONT) self.font_toolbutton.set_label('Font') self.font_toolbutton.set_tooltip_text('Font') #self.font_toolbutton.connect('clicked', self.font_callback) self.font_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.font_toolbutton, -1) # increase size self.increase_toolbutton = gtk.ToolButton(gtk.STOCK_ADD) self.increase_toolbutton.set_label('Grow') self.increase_toolbutton.set_tooltip_text('Increase Text Size') #self.increase_toolbutton.connect('clicked', self.increase_callback) self.increase_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.increase_toolbutton, -1) # decrease size self.shrink_toolbutton = gtk.ToolButton(gtk.STOCK_REMOVE) self.shrink_toolbutton.set_label('Shrink') self.shrink_toolbutton.set_tooltip_text('Decrease Text Size') #self.shrink_toolbutton.connect('clicked', self.shrink_callback) self.shrink_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.shrink_toolbutton, -1) # fill justify self.fill_justify_toolbutton = gtk.ToolButton(gtk.STOCK_JUSTIFY_FILL) self.fill_justify_toolbutton.set_label('Fill Justify') self.fill_justify_toolbutton.set_tooltip_text('Fill Justify') #self.fill_justify_toolbutton.connect('clicked', self.fill_justify_callback) self.fill_justify_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.fill_justify_toolbutton, -1) # left justify self.left_justify_toolbutton = gtk.ToolButton(gtk.STOCK_JUSTIFY_LEFT) self.left_justify_toolbutton.set_label('Left Justify') self.left_justify_toolbutton.set_tooltip_text('Left Justify') #self.left_justify_toolbutton.connect('clicked', self.left_justify_callback) self.left_justify_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.left_justify_toolbutton, -1) # center justify self.center_justify_toolbutton = gtk.ToolButton(gtk.STOCK_JUSTIFY_CENTER) self.center_justify_toolbutton.set_label('Center Justify') self.center_justify_toolbutton.set_tooltip_text('Center Justify') #self.center_justify_toolbutton.connect('clicked', self.center_justify_callback) self.center_justify_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.center_justify_toolbutton, -1) # right justify self.right_justify_toolbutton = gtk.ToolButton(gtk.STOCK_JUSTIFY_RIGHT) self.right_justify_toolbutton.set_label('Right Justify') self.right_justify_toolbutton.set_tooltip_text('Right Justify') #self.right_justify_toolbutton.connect('clicked', self.right_justify_callback) self.right_justify_toolbutton.set_property('visible', True) self.toolbar_text.insert(self.right_justify_toolbutton, -1) # apply/remove tag def flip_tag_on_selection(self, tag_name): # find the selection range (start, end) = self.current_buffer.get_selection_bounds() # is it a valid range? if start >= 0 and end > 0 and start != end: # we need to get the tag from the buffer since we have multiple buffers tag = None has_start_tag = False has_end_tag = False # search for existing tag at start for search_tag in start.get_tags(): if search_tag.get_property('name') == tag_name: tag = search_tag has_start_tag = True # search for existing tag at end for search_tag in end.get_tags(): if search_tag.get_property('name') == tag_name: tag = search_tag has_end_tag = True # did we find a tag? if has_start_tag or has_end_tag: if (start.has_tag(tag) and end.has_tag(tag)) or (start.has_tag(tag) and end.ends_tag(tag)): self.current_buffer.remove_tag(tag, start, end) else: self.current_buffer.apply_tag_by_name(tag_name, start, end) else: self.current_buffer.apply_tag_by_name(tag_name, start, end) # bold def bold_callback(self, widget): if self.editor_node and not self.editor_node.view: self.flip_tag_on_selection('bold') # italic def italic_callback(self, widget): if self.editor_node and not self.editor_node.view: self.flip_tag_on_selection('italic') # unerline def underline_callback(self, widget): if self.editor_node and not self.editor_node.view: self.flip_tag_on_selection('underline') # check for duplicate instance of kabikaboo def check_for_duplicate_instance(self): result = False # WARNING: LINUX ONLY CODE output = commands.getoutput('ps -ax') # check for 2 or more regular instances if output.count('python src/kabikaboo.py') >= 2: result = True # check for instance and development instance if output.count('python src/kabikaboo.py') >= 1 and output.count('python -W ignore::DeprecationWarning src/kabikaboo.py') >= 1: result = True if result: print 'Duplicate instance of Kabikaboo already running.' print 'Will not open last file in this case.' print 'Starting with new document.' self.file.working_file = '' return result ''' SPELLCHECK ''' # turn spellcheck on off def notify_spellcheck_change(self): # check all textviews for textview in self.book_textviews: # spellcheck is on if self.file.spellcheck: #check for existing spellcheck try: spell = gtkspell.get_from_text_view(textview) except SystemError: # none found, add one gtkspell.Spell(textview) # spellcheck is off else: # check for existing spellcheck try: spell = gtkspell.get_from_text_view(textview) # one found, remove it spell.detach() except SystemError: pass def spellcheck_menu_item_toggle(self, widget=None): if not editor.building_gui: self.file.spellcheck = self.spellcheck_menu_item.get_active() self.file.save_settings_default() self.update_settings_window() self.notify_spellcheck_change() ''' RECENTLY VISITED ''' # visited init def create_visited_gui(self): # visited self.visited_action = gtk.Action('Visits', '_Visits', None, None) self.visited_menu = self.builder.get_object("visitsmenu") # recently visited self.visited_recent_menuitem = self.visited_action.create_menu_item() try: self.visited_recent_menuitem.set_property('label', '_Recent') except TypeError: self.visited_recent_menuitem.set_property('name', '_Recent') self.visited_menu.append(self.visited_recent_menuitem) self.visited_recent_menu = gtk.Menu() self.visited_recent_menuitem.set_submenu(self.visited_recent_menu) self.visited_recent_menuitem.connect('activate', self.menuitem_visited_recent_callback) # session visited self.visited_session_menuitem = self.visited_action.create_menu_item() try: self.visited_session_menuitem.set_property('label', '_Session') except TypeError: self.visited_session_menuitem.set_property('name', '_Session') self.visited_menu.append(self.visited_session_menuitem) self.visited_session_menu = gtk.Menu() self.visited_session_menuitem.set_submenu(self.visited_session_menu) self.visited_session_menuitem.connect('activate', self.menuitem_visited_session_callback) # all visited self.visited_all_menuitem = self.visited_action.create_menu_item() try: self.visited_all_menuitem.set_property('label', '_All') except TypeError: self.visited_all_menuitem.set_property('name', '_All') self.visited_menu.append(self.visited_all_menuitem) self.visited_all_menu = gtk.Menu() self.visited_all_menuitem.set_submenu(self.visited_all_menu) self.visited_all_menuitem.connect('activate', self.menuitem_visited_all_callback) # user selects a visited from list def visited_selection_callback(self, item, data=None): if item: visited_id = int(item.get_property('name')) if visited_id >= 0: node = self.document.fetch_node_by_id(visited_id) if node: if not self.notebook_switch_node(node): self.find_iter_by_id(node.id) if self.found: self.add_to_notebook(node, self.found) self.node_visit(node) else: self.node_visit(node) # node visit def node_visit(self, node): if node: self.document.visit(node) # when user hovers over "Visited->Recent" def menuitem_visited_recent_callback(self, menuitem): self.update_recently_visited() # when user hovers over "Visited->Session" def menuitem_visited_session_callback(self, menuitem): self.update_session_visited() # when user hovers over "Visited->All" def menuitem_visited_all_callback(self, menuitem): self.update_all_visited() # update recently visited menu def update_recently_visited(self): # empty the visited submenus def visited_remove(child): self.visited_recent_menu.remove(child) self.visited_recent_menu.foreach(visited_remove) # now build the list count = 0 for node_id in self.document.visited: node = self.document.fetch_node_by_id(node_id) if node: count += 1 menu_item = gtk.MenuItem() try: menu_item.set_property('label', '%s' % (node.get_title_with_parent())) menu_item.set_property('name', node.id) except TypeError: menu_item.set_property('name', node.id) menu_item.connect('activate', self.visited_selection_callback) self.visited_recent_menu.append(menu_item) self.visited_recent_menu.show_all() # update session visited menu def update_session_visited(self): # empty the visited submenus def visited_remove(child): self.visited_session_menu.remove(child) self.visited_session_menu.foreach(visited_remove) # now build the list count = 0 visited = self.document.get_visited_sessions_list() for node in visited: count += 1 menu_item = gtk.MenuItem() try: menu_item.set_property('label', '%s (%d)' % (node.get_title_with_parent(), node.visits_session)) menu_item.set_property('name', node.id) except TypeError: menu_item.set_property('name', node.id) menu_item.connect('activate', self.visited_selection_callback) self.visited_session_menu.append(menu_item) self.visited_session_menu.show_all() # update all visited menu def update_all_visited(self): # empty the visited submenus def visited_remove(child): self.visited_all_menu.remove(child) self.visited_all_menu.foreach(visited_remove) # now build the list count = 0 visited = self.document.get_visited_all_list() for node in visited: count += 1 menu_item = gtk.MenuItem() try: menu_item.set_property('label', '%s (%d)' % (node.get_title_with_parent(), node.visits_all)) menu_item.set_property('name', node.id) except TypeError: menu_item.set_property('name', node.id) menu_item.connect('activate', self.visited_selection_callback) self.visited_all_menu.append(menu_item) self.visited_all_menu.show_all() # autosave def autosave(self): if self.file.autosave: if self.file.working_file != '' and self.file.different: if self.file.autosave_version: self.save_version() else: self.file.save(self.document) self.unbump() self.update_history() self.update_status_bar() return True else: return False # check and apply autosave settings def check_autosave(self): # remove existing autosave timers if self.autosave_id > -1: glib.source_remove(self.autosave_id) self.autosave_id = -1 # add autosave timer if self.file.autosave: # enable autosave self.autosave_id = glib.timeout_add_seconds(60*int(self.file.autosave_interval), self.autosave) # the next timeout call will stop when autosave() returns false # # ''' MAIN PROGRAM LOOP ''' # if __name__ == "__main__": # Init Step 1: create our first window editor = KabikabooMainWindow() # building gui editor.building_gui = True # Init Step 2: initialize the GUI with values editor.initialize_interface() # Init Step 3: populate the GUI with data editor.populate_interface() # Init Step 4: show the window now editor.window_show() # Init Step 5: do some tricks after the window is shown editor.post_window_show() # done building gui editor.building_gui = False # run gtk loop until close gtk.main() # after loop exits, save settings editor.file.save_settings_default() # save recovery file editor.file.save_recovery(True) kabikaboo-1.7/src/settings.py000644 001750 001750 00000055260 11313565762 014525 0ustar00000000 000000 # Kabikaboo settings file # # Novel Writing Assistance Software # Copyleft (c) 2009 # Created by David Glen Kerr # Naturally Intelligent Inc. # # Free and Open Source # Licensed under GPL v2 or later # No Warranty from file import KabikabooFile from kabikaboo import KabikabooMainWindow import os import sys import gtk import pango import pygtk class KabikabooSettingsWindow: # init main window def __init__(self): # create gtk builder self.populating = True self.builder = gtk.Builder() # create gtk builder self.builder = gtk.Builder() # load interface if os.path.isfile(os.path.join("ui", "settings.glade")): self.builder.add_from_file(os.path.join("ui", "settings.glade")) elif os.path.isfile(os.path.join("..", "ui", "settings.glade")): self.builder.add_from_file(os.path.join("..", "ui", "settings.glade")) elif os.path.isfile("/usr/share/kabikaboo/ui/settings.glade"): self.builder.add_from_file("/usr/share/kabikaboo/ui/settings.glade") # find main window self.window = self.builder.get_object("window_settings") # pointers, must be set self.file = None self.kabikaboo = None self.document = None # application icon if os.path.isfile("kabikaboo.png"): gtk.window_set_default_icon_from_file("kabikaboo.png") elif os.path.isfile(os.path.join("..", "kabikaboo.png")): gtk.window_set_default_icon_from_file(os.path.join("..", "kabikaboo.png")) elif os.path.isfile("/usr/src/kabikaboo/kabikaboo.png"): gtk.window.set_default_icon_from_file("/usr/src/kabikaboo/kabikaboo.png") # connect gui callbacks self.connect_gui() self.populating = False def set_data(self, kabikaboo, file, document): self.file = file self.kabikaboo = kabikaboo self.document = document self.set_window_title() def set_window_title(self): if self.file.show_application_name and self.file.application_name != '': self.window.set_title(self.file.application_name + ' Preferences') else: self.window.set_title(self.document.title + ' Preferences') def connect_gui(self): #close button close_button = self.builder.get_object("button_close") if close_button: close_button.connect("clicked", self.closed_button_callback) #checkbutton_openlastfile menu_item = self.builder.get_object("checkbutton_openlastfile") menu_item.connect("toggled", self.checkbutton_openlastfile_callback) #checkbutton_saveonexit menu_item = self.builder.get_object("checkbutton_saveonexit") menu_item.connect("toggled", self.checkbutton_saveonexit_callback) #checkbutton_autosave menu_item = self.builder.get_object("checkbutton_autosave") menu_item.connect("toggled", self.checkbutton_autosave_callback) #checkbutton_autosave_version menu_item = self.builder.get_object("checkbutton_autosave_version") menu_item.connect("toggled", self.checkbutton_autosave_version_callback) #checkbutton_tooltext menu_item = self.builder.get_object("checkbutton_tooltext") menu_item.connect("toggled", self.checkbutton_tooltext_callback) #checkbutton_show_tabs menu_item = self.builder.get_object("checkbutton_show_tabs") menu_item.connect("toggled", self.checkbutton_show_tabs_callback) #checkbutton_node_path menu_item = self.builder.get_object("checkbutton_node_path") menu_item.connect("toggled", self.checkbutton_node_path_callback) #checkbutton_node_path_status menu_item = self.builder.get_object("checkbutton_node_path_status") menu_item.connect("toggled", self.checkbutton_node_path_status_callback) #checkbutton_homog_tabs menu_item = self.builder.get_object("checkbutton_homog_tabs") menu_item.connect("toggled", self.checkbutton_homog_tabs_callback) #checkbutton_tab_arrows menu_item = self.builder.get_object("checkbutton_tab_arrows") menu_item.connect("toggled", self.checkbutton_tab_arrows_callback) #checkbutton_show_bullets menu_item = self.builder.get_object("checkbutton_show_bullets") menu_item.connect("toggled", self.checkbutton_show_bullets_callback) #checkbutton_attributes menu_item = self.builder.get_object("checkbutton_attributes") menu_item.connect("toggled", self.checkbutton_attributes_callback) #checkbutton_file_status menu_item = self.builder.get_object("checkbutton_file_status") menu_item.connect("toggled", self.checkbutton_file_status_callback) #checkbutton_move_on_new menu_item = self.builder.get_object("checkbutton_move_on_new") menu_item.connect("toggled", self.checkbutton_move_on_new_callback) #checkbutton_sample_data menu_item = self.builder.get_object("checkbutton_sample_data") menu_item.connect("toggled", self.checkbutton_sample_data_callback) #checkbutton_tab_bullets menu_item = self.builder.get_object("checkbutton_tab_bullets") menu_item.connect("toggled", self.checkbutton_tab_bullets_callback) #checkbutton_show_titles_in_view menu_item = self.builder.get_object("checkbutton_show_titles_in_view") menu_item.connect("toggled", self.checkbutton_show_titles_in_view_callback) #checkbutton_show_titles_in_export menu_item = self.builder.get_object("checkbutton_show_titles_in_export") menu_item.connect("toggled", self.checkbutton_show_titles_in_export_callback) #checkbutton_remember_position menu_item = self.builder.get_object("checkbutton_remember_position") menu_item.connect("toggled", self.checkbutton_remember_position_callback) #calculate_statistics menu_item = self.builder.get_object("checkbutton_calculate_statistics") menu_item.connect("toggled", self.checkbutton_calculate_statistics_callback) #show_application_name menu_item = self.builder.get_object("checkbutton_show_application_name") menu_item.connect("toggled", self.checkbutton_show_application_name_callback) #show_directory menu_item = self.builder.get_object("checkbutton_show_directory") menu_item.connect("toggled", self.checkbutton_show_directory_callback) #tree_toolbar_intree menu_item = self.builder.get_object("checkbutton_tree_toolbar_intree") menu_item.connect("toggled", self.checkbutton_tree_toolbar_intree_callback) #entry_max_tabs menu_item = self.builder.get_object("spinbutton_max_tabs") menu_item.connect("value-changed", self.entry_max_tabs_callback) #entry_max_history menu_item = self.builder.get_object("spinbutton_max_history") menu_item.connect("value-changed", self.entry_max_history_callback) #entry_application_name menu_item = self.builder.get_object("entry_application_name") menu_item.connect("activate", self.entry_application_name_callback) menu_item.connect("changed", self.entry_application_name_callback) #entry_max_bookmarks menu_item = self.builder.get_object("spinbutton_max_bookmarks") menu_item.connect("value-changed", self.entry_max_bookmarks_callback) #entry_autosave_interval menu_item = self.builder.get_object("spinbutton_autosave_interval") menu_item.connect("value-changed", self.entry_autosave_interval_callback) #entry_max_visits menu_item = self.builder.get_object("spinbutton_max_visits") menu_item.connect("value-changed", self.entry_max_visits_callback) def populate_settings(self): self.populating = True #checkbutton_openlastfile self.builder.get_object("checkbutton_openlastfile").set_active(self.file.autoopen) #checkbutton_saveonexit self.builder.get_object("checkbutton_saveonexit").set_active(self.file.save_on_exit) #checkbutton_autosave self.builder.get_object("checkbutton_autosave").set_active(self.file.autosave) #checkbutton_autosave_version self.builder.get_object("checkbutton_autosave_version").set_active(self.file.autosave_version) #checkbutton_tooltext self.builder.get_object("checkbutton_tooltext").set_active(self.file.tool_text) #checkbutton_show_tabs self.builder.get_object("checkbutton_show_tabs").set_active(self.file.show_tabs) #checkbutton_node_path self.builder.get_object("checkbutton_node_path").set_active(self.file.show_node_path) #checkbutton_node_path_status self.builder.get_object("checkbutton_node_path_status").set_active(self.file.show_node_path_status) #checkbutton_homog_tabs self.builder.get_object("checkbutton_homog_tabs").set_active(self.file.homog_tabs) #checkbutton_tab_arrows self.builder.get_object("checkbutton_tab_arrows").set_active(self.file.show_tab_arrows) #checkbutton_show_bullets self.builder.get_object("checkbutton_show_bullets").set_active(self.file.show_bullets) #checkbutton_attributes self.builder.get_object("checkbutton_attributes").set_active(self.file.show_attributes) #checkbutton_file_status self.builder.get_object("checkbutton_file_status").set_active(self.file.show_file_status) #checkbutton_move_on_new self.builder.get_object("checkbutton_move_on_new").set_active(self.file.move_on_new) #checkbutton_sample_data self.builder.get_object("checkbutton_sample_data").set_active(self.file.sample_data) #checkbutton_tab_bullets self.builder.get_object("checkbutton_tab_bullets").set_active(self.file.tab_bullets) #checkbutton_show_titles_in_view self.builder.get_object("checkbutton_show_titles_in_view").set_active(self.document.show_titles_in_view) #checkbutton_show_titles_in_export self.builder.get_object("checkbutton_show_titles_in_export").set_active(self.document.show_titles_in_export) #checkbutton_remember_position self.builder.get_object("checkbutton_remember_position").set_active(self.file.remember_position) #calculate_statistics self.builder.get_object("checkbutton_calculate_statistics").set_active(self.file.calculate_statistics) #show_application_name self.builder.get_object("checkbutton_show_application_name").set_active(self.file.show_application_name) #show_directory self.builder.get_object("checkbutton_show_directory").set_active(self.file.show_directory_status) #tree_toolbar_intree self.builder.get_object("checkbutton_tree_toolbar_intree").set_active(self.file.tree_toolbar_intree) #entry_max_tabs self.builder.get_object("spinbutton_max_tabs").set_value(self.document.tab_max) #entry_max_history self.builder.get_object("spinbutton_max_history").set_value(self.file.max_history) #entry_max_bookmarks self.builder.get_object("spinbutton_max_bookmarks").set_value(self.document.bookmark_max) #entry_max_visits self.builder.get_object("spinbutton_max_visits").set_value(self.document.visited_max) #entry_autosave_interval self.builder.get_object("spinbutton_autosave_interval").set_value(self.file.autosave_interval) #application_name self.builder.get_object("entry_application_name").set_text(self.file.application_name) self.populating = False # close def closed_button_callback(self, data=None): self.window.hide() #checkbutton_openlastfile def checkbutton_openlastfile_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_openlastfile") self.file.autoopen = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_saveonexit def checkbutton_saveonexit_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_saveonexit") self.file.save_on_exit = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_autosave def checkbutton_autosave_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_autosave") self.file.autosave = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_autosave_version def checkbutton_autosave_version_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_autosave_version") self.file.autosave_version = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_tooltext def checkbutton_tooltext_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_tooltext") self.file.tool_text = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_show_tabs def checkbutton_show_tabs_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_show_tabs") self.file.show_tabs = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_node_path def checkbutton_node_path_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_node_path") self.file.show_node_path = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() self.kabikaboo.update_node_path() #checkbutton_node_path_status def checkbutton_node_path_status_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_node_path_status") self.file.show_node_path_status = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() self.kabikaboo.update_node_path() #checkbutton_homog_tabs def checkbutton_homog_tabs_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_homog_tabs") self.file.homog_tabs = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_tab_arrows def checkbutton_tab_arrows_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_tab_arrows") self.file.show_tab_arrows = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_show_bullets def checkbutton_show_bullets_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_show_bullets") self.file.show_bullets = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_attributes def checkbutton_attributes_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_attributes") self.file.show_attributes = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_settings() #checkbutton_file_status def checkbutton_file_status_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_file_status") self.file.show_file_status = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_status_bar() #checkbutton_move_on_new def checkbutton_move_on_new_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_move_on_new") self.file.move_on_new = menu_item.get_active() self.file.save_settings_default() #checkbutton_sample_data def checkbutton_sample_data_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_sample_data") self.file.sample_data = menu_item.get_active() self.file.save_settings_default() #checkbutton_tab_bullets def checkbutton_tab_bullets_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_tab_bullets") self.file.tab_bullets = menu_item.get_active() self.file.save_settings_default() self.kabikaboo.update_notebook() #checkbutton_show_titles_in_view def checkbutton_show_titles_in_view_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_show_titles_in_view") self.document.show_titles_in_view = menu_item.get_active() self.kabikaboo.update_textview() self.bump() #checkbutton_show_titles_in_export def checkbutton_show_titles_in_export_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_show_titles_in_export") self.document.show_titles_in_export = menu_item.get_active() self.bump() #checkbutton_remember_position def checkbutton_remember_position_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_remember_position") self.file.remember_position = menu_item.get_active() self.file.diff_set = False self.file.save_settings_default() #checkbutton_calculate_statistics def checkbutton_calculate_statistics_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_calculate_statistics") self.file.calculate_statistics = menu_item.get_active() self.file.save_settings_default() #checkbutton_show_application_name def checkbutton_show_application_name_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_show_application_name") self.file.show_application_name = menu_item.get_active() self.kabikaboo.update_window_titles() self.file.save_settings_default() #checkbutton_show_directory def checkbutton_show_directory_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_show_directory") self.file.show_directory_status = menu_item.get_active() self.kabikaboo.update_status_bar() self.file.save_settings_default() #checkbutton_tree_toolbar_intree def checkbutton_tree_toolbar_intree_callback(self, toggled, data=None): menu_item = self.builder.get_object("checkbutton_tree_toolbar_intree") self.file.tree_toolbar_intree = menu_item.get_active() # we would call the next function, treebar_swap # but it seems either GTK has a bug here, or we are missing something # until this is resolved, the user has to exit and restart to see the change #self.kabikaboo.treebar_swap() self.file.save_settings_default() #entry_max_tabs def entry_max_tabs_callback(self, spinbutton, data=None): entry = self.builder.get_object("spinbutton_max_tabs") good_value = True try: tab_max = entry.get_value_as_int() except: tab_max = self.document.tab_max good_value = False if good_value and tab_max != self.document.tab_max: if(tab_max >= 1): self.document.tab_max = tab_max self.document.check_tab_overflow() entry.set_value(self.document.tab_max) self.kabikaboo.check_notebook() self.bump() #entry_max_history def entry_max_history_callback(self, spinbutton, data=None): entry = self.builder.get_object("spinbutton_max_history") good_value = True try: max_history = entry.get_value_as_int() except: max_history = self.file.max_history good_value = False if good_value and max_history != self.file.max_history: if(max_history >= 1): self.file.max_history = max_history entry.set_value(self.file.max_history) self.file.check_history() self.kabikaboo.update_history() self.file.save_settings_default() #entry_max_bookmarks def entry_max_bookmarks_callback(self, spinbutton, data=None): entry = self.builder.get_object("spinbutton_max_bookmarks") good_value = True try: bookmark_max = entry.get_value_as_int() except: bookmark_max = self.document.bookmark_max good_value = False if good_value and bookmark_max != self.document.bookmark_max: if(bookmark_max >= 1): self.document.bookmark_max = bookmark_max self.document.check_bookmark_overflow() entry.set_value(self.document.bookmark_max) self.kabikaboo.update_bookmarks() self.bump() #entry_max_visits def entry_max_visits_callback(self, spinbutton, data=None): entry = self.builder.get_object("spinbutton_max_visits") good_value = True try: visited_max = entry.get_value_as_int() except: visited_max = self.document.visited_max good_value = False if good_value and visited_max != self.document.visited_max: if(visited_max >= 1): self.document.visited_max = visited_max self.document.check_visited_overflow() entry.set_value(self.document.visited_max) self.bump() #entry_autosave_interval def entry_autosave_interval_callback(self, spinbutton, data=None): entry = self.builder.get_object("spinbutton_autosave_interval") good_value = True try: autosave_interval = entry.get_value_as_int() except: autosave_interval = self.file.autosave_interval good_value = False if good_value and autosave_interval != self.file.autosave_interval: if(autosave_interval >= 1): self.file.autosave_interval = autosave_interval entry.set_value(self.file.autosave_interval) self.kabikaboo.check_autosave() #entry_application_name def entry_application_name_callback(self, entrybox, data=None): entry = self.builder.get_object("entry_application_name") application_name = entry.get_text() if self.file.application_name != application_name: self.file.application_name = application_name self.kabikaboo.update_window_titles() self.file.save_settings_default() # mark the document as changed def bump(self): if not self.populating: self.kabikaboo.bump() kabikaboo-1.7/kabikaboo000755 001750 001750 00000000071 11313720040 013340 0ustar00000000 000000 #!/bin/bash python /usr/share/kabikaboo/src/kabikaboo.py kabikaboo-1.7/ui/000755 001750 001750 00000000000 11314161103 012107 5ustar00000000 000000 kabikaboo-1.7/ui/statistics.glade000644 001750 001750 00000023777 11313230200 015311 0ustar00000000 000000 Kabikaboo - Statistics center-on-parent 420 200 True True vertical True vertical 32 True <b><u>Document Statistics</u></b> True False 2 True 200 24 True Starting Word Count: 5 5 200 24 True ? 200 5 200 24 True Current Word Count: 5 30 200 24 True ? 200 30 200 24 True Words this Session: 5 55 200 24 True ? 200 55 200 24 True Time of Session: 5 80 200 24 True ?:??:?? 200 80 200 24 True Words per Minute: 5 105 200 24 True ? 200 105 200 24 True Words in Node: 5 130 200 24 True ? 200 130 3 True Close 80 28 True True True 350 8 0 True 2 False 1 kabikaboo-1.7/ui/settings.glade000644 001750 001750 00000134022 11313565325 014764 0ustar00000000 000000 Kabikaboo Settings center True dialog True vertical True True True vertical True 10 2 5 5 Save File on Exit True True False True 2 3 20 Open Last File on Startup True True False True 1 2 20 Create New Version on Autosave True True False True 4 5 20 True Autosave every True True False True 0 True True adjustment_autosave_interval True 1 True minutes 2 3 4 20 True True adjustment_max_history True 1 2 5 6 True 0 22 Recent File History Limit: 5 6 20 Show File in Status Bar True True False True 8 9 20 Show Directory in Status Bar True True False True 9 10 20 True 0 5 <b>Files</b> True True 0 5 <b>Window Appearance</b> True 7 8 True 6 7 False False 20 0 True General False True vertical True 8 2 5 5 Show Titles in Export True True False True 2 3 20 Show Titles in View True True False True 1 2 20 True 0 22 Notebook Page Limit: 5 6 True 0 22 Bookmarks Limit: 6 7 True 0 22 Remembered Visits Limit: 7 8 True True adjustment_max_bookmarks True 1 2 6 7 True True adjustment_max_visits True 1 2 7 8 True True adjustment_max_tabs True 1 2 5 6 True 0 5 <b>Titles</b> True True 0 5 <b>Limits</b> True 4 5 True 3 4 False 20 1 1 True Document 1 False True vertical True 8 5 5 Confine Tree Toolbar to Tree (Requires Restart) True True False 0.50999999046325684 True 4 5 20 Move to New Node after Add True True False True 7 8 20 Show Bulleting Choices True True False True 3 4 20 Show Captions on Toolbar True True False True 1 2 20 Show Node Attributes True True False True 2 3 20 True 0 5 <b>Appearance</b> True True 0 5 <b>Behaviour</b> True 6 7 True 5 6 False 20 0 2 True Tree Area 2 False True vertical True 6 5 5 Show Bullets in Tabs True True False True 4 5 20 Show Notebook Scroll Arrows True True False True 2 3 20 Show Node Path in Status Bar True True False True 7 8 20 Show Notebook Tabs True True False True 1 2 20 Homogeneous Tab Widths True True False True 3 4 20 Show Node Path above Text Area True True False True 6 7 20 True 0 5 <b>Notebook</b> True True 0 5 <b>Node Paths</b> True 5 6 False 20 0 3 True Text Area 3 False True vertical True 7 2 5 5 True True 1 2 1 2 Application Name: True True False True 1 2 20 Calculate Statistics in the Background True True False True 2 5 6 20 Generate Sample Data on Blank Startup True True False True 2 6 7 20 Remember Window Position True True False True 2 4 5 20 True 0 0.4699999988079071 5 <b>Appearance</b> True True 0 5 <b>Startup</b> True 3 4 True 2 3 False 20 0 4 True Advanced 4 False 0 40 True Close 100 28 True True True 5 5 False 1 99 1 10 99 1 10 25 1 10 10 1 10 60 1 1 kabikaboo-1.7/ui/main.glade000644 001750 001750 00000165651 11313717654 014070 0ustar00000000 000000 800 550 center 800 550 True vertical True True _File True True gtk-new True True True _Open... True True image5 False True Open _Recent True True True gtk-save True True True Save _As... True True image4 False True Save _Copy... True True Save _Version True True True Open Last File on Startup True True Save File on Exit True True _Autosave True True True _Import True True True _Import Text File to Selected Node... True True _Export True True True Export Whole Document to Text File... True True Export Selected Node to Text File... True True Export Selected Node and Children to Text File... True True Export Whole Document to HTML File... True True Export Selected Node to HTML File... True True Export Selected Node and Children to HTML File... True True gtk-quit True True True True _Edit True True gtk-undo True True True gtk-redo True True True True gtk-cut True True True gtk-copy True True True gtk-paste True True True True Pr_eferences True True image7 False True _View True True View Whole Document View Node and Children True _Close Current Page True True gtk-fullscreen True True True True True _Attributes Area True True True _Tree Toolbar True True True _Status Bar True True Show Te_xt Toolbar True True _Node True True True Split Node Every _One Line Break True True Split Node Every _Two Line Breaks True True _Unify Children With Titles True True Unify Children _Without Titles True True _Bookmarks True True True Vi_sits True True True _Tools True True True _Autocheck Spelling True True True _Document Statistics True True _Help True True _Contents True True image6 False True _Get Help Online... True True image2 False _Translate This Application... True True image1 False _Report a Problem... True True image3 False True gtk-about True True True False 0 True True True icons False True Top of Document (Alt+Home) Overview True gtk-home False True True False True True Add Child (Ctrl+C) Add Child True gtk-add False True True Add Before (Ctrl+B) Add Before True gtk-media-previous False True True Add After (Ctrl+A) Add After True gtk-media-next False True True False True True Move Up (Ctrl+Up) Move Up True gtk-go-up False True True Move Down (Ctrl+Down) Move Down True gtk-go-down False True True Move Left (Ctrl+Left) Move Left True gtk-go-back False True True Move Right (Ctrl+Right) Move Right True gtk-go-forward False True True False True True Expand (Right) Expand True gtk-indent False True True Expand Children Expand Children True gtk-sort-descending False True True Collapse (Left) Collapse True gtk-unindent False True True False True True Remove (Delete) Remove True gtk-delete False True True Remove Children (Ctrl+Delete) Remove Children True gtk-undelete False True False False True False False False False False 1 True True True True vertical True vertical True True automatic automatic True True 0 False True 125 85 True True vertical 1 True 20 24 True Node Title True 5 5 1 True Edit 100 24 True True False True True 5 2 View 100 24 True True False True radiobutton_edit 65 2 Grandchildren 125 24 True True False True 130 2 False 2 True - 100 24 True True False True True 5 5 1 100 24 True True False True radiobutton_bulleting_none 40 5 A 100 24 True True False True radiobutton_bulleting_none 80 5 a 100 24 True True False True radiobutton_bulleting_none 120 5 I 100 24 True True False True radiobutton_bulleting_none 160 5 i 100 24 True True False True radiobutton_bulleting_none 200 5 3 True False False False True True vertical True vertical 1 38 True Node Path True center False 0 True True 1 False True True True 2 True 2 True 0.10000000149011612 5 label False 0 True vertical False False 1 False 3 True gtk-open True gtk-save-as True gtk-preferences True gtk-help True lpi-help True lpi-translate True lpi-bug