keepnote-0.7.8/0000755000175000017500000000000011727170645011524 5ustar razrazkeepnote-0.7.8/bin/0000755000175000017500000000000011727170645012274 5ustar razrazkeepnote-0.7.8/bin/keepnote0000755000175000017500000002745611677423600014046 0ustar razraz#!/usr/bin/env python # # KeepNote - note-taking and organization # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import exceptions import sys import os from os.path import basename, dirname, realpath, join, isdir import time import optparse import thread import threading import traceback #============================================================================= # KeepNote import """ Three ways to run KeepNote bin_path = os.path.dirname(sys.argv[0]) (1) directly from source dir pkgdir = bin_path + "../keepnote" basedir = pkgdir sys.path.append(pkgdir) src/bin/keepnote src/keepnote/__init__.py src/keepnote/images src/keepnote/rc (2) from installation location by setup.py pkgdir = keepnote.get_basedir() basedir = pkgdir prefix/bin/keepnote prefix/lib/python-XXX/site-packages/keepnote/__init__.py prefix/lib/python-XXX/site-packages/keepnote/images prefix/lib/python-XXX/site-packages/keepnote/rc (3) windows py2exe dir pkgdir = bin_path basedir = bin_path dir/keepnote.exe dir/library.zip dir/images dir/rc """ # try to infer keepnote lib path from program path pkgdir = dirname(dirname(realpath(sys.argv[0]))) if os.path.exists(join(pkgdir, "keepnote", "__init__.py")): sys.path.insert(0, pkgdir) import keepnote # if this works we know we are running from src_path (1) basedir = keepnote.get_basedir() else: # try to import from python path import keepnote # sucessful import, therefore we are running with (2) or (3) # attempt to use basedir for (2) basedir = keepnote.get_basedir() if not isdir(join(basedir, "images")): # we must be running (3) basedir = dirname(realpath(sys.argv[0])) keepnote.set_basedir(basedir) #============================================================================= # keepnote imports import keepnote from keepnote.commands import get_command_executor, CommandExecutor from keepnote.teefile import TeeFileStream import keepnote.compat.pref _ = keepnote.translate #============================================================================= # command-line options o = optparse.OptionParser(usage= "%prog [options] [NOTEBOOK]") o.set_defaults(default_notebook=True) o.add_option("-c", "--cmd", dest="cmd", action="store_true", help="treat remaining arguments as a command") o.add_option("-l", "--list-cmd", dest="list_cmd", action="store_true", help="list available commands") o.add_option("-i", "--info", dest="info", action="store_true", help="show runtime information") o.add_option("--no-gui", dest="no_gui", action="store_true", help="run in non-gui mode") o.add_option("-t", "--continue", dest="cont", action="store_true", help="continue to run after command execution") o.add_option("", "--show-errors", dest="show_errors", action="store_true", help="show errors on console") o.add_option("--no-show-errors", dest="show_errors", action="store_false", help="do not show errors on console") o.add_option("--no-default", dest="default_notebook", action="store_false", help="do not open default notebook") o.add_option("", "--newproc", dest="newproc", action="store_true", help="start KeepNote in a new process") o.add_option("-p", "--port", dest="port", default=None, type="int", help="use a specified port for listening to commands") #============================================================================= def start_error_log(show_errors): """Starts KeepNote error log""" keepnote.init_error_log() # Test ability to write to file-like objects used to display errors. # - if stderr is unavailable, create error message, else add to stream # list. Do not exit. # Note: this code-section is necessary to allow Linux-users the option of # launching KeepNote from a *.desktop file without having it # run in a terminal. In other words, 'Terminal=false' can be safely # added to the *.desktop file; without this code, adding # 'Terminal=false' to the *.desktop file causes KeepNote # launch failure. stream_list = [] stderr_test_str = "\n" stderr_except_msg = "" if show_errors: try: sys.stderr.write(stderr_test_str) except IOError: formatted_msg = traceback.format_exc().splitlines() stderr_except_msg = ''.join( ['** stderr - unavailable for messages - ', formatted_msg[-1], "\n"]) else: stream_list.append(sys.stderr) # if errorlog is unavailable, exit with error, else add to stream list. try: errorlog = open(keepnote.get_user_error_log(), "a") except IOError: sys.exit(traceback.print_exc()) else: stream_list.append(errorlog) # redirect stderr sys.stderr = TeeFileStream(stream_list, autoflush=True) # write errorlog header keepnote.print_error_log_header() keepnote.log_message(stderr_except_msg) def parse_argv(argv): """Parse arguments""" # set default arguments options = o.get_default_values() if keepnote.get_platform() == "windows": options.show_errors = False else: options.show_errors = True # parse args and process (options, args) = o.parse_args(argv[1:], options) return options, args def setup_threading(): """Initialize threading environment""" import gtk.gdk import gobject if keepnote.get_platform() == "windows": # HACK: keep gui thread active def sleeper(): time.sleep(.001) return True # repeat timer gobject.timeout_add(400, sleeper) else: gtk.gdk.threads_init() def gui_exec(function, *args, **kw): """Execute a function in the GUI thread""" import gtk.gdk import gobject sem = threading.Semaphore() sem.acquire() def idle_func(): gtk.gdk.threads_enter() try: function(*args, **kw) return False finally: sem.release() # notify that command is done gtk.gdk.threads_leave() gobject.idle_add(idle_func) # wait for command to execute sem.acquire() def start_gui(argv, options, args, cmd_exec): import keepnote.gui # pygtk imports import pygtk pygtk.require('2.0') import gtk # setup threading environment setup_threading() # create app app = keepnote.gui.KeepNote(basedir) app.init() cmd_exec.set_app(app) need_gui = execute_command(app, argv) # begin gtk event loop if need_gui: gtk.main() def start_non_gui(argv, options, args, cmd_exec): # read preferences app = keepnote.KeepNote(basedir) app.init() cmd_exec.set_app(app) execute_command(app, argv) def execute_command(app, argv): """ Execute commands given on command line Returns True if gui event loop should be started """ options, args = parse_argv(argv) #------------------------------------------ # process builtin commands if options.list_cmd: list_commands(app) return False if options.info: keepnote.print_runtime_info(sys.stdout) return False #------------------------------------------ # process extended commands if options.cmd: # process application command (AppCommand) if len(args) == 0: raise Exception(_("Expected command")) command = app.get_command(args[0]) if command: command.func(app, args) else: raise Exception(_("Unknown command '%s'") % args[0]) # start first window if not options.no_gui: if len(app.get_windows()) == 0: app.new_window() return True return False #------------------------------------------ # process a non-command if options.no_gui: return False if len(args) > 0: for arg in args: if keepnote.notebook.is_node_url(arg): # goto a node host, nodeid = keepnote.notebook.parse_node_url(arg) app.goto_nodeid(nodeid) elif keepnote.extension.is_extension_install_file(arg): # install extension if len(app.get_windows()) == 0: app.new_window() app.install_extension(arg) else: # open specified notebook if len(app.get_windows()) == 0: app.new_window() app.get_current_window().open_notebook(arg) else: # no arguments win = app.new_window() # open default notebook if len(app.get_windows()) == 1 and options.default_notebook: # TODO: finish # reopen all windows referenced by notebooks for path in app.pref.get("default_notebooks", default=[]): win.open_notebook(path, open_here=False) return True def list_commands(app): """List available commands""" commands = app.get_commands() commands.sort(key=lambda x: x.name) print print "available commands:" for command in commands: print " " + command.name, if command.metavar: print " " + command.metavar, if command.help: print " -- " + command.help, print def main(argv): """Main execution""" options, args = parse_argv(argv) # init preference dir keepnote.compat.pref.check_old_user_pref_dir() if not os.path.exists(keepnote.get_user_pref_dir()): keepnote.init_user_pref_dir() # start error log start_error_log(options.show_errors) # get command executor if options.newproc: main_proc = True cmd_exec = CommandExecutor() else: if options.no_gui: main_proc, cmd_exec = get_command_executor( execute_command, port=options.port) else: main_proc, cmd_exec = get_command_executor( lambda app, argv: gui_exec( lambda: execute_command(app, argv)), port=options.port) if main_proc: # initiate main process if options.no_gui: start_non_gui(argv, options, args, cmd_exec) else: start_gui(argv, options, args, cmd_exec) else: # this is a command process, send command to main process cmd_exec.execute(argv) # wait for other threads to close application if options.cont: while True: time.sleep(1000) #============================================================================= # start main function # catch any exceptions that occur try: main(sys.argv) except exceptions.SystemExit, e: # sys.exit() was called pass except Exception, e: traceback.print_exc() sys.stderr.flush() keepnote-0.7.8/CHANGES0000644000175000017500000001727511677423600012527 0ustar razrazKeepNote Change Log ------------------- keepnote-0.7.7 -------------- - added "Paste As Quote" - added creation/modification time editing - added --continue option to command-line - improved support for concurrent reading (fewer database lock errors) - fixed goto_prev_node bug - fixed multiple note move bug - fixed multiple note delete bug keepnote-0.7.6 (2011/11/10) --------------------------- - added foreground and background colors to treeview and listview - added color palette saving - added listview column order and width saving - added frequent VACUUM sqlite command to notebook index - added optional fulltext indexing - added HTTP notebook forwarding - fixed windows My Documents location within virtual folders - fixed windows linking bug keepnote-0.7.5 (2011/9/14) -------------------------- - added drag and drop attachment for windows - fixed indent at start of first line bug - fixed uneven indent - fixed leading white-space bug - fixed windows portable mode bug - fixed clipboard bug on mac - improved rich text copy/paste - fixed pasting HTML comments bug keepnote-0.7.4 (2011/7/9) ------------------------- - added Slovak language - fixed notebook loading speed on Windows - fixed basedir bug - fixed widget focus bug - fixed zero size listdir bug - fixed lostdir bug keepnote-0.7.3 (2011/06/18) --------------------------- - added close button on tabs - bug fixes to notebook indexing, healing, and tamper detection keepnote-0.7.2 (2011/5/12) -------------------------- - added moving/copying notes between notebooks - fixed extension disable bug - improved command API - improved extensions API - improved auto-notebook healing - simplified notebook API - improved node caching with index - added Swedish language keepnote-0.7.1 (2011/03/01) --------------------------- - fixed helper applications bug - fixed insert image format bug - fixed missing image bug - added extension API for adding UI elements keepnote-0.7 (2011/02/25) ------------------------- - added indexed full text search (require sqlite >= 3.5.0) - added custom font sizes for treeview and listview - improved notebook loading time keepnote-0.6.8 (2011/01/24) --------------------------- - fixed save notebook on window close bug - fixed save preferences on close bug keepnote-0.6.7 (2010/11/23) --------------------------- - fixed insert image bug keepnote-0.6.6 (2010/11/16) --------------------------- - multiple window persistence - faster HTML loading - added separate close window and quit commands - fixed bugs - prevent unintended tab renames - remove images from disk when deleted in note - fixed failed open notebook bug - fixed view image bug keepnote-0.6.5 (2010/20/13) --------------------------- - optional tab naming (double click to rename) - tab persistence - new window config options - keep above - minimize on start - sticky - new translations - Polish - fixed bugs - fixed default notebook bug - fixed rename trash bug - fixed keepnote launching from *.desktop files - fixed text color bug - increased notebook opening speed keepnote-0.6.4 (2010/7/23) -------------------------- - fixed notebook file note found bug keepnote-0.6.3 (2010/7/4) ------------------------- - added tabbed browsing - copy/cut/paste in treeview/listview - multiple selection in treeview/listview - added "New File" extension - file attachment - multiple file attachment - new translations - Chinese, Japanese, Italian keepnote-0.6.2 (2010/2/19) -------------------------- - added install/uninstall extension support - added "new window" - added python_prompt extension - added German and Russian translations - faster notebook load times - fixed minimal toolbar bug - fixed font toggle bug - many other bug fixes keepnote-0.6.1 (2010/1/3) ------------------------- - added insert timestamp - added more extension support - reorganized menus - fixed Microsoft maximize window bug - added Spanish translation. keepnote-0.6 (2009/9/16) ------------------------ - added HTML export - added note-to-note links - autocomplete for making links - entry completion for note titles in search box - added note browsing history (back and forward buttons) - remember note selections between closing and opening a notebook - added recently used notebooks listed in file open menu - many bug fixes keepnote-0.5.3 (2009/7/8) ------------------------- - setup for translations - initial French translation included - translations are welcomed! see README.translations.txt - fixes for unicode filenames - unicode support within tarfile backups - added file attachments - added more tree navigation shortcuts - open dialog updated (no needed to click on notebook.nbk) - task icon minimizes and un-minimizes - updated image resize dialog - links are created with default targets (selected text) - more options for default notebook - Bug fix: horizontal rules now auto-resize keepnote-0.5.2 (2008/12/4) -------------------------- - customizable node icons - any node can have child nodes (e.g. "pages" can have children) - add minimize to tray option (hide from taskbar) - editor remembers scroll and cursor position in each page (for the session) - added scrolling to treeview/listview drag-and-drop - easy install fix - fixed modified time bug keepnote-0.5.1 (2008/1/25) -------------------------- - install fix keepnote-0.5.0 (2008/1/24) -------------------------- - external web links - new metadata file format - changed name to KeepNote - more desktop integration - use dbus to raise window with desktop shortcut keys takenote-0.4.5 (2008/12/20) --------------------------- - added "Paste As Text" - add progress bars, background tasks - backup and restore - search - safer file writing - writes are performed to temp files and renames them if write is complete - bug fixes - fixed colormap bug for some platforms (archlinux) - fixed mnemonic for "Format" in menu - fixed "modified" timestamp bug - changed User-Agent string for fetching images from the web (allows copy and paste from more web sites). - bullet point list quirks - cut and delete leave one bullet behind (also causes problems with undo) - fixed copy and paste image size - textview now properly scrolls for pastes and undos takenote-0.4.4 (2008/11/06) --------------------------- - added colored font - added xdg user configuration support - bug fixes - fixed auto-highlighting in listview when changes occur to master list nodes - drag and drop in listview cause weird scrolling - new note sometimes cause goofy scrolling and selection - helper application browser buttons didn't work takenote-0.4.3 (2008/10/01) --------------------------- - Windows version now has customizable shortcuts (using GTK) - made sure GTK and python version requirements did not creep higher - fixed setup.py problems with extensions takenote-0.4.2 (2008/9/27) -------------------------- - bullet point lists - also supports nested indented paragraphs with and without bullet points - default notebook options - default font (notebook specific) - a few Look and Feel options - use lines treeview - use ruler hints in listview - use gtk stock icons - image scaling can now automatically round ("snap") to nearest size multiple - system tray icon (requires pygtk > 2.10) - first implementation of an extensions mechanism - backup_tar implemented as extension now - spell check saves across sessions - gtk accelerator keys saving across sessions takenote-0.4.1 (2008/08/09) --------------------------- - bug fixes - better copy/paste - HTML support - image support takenote-0.4 (2008/07/12) ------------------------- - rich text editing - hierarchical organization for notes - full-text search - inline images - integrated screenshot - spell checking (via gtkspell) keepnote-0.7.8/README.translations.txt0000644000175000017500000000513511523163431015733 0ustar razrazTranslations for KeepNote ------------------------- KeepNote now supports translations through the gettext system. If you would like to create a translation for KeepNote, please let me know, rasmus[at]mit[dot]edu. Below are the basic steps for beginning or modifying a translation for KeepNote. 1. File layout -------------- Makefile.gettext Makefile with gettext commands gettext/messages.pot all strings extracted from KeepNote source gettext/$LANG.po language-specific translations locale/$LANG/LC_MESSAGES/keepnote.mo compiled translations for KeepNote 2. Extract all strings from KeepNote source ------------------------------------------- All common commands for manipulating translations are supported through the KeepNote Makefile. If strings are changed in the source code, they need to be extracted into the 'gettext/messages.pot' file by using the following command: make -f Makefile.gettext extract 3. Create a new translation --------------------------- If your language is not already present (should be 'gettext/$LANG.po'), then use this command to create a blank translation file: make -f Makefile.gettext new LANG=de_DE.UTF8 In this example, a new translation for de_DE.UTF8 (German) is created. You can now edit the file 'gettext/de_DE.UTF8.po' 4. Updating an existing translation with new extracted strings -------------------------------------------------------------- If strings within the source code have been changed, they must be extracted again (see step 2) and merged into the existing translations within 'gettext/$LANG.po'. If you were working on the German translation the command is: make -f Makefile.gettext update LANG=de_DE.UTF8 5. Compiling translations ------------------------- Once translations are written in 'gettext/$LANG.po' they must be compiled into a file named 'locale/$LANG/LC_MESSAGES/keepnote.mo'. Use this command, make -f Makefile.gettext make LANG=de_DE.UTF8 6. Testing/Using a translation ------------------------------ To test or use a translation make sure that the LANG environment variable is set to the translation you would like to use, prior to running KeepNote. For example, to run KeepNote with German translations use: LANG=de_DE.UTF8 bin/keepnote 7. Submitting your translation for inclusion in the KeepNote distribution ------------------------------------------------------------------------- If you would like your translation to be a part of the official KeepNote distribution please send your *.po file to rasmus[at]mit[edu]. If you wish I can add your name to the translation credits. keepnote-0.7.8/setup.py0000644000175000017500000001500511677423606013241 0ustar razraz#!/usr/bin/env python # # setup for KeepNote # # use the following command to install KeepNote: # python setup.py install # #============================================================================= # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # #============================================================================= # constants import keepnote KEEPNOTE_VERSION = keepnote.PROGRAM_VERSION_TEXT #============================================================================= # python and distutils imports import os, sys, shutil, itertools from distutils.core import setup # py2exe module (if building on windows) try: import py2exe except ImportError: pass #============================================================================= # helper functions def split_path(path): """Splits a path into all of its directories""" pathlist = [] while path != "": path, tail = os.path.split(path) pathlist.append(tail) pathlist.reverse() return pathlist def get_files(path, exclude=lambda f: False): """Recursively get files from a directory""" files = [] if isinstance(exclude, list): exclude_list = exclude def exclude(filename): for ext in exclude_list: if filename.endswith(ext): return True return False def walk(path): for f in os.listdir(path): filename = os.path.join(path, f) if exclude(filename): # exclude certain files continue elif os.path.isdir(filename): # recurse directories walk(filename) else: # record all other files files.append(filename) walk(path) return files def get_file_lookup(files, prefix_old, prefix_new, exclude=lambda f: False): """Create a dictionary lookup of files""" if files is None: files = get_files(prefix_old, exclude=exclude) prefix_old = split_path(prefix_old) prefix_new = split_path(prefix_new) lookup = {} for f in files: path = prefix_new + split_path(f)[len(prefix_old):] dirpath = os.path.join(*path[:-1]) lookup.setdefault(dirpath, []).append(f) return lookup def remove_package_dir(filename): i = filename.index("/") return filename[i+1:] #============================================================================= # resource files/data # get resources rc_files = get_file_lookup(None, "keepnote/rc", "rc") image_files = get_file_lookup(None, "keepnote/images", "images") efiles = get_file_lookup(None, "keepnote/extensions", "extensions", exclude=[".pyc"]) freedesktop_files = [ # application icon ("share/icons/hicolor/48x48/apps", ["desktop/keepnote.png"]), # desktop menu entry ("share/applications", ["desktop/keepnote.desktop"])] # get data files if "py2exe" in sys.argv: data_files = rc_files.items() + efiles.items() + image_files.items() package_data = {} else: data_files = freedesktop_files package_data = {'keepnote': []} for v in itertools.chain(rc_files.values(), image_files.values(), efiles.values()): package_data['keepnote'].extend(map(remove_package_dir, v)) #============================================================================= # setup setup( name='keepnote', version=KEEPNOTE_VERSION, description='A cross-platform note taking application', long_description = """ KeepNote is a cross-platform note taking application. Its features include: - rich text editing - bullet points - fonts/colors - hyperlinks - inline images - hierarchical organization for notes - full text search - integrated screenshot - spell checking (via gtkspell) - backup and restore - HTML export """, author='Matt Rasmussen', author_email='rasmus@alum.mit.edu', url='http://keepnote.org', download_url='http://keepnote.org/download/keepnote-%s.tar.gz' % KEEPNOTE_VERSION, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Environment :: Win32 (MS Windows)', 'Environment :: X11 Applications', 'Intended Audience :: Developers', 'Intended Audience :: Education', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Programming Language :: Python', ], license="GPL", packages=['keepnote', 'keepnote.gui', 'keepnote.gui.richtext', 'keepnote.notebook', 'keepnote.notebook.connection', 'keepnote.notebook.connection.fs', 'keepnote.compat', 'keepnote.mswin'], scripts=['bin/keepnote'], data_files=data_files, package_data=package_data, windows=[{ 'script': 'bin/keepnote', 'icon_resources': [(1, 'keepnote/images/keepnote.ico')], }], options = { 'py2exe' : { 'packages': 'encodings', 'includes': 'cairo,pango,pangocairo,atk,gobject,win32com.shell,win32api,win32com,win32ui,win32gui', 'dist_dir': 'dist/keepnote-%s.win' % KEEPNOTE_VERSION }, #'sdist': { # 'formats': 'zip', #} } ) # execute post-build script if "py2exe" in sys.argv: execfile("pkg/win/post_py2exe.py") keepnote-0.7.8/README0000644000175000017500000000046211523163431012373 0ustar razrazKeepNote - Note-taking and organization application Copyright 2008-2009 Matt Rasmussen rasmus@mit.edu - For install instructions see INSTALL - For license information see LICENSE,COPYING - The change log of features and bug fixes are in CHANGES - Translation information is in README.translations.txt keepnote-0.7.8/COPYING0000644000175000017500000004310311523163431012545 0ustar razraz GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program 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. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. keepnote-0.7.8/desktop/0000755000175000017500000000000011727170645013175 5ustar razrazkeepnote-0.7.8/desktop/keepnote.desktop0000644000175000017500000000046711634261533016403 0ustar razraz[Desktop Entry] Encoding=UTF-8 Type=Application Name=KeepNote GenericName=Note-taking Application GenericName[sk]=Program na správu poznámok Comment=Note-taking Application Comment[sk]=Program na správu poznámok Icon=keepnote Exec=keepnote %f Categories=Office #MimeType= Terminal=false StartupNotify=false keepnote-0.7.8/desktop/keepnote.png0000644000175000017500000002475511523163431015517 0ustar razrazPNG  IHDR00%gAMA|Q cHRMz%u0`:o_FbKGD X pHYs   vpAg00W)ylO=]=(KA% Z%*@ SEh^š4\@Tua3ֻPw:]h9 >>߅韚va@EEI_gA J< f1 Ȃ1˥F@TZ'V:Dt374k@l&tI:ftt% Z"*#`2h3#պ$ðD@Kd)ɘ60F:% EgOT89opXvԳ' @t&8W|&"fTؽ{w <^|S_L3˟8Wk`]l=>v.zߤ'* &JkgA  ,qvPch$-$ɂΠ='IӨ`۩rup{l}hpW =ֺ+֫A -q%&jyv)׸Ma/B1`fʀT4 +t[k'G6^{^yz׾7ޮ+i4y_1x>0z̘WxP1C'zmN9 z .K4 E;9OJdaғ*a秞]3Ǽ, q`BĂsgüe~l={AoIn蒯=`+@yD!8G O|_I xX=EiL :&쬃?:zl_9OnM8|x *NXE>?LufΩQ>?<8|"7/C@Y )_e ~]M-hZs6R+y撒A. S5ƀ@Ӡj >T q%%5 `ہ gu5 J$Ε.f;׬YXk)RJd.FXfE覻i}5GsO 6= 쀳9k2vwr* |f=w 37/96r^/aCK@$޶&ti-DCZbӢ A72~%%Ҡ̈́Ȓ͸ЮihЇA:σxAt5!!UT#dd&Of Ud cr &a$ "Rj:2$oJL/M9 ~e&Q>l{0"#KFg{n7oz|\jݽvٽGaۧ^@]>Ws-͑'֨uJAD(؜i3h@FT K%M d@_Tu*h5 R-) 2 `:e,D%/f6 @tK$#"6S@̑Re|Ls@ŽhHp'9/9dO|hMKKRxp@3A"@X2:% 2BFp9*Vqeq@^[I9Um'm}a? Fds$kvw5n3w;LΤ*yE >'U[UAWj] v]kׂViVjИ6Z |)q h4j{? I\S@0aH(1߶;V O^[A Z$H) f|&Y(-or.-vФIIi4imА4 4S*zmMm -2h|]{W3WFYm>mZ$Ii`TD(HxL$ Sh-5뙠0<e t{)9ۈ-ĥz"|4($)@$e9M<'-f! v^s4Ԫ%j =A!M`-%[RtgVY# xEqM![:횳51Šz c۷kqPϕVPz%dkݕ CƮ5{;@U{4qQۮQD|t{6{$I1}ݝZ L>|aټV F(NV Cā&Ii\sM:MqMj 4E|nv0sB*UTiv 6siVd1f11*U~q &(wsHHidt{G] iP%Q%fа) k'),glG&IPҤ`n;Y Z+mt'/>^dl0 @/́p(V$ ]xaJ& ѷ3y;-viIHB #2"#@`hnT,#R ^ CJc`aZAf3 % @&k:>CAd&!)АV KPdXF.Kݥn=D òS3t4O*K pw9ä_s˜~"YyOaT)WtN PB.. # 2q(2 L56_Jd.Q!)IDd$ @|;1]t8 M,2;@8AC [.27=ErhIB8h'mo/ssj'v( i ^4R@#hd6]`ܳaڔiM=*Isq#QkA t" $+Yn&m<6_PEr;T3vZy: EMt.4u* _ysVC:uM7$!kOyvu PkWr jBHI.3-f#T|z?xǑAOTT/W Mi@%d]nY7zvp¤*2u.Л8 IL ZEE7xtZҤA :"Xnn=u $z"e9`[t@ԓAr=@&\+'}ŏf@\Cڤ@ ?ѫ t)IF egǞY{cw1ξsϨf)gB`g#jyiy ߕ/la= gu0iL:j^ 43B@:@l~v}( M֨)+@=xKCj44ݩ] Z%^̔P"J/p$U~$I?1II @^~tm_%GeD`¡`mxp/f ew3eۓ*iǁhf)YtЎ+vbL^fh\ӴMbLm߾>lRd<$q!AZMZ^26F(HۻL&,Yed /`wā_wnҐ1$UFCg Fs`w[kw] گ9!MU1cf:Пؓs_@t @Jӧ4z_ہb@ϼ[އ|W*y@N@HHJ&n*jnl HK=,A*hǃ?@;<z_䯠Y=#c~h;7x09r\j"rp$ڈRFؗ@D&)r3Hill|TQx'z}@+{dX9&! I\ I߶࣡ .%īyzkhܟ:@m~Em_ o 4chco>D$Yr#DRg\*AWȃ(Ez<2⸬|OgqĀ %1&K. e`'&V5n[Q$N\,09И$!Hi& W懦LYiR B%z1tP3R`y+jt?ˉ`ߚhH`gkG#r(vFF$""#ywMHRZA B= x`v\[C+9ݢyǂII$B01($yc"v`}w% S5yǚ~SOc@(%ަomfGH1o}YYez`Z}5X<bF$$EZgn`nݒhV{m'4*L~&~ vp~ݮLxhfV,$N3a|g̓X@<3U@B:d$" $;D=͖6i BK-Hyo&oz(|{sl0i2&s; D un-w{]:9c{7}q& ׀3B6J\Qqiw [a짣g2> 5#jH#NQpZLAZ pS'`rdʅ faA\5>\oK16gk<jI8h P:wIf"G$ٷ{ϭ7_։&> c; v)w9~%dIl5i\m72kҮ), ԏ/```o n1Ɯ5Z"k`taQw5v?|-Y0N{RJȀ=g4f.s,gQ< g5]wh/~3%_$%O[h7ЮEf%Bs܋d8WYy)L<5qģ1]bڧ me~[ikAIa58蕁Z)>6ba<$gH1:g8CNg. @h>'^S7c}(wi6I+Ц)8 8[^i d|s4+G\½ɺl=KYF~ Rò  ||Wept@l `66%E4`ǭ 6gOQ,2uPU)S'Mߛ|_o:4pkb07(\MZ@ƴIHB ԑ :-^}:(R@6Jk~Pյ?Ĥo9O]A2BguGA6iRkdXkt9Q6D(3kb@Ds6Jz1-e_8z`$!35. %k!(cwB70V1F[-6Im6J~zgt%#x] ʠ zs H+kmx?R;7,Vi{GcU}WxɀDR0RV ̰߆/LlHDF<C@@v:Ф+l .Ite@˾NFAnfBp xlЅMܺ/ۭ`jWP~=/콶_ 124hݴSvH:2CWžƚ)t)`;I"]RB &Bri`~h;U$] kLAi[l3Wq7TYqi PqE='sùۮ@gK_*#Gv[]@ 2:C5hdQ̀P4`9\~-@u,|4){&&#2rI]oH4Q nݭAK]CZOh߀aE$@13GWtps`VHXK@%_$,'N v `vS@QgkkCyAۈZ Ad lDBG$ O@Ā.5"rfz5%K@7ay=ds]GIId|})iZmtF`8l,oBծ;#W$}Hjyʧ6fMdL H *^RQ4s%Lt-_ Ze@ZK@uJ7ҷZf5}hi }tnb'y}Ki3`M05farcwI^;{̑$"I?5Z5Π{{{n/,uuuEn$.""BJ "Rд41BR KXCxǑ t0 BgYG$,'xs7IENDB`keepnote-0.7.8/Makefile.gettext0000644000175000017500000000207311523163431014636 0ustar razraz# # make translations with gettext # PYTHON_FILES:=$(shell find keepnote -name '*.py') SRC_FILES=$(PYTHON_FILES) keepnote/rc/keepnote.glade.h INTLTOOL_EXTRACT:=$(shell which intltool || echo /usr/share/intltool-debian/intltool-extract) LOCALE_DIR=keepnote/rc/locale #============================================================================= # rules # make messages file, extracts all strings in _() extract: xgettext --from-code=utf-8 -k_ -kN_ \ -o gettext/messages.pot $(SRC_FILES) # make a new translation new: msginit -l $(LANG) -o gettext/$(LANG).po --input gettext/messages.pot # update language file with new strings update: msgmerge -N -U gettext/$(LANG).po gettext/messages.pot # make translations for application make: mkdir -p $(LOCALE_DIR)/$(LANG)/LC_MESSAGES/ msgfmt gettext/$(LANG).po -o $(LOCALE_DIR)/$(LANG)/LC_MESSAGES/keepnote.mo #============================================================================= # depends keepnote/rc/keepnote.glade.h: keepnote/rc/keepnote.glade $(INTLTOOL_EXTRACT) --type=gettext/glade keepnote/rc/keepnote.glade keepnote-0.7.8/PKG-INFO0000644000175000017500000000275011727170645012625 0ustar razrazMetadata-Version: 1.0 Name: keepnote Version: 0.7.8 Summary: A cross-platform note taking application Home-page: http://keepnote.org Author: Matt Rasmussen Author-email: rasmus@alum.mit.edu License: GPL Download-URL: http://keepnote.org/download/keepnote-0.7.8.tar.gz Description: KeepNote is a cross-platform note taking application. Its features include: - rich text editing - bullet points - fonts/colors - hyperlinks - inline images - hierarchical organization for notes - full text search - integrated screenshot - spell checking (via gtkspell) - backup and restore - HTML export Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Win32 (MS Windows) Classifier: Environment :: X11 Applications Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python keepnote-0.7.8/keepnote/0000755000175000017500000000000011727170645013336 5ustar razrazkeepnote-0.7.8/keepnote/listening.py0000644000175000017500000000423411677423601015704 0ustar razraz""" KeepNote Listener (Observer) pattern """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # class Listeners (object): """Maintains a list of listeners (functions) that are called when the notify function is called. """ def __init__(self): self._listeners = [] self._suppress = {} def add(self, listener): """Add a listener function to the list""" self._listeners.append(listener) self._suppress[listener] = 0 def remove(self, listener): """Remove a listener function from list""" self._listeners.remove(listener) del self._suppress[listener] def clear(self): """Clear listener list""" self._listeners = [] self._suppress = {} def notify(self, *args, **kargs): """Notify listeners""" for listener in self._listeners: if self._suppress[listener] == 0: listener(*args, **kargs) def suppress(self, listener=None): """Suppress notification""" if listener is not None: self._suppress[listener] += 1 else: for l in self._suppress: self._suppress[l] += 1 def resume(self, listener=None): """Resume notification""" if listener is not None: self._suppress[listener] -= 1 else: for l in self._suppress: self._suppress[l] -= 1 keepnote-0.7.8/keepnote/undo.py0000644000175000017500000001271311677423606014663 0ustar razraz""" KeepNote UndoStack for maintaining undo and redo actions """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import sys from keepnote.linked_list import LinkedList def cat_funcs(funcs): """Concatenate a list of functions [f,g,h,...] that take no arguments into one function: cat = { lambda: f(); g(); h(); } """ funcs = list(funcs) if len(funcs) == 1: return funcs[0] def f(): for func in funcs: func() return f class UndoStack (object): """UndoStack for maintaining undo and redo actions""" def __init__(self, maxsize=sys.maxint): """maxsize -- maximum size of undo list""" # stacks maintaining (undo,redo) pairs self._undo_actions = LinkedList() self._redo_actions = [] # grouping several actions into one self._group_counter = 0 self._pending_actions = [] # suppress undo/redo while counter > 0 self._suppress_counter = 0 # maximum size undo stack self._maxsize = maxsize self._in_progress = False def do(self, action, undo, execute=True): """Perform action() (if execute=True) and place (action,undo) pair on stack""" if self._suppress_counter > 0: return if self._group_counter == 0: # grouping is not active, push action pair and clear redo stack self._undo_actions.append((action, undo)) self._redo_actions = [] # TODO: should stack be suppressed at this time? if execute: action() # maintain proper undo size while len(self._undo_actions) > self._maxsize: self._undo_actions.pop_front() else: # grouping is active, place action pair on pending stack self._pending_actions.append((action, undo)) self._redo_actions = [] if execute: action() def undo(self): """Undo last action on stack""" assert self._group_counter == 0 if len(self._undo_actions) > 0: action, undo = self._undo_actions.pop() self.suppress() self._in_progress = True undo() self._in_progress = False self.resume() self._redo_actions.append((action, undo)) def redo(self): """Redo last action on stack""" assert self._group_counter == 0 if len(self._redo_actions) > 0: action, undo = self._redo_actions.pop() self.suppress() self._in_progress = True action() self._in_progress = False self.resume() self._undo_actions.append((action, undo)) while len(self._undo_actions) > self._maxsize: self._undo_actions.pop_front() def begin_action(self): """Start grouping actions Can be called recursively. Must have corresponding end_action() call """ self._group_counter += 1 def end_action(self): """Stop grouping actions Can be called recursively. """ self._group_counter -= 1 assert self._group_counter >= 0 if self._group_counter == 0: if len(self._pending_actions) > 0: actions, undos = zip(*self._pending_actions) self._undo_actions.append((cat_funcs(actions), cat_funcs(reversed(undos)))) self._pending_actions = [] while len(self._undo_actions) > self._maxsize: self._undo_actions.pop_front() def abort_action(self): """ Stop grouping actions and throw away actions collected so far """ self._group_counter = 0 self._pending_actions = [] def suppress(self): """Suppress pushing actions on stack Can be called recursively. Must have corresponding resume() call""" self._suppress_counter += 1 def resume(self): """Resume pushing actions on stack Can be called recursively. """ self._suppress_counter -= 1 assert self._suppress_counter >= 0 def is_suppressed(self): """Returns True if UndoStack is being suprressed""" return self._suppress_counter > 0 def reset(self): """Clear UndoStack of all actions""" self._undo_actions.clear() self._redo_actions = [] self._group_counter = 0 self._pending_actions = [] self._suppress_counter = 0 def is_in_progress(self): """Returns True if undo or redo is in progress""" return self._in_progress keepnote-0.7.8/keepnote/orderdict.py0000644000175000017500000000473611677423605015702 0ustar razraz""" KeepNote OrderDict module """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # class OrderDict (dict): """ An ordered dict """ def __init__(self, *args, **kargs): if len(args) > 0 and hasattr(args[0], "next"): dict.__init__(self) self._order = [] for k, v in args[0]: self._order.append(k) dict.__setitem__(self, k, v) else: dict.__init__(self, *args, **kargs) self._order = dict.keys(self) # The following methods keep names in sync with dictionary keys def __setitem__(self, key, value): if key not in self: self._order.append(key) dict.__setitem__(self, key, value) def __delitem__(self, key): self._order.remove(key) dict.__delitem__(self, key) def update(self, dct): for key in dct: if key not in self: self._order.append(key) dict.update(self, dct) def setdefault(self, key, value): if key not in self: self._order.append(key) return dict.setdefault(self, key, value) def clear(self): self._order = [] dict.clear(self) # keys are always sorted in order added def keys(self): return list(self._order) def iterkeys(self): return iter(self._order) def values(self): return [self[key] for key in self._order] def itervalues(self): for key in self._order: yield self[key] def items(self): return [(key, self[key]) for key in self._order] def iteritems(self): for key in self._order: yield (key, self[key]) def __iter__(self): return iter(self._order) keepnote-0.7.8/keepnote/mswin/0000755000175000017500000000000011727170645014473 5ustar razrazkeepnote-0.7.8/keepnote/mswin/__init__.py0000644000175000017500000000356411655576212016614 0ustar razraz # make sure py2exe finds win32com try: import sys import modulefinder import win32com for p in win32com.__path__[1:]: modulefinder.AddPackagePath("win32com", p) for extra in ["win32com.shell"]: __import__(extra) m = sys.modules[extra] for p in m.__path__[1:]: modulefinder.AddPackagePath(extra, p) except ImportError: # no build path setup, no worries. pass try: import pywintypes import winerror from win32com.shell import shell, shellcon import win32api import win32gui import win32con import win32ui import ctypes.windll.kernel32 except: pass def get_my_documents(): """Return the My Documents folder""" # See: # http://msdn.microsoft.com/en-us/library/windows/desktop/bb776887%28v=vs.85%29.aspx#mydocs # http://msdn.microsoft.com/en-us/library/bb762494%28v=vs.85%29.aspx#csidl_personal try: df = shell.SHGetDesktopFolder() pidl = df.ParseDisplayName(0, None, "::{450d8fba-ad25-11d0-98a8-0800361b1103}")[1] except pywintypes.com_error, e: if e.hresult == winerror.E_INVALIDARG: # This error occurs when the My Documents virtual folder is not available below the Desktop virtual folder in the file system. # This may be the case if it has been made unavailable using a Group Policy setting. # See http://technet.microsoft.com/en-us/library/cc978354.aspx. pidl = shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_PERSONAL) else: raise mydocs = shell.SHGetPathFromIDList(pidl) # TODO: may need to handle window-specific encoding here. #encoding = locale.getdefaultlocale()[1] #if encoding is None: # encoding = "utf-8" return mydocs #def set_env(key, val): # ctypes.windll.kernel32.SetEnvironmentVariableW(key, val) keepnote-0.7.8/keepnote/mswin/screenshot.py0000644000175000017500000001732311677423601017225 0ustar razraz""" KeepNote Screenshot utility for MS Windows """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import sys, os, traceback # win32api imports try: import win32api import win32gui import win32con import win32ui except ImportError, e: pass _g_class_num = 0 def capture_screen(filename, x, y, x2, y2): """Captures a screenshot from a region of the screen""" if x > x2: x, x2 = x2, x if y > y2: y, y2 = y2, y w, h = x2 - x, y2 - y screen_handle = win32gui.GetDC(0) screen_dc = win32ui.CreateDCFromHandle(screen_handle) shot_dc = screen_dc.CreateCompatibleDC() shot_bitmap = win32ui.CreateBitmap() shot_bitmap.CreateCompatibleBitmap(screen_dc, w, h) shot_dc.SelectObject(shot_bitmap) shot_dc.BitBlt((0, 0), (w, h), screen_dc, (x, y), win32con.SRCCOPY) shot_bitmap.SaveBitmapFile(shot_dc, filename) class Window (object): """Class for basic MS Windows window""" def __init__(self, title="Untitled", style=None, exstyle=None, pos=(0, 0), size=(400, 400), background=None, message_map = {}, cursor=None): global _g_class_num if style is None: style = win32con.WS_OVERLAPPEDWINDOW if exstyle is None: style = win32con.WS_EX_LEFT if background is None: background = win32con.COLOR_WINDOW if cursor is None: cursor = win32con.IDC_ARROW self._instance = win32api.GetModuleHandle(None) self.message_map = {win32con.WM_DESTROY: self._on_destroy} self.message_map.update(message_map) _g_class_num += 1 class_name = "class_name%d" % _g_class_num wc = win32gui.WNDCLASS() wc.hInstance = self._instance wc.lpfnWndProc = self.message_map # could also specify a wndproc wc.lpszClassName = class_name wc.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW wc.hbrBackground = background wc.cbWndExtra = 0 wc.hCursor = win32gui.LoadCursor(0, cursor) wc.hIcon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION) class_atom = win32gui.RegisterClass(wc) # C code: wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(HBRUSH) + (sizeof(COLORREF)); #wc.cbWndExtra = win32con.DLGWINDOWEXTRA + struct.calcsize("Pi") #wc.hIconSm = 0 self._handle = win32gui.CreateWindowEx(exstyle, class_atom, title, style, #win32con.WS_POPUP, # | win32con.WS_EX_TRANSPARENT, pos[0], pos[1], size[0], size[1], 0, # no parent 0, # no menu self._instance, None) def show(self, enabled=True): if enabled: win32gui.ShowWindow(self._handle, win32con.SW_SHOW) else: win32gui.ShowWindow(self._handle, win32con.SW_HIDE) def maximize(self): win32gui.ShowWindow(self._handle, win32con.SW_SHOWMAXIMIZED) def activate(self): win32gui.SetForegroundWindow(self._handle) #SwitchToThisWindow(self._handle, False) def _on_destroy(self, hwnd, message, wparam, lparam): self.close() return True def close(self): #win32gui.PostQuitMessage(0) win32gui.DestroyWindow(self._handle) class WinLoop (object): def __init__(self): self._running = True def start(self): while self._running: b, msg = win32gui.GetMessage(0, 0, 0) if not msg: break win32gui.TranslateMessage(msg) win32gui.DispatchMessage(msg) def stop(self): self._running = False class ScreenShotWindow (Window): """ScreenShot Window""" def __init__(self, filename, shot_callback=None): x, y, w, h = win32gui.GetWindowRect(win32gui.GetDesktopWindow()) Window.__init__(self, "Screenshot", pos=(x,y), size=(w,h), style = win32con.WS_POPUP, exstyle = win32con.WS_EX_TRANSPARENT, background = 0, message_map = { win32con.WM_MOUSEMOVE: self._on_mouse_move, win32con.WM_LBUTTONDOWN: self._on_mouse_down, win32con.WM_LBUTTONUP: self._on_mouse_up }, cursor=win32con.IDC_CROSS) self._filename = filename self._shot_callback = shot_callback self._drag = False self._draw = False def _on_mouse_down(self, hwnd, message, wparam, lparam): """Mouse down event""" self._drag = True self._start = win32api.GetCursorPos() def _on_mouse_up(self, hwnd, message, wparam, lparam): """Mouse up event""" if self._draw: # cleanup rectangle on desktop self._drag = False self._draw = False hdc = win32gui.CreateDC("DISPLAY", None, None) pycdc = win32ui.CreateDCFromHandle(hdc) pycdc.SetROP2(win32con.R2_NOTXORPEN) win32gui.Rectangle(hdc, self._start[0], self._start[1], self._end[0], self._end[1]) # save bitmap capture_screen(self._filename, self._start[0], self._start[1], self._end[0], self._end[1]) self.close() if self._shot_callback: self._shot_callback() def _on_mouse_move(self, hwnd, message, wparam, lparam): """Mouse moving event""" # get current mouse coordinates x, y = win32api.GetCursorPos() if self._drag: hdc = win32gui.CreateDC("DISPLAY", None, None) pycdc = win32ui.CreateDCFromHandle(hdc) pycdc.SetROP2(win32con.R2_NOTXORPEN) # erase old rectangle if self._draw: win32gui.Rectangle(hdc, self._start[0], self._start[1], self._end[0], self._end[1]) # draw new rectangle self._draw = True win32gui.Rectangle(hdc, self._start[0], self._start[1], x, y) self._end = (x, y) #DeleteDC ( hdc); def take_screenshot(filename): win32gui.InitCommonControls() def click(): loop.stop() loop = WinLoop() win = ScreenShotWindow(filename, click) win.maximize() win.activate() loop.start() #win32gui.PumpMessages() def main(argv): if len(argv) > 1: filename = sys.argv[1] else: filename = "screenshot.bmp" take_screenshot(filename) if __name__ == "__main__": main(sys.argv) keepnote-0.7.8/keepnote/timestamp.py0000644000175000017500000001224011677423606015714 0ustar razraz""" KeepNote timestamp module """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import locale import sys import time # determine UNIX Epoc (which should be 0, unless the current platform has a # different definition of epoc) # Use the epoc date + 1 month (SEC_OFFSET) in order to prevent underflow in date due to user's timezone SEC_OFFSET = 3600 * 24 * 31 EPOC = time.mktime((1970, 2, 1, 0, 0, 0, 3, 1, 0)) - time.timezone - SEC_OFFSET ENCODING = locale.getdefaultlocale()[1] if ENCODING is None: ENCODING = "utf-8" """ 0 tm_year (for example, 1993) 1 tm_mon range [1,12] 2 tm_mday range [1,31] 3 tm_hour range [0,23] 4 tm_min range [0,59] 5 tm_sec range [0,61]; see (1) in strftime() description 6 tm_wday range [0,6], Monday is 0 7 tm_yday range [1,366] 8 tm_isdst 0, 1 or -1; see below """ TM_YEAR, \ TM_MON, \ TM_MDAY, \ TM_HOUR, \ TM_MIN, \ TM_SEC, \ TM_WDAY, \ TM_YDAY, \ TM_ISDST = range(9) """ %a Locale's abbreviated weekday name. %A Locale's full weekday name. %b Locale's abbreviated month name. %B Locale's full month name. %c Locale's appropriate date and time representation. %d Day of the month as a decimal number [01,31]. %H Hour (24-hour clock) as a decimal number [00,23]. %I Hour (12-hour clock) as a decimal number [01,12]. %j Day of the year as a decimal number [001,366]. %m Month as a decimal number [01,12]. %M Minute as a decimal number [00,59]. %p Locale's equivalent of either AM or PM. (1) %S Second as a decimal number [00,61]. (2) %U Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0. (3) %w Weekday as a decimal number [0(Sunday),6]. %W Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. (3) %x Locale's appropriate date representation. %X Locale's appropriate time representation. %y Year without century as a decimal number [00,99]. %Y Year with century as a decimal number. %Z Time zone name (no characters if no time zone exists). %% A literal "%" character. """ DEFAULT_TIMESTAMP_FORMATS = { "same_day": u"%I:%M %p", "same_month": u"%a, %d %I:%M %p", "same_year": u"%a, %b %d %I:%M %p", "diff_year": u"%a, %b %d, %Y" } def get_timestamp(): """Returns the current timestamp""" return int(time.time() - EPOC) def get_localtime(): """Returns the local time""" return time.localtime() def get_str_timestamp(timestamp, current=None, formats=DEFAULT_TIMESTAMP_FORMATS): """ Get a string representation of a time stamp The string will be abbreviated according to the current time. """ # NOTE: I have written this function to allow unicode formats. # The encode/decode functions should allow most unicode formats to # to be processed by strftime. However, a '%' may occur inside a # multibyte character. This is a hack until python issue # http://bugs.python.org/issue2782 is resolved. if formats is None: formats = DEFAULT_TIMESTAMP_FORMATS try: if current is None: current = get_localtime() local = time.localtime(timestamp + EPOC) if local[TM_YEAR] == current[TM_YEAR]: if local[TM_MON] == current[TM_MON]: if local[TM_MDAY] == current[TM_MDAY]: return time.strftime(formats["same_day"].encode(ENCODING), local).decode(ENCODING) else: return time.strftime(formats["same_month"].encode(ENCODING), local).decode(ENCODING) else: return time.strftime(formats["same_year"].encode(ENCODING), local).decode(ENCODING) else: return time.strftime(formats["diff_year"].encode(ENCODING), local).decode(ENCODING) except: return u"[formatting error]" def format_timestamp(timestamp, format): local = time.localtime(timestamp + EPOC) return time.strftime(format.encode(ENCODING), local).decode(ENCODING) def parse_timestamp(timestamp_str, format): # raises error if timestamp cannot be parsed tstruct = time.strptime(timestamp_str, format) local = time.mktime(tstruct) return int(local - EPOC) keepnote-0.7.8/keepnote/history.py0000644000175000017500000000414311677423601015410 0ustar razraz""" KeepNote Node history data structure """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # class NodeHistory (object): """Data structure of node history""" def __init__(self, maxsize=40): self._list = [] self._pos = 0 self._suspend = 0 self._maxsize = maxsize def add(self, nodeid): if self._suspend == 0: # truncate list to current position if self._list: self._list = self._list[:self._pos+1] # add page to history self._list.append(nodeid) self._pos = len(self._list) - 1 # keep history to max size if len(self._list) > self._maxsize: self._list = self._list[-self._maxsize:] self._pos = len(self._list) - 1 def move(self, offset): self._pos += offset if self._pos < 0: self._pos = 0 if self._pos >= len(self._list): self._pos = len(self._list) - 1 if self._list: return self._list[self._pos] else: return None def begin_suspend(self): self._suspend += 1 def end_suspend(self): self._suspend -=1 assert self._suspend >= 0 def has_back(self): return self._pos > 0 def has_forward(self): return self._pos < len(self._list) - 1 keepnote-0.7.8/keepnote/util.py0000644000175000017500000000337311677423606014675 0ustar razraz""" KeepNote utilities """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # class PushIter (object): """Wrap an iterator in another iterator that allows one to push new items onto the front of the iteration stream""" def __init__(self, it): self._it = iter(it) self._queue = [] def __iter__(self): return self def next(self): if len(self._queue) > 0: return self._queue.pop() else: return self._it.next() def push(self, item): """Push a new item onto the front of the iteration stream""" self._queue.append(item) def compose2(f, g): """ Compose two functions into one compose2(f, g)(x) <==> f(g(x)) """ return lambda *args, **kargs: f(g(*args, **kargs)) def compose(*funcs): """Composes two or more functions into one function example: compose(f,g)(x) <==> f(g(x)) """ funcs = reversed(funcs) f = funcs.next() for g in funcs: f = compose2(g, f) return f keepnote-0.7.8/keepnote/extension.py0000644000175000017500000002141511677423600015723 0ustar razraz""" KeepNote Extension system """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import os import imp import sys try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.elementtree.ElementTree as ET import keepnote from keepnote.listening import Listeners from keepnote import orderdict, plist # globals EXTENSION_EXT = u".kne" # filename extension for KeepNote Extensions INFO_FILE = u"info.xml" class DependencyError (StandardError): """Exception for dependency error""" def __init__(self, ext, dep): self.ext = ext self.dep = dep def __str__(self): return "Extension '%s' has failed dependency %s" % \ (self.ext.key, self.dep) #============================================================================= # extension functions def init_user_extensions(pref_dir=None, home=None): """Ensure users extensions are initialized Install defaults if needed""" if pref_dir is None: pref_dir = keepnote.get_user_pref_dir(home) extensions_dir = keepnote.get_user_extensions_dir(pref_dir) if not os.path.exists(extensions_dir): # make user extensions directory os.makedirs(extensions_dir, 0700) extensions_data_dir = keepnote.get_user_extensions_data_dir(pref_dir) if not os.path.exists(extensions_data_dir): # make user extensions data directory os.makedirs(extensions_data_dir, 0700) def scan_extensions_dir(extensions_dir): """Iterate through the extensions in directory""" for filename in os.listdir(extensions_dir): path = os.path.join(extensions_dir, filename) if os.path.isdir(path): yield path def import_extension(app, name, filename): """Import an Extension""" filename2 = os.path.join(filename, u"__init__.py") try: infile = open(filename2) except Exception, e: raise keepnote.KeepNotePreferenceError("cannot load extension '%s'" % filename, e) try: mod = imp.load_module(name, infile, filename2, (".py", "rb", imp.PY_SOURCE)) ext = mod.Extension(app) ext.key = name ext.read_info() infile.close() return ext except Exception, e: infile.close() raise keepnote.KeepNotePreferenceError("cannot load extension '%s'" % filename, e) def get_extension_info_file(filename): """Returns an info for an extension file path""" return os.path.join(filename, INFO_FILE) def read_extension_info(filename): """Reads an extensions info""" tree = ET.ElementTree(file=get_extension_info_file(filename)) # parse xml # check tree structure matches current version root = tree.getroot() if root.tag != "extension": raise keepnote.KeepNotePreferenceError("bad extension info format") p = root.find("dict") if p is None: raise keepnote.KeepNotePreferenceError("bad extension info format") return plist.load_etree(p) def dependency_satisfied(ext, dep): """ Checks whether an extension satisfies a dependency if ext is None, only the 'no' rel is checked """ name, rel, version = dep if ext is None: return (rel == "no") if rel == ">": if not (ext.version > version): return False elif rel == ">=": if not (ext.version >= version): return False elif rel == "==": if not (ext.version == version): return False elif rel == "<=": if not (ext.version <= version): return False elif rel == "<": if not (ext.version < version): return False elif rel == "!=": if not (ext.version != version): return False return True def parse_extension_version(version_str): return tuple(map(int, version_str.split("."))) def format_extension_version(version): return ".".join(map(version, str)) def is_extension_install_file(filename): """ Returns True if file is an extension install file """ return filename.endswith(EXTENSION_EXT) class Extension (object): """KeepNote Extension""" version = (1, 0) key = "" name = "untitled" author = "no author" website = "http://keepnote.org" description = "base extension" visible = True def __init__(self, app): self._app = app self._info = {} self._enabled = False self.type = "system" self.enabled = Listeners() def read_info(self): """Populate extension info""" path = self.get_base_dir(False) self._info = read_extension_info(path) # populate info self.version = parse_extension_version(self._info["version"]) self.name = self._info["name"] self.author = self._info["author"] self.website = self._info["website"] self.description = self._info["description"] def get_info(self, key): return self._info.get(key, None) def enable(self, enable): """Enable/disable extension""" # check dependencies self.check_depends() # mark extension as enabled self._enabled = enable # notify listeners self.enabled.notify(enable) # return whether the extension is enabled return self._enabled def is_enabled(self): """Returns True if extension is enabled""" return self._enabled def check_depends(self): """Checks whether dependencies are met. Throws exception on failure""" for dep in self.get_depends(): if not self._app.dependency_satisfied(dep): raise DependencyError(self, dep) def get_depends(self): """ Returns dependencies of extension Dependencies returned as a list of tuples (NAME, REL, EXTRA) NAME is a string identify an extension (or 'keepnote' itself). EXTRA is an object whose type depends on REL REL is a string representing a relation. Options are: Version relations. For each of these values for REL, the EXTRA field is interpreted as VERSION (see below): '>=' the version must be greater than or equal to '>' the version must be greater than '==' the version must be exactly equal to '<=' the version must less than or equal to '<' the version must be less than '!=' the version must not be equal to Other relations. 'no' the extension must not exist. EXTRA is None. Possible values for EXTRA: VERSION This is a tuple representing a version number. ex: the tuple (0, 6, 1) represents version 0.6.1 All dependencies must be met to enable an extension. A extension name can appear more than once if several relations are required (such as specifying a range of valid version numbers). """ return [("keepnote", ">=", (0, 6, 1))] #=============================== # filesystem paths def get_base_dir(self, exist=True): """ Returns the directory containing the extension's code If 'exists' is True, create directory if it does not exists. """ path = self._app.get_extension_base_dir(self.key) if exist and not os.path.exists(path): os.makedirs(path) return path def get_data_dir(self, exist=True): """ Returns the directory for storing data specific to this extension If 'exists' is True, create directory if it does not exists. """ path = self._app.get_extension_data_dir(self.key) if exist and not os.path.exists(path): os.makedirs(path) return path def get_data_file(self, filename, exist=True): """ Returns a full path to a file within the extension's data directory If 'exists' is True, create directory if it does not exists. """ return os.path.join(self.get_data_dir(exist), filename) keepnote-0.7.8/keepnote/rc/0000755000175000017500000000000011727170645013742 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/0000755000175000017500000000000011727170645015201 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/ru_RU.UTF8/0000755000175000017500000000000011727170645016762 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/ru_RU.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020547 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/ru_RU.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000005756611677423605022742 0ustar razraz 0#*%EkF/v $?FW \ f r$&  1R m     )+6b} ;Tl  ,- (8 X.y8   %2:@^{    " 8 E M ! !! 0!(S>*|>>>>>' ?#4?X??m?9?]?WE@7@@@ A!A 2AD?A<A(A=A)(B-RBB&B!B BB C &Cw4C!CCC C!D$D>D TD!uD4DDDD-E 4E UE%vEEE7EEE(CF"lFF$F%FF G G1G NG\G{GG*GGG G6H>H$XH}H HHHHJH ,I7IOI-iIII8I+J,4J3aJJ#J%J'JK:K8XK'KK6K6LGL0VLL$L6L"LM8-MRfMMM6M3NONgN}NNN3N0N !O)+O UOaO OOO O OOO#O(!PJP*aPUP=PH QQiQeQ]!RRySb!TT%T(TT U1$U7VU"UUUV@WX#X?XVX)jXX X X*X XY#Y3YCY-UYY Y5YY Y Y Y ZZ +Z 9ZGZ%[Z2ZZZZZ [[ /[;[W[s[[8[4[4\:\ N\*Z\*\ \\/\]+]F] b] p]+~]k] ^#^9?^By^9^2^>)_ h_Sc85U`>ojR_P6Y]1OqG@CrLie^thfM va*wKQJ!dz$kx2'glu<;I T{,B+%}0s:  /#E~( HD=9& V A4   y bZ)X.?|\pnFmW"3N[-7%d pages%s was unable to remove temp file for screenshot1 pageAll Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)Alternative index location:AttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot rename '%s' to '%s'Cannot save notebook preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose the current notebookCollapse A_ll Child NotesCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Different year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo you want to delete this note and all of its children?Do you want to delete this note?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledErrorError occurred during backup.Error reading meta data fileError saving notebookError while attaching file '%s'.Error while updating.Expand _All Child NotesExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _Screenshot...ItalicJustify AlignKeep aspect ratioKeepNote PreferencesLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify a filename for the image.NewNew IconNew NotebookNew _Child PageNew _FolderNew _PageNo Default NotebookNo WrappingNo _WrappingNo notes are selected.Notebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen an existing notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignS_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorSnap:Standard IconsStart a new notebookStrikeThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)UnderlineUpdating NotebookUpdating notebook...Use current notebookVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView _Error Log...Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]Year with century as a decimal numberYear without century as a decimal number [00,99]_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Ok_Open File_Open Notebook..._Preferences_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...delete iconheight:icon:open-icon:save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.6.1 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2010-01-06 22:26+0200 Last-Translator: hikiko mori Language-Team: Russian Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); %d страниц%s не смог удалить временный файл со скриншотом1 страницаВсе доступные значкиУсловные обозначенияДата и времяОбщие настройки KeepNoteУправление значкамиЗначки узловОбновление блокнотаБыстрый выбор значковЗапускПараметры данного блокнота_Просмотр изображения...замечание: резервирование и обновление больших блокнотов могут занять некоторое время.Символ "%"Применить цвет _фонаВсе файлы (*.*)Другой каталог для индекса:ПрикрепитьПрикрепить файл...Прикрепить файл к блокнотуАвтосохранение:Создание резервной копииСоздание резервной копии старого блокнотаРезервированиеРезервирование отмененоЖирныйОбзор...Ненумерованный списокВыровнять по _центруОтменаНевозможно скопировать файл '%s'Невозможно создать КорзинуНевозможно создать узелНевозможно найти блокнот '%s'Невозможно инициализировать richtext файл '%s'Невозможно открыть блокнот (слишком старая версия)Невозможно загрузить настройки блокнотаНевозможно переименовать '%s' в '%s'Невозможно сохранить настройки блокнотаНевозможно записать метаданныеС учетом _регистраВыровнять по центруВыберите %sУкажите имя резервного блокнотаВыберите блокнот по умолчаниюВыберите шрифтВыберите значокВыберите значок раскрытого узлаВ_ыбрать шрифтВыберите другой каталог для индекса блокнотаЗакрыть текущий блокнотСверну_ть все дочерниеНевозможно создать блокнотНевозможно очистить корзинуНе удалось вставить изображение '%s'Невозможно загрузить блокнот '%s'.Невозможно открыть журнал ошибокНевозможно сохранить изображение '%s'.Невозможно сохранитьСоздать новую дочернюю страницуСоздать новый каталогСоздать новую страницуСоздано '%s'Дата и времяДень месяца [01,31]День года [001,366]Блокнот по умолчанию:Шрифт по умолчанию:Другой год:Недостаточно прав для перемещенияНедостаточно прав для удаленияНедостаточно прав для чтения содержимого каталогаВы хотите удалить эту и все ее дочерние заметки?Вы хотите удалить эту заметку?Тест drag & dropР_азвернутьО_чистить корзинуВключеноОшибкаПри резервировании произошла ошибка.Невозможно загрузить метаданныеОшибка при сохраненииОшибка при прикреплении файла '%s'.Ошибка при обновлении.Разв_ернуть все дочерниеРасширенияОшибка формата файлаНайти далее в_верхНайти:Найти далее в_низНайти/ЗаменитьФо_рматЗдесь вы можете настроить формат вывода времени в разных случаяхПерейти по ссы_лке_СледующаяРе_дакторСп_исокПерейти к _заметке_Родительская_ПредыдущаяДерево катало_говВнешние программыНе показывать в панели задачпо горизонталиЧас [01,12]Час [00,23]У_величить размер шрифтаУмен_ьшить отступУвели_чить отступИндексация блокнотаИндексация...ВставитьВставить изображение из файлаВставить _горизонтальный разделительВставить _изображениеВставить _скриншотНаклонныйВыровнять по ширинеСохранять пропорцииПараметры KeepNoteЯзыкЯзык:Выровнять по левой стороне_СсылкаРазметка списка:Загружен '%s'Месяц сокращенноДень недели сокращенноДата и времяДатаВремяЛокальный эквивалент AM либо PMМесяц целикомДень недели целикомВнешний видСсылкаМинуты [00,59]МоноширинныйМесяц [01,12]Нужно указать имя файла для изображения.НовыйНовый значокНовый блокнотНовая _дочерняя страницаНовый _каталогНовая с_траницаБлокнот по умолчанию не выбранНе переносить по словамН_е переносить по словамНи одна заметка не выделена.Блокнот (*.nbk)Параметры блокнотаОбновление блокнотаОбновление завершеноБлокнот закрытБлокнот измененНастройки блокнота поврежденыБлокнот перезагруженБлокнот сохраненОбновление выполнено успешноПерсональные значки блокнотаОткрытьОткрыть последний блокнотОткрытьПосл_едние открытыеОткрыть существующий блокнотВставить как текстЗакрыть KeepNoteПерезагрузить текущий блокнотПерезагрузить можно только открытый блокнотЗаменить:Заменить _всеИзменить размеры изображенияВыровнять по правой стороне_ЗачеркнутыйТот же день:Тот же месяц:Тот же год:СохранитьСохранить изображение как...Сохранить текущий блокнотВни_зПоиск по всем заметкамВвер_хПоиск в блокнотеПоиск...Секунды [00,61]Цвет фонаШрифтРазмерЦвет текстаВыравнивать:Стандартные значкиСоздать новый блокнотЗачеркнутыйКорзину нельзя удалитьКорзина должна быть каталогом верхнего уровняОшибка при создании скриншота : %sКаталог верхнего уровня нельзя удалитьЭтот узел не закреплен ни за одним блокнотомЭто блокнот версии %d. Необходимо обновиться до версии %dЭто блокнот версии 1. Его нужно обновить до версии 2.Параметры в этом разделе привязаны к конкретному блокноту. Каждому открытому блокноту невозбранно поставлен в соответствие подразделВаш %s не может открыть этот блокнот. Версия блокнота - %d. %s может работать только с версией %d.Часовой пояс (пустая строка, если зона не установлена)ПодчеркнутыйОбновление блокнотаОбновление блокнота...Вставить текущийпо вертикалиОткрыть в менеджере файловОткрыть в текстовом редактореОткрыть в браузере_Журнал ошибокНомер недели в году (понедельник - первый день недели) [00,53]. Все дни нового года перед первым понедельником включаются в неделю с номером 0Номер недели в году (воскресенье - первый день недели) [00,53]. Все дни нового года перед первым воскресеньем включаются в неделю с номером 0День недели числом [0(Воскресенье),6]Год целикомГод сокращенно [00,99]_О программе_ПрименитьПрименить цвет _текста_Прикрепить файл_Назад_ЖирныйНен_умерованный список_Отмена_Сменить значокЗа_крыть_ЗакрытьСверн_утьУменьш_ить размер шрифта_Удалить_Правка_Редактировать изображение..._Экспорт_ФайлН_айтиН_айти_Вперед_НавигацияПомо_щь_Импорт_НаклонныйВыровнять по _ширинеВыровнять по _левой стороне_МоноширинныйНовый _блокнот..._ОКОткрыть _файл_Открыть_НастройкиВ_ыход_Перезагрузить_Переименовать_Заменить_Заменить_Изменить размер изображения...Выровнять по _правой стороне_Сохранить изображение как..._СохранитьП_оиск_Поиск по всем заметкам_Проверка правописания_СервисПо_дчеркнутый_Обновить индекс блокнотаПрос_мотр_Просмотр изображения...удалить значоквысота:значок:значок раскрытого узла:создать резервную копию перед обновлением (рекомендуется)секундвыбрать значоквыбрать значок раскрытого узлапоказывать линии в дереве каталоговзначки GTK в панели инструментовкраткий набор инструментовподсветка строк списка через однуширина:keepnote-0.7.8/keepnote/rc/locale/ja_JP.UTF8/0000755000175000017500000000000011727170645016711 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/ja_JP.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020476 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/ja_JP.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000006610011677423605022651 0ustar razrazn   0"SVY \gnq% $ < K ` Fv    !!!%! C!M!a!|!!! ! ! !!!!!"$""&G" n"" """" # #!#=# U# a#m# ~#+# # # ## #$ $($9$X$o$$$$$$%"% 4% A%,O%-|%% % %%% &.'&2V&8& &*&,';' Q' ^'k's'''P'(( 5(V(v(( ("(( ()) 3)>) T)a) i)u) )) )))))) *0*0L*}* * ** *****+%+;+N+d+w+ ~+++++++ , ,,, /, ;,F,!f,1,(,(,& -3-L- g- u--"- -!-0-&.8.<.E. N. X.e. n.y. . ... . ...//#/3/L/\/#n/////// 00%040F0`0q0 00-0 0 0 0 11(1 01 :1 F1Q1V1g11111 1"11 2 2(272=2L2a2h2{2#2,2C20'3'X3%3U3T3Q4`45]55 575+ 6 76A6 R6\6q666666667/7H7[7)s77%707=798@8G8Y8i8o8 u888888888889 99!9*9.949E9M9 \9 h9s9 99 99 9 999999: :$:6:E:M: _:l: s:~:::: ::::: ; ;);D;L; U;c;z;;;;c; 4='U=}================ ====W=>>!> $> 1>=>+@>l> >+>>>.>)?H?e???5@L@h@@@@*@@-@3AQA6dAA A AAA-A!B!*B3LBBBKB3C(CC3lCC'CC D DN!D-pDDD!DD<ECE_EvE'EE+EEF<)F$fF-F<F*F0!G6RGGGGGG'G)'H'QH!yHHHHHEHIBIBI'I6I<.J0kJJJ J(J K9KPKNK?,L@lL@L9L+(M9TM?M'M M'N"+NNN"jN N NNNNNO+OEObO$OO O/O/O"-PPPmP$PPP!P"PQ%Q(BQkQ*Q'QQ QQ R+RQ>RR R R R$RR R S%S'ASiSS-SSSTT!T7TVT]T`|T?TU$U7U JUWUpUUUUUU-UV*V*AV-lVV$V'V<W*?W*jW<W3W*X<1X'nXX-XX.XY,Y$KYpY-YY-YHYGZcZzZ Z/ZZ Z Z ZZ[$[A[U[q[[ [[[![\$\7\G\!Z\ |\\\0\W\oJ]R]9 ^HG^^#__`Bsa-a6aTbHpbbbb-b! c'Bc-jc$c*c c-c- d*Ndydd3d+de'(e/Pexe e ff&f Cf NfYfjf~f f"ff"f g g%gk!Tk!vkAk*k3l9l "-m!l]1pXF diOvf@'N-XZz;6,`7gU^GA< Yk3b&Pm&/P/:Qet.'K}? 8c 7h#0s+?[(M*H=N;hB2a6anV%%`>$n4[1r\w])*U9I>uTfQSF! D#k0_by_"J4 c.xO,:MBRY8e+\<gdGEIDT95LJ|CHl5)@Lqj{~W(3VS=C^ojKZR  E2iW$A failed dependency: %s skipping extension '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d pages%j%m%p%s was unable to remove temp file for screenshot%w%x%y(Untitled)1 page50All Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A_pply Background ColorAll files (*.*)Alternative index location:Always on topAttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot rename '%s' to '%s'Cannot save notebook preferencesCannot save preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose WindowClose _TabClose a tabClose the current notebookClose windowCollapse A_ll Child NotesCopy _TreeCopy entire treeCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Delete NoteDifferent year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo not have permission to read folder contents: %sDo you want to delete this note and all of its children?Do you want to delete this note?Do you want to install the extension "%s"?Do you want to uninstall the extension "%s"?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledEnabling new extensions: ErrorError occurred during backup.Error occurred while opening file with %s. program: '%s' file: '%s' error: %sError reading meta data fileError saving notebookError while attaching file '%s'.Error while attaching files %s.Error while updating.Expand _All Child NotesExtension "%s" is now installed.Extension "%s" is now uninstalled.Extension UninstallExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatGo to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _New Image...Insert _Screenshot...Insert a new imageInstall New ExtensionInstall SuccessfulItalicJustify AlignKeep aspect ratioKeepNote Extension (*.kne)KeepNote PreferencesKeepNote can only uninstall user extensionsLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Loading...Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinimize on startMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify '%s' program in Helper ApplicationsMust specify a filename for the image.NewNew FileNew IconNew ImageNew NotebookNew PageNew WindowNew _Child PageNew _FolderNew _PageNew _TabNo Default NotebookNo WrappingNo _WrappingNo notes are selected.No version tag foundNotebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen a new tabOpen a new windowOpen an existing notebookOpening notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignRoot tag is not 'node'S_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorSnap:Standard IconsStart a new notebookStrikeSwitch to next tabSwitch to previous tabThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program did not create the necessary image file '%s'The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Unable to determine note type.Unable to install extension '%s'Unable to uninstall extension. Do not have permission.Unable to uninstall unknown extension '%s'.UnderlineUnexpected errorUninstallUninstall SuccessfulUnknown version stringUpdating NotebookUpdating notebook...Use current notebookUse system tray iconVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView Preference Files...View _Error Log...Visible on all desktopsWeekday as a decimal number [0(Sunday),6]WindowYear with century as a decimal numberYear without century as a decimal number [00,99]You must specify a Screen Shot program in Application Options_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Next Tab_Ok_Open File_Open Notebook..._Preferences_Previous Tab_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...command '%s' already existsdelete iconenabling extension '%s' format:height:icon:open-icon:removing '%s'save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.6.7 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2010-12-28 20:45+0900 Last-Translator: Toshiharu Kudoh Language-Team: Japanese Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; 依存関係の欠損: %s 機能拡張 '%s' をスキップ: %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d ページ%j%m%p%s はスクリーンショットの一時ファイルを削除できませんでした%w%x%y(無題)1 ページ50利用可能な全てのアイコン日付の書式キー日付KeepNote の一般的オプション管理アイコンノードアイコンノートブックのアップデートQuick pick アイコンスタートアップこのノートブック画像を表示(_V)...注意:巨大なノートブックのバックアップとアップグレードはしばらく時間がかかるかも知れません。背景色を適用(_P)全てのファイル (*.*)代替索引の位置:常にトップに添付ファイルを添付...ノートブックにファイルを添付自動保存:ノートブックをバックアップする古いノートブックをバックアップ中...バックアップバックアップがキャンセルされました。太字参照...箇条書き中央配置(_E)キャンセルファイル '%s' をコピーできませんゴミ箱を作成できませんノードを作成できませんノートブック '%s' を見つけられませんリッチテキストファイル '%s' を初期化できませんノートブックを開けません(バージョンが古すぎます)ノートブックの設定を読み込めません'%s' から '%s'へ改名できませんノートブックの設定を保存できません設定を保存できませんメタデータを書き込めません大文字小文字の区別(_S)中央配置%s を選択バックアップするノートブックの名称を選択してくださいデフォルトのノートブックを選択フォントを選択アイコンを選択オープンアイコンを選択フォントを選択(_F)代替のノートブック索引ディレクトリを選択ウィンドウを閉じるタブを閉じる(_T)タブを閉じる現在のノートブックを閉じるウィンドウを閉じるすべての子ノートを折り畳む(_L)ツリーを複製(_T)ツリー全体を複製新規ノートブックを作成できませんでした。ゴミ箱を空にできません。画像 '%s' を挿入できませんでしたノートブック '%s' を読み込めませんでした。エラーログを開けませんでした画像 '%s' を保存できませんでした。ノートブックを保存できませんでした。新規子ページを作成新規フォルダを作成新規ページを作成'%s' が作成されました日付月の日数を十進数で [01,31]年の日数を十進数で [001,366]デフォルトのノートブック:デフォルトのフォント:ノートを削除異なる年:移動権限がありません削除権限がありませんフォルダコンテンツを読むための権限がありませんフォルダコンテンツ %s を読むための権限がありませんこのノートとすべての子ノートを削除しますか?このノートを削除しますか?機能拡張 "%s" をインストールしますか?機能拡張 "%s" をアンインストールしますか?ドラッグアンドドロップのテスト...ノートを展開する(_X)ゴミ箱を空にする(_T)有効化済新しい機能拡張を有効化中: エラーバックアップ中にエラーが発生しました。ファイルを %s で開いている間にエラーが発生しました。 プログラム: '%s' ファイル: '%s' エラー: %sメタデータファイルを読み込み中にエラーが発生しましたノートブックの保存中にエラーが発生しましたファイル %s を添付中にエラーが発生しました。ファイル %s を添付中にエラーが発生しました。アップデート中にエラーが発生しました。すべての子ノートを展開する(_A)機能拡張 "%s" がインストールされました。機能拡張 "%s" がアンインストールされました。機能拡張のアンインストール機能拡張ファイルフォーマットエラーページ内で前を検索(_V)...検索するテキスト:ページ内で次を検索(_N)...検索/置換書式(_R)リンクへ移動(_K)次のノートに移動(_O)エディタへ移動(_E)リストビューへ移動(_L)ノートに移動(_N)親ノートに移動(_P)前のノートに移動(_P)ツリービューへ移動(_T)ヘルパーアプリケーションタスクバーから隠す横方向時間を十進数で(12時制) [01,12]時間を十進数で(24時制) [00,23]フォントサイズを増加(_S)インデントを削除(_S)インデントを追加(_O)ノートブックの索引生成中索引生成中...挿入ファイルから画像を挿入横方向のルールを挿入(_H)画像を挿入(_I)...新規画像を挿入(_N)...スクリーンショット挿入(_S)...新規画像を挿入新たな機能拡張をインストールインストールが成功しましたイタリック均等配置アスペクト比を維持KeepNote 機能拡張 (*.kne)KeepNote の設定KeepNote はユーザー機能拡張だけをアンインストールできます言語言語:左寄せリンク(_K)リストビューレイアウト:'%s' を読み込みました読込中...地域の省略した月名地域の省略した週名地域の適切な日付と時刻表現地域の適切な日付表現地域の適切な時刻表現地域の午前・午後に相当する語句地域の完全な月名地域の完全な週名外観リンクを作成起動時に最小化分を十進数で [00,59]等幅月を十進数で [00,12]ヘルパーアプリケーションで '%s' プログラムを指定する必要があります画像のファイル名を指定する必要があります。新規新規ファイル新規アイコン新規画像新規ノートブック新規ページ新規ウィンドウ新規子ページ(_C)新規フォルダ(_F)新規ページ(_P)新規タブ(_T)デフォルトではないノートブック折り返しなし折り返しなし(_W)ノートが選択されていません。バージョンタグが見つかりませんノートブック (*.nbk)ノートブックのオプションノートブックのアップデートノートブックのアップデートが完了しましたノートブックが閉じられましたノートブックが修正されましたノートブックの設定データが破損していますノートブックが再読み込みされましたノートブックが保存されましたノートブックのアップデートが成功しましたノートブック固有のアイコン開く最後に開いたノートブックを開くノートブックを開く最近使ったノートブックを開く(_C)新規タブを開く新規ウィンドウを開く既存のノートブックを開くノートブックを展開中プレーンテキストとして貼り付けKeepNote を終了現在のノートブックを再読み込み再読み込みはノートブックが開いている間のみです。置換するテキスト:すべてを置換(_A)画像をリサイズ右寄せルートタグが 'node' ではありません打ち消し(_T)同日:同月:同年:保存画像を別名で保存...現在のノートブックを保存前方を検索(_R)全てのノートを検索後方を検索(_B)ノートブックを検索中検索中...秒を十進数で [00,61]背景色を設定フォントフェイスを設定フォントサイズを設定文字色を設定スナップ:標準アイコン新規ノートブックを開始打ち消し次のタブへ切り替え前のタブへ切り替えゴミ箱フォルダは削除できません。ゴミ箱フォルダはトップレベルフォルダでなければなりません。スクリーンショットプログラムは必要な画像ファイル '%s' を生成できませんでしたスクリーンショットプログラムはエラーに遭遇しました: %sトップレベルフォルダは削除できません。このノードはどのノートブックの一部でもありませんこのノートブックのバージョンは %d なので、開く前にバージョン %d へアップデートしなくてはなりません。このノートブックはフォーマットバージョン1であり、開く前にバージョン2にアップデートしなくてはなりません。このセクションでは、保存されているノートブック毎の基本的なオプション(例えばノートブック固有のフォント)を含みます。細目は、現在開いている各ノートブック毎に現れます。このバージョンの %s ではこのノートブックは読み込めません。 このノートブックのバージョンは %d です。 %s は %d でのみ読み込めます。時間帯の名称(時間帯が存在しないなら無記入)ノートの形式を決定できません。機能拡張 '%s' をインストールできません機能拡張をアンインストールできません。権限がありません。不明な機能拡張 '%s' をアンインストールできません。下線予期しないエラーアンインストールアンインストールが成功しました不明なバージョン文字列ノートブックをアップデートノートブックをアップデート中...現在のノートブックを使うシステムトレイアイコンを使う縦方向ノートをファイルブラウザで表示ノートをテキストエディタで表示ノートをウェブブラウザで表示設定ファイルを表示...エラーログを表示...(_E)すべてのデスクトップで見えるように曜日を十進数で [0(日曜),6]ウィンドウ上二桁付きの西暦を十進数で上二桁なしの西暦を十進数で [00,99]アプリケーションオプションでスクリーンショットプログラムを指定する必要があります情報(_A)適用(_A)文字色を適用(_A)ファイルを添付(_A)...戻る(_B)太字(_B)箇条書き(_B)キャンセル(_C)ノートアイコン変更(_C)閉じる(_C)ノートブックを閉じる(_C)ノートを折り畳む(_C)フォントサイズを減少(_D)削除(_D)編集(_E)画像を編集(_E)...ノートブックを出力(_E)ファイル(_F)検索(_F)ページを検索(_F)...進む(_F)移動(_G)ヘルプ(_H)ノートブックに取り込む(_I)イタリック(_I)均等配置(_J)左寄せ(_L)等幅(_M)新規ノートブック(_N)...次のタブ(_N)Ok(_O)ファイルを開く(_O)ノートブックを開く(_O)...設定(_P)前のタブ(_P)終了(_Q)ノートブックの再読み込み(_R)リネーム(_R)置換(_R)ページ内で置換(_R)...画像をリサイズ(_R)...右寄せ(_R)画像を別名で保存(_S)...ノートブックを保存(_S)検索(_S)全てのノートを検索(_S)スペルチェック(_S)ツール(_T)下線(_U)ノートブックの索引をアップデート(_U)表示(_V)画像を表示(_V)...コマンド '%s' はすでに存在していますアイコンを消去機能拡張 '%s' を有効化中 書式:高さ:アイコン:オープンアイコン:'%s' を削除中アップデートの前にバックアップを取得(推奨)秒アイコンを設定オープンアイコンを設定ツリービューで線を表示ツールバーに GTK のストックアイコンを使用する最小限のツールバーを使用するリストビューでルーラーヒントを使用幅:keepnote-0.7.8/keepnote/rc/locale/es_ES.UTF8/0000755000175000017500000000000011727170645016724 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/es_ES.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020511 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/es_ES.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000006216511677423605022673 0ustar razrazn   0"SVY \gnq% $ < K ` Fv     !'!.!=! [!e!y!!!! ! ! !!!! " "$:"&_" "" """# "# /#9#U# m# y## #+# # ##$ $&$7$V$m$$$$$$ % % 2% ?%,M%-z%% % %%% &.%&2T&8& &*&, '9' O' \'i'q'''P'(( 3(T(t(( ("(( ()) 1)<) R)_)g) ** +*9* J*V*i*~*** *0*0*"+ 6+ C+P+ b+n+u+++++++ , ,,0,K,+`,, , ,,, , ,,!,1-(L-(u-&--- - .."#. F.!P.0r.&.... . .. / / / '/1/:/ N/ Z/g/~///////#0$060E0c0{00 0000001 1%1-A1 o1 }1 1 111 1 1 11112#242F2 Y2"f22 2 2222222# 3,13C^303'3%3U!4Tw44`w555 67/6+g6 66 66666 7 7)7D7]7v7777h8)9%@90f9=99999: : ::&:8:?:O:^:r:z:::::::::::: : ;; ;*; .;9; K; X;f;l;};;;; ;;;;; ;< <<1<7<F< b<n<< <<<<< < <)<== =%=<=[=o===0?N?l?o?r?u?x?{?~?????????? ????????? ? @@$@-:@h@%|@@@@$@ A*ATLT2]TTTTTTT UU/UHU`UyUUUU7U VV0VEV[VzVVVVVVVVV W!W 6W%BWhWwWWWWWWWWX4$XQYX0X)X$Y[+YQYYiZBZ'B[4j[;[ [[ [\!\4\J\#\\\"\\\\\ ]7]]2^)^+_U<_ _______ _`` `&`5` O`Y`a`s```` ``````` a$a4aHaLa[a kayaaa a aaaabb+b3bNb lb zbbbbbbbb ccc.c6c >cKc;[cccc"c/c'd+Bdnd*l!k\.oU5dCa cLhPue=$K.WWy<3-m]8fR]DB=" Vk0a&M+,Q07Nes/'L|< 9b 4h#-r(@X%J'I>O8gC/^7`nS%#_?$m1Z2q[vZ&*I:J;!tTfPG Aj 1\bx^ "K5c+w`~);N?OXd,Y9gHBFEQ66MG{DEl2)Apiz}_V(4US:@[nHjYR F3iT > failed dependency: %s skipping extension '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d pages%j%m%p%s was unable to remove temp file for screenshot%w%x%y(Untitled)1 page50All Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)Alternative index location:Always on topAttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot rename '%s' to '%s'Cannot save notebook preferencesCannot save preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose _TabClose a tabClose the current notebookCollapse A_ll Child NotesCopy _TreeCopy entire treeCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Delete NoteDifferent year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo not have permission to read folder contents: %sDo you want to delete this note and all of its children?Do you want to delete this note?Do you want to install the extension "%s"?Do you want to uninstall the extension "%s"?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledEnabling new extensions: ErrorError occurred during backup.Error occurred while opening file with %s. program: '%s' file: '%s' error: %sError reading meta data fileError saving notebookError while attaching file '%s'.Error while attaching files %s.Error while updating.Expand _All Child NotesExtension "%s" is now installed.Extension "%s" is now uninstalled.Extension UninstallExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _New Image...Insert _Screenshot...Insert a new imageInstall New ExtensionItalicJustify AlignKeep aspect ratioKeepNote Extension (*.kne)KeepNote PreferencesKeepNote can only uninstall user extensionsLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Loading...Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinimize on startMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify '%s' program in Helper ApplicationsMust specify a filename for the image.NewNew FileNew IconNew ImageNew NotebookNew PageNew WindowNew _Child PageNew _FolderNew _PageNew _TabNo Default NotebookNo WrappingNo _WrappingNo notes are selected.No version tag foundNotebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen a new tabOpen a new windowOpen an existing notebookOpening notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignRoot tag is not 'node'S_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorSnap:Standard IconsStart a new notebookStrikeSwitch to next tabThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program did not create the necessary image file '%s'The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Unable to install extension '%s'Unable to uninstall extension. Do not have permission.Unable to uninstall unknown extension '%s'.UnderlineUnexpected errorUninstallUnknown version stringUpdating NotebookUpdating notebook...Use current notebookUse system tray iconVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView Preference Files...View _Error Log...Visible on all desktopsWeek number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]Year with century as a decimal numberYear without century as a decimal number [00,99]You must specify a Screen Shot program in Application Options_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Next Tab_Ok_Open File_Open Notebook..._Preferences_Previous Tab_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...command '%s' already existsdelete iconenabling extension '%s' format:gtk-cancelgtk-okgtk-revert-to-savedheight:icon:open-icon:removing '%s'save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.5.3 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2010-12-18 19:43+0100 Last-Translator: Gaspar Fernández Language-Team: Spanish Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Generator: Lokalize 1.0 falla de dependencia: %s omitiendo extensión '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d páginas%j%m%p%s no pudo remover archivo temporal para la captura de pantalla%w%x%y(Sin título)1 página50Todos los íconos disponiblesClave de formatéo de Fecha & horaFecha y HoraOpciones generales de KeepNoteGestionar íconosÍconos de los nodosActualización del libroSelección rápida de íconosInicioEste libro_Ver imágen...observación: copia de respaldo y actualización puede tomar algún tiempo para libros largos.Caracter literal "%"Aplicar color de fondoTodos los archivos (*.*)Locación alternativa del índice :Siempre arribaAdjuntarAdjuntar un archivo...Adjuntar archivo al libroAutoguardar:Realizando copia de respaldo del libroRealizando copia de respaldo del libro antiguo...Copia de respaldoCopia de respaldo cancelada.NegritaBuscar...Lista de viñetasAlineación _centradaCancelarImposible copiar archivo '%s' Imposible crear carpeta de papeleraNo posible crear nodoLibro '%s' no encontradoImposible inicializar archivo de texto enriquezido '%s'No posible abrir libro (versión muy antigua)No posible leer la configuración del libroImposible renombrar '%s' a '%s'Imposible guardar la configuración del libroNo es posible salvar la configuraciónImposible guardar metadataRespetar mayusculas/mínusculasAlineación centradaElegir %sElija un nombre para la copia de respaldo del libroEligir libro predeterminadoElegir FuenteElegir íconoElegir ícono abiertoElegir FuenteSeleccionar directorio alternativo para el índice del libroCerrar _PestañaCerrar una pestañaCerrar el libro actualColapsar todas las notas hijoCopiar Ár_bolCopiar árbol completoNo posible crear nuevo libro.No posible vaciar la papelera.No pisble insertar imagen '%s'No posible abrir libro '%s'.No posible abrir log de erroresNo posible guardar imagen '%s'.No posible guardar el libroCrear una nueva página hijoCrear una nueva carpetaCrear una nueva página'%s' creadoFecha y horaDia del mes como número decimal [01,31]Dia del año como número decimal [001,366]Libro prederminado :Fuente prederminada :Eliminar NotaAño diferente :No tiene permisos para moverNo tiene permisos para eliminarNo tiene permisos para leer contenido de directorioNo tengo permiso para leer los contenidos de la carpeta: %sDesea eliminar ésta nota y todos sus hijos ?Desea eliminar ésta nota ?¿Desea instalar la extensión "%s"?¿Desea desinstalar la extensión "%s"?Prueba de arrastrar y soltar...E_xpandir nota_Vaciar PapeleraHabilitadoActivando nuevas extensiones: ErrorError durante la copia de respaldo.Ocurrió un error al abrir el archivo con %s. programa: '%s' archivo: '%s' error: %sFallo al leer el fichero de meta datosError al guardar el libroError al adjuntar archivo '%s'.Error adjuntando los archivos %s.Error durante la actualización.Expandir _todas las notas hijoLa extensión "%s" está instalada.La extensión "%s" está desinstaladaDesinstalar extensiónExtensionesError de formato de archivoBuscar _anterior en página...Buscar texto :Buscar _siguiente en página...Buscar/ReemplazarFo_rmatearAqui puede personalizar el formato de marca de tiempo en la vista de modo listado para 4 escenarios diferentes (p.ej. como mostrar la fecha de hoy, mes actual, año actual)Ir a hiperenlaceIr a nota _siguienteIr al editorIr a vista de listaIr a _notaIr a nota _padreIr a nota anteriorIr a vista de arbolProgramas auxiliaresOcultar de barra de tareasHorizontalHora (12 horas) como número decimal [01,12]Hora (24 horas) como número decimal [00,23]Aumentar tamaño de fuenteReducir indentaciónAumentar indentaciónIndexando libroIndexando...InsertarInsertar imágen desde archivoInsertar línea _horizontalInsertar _Imagen...Insertar _Nueva imagen...Insertar captura de pantalla...Insertar nueva imagenInstalar Nueva ExtensiónCursivoAlineación justificadaMantener proporcionesExtensión de KeepNote (*.kne)Preferencias de KeepNoteKeepNote sólo puede desinstalar extensiones de usuarioIdiomaIdioma:Alineación a la izquierda_HipervínculoEstilo de visualización de lista:'%s' abiertoCargando...Nombre del mes abreviado y localizadoNombre del día de semana abreviado y localizadoRepresentación localizada de fecha y horaRepresentación localizada de fechaRepresentación localizada de la horaIndicador local para equivalente de AM y PM (sistema de reloj de 12 horas)Nombre del mes completo y localizadoNombre del día de semana completo y localizadoAspecto y comportamientoCrear hipervínculoMinimizar al inicioMinutos como número decimal [00,59]Espaciado fijoMes como número decimal [01,12]Debe especificar el programa '%s' en Aplicaciones AuxiliaresDebe indicar un nombre de archivo de la imágen.NuevoNuevo archivoNuevo ÍconoNueva ImagenNuevo libroNueva páginaNueva VentanaNueva página _inferiorNueva _CarpetaNueva _PáginaNueva _PestañaNo hay libro predeterminadoSin salto de línea automático_SIn salto de línea automáticaNo hay notas seleccionadas.No se encuentra la etiqueta de la versiónLibro (*.nbk)Opciones del libroActualización de libroActualización del libro finalizadoLibro cerradoLibro modificadoDatos de configuración del libro están corruptosLibro recargadoLibro guardadoLibro actualizado exitosamenteÍconos específicos del libroAbrirAbrir libro más recienteAbrir libroAbrir Libro re_cienteAbrir una nueva pestañaAbrir una nueva ventanaAbrir un libro existenteAbriendo cuadernoPegar como texto planoSalir de KeepNoteVolver a cargar el libro actualSolamente se puede recargar cuando hay un libro abiertoReemplazar texto :Reemplazar todosRedimensionar imagenAlineado a la derechaLa etiqueta raíz no es 'node'_TachadoEl mismo día :El mismo mes :El mismo año :GuardarGuardar imagen como...Guardar el libro actualBuscar adelanteBuscar en todas las notasBuscar atrásBuscando en el libroBuscando...Segundos como número decimal [00,61]Color de fondoFamilia de fuenteTamaño de fuenteColor de textoPuntos :Íconos estándarComenzar un nuevo libroTachadoCambiar a la pestaña siguienteNo posible vaciar la papelera.La papelera debe ser una carpeta de nivel más alto.El programa de captura de pantalla no generó el fichero de imagen necesario '%s'Error en el programa de captura de pantalla: %sNo posible eliminar la carpeta principal.El nodo no es parte de ningún libroEl libro está guardado con la versión %d y se debe convertir a la versión %d para abrir.Éste libro tiene versión 1 y se debe actualizar a la versión 2 antes de abrir.La sección actual contiene opciones que se guardan con el libro (p.ej. una fuente específica para éste libro). Aparecen subsecciones para cada libro que está abierto en este momento.La versión actual %s no puede abrir el libro. La versión del libro es %d. %s solamente puede abrir %d.Nombre de zona horaria (dejar en blanco si no existe zona horaria)No se puede instalar la extensión '%s'No se puede desinstalar extensión. No tengo permisoNo se ha podido desinstalar la extensión desconocida '%s'.SubrayadoError inesperadoDesinstalarCadena de versión desconocidaActualizando LibroActualizando libro...Usar libro actualUsar icono en la bandeja de sistemaVerticalVer nota en explorador de archivosVer nota en editor de textoVer nota en navegador webVer ficheros de preferenciasVer log de _errores...Visible en todos los escritoriosNúmero de semana del año (tomando lunes como primer día de la semana) como número decimal [00,53]. Todos los días antes del primer lunes del año se consideran parte de la semana 0Número de semana del año (tomando domingo como primer día de la semana) como número decimal [00,53]. Todos los días antes del primer domingo del año se consideran parte de la semana 0Día de semana como número decimal [0(domingo),6]Año completo como número decimal (1970)Año sin siglo como número decimal [00,99]Debe especificar un programa de captura de pantalla en las opciones de la aplicación_Acerca de_AplicarAplicar color de texto_Adjuntar archivo...A_trás_Negrita_Lista de viñetas_Cancelar_Cambiar ícono_Cerrar_Cerrar Libro_Colapsar notaReducir tamaño de fuente_Eliminar_Editar_Editar imagen..._Exportar Libro_Archivo_Buscar_Buscar en página..._Adelante_Ir a_Ayuda_Importar Libro_CursivoAlineación _justificadaAlineado a la _izquierda_Fuente de espacio fijo_Nuevo Libro...Pestaña _siguiente_Ok_Abrir archivo_Abrir Libro..._Preferencias_Pestaña anterior_Salir_Recargar Libro_Renombrar_Reemplazar_Reemplazar en página..._Redimensionar imagen...Alineado a la _derecha_Guardar imagen como..._Guardar Libro_Buscar_Buscar en todas las notas_Comprobación de ortografía_Herramientas_Subrayado_Actualizar índice del libro_Ver_Ver imágen...El comando '%s' ya existeeliminar íconohabilitando extensión '%s' formato:gtk-cancelgtk-okgtk-revert-to-savedaltura:ícono:open-icon : eliminando '%s'guardar copia de respaldo antes de actualizar (recomendado)segundosestablecer íconoestablecer open-iconmostrar líneas en vista de árbolusar íconos stock GTK en barra de herramientasusar barra de herramientas mínimalistamostar indicador de regla en vista de listaancho:keepnote-0.7.8/keepnote/rc/locale/sv_SE.UTF8/0000755000175000017500000000000011727170645016745 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/sv_SE.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020532 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/sv_SE.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000006020411677423605022704 0ustar razraz] 89Tp0y %2FXoF 8H dr    ) 7 > T o  $ & # !-! H!i!!! ! !!! ! ! " "+)" U" b" m"y" "" """" #)#G#`#{#### # #,#-$H$ Z$ h$t$$ $.$2$8'% `%*%,%% % % &&+&1&D&Pb&&& &'''=' U'"v'' ''' '' ((( (( (( ( ))1)B)V) h)0s)0)) ) )* *!*(*?*W*h*}***** ***++&+R+ [+ e+p+v+ + ++!+1+(,(;,&d,,, , ,,", -!-08-&i---- - -- -- - --. . .-.D.Y.j.{....#... /)/A/F/ Y/g/}///// //-0 50 C0 P0 ]0i00 0 0 0000000 1 1",1O1 d1 r1111111#1,2C520y2'2%2U2TN33`N4544 57%5+]5 55 5555566+646O6h66666s7)!8K8%R80x8=8888999 #90989J9Q9a9p99999999999999 : :!: 2:<: @:K: ]: j:x:~::::: ::::: ;; !;,;C;I;X; t;;;;; ; ;);;; <<(<G<[<w<~< >&>F>:O> >> >&>>)>&?7VO*6{%$= 4 _IAa }P .~U;3I'$+L0:kDNH9|(\^Q&(MXju]rR!mQlG5:G# "-8BNE\Fw)/?gS]bdn@D,WoMZ@7S3/&AHRK?zOCt<052 "628Upy 4cJ>.%x,hV1TiKsLFZ!EXY`P#*;<+-C [' qYf[ T9= WJv)B e1 failed dependency: %s skipping extension '%s': %d pages%s was unable to remove temp file for screenshot(Untitled)1 pageAll Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)Alternative index location:Always on topApplication Font Size:AttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot read notebook preferences %sCannot rename '%s' to '%s'Cannot save notebook preferencesCannot save preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose WindowClose _TabClose a tabClose the current notebookClose windowCollapse A_ll Child NotesCopy _TreeCopy entire treeCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Delete NoteDifferent year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo not have permission to read folder contents: %sDo you want to delete this note and all of its children?Do you want to delete this note?Do you want to install the extension "%s"?Do you want to uninstall the extension "%s"?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledEnabling new extensions: ErrorError during indexError occurred during backup.Error occurred while opening file with %s. program: '%s' file: '%s' error: %sError reading meta data fileError saving notebookError while attaching file '%s'.Error while attaching files %s.Error while updating.Expand _All Child NotesExtension "%s" is now installed.Extension "%s" is now uninstalled.Extension UninstallExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _New Image...Insert _Screenshot...Insert a new imageInstall New ExtensionInstall SuccessfulItalicJustify AlignKeep aspect ratioKeepNote Extension (*.kne)KeepNote PreferencesKeepNote can only uninstall user extensionsLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Loading...Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinimize on startMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify '%s' program in Helper ApplicationsMust specify a filename for the image.NewNew FileNew IconNew ImageNew NotebookNew PageNew WindowNew _Child PageNew _FolderNew _PageNew _TabNo Default NotebookNo WrappingNo _WrappingNo notes are selected.No version tag foundNotebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen a new tabOpen a new windowOpen an existing notebookOpening notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignRoot tag is not 'node'S_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorStandard IconsStart a new notebookStrikeSwitch to next tabSwitch to previous tabThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program did not create the necessary image file '%s'The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Unable to determine note type.Unable to install extension '%s'Unable to uninstall extension. Do not have permission.Unable to uninstall unknown extension '%s'.UnderlineUnexpected errorUninstallUninstall SuccessfulUnknown version stringUpdating NotebookUpdating notebook...Use current notebookUse system tray iconVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView Preference Files...View _Error Log...Visible on all desktopsWeek number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]WindowYear with century as a decimal numberYear without century as a decimal number [00,99]You must specify a Screen Shot program in Application Options_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Next Tab_Ok_Open File_Open Notebook..._Preferences_Previous Tab_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...command '%s' already existsdelete iconenabling extension '%s' format:height:icon:open-icon:removing '%s'save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.7.1 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2011-04-29 23:11+0200 Last-Translator: Morgan Antonsson Language-Team: Swedish Language: sv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); felaktigt beroende: %s hoppar över utökning '%s': %d sidor% misslyckades att ta bort tillfällig fil för skärmbild(Saknar namn)1 sidaAlla tillgängliga ikonerDatum- & tidsformatsnycklarDatum och tidAllmänna alternativ för KeepNoteHantera ikonerNodikonerUppdater anteckningsblockSnabbvalsikonerUppstartDetta anteckningsblock_Visa bild...Notera: Säkerhetskopiering och uppgradering kan ta lite tid för stora anteckningsblock.Ett "%"-teckenSätt _bakgrundsfärgAlla filer (*.*)Alternativ placering av index:Alltid överstFontstorlek:BifogaBifoga fil...Bifoga en fil till anteckningenSpara automatiskt:Säkerhetskopierar anteckningsblockSäkerhetskopierar gammalt anteckningsblock...SäkerhetskopieringSäkerhetskopiering avbröts.FetBläddra...Punktlista_CentreratAvbrytKan inte kopiera filen '%s'Kan inte skapa papperskorgsmappenKan inte skapa nodKan inte hitta anteckningsblocket '%s'Kan inte initiera fil på richtext-format '%s'Kan inte öppna anteckningsblock (för gammal version)Kan inte läsa inställningar för anteckningsblockKan inte läsa inställningar för anteckningsblock %sKan inte byta namn på '%s' till '%s'Kan inte spara inställningar för anteckningsblockKan inte spara inställningarKan inte skriva metadaTeckenskiftskänsligCentreratVälj %sVälj namn för säkerhetskopia av anteckningsblockVälj förvalt anteckningsblockVälj teckensnittVälj ikonVälj utfälld ikonVälj tecken_snittVälj alternativ indexmapp för anteckningsblockS_täng fönster_Stäng flikStäng en flikStäng det här anteckningsblocketStäng fönsterFäll i_hop alla underanteckningarKopiera _trädKopiera hela trädetKunde inte skapa nytt anteckningsblock.Kunde inte tömma papperskorgen.Infoga bild '%s'Kunde inte läsa anteckningsblock '%s'.Kunde inte öppna felloggKunde inte spara bilden '%s'Kunde inte spara anteckningsblock.Skapa en ny undersidaSkapa en ny mappSkapa en ny sidaSkapade '%s'Datum och tidDag i månad [01,31]Dag på året [001,366]Förvalt anteckningsblock:Förvalt teckensnitt:Ta bort anteckningAnnat år:Saknar rättigheter att flyttaSaknar rättigheter att ta bortSaknar rättigheter att läsa mappSaknar rättigheter att läsa mappen: %sVill du ta bort den här och alla underliggande anteckningar?Vill du ta bort den här anteckningen?Vill du installera utökningen "%s"?Vill du installera utökningen "%s"?_Testa dra-och-släpp...Fäll _ut anteckningTöm _papperskorgAktiveradAktiverar nya utökningar: FelFel under indexeringFel under säkerhetskopiering.Ett fel uppstod när filen öppnades med %s. program: '%s' fil: '%s' fel: %sKan inte läsa metadatafilFel när anteckningsblock sparadesFel när fil '%s' bifogadesFel när filer bifogades: %s.Fel under uppdatering.Fäll ut alla un_deranteckningarUtökning "%s" är nu installerad.Utökningen "%s" är nu borttagen.Ta bort utökningUtökningarFel filformatSök _föregåendeSök efter:Sök _nästaSök/ersätt_FormatHärifrån kan du välja format på tider i listvyn för fyra olika scenarion (t.ex. hur tider från idag, den här månaden och det här året ska visas)Följ län_kGå till n_ästa anteckningÖppna _redigerareGå till _listvynGå till _anteckningGå till _överliggande anteckningGå till före_gående anteckningGå till _trädvynHjälpapplikationerGöm från fönsterlistaHorisontellTimme (12-timmars klocka) [01,12]Timme (24-timmars klocka) [00,23]Ök_a teckenstorlek_Minska indrag_Öka indragIndexerar anteckningsblockIndexerar...InfogaInfoga bild från filInfoga _horisontell linjeInfoga _bild...Infoga _ny bild...Infoga _skärmbild...Infoga en ny bildInstallera ny utökningInstallationen lyckadesKursivMarginaljusteringBevara bildformatKeepNote utökning (*.kne)KeepNote inställningarKeepNote kan bara ta bort användarutökningarSpråkSpråk:Vänsterjusterat_LänkListvyns utseende:Läste in '%s'Läser in...Förkortat månadsnamn i regionalt formatFörkortat veckodagsnamn i regionalt formatDatum och tid i regionalt formatDatum i regionalt formatTid i regionalt formatFm-/em-indikator i regionalt formatFullständigt månadsnamn i regionalt formatFullständigt veckodagsnamn i regionalt formatUtseendeSkapa länkMinimera vid startMinut [00,59]Fast teckenbreddMånad [01,12]Måste välja %s-program i hjälpapplikationerMåste ange ett filnamn för bilden.NyNy filNy ikonNy bildNytt anteckningsblockNy sidaNytt _fönsterNy _undersidaNy _mappNy s_ida_Ny flikInget förvalt anteckningsblockIngen radbrytningIngen _radbrytningInga anteckningar är valda.Hittade ingen versionstagAnteckningsblock (*.nbk)AnteckningsblocksalternativUppdatera anteckningsblockUppdatering av anteckningsblock färdigAnteckningsblock stängtAnteckningsblock modifieratAnteckningsblockets inställningar är oläsbaraAnteckningsblock omlästAnteckningsblock sparatUppdatering av anteckningsblock lyckadesAnteckningsblocksspecifika ikonerÖppnaÖppna senaste anteckningsblockÖppna anteckningsblockÖppna _tidigare anteckningsblockÖppna en ny flikÖppna ett nytt fönsterÖppna ett existerande anteckningsblockÖppnar anteckningsblockKlistra in oformatterad textAvsluta KeepNoteLäs om det här anteckningsblocketOmläsning kan bara göras när ett anteckningsblock är öppet.Ersätt med:Ersätt _allaÄndra storlek på bildHögerjusteratRottag är inte 'node'_GenomstrukenSamma dag:Samma månad:Samma år:SparaSpara bild som...Spara det här anteckningsblocketSök _framåtSök i alla anteckningarSök _bakåtSöker i anteckningsblockSöker...Sekund [00,61]Sätt backgrundsfärgVälj teckensnittSätt teckenstorlekSätt textfärgStandardikonerSkapa ett nytt anteckningsblockGenomstrukenByt till nästa flikByt till föregående flikPapperskorgen kan inte tas bort.Papperskorgen måste ligga på toppnivån.Skärmbildsprogrammet skapade inte den förväntade bildfilen '%s'Skärmbildsprogrammet stötte på ett fel: %sToppnivåmappen kan inte tas bort.Den här noden tillhör inte någon anteckningDetta anteckningsblock har version %d och måste uppdateras till version %d innan det kan öppnas.Det här anteckningsblocket har version 1 och måste uppdateras till version 2 innan det kan öppnas.Den här avdelningen innehåller alternativ som sparas per anteckningsblock (t.ex. teckensnitt). En underavdelning visas för varje anteckningsblock som är öppet.Denna version av %s kan inte läsa det här anteckningsblocket. Anteckningsblockets version är %d. %s kan bara läsa %d.Tidszon (tomt om tidszon saknas)Kan inte bestämma typ av anteckning.Kan inte installera utökning '%s'Kan inte installera utökning. Saknar rättigheter.Kan inte ta bort okänd utökning '%s'.UnderstrukenOväntat felTa bortBorttagningen lyckadesOkänd versionssträngUppdatera anteckningsblockUppdaterar anteckningsblock...Använd nuvarande anteckningsblockVisa ikon i notifieringsytanVertikalVisa anteckning i _filhanterareÖppna anteckning i _textredigerareÖppna anteckning i _webbläsareVisa _inställningsfiler...Visa _fellogg...Synlig på alla arbetsytorVeckonummer med måndag som första dagen i veckan [00,53]. Alla dagar före första måndagen får veckonummer 0.Veckonummer med söndag som första dagen i veckan [00,53]. Alla dagar före första söndagen får veckonummer 0.Veckodag [0(söndag),6]F_önsterÅr inklusive århundradeÅr utan århundrade [00,99]Du måste ange ett program för att skapa skärmbilder i applikationsalternativ_Om_VerkställSätt textf_ärgBifoga _fil..._Bakåt_Fet_Punktlista_Avbryt_Ändra anteckningsikon_StängStän_g anteckningsblockFäll _ihop anteckningM_inska teckenstorlek_Ta bort_Redigera_Redigera bild...E_xportera anteckningsblock_Arkiv_Sök_Sök...Fra_måt_Gå till_Hjälp_Importera anteckningsblock_KursivMarginal_justering_VänsterjusteratFast _teckenbredd_Nytt anteckningsblock..._Nästa flik_OK_Öppna fil_Öppna anteckningsblock...I_nställningar_Föregående flik_Avsluta_Läs om anteckningsblock_Byt namn_Ersätt_Ersätt..._Ändra storlek på bild..._Högerjusterat_Spara bild som..._Spara anteckningsblock_SökSök i _alla anteckningar_StavningskontrollVerk_tyg_Understruken_Uppdatera index_Visa_Visa bild...Kommandot '%s' finns redanTa bort ikonaktivera utökning '%s' format:höjd:Ikon:Utfälld ikon:tar bort '%s'skapa säkerhetskopia före uppdatering (rekommenderas)sekunderSätt ikonSätt utfälld ikonvisa linjer i trädvynanvänd GTK-ikoner i verktygsfältetanvänd minimalt verktygsfältanvänd olika färger för rader i listvynbredd:keepnote-0.7.8/keepnote/rc/locale/tr_TR.UTF8/0000755000175000017500000000000011727170645016760 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/tr_TR.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020545 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/tr_TR.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000003551511677423605022726 0ustar razraz% @0Ar%%=LaFw 2< A K We&l   4Pn  ,-Ao 8   ) ?`x   o{   0!0R   #5J P\!|1((&"Ib }" !&  + 7 A MZq  -;-W       "3E X"e#0',UTT`5`      !!!)p"%"0""" ## #-#5#G#N#^#m####### # ### ## $$ $1$9$B$V$ g$t$$$$ $$$ $$$ $)$ %(% 1%?%V%u%%]%8& /'.P'''''''(*(;(NT( (((( (()) !)+)1)9))@)j)) ) ))))) * *:*T*n********+++ =+6J+++++ +'+',C, [,f,~,,,O-a-s-- - -----. .7.'N.v......... /(/ >/J/Y/w/%////0$0 80C0R0 d0+o00 0 00 0 0001&151J1]1r11111 11129!2[2m22 22 2 2 222233-3@3 S3`3o333333B3!44\V4Z4c5;r555!5!5!616L660777 88 '858=8C8K8_8f8 u8&8888888 88 99 $9/9 >9 L9W9n9 999 99999 ::': 9: E:Q: X:.e:: ::,:2:#; B;492INlSsCvzgyj(+m]`/o<i0^ f-%K~D d@qY3h$u Lc w[?tp>r;k Bx {1ZR&G#8_He'UX|MO6V},:QA!aFn75W)E\*J=T"P.b%s was unable to remove temp file for screenshotAll Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)AttachAttach File...Attach a file to the notebookAutosave:BoldBrowse...Bullet ListC_enter AlignCancelCannot open notebook (version too old)Case _SensitiveCenter AlignChoose FontChoose _FontClose the current notebookCollapse A_ll Child NotesCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Different year:Do you want to delete this note and all of its children?Do you want to delete this note?Drag and Drop Test...E_xpand NoteEmpty _TrashError saving notebookError while attaching file '%s'.Expand _All Child NotesFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreInsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _Screenshot...ItalicJustify AlignKeep aspect ratioKeepNote PreferencesLin_kLoaded '%s'Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify a filename for the image.NewNew IconNew NotebookNew _Child PageNew _FolderNew _PageNo WrappingNo _WrappingNo notes are selected.Notebook (*.nbk)Notebook UpdateNotebook closedNotebook modifiedNotebook reloadedNotebook savedNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen an existing notebookQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignS_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Text ColorStandard IconsStart a new notebookStrikeThe Trash folder cannot be deleted.The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Use current notebookVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView _Error Log...Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]Year with century as a decimal numberYear without century as a decimal number [00,99]_About_Apply Text Color_Attach File..._Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Edit Image..._File_Find_Find In Page..._Italic_Justify Align_Left Align_Monospace_New Notebook..._Ok_Open File_Open Notebook..._Preferences_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_View_View Image...delete iconheight:icon:open-icon:save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.5.3 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2009-07-09 07:47+0300 Last-Translator: Yuce Tekol Language-Team: Turkish Language: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=1; plural=0; %s ekran görüntüsü geçici dosyasını kaldıramadıTüm Kullanılabilir simgelerTarih ve zaman biçimlendirme anahtarıTarih ve ZamanGenel KeepNote SeçenekleriSimgeleri yönetDüğüm simgeleriDefter GüncellemesiSimgeleri hızlı seçBaşlangıçBu DefterResmi _Göster...not: büyük defterlerin yedeklenmesi ve güncellenmesi uzun sürebilir"%" karakteriArkaplan _Rengini UygulaTüm dosyalar (*.*)EkleDosya Ekle...Deftere bir dosya ekleOtomatik kaydet:_KalınGözat...Liste_OrtalaİptalDefter açılamadı (sürümü çok eski)Büyük/Küçük Harf _Duyarlı_Ortala_Font Seç_Font SeçGüncel defteri kapatT_üm Altnotları DaraltYeni defter yaratılamadı.Çöp boşaltılamadı'%s' resmi eklenemediDefter '%s' yüklenemedi.Hata kaydı açılamadı.'%s' resmi kaydedilemedi.Defter kaydedilemedi.Yeni bir alt sayfa yaratYeni bir klasör yaratYeni bir sayfa yarat'%s' yaratıldıTarih ve ZamanAyın günü [01,31]Yılın günü [001,366]Varsayılan Defter:Varsayılan font:Başka yıl:Bu notu ve tüm altnotlarını silmek mi istiyorsunuz?Bu notu silmek mi istiyorsunuzSürükle ve Bırak Testi...Notu _GenişletÇöpü _BoşaltDefteri kaydederken hata oluştu'%s' dosyasını eklerken hata oluştu._Tüm Altnotları Genişlet_Önceki Sayfada Bul...Metni Bul:_Sonraki Sayfada Bul...Bul/Değiştir_BiçimBurada liste görünümündeki zaman-etiketlerini 4 farklı şekilde özelleştirebilirsiniz (mesela, bugün, bu ay yada bu yıl için bir zaman-etiketinin nasıl görüntüleneceğini)_Bağlantıya Git_Sonraki Nota Git_Düzenleyiciye Git_Liste Görünümüne Git_Nota Git_Ana Nota git_Önceki Nota Git_Ağaç Görünümüne GitYardımcı UygulamalarGörev çubuğunda göstermeYataySaat (12-saat) [01,12]Saat (24-saat) [00,23]Yazı Tipinin Büyüklüğünü A_rtır_Daha Az GirintiDaha _Fazla GirintiEkleDosyadan Resim Ekle_Yatay Çizgi Ekle_Resim Ekle..._Ekran Görüntüsü Ekle..._İtalik_İki Yana YaslaYatay/dikey oranını koruKeepNote Seçenekleri_Bağlantı'%s' yüklendiKısaltılmış yerel ay adıKısaltılmıs yerel gün adıUygun yerel tarih ve zaman gösterimiUygun yerel tarih gösterimiUygun yerel zaman gösterimiYerel AM yada PM gösterimiYerel tam ay adıYerel tam gün adıGörünümDakika [00,59]Sabit _AralıklıAy [01,12]Resim için bir dosya adı belirtmelisiniz.YeniYeni SimgeYeni DefterYeni _Alt SayfaYeni K_lasörYeni _Sayfa_Sözcük Kaydırma Yok_Sözcük Kaydırma YokHiç not seçilmedi.Defter (*.nbk)Defter GüncellemesiDefter kapatıldıDefter değştirildiDefter tekrar yüklendiDefter kaydedildiDefter SimgeleriAçSon Defteri AçDefter AçVarolan bir defteri açKeepNote uygulamasından çıkGüncel defteri tekrar yükleTekrar yükleme sadece bir defter açıkken yapılabilir.Metni Değiştir:_Tümünü DeğiştirResmi BoyutlandırS_ağa YaslaÜstü _ÇiziliAynı gün:Aynı ay:Aynı yıl:KaydetFarklı Kaydet...Güncel defteri kaydet_İleriye Doğru Ara_Tüm notları ara_Geriye Doğru AraDefterde aranıyorAranıyor...Saniye [00,61]Arkaplan _Rengini UygulaMetin Rengi _UygulaStandard SimgelerYeni bir deftere başlaÜstü _ÇiziliÇöp klasörü silinemez.Ekran görüntüsü yakalayıcı program şu hatayı bildirdi: %sEn üst seviye klasör silinemez.Bu defter sürüm %d biçiminde ve açılmadan önce sürüm %d'ye güncellenmesi gerekiyor.Bu defter sürüm 1 biçiminde ve açılmadan önce sürüm 2'ye güncellenmesi gerekiyor.Bu sürümdeki %s bu defteri okuyamaz. Bu defterin sürümü %d. %s sadece %d sürümü okuyabilir.Zaman dilimi adı (eğer zaman dilimi yoksa boş bırakın)Güncel defteri kullanDikeyNotu Dosya Yöneticisinde GösterNotu Metin Düzenleyicide GösterNotu Web Tarayıcısında Göster_Hata Kaydını Göster...Yılın kaçıncı haftası olduğu (Pazartesi haftanın il günü) [00,53] arasında. Yılın ilk pazartesi gününden önceki tüm günler 0. hafta olarak kabul edilirYılın kaçıncı haftası olduğu (Pazar haftanın il günü) [00,53] arasında. Yılın ilk pazar gününden önceki tüm günler 0. hafta olarak kabul edilirHaftanın kaçıncı günü olduğu [0(Pazar),6]Yıl, yüzyıl ile birlikteYıl, yüzyıl olmadan [00,99]_HakkındaMetin Rengi _UygulaDosya Ekle..._KalınListe_İptal_Simgeyi Değiştir_KapatDefteri K_apatNotu _DaraltYazı Tipinin Büyüklüğünü A_zaltResmi _Düzenle..._Dosya_BulSayfada _Bul..._İtalik_İki Yana YaslaS_ola YaslaSabit _Aralıklı_Yeni Defter_Tamam_Belge AçDefter _Aç..._Seçenekleri_ÇıkışDefteri _Tekrar Yükle_Adını Değiştir_DeğiştirSayfada _Değiştir...Resmi _Boyutlandır...S_ağa YaslaResmi Farklı _Kaydet...Defteri _Kaydet_Aramak_Tüm notları ara_Yazım Denetimi_GösterResmi _Göster...simgeyi silyükseklik:simge:simgeyi aç:güncellemeden önce yedek al (tavsiye edilir)saniyesimgeyi seçaçık simgesini seçağaç görünümündeki satırları gösterAraç çubuğunda GTK standard simgelerini gösterListe görünümünde cetvel kullangenişlik:keepnote-0.7.8/keepnote/rc/locale/sk_SK.UTF8/0000755000175000017500000000000011727170645016740 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/sk_SK.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020525 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/sk_SK.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000006352711677423605022712 0ustar razrazx xy0# & ) , 7 > A %\        !!0!FF!!!!! !!""$" B"L"`"{""" " " """""#$!#&F# m#### ##$$ -$ :$D$`$ x$ $$ $+$ $ $ $$ %&% @%K%\%{%%%%%&&1&E& W& d&,r&-&& & && ' )'.J'2y'8' '*(,1(^( t( ((((((P(8)!U)w) )))) )"*@* T*_*q* ** *** h+t+ ++ ++++++ ,0,0K,|, , ,, ,,,,,-$-:-M-c-v- }----+-- . ... .. :.E.!e.1.(.(.& /2/K/ f/ t/~/"/ /!/0/&070;0D0 M0 W0d0 m0x0 0 000 0 00011"121K1[1#m1111111 22$232E2_2p2 22-2 2 2 2 33'3 /3 93 E3P3U3f33333 3"33 4 4'464E4Z4a4t4#4,4C40 5'Q5%y5U5T5J6`65V77 777+8 08:8 K8U8j888888889(9A9T9l9:)::%:0;=P;;;;;;; ;;;;;<<+<3<9<H<Y<_<e<v<<<<<< < << << << = ==%=6=>=G=[= l=y==== == ===== >'>@> H>S>Z>n>v> |> >)>>> >>>?(?D?K?@#@AA!A$A'A*A-A0A3A6A9AcVc^cgc~cc cccccccddd.d FdRd hdsd d ddddde ee7e KeVe fe eee eee ef ff'f.f?f4Sfffff&f$f,!gNgd<hHF'quwf^z).+q$%y{b&(-!kR#B p?grLGacoe]@_@Seu7,4B&PYj $0fK7tT jQ. |l s D`CP9SA>~Iai\:vn`[KUO/}?sN +LA1gblx3XO69Np3m=M*")WM]2UD8V,[cn/*tEw_C;>40vmhr2R%5ZEYo(F<H#G1d=! VI5ZJ^\J8x'XWk-"QT;6i:  failed dependency: %s skipping extension '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d pages%j%m%p%s was unable to remove temp file for screenshot%w%x%y(Untitled)1 page50All Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)Alternative index location:Always on topApplication Font Size:AttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot read notebook preferences %sCannot rename '%s' to '%s'Cannot save notebook preferencesCannot save preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose WindowClose _TabClose a tabClose the current notebookClose windowCollapse A_ll Child NotesCopy _TreeCopy entire treeCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Delete NoteDifferent year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo not have permission to read folder contents: %sDo you want to delete this note and all of its children?Do you want to delete this note?Do you want to install the extension "%s"?Do you want to uninstall the extension "%s"?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledEnabling new extensions: ErrorError during indexError occurred during backup.Error occurred while opening file with %s. program: '%s' file: '%s' error: %sError reading meta data fileError reading meta data file '%s'Error saving notebookError while attaching file '%s'.Error while attaching files %s.Error while updating.Expand _All Child NotesExtension "%s" is now installed.Extension "%s" is now uninstalled.Extension UninstallExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _New Image...Insert _Screenshot...Insert a new imageInstall New ExtensionInstall SuccessfulItalicJustify AlignKeep aspect ratioKeepNote Extension (*.kne)KeepNote PreferencesKeepNote can only uninstall user extensionsLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Loading...Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinimize on startMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify '%s' program in Helper ApplicationsMust specify a filename for the image.NewNew FileNew IconNew ImageNew NotebookNew PageNew WindowNew _Child PageNew _FolderNew _PageNew _TabNo Default NotebookNo WrappingNo _WrappingNo notes are selected.No version tag foundNotebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen a new tabOpen a new windowOpen an existing notebookOpening notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignRoot tag is not 'node'S_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorStandard IconsStart a new notebookStrikeSwitch to next tabSwitch to previous tabThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program did not create the necessary image file '%s'The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Unable to determine note type.Unable to install extension '%s'Unable to uninstall extension. Do not have permission.Unable to uninstall unknown extension '%s'.UnderlineUnexpected errorUninstallUninstall SuccessfulUnknown version stringUpdating NotebookUpdating notebook...Use current notebookUse system tray iconVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView Preference Files...View _Error Log...Visible on all desktopsWeek number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]WindowYear with century as a decimal numberYear without century as a decimal number [00,99]You must specify a Screen Shot program in Application Options_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Next Tab_Ok_Open File_Open Notebook..._Preferences_Previous Tab_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...command '%s' already existsdelete iconenabling extension '%s' format:gtk-cancelgtk-okgtk-revert-to-savedheight:icon:open-icon:removing '%s'save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.7.3 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2011-07-08 08:43+0200 Last-Translator: Slavko Language-Team: Slovak Language: sk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8-bit Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; zlyhala závislosť : %s preskakovanie rozšírenia '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d stránky/ok%j%m%p%s nemohol odstrániť dočasní súbor snímku obrazovky%w%x%y(Bez mena)1 stránka50Všetky dostupné ikonyFormátovacie značky dátumu a časuDátum a časVšeobecné voľby KeepNoteSpravovať ikonyIkony uzlovAktualizácia zápisníkaRýchle ikonySpúšťanieTento zápisník_Zobraziť obrázok...Poznámka: zálohovanie a aktualizácia môže pri väčších zápisníkoch trvať dlho.Znak %Použiť _farbu pozadiaVšetky súbory (*.*)Alternatívne umiestnenie indexu:Vždy na vrchuVeľkosť písma aplikácie:PripojiťPripojiť súbor...Pripojí k zápisníku súborAuto ukladanie:Zálohovanie zápisníkaZálohovanie starého zápisníkaZálohaZálohovanie zrušené.TučnéPrechádzať...ZoznamZarovnať na _stredZrušiťNemožno kopírovať súbor '%s'Nemožno vytvoriť zložku košaNemožno vytvoriť uzolNemožno nájsť zápisník '%s'Nemožno inicializovať súbor richtext '%s'Nemožno otvoriť zápisník (príliš stará verzia)Nemožno čítať nastavenia zápisníkaNemožno čítať nastavenia zápisníka %sNemožno premenovať '%s' na '%s'Nemožno uložiť nastavenia zápisníkaNemožno uložiť nastaveniaNemožno zapísať metadátaRozlišovať _veľkosťZarovnať na stredVyberte %sZvoľte meno zálohy zápisníkaVyberte predvolený zápisníkZvoľte písmoZvoľte ikonuZvoľte otvorenú ikonuZvoliť pí_smoZvoľte alternatívny adresár indexu zápisníka_Zatvoriť oknoZ_atvoriť záložkuZatvorí záložkuZatvorí aktuálny zápisníkZavrie oknoZbaliť _všetky vnorenéKopírovať s_tromKopíruje celý stromNemožno vytvoriť nový zápisník.Nemožno vyprázdniť kôš.Nemožno vložiť obrázok '%s'Nemožno načítať zápisník '%s'.Nemožno otvoriť záznam chýbNemožno uložiť obrázok '%s'.Nemožno uložiť zápisník.Vytvorí novú vnorenú stránkuVytvorí novú zložkuVytvorí novú stránkuVytvorený '%s'Dátum a časDeň mesiaca ako desiatkové číslo [01,31]Deň roka ako desiatkové číslo [001,366]Predvolený zápisník:Predvolené písmo:Odstránenie poznámkyIný rok:Nemáte práva na presunutieNemáte práva na odstránenieNemáte práva na čítanie obsahu zložkyNemáte práva na čítanie obsahu zložky: %sChcete odstrániť túto poznámku a všetkých potomkov?Chcete odstrániť túto poznámku?Chcete nainštalovať rozšírenie "%s"?Chcete odinštalovať rozšírenie "%s"?Test Ťahaj&PusťRoz_baliť poznámkuVyprázdniť _kôšZapnutéZapínanie nových rozšírení: ChybaChyba počas indexovaniaPri zálohovaní sa vyskytla chyba.Nastala chyba pri otváraní súboru pomocou %s. program: '%s' súbor: '%s' chyba: %sChyba čítania súboru metadátChyba čítania súboru metadát '%s'Chyba pri ukladaní zápisníkaChyba pri pripájaní súboru %s.Chyba pri pripájaní súborov %s.Chyba počas aktualizácie.Rozbaliť všet_ky vnorenéRozšírenie "%s" je teraz nainštalované.Rozšírenie "%s" je teraz odinštalované.Odinštalovanie rozšíreniaRozšíreniaChyba formátu súboruNájsť v pre_došlej stránke...Hľadaný text:Nájsť v ďalšej _stránke...Nájsť/nahradiť_FormátTu si môžete prispôsobiť formát časovej značky v zobrazení zoznamu na 4 rôzne scenáre (tj. ako zobraziť časovú značku pre dnešok, tento mesiac alebo tento rok)Prejsť na od_kazPrejsť na ďalšiu p_oznámkuPrejsť na _editorPrejsť na zoz_namPrejsť na p_oznámkuPrejsť na _rodičovskúPrejsť na predošlú poznámk_uPrejsť na _stromPomocné aplikácieSkryť z panela úlohVodorovneHodina (12 hod) ako desiatkové číslo [01,12]Hodina (24 hod) ako desiatkové číslo [00,23]Zväčšiť veľ_kosť písmaZ_menšiť odsadenieZ_väčšiť odsadenieIndexovanie zápisníkaIndexovanie...VložiťVložiť obrázok zo súboruVložiť _vodorovnú čaruVlož_iť obrázokVložiť _nový obrázok...Vložiť sní_mok obrazovkyVloží nový obrázokInštalovať nové rozšírenieInštalácia úspešnáŠikméZarovnať do blokuZachovať pomer stránRozšírenie KeepNote (*.kne)Nastavenia KeepNoteKeepNote môže odinštalovať len používateľské rozšíreniaJazykJazyk:Zarovnať vľavoOd_kazVzhľad zoznamu:Načítaný '%s'Načítanie...Lokalizovaná skratka mena mesiacaLokalizovaná skratka mena dňa týždňaLokalizovaná reprezentácia dátumu a časuLokalizovaná reprezentácia dátumuLokalizovaná reprezentácia časuLokalizovaný ekvivalent AM alebo PMLokalizované meno mesiacaLokalizované meno dňa týždňaVzhľad a správanieVytvorí odkazMinimalizovať pri spusteníMinúta ako desiatkové číslo [00,59]Pevná šírkaMesiac ako desiatkové číslo [01,12]Musíte zadať '%s' program v Pomocných aplikáciáchMusíte zadať meno súboru pre obrázok.NovýNový súborNová ikonaNový obrázokNový zápisníkNová stránka_Nové oknoNová _vnorená stránkaNová _zložkaNová stránkaN_ová záložkaŽiadny predvolený zápisníkNezalamovaťNe_zalamovaťNie sú zvolené poznámky.Nebola nájdená značka verzieZápisník (*.nbk)Voľby zápisníkaAktualizácia zápisníkaAktualizácia zápisníka dokončenáZápisník zatvorenýZápisník zmenenýDáta nastavenia zápisníka sú poškodenéZápisník znova načítanýZápisník uloženýZápisník úspešne aktualizovanýIkony zápisníkaOtvoriťOtvoriť posledný zápisníkOtvoriť zápisníkOtvoriť ne_dávny zápisníkOtvorí novú záložkuOtvorí nové oknoOtvorí existujúci zápisníkOtváranie zápisníkaVložiť ako prostý textSkončí KeepNoteZnova načíta nový zápisníkZnova načítanie funguje len keď je zápisník otvorený.Nahradiť textom:Nahradiť _všetkyZmena veľkosti obrázkuZarovnať vpravoKoreňová značka nie je 'uzol'P_rečiarknutéRovnaký deň:Rovnaký mesiac:Rovnaký rok:UložiťUložiť obrázok ako...Uloží aktuálny zápisníkHľadať v_predHľadať vo všetkých poznámkachHľadať v_zadPrehľadávanie zápisníkaHľadanie...Sekundy ako desiatkové číslo [00,61]Nastaviť farbu pozadiaNastaviť vzhľad písmaNastaviť veľkosť písmaNastaviť farbu písmaŠtandardné ikonySpustí nový zápisníkPrečiarknutéPrepne na ďalšiu záložkuPrepne na predošlú záložkuZložku koša nemožno odstrániť.Zložka koša musí byť zložkou najvyššej úrovne.Program na snímanie obrazovky nevytvoril požadovaný súbor obrázka "%s"Program na snímanie obrazovky vyvolal chybu: %sZložku najvyššej úrovne nemožno odstrániť.Táto poznámka nie je časťou žiadneho zápisníkaTento zápisník má verziu formátu %d a pred otvorením musí byť aktualizovaný na verziu %d.Tento zápisník má verziu formátu 1 a pred otvorením musí byť aktualizovaný na verziu 2.Táto časť obsahuje voľby, ktoré sú uložené pre každý zápisník (napr. font zápisníka). Každý otvorený zápisník bude mať samostatnú položku.Táto verzia %s nemôž čítať tento zápisník. Zápisník má verziu %d. %s môže čítať len %d.Meno časovej zóny (bez znakov, ak zóna neexistuje)Nemožno určiť typ poznámkyNemožno nainštalovať rozšírenie '%s'Nemožno odinštalovať rozšírenie. Nemáte na to práva.Nemožno odinštalovať neznáme rozšírenie '%s'.PodčiarknutéNeočakávaná chybaOdinštalovaťOdinštalovanie úspešnéNeznámy reťazec verzieAktualizovanie zápisníkaAktualizovanie zápisníka...Použiť aktuálny zápisníkPoužiť ikonu oznamovacej oblastiZvisloZobraziť v _správcovi súborovZobraziť v textovom _editoreZobraziť vo _webovom prehliadačiSúbory _nastavení...Záznam _chýb...Viditeľné na všetkých plocháchČíslo týždňa roku (pondelok ako prvý deň týždňa) ako desiatkové číslo [00,53]. Všetky dni nového roka, ktoré predchádzajú prvému pondelku sú považované za týždeň 0.Číslo týždňa roku (nedeľa ako prvý deň týždňa) ako desiatkové číslo [00,53]. Všetky dni nového roka, ktoré predchádzajú prvej nedeli sú považované za týždeň 0.Deň týždňa ako desiatkové číslo [0(Nedeľa),6]_OknoRok so storočím ako desiatkové čísloRok bez storočia ako desiatkové číslo [00,99]Musíte zadať program na snímanie obrazovky v Nastaveniach_O programe_PoužiťPoužiť farbu _textuPrip_ojiť súbor_Späť_Tučné_Zoznam_ZrušiťZmeniť _ikonuZ_atvoriť_Zatvoriť zápisníkZba_liť poznámkuZmenšiť v_eľkosť písmaO_dstrániť_Upraviť_Upraviť obrázok..._Exportovať zápisník_Súbor_Nájsť_Nájsť v stránke..._Vpred_Prejsť_Pomocník_Importovať zápisníkŠ_ikméZarovnať do _blokuZarovnať vľ_avo_Pevná šírka_Nový zápisník...Ď_alšia záložka_OK_Otvoriť súbor_Otvoriť zápisník..._Nastavenia_Predošlá záložka_SkončiťZn_ova načítať zápisníkP_remenovať_NahradiťN_ahradiť v stránke...Zmeniť _veľkosťZarovnať vprav_o_Uložiť obrázok ako..._Uložiť zápisník_Hľadať_Hľadať vo všetkých_Kontrola preklepov_Nástroje_Podčiarknuté_Aktualizovať index zápisníka_Zobraziť_Zobraziť obrázok...príkaz '%s' už existujezmazať ikonuzapínanie rozšírenia '%s' formát:gtk-cancelgtk-okgtk-revert-to-savedvýška:ikona:otvorená ikona:odstraňovanie '%s'uložiť zálohu pred aktualizáciou (odporúčané)sekundynastaviť ikonunastaviť otvorenú ikonuzobraziť riadky v zoznamepoužiť v paneli nástrojov GTK ikonypoužiť minimálny panel nástrojovpoužiť info pravítka v zobrazení zoznamušírka:keepnote-0.7.8/keepnote/rc/locale/de_DE.UTF8/0000755000175000017500000000000011727170645016666 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/de_DE.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020453 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/de_DE.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000005116111677423605022627 0ustar razraz7 4PSVY\_behknqtwz}0%,L`rF":Jfm|    1L_$y&  "9 I V`|   ++Ja}  & 3 ,A -n    . !8> '>1>/8>$h>%>>> >>!? %?1?K?[?c?@@6@K@c@s@@@@ @ @5@52AhA{AAAA AA!AB$B>B EBOBmBBB BBB BBBBC&C!9C[C"uCC CC CCCD D#D3D CD PD[DrDDDDDDDDE (EIE_EuEEEEEE#EF3FDF=bFFFF FF FFG G(G @GaGqGGGG GG GG H HH"#HFH+VH7HIH:I4?I\tIeI7JcJ(]K KKKK KKL0LNLkLM(M(M0NCN WNaNuNNN N NN NNNNO #O/O@OWO^OeO xOOOOO O O OOO OO PP&P >P IPSPnPPPPPPPPPQ!Q)Q:QJQ gQrQyQQQQ'QQ QQ R$%RJRiR0ygLq#a'#~=)1*O[*\sR3 eb _W7!Bd |3 c&w2%;/8,Y.55H+xh(-l$}`IS2]4PUk0@ ' 6p%C.uAf+(1G)/7"?v&{^oJ6:X9 n-F4ZKz >ErM,!DmjTQN< V$ti" failed dependency: %s skipping extension '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d pages%j%m%p%s was unable to remove temp file for screenshot%w%x%y1 page50All Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A_pply Background ColorAll files (*.*)Alternative index location:AttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot rename '%s' to '%s'Cannot save notebook preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose the current notebookCollapse A_ll Child NotesCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Different year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo you want to delete this note and all of its children?Do you want to delete this note?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledErrorError occurred during backup.Error saving notebookError while attaching file '%s'.Error while updating.Expand _All Child NotesExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _Screenshot...ItalicJustify AlignKeep aspect ratioKeepNote PreferencesLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify a filename for the image.NewNew IconNew NotebookNew _Child PageNew _FolderNew _PageNo Default NotebookNo WrappingNo _WrappingNo notes are selected.Notebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen an existing notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignS_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorSnap:Standard IconsStart a new notebookStrikeThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)UnderlineUpdating NotebookUpdating notebook...Use current notebookVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView _Error Log...Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]Year with century as a decimal numberYear without century as a decimal number [00,99]_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Ok_Open File_Open Notebook..._Preferences_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...delete iconenabling extension '%s' gtk-cancelgtk-okgtk-revert-to-savedheight:icon:open-icon:save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbarwidth:Project-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2010-02-07 17:37+0100 Last-Translator: Jan Rimmek Language-Team: GERMAN Language: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit fehlende Abhängigkeit: %s überspringe Erweiterung '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d Seiten%j%m%p%s war nicht in der Lage, die temporäre Datei des Bildschirmfotos zu löschen%w%x%y1 Seite50Alle verfügbaren SymboleDatums- & ZeitformatDatum und UhrzeitAllgemeine KeepNote OptionenSymboleKnotensymboleNotizbuch UpdateSymbole für SchnellzugriffStartenDieses NotizbuchBild anzeigen...Hinweis: Backup und Upgrade können bei größeren Notizbüchern einige Zeit dauern.Hintergrundfarbe anwendenAlle Dateien (*.*)Ort für alternativen Index:AnhängenDatei anhängen...Anhang zum Notizbuch hinzufügenAuto-Speichern:Sichere NotizbuchSichere altes Notizbuch...BackupBackup abgebrochen!FettSuchen...SymbollisteZ_entriertAbbrechenDatei '%s' konnte nicht kopiert werdenPapierkorb konnte nicht angelegt werdenKnoten konnte nicht angelegt werdenNotizbuch '%s' nicht gefundenKann Richtext-Datei '%s' nicht öffnenKann Notizbuch nicht öffnen (Version zu alt)Kann Notizbucheinstellungen nicht lesen'%s' konnte nicht in '%s' umbenannt werdenKonnte Notebookvoreinstellungen nicht speichernKann Metadaten nicht schreibenGroß- und Klein_schreibung beachtenZentriert%s auswählenNamen für Notizbuch Backup auswählenStandardnotizbuch auswählenSchriftart auswählenSymbol auswählenÖffnen des Dialogs zur SymbolauswahlSchri_ft auswählenWählen Sie den Ordner für den alternativen Notizbuch-Index ausSchließt das aktuelle Notizbucha_lle Unterknoten ausblendenNeues Notizbuch konnte nicht erstellt werden.Papierkorb konnte nicht geleert werden.Konnte Bild '%s' nicht einfügenKonnte Notizbuch '%s' nicht laden.Fehlerprotokoll konnte nicht geöffnet werdenKonnte das Bild '%s' nicht speichern.Notizbuch konnte nicht gespeichert werden.Anlegen einer neuen UnterseiteEinen neuen Ordner erstellenEine neue Seite erstellen'%s' erstelltDatum und UhrzeitTag (pro Monat) als Dezimalzahl [01,31]Tag (pro Jahr) als Dezimalzahl [001,366]Standardnotizbuch:Standardschrift:Unterschiedliches Jahr:Keine Rechte zum VerschiebenKeine Rechte zum löschenKeine Rechte zum Anzeigen der OrdnerinhalteDiesen Knoten und alle Unterknoten löschen?Wollen Sie diesen Knoten wirklich Löschen?Drag and Drop Test...Knoten aufklappenPapierkorb leerenAktiviertFehlerWährend des Backup ist ein Fehler aufgetreten.Fehler beim Speichern des NotizbuchsFehler beim Anhängen der Datei '%s'.Fehler beim Update.Zeige _alle UnterknotenErweiterungenFehler im DateiformatFinde Vorhergehendes auf Seite...Suche Text:In Seite weitersuche_n...Suchen/ErsetzenFo_rmatHier kann die Datums- und Zeitanzeige im listview für 4 unterschiedliche Szenarien angepasst werden (z.B. für den aktuellen Tag, den Monat oder das Jahr).Gehe zur Ver_knüpfungZum nächsten Kn_oten springenZum _Editor wechselnZum _List View wechselnGehe zu K_notenGehe zum ElternknotenZum vorherigen Knoten s_pringenZum _Tree View wechselnHilfsprogrammeNicht in der Taskleiste anzeigenWaagerechtStunden im 12-Stundenrhythmus als Dezimalzahl [01,12]Stunden im 24-Stundenrhythmus als Dezimalzahl [00,23]größere _SchriftEinzug verringernEinzug vergrößernNotizbuch indizierenIndizierung läuft...EinfügenBild aus Datei einfügen_Horizontale Trennlinie einfügenB_ild einfügen...Bild_schirmfoto einfügenKursivBlocksatzSeitenverhältnis beibehaltenKeepNote EinstellungenSpracheSprache:LinksbündigLin_kListview Layout:'%s' geladenKurzform des MonatsnamenKurzform WochentagLokale Datums- und ZeitanzeigeLokale DatumsanzeigeLokale ZeitanzeigeLokales Äquivalent zu AM oder PMVollständiger MonatsnameVollständiger Name des WochentagsAussehenLink erzeugenMinuten als Dezimalzahl [00,59]MonospaceMonat als Dezimalzahl [01,12]Dateiname für Bild fehlt.NeuNeues SymbolNeues NotizbuchNeue UnterseiteNeuer OrdnerNeue Seitekein Standardnotizbuchkein Zeilenumbruchkein ZeilenumbruchKeine Notizen ausgewählt.Notizbuch (*.nbk)Notizbuch OptionenNotizbuch UpdateUpdate abgeschlossenNotizbuch geschlossenNotizbuch geändertNotizbuchvoreinstellungen defektNotizbuch neu geladenNotizbuch gespeichertUpdate erfolgreichnotizbuchspezifische SymboleÖffnenÖffne letztes NotizbuchNotizbuch öffnenZuletzt geöffnete NotizbücherÖffnet ein existierendes NotizbuchUnformatiert einfügenKeepNote beendenAktuelles Notizbuch neu ladenNeu Laden funktioniert nur, wenn ein Notizbuch geöffnet ist.Text ersetzen:_Alle ersetzenBildgröße ändernRechtsbündigDurchges_trichenGleicher Tag:Gleicher Monat:Gleiches Jahr:SpeichernBild speichern unter...Speichert das aktuelle NotizbuchVo_rwärtssucheSuche in allen NotizenRückwärts suchenSuche im NotizbuchSuche...Sekunden als Dezimalzahl [00,61]HintergrundfarbeSchriftartSchriftgrößeTextfarbeSnap:StandardsymboleMit einem neuen Notizbuch beginnenDurchgestrichenDer Papierkorb kann nicht gelöscht werden.Der Papierkorb muss ein Ordner auf oberster Ebene sein.Das Programm zum Erstellen eines Bildschirmfotos meldet einen Fehler: %sDer Ordner auf oberster Ebene kann nicht gelöscht werden.Dieser Knoten ist kein Bestandteil eines Notizbuchs Dieses Notizbuch hat die Version %d und muss zum Öffnen auf Version %d aktualisiert werden.Dieses Notizbuch wurde mit Version 1 erstellt und muss zum Öffnen auf Version 2 aktualisiert werden.Dieser Abschnitt enthält Optionen, die pro Notizbuch gespeichert werden (z.B. eine notizbuchspezifische Schriftart). Ein Unterabschnitt wird für alle gerade geöffneten Notizbücher erstellt.Die Version %s kann das Notizbuch nicht lesen. Die Notizbuch-Version ist %d. %s kann nur %d lesen.Name der Zeitzone (ansonsten freilassen)UnterstrichenNotizbuch wird aktualisiertUpdate des Notizbuchs...Aktuelles Notizbuch verwendenSenkrechtNotiz im Dateimanager anzeigenKnoten in Texteditor bearbeitenNotiz im Web-Browser anzeigenF_ehlerprotokoll anzeigen...Wochenanzahl pro Jahr (Montag als erster Wochentag) als Dezimalzahl [00,53]. Alle Tage in einem neuen Jahr vor dem ersten Montag werden als Teil von Woche 0 betrachtetWochenanzahl pro Jahr (Sonntag als erster Wochentag) als Dezimalzahl [00,53]. Alle Tage in einem neuen Jahr vor dem ersten Sonntag werden als Teil von Woche 0 behandeltWochentag als Dezimalzahl [0(Sonntag),6]Vierstellige Jahresanzeige (Dezimalzahl)Zweistellige Jahresanzeige [00,99] (Dezimalzahl)Info über KeepNote_AnwendenTextfarbe _anwendenDatei _anhängen...ZurückFettSym_bollisteAbbre_chenSymbol für Knoten we_chselnS_chließenNotizbuch s_chließenKnoten einklappenSchriftgröße verkleinernLöschenB_earbeitenBild b_earbeitenNotizbuch _exportieren_DateiSuchenIn Seite suchen...Vorwärts_Gehe zu_HilfeNotizbuch _importierenKurs_ivBlocksatz_Linksbündig_Monospace_Neues Notizbuch..._OkDatei öffnenN_otizbuch öffnen...EinstellungenBeendenNotizbuch e_rneut ladenUmbenennenE_rsetzenText in Seite e_rsetzen...Bildgröße ändern_RechtsbündigBild _speichern alsNotizbuch _speichern_Suchen_Suche in allen KnotenRecht_schreibprüfung_Tools_Unterstrichen_Update des Notizbuch IndexAnsichtBild anzeigen...Symbol löschenErweiterung '%s' aktivieren gtk-cancelgtk-okgtk-revert-to-savedHöhe:Symbol:Symbol zum Öffnen:Backup vor Update erstellen (empfohlen)SekundenSymbol setzenSymbol zum Öffnen setzenZeilen im treeview anzeigenGTK Symbole in Symbolleiste benutzenMinimale Symbolleiste benutzenBreite:keepnote-0.7.8/keepnote/rc/locale/it_IT.UTF8/0000755000175000017500000000000011727170645016736 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/it_IT.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020523 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/it_IT.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000005640411677423605022704 0ustar razrazFL |XYt0 %2RfxF(@Xh     $29Oj}$&  @Xo     + , 7 C ^ x      !!8!Q!i!}! ! !,!-!" " %"1"A" a"."8" "* #,6#c# y# #####P#*$G$ ]$~$$ $"$$ %%!% ;%F% \%i%q% &$& 5&C& T&`&s&&&& &0&0&,' @' M'Z' l'x''''''''( (((:(U(j( s( }((( ( ((!(1((*)(S)&|))) ) )") *!*0>*&o**** * * ** * *** + +*+A+R+c+s+++#++++,),., A,O,e,t,,,, ,,-, - +- 8- E-Q-h- p- z- -------- .".7. L. Z.h.w.}....#.,.C /0P/'/%/U/T%0z0`%151 171+2 A2K2]2r2222222 33)e4%404=4$5+525D5T5Z5 `5m5u555555555555 66660686 G6 S6^6 o6y6 }66 6 6666666 77!70787 J7W7 ^7i7777 77777 7)7$8,858L8k888i8 :(: F:=P:::!:,::#;+;A;W;u; ;;;Y;(<E<b<"u<<<<<<(< =.= H= R=^=m===(=="=7 >6E>,|>!>,>>?7?N? `?$j??? ???(? "@ .@<@X@ w@@"@@ @$@$!A FAgAAAA A A,A/BDBYB oB}BB!B:B3 C>C#[C#CC CC CCD% DL0D+}DD#DDE#E!BEdE }EE%EE#EE FFFF FFF G!G;GUG kG G)G)G$G HH0HMH ^HhHHHHHHI(I 0I;ISIoIIII II III*J+,J(XJ$JJJ(JKK#)KMK!`K;K1KK K L LL+L:LOL _L mLxLLLLLLL#M5MEM2YMMM M MMM NN2NDN\NwNNNN;NO$O5OKO\O wOO O OOOO OOPP &P$0PUPqP#PPPPPPQ3QFKQ;Q;Q? R,JRfwR^R=SsSDNT&T:T0T &U3UOUoU U UUU U V(VV;W W&XM5X XXXX X XX XXYY(Y#7Y [Y eYoYYYYYYYYYY YYZ&Z :ZHZ LZWZ jZvZZZ Z ZZZZZ[&[-[B[ Y[ d[r[[[[[[[[[ \6\F\ N\"\\/\ \)\ \@e#4By 9z~ c^4L>bXIF h!356B]!k;m.otq? ([-Al-'x?C`d2 D&%P1 :W#)aA0/Ji>YjMp% "<Of$,+=r@,<uQ}7\FHC G 0 =Vn8N v_;g2.)TD&9/"SwE|U${*36:E5Zs1'K 87*+(R failed dependency: %s skipping extension '%s': %d pages%s was unable to remove temp file for screenshot(Untitled)1 pageAll Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)Alternative index location:AttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot rename '%s' to '%s'Cannot save notebook preferencesCannot save preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose _TabClose a tabClose the current notebookCollapse A_ll Child NotesCopy _TreeCopy entire treeCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Delete NoteDifferent year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo you want to delete this note and all of its children?Do you want to delete this note?Do you want to install the extension "%s"?Do you want to uninstall the extension "%s"?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledEnabling new extensions: ErrorError occurred during backup.Error occurred while opening file with %s. program: '%s' file: '%s' error: %sError reading meta data fileError saving notebookError while attaching file '%s'.Error while updating.Expand _All Child NotesExtension "%s" is now installed.Extension "%s" is now uninstalled.Extension UninstallExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _New Image...Insert _Screenshot...Insert a new imageInstall New ExtensionItalicJustify AlignKeep aspect ratioKeepNote Extension (*.kne)KeepNote PreferencesLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Loading...Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify '%s' program in Helper ApplicationsMust specify a filename for the image.NewNew FileNew IconNew ImageNew NotebookNew WindowNew _Child PageNew _FolderNew _PageNew _TabNo Default NotebookNo WrappingNo _WrappingNo notes are selected.Notebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen a new tabOpen a new windowOpen an existing notebookOpening notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignRoot tag is not 'node'S_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorSnap:Standard IconsStart a new notebookStrikeSwitch to next tabThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program did not create the necessary image file '%s'The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Unable to install extension '%s'Unable to uninstall extension. Do not have permission.Unable to uninstall unknown extension '%s'.UnderlineUpdating NotebookUpdating notebook...Use current notebookVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView Preference Files...View _Error Log...Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]Year with century as a decimal numberYear without century as a decimal number [00,99]You must specify a Screen Shot program in Application Options_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Next Tab_Ok_Open File_Open Notebook..._Preferences_Previous Tab_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...command '%s' already existsdelete iconenabling extension '%s' format:height:icon:removing '%s'save backup before updating (recommended)secondsset iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.6.2 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2010-06-19 17:43+0200 Last-Translator: Davide Melan Language-Team: Italian Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); dipendenza fallita: %s tralascia estensione '%s': %d pagine%s incapace di rimuovere il file temporeaneo dello screenshot(senza titolo)1 paginaTutte le icone disponibiliChiave di formattazione di data & oraData e OraOpzioni generali di KeepNoteGestione iconeIcone del nodoAggiornamento QuadernoScelta veloce iconeAvvioQuesto Quaderno_Vedi immagine ...nota: archiviazione ed aggiornamento possono richiedere tempo per grandi quaderni.Un carattere \"%\" letteraleA_pplica il colore di sfondoTutti i file (*.*)Locazione dell'indice alternativo:AllegaAllega file ...Allega un file al quadernoAutosalvataggio:Sto archiviando il QuadernoArchiviazione di un vecchio quaderno ...ArchiviazioneArchiviazione cancellata.GrassettoSfoglia ...Lista di PuntiAllinea al C_entroCancellaNon posso copiare il file '%s'Non posso creare la cartella del cestinoNon posso creare il nodoNon posso trovare il quaderno '%s'Non posso inizializzare il file a testo arricchito '%s'Non posso aprire il quaderno (versione troppo vecchia)Non posso leggere le preferenze del QuadernoNon posso rinominare '%s' in '%s'Non posso salvare le preferenze del quadernoNon posso salvare le preferenzeNon posso scrivere i meta-datiMaiu_scole e MinuscoleAllinea al CentroScegli %sScegli il nome del Quaderno archivioScegli il Quaderno di defaultScegli carattereScegli iconaScegli icona di aperturaScegli il _carattereScegli un indice di quaderno alternativoChiudi _TabChiude un tabChiudi il quaderno correnteCollassa T_utte le note figlieCopia _AlberoCopia intero alberoNon posso creare un unovo quadernoNon posso svuotare il cestino.Non posso inserire immagine '%s'Non posso caricare il quaderno '%s'.Non posso aprire il log degli erroriNon posso salvare immagine '%s'.Non posso salvare il quadernoCrea una nuova pagina figliaCrea una nuova cartellaCrea una nuova paginaCreato '%s'Data e oraGiorno del mese come numero decimale [01,31]Giorno dell'anno come numero decimale [001,366]Quaderno di default:Carattere di default:Cancella NotaAnno differente:Non ho il permesso per muovereNon ho il permesso per cancellareNon ho il permesso per leggere il contenuto della cartellaVuoi cancellare questa nota e tutte le note-figlie?Vuoi cancellare questa nota?Vuoi installare la estensione "%s"?Vuoi disinstallare estensione "%s"?Test trascina e rilascia ...E_spandi NotaSvuota _CestinoAbilitatoAbilito nuove estensioni: ErroreErrore nel corso della archiviazione.Errore in apertura del file con %s. programma: '%s' file: '%s' errore: %sErrore nella lettura del file dei meta-datiErrore nel salvare il quadernoErrore nell\'allegare il file '%s'.Errore in aggiornamento.Espandi _Tutte le note figlieEstensione "%s" è installata.Estensione "%s" è disinstallata.Estensione disinstallataEstensioniErrore nel formato del fileTrova il _precedente nella pagina ...Trova il testo:Trova il _prossimo nella pagina ...Trova e rimpiazzaFo_rmattaDa qui puoi personalizzare il formato delle date nelle liste per 4 diversi scenari (e.g. come visualizzare una data da oggi, questo mese o questo anno)Vai al Colle_gamentoVai alla prossima N_otaVai a _EditorVai alla _listaVai alla _NotaVai alla Nota _PadreVai alla Nota _precedenteVai alla vista ad _alberoApplicazioni di aiutoNascondi dalla barra dei compitiOrizzontaleOra (12-ore) come numero decimale [01.12]Ora (24-ore) come numero decimale [00,23]Aumenta la _dimensione del carattereRientra _menoRientra di _piùSto indicizzando il quadernoIndicizzando ...InserisciInserisci Immagine da FileInserisci regola _orizzontaleInserisci _Immagine ...Inserisci una _nuova immagineInserisci _Screenshot ...Inserisce una nuova immagineInstalla nuova estensioneItalicoGiustificaMantieni le proporzioniEstensione KeepNote (*.kne)Preferenze di KeepNoteLinguaLingua:Allinea a sinistra_CollegamentoSchema della lista:Caricato '%s'Caricamento ...Nome abbreviato del meseNome abbreviato del giorno della settimanaRappresentazione appropriata per data e oraRappresentazione appropriata per la dataRappresentazione appropriata per oraEquivalente locale di AM o PMNome di mese completoNome completo del giorno della settimanaInterfaccia graficaCrea CollegamentoMinuto come numero decimale [00,59]Spaziatura singolaMese come numero decimale [01,12]Devi specificare programma '%s' nelle Applicazioni di AiutoDevi specificare un nome di file per la immagine.NuovoNuovo FileNuova iconaNuova ImmagineNuovo quadernoNuova finestraNuova Pagina _FigliaNuova _CartellaNuova _PaginaNuovo _TabNessun Quaderno di defaultNon confezionareNon _confezionareNessuna nota selezionata.Quaderno (*.nbk)Opzioni del QuadernoAggiornamento del QuadernoAggiornamento del Quaderno completoQuaderno chiusoQuaderno modificatoI dati delle preferenze del Quaderno sono corrottiQuaderno ricaricatoQuaderno salvatoQuaderno aggiornato con successoIcone specifiche per il QuadernoApriApri ultimo QuadernoApri quadernoApri Quaderno Re_centeApre un nuovo tabApre una nuova finestraApri un quaderno esistenteApertura quadernoIncolla come testo sempliceAbbandona KeepNoteReicarica il quaderno correnteIl ricaricamento funziona solo quando un quaderno è apertoRimpiazza testo:Rimpiazza _TuttoRidimensiona ImmagineAllinea a DestraLa radice non è un 'nodo'S_barratoStesso giorno:Stesso mese:Stesso anno:SalvaSalva immagine come ...Salva il quaderno correnteCe_rca avantiCerca tutte le noteCerca _indietroRicerca quadernoCerca ...Secondi come numero decimale [00,61]Imposta il colore di sfondoImposta il carattereImposta la dimensione del carattereImposta il colore del carattereFoto:Icone standardInizia un nuovo QuadernoSbarratoPassa al prossimo tabLa cartella del cestino non può essere cancellata.La cartella del cestino deve essere una cartella di livello superiore.Il programma Screenshot non ha creato il file immagine '%s'Il programma per lo screenshot ha incontrato un errore: %sLa cartella di livello più elevato non può essere cancellata.Questo nodo non appartiene ad alcun quadernoQuesto Quaderno è nella versione %d e deve essere aggiornato alla versione %d prima di essere aperto.Questo quaderno è in versione 1 deve essere aggiornato alla versione 2 prima di essere apertoQuesta sezione contiene opzioni che sono salvate per quaderno (e.g. caratteri specifici per il quaderno). Una sottosezione appare per ogni quaderno aperto.Questa versione di %s non può leggere questo quaderno. Il quaderno è della versione %d. %s può leggere solo %d."Nome del fuso orario (nessun carattere se non esiste un fuso orario)Incapace di installare estensione '%s'Incapace di disinstallare estensione. Non ho il permesso.Incapace di installare la nuova estensione '%s'.SottolineatoSto aggiornando il quadernoSto aggiornando il Quaderno ...Usa il quaderno correnteVerticaleVedi la nota nel gestore di fileVedi la nota in editor di testiVedi la nota nel browserVedi i file delle preferenze ...Vedi log degli _errori ...Numero di settimana dell\'anno (lunedì come primo giorno della settimana) come numero decimale [00,53]. Tutti i giorni di un nuovo anno precedenti il primo lunedì appartengono alla settimana 0Numero di settimana dell\'anno (domenica come primo giorno della settimana) come numero decimale [00,53]. Tutti i giorni di un nuovo anno precedenti la prima domenica appartengono alla settimana 0Giorno della settimana come numero decimale [0(domenica),6]Anno come numero decimale [YYYY]Anno [YY] come numero decimale [00,99]Devi specificare un programma per Screenshot nelle Opzioni della Applicazione_A proposito_Applica_Applica il colore del testo_Allega File ..._Indietro_GrassettoLista di _Punti_Cancella_Cambia icona della Nota_Chiudi_Chiudi Quaderno_Collassa Nota_Riduci la dimensione del carattere_Cancella_Modifica_Modifica immagine ..._Esporta il Quaderno_File_Trova_Trova nella pagina ..._Avanti_Vai_Aiuto_Importa un Quaderno_Italico_GiustificaAllinea a _sinistra_Spaziatura singola_Nuovo Quaderno ..._Prossimo Tab_Ok_Apri File_Apri Quaderno ..._PreferenzeTab _Precedente_Esci_Ricarica Quaderno_Rinomina_Rimpiazza_Rimpiazza nella pagina ..._Ridimensiona immagine ...Allinea a _Destra_Salva immagine come ..._Salva Quaderno_Cerca_Cerca tutte le noteControllo _ortografico_Strumenti_Sottolineato_Aggiorna indice del Quaderno_Vedi_Vedi Immagine ...comando '%s' esiste giàcancella iconaabilita estensioni '%s' formato:altezzaicona:rimuovo '%s'salva archiviazione prima di aggiornare (raccomandato)secondiimposta iconamostra linee nella vista ad alberousa icone GTK stock nella barra degli strumentiusa barra degli strumenti minimausa suggerimenti del righello nella listalarghezzakeepnote-0.7.8/keepnote/rc/locale/pl_PL.UTF8/0000755000175000017500000000000011727170645016734 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/pl_PL.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020521 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/pl_PL.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000006365711677423605022712 0ustar razraz~  ( + . 1 4 7 : = @ C F I L O R U X a d g 0j       %  !/!C!U!l!!!!F!""5"E" a"o"""" """"" # # # &#4#;#Q#l##$#&# ##$*$ E$f$~$$ $$$ $$$ % %*% ;%+H% t% % %% %% %%%&,&H&f&&&&&& & &, '-9'g' y' ''' '.'2(8F( (*(,(( ) )()0)J)P)c)P))!)* '*H*h*~* *"** ** + %+0+ F+S+[+ ,, ,-, >,J,],r,,, ,0,0,- *- 7-D- V-b-i--------. .%.7.R.+g.. . ... . ..!.1!/(S/(|/&/// 0 00"*0 M0!W00y0&0000 0 00 11 "1 .181A1 U1 a1n11111111#2+2=2L2j222 222222 33 '353-Q3 3 3 3 333 3 3 333 4#434D4V4 i4"v44 4 444445 55#45,X5C505'5%"6UH6T66`75758 T87u8+8 88 889*9<9Q9f9{9999999::)q;;%;0;=;7<><E<W<g<m< s<<<<<<<<<<<====(=,=2=C=K= Z= f=q= == === = =====>> ->:>L>[>c> u>> >>>> >> >>? ?"?)?=?E? K? V?)d??? ?????@@#A B4B7B:B=B@BCBFBIBLBOBRBUBXB[B^BaBdBmBpBsB;vBBBB BBB BBC$CBC^CrCC CCCbCCDLDaD!wDDD DDD D"E-+EYE*oE EEEEEE&F-F#IF/mF/F&F"F(G)@GjG!G"GGGG H*HIH aH nH{H H'H HHHH I" ICITI!jIII I$IJ-J!IJkJJJ J6J4JK.K >K JK#TK!xK4K/KFKFL+eL+LLLLLM#M*M$FMUkMM&MN'%N%MNsN%N)N)N O &O3O!HOjOyOO OO[PjPPPP P PQ Q2QQQ0YQ0QQQQR R%R+R?R]RmRRRRRRRRS.S@CSSSSSS S S(S,T$3TXTvT"T%T)TU UU%5U[U(nU&U"UU U U U V V V'V ?V MVZVjVVVVV V VVW.W?W&SWzWW"WWW W X"X3XLX^XxXXXXX:XYY0YEYYYwYYYYYYYYZZ,Z ?Z&IZpZ ZZZ ZZZZ Z![8[/W[D[:[6\0>\Ro\Q\l]^]A]""^*E^5p^;^ ^^ __*_:_N_e_~__'__*_`/` J`k`8a4a.b3b.EbEtbb bbbb b c c(c@cIc Xcecccccccc ccc ccdd0dDdSdhd ldzdd dd ddddde3eHe ]ekesee eee eeef %f3fPf Xfcfjf ffff2ff fgg/1g&agg g[ba,Fn{8q(25A?#3_^> t=pV@d"]c)M0\|io<P 7JIh5j}sCr%cZ=y`L >?w } \b3Et4LHAwW-D^p&-z_~RBmm&f{68nUS*ElQfFs*X[94v.CW(x~GQVuux/.H,%eT0!;O 1$/#; oeTMDXB6i]$2N '1k)SayY"j:|7ZkPKY<d'K` 9+vG@U l+I!JrhzggR:ONq failed dependency: %s skipping extension '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d pages%j%m%p%s was unable to remove temp file for screenshot%w%x%y(Untitled)1 page50All Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)Alternative index location:Always on topApplication Font Size:AttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot read notebook preferences %sCannot rename '%s' to '%s'Cannot save notebook preferencesCannot save preferencesCannot write meta dataCase _SensitiveCenter AlignChange _Bg ColorChange _Fg ColorChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose WindowClose _TabClose a tabClose the current notebookClose windowCollapse A_ll Child NotesCopy _TreeCopy entire treeCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Delete NoteDifferent year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo not have permission to read folder contents: %sDo you want to delete this note and all of its children?Do you want to delete this note?Do you want to install the extension "%s"?Do you want to uninstall the extension "%s"?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledEnabling new extensions: ErrorError during indexError occurred during backup.Error occurred while opening file with %s. program: '%s' file: '%s' error: %sError reading meta data fileError reading meta data file '%s'Error saving notebookError while attaching file '%s'.Error while attaching files %s.Error while updating.Expand _All Child NotesExtension "%s" is now installed.Extension "%s" is now uninstalled.Extension UninstallExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _New Image...Insert _Screenshot...Insert a new imageInstall New ExtensionInstall SuccessfulItalicJustify AlignKeep aspect ratioKeepNote Extension (*.kne)KeepNote PreferencesKeepNote can only uninstall user extensionsLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Loading...Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinimize on startMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify '%s' program in Helper ApplicationsMust specify a filename for the image.NewNew FileNew IconNew ImageNew NotebookNew PageNew WindowNew _Child PageNew _FolderNew _PageNew _TabNo Default NotebookNo WrappingNo _WrappingNo notes are selected.No version tag foundNotebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen a new tabOpen a new windowOpen an existing notebookOpening notebookPallete:Paste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignRoot tag is not 'node'S_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorSnap:Standard IconsStart a new notebookStrikeSwitch to next tabSwitch to previous tabThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program did not create the necessary image file '%s'The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Unable to determine note type.Unable to install extension '%s'Unable to uninstall extension. Do not have permission.Unable to uninstall unknown extension '%s'.UnderlineUnexpected errorUninstallUninstall SuccessfulUnknown version stringUpdating NotebookUpdating notebook...Use current notebookUse system tray iconVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView Preference Files...View _Error Log...Visible on all desktopsWeek number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]WindowYear with century as a decimal numberYear without century as a decimal number [00,99]You must specify a Screen Shot program in Application Options_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Next Tab_Ok_Open File_Open Notebook from URL_Open Notebook..._Preferences_Previous Tab_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image..._View Note Ascommand '%s' already existsdelete iconenabling extension '%s' format:gtk-cancelgtk-okgtk-revert-to-savedheight:icon:open-icon:removing '%s'save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: Keep Note 0.6.4 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2011-11-26 02:36+0100 Last-Translator: Bernard Baraniewski Language-Team: Bernard Baraniewski > Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Poedit-Language: Polish X-Poedit-Country: POLAND X-Poedit-SourceCharset: utf-8 niespełniona zależność: %s pominięto rozszerzenie '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d stron%j%m%p%s nie mógł usunąć pliku tymczasowego ze zrzutem ekranu%w%x%y(Bez Tytułu)1 strona50Wszystkie Dostępne IkonyFormat daty i czasuData i CzasOgólne Opcje KeepNoteZarządzanie ikonamiWęzeł ikonAktualizacja NotesuSzybki wybór ikonStartTen Notes_Podgląd Obrazu...uwaga: kopia bezpieczeństwa i aktualizacja mogą trwać dłużej przy większych notesach.Znak "%"Zastosuj Kolo_r TłaWszystkie pliki (*.*)Alternatywne położenie indeksu:Zawsze na wierzchuWielkość Fontu aplikacji:ZałącznikZałącz Plik...Załącz plik do notesuAutozapis:Zapis Kopii Bezpieczeństwa NotesuZapis kopii bezpieczeństwa starego notesu...Kopia BezpieczeństwaAnulowano wykonanie kopii bezpieczeństwa.PogrubićPrzeglądaj...Lista NieponumerowanaWyrównaj _CentralnieAnulujNie mogę skopiować pliku '%s'Nie potrafię stworzyć katalogu KoszaNie mogę stworzyć węzłaNie potrafię znaleźć notesu '%s'Nie można zainicjalizować pliku richtext '%s'Nie można otworzyć notesu (zbyt stara wersja)Nie mogę odczytać preferencji notesuNie mogę odczytać preferencji %sNie można zmienić nazwy z '%s' na '%s'Nie potrafię zapisać preferencji notesuNie mogę zapisać preferencjiNie potrafię zapisać metadanychWrażliwy na _duże i małe literyWyrównaj CentralnieZmień kolor tłaZmień kolor pierwszego planuWybierz %sWskaż Nazwę Kopii Bezpieczeństwa NotesuWybierz Domyślny NotesWybór fontuWybór IkonyWybór Otwartej IkonyWybór _FontuWybierz inny katalog dla indeksu notesuZamknij OknoZamknij Zakła_dkęZamknij zakładkęZamknij bieżący notesZamknij OknoZ_wiń Wszystkie Notesy PodrzędneSkopiuj _DrzewkoSkopiuj całe drzewkoNie mogę utworzyć nowego notesuNie mogę opróżnić kosza.Nie mogę wstawić obrazu '%s'Nie mogę otworzyć notesu '%s'.Nie mogę otworzyć logu z błędamiNie moge zapisać obrazu '%s'.Nie mogłem zapisać notesuStwórz nową podrzędną stronęStwórz nowy katalogStwórz nową stronęUtworzono '%s'Data i czasDzień miesiąca w postaci liczb dziesiętnych [01,31]Dzień _roku w postaci liczb dziesiętnych [001,366]Domyślny Notes:Domyślny font:Usuń NotesInny rok:Nie masz uprawnień aby przenieśćNie masz uprawnień aby skasowaćNie masz uprawnień aby czytać zawartość kataloguNie masz uprawnień do przeglądania folderu %sCzy chcesz usunąć ten notes wraz z przypisanymi do niego katalogami?Czy chcesz usunąć ten notes?Czy chcesz zainstalować rozszerzenie "%s"?Czy chcesz odinstalować rozszerzenie "%s"?Przeciągnij i Upuść Test..._Rozwiń NotesOróżnij _KoszAktywneUaktywniono nowe rozszerzenie: BłądBłąd podczas indeksowaniaBłąd zapisu kopii bezpieczeństwa.Wystąpił błąd podczas otwarcia pliku z %s. program: '%s' plik: '%s' błąd: %sBłąd odczytu pliku metadanychBłąd odczytu metadanych z pliku '%s'Błąd podczas zapisu notesuBłąd podczas załączania pliku '%s'.Błąd podczas załączania pliku %s.Błąd podczas aktualizacji.Rozwiń _Wszystkie Podrzędne NotatkiRozszerzenie "%s" zostało zainstalowane.Rozszerzenie "%s" zostało odinstalowane.Odinstalowanie RozszerzeniaRozszerzeniaBłąd formatu plikuZnajdź _Wcześniej na Stronie...Znajdź Tekst:Znajdź _Dalej na Stronie...Znajdź/ZastąpS_formatujTutaj możesz rozszerzyć format pieczątki czasu na liście z 4 różnymi scenariuszami (n.p. jak wyświetlić pieczątkę czasu dzisiejszą, tego miesiąca, tego roku)Idź do _LinkuPrzejdź do N_astępnej NotatkiIdź do _EdytoraIdź do Widoku _ListyPrzejdź do _NotatkiPrzejdź do _Nadrzędnej NotatkiPrzejdź do Po_przedniej NotatkiIdź do Widoku _DrzewkaPomocne AplikacjeNie wyświetlaj w pasku zadańPionowoZegar 12 godzinny jako cyfry dziesiętne [01,12]Zegar 24 godzinny jako cyfry dziesiętne [00,23]Zwiększyć Wys_okość FontuZ_mniejszyć WcięcieZwiększ_yć WcięcieIndeksacja notesuIndeksacja...WstawWstaw Obraz z PlikuWstaw _Horyzontalny SeparatorWstaw _Obraz...Wstaw N_owy Obraz...Wstaw Z_rzut Ekranu...Wstaw nowy obrazekZainstaluj Nowe RozszerzenieInstalacja pomyślnaKursywaWyrównaj do BrzegówZachować proporcjeRozszerzenie KeepNote (*.kne)Preferencje KeepNoteKeepNote może odinstalowywać jedynie rozszerzenia użytkownikaJęzykJęzyk:Wyrównaj do LewejLi_nkUłożenie okna list:Wczytano '%s'Wczytuję...Skrócona nazwa miesiąca - jak w LocaleSkrócona nazwa dnia tygodnia - jak w LocaleSkrócona data i czas - jak w LocaleSkrócona data - jak w LocaleSkrócony czas - jak w LocaleOdpowiednik AM i PM - jak w LocalePełny zapis miesiąca - jak w LocalePełny zapis dnia tygodnia - jak w LocaleWyglądUtwórz LinkMinimalizuj przy starcieMinuty jako cyfry dziesiętne [00,59]Stała SzerokośćMiesiące jako cyfry dziesiętne [01,12]Wskaż program '%s' w Aplikacji PomocyMusisz podać nazwę pliku obrazu.NowyNowy PlikNowa IkonaNowy ObrazNowy NotesNowa StronaNowe OknoNowa _Podrzędna StronaNowy _KatalogNowa _StronaNowa Za_kładkaBrak Domyślnego NotesuBez Zawijania TekstuBez Za_wijania TekstuNie wybrano notatki.Nie znaleziono znacznika wersjiNotes (*.nbk)Opcje NotesuAktualizacja NotesuNotes Został ZaktualizowanyNotes zamkniętyNotes zmodyfikowanoDane preferencji notesu są uszkodzoneNotes przeładowanoNotes zapisanyNotes zaktualizowano z powodzeniemDedykowane ikonki NotesuOtwórzOtwórz ostatnio otwierany NotesOtwórz NotesOtwórz ostat_nio otwierane NotesyOtwórz nową zakładkęOtwórz nowe oknoOtwórz istniejący notesOtwarcie notesuPaleta:Wklej jako Prosty TekstZamknij KeepNotePrzeładuj bieżący notesPrzeładowanie działa tylko wtedy gdy notes jest otwarty.Zamień Tekst:Zamień _WszystkoZmień Wymiar ObrazuWyrównaj do PrawejGłówny tag nie jest 'node'Przekr_eślenieTego samego dnia:Tego samego miesiąca:Tego samego roku:ZapiszZapisz obraz jako...Zapisz bieżący notesSzukaj _NaprzódPrzeszukaj Cały NotesSzukaj _WsteczPrzeszukuję notesSzukam...Sekundy jako cyfry dziesiętne [00,61]Ustaw Kolor TłaUstaw FontUstaw Wysokość FontuUstaw Kolor TekstuWyrównaj:Standardowe IkonyStwórz nowy notesPrzekreśleniePrzejdź do następnej zakładkiPowróć do poprzedniej zakładkiKosz nie może być usunięty.Katalog Kosza musi być katalogiem nadrzędnym.Program do Zrzutu Zawartości Ekranu nie utworzył pliku obrazu %s'Program do zrzutu zawartości ekranu zwrócił błąd: %sKatalog najwyższego poziomu nie może być usunięty.Ten węzeł nie stanowi części żadnego notesuTen notes jest w wersji %d i musi być znowelizowany do wersji %d przed otwarciem.Ten Notes jest w wersji 1 i musi być zaktualizowany do wersji 2 przed otwarciem.Sekcja zawierająca opcje zapisane (n.p. font notesu). Podsekcje dotyczą każdego notesu obecnie otwartego.Wersja %s nie może odczytać tego notesu. Ten notes jest w wersji %d. %s może odczytać %d.Nazwa Strefy Czasowej (pozostawić pustą linię gdy nie wybrano)Nie można określić typu notesu Nie można zainstalować rozszerzenia '%s'Nie masz uprawnień aby odinstalować to rozszerzenieNie można odinstalować nierozpoznanego rozszerzenia '%s'.PodkreślenieNiespodziewany błądOdinstalujOdinstalowano pomyślnieNieznana wersjaAktualizacja NotesuAktualizacja notesu...Użyj bieżącego NotesuUżyj ikonek systemowychPoziomoPodgląd Notesu w Przeglądarce PlikówOtwórz Notes w Edytorze TekstuOtwórz Notes w Przeglądarce InternetowejPokaż Pliki Konfiguracji...Po_każ log z błędami...Widoczny na wszystkich pulpitachLiczba tygodni w roku (Poniedziałek jest pierwszym dniem tygodnia) jako liczba dziesiętna [00,53]. Wszystkie dni w nowym roku poprzedzające pierwszy poniedziałek uznaje się za będące w tygodniu 0Liczba tygodni w roku (Sobota jest pierwszym dniem tygodnia) jako liczba dziesiętna [00,53]. Wszystkie dni w nowym roku poprzedzające pierwszą sobotę uznaje się za będące w tygodniu 0Dzień tygodnia jako liczba dzisiętna [0(Sobota),6]OknoPełny zapis rokuZapis roku skrótem - 2 ostatnie cyfry [00,99]Proszę wskazać program do Zrzutu Zawartości Ekranu w preferencjach_Info_ZastosujZ_astosuj Kolor TekstuZałą_czyć Plik..._Cofnij_PogrubićLista _Nieponumerowana_AnulujZmień Ikonkę _NotatkiZ_amknijZ_amknij Notes_Zwiń NotesZmn_iejszyć Wysokość Fontu_KasujE_dytuj_Edytuj obraz..._Eksport Notes_Plik_SzukajZ_najdź na Stronie..._Naprzód_IdźPo_moc_Import Notes_KursywaWyrównaj do _Brzegów_Wyrównaj do LewejS_tała Szerokość_Nowy Notes...Nastę_pna Zakładka_OkO_twórz Plik_Otwórz Notes z adresu URL_Otwórz Notes...Pre_ferencjeP_oprzednia Zakładka_Wyjście_Przeładuj Notes_Zmień NazwęZa_stąpZ_astąp na Stronie..._Zmień wielkość obrazu...Wyrównaj do _PrawejZapisz Obraz Jako...Zapi_sz NotesSz_ukajP_rzeszukaj Wszystkie Notesy_Sprawdź PisownięNarzę_dziaPod_kreślenieOdś_wież Indeks Notesu_Podgląd_Podgląd obrazu...Pokaż Notes jakoPolecenie '%s' już istniejeskasuj ikonęaktywacja rozszerzenia '%s' format:gtk-skasujgtk-okgtk-powróć-do-zapisanegowysokość:ikonka:otwórz-ikonkę:usunięcie '%s'zapisz kopię bezp. przed aktualizacją (zalecane)sekundyustaw ikonkęustaw otwartą-ikonkępokaż linie w drzewkuużyj ikonek w pasku narzędziowym z motywu GTKużyj minimalnego paska narzędziowegoużyj przewijak w drzewkuszerokość:keepnote-0.7.8/keepnote/rc/locale/fr_FR.UTF8/0000755000175000017500000000000011727170645016724 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/fr_FR.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020511 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/fr_FR.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000006544411677423605022676 0ustar razrazy 0 3 6 9 < G N Q %l      !!+!@!FV!!!!! !""%"4" R"\"p"""" " " """"##$1#&V# }#### ##$-$ =$ J$T$p$ $ $$ $+$ $ $ %% )%6% P%[%l%%%%%%&)&A&U& g& t&,&-&& & & '' 9'.Z'2'8' '*(,A(n( ( ((((((P(H)!e)) )))) *"-*P* d*o** ** *** x++ ++ +++++ , ,0*,0[,, , ,, ,,,,--4-J-]-s-- ----+- . . .'.-. >. J.U.!u.1.(.(.&/B/[/ v/ //"/ /!/0/& 0G0K0T0 ]0 g0t0 }00 0 000 0 0001!121B1[1k1#}1111111 2242C2U2o22 22-2 2 2 3 3 373 ?3 I3 U3`3e3v33333 3"34 4 )474F4L4[4p4w44#4,4C4065'g5%5U5T 6`6` 75l77 777+8 F8P8 a8k88888888 9%9>9W9j990:):;%;05;=f;;;;;;; ;;;<<<-<A<I<O<^<o<u<{<<<<<<< < << << <= = '=5=;=L=T=]=q= ===== == ==>>> 1>=>V> ^>i>p>>> > >)>>> >> ?*?>?Z?a?$+A PAqAtAwAzA}AAAAAAAAAAAAAAAADAAAA BBB%B->BlB&BBBBBC(C9CnUCC%CC-D?D/XDDDDDD!D EE4E 9E FETEjE$rE(EE$E,F90F1jF@F#F8G7:G&rGGG G6G#H(H@H%\HH>HHHH I"I$5IZIqI'I!I'I'J-*J+XJ'J"JJJ K K)K/FKvKKKK K)K1 L6>L>uL"L'L,L,MIMhM|M'MM,M.MiN$yN4N:N?O=NO0O%O$O(P1P JPUP(qP P)PPPPQQQRR0RJR"iRR)R R"R"R!SASVSlSSS)SSSS!T:TVTvTTTTTTDUGU OUYU nU"yU UUU)UDU$CVhVIVV!V W W!W9WIW!^WEW(WWWXX&X 5XCXUXnXXX X%X&X! Y-YLY[YmY Y YY=YZZ7&Z^Z}Z!ZZZZZ[ [4[N[_["|[ [[[[+[\ \ (\5\ D\P\l\\\\\ \\ ]#]*];] M]Y]k]]]]&]E]E3^>y^+^&^c _ao__d`Na*ca'aDa6a 2bc'ic)c"c(cBdEJd7dd"d%dQe le ve"ee eeeeeeff 0f Qf\fefzff fff fff ff g"g8gKg[g_grggggg g gggh*hGh ^hjhhh h!h hhhi*iJi Si^iei yiiiiEij j'*j,Rj<j$j8j kd=iHF'rvwf^z*/,q%%y{b'(-"lS#B p?hsMGbdoe^A_@Tfu7,5C&PYk $1gL8uU jR.|l t EaDQ:SA?~Iaj\:wo`[KUP0}@ sN +LB2gcmy4XO69 Oq3m>M+")XN]2VD8W-\cn/*tEx`C<>40vnhr3R&6[FZp)G<I$H1e=! VJ5ZK_]J9x(YWk.#QT;7i;! failed dependency: %s skipping extension '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d pages%j%m%p%s was unable to remove temp file for screenshot%w%x%y(Untitled)1 page50All Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)Alternative index location:Always on topApplication Font Size:AttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot read notebook preferences %sCannot rename '%s' to '%s'Cannot save notebook preferencesCannot save preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose WindowClose _TabClose a tabClose the current notebookClose windowCollapse A_ll Child NotesCopy _TreeCopy entire treeCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Delete NoteDifferent year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo not have permission to read folder contents: %sDo you want to delete this note and all of its children?Do you want to delete this note?Do you want to install the extension "%s"?Do you want to uninstall the extension "%s"?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabledEnabling new extensions: ErrorError during indexError occurred during backup.Error occurred while opening file with %s. program: '%s' file: '%s' error: %sError reading meta data fileError reading meta data file '%s'Error saving notebookError while attaching file '%s'.Error while attaching files %s.Error while updating.Expand _All Child NotesExtension "%s" is now installed.Extension "%s" is now uninstalled.Extension UninstallExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _New Image...Insert _Screenshot...Insert a new imageInstall New ExtensionInstall SuccessfulItalicJustify AlignKeep aspect ratioKeepNote Extension (*.kne)KeepNote PreferencesKeepNote can only uninstall user extensionsLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Loading...Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinimize on startMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify '%s' program in Helper ApplicationsMust specify a filename for the image.NewNew FileNew IconNew ImageNew NotebookNew PageNew WindowNew _Child PageNew _FolderNew _PageNew _TabNo Default NotebookNo WrappingNo _WrappingNo notes are selected.No version tag foundNotebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen a new tabOpen a new windowOpen an existing notebookOpening notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignRoot tag is not 'node'S_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorSnap:Standard IconsStart a new notebookStrikeSwitch to next tabSwitch to previous tabThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program did not create the necessary image file '%s'The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Unable to determine note type.Unable to install extension '%s'Unable to uninstall extension. Do not have permission.Unable to uninstall unknown extension '%s'.UnderlineUnexpected errorUninstallUninstall SuccessfulUnknown version stringUpdating NotebookUpdating notebook...Use current notebookUse system tray iconVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView Preference Files...View _Error Log...Visible on all desktopsWeek number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]WindowYear with century as a decimal numberYear without century as a decimal number [00,99]You must specify a Screen Shot program in Application Options_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Next Tab_Ok_Open File_Open Notebook..._Preferences_Previous Tab_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...command '%s' already existsdelete iconenabling extension '%s' format:gtk-cancelgtk-okgtk-revert-to-savedheight:icon:open-icon:removing '%s'save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.5.3 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2011-09-19 12:39+0100 Last-Translator: Sebastien KALT Language-Team: French Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Poedit-Language: French X-Poedit-Country: FRANCE X-Poedit-Bookmarks: 59,233,-1,-1,-1,-1,-1,-1,-1,-1 dépendance non satisfaite : %s omission de l'extension '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d pages%j%m%p%s n'a pu supprimer le fichier provisoirement créé pour la capture%w%x%y(Sans titre)1 page50Toutes les icônes disponiblesCode de formatage des date & heureDate et heureOptions générales de KeepNoteGérer les icônesIcônes des nœudsMise à jour du carnetSélection d'icônesDémarrageCe carnet_Afficher l'image...remarque : la sauvegarde et la mise à jour peuvent prendre du temps pour les carnets de grande taille.Caractère "%"Appliquer la couleur d'arrière-p_lanTous les fichiers (*.*)Emplacement alternatif de l'index du carnet :Toujours au premier planTaille de la police de caractère du logiciel :JoindreJoindre un fichier ...Joindre un fichier au carnetSauvegarde automatique :Sauvegarde du carnetSauvegarde de l'ancien carnet ...SauvegardeSauvegarde annulée.GrasParcourir...ÉnumérationAlignement au _centreAnnulerLe fichier '%s' ne peut être copiéLe dossier Corbeille n'a pu être crééImpossible de créer le nœudImpossible de trouver le carnet '%s'Impossible d'initialiser le texte riche '%s'La version du carnet est trop ancienne pour être ouverteImpossible d'accéder aux préférences du carnetImpossible d'accéder au réglage des préférences du carnet %sImpossible de renommer '%s' en '%s'Les préférences du carnet n'ont pu être sauvegardéesImpossible de sauvegarder le réglage des préférencesImpossible d'écrire les métadonnéesRespecter la ca_sseAlignement au centreChoisir %sSaisissez un nom pour la copie de sauvegarde du carnetSélectionner le carnet par défautSélectionner la policeChoisir l'icône des nœudsChoisir l'icône des nœuds dépliésChoisir la polic_eSélectionner un autre dossier pour stocker l'index du carnet Fermer la fenêtreFermer _l'ongletFermer un ongletFermer le carnet courantFermer la fenêtreMasquer t_outes les notes affiliéesCopier l'_arborescenceCopier l'arborescence entièreImpossible de créer un nouveau carnet.La corbeille n'a pu être vidée.L'image '%s' n'a pas pu être inséréeLe carnet '%s' n'a pas pu être ouvert.Le journal d'erreurs n'a pas pu être ouvert.L'image '%s' n'a pas pu être enregistrée.Le carnet n'a pas pu être enregistré.Créer une nouvelle page affiliéeCréer un nouveau dossierCréer une nouvelle page'%s' crééDate et heureJour du mois au format numérique [01,31]Jour de l'année au format numérique [001,366]Carnet par défaut :Police par défaut :Supprimer la noteAutre année :Permission de déplacer refuséeVous n'avez pas les droits pour supprimerPermission de lire le contenu du dossier refuséeVous n'avez pas les droits pour accéder au dossier %sVoulez-vous supprimer cette note et toutes celles affiliées ?Voulez-vous supprimer cette note ?Voulez-vous installer l'extension "%s"?Voulez-vous désinstaller l'extension "%s"?Test du glisser-déposer ..._Afficher les notes affiliées_Vider la corbeilleActivéActivation des nouvelles extensions : ErreurUne erreur est survenue pendant l'indexationUne erreur est survenue pendant la sauvegarde.Une erreur est survenue à l'ouverture du fichier avec %s. programme : '%s' fichier : '%s' erreur : %sImpossible de lire les métadonnéesImpossible de lire les métadonnées du fichier '%s'Une erreur est survenue pendant l'enregistrement du carnetUne erreur est survenue en essayant d'attacher le fichier '%s'.Une erreur est survenue en essayant d'attacher le fichier %s.Une erreur est survenue pendant la mise à jour.Afficher _toutes les notes affiliéesL'extension "%s" a été installée.L'extension "%s" a été désinstallée.Extension désinstalléeExtensionsErreur de format de fichierRechercher en _remontant dans la page...Rechercher :Rechercher en _descendant dans la page...Rechercher/RemplacerFo_rmatDéfinissez ici le format des dates et heures affichées dans la liste des notes suivant 4 scénarios différents (ex : afficher la date des notes relativement au jour, au mois ou à l'année en cours)Suivre le l_ienAller à la note sui_vanteAller à l'édit_eur de notesAller à la _liste des notesAller à la _NoteAller à la note pa_renteAller à la note pré_cédenteAller à l'ar_borescence des notesApplications associéesNe pas afficher dans la barre des tâchesHorizontalHeures au format 12 heures [01,12]Heures au format 24 heures [00,23]_Augmenter la taille de la police_Diminuer le retraitA_ugmenter le retraitIndexation du carnetIndexation ...InsérerInsérer une image à partir d'un fichierInsérer un trait _horizontalInsérer une _image...Insérer une _nouvelle image...Insérer une _capture d'écran...Insérer une nouvelle imageInstaller la nouvelle extensionInstallation réussieItaliqueAlignement justifiéConserver le ratio de l'imageExtension pour KeepNote (*.kne)Préférences de KeepNoteSeules les extensions propres à l'utilisateur sont désinstallablesLangageLangage :Alignement à gauche_HyperlienAgencement de la liste des notes :'%s' ouvertChargement ...Nom abrégé du mois (déc.)Nom abrégé du jour de la semaine (dim.)Date abrégée, heure et fuseau horaire (Sam. 04 Nov. 12:02:33 CEST)Date au format numérique [jj/mm/aa]Heure précise [hh:mm:ss]Indicateur de demi-journée pour système horaire de 12 heures (AM ou PM)Nom complet du moisNom complet du jour de la semaineApparenceCréer un hyperlien Minimiser au démarrageMinutes [00,59]Fonte à chasse fixeMois au format numérique [01,12]Vous devez préciser le logiciel de '%s' dans Applications associéesSpécifiez un nom pour le fichier image.NouveauNouveau fichierNouvelle icôneNouvelle imageNouveau carnetNouvelle pageNouvelle fenêtreNouvelle page aff_iliéeNouveau _dossierNouvelle _pageNouvel _ongletAucun carnet défini par défautPas de retour à la ligne automatiquePas de retour à la ligne automati_queAucune note n'est sélectionnée.Numéro de version non trouvéCarnet (*.nbk)Options du carnetMise à jour du carnetMise à jour du carnet terminéeCarnet ferméCarnet modifiéLes informations de préférences du carnet sont endommagéesCarnet actualiséCarnet enregistréLa mise à jour du carnet s'est déroulée avec succèsIcônes spécifiques du carnetOuvrirOuvrir le dernier carnet utiliséOuvrir un carnetOuvrir un carnet réce_ntOuvrir un nouvel ongletOuvrir une nouvelle fenêtreOuvrir un carnet existantOuverture du carnetColler comme texte simpleQuitter KeepNoteActualiser le carnet courantAucun carnet ouvert à actualiser.Remplacer :Remplacer _toutRedimensionner l'imageAlignement à droiteL'étiquette de la racine n'est pas 'nœud'_BarréMême jour :Même mois :Même année :EnregistrerEnregistrer l'image sous...Enregistrer le carnet courantChercher en _descendantRechercher dans les notesChercher en _remontantRecherche dans le carnetRecherche ...Secondes [00,61]Couleur d'arrière-planPoliceTaille de policeCouleur de policeGradation :Icônes standardsCommencer un nouveau carnetBarréAller à l'onglet suivantAller à l'onglet précédentLa corbeille ne peut être supprimée.Le dossier de la corbeille doit être à la racine de l'arborescence Le logiciel de capture d'écran n'a pas créé le fichier '%s' requisLe programme de capture d'écran a rencontré une erreur : %sLe premier dossier ne peut être supprimé.Ce nœud ne fait partie d'aucun carnetLa version %d du format de ce carnet doit être mise à jour vers la version %d avant de continuer.La version 1 du format de ce carnet doit être mise à jour vers la version 2 avant de continuer.Cette section contient des options qui sont sauvegardées de manière distincte pour chaque carnet (ex : police de caractère spécifique à un carnet). Une sous-section apparaîtra pour chaque carnet actuellement ouvert.Cette version de %s ne peut lire ce carnet. La version du carnet est %d. %s peut seulement lire %d.Nom du fuseau horaire (nul si le fuseau horaire ne peut pas être déterminé)Impossible de déterminer le type de note.Impossible d'installer l'extension '%s'Impossible de désinstaller l'extension. Vous n'avez pas les droits.Impossible de désinstaller l'extension inconnue '%s'.SoulignéErreur inattendueDésinstallerDésinstallation réussieVersion de chaîne inconnueMise à jour du carnetMise à jour du carnet ...Utiliser le carnet courantAfficher dans la zone de notificationVerticalAfficher la note dans le navigateur de fichiersAfficher la note dans l'éditeur de textesAfficher la note dans le navigateur WebAfficher le fichier des préférences ...Afficher le _journal d'erreurs ...Afficher sur tous les espaces de travailNuméro de la semaine dans l'année débutant par le lundi [00,53]Numéro de la semaine dans l'année débutant par le dimanche [00,53]Jour de la semaine au format numérique [0(dimanche),6]FenêtreNuméro complet de l'année (1970)Numéro abrégé de l'année [00,99]Vous devez préciser un logiciel de capture d'écran dans Applications associées_A propos_AppliquerAppliquer la couleur de la _police_Joindre un fichier..._PrécédentG_rasÉ_numérationA_nnulerCh_anger l'icône_Fermer_Fermer le carnet_Masquer les notes affiliéesDi_minuer la taille de la police_Supprimer_Edition_Modifier l'image..._Exporter le carnet_FichierRec_hercherRechercher dans la _page..._SuivantA_ller àAid_e_Importer un carnet_ItaliqueAlignement _justifiéAlignement à _gauche_Fonte à chasse fixeNouveau _carnet..._Onglet suivant_OkOuvrir le _fichier_Ouvrir un carnet..._PréférencesOnglet _précédent_Quitter_Actualiser le carnet_RenommerR_emplacerRe_mplacer dans la page..._Redimensionner l'image...Alignement à dr_oite_Enregistrer l'image sous...En_registrer le carnet_RechercherRechercher dans _les notesVérification ortho_graphique_Outils_SoulignéMettre à _jour l'index du carnet_Affichage_Afficher l'image...la commande '%s' existe déjàsupprimer l'icôneactivation de l'extension '%s' format :gtk-cancelgtk-okgtk-revert-to-savedhauteur :icône :icône de nœud déplié : suppression de '%s'Effectuez une copie de sauvegarde avant la mise à jour (recommandé)secondesdéfinir comme icône de nœuddéfinir comme icône de nœud dépliéafficher les pointillés dans l'arborescenceutiliser la collection d'icônes GTK dans la barre d'icônesutiliser la barre d'icônes minimaleteinter les lignes en alternance dans la liste des noteslargeur :keepnote-0.7.8/keepnote/rc/locale/zh_CN.UTF8/0000755000175000017500000000000011727170645016727 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/zh_CN.UTF8/LC_MESSAGES/0000755000175000017500000000000011727170645020514 5ustar razrazkeepnote-0.7.8/keepnote/rc/locale/zh_CN.UTF8/LC_MESSAGES/keepnote.mo0000644000175000017500000005363411677423606022700 0ustar razrazT \0+.14;>%Y -FC $8SZk p z $& E f     ! ,! 8!D! U!+b!!!!!!"3"L"g"""" " ","-#4# F# T#`#p# #.#8# $*:$,e$$ $ $$$$P%Q%n% %%% %"%& +&6&H& b&m& &&& ?'K' \'j' {'''''' '0'0"(S( g( t(( ((((((()$):) A)O)a)|)) ) ))) ) ))!)1*(Q*(z*&*** * +"+ 9+!C+0e+&+++ + + ++ + ,, &, 2,?,V,g,x,,,,#,,,-&->-C- V-d-z---- ---- #. 1. >. K.W. _. i. u........ ."/&/ ;/ I/W/f/u//#/,/C/0&0'W0%0U0T0P1`15\2 272+2 3!333H3]3f3333334);5%e505=55666*606 66C6K6]6d6t666666666666677 7 )747E7 I7T7 f7s7y77777 77777 88 8'8>8D8S8 o8{88 88888 8 8)899 $929I9h9|9991;F;\;_;b;e;h;k;n;q;t;w;z;};;;;;;;;;*;;;;;;;);<4<R<f<z<< <<<Z<F=[=o===== ===> >> > %>2>C>J>b>x>>#>#>>??;?N?a? u? ??? ? ???!@$@:@T@p@@@@@@ A"A5A HATA&dA(AA A A AABB63BjB$BBBBBBC!CI@CCCCCCD!D =DJDQDdD DD D DDHEYEvEEEEEE FF)F#0F!TFvF F FF FFFFFGG7GGGZG aGnG~GGG G G GGG GGH$(HMHiHHHHH HH I I*&I!QIsI zI II III IIIJJ%J7JGJWJmJJJJJJJKK*K:KTKmKKKKK*K LL"L 5L ?LMLUL]LeLlLLLLLL LLMM%M8M KMXM qM!{M-M8M#N$(N$MNJrNGNOZO/O$P$S RS ]ShS ySS SSSS S SST T %T0T GTRT VTaT uTT TTT TTT T U U 'U 5U@UZU tUUU UUU U UU VV2V NV[VnV vV VVVV VV VV VWW$1WVW$oWWP< @ - pelFJHrRA 'E5>GsJ  Nf|*^0()06NBA2d+ ;4t=98EQ$$}T4"(m:/@&<.;Z11MH k`C w L uj#n,39DK?/Y8iC)VqGcL.X+oI:TMU?'67Ba!%2g*!z%[OhKWx_=R-S"PDF#SQv>{7&,IbO\]35~y failed dependency: %s skipping extension '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d pages%j%m%p%s was unable to remove temp file for screenshot%w%x%y1 page50All Available iconsDate & time formatting keyDate and TimeGeneral KeepNote OptionsManage iconsNode iconsNotebook UpdateQuick pick iconsStartupThis Notebook_View Image...note: backup and upgrade may take a while for larger notebooks.A literal "%" characterA_pply Background ColorAll files (*.*)Alternative index location:AttachAttach File...Attach a file to the notebookAutosave:Backing Up NotebookBacking up old notebook...BackupBackup canceled.BoldBrowse...Bullet ListC_enter AlignCancelCannot copy file '%s'Cannot create Trash folderCannot create nodeCannot find notebook '%s'Cannot initialize richtext file '%s'Cannot open notebook (version too old)Cannot read notebook preferencesCannot rename '%s' to '%s'Cannot save notebook preferencesCannot save preferencesCannot write meta dataCase _SensitiveCenter AlignChoose %sChoose Backup Notebook NameChoose Default NotebookChoose FontChoose IconChoose Open IconChoose _FontChoose alternative notebook index directoryClose the current notebookCollapse A_ll Child NotesCould not create new notebook.Could not empty trash.Could not insert image '%s'Could not load notebook '%s'.Could not open error logCould not save image '%s'.Could not save notebook.Create a new child pageCreate a new folderCreate a new pageCreated '%s'Date and TimeDay of the month as a decimal number [01,31]Day of the year as a decimal number [001,366]Default Notebook:Default font:Delete NoteDifferent year:Do not have permission for moveDo not have permission to deleteDo not have permission to read folder contentsDo you want to delete this note and all of its children?Do you want to delete this note?Do you want to install the extension "%s"?Do you want to uninstall the extension "%s"?Drag and Drop Test...E_xpand NoteEmpty _TrashEnabling new extensions: ErrorError occurred during backup.Error occurred while opening file with %s. program: '%s' file: '%s' error: %sError reading meta data fileError saving notebookError while attaching file '%s'.Error while updating.Expand _All Child NotesExtension "%s" is now installed.Extension "%s" is now uninstalled.Extension UninstallExtensionsFile format errorFind Pre_vious In Page...Find Text:Find _Next In Page...Find/ReplaceFo_rmatFrom here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)Go to Lin_kGo to Next N_oteGo to _EditorGo to _List ViewGo to _NoteGo to _Parent NoteGo to _Previous NoteGo to _Tree ViewHelper ApplicationsHide from taskbarHorizontalHour (12-hour clock) as a decimal number [01,12]Hour (24-hour clock) as a decimal number [00,23]Increase Font _SizeIndent Le_ssIndent M_oreIndexing notebookIndexing...InsertInsert Image From FileInsert _Horizontal RuleInsert _Image...Insert _New Image...Insert _Screenshot...Insert a new imageInstall New ExtensionItalicJustify AlignKeep aspect ratioKeepNote Extension (*.kne)KeepNote PreferencesLanguageLanguage:Left AlignLin_kListview Layout:Loaded '%s'Loading...Locale's abbreviated month nameLocale's abbreviated weekday nameLocale's appropriate date and time representationLocale's appropriate date representationLocale's appropriate time representationLocale's equivalent of either AM or PMLocale's full month nameLocale's full weekday nameLook and FeelMake LinkMinute as a decimal number [00,59]MonospaceMonth as a decimal number [01,12]Must specify '%s' program in Helper ApplicationsMust specify a filename for the image.NewNew IconNew ImageNew NotebookNew WindowNew _Child PageNew _FolderNew _PageNo Default NotebookNo WrappingNo _WrappingNo notes are selected.Notebook (*.nbk)Notebook OptionsNotebook UpdateNotebook Update CompleteNotebook closedNotebook modifiedNotebook preference data is corruptNotebook reloadedNotebook savedNotebook updated successfullyNotebook-specific IconsOpenOpen Last NotebookOpen NotebookOpen Re_cent NotebookOpen a new windowOpen an existing notebookOpening notebookPaste As Plain TextQuit KeepNoteReload the current notebookReloading only works when a notebook is open.Replace Text:Replace _AllResize ImageRight AlignS_trikeSame day:Same month:Same year:SaveSave Image As...Save the current notebookSea_rch ForwardSearch All NotesSearch _Backward Searching notebookSearching...Second as a decimal number [00,61]Set Background ColorSet Font FaceSet Font SizeSet Text ColorStandard IconsStart a new notebookStrikeThe Trash folder cannot be deleted.The Trash folder must be a top-level folder.The screenshot program did not create the necessary image file '%s'The screenshot program encountered an error: %sThe top-level folder cannot be deleted.This node is not part of any notebookThis notebook has format version %d and must be updated to version %d before opening.This notebook has format version 1 and must be updated to version 2 before openning.This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.This version of %s cannot read this notebook. The notebook has version %d. %s can only read %d.Time zone name (no characters if no time zone exists)Unable to install extension '%s'Unable to uninstall extension. Do not have permission.Unable to uninstall unknown extension '%s'.UnderlineUpdating NotebookUpdating notebook...Use current notebookVerticalView Note in File ExplorerView Note in Text EditorView Note in Web BrowserView Preference Files...View _Error Log...Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0Weekday as a decimal number [0(Sunday),6]Year with century as a decimal numberYear without century as a decimal number [00,99]You must specify a Screen Shot program in Application Options_About_Apply_Apply Text Color_Attach File..._Back_Bold_Bullet List_Cancel_Change Note Icon_Close_Close Notebook_Collapse Note_Decrease Font Size_Delete_Edit_Edit Image..._Export Notebook_File_Find_Find In Page..._Forward_Go_Help_Import Notebook_Italic_Justify Align_Left Align_Monospace_New Notebook..._Ok_Open File_Open Notebook..._Preferences_Quit_Reload Notebook_Rename_Replace_Replace In Page..._Resize Image..._Right Align_Save Image As..._Save Notebook_Search_Search All Notes_Spell Check_Tools_Underline_Update Notebook Index_View_View Image...command '%s' already existsdelete iconenabling extension '%s' format:gtk-cancelgtk-okgtk-revert-to-savedheight:icon:open-icon:removing '%s'save backup before updating (recommended)secondsset iconset open-iconshow lines in treeviewuse GTK stock icons in toolbaruse minimal toolbaruse ruler hints in listviewwidth:Project-Id-Version: keepnote 0.6.2 Report-Msgid-Bugs-To: POT-Creation-Date: 2011-11-30 21:40-0500 PO-Revision-Date: 2010-03-24 15:32+0800 Last-Translator: 胡达川(hu dachuan) Language-Team: 胡达川(hu dachuan) Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Poedit-Language: Chinese X-Poedit-Country: CHINA 失败的依赖: %s 忽略扩展 '%s': %%%A%B%H%I%M%S%U%W%X%Y%Z%a%b%c%d%d 页%j%m%p%s 不能为屏幕截图移动临时文件%w%x%y1 页50所有可用图标日期 & 时间格式关键字日期和时间常规 KeepNote 选项管理图标节点图标笔记本更新快速选择图标启动这个笔记本查看图片(_V)...提示: 对于较大的笔记本,备份和升级可能会花费跟多的时间。逐字的 "%" 字符应用背景色(_A)所有文件 (*.*)选择一个索引目录:附加附加文件...附加文件到笔记本自动保存:备份笔记本备份旧笔记本...备份备份取消粗体浏览...项目符号中间对齐(_E)取消不能拷贝文件 '%s'无法创建回收站不能创建节点未找到笔记 '%s'不能初始化富文本文件 '%s'不能打开笔记本(版本太旧)不能读笔记本配置不能重命名 '%s' 为 '%s'不能保存笔记本配置不能保存配置不能写元数据区分大小写(_S)中间对齐选择 %s选择备份笔记本名称选择默认笔记本选择字体选择图标选择一个打开图标选择字体(_F)选择一个笔记本索引目录关闭当前笔记本折叠所有子笔记(_L)不能创建新笔记本。不能清空回收站。不能插入图片 '%s'不能载入笔记本 '%s'。不能打开错误日志不能保存图片 '%s'。不能保存笔记本。创建一个新子页创建新文件夹创建一个新页创建 '%s'日期和时间十进制中月份的每一天 [01,31]十进制年份中的每一天 [001,366]默认笔记本:默认字体:删除笔记不同年份:没有移动权限没有删除权限没有读文件夹内容权限你想要删除这个笔记及其所有子笔记么?你想要删除本笔记么?你想要安装这个扩展 "%s"么?你想要卸载扩展 "%s"?拖拽测试...展开笔记(_X)清空回收站(_T)使用一个新扩展: 错误执行备份过程中发生错误用 %s打开文件时发生错误 程序: '%s' 文件: '%s' 错误: %s不能读元数据保存笔记本时错误附加文件时错误 '%s'.更新时发生错误。展开所有子笔记(_A)扩展 "%s" 正在安装。扩展 "%s" 正在卸载。扩展卸载扩展文件格式错误在上一页中查找(_V)...查找文本:在下一页中查找(_N)...查找/替换格式(_R)在这你可以定制4种不同场景中列表视图的时间戳的格式(如:怎样显示今天、本月或者本年的时间戳)打开链接(_K)跳转到下一个笔记(_O)跳转到编辑器(_E)跳转到列表视图(_L)跳转到笔记(_N)跳转到父节点(_P)跳转到上一个笔记(_P)跳转到树形视图(_T)助手应用从任务栏隐藏水平十进制小时 (12小时) [01,12]十进制小时(24小时) [00,23]增加字号(_S)减少缩进增加缩进正在索引笔记本索引中插入从文件插入图片插入水平线(_H)插入图片(_I)...插入新图片(_N)...插入屏幕截图(_S)...插入新图片安装新的扩展斜体两端对齐保持纵横比KeepNote 扩展 (*.kne)KeepNote 首选项语言语言:左对齐链接(_K)列表视图布局:已载入 '%s'载入中...本地月份名称缩写本地周缩写本地适当的日期和时间表示本地适当的日期表示本地适当的时间表示本地类似AM或PM的词汇本地月份名称本地全部周全称外观和感觉创建链接十进制分钟 [00,59]等宽字体M十进制月份 [01,12]必须在助手应用中指定 '%s' 程序必须制定图片的文件名。新建新图标新图片新建笔记本新窗口新建子页(_C)新建文件夹(_F)新建页(_P)没有默认笔记本不自动换行不自动换行(_W)未选择笔记笔记本 (*.nbk)笔记本选项笔记本更新笔记本更新完成笔记本已关闭笔记本已修改笔记本配置数据错误笔记本已重新载入笔记本已保存笔记本更新成功指定笔记本图标打开打开最后访问的笔记打开笔记本最近打开的文档(_C)打开一个新的窗口打开存在的笔记本正在打开笔记本无格式粘贴退出 KeepNote重新加载当前笔记本当笔记本打开时,仅重载工作。替换文本:替换全部(_A)调整图片大小右对齐删除线(_T)当天:当月:当年:保存另存图片为...保存当前笔记本向前查找(_R)搜索所有笔记向后查找(_B)正在查找笔记本搜索中...十进制秒 [00,61]设置背景色设置字体风格设置字体大小设置文本颜色标准图标打开一个新笔记本删除线回收站文件夹不能删除。回收站文件夹必须是最上层文件夹屏幕截图程序不能创建必须的图片文件 '%s'屏幕截图程序遇到错误: %s最上层文件夹不能被删除。这个节点不属于任何笔记本该笔记本拥有格式版本 %d ,在打开前必须更新版本到 %d 该笔记本拥有格式版本1,在打开前必须更新版本到2。这部分包含的选项是保存每一个笔记本的基本选项(如:指定笔记本字体)。字部分显示么一个当前打开的笔记本。本版本 %s 不能读这个笔记本。 当前笔记本有版本 %d。 %s只能读 %d。时区名称(如果没有时区,则无字符)不能安装扩展 '%s'没有权限,不能卸载扩展。不能卸载未知扩展 '%s'.下划线正在更新笔记本正在更新笔记本...使用当前笔记本垂直在文件浏览器中查看笔记在文字编辑器中查看笔记在WEB浏览器中查看笔记查看配置文件...查看错误日志(_E)...十进制一年中周数[00,53] (星期一作为一周的开始)。 在新的一年中 前面的所有天 第一个星期日被作为本周的0十进制一年中周数[00,53] (星期日作为一周的开始)。 在新的一年中 前面的所有天 第一个星期日被作为本周的0十进制周 [0(Sunday),6]十进制有世纪年十进制无世纪年 [00,99]你必须在应用选项中指定屏幕截图程序。关于(_A)应用(_A)应用文本颜色(_A)附加文件(_A)...后退(_B)粗体(_B)项目符号(_B)取消(_C)改变节点图标(_C)关闭(_C)关闭笔记本(_C)折叠笔记(_C)减小字号(_D)删除(_D)编辑(_E)编辑图片(_E)...导出笔记本(_E)文件(_F)查找(_F)在本页内查找(_F)前进(_F)_Go帮助(_H)导入笔记本(_I)斜体(_I)两端对齐(_J)左对齐(_L)等宽字体(_M)新建笔记本(_N)...确定(_O)打开文件(_O)打开笔记本(_O)配置(_P)退出(_Q)重新载入笔记本(_R)重命名(_R)替换(_R)在本页内替换(_R)...调整图片大小(_R)...右对齐(_R)保存图片为(_S)...保存笔记本(_S)搜索(_S)搜索所有笔记(_S)拼写检查(_S)工具(_T)下划线(_U)更新笔记本索引(_U)视图(_V)查看图片(_V)...命令 '%s' 已经存在。删除图标使用扩展 '%s' 格式:gtk-取消gtk-确定gtk-revert-to-saved高度:图标:打开图标:正在移除 '%s'在更新前保存备份(推荐)秒设置图标设置打开图标在树状视图中显示线在工具栏中使用GTK stock图标使用最小化工具栏在列表视图使用规则提示。宽度:keepnote-0.7.8/keepnote/rc/folder.png0000644000175000017500000000103411523163436015713 0ustar razrazPNG  IHDRasRGBbKGD pHYs  tIME!|IDAT8˥1hA73$h(XiatꁅM:Eh.+SYiR&EF=yzL?47BpubKTg/Kr=q Y<Øj lK[wC2< !ƘwYf`a!HJTUP"$UbL$FU>'NmoCƏ j*1%튇)@Q!%5um~ƻ0n {Q;L93` ))u 82U:w0(+hZl ,6&:IENDB`keepnote-0.7.8/keepnote/rc/COPYING0000644000175000017500000004310311523163436014770 0ustar razraz GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program 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. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. keepnote-0.7.8/keepnote/rc/folder-open.png0000644000175000017500000000104411523163436016653 0ustar razrazPNG  IHDRasRGBbKGD"Vz ; pHYs  tIME  "dIDAT8˥1HQϼ3\:I]nT0t*ل⤒&]jBv"Cm2 7 P]=˥}\b?=}יcK1N1 _,%ZHqyZ\:% QZMNe_}>\~Z-,t;|̢x2]fZ7 AA+Y+ Z_XBy!,eLa$8i`[&V|~%)ָ@S{~ylr Sq/AF,VbARTx1@)?XAV ac%5Wޯ?a@O,jt\T`BND<;-@_0szmsoP [YdIENDB`keepnote-0.7.8/keepnote/rc/keepnote.glade0000644000175000017500000050372011655576212016561 0ustar razraz GDK_KEY_RELEASE_MASK | GDK_STRUCTURE_MASK Find/Replace True GDK_WINDOW_TYPE_HINT_DIALOG True True 10 5 True 2 2 5 5 True True Replace Text: True 0 True 1 2 True True 1 2 1 2 True True True 1 2 True 1 Find Text: GTK_JUSTIFY_RIGHT False True 2 2 True 0 1 2 1 2 True True Sea_rch Forward True 0 0 True True GTK_FILL True True Search _Backward True 0 0 True forward_button 1 2 GTK_FILL True True Case _Sensitive True 0 0 True 1 2 GTK_FILL False 1 True False False 2 2 True GTK_BUTTONBOX_END True True _Close True 0 False False True True _Replace True 0 False False 1 True True Replace _All True 0 False False 2 True True True True _Find True 0 False False 3 False GTK_PACK_END 700 450 KeepNote Preferences True GTK_WIN_POS_CENTER_ON_PARENT True GDK_WINDOW_TYPE_HINT_NORMAL False True GTK_ORIENTATION_VERTICAL 2 GTK_ORIENTATION_VERTICAL True 200 True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_IN True True False False True True GTK_POLICY_NEVER GTK_POLICY_AUTOMATIC GTK_SHADOW_IN True GTK_RESIZE_QUEUE GTK_SHADOW_NONE True 5 5 5 5 True True False False 0 0 0 5 1 1 True GTK_BUTTONBOX_END True True _Cancel True 1 False False True True True True _Apply True 0 False False 1 True True True True _Ok True 0 False False 2 False GTK_PACK_END Resize Image True GTK_WIN_POS_CENTER_ON_PARENT GDK_WINDOW_TYPE_HINT_DIALOG True True 10 10 10 10 True 5 True 2 3 5 5 True True 3 1 2 1 2 GTK_FILL True True 3 1 2 GTK_FILL True True 150 1 1000 10 50 10 GTK_SENSITIVITY_ON GTK_SENSITIVITY_ON False GTK_POS_LEFT 2 3 1 2 True True 150 1 1000 10 50 10 GTK_SENSITIVITY_ON GTK_SENSITIVITY_ON False GTK_POS_LEFT 2 3 True 0 height: 1 2 GTK_FILL True 0 width: GTK_FILL True True True True True True Snap: 0 0 True False False True True 4 50 False False 1 False True True Keep aspect ratio True 0 True True False False 1 False False 1 True 2 False False 1 False 2 True GTK_BUTTONBOX_END True True True gtk-revert-to-saved True -2 False False True True True gtk-apply True -10 False False 1 True True True gtk-cancel True -6 False False 2 True True True True gtk-ok True -5 False False 3 False GTK_PACK_END 500 5 False True GTK_WIN_POS_CENTER_ON_PARENT 500 True GDK_WINDOW_TYPE_HINT_DIALOG False False True 2 True 400 True 0 True 400 True False 1 1 True GTK_BUTTONBOX_END True True True _Cancel True 0 False False False GTK_PACK_END 5 Notebook Update False True GTK_WIN_POS_CENTER_ON_PARENT GDK_WINDOW_TYPE_HINT_DIALOG False True 2 True 5 True <b>Notebook Update</b> True GTK_JUSTIFY_CENTER False True This notebook has format version 1 and must be updated to version 2 before openning. True False False 1 True 14 True True save backup before updating (recommended) 0 True True False 2 False 1 True <i>note: backup and upgrade may take a while for larger notebooks.</i> True GTK_JUSTIFY_CENTER True False False 22 2 True GTK_BUTTONBOX_END True True True gtk-cancel True -6 False False True True True True True gtk-ok True -5 False False 1 False GTK_PACK_END 5 New Icon True GTK_WIN_POS_CENTER_ON_PARENT GDK_WINDOW_TYPE_HINT_NORMAL False True GTK_ORIENTATION_VERTICAL 2 GTK_ORIENTATION_VERTICAL True GTK_ORIENTATION_VERTICAL 5 GTK_ORIENTATION_VERTICAL True 0 True 5 5 10 5 True 2 5 6 5 True True 0 True 0 0 True 2 True gtk-open 2 False False True 5 Browse... True False False 1 2 3 1 2 True True 0 True 0 0 True 2 True gtk-open 2 False False True 5 Browse... True False False 1 2 3 True gtk-missing-image 3 4 True gtk-missing-image 3 4 1 2 True True 30 1 2 True True 30 1 2 1 2 True 1 icon: GTK_JUSTIFY_RIGHT GTK_FILL True 1 open-icon: GTK_JUSTIFY_RIGHT 1 2 GTK_FILL True 4 5 True 4 5 1 2 True <b>Node icons</b> True label_item False False True 0 True 5 5 5 5 True GTK_ORIENTATION_VERTICAL 8 GTK_ORIENTATION_VERTICAL True 5 True True True True 0 True 0 True 5 True folder.png False True set icon False 1 True True True 0 True 0 True 5 True folder-open.png False True set open-icon False 1 1 True True True 0 True 0 True 5 True trash.png False True delete icon False 1 2 False True True 0 GTK_SHADOW_NONE True 0.94999998807907104 5 True True GTK_POLICY_NEVER GTK_POLICY_AUTOMATIC GTK_SHADOW_IN True GTK_SHADOW_NONE True GTK_ORIENTATION_VERTICAL GTK_ORIENTATION_VERTICAL True Standard Icons False False True True 1 True Notebook-specific Icons False False 2 True True 3 True <b>All Available icons</b> True label_item True 3 True True True True GTK_RELIEF_NONE 0 True gtk-go-forward False False 1 True 2 False False 1 True 0 GTK_SHADOW_NONE True 5 True True GTK_POLICY_NEVER GTK_POLICY_AUTOMATIC GTK_SHADOW_IN 200 True True True True <b>Quick pick icons</b> True label_item 2 1 True <b>Manage icons</b> True label_item 1 1 True GTK_BUTTONBOX_END True True True gtk-cancel True -6 False False True True True gtk-ok True -5 False False 1 False GTK_PACK_END True 0 GTK_SHADOW_NONE True 10 10 True 10 GTK_ORIENTATION_VERTICAL 5 GTK_ORIENTATION_VERTICAL True 0 GTK_SHADOW_NONE True 10 True 3 2 5 5 True True Default Notebook: 0 0 0 True True no_default_notebook_radio 1 2 GTK_FILL True True No Default Notebook 0 0 0 True True GTK_FILL True 2 2 5 5 True 1 2 1 2 True True 0 True 0 0 True 2 True gtk-open 2 False False True 5 Browse... True False False 1 1 2 True True True True True Use current notebook 0 1 2 GTK_FILL 1 2 1 2 GTK_FILL True True Open Last Notebook 0 0 0 True True no_default_notebook_radio 2 3 GTK_FILL True 1 2 2 3 True 1 2 True <b>Startup</b> True label_item False True 5 True True Autosave: 1 0 True False True True 5 False False 1 True 0 seconds 2 False 1 True True Use system tray icon 0 0 True This option may have no effect depending on combination of OS and desktop environment (window manager, panel, system tray, etc.). False False 2 True 20 True True Hide from taskbar 0 0 True This option may have no effect depending on combination of OS and desktop environment (window manager, panel, system tray, etc.). False False 3 True 20 True True Minimize on start 0 0 True This option may have no effect depending on combination of OS and desktop environment (window manager, panel, system tray, etc.). False False 4 True True Always on top 0 0 True This option may have no effect depending on combination of OS and desktop environment (window manager, panel, system tray, etc.). False False 5 True True Visible on all desktops 0 0 True This option may have no effect depending on combination of OS and desktop environment (window manager, panel, system tray, etc.). False False 6 True True Use fulltext indexing 0 0 True You need to disbale this option if fulltext indexing doesn't work for your language. False False 7 True False False 8 True <b>General KeepNote Options</b> True label_item True 0 GTK_SHADOW_NONE True 10 10 True GTK_ORIENTATION_VERTICAL 5 GTK_ORIENTATION_VERTICAL True 0 From here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year) True False False True 5 5 10 True 4 2 5 5 True True 1 2 3 4 True True 1 2 2 3 True True 1 2 1 2 True 1 Different year: GTK_JUSTIFY_RIGHT 3 4 GTK_FILL True 1 Same year: GTK_JUSTIFY_RIGHT 2 3 GTK_FILL True 1 Same month: GTK_JUSTIFY_RIGHT 1 2 GTK_FILL True True 1 2 True 1 Same day: GTK_JUSTIFY_RIGHT GTK_FILL False 1 True 0 GTK_SHADOW_NONE True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC True GTK_RESIZE_QUEUE True 22 2 10 2 True 0 A literal "%" character 1 2 21 22 GTK_FILL True 0 Time zone name (no characters if no time zone exists) 1 2 20 21 GTK_FILL True 0 Year with century as a decimal number 1 2 19 20 GTK_FILL True 0 Year without century as a decimal number [00,99] 1 2 18 19 GTK_FILL True 0 Locale's appropriate time representation 1 2 17 18 GTK_FILL True 0 Locale's appropriate date representation 1 2 16 17 GTK_FILL True 0 Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0 1 2 15 16 GTK_FILL True 0 Weekday as a decimal number [0(Sunday),6] 1 2 14 15 GTK_FILL True 0 Week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Sunday are considered to be in week 0 1 2 13 14 GTK_FILL True 0 Second as a decimal number [00,61] 1 2 12 13 GTK_FILL True 0 Locale's equivalent of either AM or PM 1 2 11 12 GTK_FILL True 0 Minute as a decimal number [00,59] 1 2 10 11 GTK_FILL True 0 Month as a decimal number [01,12] 1 2 9 10 GTK_FILL True 0 Day of the year as a decimal number [001,366] 1 2 8 9 GTK_FILL True 0 %% 21 22 GTK_FILL True 0 %Z 20 21 GTK_FILL True 0 %Y 19 20 GTK_FILL True 0 %y 18 19 GTK_FILL True 0 %X 17 18 GTK_FILL True 0 %x 16 17 GTK_FILL True 0 0 %W 15 16 GTK_FILL True 0 %w 14 15 GTK_FILL True 0 0 %U 13 14 GTK_FILL True 0 %S 12 13 GTK_FILL True 0 %p 11 12 GTK_FILL True 0 %M 10 11 GTK_FILL True 0 %m 9 10 GTK_FILL True 0 %j 8 9 GTK_FILL True 0 Hour (12-hour clock) as a decimal number [01,12] 1 2 7 8 GTK_FILL True 0 Hour (24-hour clock) as a decimal number [00,23] 1 2 6 7 GTK_FILL True 0 Day of the month as a decimal number [01,31] 1 2 5 6 GTK_FILL True 0 %I 7 8 GTK_FILL True 0 %H 6 7 GTK_FILL True 0 %d 5 6 GTK_FILL True 0 Locale's appropriate date and time representation 1 2 4 5 GTK_FILL True 0 %c 4 5 GTK_FILL True 0 Locale's full month name 1 2 3 4 GTK_FILL True 0 Locale's abbreviated month name 1 2 2 3 GTK_FILL True 0 Locale's full weekday name 1 2 1 2 GTK_FILL True 0 Locale's abbreviated weekday name 1 2 GTK_FILL True 0 %B 3 4 GTK_FILL True 0 %b 2 3 GTK_FILL True 0 %A 1 2 GTK_FILL True 0 %a GTK_FILL True <b>Date &amp; time formatting key</b> True label_item 2 True <b>Date and Time</b> True label_item True 0 GTK_SHADOW_NONE True 10 10 True GTK_ORIENTATION_VERTICAL GTK_ORIENTATION_VERTICAL True 2 2 5 True True True True True 0 True 0 0 True 2 True gtk-open 2 False False True 5 Browse... True False False 1 1 True 2 1 2 1 2 True 5 True False True True 1 1 100 1 10 10 False 1 True 2 1 2 True Default font: GTK_JUSTIFY_RIGHT True Alternative index location: GTK_JUSTIFY_RIGHT 1 2 True <b>This Notebook</b> True label_item keepnote-0.7.8/keepnote/rc/trash.png0000644000175000017500000000141711523163436015566 0ustar razrazPNG  IHDRabKGDC pHYs  tIME6r8>IDAT8uKq?۳=s^D+IP [$fJ]]t]E7DW]t'݈]RJEL4|3$]{{(mcu}89Q -@(!s30q7QP+]A#afvAP9Wht? =B߳L}deiȥNθ8 *oQP\\zDb,o`um(Z NqvdppZ`h"Jny@{{xRPd0ȄLTjubfe>$DeERݧ),r(qRb`.$Aju//C Li49t:MZU5XUUx^,^OC#D[yf>Md4IqSy7o yVYJIENDB`keepnote-0.7.8/keepnote/rc/keepnote.glade.h0000644000175000017500000001043111523163436016771 0ustar razrazchar *s = N_("%%"); char *s = N_("%A"); char *s = N_("%B"); char *s = N_("%H"); char *s = N_("%I"); char *s = N_("%M"); char *s = N_("%S"); char *s = N_("%U"); char *s = N_("%W"); char *s = N_("%X"); char *s = N_("%Y"); char *s = N_("%Z"); char *s = N_("%a"); char *s = N_("%b"); char *s = N_("%c"); char *s = N_("%d"); char *s = N_("%j"); char *s = N_("%m"); char *s = N_("%p"); char *s = N_("%w"); char *s = N_("%x"); char *s = N_("%y"); char *s = N_("50"); char *s = N_("All Available icons"); char *s = N_("Date & time formatting key"); char *s = N_("Date and Time"); char *s = N_("General KeepNote Options"); char *s = N_("Manage icons"); char *s = N_("Node icons"); char *s = N_("Notebook Update"); char *s = N_("Quick pick icons"); char *s = N_("Startup"); char *s = N_("This Notebook"); char *s = N_("note: backup and upgrade may take a while for larger notebooks."); char *s = N_("A literal \"%\" character"); char *s = N_("Alternative index location:"); char *s = N_("Always on top"); char *s = N_("Autosave:"); char *s = N_("Browse..."); char *s = N_("Case _Sensitive"); char *s = N_("Day of the month as a decimal number [01,31]"); char *s = N_("Day of the year as a decimal number [001,366]"); char *s = N_("Default Notebook:"); char *s = N_("Default font:"); char *s = N_("Different year:"); char *s = N_("Find Text:"); char *s = N_("Find/Replace"); char *s = N_("From here, you can customize the format of timestamps in the listview for 4 different scenarios (e.g. how to display a timestamp from today, this month, or this year)"); char *s = N_("Hide from taskbar"); char *s = N_("Hour (12-hour clock) as a decimal number [01,12]"); char *s = N_("Hour (24-hour clock) as a decimal number [00,23]"); char *s = N_("Keep aspect ratio"); char *s = N_("KeepNote Preferences"); char *s = N_("Locale's abbreviated month name"); char *s = N_("Locale's abbreviated weekday name"); char *s = N_("Locale's appropriate date and time representation"); char *s = N_("Locale's appropriate date representation"); char *s = N_("Locale's appropriate time representation"); char *s = N_("Locale's equivalent of either AM or PM"); char *s = N_("Locale's full month name"); char *s = N_("Locale's full weekday name"); char *s = N_("Minimize on start"); char *s = N_("Minute as a decimal number [00,59]"); char *s = N_("Month as a decimal number [01,12]"); char *s = N_("New Icon"); char *s = N_("No Default Notebook"); char *s = N_("Notebook Update"); char *s = N_("Notebook-specific Icons"); char *s = N_("Open Last Notebook"); char *s = N_("Replace Text:"); char *s = N_("Replace _All"); char *s = N_("Resize Image"); char *s = N_("Same day:"); char *s = N_("Same month:"); char *s = N_("Same year:"); char *s = N_("Sea_rch Forward"); char *s = N_("Search _Backward "); char *s = N_("Second as a decimal number [00,61]"); char *s = N_("Snap:"); char *s = N_("Standard Icons"); char *s = N_("This notebook has format version 1 and must be updated to version 2 before openning."); char *s = N_("Time zone name (no characters if no time zone exists)"); char *s = N_("Use current notebook"); char *s = N_("Use system tray icon"); char *s = N_("Visible on all desktops"); char *s = N_("Week number of the year (Monday as the first day of the week) \n" "as a decimal number [00,53]. All days in a new year preceding \n" "the first Monday are considered to be in week 0"); char *s = N_("Week number of the year (Sunday as the first day of the week) \n" "as a decimal number [00,53]. All days in a new year preceding \n" "the first Sunday are considered to be in week 0"); char *s = N_("Weekday as a decimal number [0(Sunday),6]"); char *s = N_("Year with century as a decimal number"); char *s = N_("Year without century as a decimal number [00,99]"); char *s = N_("_Apply"); char *s = N_("_Cancel"); char *s = N_("_Close"); char *s = N_("_Find"); char *s = N_("_Ok"); char *s = N_("_Replace"); char *s = N_("delete icon"); char *s = N_("gtk-cancel"); char *s = N_("gtk-ok"); char *s = N_("gtk-revert-to-saved"); char *s = N_("height:"); char *s = N_("icon:"); char *s = N_("open-icon:"); char *s = N_("save backup before updating (recommended)"); char *s = N_("seconds"); char *s = N_("set icon"); char *s = N_("set open-icon"); char *s = N_("width:"); keepnote-0.7.8/keepnote/linked_list.py0000644000175000017500000001067411677423601016216 0ustar razraz""" KeepNote Linked list data structure """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # class LinkedNode (object): """A node in a doubly linked list""" def __init__(self, item): self._next = None self._prev = None self._item = item def get_next(self): return self._next def get_prev(self): return self._prev def get_item(self): return self._item class LinkedList (object): """A doubly linked list""" def __init__(self, items=[]): self._head = None self._tail = None self._size = 0 self.extend(items) def __len__(self): """Return size of list""" return self._size def __iter__(self): """Iterate over the items in a linked list""" ptr = self._head while ptr is not None: yield ptr._item ptr = ptr._next def __reversed__(self): """Iterate backwards over list""" ptr = self._tail while ptr is not None: yield ptr._item ptr = ptr._prev def get_head(self): return self._head def get_tail(self): return self._tail def iternodes(self): """Iterate over the linked nodes in a list""" node = self._head while node is not None: next = node._next yield node node = next def iternodesreversed(self): """Iterate over the linked nodes in a list in reverse""" node = self._tail while node is not None: prev = ndoe._prev yield node node = prev def append(self, item): """Append item to end of list""" if self._tail is None: # append first node self._head = LinkedNode(item) self._tail = self._head else: # append to end of list node = LinkedNode(item) self._tail._next = node node._prev = self._tail self._tail = node self._size += 1 def prepend(self, item): """Prepend item to front of list""" if self._head is None: # append first node self._head = LinkedNode(item) self._tail = self._head else: # append to front of list node = LinkedNode(item) self._head._prev = node node._next = self._head self._head = node self._size += 1 def extend(self, items): """Append many items to end of list""" for item in items: self.append(item) def extend_front(self, items): """Prepend many items to front of list""" for item in items: self.prepend(item) def pop(self): """Pop item from end of list""" if self._tail is None: raise IndexError("pop from empty list") item = self._tail._item self._tail = self._tail._prev if self._tail is None: # list is empty self._head = None else: self._tail._next = None self._size -= 1 return item def pop_front(self): """Pop item from front of list""" if self._head is None: raise IndexError("pop from empty list") item = self._head._item self._head = self._head._next if self._head is None: # list is empty self._tail = None else: self._head._prev = None self._size -= 1 return item def clear(self): """Clear the list of all items""" self._head = None self._tail = None self._size = 0 keepnote-0.7.8/keepnote/cache.py0000644000175000017500000000547411677423600014761 0ustar razraz""" KeepNote Task object for threading """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports from heapq import heappush, heappop NULL = object() class LRUDict (dict): """A Least Recently Used (LRU) dict-based cache""" def __init__(self, limit=1000): dict.__init__(self) self._limit = limit self._age = 0 self._age_lookup = {} self._ages = [] assert limit > 1 def __setitem__(self, key, val): dict.__setitem__(self, key, val) self._age_lookup[key] = self._age self._ages.append((self._age, key)) self._age += 1 # shirk cache if it is over limit while len(self._ages) > self._limit: minage, minkey = heappop(self._ages) if self._age_lookup[minkey] == minage: del self._age_lookup[minkey] self.__delitem__(minkey) def __getitem__(self, key): val = dict.__getitem(self, key) self._age_lookup[key] = self._age self._ages.append((self._age, key)) self._age += 1 class DictCache (object): def __init__(self, func, cache_dict): self._func = func self._cache_dict = cache_dict def __getitem__(self, key): val = self._cache_dict.get(key, NULL) if val is NULL: val = self._cache_dict[key] = self._func(key) return val class LRUCache (DictCache): def __init__(self, func, limit=1000): DictCache.__init__(self, func, LRUDict(limit)) #============================================================================= if __name__ == "__main__": import random h = [] heappush(h, 2) heappush(h, 5) heappush(h, 1) heappush(h, 9) heappush(h, 1) print h while h: print heappop(h) c = LRUDict(10) for i in xrange(100): c[str(i)] = i print c print c._ages print c._age_lookup c = LRUCache(lambda x: int(x), 10) for i in range(100): j = str(random.randint(0, 20)) print c[j] print c._cache_dict print c._cache_dict._ages keepnote-0.7.8/keepnote/trans.py0000644000175000017500000001164611677423606015051 0ustar razraz""" KeepNote Translation module """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import os import gettext import locale import ctypes from ctypes import cdll # try to import windows lib try: msvcrt = cdll.msvcrt msvcrt._putenv.argtypes = [ctypes.c_char_p] _windows = True except: _windows = False # global translation object GETTEXT_DOMAIN = 'keepnote' _locale_dir = u"." _translation = None _lang = None try: locale.setlocale(locale.LC_ALL, "") except locale.Error: # environment variable LANG may specify an unsupported locale pass # we must not let windows environment variables deallocate # thus we keep a global list of pointers #_win_env = [] def set_env(key, val): """Cross-platform environment setting""" if _windows: # ignore settings that don't change if os.environ.get(key, "") == val: return setstr = u"%s=%s" % (key, val) #setstr = x.encode(locale.getpreferredencoding()) msvcrt._putenv(setstr) #win32api.SetEnvironmentVariable(key, val) #ctypes.windll.kernel32.SetEnvironmentVariableA(key, val) # NOTE: we only need to change the python copy of the environment # The data member is only available if we are on windows os.environ.data[key] = val else: os.environ[key] = val def set_local_dir(dirname): """Set the locale directory""" global _locale_dir _locale_dir = dirname def set_lang(lang=None, localedir=None): """Set the locale""" global _translation, _lang # setup language preference order languages = [] # default language from environment deflang, defencoding = locale.getdefaultlocale() if deflang: languages = [deflang+"."+defencoding] + languages # specified language if lang: languages = [lang] + languages # initialize gettext if localedir is None: localedir = _locale_dir gettext.bindtextdomain(GETTEXT_DOMAIN, localedir) gettext.textdomain(GETTEXT_DOMAIN) # search for language file langfile = gettext.find(GETTEXT_DOMAIN, localedir, languages) # setup language translations if langfile: _lang = os.path.basename(os.path.dirname( os.path.dirname(langfile))) set_env("LANG", _lang) set_env("LANGUAGE", _lang) _translation = gettext.GNUTranslations(open(langfile, "rb")) else: _lang = "" set_env("LANG", _lang) set_env("LANGUAGE", _lang) _translation = gettext.NullTranslations() # install "_" into python builtins _translation.install() def get_lang(): return _lang def translate(message): """Translate a string into the current language""" if _translation is None: return message return _translation.gettext(message) def get_langs(localedir=None): """Return available languages""" if localedir is None: localedir = _locale_dir return os.listdir(localedir) ''' #Translation stuff #Get the local directory since we are not installing anything self.local_path = os.path.realpath(os.path.dirname(sys.argv[0])) # Init the list of languages to support langs = [] #Check the default locale lc, encoding = locale.getdefaultlocale() if (lc): #If we have a default, it's the first in the list langs = [lc] # Now lets get all of the supported languages on the system language = os.environ.get('LANGUAGE', None) if (language): """langage comes back something like en_CA:en_US:en_GB:en on linuxy systems, on Win32 it's nothing, so we need to split it up into a list""" langs += language.split(":") """Now add on to the back of the list the translations that we know that we have, our defaults""" langs += ["en_CA", "en_US"] """Now langs is a list of all of the languages that we are going to try to use. First we check the default, then what the system told us, and finally the 'known' list""" gettext.bindtextdomain(APP_NAME, self.local_path) gettext.textdomain(APP_NAME) # Get the language to use self.lang = gettext.translation(APP_NAME, self.local_path , languages=langs, fallback = True) """Install the language, map _() (which we marked our strings to translate with) to self.lang.gettext() which will translate them.""" _ = self.lang.gettext ''' keepnote-0.7.8/keepnote/__init__.py0000644000175000017500000012637111727170552015456 0ustar razraz""" KeepNote Module for KeepNote Basic backend data structures for KeepNote and NoteBooks """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import os import shutil import sys import time import re import subprocess import tempfile import traceback import uuid import zipfile try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.elementtree.ElementTree as ET # work around pygtk changing default encoding DEFAULT_ENCODING = sys.getdefaultencoding() FS_ENCODING = sys.getfilesystemencoding() # keepnote imports from keepnote.notebook import \ NoteBookError, \ get_unique_filename_list import keepnote.notebook as notebooklib import keepnote.notebook.connection import keepnote.notebook.connection.fs import keepnote.notebook.connection.http import keepnote.timestamp import keepnote.xdg from keepnote.listening import Listeners from keepnote import mswin import keepnote.trans from keepnote.trans import GETTEXT_DOMAIN from keepnote import extension from keepnote import plist from keepnote import safefile from keepnote.pref import Pref #============================================================================= # modules needed by builtin extensions # these are imported here, so that py2exe can auto-discover them from keepnote import tarfile import xml.dom.minidom import xml.sax.saxutils import sgmllib import htmlentitydefs import re import base64 import string import random # import screenshot so that py2exe discovers it try: import mswin.screenshot except ImportError: pass #============================================================================= # globals / constants PROGRAM_NAME = u"KeepNote" PROGRAM_VERSION_MAJOR = 0 PROGRAM_VERSION_MINOR = 7 PROGRAM_VERSION_RELEASE = 8 PROGRAM_VERSION = (PROGRAM_VERSION_MAJOR, PROGRAM_VERSION_MINOR, PROGRAM_VERSION_RELEASE) if PROGRAM_VERSION_RELEASE != 0: PROGRAM_VERSION_TEXT = "%d.%d.%d" % (PROGRAM_VERSION_MAJOR, PROGRAM_VERSION_MINOR, PROGRAM_VERSION_RELEASE) else: PROGRAM_VERSION_TEXT = "%d.%d" % (PROGRAM_VERSION_MAJOR, PROGRAM_VERSION_MINOR) WEBSITE = u"http://keepnote.org" LICENSE_NAME = u"GPL version 2" COPYRIGHT = u"Copyright Matt Rasmussen 2011." TRANSLATOR_CREDITS = ( u"Chinese: hu dachuan \n" u"French: tb \n" u"French: Sebastien KALT \n" u"German: Jan Rimmek \n" u"Japanese: Toshiharu Kudoh \n" u"Italian: Davide Melan \n" u"Polish: Bernard Baraniewski \n" u"Russian: Hikiko Mori \n" u"Spanish: Klemens Hackel \n" u"Slovak: Slavko \n" u"Swedish: Morgan Antonsson \n" u"Turkish: Yuce Tekol \n" ) BASEDIR = os.path.dirname(unicode(__file__, FS_ENCODING)) PLATFORM = None USER_PREF_DIR = u"keepnote" USER_PREF_FILE = u"keepnote.xml" USER_LOCK_FILE = u"lockfile" USER_ERROR_LOG = u"error-log.txt" USER_EXTENSIONS_DIR = u"extensions" USER_EXTENSIONS_DATA_DIR = u"extensions_data" PORTABLE_FILE = u"portable.txt" #============================================================================= # application resources # TODO: cleanup, make get/set_basedir symmetrical def get_basedir(): return os.path.dirname(unicode(__file__, FS_ENCODING)) def set_basedir(basedir): global BASEDIR if basedir is None: BASEDIR = get_basedir() else: BASEDIR = basedir keepnote.trans.set_local_dir(get_locale_dir()) def get_resource(*path_list): return os.path.join(BASEDIR, *path_list) #============================================================================= # common functions def get_platform(): """Returns a string for the current platform""" global PLATFORM if PLATFORM is None: p = sys.platform if p == 'darwin': PLATFORM = 'darwin' elif p.startswith('win'): PLATFORM = 'windows' else: PLATFORM = 'unix' return PLATFORM def is_url(text): """Returns True is text is a url""" return re.match("^[^:]+://", text) is not None def ensure_unicode(text, encoding="utf8"): """Ensures a string is unicode""" # let None's pass through if text is None: return None # make sure text is unicode if not isinstance(text, unicode): return unicode(text, encoding) return text def unicode_gtk(text): """ Converts a string from gtk (utf8) to unicode All strings from the pygtk API are returned as byte strings (str) encoded as utf8. KeepNote has the convention to keep all strings as unicode internally. So strings from pygtk must be converted to unicode immediately. Note: pygtk can accept either unicode or utf8 encoded byte strings. """ if text is None: return None return unicode(text, "utf8") def print_error_log_header(out=None): """Display error log header""" if out is None: out = sys.stderr out.write("==============================================\n" "%s %s: %s\n" % (keepnote.PROGRAM_NAME, keepnote.PROGRAM_VERSION_TEXT, time.asctime())) def print_runtime_info(out=None): """Display runtime information""" if out is None: out = sys.stderr import keepnote out.write("Python runtime\n" "--------------\n" "sys.version=" + sys.version+"\n" "sys.getdefaultencoding()="+DEFAULT_ENCODING+"\n" "sys.getfilesystemencoding()="+FS_ENCODING+"\n" "PYTHONPATH=" " "+"\n ".join(sys.path)+"\n" "\n" "Imported libs\n" "-------------\n" "keepnote: " + keepnote.__file__+"\n") try: import gtk out.write("gtk: "+ gtk.__file__+"\n") out.write("gtk.gtk_version: "+repr(gtk.gtk_version)+"\n") except: out.write("gtk: NOT PRESENT\n") from keepnote.notebook.connection.fs.index import sqlite out.write("sqlite: " + sqlite.__file__+"\n" "sqlite.version: " + sqlite.version+"\n" "sqlite.sqlite_version: " + sqlite.sqlite_version+"\n" "sqlite.fts3: " + str(test_fts3())+"\n") try: import gtkspell out.write("gtkspell: " + gtkspell.__file__+"\n") except ImportError: out.write("gtkspell: NOT PRESENT\n") out.write("\n") def test_fts3(): from keepnote.notebook.connection.fs.index import sqlite con = sqlite.connect(":memory:") try: con.execute("CREATE VIRTUAL TABLE fts3test USING fts3(col TEXT);") except: return False finally: con.close() return True #============================================================================= # locale functions def translate(message): """Translate a string""" return keepnote.trans.translate(message) def get_locale_dir(): """Returns KeepNote's locale directory""" return get_resource(u"rc", u"locale") _ = translate #============================================================================= # preference filenaming scheme def get_home(): """Returns user's HOME directory""" home = ensure_unicode(os.getenv(u"HOME"), FS_ENCODING) if home is None: raise EnvError("HOME environment variable must be specified") return home def get_user_pref_dir(home=None): """Returns the directory of the application preference file""" p = get_platform() if p == "unix" or p == "darwin": if home is None: home = get_home() return keepnote.xdg.get_config_file(USER_PREF_DIR, default=True) elif p == "windows": # look for portable config if os.path.exists(os.path.join(BASEDIR, PORTABLE_FILE)): return os.path.join(BASEDIR, USER_PREF_DIR) # otherwise, use application data dir appdata = get_win_env("APPDATA") if appdata is None: raise EnvError("APPDATA environment variable must be specified") return os.path.join(appdata, USER_PREF_DIR) else: raise Exception("unknown platform '%s'" % p) def get_user_extensions_dir(pref_dir=None, home=None): """Returns user extensions directory""" if pref_dir is None: pref_dir = get_user_pref_dir(home) return os.path.join(pref_dir, USER_EXTENSIONS_DIR) def get_user_extensions_data_dir(pref_dir=None, home=None): """Returns user extensions data directory""" if pref_dir is None: pref_dir = get_user_pref_dir(home) return os.path.join(pref_dir, USER_EXTENSIONS_DATA_DIR) def get_system_extensions_dir(): """Returns system-wide extensions directory""" return os.path.join(BASEDIR, u"extensions") def get_user_documents(home=None): """Returns the directory of the user's documents""" p = get_platform() if p == "unix" or p == "darwin": if home is None: home = get_home() return home elif p == "windows": return unicode(mswin.get_my_documents(), FS_ENCODING) else: return u"" def get_user_pref_file(pref_dir=None, home=None): """Returns the filename of the application preference file""" if pref_dir is None: pref_dir = get_user_pref_dir(home) return os.path.join(pref_dir, USER_PREF_FILE) def get_user_lock_file(pref_dir=None, home=None): """Returns the filename of the application lock file""" if pref_dir is None: pref_dir = get_user_pref_dir(home) return os.path.join(pref_dir, USER_LOCK_FILE) def get_user_error_log(pref_dir=None, home=None): """Returns a file for the error log""" if pref_dir is None: pref_dir = get_user_pref_dir(home) return os.path.join(pref_dir, USER_ERROR_LOG) def get_win_env(key): """Returns a windows environment variable""" # try both encodings try: return ensure_unicode(os.getenv(key), DEFAULT_ENCODING) except UnicodeDecodeError: return ensure_unicode(os.getenv(key), FS_ENCODING) #============================================================================= # preference/extension initialization def init_user_pref_dir(pref_dir=None, home=None): """Initializes the application preference file""" if pref_dir is None: pref_dir = get_user_pref_dir(home) # make directory if not os.path.exists(pref_dir): os.makedirs(pref_dir, 0700) # init empty pref file pref_file = get_user_pref_file(pref_dir) if not os.path.exists(pref_file): out = open(pref_file, "w") out.write("\n") out.write("\n") out.write("\n") out.close() # init error log init_error_log(pref_dir) # init user extensions extension.init_user_extensions(pref_dir) def init_error_log(pref_dir=None, home=None): """Initialize the error log""" if pref_dir is None: pref_dir = get_user_pref_dir(home) error_log = get_user_error_log(pref_dir) if not os.path.exists(error_log): error_dir = os.path.dirname(error_log) if not os.path.exists(error_dir): os.makedirs(error_dir) open(error_log, "a").close() def log_error(error=None, tracebk=None, out=None): """Write an exception error to the error log""" if out is None: out = sys.stderr if error is None: ty, error, tracebk = sys.exc_info() try: out.write("\n") traceback.print_exception(type(error), error, tracebk, file=out) out.flush() except UnicodeEncodeError: out.write(error.encode("ascii", "replace")) def log_message(message, out=None): """Write a message to the error log""" if out is None: out = sys.stderr try: out.write(message) except UnicodeEncodeError: out.write(message.encode("ascii", "replace")) out.flush() #============================================================================= # Exceptions class EnvError (StandardError): """Exception that occurs when environment variables are ill-defined""" def __init__(self, msg, error=None): StandardError.__init__(self) self.msg = msg self.error = error def __str__(self): if self.error: return str(self.error) + "\n" + self.msg else: return self.msg class KeepNoteError (StandardError): def __init__(self, msg, error=None): StandardError.__init__(self, msg) self.msg = msg self.error = error def __repr__(self): if self.error: return str(self.error) + "\n" + self.msg else: return self.msg def __str__(self): return self.msg class KeepNotePreferenceError (StandardError): """Exception that occurs when manipulating preferences""" def __init__(self, msg, error=None): StandardError.__init__(self) self.msg = msg self.error = error def __str__(self): if self.error: return str(self.error) + "\n" + self.msg else: return self.msg #============================================================================= # Preference data structures class ExternalApp (object): """Class represents the information needed for calling an external application""" def __init__(self, key, title, prog, args=[]): self.key = key self.title = title self.prog = prog self.args = args DEFAULT_EXTERNAL_APPS = [ ExternalApp("file_launcher", "File Launcher", u""), ExternalApp("web_browser", "Web Browser", u""), ExternalApp("file_explorer", "File Explorer", u""), ExternalApp("text_editor", "Text Editor", u""), ExternalApp("image_editor", "Image Editor", u""), ExternalApp("image_viewer", "Image Viewer", u""), ExternalApp("screen_shot", "Screen Shot", u"") ] def get_external_app_defaults(): if get_platform() == "windows": files = ensure_unicode( os.environ.get(u"PROGRAMFILES", u"C:\\Program Files"),FS_ENCODING) return [ ExternalApp("file_launcher", "File Launcher", u"explorer.exe"), ExternalApp("web_browser", "Web Browser", files + u"\\Internet Explorer\\iexplore.exe"), ExternalApp("file_explorer", "File Explorer", u"explorer.exe"), ExternalApp("text_editor", "Text Editor", files + u"\\Windows NT\\Accessories\\wordpad.exe"), ExternalApp("image_editor", "Image Editor", u"mspaint.exe"), ExternalApp("image_viewer", "Image Viewer", files + u"\\Internet Explorer\\iexplore.exe"), ExternalApp("screen_shot", "Screen Shot", "") ] elif get_platform() == "unix": return [ ExternalApp("file_launcher", "File Launcher", u"xdg-open"), ExternalApp("web_browser", "Web Browser", u""), ExternalApp("file_explorer", "File Explorer", u""), ExternalApp("text_editor", "Text Editor", u""), ExternalApp("image_editor", "Image Editor", u""), ExternalApp("image_viewer", "Image Viewer", u"display"), ExternalApp("screen_shot", "Screen Shot", u"import") ] else: return DEFAULT_EXTERNAL_APPS class KeepNotePreferences (Pref): """Preference data structure for the KeepNote application""" def __init__(self, pref_dir=None): Pref.__init__(self) if pref_dir is None: self._pref_dir = get_user_pref_dir() else: self._pref_dir = pref_dir # listener self.changed = Listeners() #self.changed.add(self._on_changed) def get_pref_dir(self): """Returns preference directory""" return self._pref_dir #def _on_changed(self): # """Listener for preference changes""" # self.write() #========================================= # Input/Output def read(self): """Read preferences from file""" # ensure preference file exists if not os.path.exists(get_user_pref_file(self._pref_dir)): # write default try: init_user_pref_dir(self._pref_dir) self.write() except Exception, e: raise KeepNotePreferenceError("Cannot initialize preferences", e) try: # read preferences xml tree = ET.ElementTree( file=get_user_pref_file(self._pref_dir)) # parse xml # check tree structure matches current version root = tree.getroot() if root.tag == "keepnote": p = root.find("pref") if p is None: # convert from old preference version import keepnote.compat.pref as old old_pref = old.KeepNotePreferences() old_pref.read(get_user_pref_file(self._pref_dir)) data = old_pref._get_data() else: # get data object from xml d = p.find("dict") if d is not None: data = plist.load_etree(d) else: data = orderdict.OrderDict() # set data self._data.clear() self._data.update(data) except Exception, e: raise KeepNotePreferenceError("Cannot read preferences", e) # notify listeners self.changed.notify() def write(self): """Write preferences to file""" try: if not os.path.exists(self._pref_dir): init_user_pref_dir(self._pref_dir) out = safefile.open(get_user_pref_file(self._pref_dir), "w", codec="utf-8") out.write(u'\n' u'\n' u'\n') plist.dump(self._data, out, indent=4, depth=4) out.write(u'\n' u'\n') out.close() except (IOError, OSError), e: log_error(e, sys.exc_info()[2]) raise NoteBookError(_("Cannot save preferences"), e) #============================================================================= # Application class class ExtensionEntry (object): """An entry for an Extension in the KeepNote application""" def __init__(self, filename, ext_type, ext): self.filename = filename self.ext_type = ext_type self.ext = ext def get_key(self): return os.path.basename(self.filename) class AppCommand (object): """Application Command""" def __init__(self, name, func=lambda app, args: None, metavar="", help=""): self.name = name self.func = func self.metavar = metavar self.help = help class KeepNote (object): """KeepNote application class""" def __init__(self, basedir=None): # base directory of keepnote library if basedir is not None: set_basedir(basedir) self._basedir = BASEDIR # load application preferences self.pref = KeepNotePreferences() self.pref.changed.add(self._on_pref_changed) self.id = None # list of registered application commands self._commands = {} # list of opened notebooks self._notebooks = {} self._notebook_count = {} # notebook ref counts # default protocols for notebooks self._conns = keepnote.notebook.connection.NoteBookConnections() self._conns.add("file", keepnote.notebook.connection.fs.NoteBookConnectionFS) self._conns.add("http", keepnote.notebook.connection.http.NoteBookConnectionHttp) # external apps self._external_apps = [] self._external_apps_lookup = {} # set of registered extensions for this application self._extension_paths = [] self._extensions = {} self._disabled_extensions = [] # listeners self._listeners = {} def init(self): """Initialize from preferences saved on disk""" # read preferences self.pref.read() self.load_preferences() # init extension paths self._extension_paths = [ (get_system_extensions_dir(), "system"), (get_user_extensions_dir(), "user")] # initialize all extensions self.init_extensions() def load_preferences(self): """Load information from preferences""" self.language = self.pref.get("language", default="") self.set_lang() # setup id self.id = self.pref.get("id", default="") if self.id == "": self.id = str(uuid.uuid4()) self.pref.set("id", self.id) # TODO: move to gui app? # set default timestamp formats self.pref.get( "timestamp_formats", default=dict(keepnote.timestamp.DEFAULT_TIMESTAMP_FORMATS)) # external apps self._load_external_app_preferences() # extensions self._disabled_extensions = self.pref.get( "extension_info", "disabled", default=[]) self.pref.get("extensions", define=True) def save_preferences(self): """Save information into preferences""" # language self.pref.set("language", self.language) # external apps self.pref.set("external_apps", [ {"key": app.key, "title": app.title, "prog": app.prog, "args": app.args} for app in self._external_apps]) # extensions self.pref.set("extension_info", { "disabled": self._disabled_extensions[:] }) # save to disk self.pref.write() def _on_pref_changed(self): """Callback for when application preferences change""" self.load_preferences() def set_lang(self): """Set the language based on preference""" keepnote.trans.set_lang(self.language) def error(self, text, error=None, tracebk=None): """Display an error message""" keepnote.log_message(text) if error is not None: keepnote.log_error(error, tracebk) def quit(self): """Stop the application""" if self.pref.get("use_last_notebook", default=False): self.pref.set("default_notebooks", [n.get_path() for n in self.iter_notebooks()]) self.save_preferences() def get_default_path(self, name): """Returns a default path for saving/reading files""" return self.pref.get("default_paths", name, default=get_user_documents()) def set_default_path(self, name, path): """Sets the default path for saving/reading files""" self.pref.set("default_paths", name, path) #================================== # Notebooks def open_notebook(self, filename, window=None, task=None): """Open a new notebook""" try: conn = self._conns.get(filename) notebook = notebooklib.NoteBook() notebook.load(filename, conn) except Exception: return None return notebook def close_notebook(self, notebook): """Close notebook""" if self.has_ref_notebook(notebook): self.unref_notebook(notebook) def close_all_notebook(self, notebook, save=True): """Close all instances of a notebook""" try: notebook.close(save) except: keepnote.log_error() notebook.closing_event.remove(self._on_closing_notebook) del self._notebook_count[notebook] for key, val in self._notebooks.iteritems(): if val == notebook: del self._notebooks[key] break def _on_closing_notebook(self, notebook, save): """ Callback for when notebook is about to close """ pass def get_notebook(self, filename, window=None, task=None): """ Returns a an opened notebook referenced by filename Open a new notebook if it is not already opened. """ try: filename = notebooklib.normalize_notebook_dirname( filename, longpath=False) filename = os.path.realpath(filename) except: pass if filename not in self._notebooks: notebook = self.open_notebook(filename, window, task=task) if notebook is None: return None # perform bookkeeping self._notebooks[filename] = notebook notebook.closing_event.add(self._on_closing_notebook) self.ref_notebook(notebook) else: notebook = self._notebooks[filename] self.ref_notebook(notebook) return notebook def ref_notebook(self, notebook): if notebook not in self._notebook_count: self._notebook_count[notebook] = 1 else: self._notebook_count[notebook] += 1 def unref_notebook(self, notebook): self._notebook_count[notebook] -= 1 # close if refcount is zero if self._notebook_count[notebook] == 0: self.close_all_notebook(notebook) def has_ref_notebook(self, notebook): return notebook in self._notebook_count def iter_notebooks(self): """Iterate through open notebooks""" return self._notebooks.itervalues() def save_notebooks(self, silent=False): """Save all opened notebooks""" # save all the notebooks for notebook in self._notebooks.itervalues(): notebook.save() def get_node(self, nodeid): """Returns a node with 'nodeid' from any of the opened notebooks""" for notebook in self._notebooks.itervalues(): node = notebook.get_node_by_id(nodeid) if node is not None: return node return None def save(self, silent=False): """Save notebooks and preferences""" self.save_notebooks() self.save_preferences() #================================ # listeners def get_listeners(self, key): listeners = self._listeners.get(key, None) if listeners is None: listeners = Listeners() self._listeners[key] = listeners return listeners #================================ # external apps def _load_external_app_preferences(self): # external apps self._external_apps = [] for app in self.pref.get("external_apps", default=[]): if "key" not in app: continue app2 = ExternalApp(app["key"], app.get("title", ""), app.get("prog", ""), app.get("args", "")) self._external_apps.append(app2) # make lookup self._external_apps_lookup = {} for app in self._external_apps: self._external_apps_lookup[app.key] = app # add default programs lst = get_external_app_defaults() for defapp in lst: if defapp.key not in self._external_apps_lookup: self._external_apps.append(defapp) self._external_apps_lookup[defapp.key] = defapp # place default apps first lookup = dict((x.key, i) for i, x in enumerate(DEFAULT_EXTERNAL_APPS)) top = len(DEFAULT_EXTERNAL_APPS) self._external_apps.sort(key=lambda x: (lookup.get(x.key, top), x.key)) def get_external_app(self, key): """Return an external application by its key name""" app = self._external_apps_lookup.get(key, None) if app == "": app = None return app def iter_external_apps(self): return iter(self._external_apps) def run_external_app(self, app_key, filename, wait=False): """Runs a registered external application on a file""" app = self.get_external_app(app_key) if app is None or app.prog == "": if app: raise KeepNoteError(_("Must specify '%s' program in Helper Applications" % app.title)) else: raise KeepNoteError(_("Must specify '%s' program in Helper Applications" % app_key)) # build command arguments cmd = [app.prog] + app.args if "%f" not in cmd: cmd.append(filename) else: for i in xrange(len(cmd)): if cmd[i] == "%f": cmd[i] = filename # create proper encoding cmd = map(lambda x: unicode(x), cmd) if get_platform() == "windows": cmd = [x.encode('mbcs') for x in cmd] else: cmd = [x.encode(FS_ENCODING) for x in cmd] # execute command try: proc = subprocess.Popen(cmd) except OSError, e: raise KeepNoteError( _(u"Error occurred while opening file with %s.\n\n" u"program: '%s'\n\n" u"file: '%s'\n\n" u"error: %s") % (app.title, app.prog, filename, unicode(e)), e) # wait for process to return # TODO: perform waiting in gtk loop # NOTE: I do not wait for any program yet if wait: return proc.wait() def run_external_app_node(self, app_key, node, kind, wait=False): """Runs an external application on a node""" if kind == "dir": filename = node.get_path() else: if node.get_attr("content_type") == notebooklib.CONTENT_TYPE_PAGE: # get html file filename = node.get_data_file() elif node.get_attr("content_type") == notebooklib.CONTENT_TYPE_DIR: # get node dir filename = node.get_path() elif node.has_attr("payload_filename"): # get payload file filename = node.get_file(node.get_attr("payload_filename")) else: raise KeepNoteError(_("Unable to determine note type.")) #if not filename.startswith("http://"): # filename = os.path.realpath(filename) self.run_external_app(app_key, filename, wait=wait) def open_webpage(self, url): """View a node with an external web browser""" if url: self.run_external_app("web_browser", url) def take_screenshot(self, filename): """Take a screenshot and save it to 'filename'""" # make sure filename is unicode filename = ensure_unicode(filename, "utf-8") if get_platform() == "windows": # use win32api to take screenshot # create temp file f, imgfile = tempfile.mkstemp(u".bmp", filename) os.close(f) mswin.screenshot.take_screenshot(imgfile) else: # use external app for screen shot screenshot = self.get_external_app("screen_shot") if screenshot is None or screenshot.prog == "": raise Exception(_("You must specify a Screen Shot program in Application Options")) # create temp file f, imgfile = tempfile.mkstemp(".png", filename) os.close(f) proc = subprocess.Popen([screenshot.prog, imgfile]) if proc.wait() != 0: raise OSError("Exited with error") if not os.path.exists(imgfile): # catch error if image is not created raise Exception(_("The screenshot program did not create the necessary image file '%s'") % imgfile) return imgfile #================================ # commands def get_command(self, command_name): """Returns a command of the given name 'command_name'""" return self._commands.get(command_name, None) def get_commands(self): """Returns a list of all registered commands""" return self._commands.values() def add_command(self, command): """Adds a command to the application""" if command.name in self._commands: raise Exception(_("command '%s' already exists") % command.name) self._commands[command.name] = command def remove_command(self, command_name): """Removes a command from the application""" if command_name in self._commands: del self._commands[command_name] #================================ # extensions def init_extensions(self): """Enable all extensions""" # remove all existing extensions self._clear_extensions() # scan for extensions self._scan_extension_paths() # import all extensions self._import_all_extensions() # enable those extensions that have their dependencies met for ext in self.get_imported_extensions(): # enable extension try: if ext.key not in self._disabled_extensions: log_message(_("enabling extension '%s'\n") % ext.key) enabled = ext.enable(True) except extension.DependencyError, e: # could not enable due to failed dependency log_message(_(" skipping extension '%s':\n") % ext.key) for dep in ext.get_depends(): if not self.dependency_satisfied(dep): log_message(_(" failed dependency: %s\n") % repr(dep)) except Exception, e: # unknown error log_error(e, sys.exc_info()[2]) def _clear_extensions(self): """Disable and unregister all extensions for the app""" # disable all enabled extensions for ext in list(self.get_enabled_extensions()): ext.disable() # reset registered extensions list self._extensions = { "keepnote": ExtensionEntry("", "system", KeepNoteExtension(self))} def _scan_extension_paths(self): """Scan all extension paths""" for path, ext_type in self._extension_paths: self._scan_extension_path(path, ext_type) def _scan_extension_path(self, extensions_path, ext_type): """ Scan extensions directory and register extensions with app extensions_path -- path for extensions ext_type -- "user"/"system" """ for filename in extension.scan_extensions_dir(extensions_path): self.add_extension(filename, ext_type) def add_extension(self, filename, ext_type): """Add an extension filename to the app's extension entries""" entry = ExtensionEntry(filename, ext_type, None) self._extensions[entry.get_key()] = entry return entry def remove_extension(self, ext_key): """Remove an extension entry""" # retrieve information about extension entry = self._extensions.get(ext_key, None) if entry: if entry.ext: # disable extension entry.ext.enable(False) # unregister extension from app del self._extensions[ext_key] def get_extension(self, name): """Get an extension module by name""" # return None if extension name is unknown if name not in self._extensions: return None # get extension information entry = self._extensions[name] # load if first use if entry.ext is None: self._import_extension(entry) return entry.ext def get_installed_extensions(self): """Iterates through installed extensions""" return self._extensions.iterkeys() def get_imported_extensions(self): """Iterates through imported extensions""" for entry in self._extensions.values(): if entry.ext is not None: yield entry.ext def get_enabled_extensions(self): """Iterates through enabled extensions""" for ext in self.get_imported_extensions(): if ext.is_enabled(): yield ext def _import_extension(self, entry): """Import an extension from an extension entry""" try: entry.ext = extension.import_extension( self, entry.get_key(), entry.filename) except KeepNotePreferenceError, e: log_error(e, sys.exc_info()[2]) return None entry.ext.type = entry.ext_type entry.ext.enabled.add( lambda e: self.on_extension_enabled(entry.ext, e)) return entry.ext def _import_all_extensions(self): """Import all extensions""" for entry in self._extensions.values(): # load if first use if entry.ext is None: self._import_extension(entry) def dependency_satisfied(self, dep): """ Returns True if dependency 'dep' is satisfied by registered extensions """ ext = self.get_extension(dep[0]) return extension.dependency_satisfied(ext, dep) def dependencies_satisfied(self, depends): """Returns True if dependencies 'depends' are satisfied""" for dep in depends: ext = self.get_extension(dep[0]) if ext is None or not extension.dependency_satisfied(ext, dep): return False return True def on_extension_enabled(self, ext, enabled): """Callback for when extension is enabled""" # update user preference on which extensions are disabled if enabled: if ext.key in self._disabled_extensions: self._disabled_extensions.remove(ext.key) else: if ext.key not in self._disabled_extensions: self._disabled_extensions.append(ext.key) def install_extension(self, filename): """Install a new extension from package 'filename'""" userdir = get_user_extensions_dir() newfiles = [] try: # unzip and record new files for fn in unzip(filename, userdir): newfiles.append(fn) # rescan user extensions exts = set(self._extensions.keys()) self._scan_extension_path(userdir, "user") # find new extensions new_names = set(self._extensions.keys()) - exts new_exts = [self.get_extension(name) for name in new_names] except Exception, e: self.error(_("Unable to install extension '%s'") % filename, e, tracebk=sys.exc_info()[2]) # delete newfiles for fn in newfiles: try: keepnote.log_message(_("removing '%s'") % newfile) os.remove(newfile) except: # delete may fail, continue pass return [] # enable new extensions log_message(_("Enabling new extensions:\n")) for ext in new_exts: log_message(_("enabling extension '%s'\n") % ext.key) ext.enable(True) return new_exts def uninstall_extension(self, ext_key): """Uninstall an extension""" # retrieve information about extension entry = self._extensions.get(ext_key, None) if entry is None: self.error(_("Unable to uninstall unknown extension '%s'.") % ext_key) return False # cannot uninstall system extensions if entry.ext_type != "user": self.error(_("KeepNote can only uninstall user extensions")) return False # remove extension from runtime self.remove_extension(ext_key) # delete extension from filesystem try: shutil.rmtree(entry.filename) except OSError, e: self.error(_("Unable to uninstall extension. Do not have permission.")) return False return True def can_uninstall(self, ext): """Return True if extension can be uninstalled""" return ext.type != "system" def get_extension_base_dir(self, extkey): """Get base directory of an extension""" return self._extensions[extkey].filename def get_extension_data_dir(self, extkey): """Get the data directory of an extension""" return os.path.join(get_user_extensions_data_dir(), extkey) def unzip(filename, outdir): """Unzip an extension""" extzip = zipfile.ZipFile(filename) for fn in extzip.namelist(): if fn.endswith("/") or fn.endswith("\\"): # skip directory entries continue # quick test for unusual filenames if fn.startswith("../") or "/../" in fn: raise Exception("bad file paths in zipfile '%s'" % fn) # determine extracted filename newfilename = os.path.join(outdir, fn) # ensure directory exists dirname = os.path.dirname(newfilename) if not os.path.exists(dirname): os.makedirs(dirname) elif not os.path.isdir(dirname) or os.path.exists(newfilename): raise Exception("Cannot unzip. Other files are in the way") # extract file out = open(newfilename, "wb") out.write(extzip.read(fn)) out.flush() out.close() yield newfilename class KeepNoteExtension (extension.Extension): """Extension that represents the application itself""" version = PROGRAM_VERSION key = "keepnote" name = "KeepNote" description = "The KeepNote application" visible = False def __init__(self, app): extension.Extension.__init__(self, app) def enable(self, enable): """This extension is always enabled""" extension.Extension.enable(self, True) return True def get_depends(self): """Application has no dependencies, returns []""" return [] keepnote-0.7.8/keepnote/gui/0000755000175000017500000000000011727170645014122 5ustar razrazkeepnote-0.7.8/keepnote/gui/editor_text.py0000644000175000017500000003442711677423601017035 0ustar razraz""" KeepNote Editor widget in main window """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import gettext import sys, os # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk.glade import gobject # keepnote imports import keepnote from keepnote import \ KeepNoteError, is_url, unicode_gtk from keepnote.notebook import \ NoteBookError, \ get_node_url, \ parse_node_url, \ is_node_url from keepnote import notebook as notebooklib from keepnote import safefile from keepnote.gui import richtext from keepnote.gui.richtext import \ RichTextView, RichTextBuffer, \ RichTextIO, RichTextError from keepnote.gui import \ CONTEXT_MENU_ACCEL_PATH, \ FileChooserDialog, \ get_resource, \ Action, \ ToggleAction, \ add_actions, \ dialog_find from keepnote.gui.editor import KeepNoteEditor _ = keepnote.translate class TextEditor (KeepNoteEditor): def __init__(self, app): KeepNoteEditor.__init__(self, app) self._app = app self._notebook = None # state self._page = None # current NoteBookPage self._page_scrolls = {} # remember scroll in each page self._page_cursors = {} self._textview_io = RichTextIO() # textview and its callbacks self._textview = RichTextView(RichTextBuffer( self._app.get_richtext_tag_table())) # textview self._textview.disable() self._textview.connect("modified", self._on_modified_callback) self._textview.connect("visit-url", self._on_visit_url) # scrollbars self._sw = gtk.ScrolledWindow() self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self._sw.set_shadow_type(gtk.SHADOW_IN) self._sw.add(self._textview) self.pack_start(self._sw) #self._socket = gtk.Socket() #self.pack_start(self._socket) # menus self.editor_menus = EditorMenus(self._app, self) # find dialog self.find_dialog = dialog_find.KeepNoteFindDialog(self) self.show_all() def set_notebook(self, notebook): """Set notebook for editor""" # set new notebook self._notebook = notebook if self._notebook: # read default font pass else: # no new notebook, clear the view self.clear_view() def load_preferences(self, app_pref, first_open=False): """Load application preferences""" self.editor_menus.enable_spell_check( self._app.pref.get("editors", "general", "spell_check", default=True)) self._textview.set_default_font("Monospace 10") def save_preferences(self, app_pref): """Save application preferences""" # record state in preferences app_pref.set("editors", "general", "spell_check", self._textview.is_spell_check_enabled()) def get_textview(self): """Return the textview""" return self._textview def is_focus(self): """Return True if text editor has focus""" return self._textview.is_focus() def grab_focus(self): """Pass focus to textview""" self._textview.grab_focus() def clear_view(self): """Clear editor view""" self._page = None self._textview.disable() def undo(self): """Undo the last action in the viewer""" self._textview.undo() def redo(self): """Redo the last action in the viewer""" self._textview.redo() def view_nodes(self, nodes): """View a node(s) in the editor""" # editor cannot view multiple nodes at once # if asked to, it will view none if len(nodes) > 1: nodes = [] # save current page before changing nodes self.save() self._save_cursor() if len(nodes) == 0: self.clear_view() else: page = nodes[0] self._page = page self._textview.enable() try: if page.has_attr("payload_filename"): infile = page.open_file( page.get_attr("payload_filename"), "r", "utf-8") text = infile.read() infile.close() self._textview.get_buffer().set_text(text) self._load_cursor() else: self.clear_view() except UnicodeDecodeError, e: self.clear_view() except RichTextError, e: self.clear_view() self.emit("error", e.msg, e) except Exception, e: keepnote.log_error() self.clear_view() self.emit("error", "Unknown error", e) if len(nodes) > 0: self.emit("view-node", nodes[0]) def _save_cursor(self): if self._page is not None: it = self._textview.get_buffer().get_iter_at_mark( self._textview.get_buffer().get_insert()) self._page_cursors[self._page] = it.get_offset() x, y = self._textview.window_to_buffer_coords( gtk.TEXT_WINDOW_TEXT, 0, 0) it = self._textview.get_iter_at_location(x, y) self._page_scrolls[self._page] = it.get_offset() def _load_cursor(self): # place cursor in last location if self._page in self._page_cursors: offset = self._page_cursors[self._page] it = self._textview.get_buffer().get_iter_at_offset(offset) self._textview.get_buffer().place_cursor(it) # place scroll in last position if self._page in self._page_scrolls: offset = self._page_scrolls[self._page] buf = self._textview.get_buffer() it = buf.get_iter_at_offset(offset) mark = buf.create_mark(None, it, True) self._textview.scroll_to_mark(mark, 0.49, use_align=True, xalign=0.0) buf.delete_mark(mark) def save(self): """Save the loaded page""" if self._page is not None and \ self._page.is_valid() and \ self._textview.is_modified(): try: # save text data buf = self._textview.get_buffer() text = unicode_gtk(buf.get_text(buf.get_start_iter(), buf.get_end_iter())) out = self._page.open_file( self._page.get_attr("payload_filename"), "w", "utf-8") out.write(text) out.close() # save meta data self._page.set_attr_timestamp("modified_time") self._page.save() except RichTextError, e: self.emit("error", e.msg, e) except NoteBookError, e: self.emit("error", e.msg, e) except Exception, e: self.emit("error", str(e), e) def save_needed(self): """Returns True if textview is modified""" return self._textview.is_modified() return False def add_ui(self, window): self._textview.set_accel_group(window.get_accel_group()) self._textview.set_accel_path(CONTEXT_MENU_ACCEL_PATH) self.editor_menus.add_ui(window) def remove_ui(self, window): self.editor_menus.remove_ui(window) #=========================================== # callbacks for textview def _on_modified_callback(self, textview, modified): """Callback for textview modification""" self.emit("modified", self._page, modified) # make notebook node modified if modified: self._page.mark_modified() self._page.notify_change(False) def _on_visit_url(self, textview, url): """Callback for textview visiting a URL""" if is_node_url(url): host, nodeid = parse_node_url(url) node = self._notebook.get_node_by_id(nodeid) if node: self.emit("visit-node", node) else: try: self._app.open_webpage(url) except KeepNoteError, e: self.emit("error", e.msg, e) class EditorMenus (gobject.GObject): def __init__(self, app, editor): gobject.GObject.__init__(self) self._app = app self._editor = editor self._action_group = None self._uis = [] self.spell_check_toggle = None self._removed_widgets = [] #======================================================= # spellcheck def enable_spell_check(self, enabled): """Spell check""" self._editor.get_textview().enable_spell_check(enabled) # see if spell check became enabled enabled = self._editor.get_textview().is_spell_check_enabled() # update UI to match if self.spell_check_toggle: self.spell_check_toggle.set_active(enabled) return enabled def on_spell_check_toggle(self, widget): """Toggle spell checker""" self.enable_spell_check(widget.get_active()) #===================================================== # toolbar and menus def add_ui(self, window): self._action_group = gtk.ActionGroup("Editor") self._uis = [] add_actions(self._action_group, self.get_actions()) window.get_uimanager().insert_action_group( self._action_group, 0) for s in self.get_ui(): self._uis.append(window.get_uimanager().add_ui_from_string(s)) window.get_uimanager().ensure_update() self.setup_menu(window, window.get_uimanager()) def remove_ui(self, window): # remove ui for ui in reversed(self._uis): window.get_uimanager().remove_ui(ui) self._uis = [] window.get_uimanager().ensure_update() # remove action group window.get_uimanager().remove_action_group(self._action_group) self._action_group = None def get_actions(self): def BothAction(name1, *args): return [Action(name1, *args), ToggleAction(name1 + " Tool", *args)] return (map(lambda x: Action(*x), [ # finding ("Find In Page", gtk.STOCK_FIND, _("_Find In Page..."), "F", None, lambda w: self._editor.find_dialog.on_find(False)), ("Find Next In Page", gtk.STOCK_FIND, _("Find _Next In Page..."), "G", None, lambda w: self._editor.find_dialog.on_find(False, forward=True)), ("Find Previous In Page", gtk.STOCK_FIND, _("Find Pre_vious In Page..."), "G", None, lambda w: self._editor.find_dialog.on_find(False, forward=False)), ("Replace In Page", gtk.STOCK_FIND_AND_REPLACE, _("_Replace In Page..."), "R", None, lambda w: self._editor.find_dialog.on_find(True)), ]) + [ToggleAction("Spell Check", None, _("_Spell Check"), "", None, self.on_spell_check_toggle)] ) def get_ui(self): ui = [""" """] ui.append(""" """) return ui def setup_menu(self, window, uimanager): u = uimanager # get spell check toggle self.spell_check_toggle = \ uimanager.get_widget("/main_menu_bar/Tools/Viewer/Spell Check") self.spell_check_toggle.set_sensitive( self._editor.get_textview().can_spell_check()) self.spell_check_toggle.set_active(window.get_app().pref.get( "editors", "general", "spell_check", default=True)) keepnote-0.7.8/keepnote/gui/dialog_image_new.py0000644000175000017500000000527311677423601017752 0ustar razraz""" KeepNote Image Resize Dialog """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import os # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk.glade # keepnote imports import keepnote from keepnote import get_resource class NewImageDialog (object): """New Image dialog""" def __init__(self, main_window, app): self.main_window = main_window self.app = app def show(self): dialog = gtk.Dialog(_("New Image"), self.main_window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) table = gtk.Table(3, 2) dialog.vbox.pack_start(table, False, True, 0) label = gtk.Label(_("format:")) table.attach(label, 0, 1, 0, 1, xoptions=0, yoptions=0, xpadding=2, ypadding=2) # make this a drop down self.width = gtk.Entry() table.attach(self.width, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=0, xpadding=2, ypadding=2) label = gtk.Label(_("width:")) table.attach(label, 0, 1, 0, 1, xoptions=0, yoptions=0, xpadding=2, ypadding=2) self.width = gtk.Entry() table.attach(self.width, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=0, xpadding=2, ypadding=2) label = gtk.Label(_("height:")) table.attach(label, 0, 1, 0, 1, xoptions=0, yoptions=0, xpadding=2, ypadding=2) self.width = gtk.Entry() table.attach(self.width, 1, 2, 0, 1, xoptions=gtk.FILL, yoptions=0, xpadding=2, ypadding=2) table.show_all() response = dialog.run() dialog.destroy() keepnote-0.7.8/keepnote/gui/colortool.py0000644000175000017500000005131011677423601016505 0ustar razraz""" KeepNote Color picker for the toolbar """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk.glade import gobject import pango #============================================================================= # constants FONT_LETTER = "A" DEFAULT_COLORS_FLOAT = [ # lights (1, .6, .6), (1, .8, .6), (1, 1, .6), (.6, 1, .6), (.6, 1, 1), (.6, .6, 1), (1, .6, 1), # trues (1, 0, 0), (1, .64, 0), (1, 1, 0), (0, 1, 0), (0, 1, 1), (0, 0, 1), (1, 0, 1), # darks (.5, 0, 0), (.5, .32, 0), (.5, .5, 0), (0, .5, 0), (0, .5, .5), (0, 0, .5), (.5, 0, .5), # white, gray, black (1, 1, 1), (.9, .9, .9), (.75, .75, .75), (.5, .5, .5), (.25, .25, .25), (.1, .1, .1), (0, 0, 0), ] #============================================================================= # color conversions def color_float_to_int8(color): return (int(255*color[0]), int(255*color[1]), int(255*color[2])) def color_float_to_int16(color): return (int(65535*color[0]), int(65535*color[1]), int(65535*color[2])) def color_int8_to_int16(color): return (256*color[0], 256*color[1], 256*color[2]) def color_int16_to_int8(color): return (color[0]//256, color[1]//256, color[2]//256) def color_str_to_int8(colorstr): # "#AABBCC" ==> (170, 187, 204) return (int(colorstr[1:3], 16), int(colorstr[3:5], 16), int(colorstr[5:7], 16)) def color_str_to_int16(colorstr): # "#AABBCC" ==> (43520, 47872, 52224) return (int(colorstr[1:3], 16)*256, int(colorstr[3:5], 16)*256, int(colorstr[5:7], 16)*256) def color_int16_to_str(color): return "#%02x%02x%02x" % (color[0]//256, color[1]//256, color[2]//256) def color_int8_to_str(color): return "#%02x%02x%02x" % (color[0], color[1], color[2]) # convert to str DEFAULT_COLORS = [color_int8_to_str(color_float_to_int8(color)) for color in DEFAULT_COLORS_FLOAT] #============================================================================= # color menus class ColorTextImage (gtk.Image): """Image widget that display a color box with and without text""" def __init__(self, width, height, letter, border=True): gtk.Image.__init__(self) self.width = width self.height = height self.letter = letter self.border = border self.marginx = int((width - 10) / 2.0) self.marginy = - int((height - 12) / 2.0) self._pixmap = None self._colormap = None self.fg_color = None self.bg_color = None self._exposed = False self.connect("parent-set", self.on_parent_set) self.connect("expose-event", self.on_expose_event) def on_parent_set(self, widget, old_parent): self._exposed = False def on_expose_event(self, widget, event): """Set up colors on exposure""" if not self._exposed: self._exposed = True self.init_colors() def init_colors(self): self._pixmap = gdk.Pixmap(None, self.width, self.height, 24) self._colormap = self._pixmap.get_colormap() #self._colormap = gtk.gdk.colormap_get_system() #gtk.gdk.screen_get_default().get_default_colormap() self._gc = self._pixmap.new_gc() self._context = self.get_pango_context() self._fontdesc = pango.FontDescription("sans bold 10") if isinstance(self.fg_color, basestring): self.fg_color = self._colormap.alloc_color(self.fg_color) elif self.fg_color is None: self.fg_color = self._colormap.alloc_color( self.get_style().text[gtk.STATE_NORMAL]) if isinstance(self.bg_color, basestring): self.bg_color = self._colormap.alloc_color(self.bg_color) elif self.bg_color is None: self.bg_color = self._colormap.alloc_color( self.get_style().bg[gtk.STATE_NORMAL]) self._border_color = self._colormap.alloc_color(0, 0, 0) self.refresh() def set_fg_color(self, color, refresh=True): """Set the color of the color chooser""" if self._colormap: self.fg_color = self._colormap.alloc_color(color) if refresh: self.refresh() else: self.fg_color = color def set_bg_color(self, color, refresh=True): """Set the color of the color chooser""" if self._colormap: self.bg_color = self._colormap.alloc_color(color) if refresh: self.refresh() else: self.bg_color = color def refresh(self): self._gc.foreground = self.bg_color self._pixmap.draw_rectangle(self._gc, True, 0, 0, self.width, self.height) if self.border: self._gc.foreground = self._border_color self._pixmap.draw_rectangle(self._gc, False, 0, 0, self.width-1, self.height-1) if self.letter: self._gc.foreground = self.fg_color layout = pango.Layout(self._context) layout.set_text(FONT_LETTER) layout.set_font_description(self._fontdesc) self._pixmap.draw_layout(self._gc, self.marginx, self.marginy, layout) self.set_from_pixmap(self._pixmap, None) class ColorMenu (gtk.Menu): """Color picker menu""" def __init__(self, colors=DEFAULT_COLORS): gtk.Menu.__init__(self) self.width = 7 self.posi = 4 self.posj = 0 self.color_items = [] no_color = gtk.MenuItem("_Default Color") no_color.show() no_color.connect("activate", self.on_no_color) self.attach(no_color, 0, self.width, 0, 1) # new color new_color = gtk.MenuItem("_New Color...") new_color.show() new_color.connect("activate", self.on_new_color) self.attach(new_color, 0, self.width, 1, 2) # grab color #new_color = gtk.MenuItem("_Grab Color") #new_color.show() #new_color.connect("activate", self.on_grab_color) #self.attach(new_color, 0, self.width, 2, 3) # separator item = gtk.SeparatorMenuItem() item.show() self.attach(item, 0, self.width, 3, 4) # default colors self.set_colors(colors) def on_new_color(self, menu): """Callback for new color""" dialog = ColorSelectionDialog("Choose color") dialog.set_modal(True) dialog.set_transient_for(self.get_toplevel()) # TODO: does this work? dialog.set_colors(self.colors) response = dialog.run() if response == gtk.RESPONSE_OK: color = dialog.colorsel.get_current_color() color = color_int16_to_str((color.red, color.green, color.blue)) self.set_colors(dialog.get_colors()) # add new color to pallete if color not in self.colors: self.colors.append(color) self.append_color(color) self.emit("set-colors", self.colors) self.emit("set-color", color) dialog.destroy() def on_no_color(self, menu): """Callback for no color""" self.emit("set-color", None) def on_grab_color(self, menu): pass # TODO: complete def clear_colors(self): """Clears color pallete""" children = set(self.get_children()) for item in reversed(self.color_items): if item in children: self.remove(item) self.posi = 4 self.posj = 0 self.color_items = [] self.colors = [] def set_colors(self, colors): """Sets color pallete""" self.clear_colors() self.colors = list(colors) for color in self.colors: self.append_color(color, False) # TODO: add check for visible # make change visible self.unrealize() self.realize() def get_colors(self): """Returns color pallete""" return self.colors def append_color(self, color, refresh=True): """Appends color to menu""" self.add_color(self.posi, self.posj, color, refresh=refresh) self.posj += 1 if self.posj >= self.width: self.posj = 0 self.posi += 1 def add_color(self, i, j, color, refresh=True): """Add color to location in the menu""" if refresh: self.unrealize() child = gtk.MenuItem("") child.remove(child.child) img = ColorTextImage(15, 15, False) img.set_bg_color(color) child.add(img) child.child.show() child.show() child.connect("activate", lambda w: self.emit("set_color", color)) self.attach(child, j, j+1, i, i+1) self.color_items.append(child) if refresh: self.realize() gobject.type_register(ColorMenu) gobject.signal_new("set-color", ColorMenu, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) gobject.signal_new("set-colors", ColorMenu, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) gobject.signal_new("get-colors", ColorMenu, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) #============================================================================= # color selection ToolBarItem class ColorTool (gtk.MenuToolButton): """Abstract base class for a ColorTool""" def __init__(self, icon, default): gtk.MenuToolButton.__init__(self, self.icon, "") self.icon = icon self.color = None self.colors = DEFAULT_COLORS self.default = default self.default_set = True # menu self.menu = ColorMenu([]) self.menu.connect("set-color", self.on_set_color) self.menu.connect("set-colors", self.on_set_colors) self.set_menu(self.menu) self.connect("clicked", self.use_color) self.connect("show-menu", self.on_show_menu) def on_set_color(self, menu, color): """Callback from menu when color is set""" raise Exception("unimplemented") def on_set_colors(self, menu, color): """Callback from menu when pallete is set""" self.colors = list(self.menu.get_colors()) self.emit("set-colors", self.colors) def set_colors(self, colors): """Sets pallete""" self.colors = list(colors) self.menu.set_colors(colors) def get_colors(self): return self.colors def use_color(self, menu): """Callback for when button is clicked""" self.emit("set-color", self.color) def set_default(self, color): """Set default color""" self.default = color if self.default_set: self.icon.set_fg_color(self.default) def on_show_menu(self, widget): """Callback for when menu is displayed""" self.emit("get-colors") self.menu.set_colors(self.colors) gobject.type_register(ColorTool) gobject.signal_new("set-color", ColorTool, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) gobject.signal_new("set-colors", ColorTool, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) gobject.signal_new("get-colors", ColorTool, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) class FgColorTool (ColorTool): """ToolItem for choosing the foreground color""" def __init__(self, width, height, default): self.icon = ColorTextImage(width, height, True, True) self.icon.set_fg_color(default) self.icon.set_bg_color("#ffffff") ColorTool.__init__(self, self.icon, default) def on_set_color(self, menu, color): """Callback from menu""" if color is None: self.default_set = True self.icon.set_fg_color(self.default) else: self.default_set = False self.icon.set_fg_color(color) self.color = color self.emit("set-color", color) class BgColorTool (ColorTool): """ToolItem for choosing the backgroundground color""" def __init__(self, width, height, default): self.icon = ColorTextImage(width, height, False, True) self.icon.set_bg_color(default) ColorTool.__init__(self, self.icon, default) def on_set_color(self, menu, color): """Callback from menu""" if color is None: self.default_set = True self.icon.set_bg_color(self.default) else: self.default_set = False self.icon.set_bg_color(color) self.color = color self.emit("set-color", color) #============================================================================= # color selection dialog and pallete class ColorSelectionDialog (gtk.ColorSelectionDialog): def __init__(self, title="Choose color"): gtk.ColorSelectionDialog.__init__(self, title) self.colorsel.set_has_opacity_control(False) # hide default gtk pallete self.colorsel.set_has_palette(False) # structure of ColorSelection widget # colorsel = VBox(HBox(selector, VBox(Table, VBox(Label, pallete), # my_pallete))) # pallete = Table(Frame(DrawingArea), ...) # #vbox = self.colorsel.get_children()[0].get_children()[1].get_children()[1] #pallete = vbox.get_children()[1] vbox = self.colorsel.get_children()[0].get_children()[1] # label label = gtk.Label(_("Pallete:")) label.set_alignment(0, .5) label.show() vbox.pack_start(label, expand=False, fill=True, padding=0) # pallete self.pallete = ColorPallete(DEFAULT_COLORS) self.pallete.connect("pick-color", self.on_pick_pallete_color) self.pallete.show() vbox.pack_start(self.pallete, expand=False, fill=True, padding=0) # pallete buttons hbox = gtk.HButtonBox() hbox.show() vbox.pack_start(hbox, expand=False, fill=True, padding=0) # new color button = gtk.Button("new", stock=gtk.STOCK_NEW) button.set_relief(gtk.RELIEF_NONE) button.connect("clicked", self.on_new_color) button.show() hbox.pack_start(button, expand=False, fill=False, padding=0) # delete color button = gtk.Button("delete", stock=gtk.STOCK_DELETE) button.set_relief(gtk.RELIEF_NONE) button.connect("clicked", self.on_delete_color) button.show() hbox.pack_start(button, expand=False, fill=False, padding=0) # reset colors button = gtk.Button(stock=gtk.STOCK_UNDO) button.get_children()[0].get_child().get_children()[1].set_text_with_mnemonic("_Reset") button.set_relief(gtk.RELIEF_NONE) button.connect("clicked", self.on_reset_colors) button.show() hbox.pack_start(button, expand=False, fill=False, padding=0) # colorsel signals def func(w): color = self.colorsel.get_current_color() self.pallete.set_color( color_int16_to_str((color.red, color.green, color.blue))) self.colorsel.connect("color-changed", func) def set_colors(self, colors): """Set pallete colors""" self.pallete.set_colors(colors) def get_colors(self): """Get pallete colors""" return self.pallete.get_colors() def on_pick_pallete_color(self, widget, color): self.colorsel.set_current_color(gtk.gdk.Color(color)) def on_new_color(self, widget): color = self.colorsel.get_current_color() self.pallete.new_color( color_int16_to_str((color.red, color.green, color.blue))) def on_delete_color(self, widget): self.pallete.remove_selected() def on_reset_colors(self, widget): self.pallete.set_colors(DEFAULT_COLORS) class ColorPallete (gtk.IconView): def __init__(self, colors=DEFAULT_COLORS, nrows=1, ncols=7): gtk.IconView.__init__(self) self._model = gtk.ListStore(gtk.gdk.Pixbuf, object) self._cell_size = [30, 20] self.set_model(self._model) self.set_reorderable(True) self.set_property("columns", 7) self.set_property("spacing", 0) self.set_property("column-spacing", 0) self.set_property("row-spacing", 0) self.set_property("item-padding", 1) self.set_property("margin", 1) self.set_pixbuf_column(0) self.connect("selection-changed", self._on_selection_changed) self.set_colors(colors) # TODO: could ImageColorText become a DrawingArea widget? def clear_colors(self): """Clears all colors from pallete""" self._model.clear() def set_colors(self, colors): """Sets colors in pallete""" self.clear_colors() for color in colors: self.append_color(color) def get_colors(self): """Returns colors in pallete""" colors = [] self._model.foreach( lambda m, p, i: colors.append(m.get_value(i, 1))) return colors def append_color(self, color): """Append color to pallete""" width, height = self._cell_size # make pixbuf pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height) self._draw_color(pixbuf, color, 0, 0, width, height) self._model.append([pixbuf, color]) def remove_selected(self): """Remove selected color""" for path in self.get_selected_items(): self._model.remove(self._model.get_iter(path)) def new_color(self, color): """Adds a new color""" self.append_color(color) n = self._model.iter_n_children(None) self.select_path((n-1,)) def set_color(self, color): """Sets the color of the selected cell""" width, height = self._cell_size it = self._get_selected_iter() if it: pixbuf = self._model.get_value(it, 0) self._draw_color(pixbuf, color, 0, 0, width, height) self._model.set_value(it, 1, color) def _get_selected_iter(self): """Returns the selected cell (TreeIter)""" for path in self.get_selected_items(): return self._model.get_iter(path) return None def _on_selection_changed(self, view): """Callback for when selection changes""" it = self._get_selected_iter() if it: color = self._model.get_value(it, 1) self.emit("pick-color", color) def _draw_color(self, pixbuf, color, x, y, width, height): """Draws a color cell""" border_color = "#000000" # create pixmap pixmap = gdk.Pixmap(None, width, height, 24) cmap = pixmap.get_colormap() gc = pixmap.new_gc() color1 = cmap.alloc_color(color) color2 = cmap.alloc_color(border_color) # draw fill gc.foreground = color1 #gtk.gdk.Color(* color) pixmap.draw_rectangle(gc, True, 0, 0, width, height) # draw border gc.foreground = color2 #gtk.gdk.Color(* border_color) pixmap.draw_rectangle(gc, False, 0, 0, width-1, height-1) pixbuf.get_from_drawable(pixmap, cmap, 0, 0, 0, 0, width, height) gobject.type_register(ColorPallete) gobject.signal_new("pick-color", ColorPallete, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) keepnote-0.7.8/keepnote/gui/main_window.py0000644000175000017500000015340211722232557017010 0ustar razraz""" KeepNote Graphical User Interface for KeepNote Application """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import mimetypes import os import shutil import subprocess import sys import time import thread import threading import uuid # pygtk imports import pygtk pygtk.require('2.0') import gtk import gobject # keepnote imports import keepnote from keepnote import \ KeepNoteError, \ ensure_unicode, \ unicode_gtk, \ FS_ENCODING from keepnote.notebook import \ NoteBookError, \ NoteBookVersionError from keepnote import notebook as notebooklib from keepnote import tasklib from keepnote.gui import \ get_resource, \ get_resource_image, \ get_resource_pixbuf, \ Action, \ ToggleAction, \ add_actions, \ CONTEXT_MENU_ACCEL_PATH, \ CLIPBOARD_NAME, \ FileChooserDialog, \ init_key_shortcuts, \ UIManager from keepnote.gui.icons import \ lookup_icon_filename from keepnote.gui import richtext from keepnote.gui import \ dialog_image_new, \ dialog_drag_drop_test, \ dialog_wait, \ update_file_preview from keepnote.gui.icon_menu import IconMenu from keepnote.gui.three_pane_viewer import ThreePaneViewer from keepnote.gui.tabbed_viewer import TabbedViewer _ = keepnote.translate #============================================================================= # constants DEFAULT_WINDOW_SIZE = (1024, 600) DEFAULT_WINDOW_POS = (-1, -1) #============================================================================= class KeepNoteWindow (gtk.Window): """Main windows for KeepNote""" def __init__(self, app, winid=None): gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) self._app = app # application object self._winid = winid if winid else unicode(uuid.uuid4()) self._viewers = [] # window state self._maximized = False # True if window is maximized self._was_maximized = False # True if iconified and was maximized self._iconified = False # True if window is minimized self._tray_icon = None # True if tray icon is present self._recent_notebooks = [] self._uimanager = UIManager() self._accel_group = self._uimanager.get_accel_group() self.add_accel_group(self._accel_group) init_key_shortcuts() self.init_layout() self.setup_systray() # load preferences for the first time self.load_preferences(True) def get_id(self): return self._winid def init_layout(self): # init main window self.set_title(keepnote.PROGRAM_NAME) self.set_default_size(*DEFAULT_WINDOW_SIZE) self.set_icon_list(get_resource_pixbuf("keepnote-16x16.png"), get_resource_pixbuf("keepnote-32x32.png"), get_resource_pixbuf("keepnote-64x64.png")) # main window signals self.connect("error", lambda w,m,e,t: self.error(m,e,t)) self.connect("delete-event", lambda w,e: self._on_close()) self.connect("window-state-event", self._on_window_state) self.connect("size-allocate", self._on_window_size) #self._app.pref.changed.add(self._on_app_options_changed) #==================================== # Dialogs self.drag_test = dialog_drag_drop_test.DragDropTestDialog(self) self.viewer = self.new_viewer() #==================================== # Layout # vertical box main_vbox = gtk.VBox(False, 0) self.add(main_vbox) # menu bar main_vbox.set_border_width(0) self.menubar = self.make_menubar() main_vbox.pack_start(self.menubar, False, True, 0) # toolbar main_vbox.pack_start(self.make_toolbar(), False, True, 0) main_vbox2 = gtk.VBox(False, 0) main_vbox2.set_border_width(1) main_vbox.pack_start(main_vbox2, True, True, 0) # viewer self.viewer_box = gtk.VBox(False, 0) main_vbox2.pack_start(self.viewer_box, True, True, 0) # status bar status_hbox = gtk.HBox(False, 0) main_vbox.pack_start(status_hbox, False, True, 0) # message bar self.status_bar = gtk.Statusbar() status_hbox.pack_start(self.status_bar, False, True, 0) self.status_bar.set_property("has-resize-grip", False) self.status_bar.set_size_request(300, -1) # stats bar self.stats_bar = gtk.Statusbar() status_hbox.pack_start(self.stats_bar, True, True, 0) #==================================================== # viewer self.viewer_box.pack_start(self.viewer, True, True, 0) # add viewer menus self.viewer.add_ui(self) def setup_systray(self): """Setup systray for window""" # system tray icon if gtk.gtk_version > (2, 10): if not self._tray_icon: self._tray_icon = gtk.StatusIcon() self._tray_icon.set_from_pixbuf( get_resource_pixbuf("keepnote-32x32.png")) self._tray_icon.set_tooltip(keepnote.PROGRAM_NAME) self._statusicon_menu = self.make_statusicon_menu() self._tray_icon.connect("activate", self._on_tray_icon_activate) self._tray_icon.connect('popup-menu', self._on_systray_popup_menu) self._tray_icon.set_property( "visible", self._app.pref.get("window", "use_systray", default=True)) else: self._tray_icon = None def _on_systray_popup_menu(self, status, button, time): self._statusicon_menu.popup(None, None, None, button, time) #============================================== # viewers def new_viewer(self): """Creates a new viewer for this window""" #viewer = ThreePaneViewer(self._app, self) viewer = TabbedViewer(self._app, self) viewer.connect("error", lambda w,m,e: self.error(m, e, None)) viewer.connect("status", lambda w,m,b: self.set_status(m, b)) viewer.connect("window-request", self._on_window_request) viewer.connect("current-node", self._on_current_node) viewer.connect("modified", self._on_viewer_modified) return viewer def add_viewer(self, viewer): """Adds a viewer to the window""" self._viewers.append(viewer) def remove_viewer(self, viewer): """Removes a viewer from the window""" self._viewers.remove(viewer) def get_all_viewers(self): """Returns list of all viewers associated with window""" return self._viewers def get_all_notebooks(self): """Returns all notebooks loaded by all viewers""" return set(filter(lambda n: n is not None, (v.get_notebook() for v in self._viewers))) #=============================================== # accessors def get_app(self): """Returns application object""" return self._app def get_uimanager(self): """Returns the UIManager for the window""" return self._uimanager def get_viewer(self): """Returns window's viewer""" return self.viewer def get_accel_group(self): """Returns the accel group for the window""" return self._accel_group def get_notebook(self): """Returns the currently loaded notebook""" return self.viewer.get_notebook() def get_current_node(self): """Returns the currently selected node""" return self.viewer.get_current_node() #========================================================= # main window gui callbacks def _on_window_state(self, window, event): """Callback for window state""" iconified = self._iconified # keep track of maximized and minimized state self._iconified = bool(event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED) # detect recent iconification if not iconified and self._iconified: # save maximized state before iconification self._was_maximized = self._maximized self._maximized = bool(event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED) # detect recent de-iconification if iconified and not self._iconified: # explicitly maximize if not maximized # NOTE: this is needed to work around a MS windows GTK bug if self._was_maximized: gobject.idle_add(self.maximize) def _on_window_size(self, window, event): """Callback for resize events""" # record window size if it is not maximized or minimized if not self._maximized and not self._iconified: self._app.pref.get("window")["window_size"] = self.get_size() #def _on_app_options_changed(self): # self.load_preferences() def _on_tray_icon_activate(self, icon): """Try icon has been clicked in system tray""" if self.is_active(): self.minimize_window() else: self.restore_window() #============================================================= # viewer callbacks def _on_window_request(self, viewer, action): """Callback for requesting an action from the main window""" if action == "minimize": self.minimize_window() elif action == "restore": self.restore_window() else: raise Exception("unknown window request: " + str(action)) #================================================= # Window manipulation def minimize_window(self): """Minimize the window (block until window is minimized""" if self._iconified: return # TODO: add timer in case minimize fails def on_window_state(window, event): if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: gtk.main_quit() sig = self.connect("window-state-event", on_window_state) self.iconify() gtk.main() self.disconnect(sig) def restore_window(self): """Restore the window from minimization""" self.deiconify() self.present() def on_new_window(self): """Open a new window""" win = self._app.new_window() notebook = self.get_notebook() if notebook: self._app.ref_notebook(notebook) win.set_notebook(notebook) #============================================== # Application preferences def load_preferences(self, first_open=False): """Load preferences""" p = self._app.pref # notebook window_size = p.get("window", "window_size", default=DEFAULT_WINDOW_SIZE) window_maximized = p.get("window", "window_maximized", default=True) self.setup_systray() use_systray = p.get("window", "use_systray", default=True) # window config for first open if first_open: self.resize(*window_size) if window_maximized: self.maximize() minimize = p.get("window", "minimize_on_start", default=False) if use_systray and minimize: self.iconify() # config window skip = p.get("window", "skip_taskbar", default=False) if use_systray: self.set_property("skip-taskbar-hint", skip) self.set_keep_above(p.get("window", "keep_above", default=False)) if p.get("window", "stick", default=False): self.stick() else: self.unstick() # other window wide properties self._recent_notebooks = p.get("recent_notebooks", default=[]) self.set_recent_notebooks_menu(self._recent_notebooks) self._uimanager.set_force_stock( p.get("look_and_feel", "use_stock_icons", default=False)) self.viewer.load_preferences(self._app.pref, first_open) def save_preferences(self): """Save preferences""" p = self._app.pref # save window preferences p.set("window", "window_maximized", self._maximized) p.set("recent_notebooks", self._recent_notebooks) # let viewer save preferences self.viewer.save_preferences(self._app.pref) def set_recent_notebooks_menu(self, recent_notebooks): """Set the recent notebooks in the file menu""" menu = self._uimanager.get_widget("/main_menu_bar/File/Open Recent Notebook") # init menu if menu.get_submenu() is None: submenu = gtk.Menu() submenu.show() menu.set_submenu(submenu) menu = menu.get_submenu() # clear menu menu.foreach(lambda x: menu.remove(x)) def make_filename(filename, maxsize=30): if len(filename) > maxsize: base = os.path.basename(filename) pre = max(maxsize - len(base), 10) return os.path.join(filename[:pre] + u"...", base) else: return filename def make_func(filename): return lambda w: self.open_notebook(filename) # populate menu for i, notebook in enumerate(recent_notebooks): item = gtk.MenuItem(u"%d. %s" % (i+1, make_filename(notebook))) item.connect("activate", make_func(notebook)) item.show() menu.append(item) def add_recent_notebook(self, filename): """Add recent notebook""" if filename in self._recent_notebooks: self._recent_notebooks.remove(filename) self._recent_notebooks = [filename] + \ self._recent_notebooks[:keepnote.gui.MAX_RECENT_NOTEBOOKS] self.set_recent_notebooks_menu(self._recent_notebooks) #============================================= # Notebook open/save/close UI def on_new_notebook(self): """Launches New NoteBook dialog""" dialog = FileChooserDialog( _("New Notebook"), self, action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(_("Cancel"), gtk.RESPONSE_CANCEL, _("New"), gtk.RESPONSE_OK), app=self._app, persistent_path="new_notebook_path") response = dialog.run() if response == gtk.RESPONSE_OK: # create new notebook if dialog.get_filename(): self.new_notebook(unicode_gtk(dialog.get_filename())) dialog.destroy() def on_open_notebook(self): """Launches Open NoteBook dialog""" dialog = gtk.FileChooserDialog( _("Open Notebook"), self, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(_("Cancel"), gtk.RESPONSE_CANCEL, _("Open"), gtk.RESPONSE_OK)) def on_folder_changed(filechooser): folder = unicode_gtk(filechooser.get_current_folder()) if os.path.exists(os.path.join(folder, notebooklib.PREF_FILE)): filechooser.response(gtk.RESPONSE_OK) dialog.connect("current-folder-changed", on_folder_changed) path = self._app.get_default_path("new_notebook_path") if os.path.exists(path): dialog.set_current_folder(path) file_filter = gtk.FileFilter() file_filter.add_pattern("*.nbk") file_filter.set_name(_("Notebook (*.nbk)")) dialog.add_filter(file_filter) file_filter = gtk.FileFilter() file_filter.add_pattern("*") file_filter.set_name(_("All files (*.*)")) dialog.add_filter(file_filter) response = dialog.run() if response == gtk.RESPONSE_OK: path = ensure_unicode(dialog.get_current_folder(), FS_ENCODING) if path: self._app.pref.set("default_paths", "new_notebook_path", os.path.dirname(path)) notebook_file = ensure_unicode(dialog.get_filename(), FS_ENCODING) if notebook_file: self.open_notebook(notebook_file) dialog.destroy() def on_open_notebook_url(self): """Launches Open NoteBook from URL dialog""" dialog = gtk.Dialog("Open Notebook from URL", self, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) p = dialog.get_content_area() h = gtk.HBox() h.show() p.pack_start(h, expand=False, fill=True, padding=0) # url label l = gtk.Label("URL: ") l.show() h.pack_start(l, expand=False, fill=True, padding=0) # url entry entry = gtk.Entry() entry.set_width_chars(80) entry.connect("activate", lambda w: dialog.response(gtk.RESPONSE_OK)) entry.show() h.pack_start(entry, expand=True, fill=True, padding=0) # actions dialog.add_button("_Cancel", gtk.RESPONSE_CANCEL) dialog.add_button("_Open", gtk.RESPONSE_OK) response = dialog.run() if response == gtk.RESPONSE_OK: url = unicode_gtk(entry.get_text()) if url: self.open_notebook(url) dialog.destroy() def _on_close(self): """Callback for window close""" try: # TODO: decide if a clipboard action is needed before # closing down. #clipboard = self.get_clipboard(selection=CLIPBOARD_NAME) #clipboard.set_can_store(None) #clipboard.store() self._app.save() self.close_notebook() if self._tray_icon: # turn off try icon self._tray_icon.set_property("visible", False) except Exception, e: self.error("Error while closing", e, sys.exc_info()[2]) return False def close(self): """Close the window""" self._on_close() self.emit("delete-event", None) self.destroy() def on_quit(self): """Quit the application""" self._app.save() self._app.quit() #=============================================== # Notebook actions def save_notebook(self, silent=False): """Saves the current notebook""" try: # save window information for all notebooks associated with this # window for notebook in self.get_all_notebooks(): p = notebook.pref.get("windows", "ids", define=True) p[self._winid] = { "viewer_type": self.viewer.get_name(), "viewerid": self.viewer.get_id()} # let the viewer save its information self.viewer.save() self.set_status(_("Notebook saved")) except Exception, e: if not silent: self.error(_("Could not save notebook."), e, sys.exc_info()[2]) self.set_status(_("Error saving notebook")) return def reload_notebook(self): """Reload the current NoteBook""" notebook = self.viewer.get_notebook() if notebook is None: self.error(_("Reloading only works when a notebook is open.")) return filename = notebook.get_filename() self._app.close_all_notebook(notebook, False) self.open_notebook(filename) self.set_status(_("Notebook reloaded")) def new_notebook(self, filename): """Creates and opens a new NoteBook""" if self.viewer.get_notebook() is not None: self.close_notebook() try: # make sure filename is unicode filename = ensure_unicode(filename, FS_ENCODING) notebook = notebooklib.NoteBook() notebook.create(filename) notebook.set_attr("title", os.path.basename(filename)) notebook.close() self.set_status(_("Created '%s'") % notebook.get_title()) except NoteBookError, e: self.error(_("Could not create new notebook."), e, sys.exc_info()[2]) self.set_status("") return None return self.open_notebook(filename, new=True) def _load_notebook(self, filename): """Loads notebook in background with progress bar""" notebook = self._app.get_notebook(filename, self) if notebook is None: return None # check for indexing # TODO: is this the best place for checking? # There is a difference between normal incremental indexing # and indexing due version updating. # incremental updating (checking a few files that have changed on # disk) should be done within notebook.load(). # Whole notebook re-indexing, triggered by version upgrade # should be done separately, and with a different wait dialog # clearly indicating that notebook loading is going to take # longer than usual. if notebook.index_needed(): self.update_index(notebook) return notebook def _restore_windows(self, notebook, open_here=True): """ Restore multiple windows for notebook open_here -- if True, will open notebook in this window Cases: 1. if notebook has no saved windows, just open notebook in this window 2. if notebook has 1 saved window if open_here: open it in this window else: if this window has no opened notebooks, reassign its ids to the notebook and open it here else reassign notebooks saved ids to this window and viewer 3. if notebook has >1 saved windows, open them in their own windows if this window has no notebook, reassign its id to one of the saved ids. """ # init window lookup win_lookup = dict((w.get_id(), w) for w in self._app.get_windows()) def open_in_window(winid, viewerid, notebook): win = win_lookup.get(winid, None) if win is None: # open new window win = self._app.new_window() win_lookup[winid] = win win._winid = winid if viewerid: win.get_viewer().set_id(viewerid) # set notebook self._app.ref_notebook(notebook) win.set_notebook(notebook) # find out how many windows this notebook had last time # init viewer if needed windows = notebook.pref.get("windows", "ids", define=True) notebook.pref.get("viewers", "ids", define=True) if len(windows) == 0: # no presistence info found, just open notebook in this window self.set_notebook(notebook) elif len(windows) == 1: # restore a single window winid, winpref = windows.items()[0] viewerid = winpref.get("viewerid", None) if viewerid is not None: if len(self.get_all_notebooks()) == 0: # no notebooks are open, so it is ok to reassign # the viewer's id to match the notebook pref self._winid = winid self.viewer.set_id(viewerid) self.set_notebook(notebook) elif open_here: # TODO: needs more testing # notebooks are open, so reassign the notebook's pref to # match the existing viewer notebook.pref.set("windows", "ids", {self._winid: {"viewerid": self.viewer.get_id(), "viewer_type": self.viewer.get_name()}}) notebook.pref.set( "viewers", "ids", self.viewer.get_id(), notebook.pref.get("viewers", "ids", viewerid, define=True)) del notebook.pref.get("viewers", "ids")[viewerid] self.set_notebook(notebook) else: # open in whatever window the notebook wants open_in_window(winid, viewerid, notebook) self._app.unref_notebook(notebook) elif len(windows) > 1: # get different kinds of window ids restoring_ids = set(windows.keys()) new_ids = restoring_ids - set(win_lookup.keys()) if len(self.get_all_notebooks()) == 0: # special case: if no notebooks opened, then make sure # to reuse this window if self._winid not in restoring_ids: self._winid = iter(restoring_ids).next() restoring_ids.remove(self._winid) viewerid = windows[self._winid].get("viewerid", None) if viewerid: self.viewer.set_id(viewerid) self.set_notebook(notebook) # restore remaining windows while len(restoring_ids) > 0: winid = restoring_ids.pop() viewerid = windows[winid].get("viewerid", None) open_in_window(winid, viewerid, notebook) self._app.unref_notebook(notebook) def open_notebook(self, filename, new=False, open_here=True): """Opens a new notebook""" #try: # filename = notebooklib.normalize_notebook_dirname( # filename, longpath=False) #except Exception, e: # self.error(_("Could note find notebook '%s'.") % filename, e, # sys.exc_info()[2]) # notebook = None #else: notebook = self._load_notebook(filename) if notebook is None: return # setup notebook self._restore_windows(notebook, open_here=open_here) if not new: self.set_status(_("Loaded '%s'") % notebook.get_title()) self.update_title() # save notebook to recent notebooks self.add_recent_notebook(filename) return notebook def close_notebook(self, notebook=None): """Close the NoteBook""" if notebook is None: notebook = self.get_notebook() self.viewer.close_notebook(notebook) self.set_status(_("Notebook closed")) def _on_close_notebook(self, notebook): """Callback when notebook is closing""" pass def set_notebook(self, notebook): """Set the NoteBook for the window""" self.viewer.set_notebook(notebook) def update_index(self, notebook=None, clear=False): """Update notebook index""" if notebook is None: notebook = self.viewer.get_notebook() if notebook is None: return def update(task): # erase database first # NOTE: I do this right now so that corrupt databases can be # cleared out of the way. if clear: notebook.clear_index() try: for node in notebook.index_all(): # terminate if search is canceled if task.aborted(): break except Exception, e: self.error(_("Error during index"), e, sys.exc_info()[2]) task.finish() # launch task self.wait_dialog(_("Indexing notebook"), _("Indexing..."), tasklib.Task(update)) def compact_index(self, notebook=None): """Update notebook index""" if notebook is None: notebook = self.viewer.get_notebook() if notebook is None: return def update(task): notebook.index("compact") # launch task self.wait_dialog(_("Compacting notebook index"), _("Compacting..."), tasklib.Task(update)) #===================================================== # viewer callbacks def update_title(self, node=None): """Set the modification state of the notebook""" notebook = self.viewer.get_notebook() if notebook is None: self.set_title(keepnote.PROGRAM_NAME) else: title = notebook.get_attr("title", u"") if node is None: node = self.get_current_node() if node is not None: title += u": " + node.get_attr("title", "") modified = notebook.save_needed() if modified: self.set_title(u"* %s" % title) self.set_status(_("Notebook modified")) else: self.set_title(title) def _on_current_node(self, viewer, node): """Callback for when viewer changes the current node""" self.update_title(node) def _on_viewer_modified(self, viewer, modified): """Callback for when viewer has a modified notebook""" self.update_title() #=========================================================== # page and folder actions def get_selected_nodes(self): """ Returns list of selected nodes """ return self.viewer.get_selected_nodes() def confirm_delete_nodes(self, nodes): """Confirm whether nodes should be deleted""" # TODO: move to app? # TODO: add note names to dialog # TODO: assume one node is selected # could make this a stand alone function/dialog box for node in nodes: if node.get_attr("content_type") == notebooklib.CONTENT_TYPE_TRASH: self.error(_("The Trash folder cannot be deleted."), None) return False if node.get_parent() == None: self.error(_("The top-level folder cannot be deleted."), None) return False if len(nodes) > 1 or len(nodes[0].get_children()) > 0: message = _("Do you want to delete this note and all of its children?") else: message = _("Do you want to delete this note?") return self._app.ask_yes_no(message, _("Delete Note"), parent=self.get_toplevel()) def on_empty_trash(self): """Empty Trash folder in NoteBook""" if self.get_notebook() is None: return try: self.get_notebook().empty_trash() except NoteBookError, e: self.error(_("Could not empty trash."), e, sys.exc_info()[2]) #================================================= # action callbacks def on_view_node_external_app(self, app, node=None, kind=None): """View a node with an external app""" self._app.save() # determine node to view if node is None: nodes = self.get_selected_nodes() if len(nodes) == 0: self.emit("error", _("No notes are selected."), None, None) return node = nodes[0] try: self._app.run_external_app_node(app, node, kind) except KeepNoteError, e: self.emit("error", e.msg, e, sys.exc_info()[2]) #===================================================== # Cut/copy/paste # forward cut/copy/paste to the correct widget def on_cut(self): """Cut callback""" widget = self.get_focus() if gobject.signal_lookup("cut-clipboard", widget) != 0: widget.emit("cut-clipboard") def on_copy(self): """Copy callback""" widget = self.get_focus() if gobject.signal_lookup("copy-clipboard", widget) != 0: widget.emit("copy-clipboard") def on_copy_tree(self): """Copy tree callback""" widget = self.get_focus() if gobject.signal_lookup("copy-tree-clipboard", widget) != 0: widget.emit("copy-tree-clipboard") def on_paste(self): """Paste callback""" widget = self.get_focus() if gobject.signal_lookup("paste-clipboard", widget) != 0: widget.emit("paste-clipboard") def on_undo(self): """Undo callback""" self.viewer.undo() def on_redo(self): """Redo callback""" self.viewer.redo() #=================================================== # Misc. def view_error_log(self): """View error in text editor""" # windows locks open files # therefore we should copy error log before viewing it try: filename = os.path.realpath(keepnote.get_user_error_log()) filename2 = filename + u".bak" shutil.copy(filename, filename2) # use text editor to view error log self._app.run_external_app("text_editor", filename2) except Exception, e: self.error(_("Could not open error log") + ":\n" + str(e), e, sys.exc_info()[2]) def view_config_files(self): """View config folder in a file explorer""" try: # use text editor to view error log filename = keepnote.get_user_pref_dir() self._app.run_external_app("file_explorer", filename) except Exception, e: self.error(_("Could not open error log") + ":\n" + str(e), e, sys.exc_info()[2]) #================================================== # Help/about dialog def on_about(self): """Display about dialog""" def func(dialog, link, data): try: self._app.open_webpage(link) except KeepNoteError, e: self.error(e.msg, e, sys.exc_info()[2]) gtk.about_dialog_set_url_hook(func, None) about = gtk.AboutDialog() about.set_name(keepnote.PROGRAM_NAME) about.set_version(keepnote.PROGRAM_VERSION_TEXT) about.set_copyright(keepnote.COPYRIGHT) about.set_logo(get_resource_pixbuf("keepnote-icon.png")) about.set_website(keepnote.WEBSITE) about.set_license(keepnote.LICENSE_NAME) about.set_translator_credits(keepnote.TRANSLATOR_CREDITS) license_file = keepnote.get_resource(u"rc", u"COPYING") if os.path.exists(license_file): about.set_license(open(license_file).read()) #about.set_authors(["Matt Rasmussen "]) about.set_transient_for(self) about.set_position(gtk.WIN_POS_CENTER_ON_PARENT) about.connect("response", lambda d,r: about.destroy()) about.show() #=========================================== # Messages, warnings, errors UI/dialogs def set_status(self, text, bar="status"): """Sets a status message in the status bar""" if bar == "status": self.status_bar.pop(0) self.status_bar.push(0, text) elif bar == "stats": self.stats_bar.pop(0) self.stats_bar.push(0, text) else: raise Exception("unknown bar '%s'" % bar) def error(self, text, error=None, tracebk=None): """Display an error message""" self._app.error(text, error, tracebk) def wait_dialog(self, title, text, task, cancel=True): """Display a wait dialog""" # NOTE: pause autosave while performing long action self._app.pause_auto_save(True) dialog = dialog_wait.WaitDialog(self) dialog.show(title, text, task, cancel=cancel) self._app.pause_auto_save(False) #================================================ # Menus def get_actions(self): actions = map(lambda x: Action(*x), [ ("File", None, _("_File")), ("New Notebook", gtk.STOCK_NEW, _("_New Notebook..."), "", _("Start a new notebook"), lambda w: self.on_new_notebook()), ("Open Notebook", gtk.STOCK_OPEN, _("_Open Notebook..."), "O", _("Open an existing notebook"), lambda w: self.on_open_notebook()), ("Open Recent Notebook", gtk.STOCK_OPEN, _("Open Re_cent Notebook")), ("Reload Notebook", gtk.STOCK_REVERT_TO_SAVED, _("_Reload Notebook"), "", _("Reload the current notebook"), lambda w: self.reload_notebook()), ("Save Notebook", gtk.STOCK_SAVE, _("_Save Notebook"), "S", _("Save the current notebook"), lambda w: self._app.save()), ("Close Notebook", gtk.STOCK_CLOSE, _("_Close Notebook"), "", _("Close the current notebook"), lambda w: self._app.close_all_notebook(self.get_notebook())), ("Empty Trash", gtk.STOCK_DELETE, _("Empty _Trash"), "", None, lambda w: self.on_empty_trash()), ("Export", None, _("_Export Notebook")), ("Import", None, _("_Import Notebook")), ("Quit", gtk.STOCK_QUIT, _("_Quit"), "Q", _("Quit KeepNote"), lambda w: self.on_quit()), #======================================= ("Edit", None, _("_Edit")), ("Undo", gtk.STOCK_UNDO, None, "Z", None, lambda w: self.on_undo()), ("Redo", gtk.STOCK_REDO, None, "Z", None, lambda w: self.on_redo()), ("Cut", gtk.STOCK_CUT, None, "X", None, lambda w: self.on_cut()), ("Copy", gtk.STOCK_COPY, None, "C", None, lambda w: self.on_copy()), ("Copy Tree", gtk.STOCK_COPY, None, "C", None, lambda w: self.on_copy_tree()), ("Paste", gtk.STOCK_PASTE, None, "V", None, lambda w: self.on_paste()), ("KeepNote Preferences", gtk.STOCK_PREFERENCES, _("_Preferences"), "", None, lambda w: self._app.app_options_dialog.show(self)), #======================================== ("Search", None, _("_Search")), ("Search All Notes", gtk.STOCK_FIND, _("_Search All Notes"), "K", None, lambda w: self.search_box.grab_focus()), #======================================= ("Go", None, _("_Go")), #======================================== ("View", None, _("_View")), ("View Note As", gtk.STOCK_OPEN, _("_View Note As")), ("View Note in File Explorer", gtk.STOCK_OPEN, _("View Note in File Explorer"), "", None, lambda w: self.on_view_node_external_app("file_explorer", kind="dir")), ("View Note in Text Editor", gtk.STOCK_OPEN, _("View Note in Text Editor"), "", None, lambda w: self.on_view_node_external_app("text_editor", kind="page")), ("View Note in Web Browser", gtk.STOCK_OPEN, _("View Note in Web Browser"), "", None, lambda w: self.on_view_node_external_app("web_browser", kind="page")), ("Open File", gtk.STOCK_OPEN, _("_Open File"), "", None, lambda w: self.on_view_node_external_app("file_launcher", kind="file")), #========================================= ("Tools", None, _("_Tools")), ("Update Notebook Index", None, _("_Update Notebook Index"), "", None, lambda w: self.update_index(clear=True)), ("Compact Notebook Index", None, _("_Compact Notebook Index"), "", None, lambda w: self.compact_index()), ("Open Notebook URL", None, _("_Open Notebook from URL"), "", None, lambda w: self.on_open_notebook_url()), #========================================= ("Window", None, _("Window")), ("New Window", None, _("New Window"), "", _("Open a new window"), lambda w: self.on_new_window()), ("Close Window", None, _("Close Window"), "", _("Close window"), lambda w: self.close()), #========================================= ("Help", None, _("_Help")), ("View Error Log...", gtk.STOCK_DIALOG_ERROR, _("View _Error Log..."), "", None, lambda w: self.view_error_log()), ("View Preference Files...", None, _("View Preference Files..."), "", None, lambda w: self.view_config_files()), ("Drag and Drop Test...", None, _("Drag and Drop Test..."), "", None, lambda w: self.drag_test.on_drag_and_drop_test()), ("About", gtk.STOCK_ABOUT, _("_About"), "", None, lambda w: self.on_about()) ]) + [ Action("Main Spacer Tool"), Action("Search Box Tool", None, None, "", _("Search All Notes")), Action("Search Button Tool", gtk.STOCK_FIND, None, "", _("Search All Notes"), lambda w: self.search_box.on_search_nodes())] # make sure recent notebooks is always visible recent = [x for x in actions if x.get_property("name") == "Open Recent Notebook"][0] recent.set_property("is-important", True) return actions def setup_menus(self, uimanager): pass def get_ui(self): return [""" """] def get_actions_statusicon(self): """Set actions for StatusIcon menu and return.""" actions = map(lambda x: Action(*x), [ ("KeepNote Preferences", gtk.STOCK_PREFERENCES, _("_Preferences"), "", None, lambda w: self._app.app_options_dialog.show(self)), ("Quit", gtk.STOCK_QUIT, _("_Quit"), "Q", _("Quit KeepNote"), lambda w: self.close()), ("About", gtk.STOCK_ABOUT, _("_About"), "", None, lambda w: self.on_about()) ]) return actions def get_ui_statusicon(self): """Create UI xml-definition for StatusIcon menu and return.""" return [""" """] def make_menubar(self): """Initialize the menu bar""" #=============================== # ui manager self._actiongroup = gtk.ActionGroup('MainWindow') self._uimanager.insert_action_group(self._actiongroup, 0) # setup menus add_actions(self._actiongroup, self.get_actions()) for s in self.get_ui(): self._uimanager.add_ui_from_string(s) self.setup_menus(self._uimanager) # return menu bar menubar = self._uimanager.get_widget('/main_menu_bar') return menubar def make_toolbar(self): # configure toolbar toolbar = self._uimanager.get_widget('/main_tool_bar') toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) toolbar.set_style(gtk.TOOLBAR_ICONS) toolbar.set_border_width(0) try: # NOTE: if this version of GTK doesn't have this size, then # ignore it toolbar.set_property("icon-size", gtk.ICON_SIZE_SMALL_TOOLBAR) except: pass # separator (is there a better way to do this?) spacer = self._uimanager.get_widget("/main_tool_bar/Main Spacer Tool") spacer.remove(spacer.child) spacer.set_expand(True) # search box self.search_box = SearchBox(self) self.search_box.show() w = self._uimanager.get_widget("/main_tool_bar/Search Box Tool") w.remove(w.child) w.add(self.search_box) return toolbar def make_statusicon_menu(self): """Initialize the StatusIcon menu.""" #=============================== # ui manager self._actiongroup_statusicon = gtk.ActionGroup('StatusIcon') self._tray_icon.uimanager = gtk.UIManager() self._tray_icon.uimanager.insert_action_group( self._actiongroup_statusicon, 0) # setup menu add_actions(self._actiongroup_statusicon, self.get_actions_statusicon()) for s in self.get_ui_statusicon(): self._tray_icon.uimanager.add_ui_from_string(s) self.setup_menus(self._tray_icon.uimanager) # return menu statusicon_menu = self._tray_icon.uimanager.get_widget( '/statusicon_menu') return statusicon_menu gobject.type_register(KeepNoteWindow) gobject.signal_new("error", KeepNoteWindow, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, object, object)) class SearchBox (gtk.Entry): def __init__(self, window): gtk.Entry.__init__(self) self._window = window self.connect("changed", self._on_search_box_text_changed) self.connect("activate", lambda w: self.on_search_nodes()) self.search_box_list = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self.search_box_completion = gtk.EntryCompletion() self.search_box_completion.connect("match-selected", self._on_search_box_completion_match) self.search_box_completion.set_match_func(lambda c, k, i: True) self.search_box_completion.set_model(self.search_box_list) self.search_box_completion.set_text_column(0) self.set_completion(self.search_box_completion) def on_search_nodes(self): """Search nodes""" # do nothing if notebook is not defined if not self._window.get_notebook(): return # TODO: add parsing grammar # get words words = [x.lower() for x in unicode_gtk(self.get_text()).strip().split()] # clear listview self._window.get_viewer().start_search_result() # queue for sending results between threads from threading import Lock from Queue import Queue queue = Queue() lock = Lock() # a mutex for the notebook (protect sqlite) # update gui with search result def search(task): alldone = Lock() # ensure gui and background sync up at end alldone.acquire() def gui_update(): lock.acquire() more = True try: maxstep = 20 for i in xrange(maxstep): # check if search is aborted if task.aborted(): more = False break # skip if queue is empty if queue.empty(): break node = queue.get() # no more nodes left, finish if node is None: more = False break # add result to gui self._window.get_viewer().add_search_result(node) except Exception, e: self._window.error(_("Unexpected error"), e) more = False finally: lock.release() if not more: alldone.release() return more gobject.idle_add(gui_update) # init search notebook = self._window.get_notebook() try: nodes = (notebook.get_node_by_id(nodeid) for nodeid in notebook.search_node_contents(" ".join(words)) if nodeid) except: keepnote.log_error() # do search in thread try: lock.acquire() for node in nodes: if task.aborted(): break lock.release() if node: queue.put(node) lock.acquire() lock.release() queue.put(None) except Exception, e: self.error(_("Unexpected error"), e) # wait for gui thread to finish # NOTE: if task is aborted, then gui_update stops itself for # some reason, thus no need to acquire alldone. if not task.aborted(): alldone.acquire() # launch task task = tasklib.Task(search) self._window.wait_dialog( _("Searching notebook"), _("Searching..."), task) if task.exc_info()[0]: e, t, tr = task.exc_info() keepnote.log_error(e, tr) self._window.get_viewer().end_search_result() def focus_on_search_box(self): """Place cursor in search box""" self.grab_focus() def _on_search_box_text_changed(self, url_text): self.search_box_update_completion() def search_box_update_completion(self): if not self._window.get_notebook(): return text = unicode_gtk(self.get_text()) self.search_box_list.clear() if len(text) > 0: results = self._window.get_notebook().search_node_titles(text)[:10] for nodeid, title in results: self.search_box_list.append([title, nodeid]) def _on_search_box_completion_match(self, completion, model, iter): if not self._window.get_notebook(): return nodeid = model[iter][1] node = self._window.get_notebook().get_node_by_id(nodeid) if node: self._window.get_viewer().goto_node(node, False) keepnote-0.7.8/keepnote/gui/dialog_wait.py0000644000175000017500000000767311677423601016771 0ustar razraz""" KeepNote General Wait Dialog """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import os, sys, threading, time, traceback # pygtk imports import pygtk pygtk.require('2.0') import gtk.glade import gobject # keepnote imports import keepnote from keepnote import get_resource from keepnote import tasklib class WaitDialog (object): """General dialog for background tasks""" def __init__(self, parent_window): self.parent_window = parent_window self._task = None def show(self, title, message, task, cancel=True): self.xml = gtk.glade.XML(get_resource("rc", "keepnote.glade"), "wait_dialog", keepnote.GETTEXT_DOMAIN) self.dialog = self.xml.get_widget("wait_dialog") self.xml.signal_autoconnect(self) self.dialog.connect("close", self._on_close) self.dialog.set_transient_for(self.parent_window) self.text = self.xml.get_widget("wait_text_label") self.progressbar = self.xml.get_widget("wait_progressbar") self.dialog.set_title(title) self.text.set_text(message) self._task = task self._task.change_event.add(self._on_task_update) cancel_button = self.xml.get_widget("cancel_button") cancel_button.set_sensitive(cancel) self.dialog.show() self._task.run() self._on_idle() self.dialog.run() self._task.join() self._task.change_event.remove(self._on_task_update) def _on_idle(self): """Idle thread""" lasttime = [time.time()] pulse_rate = 0.5 # seconds per sweep update_rate = 100 def gui_update(): # close dialog if task is stopped if self._task.is_stopped(): self.dialog.destroy() # do not repeat this timeout function return False # update progress bar percent = self._task.get_percent() if percent is None: t = time.time() timestep = t - lasttime[0] lasttime[0] = t step = max(min(timestep / pulse_rate, .1), .001) self.progressbar.set_pulse_step(step) self.progressbar.pulse() else: self.progressbar.set_fraction(percent) # filter for messages we process messages = filter(lambda x: isinstance(x, tuple) and len(x) == 2, self._task.get_messages()) texts = filter(lambda (a,b): a == "text", messages) details = filter(lambda (a,b): a == "detail", messages) # update text if len(texts) > 0: self.text.set_text(texts[-1][1]) if len(details) > 0: self.progressbar.set_text(details[-1][1]) # repeat this timeout function return True gobject.timeout_add(update_rate, gui_update) def _on_task_update(self): pass def _on_close(self, window): self._task.stop() def on_cancel_button_clicked(self, button): """Attempt to stop the task""" self.text.set_text("Canceling...") self._task.stop() keepnote-0.7.8/keepnote/gui/dialog_app_options.py0000644000175000017500000010650511677423601020352 0ustar razraz""" KeepNote Application Options Dialog """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import os import sys import gettext # pygtk imports import pygtk pygtk.require('2.0') import gtk.glade from gtk import gdk # keepnote imports import keepnote from keepnote import unicode_gtk from keepnote import get_resource from keepnote.gui.font_selector import FontSelector import keepnote.gui from keepnote.gui.icons import get_icon_filename import keepnote.trans import keepnote.gui.extension _ = keepnote.translate def on_browse(parent, title, filename, entry, action=gtk.FILE_CHOOSER_ACTION_OPEN): """Callback for selecting file browser associated with a text entry""" dialog = gtk.FileChooserDialog(title, parent, action=action, buttons=(_("Cancel"), gtk.RESPONSE_CANCEL, _("Open"), gtk.RESPONSE_OK)) dialog.set_transient_for(parent) dialog.set_modal(True) # set the filename if it is fully specified if filename == "": filename = entry.get_text() if os.path.isabs(filename): dialog.set_filename(filename) if dialog.run() == gtk.RESPONSE_OK and dialog.get_filename(): entry.set_text(dialog.get_filename()) dialog.destroy() class Section (object): """A Section in the Options Dialog""" def __init__(self, key, dialog, app, label=u"", icon=None): self.key = key self.dialog = dialog self.label = label self.icon = icon self.frame = gtk.Frame("") self.frame.get_label_widget().set_text("%s" % label) self.frame.get_label_widget().set_use_markup(True) self.frame.set_property("shadow-type", gtk.SHADOW_NONE) self.__align = gtk.Alignment() self.__align.set_padding(10, 0, 10, 0) self.__align.show() self.frame.add(self.__align) def get_default_widget(self): """Returns the default parent widget for a Section""" return self.__align def load_options(self, app): """Load options from app to UI""" pass def save_options(self, app): """Save options to the app""" pass class GeneralSection (Section): def __init__(self, key, dialog, app, label=u"", icon="keepnote-16x16.png"): Section.__init__(self, key, dialog, app, label, icon) self.notebook = None self.xml = gtk.glade.XML(get_resource("rc", "keepnote.glade"), "general_frame", keepnote.GETTEXT_DOMAIN) self.xml.signal_autoconnect(self) self.xml.signal_autoconnect({ "on_default_notebook_button_clicked": lambda w: on_browse(self.dialog, _("Choose Default Notebook"), "", self.xml.get_widget("default_notebook_entry")), }) self.frame = self.xml.get_widget("general_frame") def on_default_notebook_radio_changed(self, radio): """Default notebook radio changed""" no_default = self.xml.get_widget("no_default_notebook_radio") default = self.xml.get_widget("default_notebook_radio") last = self.xml.get_widget("last_notebook_radio") default_tab = self.xml.get_widget("default_notebook_table") default_tab.set_sensitive(default.get_active()) def on_autosave_check_toggled(self, widget): """The autosave option controls sensitivity of autosave time""" self.xml.get_widget("autosave_entry").set_sensitive( widget.get_active()) self.xml.get_widget("autosave_label").set_sensitive( widget.get_active()) def on_systray_check_toggled(self, widget): """Systray option controls sensitivity of sub-options""" self.xml.get_widget("skip_taskbar_check").set_sensitive( widget.get_active()) self.xml.get_widget("minimize_on_start_check").set_sensitive( widget.get_active()) def on_set_default_notebook_button_clicked(self, widget): if self.notebook: self.xml.get_widget("default_notebook_entry").set_text( self.notebook.get_path()) def load_options(self, app): win = app.get_current_window() if win: self.notebook = win.get_notebook() # populate default notebook if app.pref.get("use_last_notebook", default=True): self.xml.get_widget("last_notebook_radio").set_active(True) elif app.pref.get("default_notebooks", default=[]) == []: self.xml.get_widget("no_default_notebook_radio").set_active(True) else: self.xml.get_widget("default_notebook_radio").set_active(True) self.xml.get_widget("default_notebook_entry").\ set_text( (app.pref.get("default_notebooks", default=[]) + [""])[0] ) # populate autosave self.xml.get_widget("autosave_check").set_active( app.pref.get("autosave")) self.xml.get_widget("autosave_entry").set_text( str(int(app.pref.get("autosave_time") / 1000))) self.xml.get_widget("autosave_entry").set_sensitive( app.pref.get("autosave")) self.xml.get_widget("autosave_label").set_sensitive( app.pref.get("autosave")) # use systray icon self.xml.get_widget("systray_check").set_active( app.pref.get("window", "use_systray")) self.xml.get_widget("skip_taskbar_check").set_active( app.pref.get("window", "skip_taskbar")) self.xml.get_widget("skip_taskbar_check").set_sensitive( app.pref.get("window", "use_systray")) self.xml.get_widget("minimize_on_start_check").set_active( app.pref.get("window", "minimize_on_start")) self.xml.get_widget("minimize_on_start_check").set_sensitive( app.pref.get("window", "use_systray")) self.xml.get_widget("window_keep_above_check").set_active( app.pref.get("window", "keep_above")) # set window 'always on top' self.xml.get_widget("window_stick_check").set_active( app.pref.get("window", "stick")) self.xml.get_widget("use_fulltext_check").set_active( app.pref.get("use_fulltext_search", default=True)) def save_options(self, app): if self.xml.get_widget("last_notebook_radio").get_active(): app.pref.set("use_last_notebook", True) elif self.xml.get_widget("default_notebook_radio").get_active(): app.pref.set("use_last_notebook", False) app.pref.set("default_notebooks", [unicode_gtk( self.xml.get_widget( "default_notebook_entry").get_text())]) else: app.pref.set("use_last_notebook", False) app.pref.set("default_notebooks", []) # save autosave app.pref.set("autosave", self.xml.get_widget("autosave_check").get_active()) try: app.pref.set("autosave_time", int(self.xml.get_widget("autosave_entry").get_text()) * 1000) except: pass # use systray icon app.pref.set("window", "use_systray", self.xml.get_widget("systray_check").get_active()) app.pref.set("window", "skip_taskbar", self.xml.get_widget("skip_taskbar_check").get_active()) app.pref.set( "window", "minimize_on_start", self.xml.get_widget("minimize_on_start_check").get_active()) # window 'always above' app.pref.set( "window", "keep_above", self.xml.get_widget("window_keep_above_check").get_active()) # window 'stick to all desktops' app.pref.set( "window", "stick", self.xml.get_widget("window_stick_check").get_active()) app.pref.set( "use_fulltext_search", self.xml.get_widget("use_fulltext_check").get_active()) class LookAndFeelSection (Section): def __init__(self, key, dialog, app, label=u"", icon="lookandfeel.png"): Section.__init__(self, key, dialog, app, label, icon) w = self.get_default_widget() v = gtk.VBox(False, 5) v.show() w.add(v) def add_check(label): c = gtk.CheckButton(label) c.show() v.pack_start(c, False, False, 0) return c self.treeview_lines_check = add_check( _("show lines in treeview")) self.listview_rules_check = add_check( _("use ruler hints in listview")) self.use_stock_icons_check = add_check( _("use GTK stock icons in toolbar")) self.use_minitoolbar = add_check( _("use minimal toolbar")) # app font size font_size = 10 h = gtk.HBox(False, 5); h.show() l = gtk.Label(_("Application Font Size:")); l.show() h.pack_start(l, False, False, 0) self.app_font_size = gtk.SpinButton( gtk.Adjustment(value=font_size, lower=2, upper=500, step_incr=1)) self.app_font_size.set_value(font_size) #font_size_button.set_editable(False) self.app_font_size.show() h.pack_start(self.app_font_size, False, False, 0) v.pack_start(h, False, False, 0) # view mode combo h = gtk.HBox(False, 5); h.show() l = gtk.Label(_("Listview Layout:")); l.show() h.pack_start(l, False, False, 0) c = gtk.combo_box_new_text(); c.show() c.append_text(_("Vertical")) c.append_text(_("Horizontal")) h.pack_start(c, False, False, 0) v.pack_start(h) self.listview_layout = c def load_options(self, app): l = app.pref.get("look_and_feel") self.treeview_lines_check.set_active(l.get("treeview_lines")) self.listview_rules_check.set_active(l.get("listview_rules")) self.use_stock_icons_check.set_active(l.get("use_stock_icons")) self.use_minitoolbar.set_active(l.get("use_minitoolbar")) self.app_font_size.set_value(l.get("app_font_size")) if app.pref.get("viewers", "three_pane_viewer", "view_mode", default="") == "horizontal": self.listview_layout.set_active(1) else: self.listview_layout.set_active(0) def save_options(self, app): l = app.pref.get("look_and_feel") l["treeview_lines"] = self.treeview_lines_check.get_active() l["listview_rules"] = self.listview_rules_check.get_active() l["use_stock_icons"] = self.use_stock_icons_check.get_active() l["use_minitoolbar"] = self.use_minitoolbar.get_active() l["app_font_size"] = self.app_font_size.get_value() app.pref.set("viewers", "three_pane_viewer", "view_mode", ["vertical", "horizontal"][ self.listview_layout.get_active()]) class LanguageSection (Section): def __init__(self, key, dialog, app, label=u"", icon=None): Section.__init__(self, key, dialog, app, label, icon) w = self.get_default_widget() v = gtk.VBox(False, 5) v.show() w.add(v) # language combo h = gtk.HBox(False, 5); h.show() l = gtk.Label(_("Language:")); l.show() h.pack_start(l, False, False, 0) c = gtk.combo_box_new_text(); c.show() # populate language options c.append_text("default") for lang in keepnote.trans.get_langs(): c.append_text(lang) # pack combo h.pack_start(c, False, False, 0) v.pack_start(h) self.language_box = c def load_options(self, app): lang = app.pref.get("language", default="") # set default if lang == "": self.language_box.set_active(0) else: for i, row in enumerate(self.language_box.get_model()): if lang == row[0]: self.language_box.set_active(i) break def save_options(self, app): if self.language_box.get_active() > 0: app.pref.set("language", self.language_box.get_active_text()) else: # set default app.pref.set("language", "") class HelperAppsSection (Section): def __init__(self, key, dialog, app, label=u"", icon=None): Section.__init__(self, key, dialog, app, label, icon) self.entries = {} w = self.get_default_widget() self.table = gtk.Table(max(len(list(app.iter_external_apps())), 1), 2) self.table.show() w.add(self.table) # set icon try: self.icon = keepnote.gui.get_pixbuf(get_icon_filename(gtk.STOCK_EXECUTE), size=(15, 15)) except: pass def load_options(self, app): # clear table, resize self.table.foreach(lambda x: self.table.remove(x)) self.table.resize(len(list(app.iter_external_apps())), 2) for i, app in enumerate(app.iter_external_apps()): key = app.key app_title = app.title prog = app.prog # program label label = gtk.Label(app_title +":") label.set_justify(gtk.JUSTIFY_RIGHT) label.set_alignment(1.0, 0.5) label.show() self.table.attach(label, 0, 1, i, i+1, xoptions=gtk.FILL, yoptions=0, xpadding=2, ypadding=2) # program entry entry = gtk.Entry() entry.set_text(prog) entry.set_width_chars(30) entry.show() self.entries[key] = entry self.table.attach(entry, 1, 2, i, i+1, xoptions=gtk.FILL | gtk.EXPAND, yoptions=0, xpadding=2, ypadding=2) # browse button def button_clicked(key, title, prog): return lambda w: \ on_browse(self.dialog, _("Choose %s") % title, "", self.entries[key]) button = gtk.Button(_("Browse...")) button.set_image( gtk.image_new_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_SMALL_TOOLBAR)) button.show() button.connect("clicked", button_clicked(key, app_title, prog)) self.table.attach(button, 2, 3, i, i+1, xoptions=0, yoptions=0, xpadding=2, ypadding=2) def save_options(self, app): # TODO: use a public interface # save external app options apps = app.pref.get("external_apps", default=[]) for app in apps: key = app.get("key", None) if key: entry = self.entries.get(key, None) if entry: app["prog"] = unicode_gtk(entry.get_text()) class DatesSection (Section): def __init__(self, key, dialog, app, label=u"", icon="time.png"): Section.__init__(self, key, dialog, app, label, icon) self.date_xml = gtk.glade.XML(get_resource("rc", "keepnote.glade"), "date_time_frame", keepnote.GETTEXT_DOMAIN) self.date_xml.signal_autoconnect(self) self.frame = self.date_xml.get_widget("date_time_frame") def load_options(self, app): for name in ["same_day", "same_month", "same_year", "diff_year"]: self.date_xml.get_widget("date_%s_entry" % name).\ set_text(app.pref.get("timestamp_formats", name)) def save_options(self, app): # save date formatting for name in ["same_day", "same_month", "same_year", "diff_year"]: app.pref.set("timestamp_formats", name, unicode_gtk( self.date_xml.get_widget("date_%s_entry" % name).get_text())) class EditorSection (Section): def __init__(self, key, dialog, app, label=u"", icon=None): Section.__init__(self, key, dialog, app, label, icon) w = self.get_default_widget() v = gtk.VBox(False, 5) v.show() w.add(v) # language combo h = gtk.HBox(False, 5); h.show() l = gtk.Label(_("Quote format:")); l.show() h.pack_start(l, False, False, 0) e = gtk.Entry(); e.show() e.set_width_chars(40) # pack entry h.pack_start(e, False, False, 0) v.pack_start(h) self.quote_format = e def load_options(self, app): try: quote_format = app.pref.get("editors", "general", "quote_format") self.quote_format.set_text(quote_format) except: pass def save_options(self, app): quote_format = self.quote_format.get_text() if quote_format: app.pref.set("editors", "general", "quote_format", quote_format) class AllNoteBooksSection (Section): def __init__(self, key, dialog, app, label=u"", icon="folder.png"): Section.__init__(self, key, dialog, app, label, icon) w = self.get_default_widget() l = gtk.Label(_("This section contains options that are saved on a per notebook basis (e.g. notebook-specific font). A subsection will appear for each notebook that is currently opened.")) l.set_line_wrap(True) w.add(l) w.show_all() class NoteBookSection (Section): def __init__(self, key, dialog, app, notebook, label=u"", icon="folder.png"): Section.__init__(self, key, dialog, app, label, icon) self.entries = {} self.notebook = notebook # add notebook font widget self.notebook_xml = gtk.glade.XML(get_resource("rc", "keepnote.glade"), "notebook_frame", keepnote.GETTEXT_DOMAIN) self.notebook_xml.signal_autoconnect(self) self.frame = self.notebook_xml.get_widget("notebook_frame") notebook_font_spot = self.notebook_xml.get_widget("notebook_font_spot") self.notebook_font_family = FontSelector() notebook_font_spot.add(self.notebook_font_family) self.notebook_font_family.show() # populate notebook font self.notebook_font_size = self.notebook_xml.get_widget("notebook_font_size") self.notebook_font_size.set_value(10) self.notebook_index_dir = self.notebook_xml.get_widget("index_dir_entry") self.notebook_xml.get_widget("index_dir_browse").connect( "clicked", lambda w: on_browse(self.dialog, _("Choose alternative notebook index directory"), "", self.notebook_index_dir, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)) self.frame.show_all() def load_options(self, app): if self.notebook is not None: font = self.notebook.pref.get("default_font", default=keepnote.gui.DEFAULT_FONT) family, mods, size = keepnote.gui.richtext.parse_font(font) self.notebook_font_family.set_family(family) self.notebook_font_size.set_value(size) self.notebook_index_dir.set_text( self.notebook.pref.get("index_dir", default=u"", type=basestring)) def save_options(self, app): if self.notebook is not None: pref = self.notebook.pref # save notebook font pref.set("default_font", "%s %d" % ( self.notebook_font_family.get_family(), self.notebook_font_size.get_value())) # alternative index directory pref.set("index_dir", self.notebook_index_dir.get_text()) class ExtensionsSection (Section): def __init__(self, key, dialog, app, label=u"", icon=None): Section.__init__(self, key, dialog, app, label, icon) self.app = app self.entries = {} self.frame = gtk.Frame("") self.frame.get_label_widget().set_text("Extensions") self.frame.get_label_widget().set_use_markup(True) self.frame.set_property("shadow-type", gtk.SHADOW_NONE) align = gtk.Alignment() align.set_padding(10, 0, 10, 0) align.show() self.frame.add(align) v = gtk.VBox(False, 0) v.show() align.add(v) # extension list scrollbar self.sw = gtk.ScrolledWindow() self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.sw.set_shadow_type(gtk.SHADOW_IN) self.sw.show() v.pack_start(self.sw, True, True, 0) # extension list self.extlist = gtk.VBox(False, 0) self.extlist.show() self.sw.add_with_viewport(self.extlist) # hbox h = gtk.HBox(False, 0) h.show() v.pack_start(h, True, True, 0) # install button self.install_button = gtk.Button("Install new extension") self.install_button.set_relief(gtk.RELIEF_NONE) self.install_button.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0, 65535)) self.install_button.connect("clicked", self._on_install) self.install_button.show() h.pack_start(self.install_button, False, True, 0) # set icon try: self.icon = keepnote.gui.get_pixbuf(get_icon_filename(gtk.STOCK_ADD), size=(15, 15)) except: pass def load_options(self, app): # clear extension list self.extlist.foreach(self.extlist.remove) def callback(ext): return lambda w: self._on_uninstall(ext.key) # populate extension list exts = list(app.get_imported_extensions()) d = {"user": 0, "system": 1} exts.sort(key=lambda e: (d.get(e.type, 10), e.name)) for ext in exts: if ext.visible: p = ExtensionWidget(app, ext) p.uninstall_button.connect("clicked", callback(ext)) p.show() self.extlist.pack_start(p, True, True, 0) # setup scroll bar size maxheight = 270 # TODO: make this more dynamic w, h = self.extlist.size_request() w2, h2 = self.sw.get_vscrollbar().size_request() self.sw.set_size_request(400, min(maxheight, h+10)) def save_options(self, app): app.pref.set( "extension_info", "disabled", [widget.ext.key for widget in self.extlist if not widget.enabled]) # enable/disable extensions for widget in self.extlist: if widget.enabled != widget.ext.is_enabled(): try: widget.ext.enable(widget.enabled) except: keepnote.log_error() def _on_uninstall(self, ext): if self.app.uninstall_extension(ext): self.load_options(self.app) def _on_install(self, widget): # open file dialog dialog = gtk.FileChooserDialog( _("Install New Extension"), self.dialog, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(_("Cancel"), gtk.RESPONSE_CANCEL, _("Open"), gtk.RESPONSE_OK)) dialog.set_transient_for(self.dialog) dialog.set_modal(True) file_filter = gtk.FileFilter() file_filter.add_pattern("*.kne") file_filter.set_name(_("KeepNote Extension (*.kne)")) dialog.add_filter(file_filter) file_filter = gtk.FileFilter() file_filter.add_pattern("*") file_filter.set_name(_("All files (*.*)")) dialog.add_filter(file_filter) response = dialog.run() if gtk.RESPONSE_OK and dialog.get_filename(): # install extension self.app.install_extension(dialog.get_filename()) self.load_options(self.app) dialog.destroy() class ExtensionWidget (gtk.EventBox): def __init__(self, app, ext): gtk.EventBox.__init__(self) self.app = app self.enabled = ext.is_enabled() self.ext = ext self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(65535, 65535, 65535)) frame = gtk.Frame(None) frame.set_property("shadow-type", gtk.SHADOW_OUT) frame.show() self.add(frame) # name frame2 = gtk.Frame("") frame2.set_property("shadow-type", gtk.SHADOW_NONE) frame2.get_label_widget().set_text("%s (%s/%s)" % (ext.name, ext.type, ext.key)) frame2.get_label_widget().set_use_markup(True) frame2.show() frame.add(frame2) # margin align = gtk.Alignment() align.set_padding(10, 10, 10, 10) align.show() frame2.add(align) # vbox v = gtk.VBox(False, 5) v.show() align.add(v) # description l = gtk.Label(ext.description) l.set_justify(gtk.JUSTIFY_LEFT) l.set_alignment(0.0, 0.0) l.show() v.pack_start(l, True, True, 0) # hbox h = gtk.HBox(False, 0) h.show() v.pack_start(h, True, True, 0) # enable button self.enable_check = gtk.CheckButton(_("Enabled")) self.enable_check.set_active(self.enabled) self.enable_check.show() self.enable_check.connect( "toggled", lambda w: self._on_enabled(ext)) h.pack_start(self.enable_check, False, True, 0) # divider l = gtk.Label("|"); l.show() h.pack_start(l, False, True, 0) # uninstall button self.uninstall_button = gtk.Button(_("Uninstall")) self.uninstall_button.set_relief(gtk.RELIEF_NONE) self.uninstall_button.set_sensitive(app.can_uninstall(ext)) self.uninstall_button.show() h.pack_start(self.uninstall_button, False, True, 0) def update(self): self.enable_check.set_active(self.ext.is_enabled()) def _on_enabled(self, ext): self.enabled = self.enable_check.get_active() #============================================================================= class ApplicationOptionsDialog (object): """Application options""" def __init__(self, app): self.app = app self.parent = None self._sections = [] self.xml = gtk.glade.XML(get_resource("rc", "keepnote.glade"), "app_options_dialog", keepnote.GETTEXT_DOMAIN) self.dialog = self.xml.get_widget("app_options_dialog") self.dialog.connect("delete-event", self._on_delete_event) self.tabs = self.xml.get_widget("app_options_tabs") self.xml.signal_autoconnect({ "on_cancel_button_clicked": lambda w: self.on_cancel_button_clicked(), "on_ok_button_clicked": lambda w: self.on_ok_button_clicked(), "on_apply_button_clicked": lambda w: self.on_apply_button_clicked()}) # setup treeview self.overview = self.xml.get_widget("app_config_treeview") self.overview_store = gtk.TreeStore(str, object, gdk.Pixbuf) self.overview.set_model(self.overview_store) self.overview.connect("cursor-changed", self.on_overview_select) # create the treeview column column = gtk.TreeViewColumn() self.overview.append_column(column) cell_text = gtk.CellRendererText() cell_icon = gtk.CellRendererPixbuf() column.pack_start(cell_icon, True) column.add_attribute(cell_icon, 'pixbuf', 2) column.pack_start(cell_text, True) column.add_attribute(cell_text, 'text', 0) # add tabs self.add_default_sections() def show(self, parent, section=None): """Display application options""" self.parent = parent self.dialog.set_transient_for(parent) # add notebook options self.notebook_sections = [ self.add_section(NoteBookSection("notebook_%d" % i, self.dialog, self.app, notebook, notebook.get_title()), "notebooks") for i, notebook in enumerate(self.app.iter_notebooks())] # add extension options self.extensions_ui = [] for ext in self.app.get_enabled_extensions(): if isinstance(ext, keepnote.gui.extension.Extension): ext.on_add_options_ui(self) self.extensions_ui.append(ext) # populate options ui self.load_options(self.app) self.dialog.show() if section: try: self.overview.set_cursor(self.get_section_path(section)) except: pass def finish(self): # remove extension options for ext in self.extensions_ui: if isinstance(ext, keepnote.gui.extension.Extension): ext.on_remove_options_ui(self) self.extensions_ui = [] # remove notebook options for section in self.notebook_sections: self.remove_section(section.key) def add_default_sections(self): self.add_section( GeneralSection("general", self.dialog, self.app, keepnote.PROGRAM_NAME)) self.add_section( LookAndFeelSection("look_and_feel", self.dialog, self.app, _("Look and Feel")), "general") self.add_section( LanguageSection("language", self.dialog, self.app, _("Language")), "general") self.add_section( DatesSection("date_and_time", self.dialog, self.app, _("Date and Time")), "general") self.add_section( EditorSection("ediotr", self.dialog, self.app, _("Editor")), "general") self.add_section( HelperAppsSection("helper_apps", self.dialog, self.app, _("Helper Applications")), "general") self.add_section( AllNoteBooksSection("notebooks", self.dialog, self.app, _("Notebook Options"), "folder.png")) self.add_section( ExtensionsSection("extensions", self.dialog, self.app, _("Extensions"))) #===================================== # options def load_options(self, app): """Load options into sections""" for section in self._sections: section.load_options(self.app) def save_options(self, app): """Save the options from each section""" # let app record its preferences first app.save_preferences() # let sections record their preferences for section in self._sections: section.save_options(self.app) # notify changes # app and notebook will load prefs from plist self.app.pref.changed.notify() for notebook in self.app.iter_notebooks(): notebook.notify_change(False) # force a app and notebook preference save # save prefs to plist and to disk app.save() #===================================== # section handling def add_section(self, section, parent=None): """Add a section to the Options Dialog""" # icon size size = (15, 15) # determine parent section if parent is not None: path = self.get_section_path(parent) it = self.overview_store.get_iter(path) else: it = None self._sections.append(section) self.tabs.insert_page(section.frame, tab_label=None) section.frame.show() section.frame.queue_resize() icon = section.icon if icon is None: icon = icon="note.png" if isinstance(icon, basestring): pixbuf = keepnote.gui.get_resource_pixbuf(icon, size=size) else: pixbuf = icon # add to overview it = self.overview_store.append(it, [section.label, section, pixbuf]) path = self.overview_store.get_path(it) self.overview.expand_to_path(path) return section def remove_section(self, key): # remove from tabs section = self.get_section(key) if section: self.tabs.remove_page(self._sections.index(section)) self._sections.remove(section) # remove from tree path = self.get_section_path(key) if path is not None: self.overview_store.remove(self.overview_store.get_iter(path)) def get_section(self, key): """Returns the section for a key""" for section in self._sections: if section.key == key: return section return None def get_section_path(self, key): """Returns the TreeModel path for a section""" def walk(node): child = self.overview_store.iter_children(node) while child: row = self.overview_store[child] if row[1].key == key: return row.path # recurse ret = walk(child) if ret: return ret child = self.overview_store.iter_next(child) return None return walk(None) #========================================================== # callbacks def on_overview_select(self, overview): """Callback for changing topic in overview""" row, col = overview.get_cursor() if row is not None: section = self.overview_store[row][1] self.tabs.set_current_page(self._sections.index(section)) def on_cancel_button_clicked(self): """Callback for cancel button""" self.dialog.hide() self.finish() def on_ok_button_clicked(self): """Callback for ok button""" self.save_options(self.app) self.dialog.hide() self.finish() def on_apply_button_clicked(self): """Callback for apply button""" self.save_options(self.app) # clean up and reshow dialog self.finish() self.show(self.parent) def _on_delete_event(self, widget, event): """Callback for window close""" self.dialog.hide() self.finish() self.dialog.stop_emission("delete-event") return True keepnote-0.7.8/keepnote/gui/icon_menu.py0000644000175000017500000000767311677423601016462 0ustar razraz""" KeepNote Change Node Icon Submenu """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # pygtk imports import pygtk pygtk.require('2.0') import gobject import gtk import keepnote.gui.icons from keepnote.gui.icons import \ lookup_icon_filename default_menu_icons = [x for x in keepnote.gui.icons.builtin_icons if "-open." not in x][:20] class IconMenu (gtk.Menu): """Icon picker menu""" def __init__(self): gtk.Menu.__init__(self) self._notebook = None # default icon self.default_icon = gtk.MenuItem("_Default Icon") self.default_icon.connect("activate", lambda w: self.emit("set-icon", "")) self.default_icon.show() # new icon self.new_icon = gtk.MenuItem("_More Icons...") self.new_icon.show() self.width = 4 self.posi = 0 self.posj = 0 self.setup_menu() def clear(self): """clear menu""" self.foreach(lambda item: self.remove(item)) self.posi = 0 self.posj = 0 def set_notebook(self, notebook): """Set notebook for menu""" if self._notebook is not None: # disconnect from old notebook self._notebook.pref.quick_pick_icons_changed.remove(self.setup_menu) self._notebook = notebook if self._notebook is not None: # listener to new notebook self._notebook.pref.quick_pick_icons_changed.add(self.setup_menu) self.setup_menu() def setup_menu(self): """Update menu to reflect notebook""" self.clear() if self._notebook is None: for iconfile in default_menu_icons: self.add_icon(iconfile) else: for iconfile in self._notebook.pref.get_quick_pick_icons(): self.add_icon(iconfile) # separator item = gtk.SeparatorMenuItem() item.show() self.append(item) # default icon self.append(self.default_icon) # new icon self.append(self.new_icon) # make changes visible self.unrealize() self.realize() def append_grid(self, item): self.attach(item, self.posj, self.posj+1, self.posi, self.posi+1) self.posj += 1 if self.posj >= self.width: self.posj = 0 self.posi += 1 def append(self, item): # reset posi, posj if self.posj > 0: self.posi += 1 self.posj = 0 gtk.Menu.append(self, item) def add_icon(self, iconfile): child = gtk.MenuItem("") child.remove(child.child) img = gtk.Image() iconfile2 = lookup_icon_filename(self._notebook, iconfile) img.set_from_file(iconfile2) child.add(img) child.child.show() child.show() child.connect("activate", lambda w: self.emit("set-icon", iconfile)) self.append_grid(child) gobject.type_register(IconMenu) gobject.signal_new("set-icon", IconMenu, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) keepnote-0.7.8/keepnote/gui/dialog_image_resize.py0000644000175000017500000002013111677423601020450 0ustar razraz""" KeepNote Image Resize Dialog """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import os # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk.glade # keepnote imports import keepnote from keepnote import get_resource # TODO: separate out error callback class ImageResizeDialog (object): """Image Resize dialog """ def __init__(self, main_window, app_pref): self.main_window = main_window self.app_pref = app_pref self.dialog = None self.image = None self.aspect = True self.owidth, self.oheight = None, None self.init_width, self.init_height = None, None self.ignore_width_changed = 0 self.ignore_height_changed = 0 self.snap_size = self.app_pref.get( "editors", "general", "image_size_snap_amount", default=50) self.snap_enabled = self.app_pref.get( "editors", "general", "image_size_snap", default=True) # widgets self.size_width_scale = None self.size_height_scale = None self.width_entry = None self.height_entry = None self.aspect_check = None self.snap_check = None self.snap_entry = None def on_resize(self, image): """Launch resize dialog""" if not image.is_valid(): self.main_window.error("Cannot resize image that is not properly loaded") return self.xml = gtk.glade.XML(get_resource("rc", "keepnote.glade"), domain=keepnote.GETTEXT_DOMAIN) self.dialog = self.xml.get_widget("image_resize_dialog") self.dialog.set_transient_for(self.main_window) self.dialog.connect("response", lambda d, r: self.on_response(r)) # TODO: convert to run self.dialog.show() self.image = image self.aspect = True width, height = image.get_size(True) self.init_width, self.init_height = width, height self.owidth, self.oheight = image.get_original_size() # get widgets self.width_entry = self.xml.get_widget("width_entry") self.height_entry = self.xml.get_widget("height_entry") self.size_width_scale = self.xml.get_widget("size_width_scale") self.size_height_scale = self.xml.get_widget("size_height_scale") self.aspect_check = self.xml.get_widget("aspect_check") self.snap_check = self.xml.get_widget("img_snap_check") self.snap_entry = self.xml.get_widget("img_snap_amount_entry") # populate info self.width_entry.set_text(str(width)) self.height_entry.set_text(str(height)) self.size_width_scale.set_value(width) self.size_height_scale.set_value(height) self.snap_check.set_active(self.snap_enabled) self.snap_entry.set_text(str(self.snap_size)) # callback self.xml.signal_autoconnect({ "on_width_entry_changed": lambda w: self.on_size_changed("width"), "on_height_entry_changed": lambda w: self.on_size_changed("height")}) self.xml.signal_autoconnect(self) def get_size(self): """Returns the current size setting of the dialog""" wstr = self.width_entry.get_text() hstr = self.height_entry.get_text() try: width, height = int(wstr), int(hstr) if width <= 0: width = None if height <= 0: height = None except ValueError: width, height = None, None return width, height def on_response(self, response): """Callback for a response button in dialog""" if response == gtk.RESPONSE_OK: width, height = self.get_size() p = self.app_pref.get("editors", "general") p["image_size_snap"] = self.snap_enabled p["image_size_snap_amount"] = self.snap_size if width is not None: self.image.scale(width, height) self.dialog.destroy() else: self.main_window.error("Must specify positive integers for image size") elif response == gtk.RESPONSE_CANCEL: self.dialog.destroy() elif response == gtk.RESPONSE_APPLY: width, height = self.get_size() if width is not None: self.image.scale(width, height) elif response == gtk.RESPONSE_REJECT: # restore default image size width, height = self.image.get_original_size() self.width_entry.set_text(str(width)) self.height_entry.set_text(str(height)) def set_size(self, dim, value): if dim == "width": if self.ignore_width_changed == 0: self.ignore_width_changed += 1 self.width_entry.set_text(value) self.ignore_width_changed -= 1 else: if self.ignore_height_changed == 0: self.ignore_height_changed += 1 self.height_entry.set_text(value) self.ignore_height_changed -= 1 def on_size_changed(self, dim): """Callback when a size changes""" if dim == "width": self.ignore_width_changed += 1 else: self.ignore_height_changed += 1 width, height = self.get_size() if self.aspect: if dim == "width" and width is not None: height = int(width / float(self.owidth) * self.oheight) self.size_width_scale.set_value(width) self.set_size("height", str(height)) elif dim == "height" and height is not None: width = int(height / float(self.oheight) * self.owidth) self.size_height_scale.set_value(height) self.set_size("width", str(width)) if width is not None and height is not None: self.init_width, self.init_height = width, height if dim == "width": self.ignore_width_changed -= 1 else: self.ignore_height_changed -= 1 def on_aspect_check_toggled(self, widget): """Callback when aspect checkbox is toggled""" self.aspect = self.aspect_check.get_active() def on_size_width_scale_value_changed(self, scale): """Callback for when scale value changes""" width = int(scale.get_value()) if self.snap_enabled: snap = self.snap_size width = int((width + snap/2.0) // snap * snap) self.set_size("width", str(width)) def on_size_height_scale_value_changed(self, scale): """Callback for when scale value changes""" height = int(scale.get_value()) if self.snap_enabled: snap = self.snap_size height = int((height + snap/2.0) // snap * snap) self.set_size("height", str(height)) def on_img_snap_check_toggled(self, check): """Callback when snap checkbox is toggled""" self.snap_enabled = self.snap_check.get_active() self.snap_entry.set_sensitive(self.snap_enabled) def on_img_snap_entry_changed(self, entry): """Callback when snap text entry changes""" try: self.snap_size = int(self.snap_entry.get_text()) except ValueError: pass keepnote-0.7.8/keepnote/gui/extension.py0000644000175000017500000001246311677423601016513 0ustar razraz""" KeepNote Extension system with GUI relevant functions """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import os import sys # gtk imports import gtk # keepnote imports import keepnote from keepnote import extension #============================================================================= # extension functions class Extension (extension.Extension): """KeepNote Extension""" def __init__(self, app): extension.Extension.__init__(self, app) self.__windows = set() self.__uis = set() # UI interface self.__ui_ids = {} # toolbar/menu ids (per window) self.__action_groups = {} # ui actions (per window) self.enabled.add(self._on_enable_ui) #================================ # window interactions def _on_enable_ui(self, enabled): """Initialize UI during enable/disable""" if enabled: # TODO: should each extension have to remember what windows it has? for window in self.__windows: if window not in self.__uis: self.on_add_ui(window) self.__uis.add(window) else: for window in self.__uis: self.on_remove_ui(window) self.__uis.clear() def on_new_window(self, window): """Initialize extension for a particular window""" if self._enabled: try: self.on_add_ui(window) self.__uis.add(window) except Exception, e: keepnote.log_error(e, sys.exc_info()[2]) self.__windows.add(window) def on_close_window(self, window): """Callback for when window is closed""" if window in self.__windows: if window in self.__uis: try: self.on_remove_ui(window) except Exception, e: keepnote.log_error(e, sys.exc_info()[2]) self.__uis.remove(window) self.__windows.remove(window) def get_windows(self): """Returns windows associated with extension""" return self.__windows #=============================== # UI interaction def on_add_ui(self, window): pass def on_remove_ui(self, window): # remove actions for window self.remove_all_actions(window) # remove ui elements for window self.remove_all_ui(window) def on_add_options_ui(self, dialog): pass def on_remove_options_ui(self, dialog): pass #=============================== # helper functions def add_action(self, window, action_name, menu_text, callback=lambda w: None, stock_id=None, accel="", tooltip=None): # init action group if window not in self.__action_groups: group = gtk.ActionGroup("MainWindow") self.__action_groups[window] = group window.get_uimanager().insert_action_group(group, 0) # add action self.__action_groups[window].add_actions([ (action_name, stock_id, menu_text, accel, tooltip, callback)]) def remove_action(self, window, action_name): group = self.__action_groups.get(window, None) if group is not None: action = group.get_action(action_name) if action: group.remove_action(action) def remove_all_actions(self, window): group = self.__action_groups.get(window, None) if group is not None: window.get_uimanager().remove_action_group(group) del self.__action_groups[window] def add_ui(self, window, uixml): # init list of ui ids uids = self.__ui_ids.get(window, None) if uids is None: uids = self.__ui_ids[window] = [] # add ui, record id uid = window.get_uimanager().add_ui_from_string(uixml) uids.append(uid) # return id return uid def remove_ui(self, window, uid): uids = self.__ui_ids.get(window, None) if uids is not None and uid in uids: window.get_uimanager().remove_ui(uid) uids.remove(uid) # remove uid list if last uid removed if len(uids) == 0: del self.__ui_ids[window] def remove_all_ui(self, window): uids = self.__ui_ids.get(window, None) if uids is not None: for uid in uids: window.get_uimanager().remove_ui(uid) del self.__ui_ids[window] keepnote-0.7.8/keepnote/gui/treeview.py0000644000175000017500000001060311677423601016323 0ustar razraz""" KeepNote TreeView """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # pygtk imports import pygtk pygtk.require('2.0') import gtk, gobject, pango from gtk import gdk # keepnote imports from keepnote.gui import treemodel from keepnote.gui import basetreeview from keepnote.gui.icons import get_node_icon class KeepNoteTreeView (basetreeview.KeepNoteBaseTreeView): """ TreeView widget for the KeepNote NoteBook """ def __init__(self): basetreeview.KeepNoteBaseTreeView.__init__(self) self._notebook = None self.set_model(treemodel.KeepNoteTreeModel()) # treeview signals self.connect("key-release-event", self.on_key_released) self.connect("button-press-event", self.on_button_press) # selection config self.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.set_headers_visible(False) # tree style try: # available only on gtk > 2.8 self.set_property("enable-tree-lines", True) except TypeError, e: pass self._setup_columns() self.set_sensitive(False) def _setup_columns(self): self.clear_columns() if self._notebook is None: return # create the treeview column self.column = gtk.TreeViewColumn() self.column.set_clickable(False) self.append_column(self.column) self._add_model_column("title") self._add_title_render(self.column, "title") # make treeview searchable self.set_search_column(self.model.get_column_by_name("title").pos) #self.set_fixed_height_mode(True) #============================================= # gui callbacks def on_key_released(self, widget, event): """Process key presses""" # no special processing while editing nodes if self.editing_path: return if event.keyval == gtk.keysyms.Delete: self.emit("delete-node", self.get_selected_nodes()) self.stop_emission("key-release-event") def on_button_press(self, widget, event): """Process context popup menu""" if event.button == 3: # popup menu return self.popup_menu(event.x, event.y, event.button, event.time) if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: nodes = self.get_selected_nodes() if len(nodes) > 0: # double click --> goto node self.emit("activate-node", nodes[0]) #============================================== # actions def set_notebook(self, notebook): basetreeview.KeepNoteBaseTreeView.set_notebook(self, notebook) if self._notebook is None: self.model.set_root_nodes([]) self.set_sensitive(False) else: self.set_sensitive(True) root = self._notebook model = self.model self.set_model(None) model.set_root_nodes([root]) self.set_model(model) self._setup_columns() if root.get_attr("expanded", True): self.expand_to_path((0,)) def edit_node(self, node): path = treemodel.get_path_from_node(self.model, node, self.rich_model.get_node_column_pos()) gobject.idle_add(lambda: self.set_cursor_on_cell(path, self.column, self.title_text, True)) #gobject.idle_add(lambda: self.scroll_to_cell(path)) keepnote-0.7.8/keepnote/gui/tabbed_viewer.py0000644000175000017500000005151211677423601017277 0ustar razraz""" KeepNote Tabbed Viewer for KeepNote. """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import gettext import os import traceback # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk import gobject # keepnote imports import keepnote from keepnote import unicode_gtk, KeepNoteError from keepnote import notebook as notebooklib from keepnote.gui import \ add_actions, Action from keepnote.gui.three_pane_viewer import ThreePaneViewer from keepnote.gui.viewer import Viewer from keepnote.gui.icons import get_node_icon _ = keepnote.translate class TwoWayDict (object): def __init__(self): self._lookup1 = {} self._lookup2 = {} def add(self, item1, item2): self._lookup1[item1] = item2 self._lookup2[item2] = item1 def get1(self, item1, default=None): return self._lookup1.get(item1, default) def get2(self, item2, default=None): return self._lookup2.get(item2, default) class TabbedViewer (Viewer): """A viewer with a treeview, listview, and editor""" def __init__(self, app, main_window, viewerid=None, default_viewer=ThreePaneViewer): Viewer.__init__(self, app, main_window, viewerid, viewer_name="tabbed_viewer") self._default_viewer = default_viewer self._current_viewer = None self._callbacks = {} self._ui_ready = False self._null_viewer = Viewer(app, main_window) self._tab_names = {} # TODO: move to the app? # viewer registry self._viewer_lookup = TwoWayDict() self._viewer_lookup.add(ThreePaneViewer(app, main_window).get_name(), ThreePaneViewer) # layout self._tabs = gtk.Notebook() self._tabs.show() self._tabs.set_property("show-border", False) self._tabs.set_property("homogeneous", True) self._tabs.set_property("scrollable", True) self._tabs.connect("switch-page", self._on_switch_tab) self._tabs.connect("page-added", self._on_tab_added) self._tabs.connect("page-removed", self._on_tab_removed) self._tabs.connect("button-press-event", self._on_button_press) self.pack_start(self._tabs, True, True, 0) # initialize with a single tab self.new_tab() # TODO: maybe add close_viewer() function def get_current_viewer(self): """Get currently focused viewer""" pos = self._tabs.get_current_page() if pos == -1: return self._null_viewer else: return self._tabs.get_nth_page(pos) def iter_viewers(self): """Iterate through all viewers""" for i in xrange(self._tabs.get_n_pages()): yield self._tabs.get_nth_page(i) def new_tab(self, viewer=None, init="current_node"): """Open a new tab with a viewer""" # TODO: make new tab appear next to existing tab # create viewer and add to notebook if viewer is None: viewer = self._default_viewer(self._app, self._main_window) label = TabLabel(self, viewer, None, _("(Untitled)")) label.connect("new-name", lambda w, text: self._on_new_tab_name(viewer, text)) self._tabs.append_page(viewer, label) self._tabs.set_tab_reorderable(viewer, True) self._tab_names[viewer] = None viewer.show_all() # setup viewer signals self._callbacks[viewer] = [ viewer.connect("error", lambda w,m,e: self.emit("error", m, e)), viewer.connect("status", lambda w,m,b: self.emit("status", m, b)), viewer.connect("window-request", lambda w,t: self.emit("window-request", t)), viewer.connect("current-node", self.on_tab_current_node), viewer.connect("modified", self.on_tab_modified)] # load app pref viewer.load_preferences(self._app.pref, True) # set notebook and node, if requested if init == "current_node": # replicate current view old_viewer = self._current_viewer if old_viewer is not None: viewer.set_notebook(old_viewer.get_notebook()) node = old_viewer.get_current_node() if node: viewer.goto_node(node) elif init == "none": pass else: raise Exception("unknown init") # switch to the new tab self._tabs.set_current_page(self._tabs.get_n_pages() - 1) def close_viewer(self, viewer): self.close_tab(self._tabs.page_num(viewer)) def close_tab(self, pos=None): """Close a tab""" # do not close last tab if self._tabs.get_n_pages() <= 1: return # determine tab to close if pos is None: pos = self._tabs.get_current_page() viewer = self._tabs.get_nth_page(pos) # clean up viewer viewer.set_notebook(None) for callid in self._callbacks[viewer]: viewer.disconnect(callid) del self._callbacks[viewer] del self._tab_names[viewer] self._main_window.remove_viewer(viewer) # clean up possible ui if pos == self._tabs.get_current_page(): viewer.remove_ui(self._main_window) self._current_viewer = None # perform removal from notebook self._tabs.remove_page(pos) def _on_switch_tab(self, tabs, page, page_num): """Callback for switching between tabs""" if not self._ui_ready: self._current_viewer = self._tabs.get_nth_page(page_num) return # remove old tab ui if self._current_viewer: self._current_viewer.remove_ui(self._main_window) # add new tab ui self._current_viewer = self._tabs.get_nth_page(page_num) self._current_viewer.add_ui(self._main_window) # notify listeners of new current tab def func(): self.emit("current-node", self._current_viewer.get_current_node()) notebook = self._current_viewer.get_notebook() if notebook: self.emit("modified", notebook.save_needed()) else: self.emit("modified", False) gobject.idle_add(func) def _on_tab_added(self, tabs, child, page_num): """Callback when a tab is added""" # ensure that tabs are shown if npages > 1, else hidden self._tabs.set_show_tabs(self._tabs.get_n_pages() > 1) def _on_tab_removed(self, tabs, child, page_num): """Callback when a tab is added""" # ensure that tabs are shown if npages > 1, else hidden self._tabs.set_show_tabs(self._tabs.get_n_pages() > 1) def on_tab_current_node(self, viewer, node): """Callback for when a viewer wants to set its title""" # get node title if node is None: if viewer.get_notebook(): title = viewer.get_notebook().get_attr("title", "") icon = None else: title = _("(Untitled)") icon = None else: title = node.get_attr("title", "") icon = get_node_icon(node, expand=False) # truncate title MAX_TITLE = 20 if len(title) > MAX_TITLE - 3: title = title[:MAX_TITLE-3] + "..." # set tab label with node title tab = self._tabs.get_tab_label(viewer) if self._tab_names[viewer] is None: # only update tab title if it does not have a name already tab.set_text(title) tab.set_icon(icon) # propogate current-node signal self.emit("current-node", node) def on_tab_modified(self, viewer, modified): """Callback for when viewer contains modified data""" # propogate modified signal self.emit("modified", modified) def switch_tab(self, step): """Switches to the next or previous tab""" pos = self._tabs.get_current_page() pos = (pos + step) % self._tabs.get_n_pages() self._tabs.set_current_page(pos) def _on_button_press(self, widget, event): if (self.get_toplevel().get_focus() == self._tabs and event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS): # double click, start tab name editing label = self._tabs.get_tab_label(self._tabs.get_nth_page( self._tabs.get_current_page())) label.start_editing() def _on_new_tab_name(self, viewer, name): """Callback for when a tab gets a new name""" if name == "": name = None self._tab_names[viewer] = name if name is None: self.on_tab_current_node(viewer, viewer.get_current_node()) #============================================== def set_notebook(self, notebook): """Set the notebook for the viewer""" if notebook is None: # clear the notebook in the viewer return self._current_viewer.set_notebook(notebook) # restore saved tabs tabs = notebook.pref.get("viewers", "ids", self._viewerid, "tabs", default=[]) if len(tabs) == 0: # no tabs to restore if self._current_viewer.get_notebook(): # create one new tab self.new_tab(init="none") return self._current_viewer.set_notebook(notebook) for tab in tabs: # TODO: add check for unknown type viewer_type = self._viewer_lookup.get1(tab.get("viewer_type", "")) viewer = self._current_viewer if viewer.get_notebook() or type(viewer) != viewer_type: # create new tab if notebook already loaded or # viewer type does not match viewer = (viewer_type( self._app, self._main_window, tab.get("viewerid", None)) if viewer_type else None) self.new_tab(viewer, init="none") else: # no notebook loaded, so adopt viewerid viewer.set_id(tab.get("viewerid", None)) # set notebook and node viewer.set_notebook(notebook) # set tab name name = tab.get("name", "") if name: self._tab_names[viewer] = name self._tabs.get_tab_label(viewer).set_text(name) # set tab focus current_id = notebook.pref.get( "viewers", "ids", self._viewerid, "current_viewer", default="") for i, viewer in enumerate(self.iter_viewers()): if viewer.get_id() == current_id: self._tabs.set_current_page(i) break def get_notebook(self): return self._current_viewer.get_notebook() def close_notebook(self, notebook): # progate close notebook closed_tabs = [] for i, viewer in enumerate(self.iter_viewers()): notebook2 = viewer.get_notebook() viewer.close_notebook(notebook) if notebook2 is not None and viewer.get_notebook() is None: closed_tabs.append(i) # close tabs for pos in reversed(closed_tabs): self.close_tab(pos) def load_preferences(self, app_pref, first_open=False): """Load application preferences""" for viewer in self.iter_viewers(): viewer.load_preferences(app_pref, first_open) def save_preferences(self, app_pref): """Save application preferences""" # TODO: loop through all viewers to save app_pref self._current_viewer.save_preferences(app_pref) def save(self): """Save the current notebook""" notebooks = set() for viewer in self.iter_viewers(): viewer.save() # add to list of all notebooks notebook = viewer.get_notebook() if notebook: notebooks.add(notebook) # clear tab info for all open notebooks for notebook in notebooks: tabs = notebook.pref.get("viewers", "ids", self._viewerid, "tabs", default=[]) tabs[:] = [] current_viewer = self._current_viewer # record tab info for viewer in self.iter_viewers(): notebook = viewer.get_notebook() if notebook: tabs = notebook.pref.get( "viewers", "ids", self._viewerid, "tabs") node = viewer.get_current_node() name = self._tab_names[viewer] tabs.append( {"viewer_type": viewer.get_name(), "viewerid": viewer.get_id(), "name": name if name is not None else ""}) # mark current viewer if viewer == current_viewer: notebook.pref.set("viewers", "ids", self._viewerid, "current_viewer", viewer.get_id()) def undo(self): """Undo the last action in the viewer""" return self._current_viewer.undo() def redo(self): """Redo the last action in the viewer""" return self._current_viewer.redo() def get_editor(self): return self._current_viewer.get_editor() #=============================================== # node operations def new_node(self, kind, pos, parent=None): return self._current_viewer.new_node(kind, pos, parent) def get_current_node(self): """Returns the currently focused page""" return self._current_viewer.get_current_node() def get_selected_nodes(self): """ Returns (nodes, widget) where 'nodes' are a list of selected nodes in widget 'widget' """ return self._current_viewer.get_selected_nodes() def goto_node(self, node, direct=False): """Move view focus to a particular node""" return self._current_viewer.goto_node(node, direct) def visit_history(self, offset): """Visit a node in the viewer's history""" self._current_viewer.visit_history(offset) #============================================ # Search def start_search_result(self): """Start a new search result""" return self._current_viewer.start_search_result() def add_search_result(self, node): """Add a search result""" return self._current_viewer.add_search_result(node) def end_search_result(self): """Start a new search result""" return self._current_viewer.end_search_result() #=========================================== # ui def add_ui(self, window): """Add the view's UI to a window""" assert window == self._main_window self._ui_ready = True self._action_group = gtk.ActionGroup("Tabbed Viewer") self._uis = [] add_actions(self._action_group, self._get_actions()) self._main_window.get_uimanager().insert_action_group( self._action_group, 0) for s in self._get_ui(): self._uis.append( self._main_window.get_uimanager().add_ui_from_string(s)) self._current_viewer.add_ui(window) def remove_ui(self, window): """Remove the view's UI from a window""" assert window == self._main_window self._ui_ready = False self._current_viewer.remove_ui(window) for ui in reversed(self._uis): self._main_window.get_uimanager().remove_ui(ui) self._uis = [] self._main_window.get_uimanager().remove_action_group( self._action_group) def _get_ui(self): return [""" """] def _get_actions(self): actions = map(lambda x: Action(*x), [ ("New Tab", None, _("New _Tab"), "T", _("Open a new tab"), lambda w: self.new_tab()), ("Close Tab", None, _("Close _Tab"), "W", _("Close a tab"), lambda w: self.close_tab()), ("Next Tab", None, _("_Next Tab"), "Page_Down", _("Switch to next tab"), lambda w: self.switch_tab(1)), ("Previous Tab", None, _("_Previous Tab"), "Page_Up", _("Switch to previous tab"), lambda w: self.switch_tab(-1)) ]) return actions class TabLabel (gtk.HBox): def __init__(self, tabs, viewer, icon, text): gtk.HBox.__init__(self, False, 2) #self.name = None self.tabs = tabs self.viewer = viewer # icon self.icon = gtk.Image() if icon: self.icon.set_from_pixbuf(icon) self.icon.show() # label self.label = gtk.Label(text) self.label.set_alignment(0, .5) self.label.show() # entry self.entry = gtk.Entry() self.entry.set_alignment(0) self.entry.connect("focus-out-event", lambda w, e: self.stop_editing()) self.entry.connect("editing-done", self._done) self._editing = False # close button self.close_button_state = [gtk.STATE_NORMAL] def highlight(w, state): self.close_button_state[0] = w.get_state() w.set_state(state) self.eclose_button = gtk.EventBox() self.close_button = keepnote.gui.get_resource_image("close_tab.png") self.eclose_button.add(self.close_button) self.eclose_button.show() self.close_button.set_alignment(0, .5) self.eclose_button.connect( "enter-notify-event", lambda w, e: highlight(w, gtk.STATE_PRELIGHT)) self.eclose_button.connect( "leave-notify-event", lambda w, e: highlight(w, self.close_button_state[0])) self.close_button.show() self.eclose_button.connect("button-press-event", lambda w, e: self.tabs.close_viewer(self.viewer) if e.button == 1 else None) # layout self.pack_start(self.icon, False, False, 0) self.pack_start(self.label, True, True, 0) self.pack_start(self.eclose_button, False, False, 0) def _done(self, widget): text = self.entry.get_text() self.stop_editing() self.label.set_label(text) self.emit("new-name", text) def start_editing(self): if not self._editing: self._editing = True w, h = self.label.get_child_requisition() self.remove(self.label) self.entry.set_text(self.label.get_label()) self.pack_start(self.entry, True, True, 0) self.reorder_child(self.entry, 1) self.entry.set_size_request(w, h) self.entry.show() self.entry.grab_focus() self.entry.start_editing(gtk.gdk.Event(gtk.gdk.NOTHING)) def stop_editing(self): if self._editing: self._editing = False self.remove(self.entry) self.pack_start(self.label, True, True, 0) self.reorder_child(self.label, 1) self.label.show() def set_text(self, text): if not self._editing: self.label.set_text(text) def set_icon(self, pixbuf): self.icon.set_from_pixbuf(pixbuf) gobject.type_register(TabLabel) gobject.signal_new("new-name", TabLabel, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) keepnote-0.7.8/keepnote/gui/editor_sourceview.py0000644000175000017500000004160411677423601020237 0ustar razraz""" KeepNote Editor widget in main window """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import gettext import sys, os # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk.glade import gobject try: raise ImportError() from gtksourceview2 import View as SourceView from gtksourceview2 import Buffer as SourceBuffer from gtksourceview2 import LanguageManager as SourceLanguageManager except ImportError: SourceView = None # keepnote imports import keepnote from keepnote import \ KeepNoteError, is_url, unicode_gtk from keepnote.notebook import \ NoteBookError, \ get_node_url, \ parse_node_url, \ is_node_url from keepnote import notebook as notebooklib from keepnote import safefile from keepnote.gui import richtext from keepnote.gui.richtext import \ RichTextView, RichTextBuffer, \ RichTextIO, RichTextError, RichTextImage from keepnote.gui.richtext.richtext_tags import \ RichTextTagTable, RichTextLinkTag from keepnote.gui import \ CONTEXT_MENU_ACCEL_PATH, \ FileChooserDialog, \ get_pixbuf, \ get_resource, \ get_resource_image, \ get_resource_pixbuf, \ Action, \ ToggleAction, \ add_actions, \ update_file_preview, \ dialog_find, \ dialog_image_resize from keepnote.gui.icons import \ get_node_icon, lookup_icon_filename from keepnote.gui.font_selector import FontSelector from keepnote.gui.colortool import FgColorTool, BgColorTool from keepnote.gui.richtext.richtext_tags import color_tuple_to_string from keepnote.gui.popupwindow import PopupWindow from keepnote.gui.linkcomplete import LinkPickerPopup from keepnote.gui.link_editor import LinkEditor from keepnote.gui.editor import KeepNoteEditor from keepnote.gui.editor_richtext import ComboToolItem _ = keepnote.translate class TextEditor (KeepNoteEditor): def __init__(self, app): KeepNoteEditor.__init__(self, app) self._app = app self._notebook = None self._link_picker = None self._maxlinks = 10 # maximum number of links to show in link picker # state self._page = None # current NoteBookPage self._page_scrolls = {} # remember scroll in each page self._page_cursors = {} self._textview_io = RichTextIO() # textview and its callbacks if SourceView: self._textview = SourceView(SourceBuffer()) self._textview.get_buffer().set_highlight_syntax(True) #self._textview.set_show_margin(True) #self._textview.disable() else: self._textview = RichTextView(RichTextBuffer( self._app.get_richtext_tag_table())) # textview self._textview.disable() self._textview.connect("modified", self._on_modified_callback) self._textview.connect("visit-url", self._on_visit_url) # scrollbars self._sw = gtk.ScrolledWindow() self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self._sw.set_shadow_type(gtk.SHADOW_IN) self._sw.add(self._textview) self.pack_start(self._sw) #self._socket = gtk.Socket() #self.pack_start(self._socket) # menus #self.editor_menus = EditorMenus(self._app, self) # find dialog #self.find_dialog = dialog_find.KeepNoteFindDialog(self) self.show_all() def set_notebook(self, notebook): """Set notebook for editor""" # set new notebook self._notebook = notebook if self._notebook: # read default font pass else: # no new notebook, clear the view self.clear_view() def load_preferences(self, app_pref, first_open=False): """Load application preferences""" #self.editor_menus.enable_spell_check( # self._app.pref.get("editors", "general", "spell_check", # default=True)) if not SourceView: self._textview.set_default_font("Monospace 10") def save_preferences(self, app_pref): """Save application preferences""" # record state in preferences #app_pref.set("editors", "general", "spell_check", # self._textview.is_spell_check_enabled()) def get_textview(self): """Return the textview""" return self._textview def is_focus(self): """Return True if text editor has focus""" return self._textview.is_focus() def grab_focus(self): """Pass focus to textview""" self._textview.grab_focus() def clear_view(self): """Clear editor view""" self._page = None if not SourceView: self._textview.disable() def undo(self): """Undo the last action in the viewer""" self._textview.undo() def redo(self): """Redo the last action in the viewer""" self._textview.redo() def view_nodes(self, nodes): """View a page in the editor""" # editor cannot view multiple nodes at once # if asked to, it will view none if len(nodes) > 1: nodes = [] # save current page before changing nodes self.save() self._save_cursor() if len(nodes) == 0: self.clear_view() else: page = nodes[0] self._page = page if not SourceView: self._textview.enable() try: if page.has_attr("payload_filename"): #text = safefile.open( # os.path.join(page.get_path(), # page.get_attr("payload_filename")), # codec="utf-8").read() infile = page.open_file( page.get_attr("payload_filename"), "r", "utf-8") text = infile.read() infile.close() self._textview.get_buffer().set_text(text) self._load_cursor() if SourceView: manager = SourceLanguageManager() #print manager.get_language_ids() #lang = manager.get_language_from_mime_type( # page.get_attr("content_type")) lang = manager.get_language("python") self._textview.get_buffer().set_language(lang) else: self.clear_view() except RichTextError, e: self.clear_view() self.emit("error", e.msg, e) except Exception, e: self.clear_view() self.emit("error", "Unknown error", e) if len(nodes) > 0: self.emit("view-node", nodes[0]) def _save_cursor(self): if self._page is not None: it = self._textview.get_buffer().get_iter_at_mark( self._textview.get_buffer().get_insert()) self._page_cursors[self._page] = it.get_offset() x, y = self._textview.window_to_buffer_coords( gtk.TEXT_WINDOW_TEXT, 0, 0) it = self._textview.get_iter_at_location(x, y) self._page_scrolls[self._page] = it.get_offset() def _load_cursor(self): # place cursor in last location if self._page in self._page_cursors: offset = self._page_cursors[self._page] it = self._textview.get_buffer().get_iter_at_offset(offset) self._textview.get_buffer().place_cursor(it) # place scroll in last position if self._page in self._page_scrolls: offset = self._page_scrolls[self._page] buf = self._textview.get_buffer() it = buf.get_iter_at_offset(offset) mark = buf.create_mark(None, it, True) self._textview.scroll_to_mark(mark, 0.49, use_align=True, xalign=0.0) buf.delete_mark(mark) def save(self): """Save the loaded page""" if self._page is not None and \ self._page.is_valid() and \ (SourceView or self._textview.is_modified()): try: # save text data buf = self._textview.get_buffer() text = unicode_gtk(buf.get_text(buf.get_start_iter(), buf.get_end_iter())) #out = safefile.open( # os.path.join(self._page.get_path(), # self._page.get_attr("payload_filename")), "w", # codec="utf-8") out = self._page.open_file( self._page.get_attr("payload_filename"), "w", "utf-8") out.write(text) out.close() # save meta data self._page.set_attr_timestamp("modified_time") self._page.save() except RichTextError, e: self.emit("error", e.msg, e) except NoteBookError, e: self.emit("error", e.msg, e) except Exception, e: self.emit("error", str(e), e) def save_needed(self): """Returns True if textview is modified""" if not SourceView: return self._textview.is_modified() return False def add_ui(self, window): if not SourceView: self._textview.set_accel_group(window.get_accel_group()) self._textview.set_accel_path(CONTEXT_MENU_ACCEL_PATH) #if hasattr(self, "_socket"): # print "id", self._socket.get_id() # self._socket.add_id(0x480001f) #self.editor_menus.add_ui(window, # use_minitoolbar= # self._app.pref.get("look_and_feel", # "use_minitoolbar", # default=False)) def remove_ui(self, window): pass #self.editor_menus.remove_ui(window) #=========================================== # callbacks for textview def _on_modified_callback(self, textview, modified): """Callback for textview modification""" self.emit("modified", self._page, modified) # make notebook node modified if modified: self._page.mark_modified() self._page.notify_change(False) def _on_visit_url(self, textview, url): """Callback for textview visiting a URL""" if is_node_url(url): host, nodeid = parse_node_url(url) node = self._notebook.get_node_by_id(nodeid) if node: self.emit("visit-node", node) else: try: self._app.open_webpage(url) except KeepNoteError, e: self.emit("error", e.msg, e) class EditorMenus (gobject.GObject): def __init__(self, app, editor): gobject.GObject.__init__(self) self._app = app self._editor = editor self._action_group = None self._uis = [] self.spell_check_toggle = None self._removed_widgets = [] #======================================================= # spellcheck def enable_spell_check(self, enabled): """Spell check""" self._editor.get_textview().enable_spell_check(enabled) # see if spell check became enabled enabled = self._editor.get_textview().is_spell_check_enabled() # update UI to match if self.spell_check_toggle: self.spell_check_toggle.set_active(enabled) return enabled def on_spell_check_toggle(self, widget): """Toggle spell checker""" self.enable_spell_check(widget.get_active()) #===================================================== # toolbar and menus def add_ui(self, window): self._action_group = gtk.ActionGroup("Editor") self._uis = [] add_actions(self._action_group, self.get_actions()) window.get_uimanager().insert_action_group( self._action_group, 0) for s in self.get_ui(): self._uis.append(window.get_uimanager().add_ui_from_string(s)) window.get_uimanager().ensure_update() self.setup_menu(window, window.get_uimanager()) def remove_ui(self, window): # remove ui for ui in reversed(self._uis): window.get_uimanager().remove_ui(ui) self._uis = [] window.get_uimanager().ensure_update() # remove action group window.get_uimanager().remove_action_group(self._action_group) self._action_group = None def get_actions(self): def BothAction(name1, *args): return [Action(name1, *args), ToggleAction(name1 + " Tool", *args)] return (map(lambda x: Action(*x), [ # finding ("Find In Page", gtk.STOCK_FIND, _("_Find In Page..."), "F", None, lambda w: self._editor.find_dialog.on_find(False)), ("Find Next In Page", gtk.STOCK_FIND, _("Find _Next In Page..."), "G", None, lambda w: self._editor.find_dialog.on_find(False, forward=True)), ("Find Previous In Page", gtk.STOCK_FIND, _("Find Pre_vious In Page..."), "G", None, lambda w: self._editor.find_dialog.on_find(False, forward=False)), ("Replace In Page", gtk.STOCK_FIND_AND_REPLACE, _("_Replace In Page..."), "R", None, lambda w: self._editor.find_dialog.on_find(True)), ]) + [ToggleAction("Spell Check", None, _("_Spell Check"), "", None, self.on_spell_check_toggle)] ) def get_ui(self): ui = [""" """] ui.append(""" """) return ui def setup_menu(self, window, uimanager): u = uimanager # get spell check toggle self.spell_check_toggle = \ uimanager.get_widget("/main_menu_bar/Tools/Viewer/Spell Check") self.spell_check_toggle.set_sensitive( self._editor.get_textview().can_spell_check()) self.spell_check_toggle.set_active(window.get_app().pref.get( "editors", "general", "spell_check", default=True)) keepnote-0.7.8/keepnote/gui/link_editor.py0000644000175000017500000001602111677423601016774 0ustar razraz""" KeepNote Link Editor Widget """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk, gobject # keepnote imports from keepnote import is_url, unicode_gtk from keepnote.notebook import get_node_url # TODO: make more checks for start, end not None class LinkEditor (gtk.Frame): """Widget for editing KeepNote links""" def __init__(self): gtk.Frame.__init__(self, "Link editor") self.use_text = False self.current_url = None self.active = False self.textview = None self.search_nodes = None self.layout() def set_textview(self, textview): self.textview = textview def layout(self): # layout self.set_no_show_all(True) self.align = gtk.Alignment() self.add(self.align) self.align.set_padding(5, 5, 5, 5) self.align.set(0, 0, 1, 1) self.show() self.align.show_all() vbox = gtk.VBox(False, 5) self.align.add(vbox) hbox = gtk.HBox(False, 5) #self.align.add(hbox) vbox.pack_start(hbox, True, True, 0) label = gtk.Label("url:") hbox.pack_start(label, False, False, 0) label.set_alignment(0, .5) self.url_text = gtk.Entry() hbox.pack_start(self.url_text, True, True, 0) self.url_text.set_width_chars(-1) self.url_text.connect("key-press-event", self._on_key_press_event) self.url_text.connect("focus-in-event", self._on_url_text_start) self.url_text.connect("focus-out-event", self._on_url_text_done) self.url_text.connect("changed", self._on_url_text_changed) self.url_text.connect("activate", self._on_activate) self._liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self.completion = gtk.EntryCompletion() self.completion.connect("match-selected", self._on_completion_match) self.completion.set_match_func(self._match_func) self.completion.set_model(self._liststore) self.completion.set_text_column(0) self.url_text.set_completion(self.completion) self._ignore_text = False #self.use_text_check = gtk.CheckButton("_use text as url") #vbox.pack_start(self.use_text_check, False, False, 0) #self.use_text_check.connect("toggled", self._on_use_text_toggled) #self.use_text = self.use_text_check.get_active() if not self.active: self.hide() def set_search_nodes(self, search): self.search_nodes = search def _match_func(self, completion, key_string, iter): return True def _on_url_text_changed(self, url_text): if not self._ignore_text: self.update_completion() def update_completion(self): text = unicode_gtk(self.url_text.get_text()) self._liststore.clear() if self.search_nodes and len(text) > 0: results = self.search_nodes(text)[:10] for nodeid, title in results: self._liststore.append([title, nodeid]) def _on_completion_match(self, completion, model, iter): url = get_node_url(model[iter][1]) self._ignore_text = True self.url_text.set_text(url) self._ignore_text = False self.dismiss(True) def _on_use_text_toggled(self, check): self.use_text = check.get_active() if self.use_text and self.current_url is not None: self.url_text.set_text(self.current_url) self.url_text.set_sensitive(False) self.set_url() else: self.url_text.set_sensitive(True) def _on_url_text_done(self, widget, event): self.set_url() def _on_url_text_start(self, widget, event): if self.textview: tag, start, end = self.textview.get_link() if tag: self.textview.get_buffer().select_range(start, end) self.update_completion() else: self.dismiss(False) def set_url(self): if self.textview is None: return url = unicode_gtk(self.url_text.get_text()) tag, start, end = self.textview.get_link() if start is not None: if url == "": self.textview.set_link(None, start, end) elif tag.get_href() != url: self.textview.set_link(url, start, end) def on_font_change(self, editor, font): """Callback for when font changes under richtext cursor""" if font.link: self.active = True self.url_text.set_width_chars(-1) self.show() self.align.show_all() self.current_url = font.link.get_href() self._ignore_text = True self.url_text.set_text(self.current_url) self._ignore_text = False if self.textview: gobject.idle_add(lambda : self.textview.scroll_mark_onscreen( self.textview.get_buffer().get_insert())) elif self.active: self.set_url() self.active = False self.hide() self.current_url = None self.url_text.set_text("") def edit(self): if self.active: self.url_text.select_region(0, -1) self.url_text.grab_focus() if self.textview: tag, start, end = self.textview.get_link() if start: self.textview.get_buffer().select_range(start, end) def _on_activate(self, entry): self.dismiss(True) def _on_key_press_event(self, widget, event): if event.keyval == gtk.keysyms.Escape: self.dismiss(False) def dismiss(self, set_url): if self.textview is None: return tag, start, end = self.textview.get_link() if end: if set_url: self.set_url() #self.textview.get_buffer().place_cursor(end) else: # DEBUG #print "NO LINK" pass self.textview.grab_focus() keepnote-0.7.8/keepnote/gui/__init__.py0000644000175000017500000007743011677423601016243 0ustar razraz""" KeepNote Graphical User Interface for KeepNote Application """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import gettext import mimetypes import os import subprocess import sys import tempfile import thread import threading # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk.glade import gobject # keepnote imports import keepnote from keepnote import log_error import keepnote.gui.richtext.richtext_tags from keepnote import get_resource, ensure_unicode, get_platform, unicode_gtk from keepnote import tasklib from keepnote.notebook import \ NoteBookError import keepnote.notebook as notebooklib import keepnote.gui.dialog_app_options import keepnote.gui.dialog_node_icon import keepnote.gui.dialog_wait from keepnote.gui.icons import \ DEFAULT_QUICK_PICK_ICONS, uncache_node_icon _ = keepnote.translate #============================================================================= # constants MAX_RECENT_NOTEBOOKS = 20 ACCEL_FILE = u"accel.txt" IMAGE_DIR = u"images" CONTEXT_MENU_ACCEL_PATH = "
/context_menu" DEFAULT_AUTOSAVE_TIME = 10 * 1000 # 10 sec (in msec) # font constants DEFAULT_FONT_FAMILY = "Sans" DEFAULT_FONT_SIZE = 10 DEFAULT_FONT = "%s %d" % (DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE) if keepnote.get_platform() == "darwin": CLIPBOARD_NAME = gdk.SELECTION_PRIMARY else: CLIPBOARD_NAME = "CLIPBOARD" DEFAULT_COLORS_FLOAT = [ # lights (1, .6, .6), (1, .8, .6), (1, 1, .6), (.6, 1, .6), (.6, 1, 1), (.6, .6, 1), (1, .6, 1), # trues (1, 0, 0), (1, .64, 0), (1, 1, 0), (0, 1, 0), (0, 1, 1), (0, 0, 1), (1, 0, 1), # darks (.5, 0, 0), (.5, .32, 0), (.5, .5, 0), (0, .5, 0), (0, .5, .5), (0, 0, .5), (.5, 0, .5), # white, gray, black (1, 1, 1), (.9, .9, .9), (.75, .75, .75), (.5, .5, .5), (.25, .25, .25), (.1, .1, .1), (0, 0, 0), ] def color_float_to_int8(color): return (int(255*color[0]), int(255*color[1]), int(255*color[2])) def color_int8_to_str(color): return "#%02x%02x%02x" % (color[0], color[1], color[2]) DEFAULT_COLORS = [color_int8_to_str(color_float_to_int8(color)) for color in DEFAULT_COLORS_FLOAT] #============================================================================= # resources class PixbufCache (object): """A cache for loading pixbufs from the filesystem""" def __init__(self): self._pixbufs = {} def get_pixbuf(self, filename, size=None, key=None): """ Get pixbuf from a filename Cache pixbuf for later use """ if key is None: key = (filename, size) if key in self._pixbufs: return self._pixbufs[key] else: # may raise GError pixbuf = gtk.gdk.pixbuf_new_from_file(filename) # resize if size: if size != (pixbuf.get_width(), pixbuf.get_height()): pixbuf = pixbuf.scale_simple(size[0], size[1], gtk.gdk.INTERP_BILINEAR) self._pixbufs[key] = pixbuf return pixbuf def cache_pixbuf(self, pixbuf, key): self._pixbufs[key] = pixbuf def is_pixbuf_cached(self, key): return key in self._pixbufs # singleton pixbufs = PixbufCache() get_pixbuf = pixbufs.get_pixbuf cache_pixbuf = pixbufs.cache_pixbuf is_pixbuf_cached = pixbufs.is_pixbuf_cached def get_resource_image(*path_list): """Returns gtk.Image from resource path""" filename = get_resource(IMAGE_DIR, *path_list) img = gtk.Image() img.set_from_file(filename) return img def get_resource_pixbuf(*path_list, **options): """Returns cached pixbuf from resource path""" # raises GError return pixbufs.get_pixbuf(get_resource(IMAGE_DIR, *path_list), **options) def fade_pixbuf(pixbuf, alpha=128): """Returns a new faded a pixbuf""" width, height = pixbuf.get_width(), pixbuf.get_height() pixbuf2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, width, height) pixbuf2.fill(0xffffff00) # fill with transparent pixbuf.composite(pixbuf2, 0, 0, width, height, 0, 0, 1.0, 1.0, gtk.gdk.INTERP_NEAREST, alpha) #pixbuf.composite_color(pixbuf2, 0, 0, width, height, # 0, 0, 1.0, 1.0, gtk.gdk.INTERP_NEAREST, alpha, # 0, 0, 1, 0xcccccc, 0x00000000) return pixbuf2 #============================================================================= # misc. gui functions def get_accel_file(): """Returns gtk accel file""" return os.path.join(keepnote.get_user_pref_dir(), ACCEL_FILE) def init_key_shortcuts(): """Setup key shortcuts for the window""" accel_file = get_accel_file() if os.path.exists(accel_file): gtk.accel_map_load(accel_file) else: gtk.accel_map_save(accel_file) def set_gtk_style(font_size=10, vsep=0): """ Set basic GTK style settings """ gtk.rc_parse_string(""" style "keepnote-treeview" { font_name = "%(font_size)d" GtkTreeView::vertical-separator = %(vsep)d GtkTreeView::expander-size = 10 } class "GtkTreeView" style "keepnote-treeview" class "GtkEntry" style "keepnote-treeview" """ % {"font_size": font_size, "vsep": vsep}) def update_file_preview(file_chooser, preview): """Preview widget for file choosers""" filename = file_chooser.get_preview_filename() try: pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 128, 128) preview.set_from_pixbuf(pixbuf) have_preview = True except: have_preview = False file_chooser.set_preview_widget_active(have_preview) class FileChooserDialog (gtk.FileChooserDialog): """File Chooser Dialog with a persistent path""" def __init__(self, title=None, parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=None, backend=None, app=None, persistent_path=None): gtk.FileChooserDialog.__init__(self, title, parent, action, buttons, backend) self._app = app self._persistent_path = persistent_path if self._app and self._persistent_path: path = self._app.get_default_path(self._persistent_path) if path and os.path.exists(path): self.set_current_folder(path) def run(self): response = gtk.FileChooserDialog.run(self) if (response == gtk.RESPONSE_OK and self._app and self._persistent_path): self._app.set_default_path( self._persistent_path, unicode_gtk(self.get_current_folder())) return response #============================================================================= # menu actions class UIManager (gtk.UIManager): """Specialization of UIManager for use in KeepNote""" def __init__(self, force_stock=False): gtk.UIManager.__init__(self) self.connect("connect-proxy", self._on_connect_proxy) self.connect("disconnect-proxy", self._on_disconnect_proxy) self.force_stock = force_stock self.c = gtk.VBox() def _on_connect_proxy(self, uimanager, action, widget): """Callback for a widget entering management""" if isinstance(action, (Action, ToggleAction)) and action.icon: self.set_icon(widget, action) def _on_disconnect_proxy(self, uimanager, action, widget): """Callback for a widget leaving management""" pass def set_force_stock(self, force): """Sets the 'force stock icon' option""" self.force_stock = force for ag in self.get_action_groups(): for action in ag.list_actions(): for widget in action.get_proxies(): self.set_icon(widget, action) def set_icon(self, widget, action): """Sets the icon for a managed widget""" # do not handle actions that are not of our custom classes if not isinstance(action, (Action, ToggleAction)): return if isinstance(widget, gtk.ImageMenuItem): if self.force_stock and action.get_property("stock-id"): img = gtk.Image() img.set_from_stock(action.get_property("stock-id"), gtk.ICON_SIZE_MENU) img.show() widget.set_image(img) elif action.icon: img = gtk.Image() img.set_from_pixbuf(get_resource_pixbuf(action.icon)) img.show() widget.set_image(img) elif isinstance(widget, gtk.ToolButton): if self.force_stock and action.get_property("stock-id"): w = widget.get_icon_widget() if w: w.set_from_stock(action.get_property("stock-id"), gtk.ICON_SIZE_MENU) elif action.icon: w = widget.get_icon_widget() if w: w.set_from_pixbuf(get_resource_pixbuf(action.icon)) else: img = gtk.Image() img.set_from_pixbuf(get_resource_pixbuf(action.icon)) img.show() widget.set_icon_widget(img) class Action (gtk.Action): def __init__(self, name, stockid=None, label=None, accel="", tooltip="", func=None, icon=None): gtk.Action.__init__(self, name, label, tooltip, stockid) self.func = func self.accel = accel self.icon = icon self.signal = None if func: self.signal = self.connect("activate", func) class ToggleAction (gtk.ToggleAction): def __init__(self, name, stockid, label=None, accel="", tooltip="", func=None, icon=None): gtk.ToggleAction.__init__(self, name, label, tooltip, stockid) self.func = func self.accel = accel self.icon = icon self.signal = None if func: self.signal = self.connect("toggled", func) def add_actions(actiongroup, actions): """Add a list of Action's to an gtk.ActionGroup""" for action in actions: actiongroup.add_action_with_accel(action, action.accel) #============================================================================= # Application for GUI # TODO: implement 'close all' for notebook # requires listening for close. class KeepNote (keepnote.KeepNote): """GUI version of the KeepNote application instance""" def __init__(self, basedir=None): keepnote.KeepNote.__init__(self, basedir) # window management self._current_window = None self._windows = [] # shared gui resources self._tag_table = keepnote.gui.richtext.richtext_tags.RichTextTagTable() self.app_options_dialog = keepnote.gui.dialog_app_options.ApplicationOptionsDialog(self) self.node_icon_dialog = keepnote.gui.dialog_node_icon.NodeIconDialog(self) # auto save self._auto_saving = False # True if autosave is on self._auto_save_registered = False # True if autosave is registered self._auto_save_pause = 0 # >0 if autosave is paused def init(self): """Initialize application from disk""" keepnote.KeepNote.init(self) def set_lang(self): """Set language for application""" keepnote.KeepNote.set_lang(self) # setup glade with gettext import gtk.glade gtk.glade.bindtextdomain(keepnote.GETTEXT_DOMAIN, keepnote.get_locale_dir()) gtk.glade.textdomain(keepnote.GETTEXT_DOMAIN) def load_preferences(self): """Load information from preferences""" keepnote.KeepNote.load_preferences(self) # set defaults for auto save p = self.pref use_autosave = p.get("autosave", default=True) p.get("autosave_time", default=DEFAULT_AUTOSAVE_TIME) # set style set_gtk_style(font_size=p.get("look_and_feel", "app_font_size", default=10)) # let windows load their preferences for window in self._windows: window.load_preferences() for notebook in self._notebooks.itervalues(): notebook.enable_fulltext_search(p.get("use_fulltext_search", default=True)) # start autosave loop, if requested self.begin_auto_save() def save_preferences(self): """Save information into preferences""" # let windows save their preferences for window in self._windows: window.save_preferences() keepnote.KeepNote.save_preferences(self) #================================= # GUI def get_richtext_tag_table(self): """Returns the application-wide richtext tag table""" return self._tag_table def new_window(self): """Create a new main window""" import keepnote.gui.main_window window = keepnote.gui.main_window.KeepNoteWindow(self) window.connect("delete-event", self._on_window_close) window.connect("focus-in-event", self._on_window_focus) self._windows.append(window) self.init_extensions_windows([window]) window.show_all() if self._current_window is None: self._current_window = window return window def get_current_window(self): """Returns the currenly active window""" return self._current_window def get_windows(self): """Returns a list of open windows""" return self._windows def open_notebook(self, filename, window=None, task=None): """Open notebook""" from keepnote.gui import dialog_update_notebook # HACK if isinstance(self._conns.get(filename), keepnote.notebook.connection.fs.NoteBookConnectionFS): try: version = notebooklib.get_notebook_version(filename) except Exception, e: self.error(_("Could not load notebook '%s'.") % filename, e, sys.exc_info()[2]) return None if version < notebooklib.NOTEBOOK_FORMAT_VERSION: dialog = dialog_update_notebook.UpdateNoteBookDialog( self, window) if not dialog.show(filename, version=version, task=task): self.error(_("Cannot open notebook (version too old)")) gtk.gdk.threads_leave() return None # load notebook in background def update(task): sem = threading.Semaphore() sem.acquire() # perform notebook load in gui thread. # Ideally, this should be in the background, but it is very # slow. If updating the wait dialog wasn't so expensive, I would # simply do loading in the background thread. def func(): try: conn = self._conns.get(filename) notebook = notebooklib.NoteBook() notebook.load(filename, conn) task.set_result(notebook) except Exception, e: task.set_exc_info() task.stop() sem.release() # notify that notebook is loaded return False gobject.idle_add(func) # wait for notebook to load sem.acquire() def update_old(task): notebook = notebooklib.NoteBook() notebook.load(filename) task.set_result(notebook) task = tasklib.Task(update) dialog = keepnote.gui.dialog_wait.WaitDialog(window) dialog.show(_("Opening notebook"), _("Loading..."), task, cancel=False) # detect errors try: if task.aborted(): raise task.exc_info()[1] else: notebook = task.get_result() if notebook is None: return None except notebooklib.NoteBookVersionError, e: self.error(_("This version of %s cannot read this notebook.\n" "The notebook has version %d. %s can only read %d.") % (keepnote.PROGRAM_NAME, e.notebook_version, keepnote.PROGRAM_NAME, e.readable_version), e, task.exc_info()[2]) return None except NoteBookError, e: self.error(_("Could not load notebook '%s'.") % filename, e, task.exc_info()[2]) return None except Exception, e: # give up opening notebook self.error(_("Could not load notebook '%s'.") % filename, e, task.exc_info()[2]) return None self._init_notebook(notebook) return notebook def _init_notebook(self, notebook): write_needed = False # install default quick pick icons if len(notebook.pref.get_quick_pick_icons()) == 0: notebook.pref.set_quick_pick_icons( list(DEFAULT_QUICK_PICK_ICONS)) notebook.set_preferences_dirty() write_needed = True # install default quick pick icons if len(notebook.pref.get("colors", default=())) == 0: notebook.pref.set("colors", DEFAULT_COLORS) notebook.set_preferences_dirty() write_needed = True notebook.enable_fulltext_search(self.pref.get("use_fulltext_search", default=True)) # TODO: use listeners to invoke saving if write_needed: notebook.write_preferences() def save_notebooks(self, silent=False): """Save all opened notebooks""" # clear all window and viewer info in notebooks for notebook in self._notebooks.itervalues(): notebook.pref.clear("windows", "ids") notebook.pref.clear("viewers", "ids") # save all the windows for window in self._windows: window.save_notebook(silent=silent) # save all the notebooks for notebook in self._notebooks.itervalues(): notebook.save() # let windows know about completed save for window in self._windows: window.update_title() def _on_closing_notebook(self, notebook, save): """ Callback for when notebook is about to close """ keepnote.KeepNote._on_closing_notebook(self, notebook, save) try: if save: self.save() except: keepnote.log_error("Error while closing notebook") for window in self._windows: window.close_notebook(notebook) def goto_nodeid(self, nodeid): """ Open a node by nodeid """ for window in self.get_windows(): notebook = window.get_notebook() if not notebook: continue node = notebook.get_node_by_id(nodeid) if node: window.get_viewer().goto_node(node) break #===================================== # auto-save def begin_auto_save(self): """Begin autosave callbacks""" if self.pref.get("autosave"): self._auto_saving = True if not self._auto_save_registered: self._auto_save_registered = True gobject.timeout_add(self.pref.get("autosave_time"), self.auto_save) else: self._auto_saving = False def end_auto_save(self): """Stop autosave""" self._auto_saving = False def auto_save(self): """Callback for autosaving""" self._auto_saving = self.pref.get("autosave") # NOTE: return True to activate next timeout callback if not self._auto_saving: self._auto_save_registered = False return False # don't do autosave if it is paused if self._auto_save_pause > 0: return True self.save(True) return True def pause_auto_save(self, pause): """Pauses autosaving""" self._auto_save_pause += 1 if pause else -1 #=========================================== # node icons def on_set_icon(self, icon_file, icon_open_file, nodes): """ Change the icon for a node icon_file, icon_open_file -- icon basenames use "" to delete icon setting (set default) use None to leave icon setting unchanged """ # TODO: maybe this belongs inside the node_icon_dialog? for node in nodes: if icon_file == u"": node.del_attr("icon") elif icon_file is not None: node.set_attr("icon", icon_file) if icon_open_file == u"": node.del_attr("icon_open") elif icon_open_file is not None: node.set_attr("icon_open", icon_open_file) # uncache pixbufs uncache_node_icon(node) def on_new_icon(self, nodes, notebook, window=None): """Change the icon for a node""" # TODO: maybe this belongs inside the node_icon_dialog? if notebook is None: return # TODO: assume only one node is selected node = nodes[0] icon_file, icon_open_file = self.node_icon_dialog.show(node, window=window) newly_installed = set() # NOTE: files may be filename or basename, use isabs to distinguish if icon_file and os.path.isabs(icon_file) and \ icon_open_file and os.path.isabs(icon_open_file): icon_file, icon_open_file = notebook.install_icons( icon_file, icon_open_file) newly_installed.add(os.path.basename(icon_file)) newly_installed.add(os.path.basename(icon_open_file)) else: if icon_file and os.path.isabs(icon_file): icon_file = notebook.install_icon(icon_file) newly_installed.add(os.path.basename(icon_file)) if icon_open_file and os.path.isabs(icon_open_file): icon_open_file = notebook.install_icon(icon_open_file) newly_installed.add(os.path.basename(icon_open_file)) # set quick picks if OK was pressed if icon_file is not None: notebook.pref.set_quick_pick_icons( self.node_icon_dialog.get_quick_pick_icons()) # TODO: figure out whether I need to track newly installed or not. # set notebook icons notebook_icons = notebook.get_icons() keep_set = set(self.node_icon_dialog.get_notebook_icons()) | \ newly_installed for icon in notebook_icons: if icon not in keep_set: notebook.uninstall_icon(icon) notebook.set_preferences_dirty() # TODO: should this be done with a notify? notebook.write_preferences() self.on_set_icon(icon_file, icon_open_file, nodes) #================================== # file attachment def on_attach_file(self, node=None, parent_window=None): dialog = FileChooserDialog( _("Attach File..."), parent_window, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(_("Cancel"), gtk.RESPONSE_CANCEL, _("Attach"), gtk.RESPONSE_OK), app=self, persistent_path="attach_file_path") dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_select_multiple(True) # setup preview preview = gtk.Image() dialog.set_preview_widget(preview) dialog.connect("update-preview", update_file_preview, preview) response = dialog.run() if response == gtk.RESPONSE_OK: if len(dialog.get_filenames()) > 0: filenames = map(unicode_gtk, dialog.get_filenames()) self.attach_files(filenames, node, parent_window=parent_window) dialog.destroy() def attach_file(self, filename, parent, index=None, parent_window=None): self.attach_files([filename], parent, index, parent_window) def attach_files(self, filenames, parent, index=None, parent_window=None): if parent_window is None: parent_window = self.get_current_window() #def func(task): # for filename in filenames: # task.set_message(("detail", _("attaching %s") % # os.path.basename(filename))) # notebooklib.attach_file(filename, parent, index) # if not task.is_running(): # task.abort() #task = tasklib.Task(func) try: for filename in filenames: notebooklib.attach_file(filename, parent, index) #dialog = keepnote.gui.dialog_wait.WaitDialog(parent_window) #dialog.show(_("Attach File"), _("Attaching files to notebook."), # task, cancel=False) #if task.aborted(): # raise task.exc_info()[1] except Exception, e: if len(filenames) > 1: self.error(_("Error while attaching files %s." % ", ".join(["'%s'" % f for f in filenames])), e, sys.exc_info()[2]) else: self.error(_("Error while attaching file '%s'." % filenames[0]), e, sys.exc_info()[2]) #================================== # misc GUI def focus_windows(self): """Focus all open windows on desktop""" for window in self._windows: window.restore_window() def error(self, text, error=None, tracebk=None, parent=None): """Display an error message""" if parent is None: parent = self.get_current_window() dialog = gtk.MessageDialog(parent, flags= gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_OK, message_format=text) dialog.connect("response", lambda d,r: d.destroy()) dialog.set_title(_("Error")) dialog.show() # add message to error log if error is not None: keepnote.log_error(error, tracebk) def message(self, text, title="KeepNote", parent=None): """Display a message window""" if parent is None: parent = self.get_current_window() dialog = gtk.MessageDialog( parent, flags= gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format=text) dialog.set_title(title) dialog.run() dialog.destroy() def ask_yes_no(self, text, title="KeepNote", parent=None): """Display a yes/no window""" if parent is None: parent = self.get_current_window() dialog = gtk.MessageDialog(parent, flags= gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO, message_format=text) dialog.set_title(title) response = dialog.run() dialog.destroy() return response == gtk.RESPONSE_YES def quit(self): """Quit the gtk event loop""" keepnote.KeepNote.quit(self) gtk.accel_map_save(get_accel_file()) gtk.main_quit() #=================================== # callbacks def _on_window_close(self, window, event): """Callback for window close event""" if window in self._windows: for ext in self.get_enabled_extensions(): try: if isinstance(ext, keepnote.gui.extension.Extension): ext.on_close_window(window) except Exception, e: log_error(e, sys.exc_info()[2]) # remove window from window list self._windows.remove(window) if window == self._current_window: self._current_window = None # quit app if last window closes if len(self._windows) == 0: self.quit() def _on_window_focus(self, window, event): """Callback for when a window gains focus""" self._current_window = window #==================================== # extension methods def init_extensions_windows(self, windows=None, exts=None): """Initialize all extensions for a window""" if exts is None: exts = self.get_enabled_extensions() if windows is None: windows = self.get_windows() for window in windows: for ext in exts: try: if isinstance(ext, keepnote.gui.extension.Extension): ext.on_new_window(window) except Exception, e: log_error(e, sys.exc_info()[2]) def install_extension(self, filename): """Install a new extension""" if self.ask_yes_no(_("Do you want to install the extension \"%s\"?") % filename, "Extension Install"): # install extension new_exts = keepnote.KeepNote.install_extension(self, filename) # initialize extensions with windows self.init_extensions_windows(exts=new_exts) if len(new_exts) > 0: self.message(_("Extension \"%s\" is now installed.") % filename, _("Install Successful")) return True return False def uninstall_extension(self, ext_key): """Install a new extension""" if self.ask_yes_no(_("Do you want to uninstall the extension \"%s\"?") % ext_key, _("Extension Uninstall")): if keepnote.KeepNote.uninstall_extension(self, ext_key): self.message(_("Extension \"%s\" is now uninstalled.") % ext_key, _("Uninstall Successful")) return True return False keepnote-0.7.8/keepnote/gui/linkcomplete.py0000644000175000017500000001074611523163436017164 0ustar razraz # pygtk imports import pygtk pygtk.require('2.0') import gtk import gobject # keepnote imports from keepnote import unicode_gtk from keepnote.gui.popupwindow import PopupWindow class LinkPicker (gtk.TreeView): def __init__(self, maxwidth=450): gtk.TreeView.__init__(self) self._maxwidth = maxwidth self.set_headers_visible(False) # add column self.column = gtk.TreeViewColumn() self.append_column(self.column) # create a cell renderers self.cell_icon = gtk.CellRendererPixbuf() self.cell_text = gtk.CellRendererText() # add the cells to column self.column.pack_start(self.cell_icon, False) self.column.pack_start(self.cell_text, True) # map cells to columns in treestore self.column.add_attribute(self.cell_icon, 'pixbuf', 0) self.column.add_attribute(self.cell_text, 'text', 1) self.list = gtk.ListStore(gtk.gdk.Pixbuf, str, object) self.set_model(self.list) self.maxlinks = 10 def set_links(self, urls): self.list.clear() for nodeid, url, icon in urls[:self.maxlinks]: self.list.append([icon, url, nodeid]) self.column.queue_resize() w, h = self.size_request() if w > self._maxwidth: self.set_size_request(self._maxwidth, -1) else: self.set_size_request(-1, -1) class LinkPickerPopup (PopupWindow): def __init__(self, parent, maxwidth=100): PopupWindow.__init__(self, parent) self._maxwidth = maxwidth self._link_picker = LinkPicker() self._link_picker.show() self._link_picker.get_selection().connect("changed", self.on_select_changed) self._cursor_move = False self._shown = False # use frame for border frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_ETCHED_IN) frame.add(self._link_picker) frame.show() self.add(frame) def set_links(self, urls): """Set links in popup""" self._link_picker.set_links(urls) if len(urls) == 0: self.hide() self._shown = False else: self.show() self._shown = True def shown(self): """Return True if popup is visible""" return self._shown def on_key_press_event(self, widget, event): """Callback for key press events""" model, sel = self._link_picker.get_selection().get_selected() if event.keyval == gtk.keysyms.Down: # move selection down self._cursor_move = True if sel is None: self._link_picker.set_cursor((0,)) else: i = model.get_path(sel)[0] n = model.iter_n_children(None) if i < n - 1: self._link_picker.set_cursor((i+1,)) return True elif event.keyval == gtk.keysyms.Up: # move selection up self._cursor_move = True if sel is None: n = model.iter_n_children(None) self._link_picker.set_cursor((n-1,)) else: i = model.get_path(sel)[0] if i > 0: self._link_picker.set_cursor((i-1,)) return True elif event.keyval == gtk.keysyms.Return: # accept selection if sel: icon, title, nodeid = model[sel] self.emit("pick-link", unicode_gtk(title), nodeid) return True elif event.keyval == gtk.keysyms.Escape: # discard popup self.set_links([]) return False def on_select_changed(self, treeselect): if not self._cursor_move: model, sel = self._link_picker.get_selection().get_selected() if sel: icon, title, nodeid = model[sel] self.emit("pick-link", unicode_gtk(title), nodeid) self._cursor_move = False #model, paths = treeselect.get_selected_rows() #self.__sel_nodes = [self.model.get_value(self.model.get_iter(path), # self._node_col) # for path in paths] gobject.type_register(LinkPickerPopup) gobject.signal_new("pick-link", LinkPickerPopup, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, object)) keepnote-0.7.8/keepnote/gui/listview.py0000644000175000017500000004356611677423601016355 0ustar razraz""" KeepNote ListView """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import gettext # pygtk imports import pygtk pygtk.require('2.0') import gtk, gobject, pango from gtk import gdk from keepnote.gui import basetreeview from keepnote.gui import \ get_resource, \ get_resource_image, \ get_resource_pixbuf from keepnote.gui.icons import get_node_icon from keepnote.gui import treemodel from keepnote import notebook from keepnote.notebook import NoteBookError import keepnote import keepnote.timestamp _ = keepnote.translate DEFAULT_ATTR_COL_WIDTH = 150 DEFAULT_TITLE_COL_WIDTH = 250 class KeepNoteListView (basetreeview.KeepNoteBaseTreeView): def __init__(self): basetreeview.KeepNoteBaseTreeView.__init__(self) self._sel_nodes = None self._columns_set = False self._current_table = "default" self._col_widths = {} self.time_edit_format = "%Y/%m/%d %H:%M:%S" # configurable callback for setting window status self.on_status = None # selection config self.get_selection().set_mode(gtk.SELECTION_MULTIPLE) # init view self.connect("key-release-event", self.on_key_released) self.connect("button-press-event", self.on_button_press) self.connect("row-expanded", self._on_listview_row_expanded) self.connect("row-collapsed", self._on_listview_row_collapsed) self.connect("columns-changed", self._on_columns_changed) self.set_rules_hint(True) self.set_fixed_height_mode(True) self.set_sensitive(False) # init model self.set_model(gtk.TreeModelSort(treemodel.KeepNoteTreeModel())) self.setup_columns() def load_preferences(self, app_pref, first_open=False): """Load application preferences""" self.set_date_formats(app_pref.get("timestamp_formats")) self.set_rules_hint( app_pref.get("look_and_feel", "listview_rules", default=True)) def save_preferences(self, app_pref): """Save application preferences""" pass def set_notebook(self, notebook): """Set the notebook for listview""" if notebook != self._notebook and self._notebook is not None: self._notebook.get_listeners("table_changed").remove( self._on_table_changed) basetreeview.KeepNoteBaseTreeView.set_notebook(self, notebook) if self.rich_model is not None: self.rich_model.set_root_nodes([]) if notebook: # load notebook prefs self.set_sensitive(True) notebook.get_listeners("table_changed").add(self._on_table_changed) else: self.set_sensitive(False) self.setup_columns() def save(self): """Save notebook preferences""" if self._notebook is None: return self._save_column_widths() self._save_column_order() self._notebook.mark_modified() def _save_column_widths(self): # save attr column widths widths = self._notebook.get_attr("column_widths", {}) for col in self.get_columns(): widths[col.attr] = col.get_width() self._notebook.set_attr("column_widths", widths) def _save_column_order(self): # save column attrs table = self._notebook.attr_tables.get(self._current_table) table.attrs = [col.attr for col in self.get_columns()] # TODO: notify table change def _load_column_widths(self): widths = self._notebook.get_attr("column_widths", {}) for col in self.get_columns(): width = widths.get(col.attr, DEFAULT_ATTR_COL_WIDTH) if col.get_width() != width and width > 0: col.set_fixed_width(width) widths[col.attr] = width def _load_column_order(self): current_attrs = [col.attr for col in self.get_columns()] table = self._notebook.attr_tables.get(self._current_table) if table.attrs != current_attrs: if set(current_attrs) == set(table.attrs): # only order changed lookup = dict((col.attr, col) for col in self.get_columns()) prev = None for attr in table.attrs: col = lookup[attr] self.move_column_after(col, prev) prev = col else: # resetup all columns self.setup_columns() def _on_table_changed(self, notebook, table): if self._notebook == notebook and table == self._current_table: self._load_column_widths() self._load_column_order() #================================== # model and view setup def set_model(self, model): basetreeview.KeepNoteBaseTreeView.set_model(self, model) self.model.connect("sort-column-changed", self._sort_column_changed) def setup_columns(self): self.clear_columns() if self._notebook is None: self._columns_set = False return # TODO: eventually columns may change when ever master node changes attrs = self._notebook.attr_tables.get(self._current_table).attrs # add columns for attr in attrs: col = self._add_column(attr) col.set_reorderable(True) # allow column reordering if attr == self._attr_title: self.title_column = col # add model columns self._add_model_column("order") # NOTE: must create a new TreeModelSort whenever we add new columns # to the rich_model that could be used in sorting # Perhaps something is being cached self.set_model(gtk.TreeModelSort(self.rich_model)) # config columns view self.set_expander_column(self.get_column(0)) # TODO: load correct sorting right away # set default sorting # remember sort per node self.model.set_sort_column_id( self.rich_model.get_column_by_name("order").pos, gtk.SORT_ASCENDING) self.set_reorder(basetreeview.REORDER_ALL) self._columns_set = True def _add_column(self, attr, cell_attr=None): # get attribute definition from notebook attr_def = self._notebook.attr_defs.get(attr) # get datatype if attr_def is not None: datatype = attr_def.datatype col_title = attr_def.name else: datatype = "string" col_title = attr # get/make model column self._add_model_column(attr) # create column view column = gtk.TreeViewColumn() column.attr = attr column.set_property("sizing", gtk.TREE_VIEW_COLUMN_FIXED) column.set_property("resizable", True) column.connect("notify::width", self._on_column_width_change) column.set_min_width(10) column.set_fixed_width( self._notebook.get_attr("column_widths", {}).get( attr, DEFAULT_ATTR_COL_WIDTH)) column.set_title(col_title) # define column sorting attr_sort = attr + "_sort" col = self.rich_model.get_column_by_name(attr_sort) if col: column.set_sort_column_id(col.pos) # add cell renders if attr == self._attr_title: self._add_title_render(column, attr) elif datatype == "timestamp": self._add_text_render(column, attr, editable=True, validator=basetreeview.TextRendererValidator( lambda x: keepnote.timestamp.format_timestamp( x, self.time_edit_format), lambda x: keepnote.timestamp.parse_timestamp( x, self.time_edit_format))) else: self._add_text_render(column, attr) self.append_column(column) return column #============================================= # gui callbacks def is_node_expanded(self, node): return node.get_attr("expanded2", False) def set_node_expanded(self, node, expand): # don't save the expand state of the master node if len(treemodel.get_path_from_node( self.model, node, self.rich_model.get_node_column_pos())) > 1: node.set_attr("expanded2", expand) def _sort_column_changed(self, sortmodel): self._update_reorder() def _update_reorder(self): col_id, sort_dir = self.model.get_sort_column_id() if col_id is None or col_id < 0: col = None else: col = self.rich_model.get_column(col_id) if col is None: # or col.attr == "order" self.model.set_sort_column_id( self.rich_model.get_column_by_name("order").pos, gtk.SORT_ASCENDING) self.set_reorder(basetreeview.REORDER_ALL) else: self.set_reorder(basetreeview.REORDER_FOLDER) def on_key_released(self, widget, event): """Callback for key release events""" # no special processing while editing nodes if self.editing_path: return if event.keyval == gtk.keysyms.Delete: # capture node deletes self.stop_emission("key-release-event") self.emit("delete-node", self.get_selected_nodes()) elif event.keyval == gtk.keysyms.BackSpace and \ event.state & gdk.CONTROL_MASK: # capture goto parent node self.stop_emission("key-release-event") self.emit("goto-parent-node") elif event.keyval == gtk.keysyms.Return and \ event.state & gdk.CONTROL_MASK: # capture goto node self.stop_emission("key-release-event") self.emit("activate-node", None) def on_button_press(self, widget, event): if event.button == 3: # popup menu return self.popup_menu(event.x, event.y, event.button, event.time) if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: model, paths = self.get_selection().get_selected_rows() # double click --> goto node if len(paths) > 0: nodes = [self.model.get_value(self.model.get_iter(x), self.rich_model.get_node_column_pos()) for x in paths] # NOTE: can only view one node self.emit("activate-node", nodes[0]) def is_view_tree(self): # TODO: document this more return self.get_master_node() is not None def _on_columns_changed(self, treeview): """Callback for when columns change order""" if not self._columns_set: return # config columns view col = self.get_column(0) if col: self.set_expander_column(col) if self._notebook: self._save_column_order() self._notebook.get_listeners("table_changed").notify( self._notebook, self._current_table) def _on_column_width_change(self, col, width): width = col.get_width() if (self._notebook and self._col_widths.get(col.attr, None) != width): self._col_widths[col.attr] = width self._save_column_widths() self._notebook.get_listeners("table_changed").notify( self._notebook, self._current_table) #==================================================== # actions def view_nodes(self, nodes, nested=True): # TODO: learn how to deactivate expensive sorting #self.model.set_default_sort_func(None) #self.model.set_sort_column_id(-1, gtk.SORT_ASCENDING) # save sorting if a single node was selected if self._sel_nodes is not None and len(self._sel_nodes) == 1: self.save_sorting(self._sel_nodes[0]) if len(nodes) > 1: nested = False self._sel_nodes = nodes self.rich_model.set_nested(nested) # set master node self.set_master_node(None) # populate model roots = nodes self.rich_model.set_root_nodes(roots) # load sorting if single node is selected if len(nodes) == 1: self.load_sorting(nodes[0], self.model) # expand rows for node in roots: self.expand_to_path(treemodel.get_path_from_node( self.model, node, self.rich_model.get_node_column_pos())) # disable if no roots if len(roots) == 0: self.set_sensitive(False) else: self.set_sensitive(True) # update status self.display_page_count() self.emit("select-nodes", []) def get_root_nodes(self): """Returns the root nodes displayed in listview""" if self.rich_model: return self.rich_model.get_root_nodes() else: return [] def append_node(self, node): # do not allow appending of nodes unless we are masterless if self.get_master_node() is not None: return self.rich_model.append(node) if node.get_attr("expanded2", False): self.expand_to_path(treemodel.get_path_from_node( self.model, node, self.rich_model.get_node_column_pos())) self.set_sensitive(True) # update status #self.display_page_count() def display_page_count(self, npages=None): if npages is None: npages = self.count_pages(self.get_root_nodes()) if npages != 1: self.set_status(_("%d pages") % npages, "stats") else: self.set_status(_("1 page"), "stats") def count_pages(self, roots): # TODO: is there a way to make this faster? def walk(node): npages = 1 if (self.rich_model.get_nested() and (node.get_attr("expanded2", False))): for child in node.get_children(): npages += walk(child) return npages return sum(walk(child) for node in roots for child in node.get_children()) def edit_node(self, page): path = treemodel.get_path_from_node( self.model, page, self.rich_model.get_node_column_pos()) if path is None: # view page first if not in view self.emit("goto-node", page) path = treemodel.get_path_from_node( self.model, page, self.rich_model.get_node_column_pos()) assert path is not None self.set_cursor_on_cell(path, self.title_column, self.title_text, True) path, col = self.get_cursor() self.scroll_to_cell(path) #def cancel_editing(self): # # TODO: add this # pass # #self.cell_text.stop_editing(True) def save_sorting(self, node): """Save sorting information into node""" info_sort, sort_dir = self.model.get_sort_column_id() if sort_dir == gtk.SORT_ASCENDING: sort_dir = 1 else: sort_dir = 0 if info_sort is None or info_sort < 0: col = self.rich_model.get_column_by_name("order") else: col = self.rich_model.get_column(info_sort) if col.attr: node.set_attr("info_sort", col.attr) node.set_attr("info_sort_dir", sort_dir) def load_sorting(self, node, model): """Load sorting information from node""" info_sort = node.get_attr("info_sort", "order") sort_dir = node.get_attr("info_sort_dir", 1) if sort_dir: sort_dir = gtk.SORT_ASCENDING else: sort_dir = gtk.SORT_DESCENDING # default sorting if info_sort == "": info_sort = "order" # TODO: do not rely on *_sort convention for col in self.rich_model.get_columns(): if info_sort == col.attr and col.name.endswith("_sort"): model.set_sort_column_id(col.pos, sort_dir) self._update_reorder() def set_status(self, text, bar="status"): if self.on_status: self.on_status(text, bar=bar) def _on_node_changed_end(self, model, nodes): basetreeview.KeepNoteBaseTreeView._on_node_changed_end(self, model, nodes) # make sure root is always expanded if self.rich_model.get_nested(): # determine root set child = model.iter_children(None) while child is not None: self.expand_row(model.get_path(child), False) child = model.iter_next(child) def _on_listview_row_expanded(self, treeview, it, path): """Callback for row expand""" self.display_page_count() def _on_listview_row_collapsed(self, treeview, it, path): self.display_page_count() keepnote-0.7.8/keepnote/gui/dialog_drag_drop_test.py0000644000175000017500000001067611677423601021022 0ustar razraz""" KeepNote Drag Drop Testing Dialog """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import os # pygtk imports import pygtk pygtk.require('2.0') #from gtk import gdk import gtk.glade # keepnote imports import keepnote from keepnote import get_resource def parse_utf(text): # TODO: lookup the standard way to do this if text[:2] in ('\xff\xfe', '\xfe\xff') or ( len(text) > 1 and text[1] == '\x00') or ( len(text) > 3 and text[3] == '\x00'): return text.decode("utf16") else: return unicode(text, "utf8") class DragDropTestDialog (object): """Drag and drop testing dialog""" def __init__(self, main_window): self.main_window = main_window def on_drag_and_drop_test(self): self.drag_win = gtk.Window(gtk.WINDOW_TOPLEVEL) self.drag_win.connect("delete-event", lambda d,r: self.drag_win.destroy()) self.drag_win.drag_dest_set(0, [], gtk.gdk.ACTION_DEFAULT) self.drag_win.set_default_size(400, 400) vbox = gtk.VBox(False, 0) self.drag_win.add(vbox) self.drag_win.mime = gtk.TextView() vbox.pack_start(self.drag_win.mime, False, True, 0) self.drag_win.editor = gtk.TextView() self.drag_win.editor.connect("drag-motion", self.on_drag_and_drop_test_motion) self.drag_win.editor.connect("drag-data-received", self.on_drag_and_drop_test_data) self.drag_win.editor.connect("paste-clipboard", self.on_drag_and_drop_test_paste) self.drag_win.editor.set_wrap_mode(gtk.WRAP_WORD) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.set_shadow_type(gtk.SHADOW_IN) sw.add(self.drag_win.editor) vbox.pack_start(sw) self.drag_win.show_all() def on_drag_and_drop_test_motion(self, textview, drag_context, x, y, timestamp): buf = self.drag_win.mime.get_buffer() target = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) if target != "": textview.drag_dest_set_target_list([(target, 0, 0)]) def on_drag_and_drop_test_data(self, textview, drag_context, x, y, selection_data, info, eventtime): textview.get_buffer().insert_at_cursor("drag_context = " + str(drag_context.targets) + "\n") textview.stop_emission("drag-data-received") buf = textview.get_buffer() buf.insert_at_cursor("type(sel.data) = " + str(type(selection_data.data)) + "\n") buf.insert_at_cursor("sel.data = " + repr(selection_data.data)[:1000] + "\n") drag_context.finish(False, False, eventtime) def on_drag_and_drop_test_paste(self, textview): clipboard = self.main_window.get_clipboard(selection="CLIPBOARD") targets = clipboard.wait_for_targets() textview.get_buffer().insert_at_cursor("clipboard.targets = " + str(targets)+"\n") textview.stop_emission('paste-clipboard') buf = self.drag_win.mime.get_buffer() target = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) if target != "": clipboard.request_contents(target, self.on_drag_and_drop_test_contents) def on_drag_and_drop_test_contents(self, clipboard, selection_data, data): buf = self.drag_win.editor.get_buffer() data = selection_data.data buf.insert_at_cursor("sel.targets = " + repr(selection_data.get_targets()) + "\n") buf.insert_at_cursor("type(sel.data) = " + str(type(data))+"\n") print "sel.data = " + repr(data)[:1000]+"\n" buf.insert_at_cursor("sel.data = " + repr(data)[:5000]+"\n") keepnote-0.7.8/keepnote/gui/viewer.py0000644000175000017500000001142311677423601015773 0ustar razraz""" KeepNote Base class for a viewer """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import gettext import os import subprocess import traceback import uuid # pygtk imports import pygtk pygtk.require('2.0') from gtk import gdk import gtk import gobject # keepnote imports import keepnote from keepnote import KeepNoteError from keepnote.history import NodeHistory from keepnote import notebook as notebooklib from keepnote.gui.treemodel import iter_children _ = keepnote.translate class Viewer (gtk.VBox): def __init__(self, app, parent, viewerid=None, viewer_name="viewer"): gtk.VBox.__init__(self, False, 0) self._app = app self._main_window = parent self._viewerid = viewerid if viewerid else unicode(uuid.uuid4()) self._viewer_name = viewer_name self._notebook = None self._history = NodeHistory() # register viewer self._main_window.add_viewer(self) def get_id(self): return self._viewerid def set_id(self, viewerid): self._viewerid = viewerid if viewerid else unicode(uuid.uuid4()) def get_name(self): return self._viewer_name def set_notebook(self, notebook): """Sets the current notebook for the viewer""" self._notebook = notebook def get_notebook(self): """Returns the current notebook for the viewer""" return self._notebook def close_notebook(self, notebook): if notebook == self.get_notebook(): self.set_notebook(None) def load_preferences(self, app_pref, first_open): pass def save_preferences(self, app_pref): pass def save(self): pass def undo(self): pass def redo(self): pass def get_editor(self): return None #======================== # node interaction def get_current_node(self): return None def get_selected_nodes(self): return [] def new_node(self, kind, pos, parent=None): if parent is None: parent = self._notebook if pos == "sibling" and parent.get_parent() is not None: index = parent.get_attr("order") + 1 parent = parent.get_parent() else: index = None if kind == notebooklib.CONTENT_TYPE_DIR: node = parent.new_child(notebooklib.CONTENT_TYPE_DIR, notebooklib.DEFAULT_DIR_NAME, index) else: node = notebooklib.new_page( parent, title=notebooklib.DEFAULT_PAGE_NAME, index=index) return node def goto_node(self, node, direct=False): pass def visit_history(self, offset): """Visit a node in the viewer's history""" nodeid = self._history.move(offset) if nodeid is None: return node = self._notebook.get_node_by_id(nodeid) if node: self._history.begin_suspend() self.goto_node(node, False) self._history.end_suspend() #=============================================== # search def start_search_result(self): pass def add_search_result(self, node): pass def end_search_result(self): pass #================================================ # UI management def add_ui(self, window): pass def remove_ui(self, window): pass gobject.type_register(Viewer) gobject.signal_new("error", Viewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, object)) gobject.signal_new("status", Viewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, str)) gobject.signal_new("history-changed", Viewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) gobject.signal_new("window-request", Viewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)) gobject.signal_new("modified", Viewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (bool,)) gobject.signal_new("current-node", Viewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) keepnote-0.7.8/keepnote/gui/richtext/0000755000175000017500000000000011727170645015754 5ustar razrazkeepnote-0.7.8/keepnote/gui/richtext/indent_handler.py0000644000175000017500000003374111677423601021311 0ustar razraz""" KeepNote Richtext indentation handler """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # pygtk imports import pygtk pygtk.require('2.0') import gtk, gobject, pango from gtk import gdk # keepnote imports import keepnote from keepnote.undo import UndoStack from .textbuffer_tools import \ move_to_start_of_line, \ move_to_end_of_line, \ paragraph_iter, \ iter_buffer_contents, \ buffer_contents_iter_to_offset, \ normalize_tags, \ insert_buffer_contents, \ buffer_contents_apply_tags # import tags from .richtext_tags import \ RichTextIndentTag, \ RichTextBulletTag from .richtextbasebuffer import \ RichTextBaseBuffer, \ add_child_to_buffer, \ get_paragraph, \ get_paragraphs_selected # string for bullet points BULLET_STR = u"\u2022 " #============================================================================= class IndentHandler (object): """This object will manage the indentation of paragraphs in a TextBuffer with RichTextTags """ def __init__(self, textbuffer): self._buf = textbuffer self._indent_update = False self._indent_update_start = self._buf.create_mark("indent_update_start", self._buf.get_start_iter(), True) self._indent_update_end = self._buf.create_mark("indent_update_end", self._buf.get_end_iter(), False) self._bullet_mark = self._buf.create_mark("bullet", self._buf.get_start_iter(), True) self._delete_start = self._buf.create_mark( None, self._buf.get_start_iter(), True) self._delete_end = self._buf.create_mark( None, self._buf.get_start_iter(), True) self._delete_queued = False def change_indent(self, start, end, change): """Change indentation level""" # determine region if start is None or end is None: start, end = get_paragraphs_selected(self._buf) self._buf.begin_user_action() # loop through paragraphs for pos in paragraph_iter(self._buf, start, end): par_end = pos.copy() par_end.forward_line() indent, par_indent = self.get_indent(pos) if indent + change > 0: tag = self._buf.tag_table.lookup( RichTextIndentTag.tag_name(indent + change, par_indent)) self._buf.clear_tag_class(tag, pos, par_end) self._buf.apply_tag_selected(tag, pos, par_end) elif indent > 0: # remove indent and possible bullets self._buf.clear_tag_class( self._buf.tag_table.lookup( RichTextIndentTag.tag_name(indent, par_indent)), pos, par_end) self._remove_bullet(pos) else: # do nothing pass self._buf.end_user_action() def _ensure_par_newline(self, start, end): # trying to insert paragraph at end of buffer if start.compare(end) == 0: # insert a newline self._buf.insert_at_cursor("\n") end = self._buf.get_end_iter() start = end.copy() start.backward_line() self._buf.place_cursor(start) return start, end def toggle_bullet_list(self, par_type=None): """Toggle the state of a bullet list""" self._buf.begin_user_action() # round selection to nearest paragraph start, end = get_paragraphs_selected(self._buf) start, end = self._ensure_par_newline(start, end) # toggle bullet presence if par_type is None: # are all paragraphs bulleted? all_bullets = True for pos in paragraph_iter(self._buf, start, end): if self.get_indent(pos)[1] != "bullet": all_bullets = False break # choose opposite new par_type if all_bullets: par_type = "none" else: par_type = "bullet" # set each paragraph's bullet status for pos in paragraph_iter(self._buf, start, end): par_end = pos.copy() par_end.forward_line() self._set_bullet_list_paragraph(pos, par_end, par_type) self._buf.end_user_action() def _set_bullet_list_paragraph(self, par_start, par_end, par_type): """Set the state of a bullet list for a paragraph""" # start indent if it is not present indent, _ = self.get_indent(par_start) if indent == 0: indent = 1 # apply indent to whole paragraph indent_tag = self._buf.tag_table.lookup( RichTextIndentTag.tag_name(indent, par_type)) self._buf.clear_tag_class(indent_tag, par_start, par_end) self._buf.apply_tag(indent_tag, par_start, par_end) self._queue_update_indentation(par_start, par_end) def _insert_bullet(self, par_start, indent_tag): """Insert a bullet point at the begining of the paragraph""" if self.par_has_bullet(par_start): return par_start end = par_start.copy() end.forward_char() self._buf.begin_user_action() # insert text self._buf.move_mark(self._bullet_mark, par_start) self._buf.insert(par_start, BULLET_STR) # apply tag to just the bullet point par_start = self._buf.get_iter_at_mark(self._bullet_mark) bullet_end = par_start.copy() bullet_end.forward_chars(len(BULLET_STR)) bullet_tag = self._buf.tag_table.bullet_tag self._buf.apply_tag(bullet_tag, par_start, bullet_end) self._buf.apply_tag(indent_tag, par_start, bullet_end) self._buf.end_user_action() return par_start def _remove_bullet(self, par_start): """Remove a bullet point from the paragraph""" self._buf.begin_user_action() bullet_end = par_start.copy() bullet_end.forward_chars(len(BULLET_STR)) self._buf.move_mark(self._bullet_mark, par_start) if par_start.get_text(bullet_end) == BULLET_STR: bullet_tag = self._buf.tag_table.bullet_tag self._buf.remove_tag(bullet_tag, par_start, bullet_end) self._buf.delete(par_start, bullet_end) self._buf.end_user_action() return self._buf.get_iter_at_mark(self._bullet_mark) def par_has_bullet(self, par_start): """Returns True if paragraph starts with bullet point""" bullet_end = par_start.copy() bullet_end.forward_chars(len(BULLET_STR)) return par_start.get_text(bullet_end) == BULLET_STR def on_paragraph_merge(self, start, end): """Callback for when paragraphs have merged""" self._queue_update_indentation(start, end) def on_paragraph_split(self, start, end): """Callback for when paragraphs have split""" self._queue_update_indentation(start, end) def on_paragraph_change(self, start, end): """Callback for when the tags of a paragraph changes""" start = move_to_start_of_line(start.copy()) end = move_to_end_of_line(end.copy()) self._queue_update_indentation(start, end) def _queue_update_indentation(self, start, end): """Queues an indentation update""" if not self._indent_update: # first update self._indent_update = True self._buf.move_mark(self._indent_update_start, start) self._buf.move_mark(self._indent_update_end, end) else: # expand update region a = self._buf.get_iter_at_mark(self._indent_update_start) b = self._buf.get_iter_at_mark(self._indent_update_end) if start.compare(a) == -1: self._buf.move_mark(self._indent_update_start, start) if end.compare(b) == 1: self._buf.move_mark(self._indent_update_end, end) def is_insert_allowed(self, it, text=""): """Returns True if insertion is allowed at iter 'it'""" # check to make sure insert is not in front of bullet it2 = it.copy() it2 = move_to_start_of_line(it2) if self.par_has_bullet(it2): if it.starts_line() and text.endswith("\n"): return True else: return not self.within_bullet(it) else: return True def update_indentation(self): """Ensure the indentation tags between start and end are up to date""" # fixup indentation tags # The general rule is that the indentation at the start of # each paragraph determines the indentation of the rest # of the paragraph # if no indentation requests are queued then do nothing if not self._indent_update: return self._indent_update = False self._buf.begin_user_action() self._buf.begin_noninteractive() # get range of updating pos = self._buf.get_iter_at_mark(self._indent_update_start) end = self._buf.get_iter_at_mark(self._indent_update_end) pos = move_to_start_of_line(pos) end.forward_line() # iterate through the paragraphs that need updating for pos in paragraph_iter(self._buf, pos, end): par_end = pos.copy() par_end.forward_line() indent_tag = self.get_indent_tag(pos) # remove bullets mid paragraph it = pos.copy() it.forward_char() while True: match = it.forward_search(BULLET_STR, 0, par_end) if not match: it.backward_char() pos, par_end = get_paragraph(it) break self._buf.move_mark(self._indent_update_start, match[0]) self._buf.delete(match[0], match[1]) it = self._buf.get_iter_at_mark(self._indent_update_start) par_end = it.copy() par_end.forward_line() # check indentation if indent_tag is None: # remove all indent tags # TODO: RichTextBaseBuffer function self._buf.clear_tag_class( self._buf.tag_table.lookup( RichTextIndentTag.tag_name(1)), pos, par_end) # remove bullets par_type = "none" else: self._buf.clear_tag_class(indent_tag, pos, par_end) self._buf.apply_tag(indent_tag, pos, par_end) # check for bullets par_type = indent_tag.get_par_indent() # check paragraph type if par_type == "bullet": # ensure proper bullet is in place pos = self._insert_bullet(pos, indent_tag) elif par_type == "none": # remove bullets pos = self._remove_bullet(pos) else: raise Exception("unknown par_type '%s'" % par_type) #self._updating = False self._buf.end_noninteractive() self._buf.end_user_action() #========================================== # query and navigate paragraphs/indentation def get_indent(self, it=None): """Returns the indentation level at iter 'it'""" tag = self.get_indent_tag(it) if tag: return tag.get_indent(), tag.get_par_indent() else: return 0, "none" def get_indent_tag(self, it=None): """Returns the indentation level at iter 'it'""" if not it: it = self._buf.get_iter_at_mark(self._buf.get_insert()) it2 = it.copy() if not it2.ends_line(): it2.forward_char() for tag in it2.get_tags(): if isinstance(tag, RichTextIndentTag): return tag return None def starts_par(self, it): """Returns True if iter 'it' starts paragraph (or is within bullet)""" if it.starts_line(): return True else: # handle case where it is within bullet it2 = it.copy() it2 = move_to_start_of_line(it2) return self.par_has_bullet(it2) and \ it.get_offset() <= it2.get_offset() + len(BULLET_STR) def within_bullet(self, it): """Returns True if iter 'it' is within bullet phrase""" if it.starts_line(): return True else: # handle case where it is within bullet it2 = it.copy() it2 = move_to_start_of_line(it2) return self.par_has_bullet(it2) and \ it.get_offset() < it2.get_offset() + len(BULLET_STR) def _get_cursor(self): return self._buf.get_iter_at_mark(self._buf.get_insert()) keepnote-0.7.8/keepnote/gui/richtext/richtextbuffer.py0000644000175000017500000007746711677423601021374 0ustar razraz""" KeepNote Richtext buffer class """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import sys, os, tempfile, re import urllib2 from itertools import chain # pygtk imports import pygtk pygtk.require('2.0') import gtk, gobject, pango from gtk import gdk # TODO: remove # keepnote imports import keepnote # textbuffer imports from .textbuffer_tools import \ move_to_start_of_line, \ move_to_end_of_line, \ iter_buffer_contents, \ iter_buffer_anchors, \ buffer_contents_iter_to_offset, \ normalize_tags, \ insert_buffer_contents, \ buffer_contents_apply_tags # richtext imports from .richtextbasebuffer import \ RichTextBaseBuffer, \ add_child_to_buffer, \ RichTextAnchor from .indent_handler import IndentHandler from .font_handler import \ FontHandler, RichTextBaseFont # richtext tags imports from .richtext_tags import \ RichTextTagTable, \ RichTextTag, \ RichTextModTag, \ RichTextJustifyTag, \ RichTextFamilyTag, \ RichTextSizeTag, \ RichTextFGColorTag, \ RichTextBGColorTag, \ RichTextIndentTag, \ RichTextBulletTag, \ RichTextLinkTag, \ color_to_string, \ get_attr_size # these tags will not be enumerated by iter_buffer_contents IGNORE_TAGS = set(["gtkspell-misspelled"]) # default maximum undo levels MAX_UNDOS = 100 # string for bullet points BULLET_STR = u"\u2022 " # NOTE: use a blank user agent for downloading images # many websites refuse the python user agent USER_AGENT = "" # default color of a richtext background DEFAULT_BGCOLOR = (65535, 65535, 65535) DEFAULT_HR_COLOR = (0, 0, 0) def ignore_tag(tag): return tag.get_property("name") in IGNORE_TAGS # TODO: Maybe move somewhere more general def download_file(url, filename): """Download a url to a file 'filename'""" try: # open url and download image opener = urllib2.build_opener() request = urllib2.Request(url) request.add_header('User-Agent', USER_AGENT) infile = opener.open(request) outfile = open(filename, "wb") outfile.write(infile.read()) outfile.close() return True except Exception, e: return False #============================================================================= # RichText child objects # TODO: remove init signals class BaseWidget (gtk.EventBox): """Widgets in RichTextBuffer must support this interface""" def __init__(self): gtk.EventBox.__init__(self) # TODO: will this be configurable? # set to white background self.modify_bg(gtk.STATE_NORMAL, gdk.Color(*DEFAULT_BGCOLOR)) # gtk.STATE_ACTIVE # gtk.STATE_PRELIGHT # gtk.STATE_SELECTED # gtk.STATE_INSENSITIVE def highlight(self): pass def unhighlight(self): pass def show(self): gtk.EventBox.show_all(self) #gobject.type_register(BaseWidget) #gobject.signal_new("init", BaseWidget, gobject.SIGNAL_RUN_LAST, # gobject.TYPE_NONE, ()) class RichTextSep (BaseWidget): """Separator widget for a Horizontal Rule""" def __init__(self): BaseWidget.__init__(self) self._sep = gtk.HSeparator() self.add(self._sep) self._size = None self._sep.modify_bg(gtk.STATE_NORMAL, gdk.Color(* DEFAULT_HR_COLOR)) self._sep.modify_fg(gtk.STATE_NORMAL, gdk.Color(* DEFAULT_HR_COLOR)) self.connect("size-request", self._on_resize) self.connect("parent-set", self._on_parent_set) self._resizes_id = None #pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, width, height) #pixbuf.fill(color) #self._widget.set_from_pixbuf(pixbuf) #self._widget.img.set_padding(0, padding) def _on_parent_set(self, widget, old_parent): """Callback for changing parent""" if old_parent: old_parent.disconnect(self._resize_id) if self.get_parent(): self._resize_id = self.get_parent().connect("size-allocate", self._on_size_change) def _on_size_change(self, widget, req): """callback for parent's changed size allocation""" w, h = self.get_desired_size() self.set_size_request(w, h) def _on_resize(self, sep, req): """Callback for widget resize""" w, h = self.get_desired_size() req.width = w req.height = h def get_desired_size(self): """Returns the desired size""" HR_HORIZONTAL_MARGIN = 20 HR_VERTICAL_MARGIN = 10 self._size = (self.get_parent().get_allocation().width - HR_HORIZONTAL_MARGIN, HR_VERTICAL_MARGIN) return self._size class RichTextHorizontalRule (RichTextAnchor): def __init__(self): RichTextAnchor.__init__(self) #self.add_view(None) def add_view(self, view): self._widgets[view] = RichTextSep() self._widgets[view].show() return self._widgets[view] def copy(self): return RichTextHorizontalRule() class BaseImage (BaseWidget): """Subclasses gtk.Image to make an Image Widget that can be used within RichTextViewS""" def __init__(self, *args, **kargs): BaseWidget.__init__(self) self._img = gtk.Image(*args, **kargs) self._img.show() self.add(self._img) def highlight(self): self.drag_highlight() def unhighlight(self): self.drag_unhighlight() def set_from_pixbuf(self, pixbuf): self._img.set_from_pixbuf(pixbuf) def set_from_stock(self, stock, size): self._img.set_from_stock(stock, size) def get_image_format(filename): """Returns the image format for a filename""" f, ext = os.path.splitext(filename) ext = ext.replace(u".", "").lower() if ext == "jpg": ext = "jpeg" return ext class RichTextImage (RichTextAnchor): """An Image child widget in a RichTextView""" def __init__(self): RichTextAnchor.__init__(self) self._filename = None self._download = False self._pixbuf = None self._pixbuf_original = None self._size = [None, None] self._save_needed = False def __del__(self): for widget in self._widgets: widget.disconnect("destroy") widget.disconnect("button-press-event") def add_view(self, view): self._widgets[view] = BaseImage() self._widgets[view].connect("destroy", self._on_image_destroy) self._widgets[view].connect("button-press-event", self._on_clicked) if self._pixbuf is not None: self._widgets[view].set_from_pixbuf(self._pixbuf) return self._widgets[view] def is_valid(self): """Did the image successfully load an image""" return self._pixbuf is not None def set_filename(self, filename): """Sets the filename used for saving image""" self._filename = filename def get_filename(self): """Returns the filename used for saving image""" return self._filename def get_original_pixbuf(self): """Returns the pixbuf of the image at its original size (no scaling)""" return self._pixbuf_original def set_save_needed(self, save): """Sets whether image needs to be saved to disk""" self._save_needed = save def save_needed(self): """Returns True if image needs to be saved to disk""" return self._save_needed def write(self, filename): """Write image to file""" # TODO: make more checks on saving if self._pixbuf: ext = get_image_format(filename) self._pixbuf_original.save(filename, ext) self._save_needed = False def write_stream(self, stream, filename="image.png"): """ Write image to stream 'filename' is used to infer picture format only. """ def write(buf): stream.write(buf) return True format = get_image_format(filename) self._pixbuf_original.save_to_callback(write, format) self._save_needed = False def copy(self): """Returns a new copy of the image""" img = RichTextImage() img.set_filename(self._filename) img._size = list(self.get_size()) if self._pixbuf: img._pixbuf = self._pixbuf img._pixbuf_original = self._pixbuf_original else: img.set_no_image() return img #===================================================== # set image def set_from_file(self, filename): """Sets the image from a file""" # TODO: remove this assumption (perhaps save full filename, and # caller will basename() if necessary if self._filename is None: self._filename = os.path.basename(filename) try: self._pixbuf_original = gdk.pixbuf_new_from_file(filename) except Exception: # use missing image instead self.set_no_image() else: # successful image load, set its size self._pixbuf = self._pixbuf_original if self.is_size_set(): self.scale(self._size[0], self._size[1], False) for widget in self.get_all_widgets().itervalues(): widget.set_from_pixbuf(self._pixbuf) def set_from_stream(self, stream): loader = gtk.gdk.PixbufLoader() try: loader.write(stream.read()) loader.close() self._pixbuf_original = loader.get_pixbuf() except Exception: self.set_no_image() else: # successful image load, set its size self._pixbuf = self._pixbuf_original if self.is_size_set(): self.scale(self._size[0], self._size[1], False) for widget in self.get_all_widgets().itervalues(): widget.set_from_pixbuf(self._pixbuf) def set_no_image(self): """Set the 'no image' icon""" for widget in self.get_all_widgets().itervalues(): widget.set_from_stock(gtk.STOCK_MISSING_IMAGE, gtk.ICON_SIZE_MENU) self._pixbuf_original = None self._pixbuf = None def set_from_pixbuf(self, pixbuf, filename=None): """Set the image from a pixbuf""" if filename is not None: self._filename = filename self._pixbuf = pixbuf self._pixbuf_original = pixbuf if self.is_size_set(): self.scale(self._size[0], self._size[1], True) else: for widget in self.get_all_widgets().itervalues(): widget.set_from_pixbuf(self._pixbuf) def set_from_url(self, url, filename=None): """Set image by url""" imgfile = None try: # make local temp file f, imgfile = tempfile.mkstemp("", "image") os.close(f) if download_file(url, imgfile): self.set_from_file(imgfile) if filename is not None: self.set_filename(filename) else: self.set_filename(url) else: raise Exception("Could not download file") except Exception: self.set_no_image() # remove tempfile if imgfile and os.path.exists(imgfile): os.remove(imgfile) #====================== # Image Scaling def get_size(self, actual_size=False): """Returns the size of the image actual_size -- if True, None values will be replaced by original size """ if actual_size: if self._pixbuf_original is not None: w, h = self._size if w is None: w = self._pixbuf_original.get_width() if h is None: h = self._pixbuf_original.get_height() return [w, h] else: return [0, 0] else: return self._size def get_original_size(self): return [self._pixbuf_original.get_width(), self._pixbuf_original.get_height()] def is_size_set(self): return self._size[0] is not None or self._size[1] is not None def scale(self, width, height, set_widget=True): """Scale the image to a new width and height""" if not self.is_valid: return self._size = [width, height] if not self.is_size_set(): # use original image size if self._pixbuf != self._pixbuf_original: self._pixbuf = self._pixbuf_original if self._pixbuf is not None and set_widget: for widget in self.get_all_widgets().itervalues(): widget.set_from_pixbuf(self._pixbuf) elif self._pixbuf_original is not None: # perform scaling width2 = self._pixbuf_original.get_width() height2 = self._pixbuf_original.get_height() if width is None: factor = height / float(height2) width = int(factor * width2) if height is None: factor = width / float(width2) height = int(factor * height2) self._pixbuf = self._pixbuf_original.scale_simple( width, height, gtk.gdk.INTERP_BILINEAR) if set_widget: for widget in self.get_all_widgets().itervalues(): widget.set_from_pixbuf(self._pixbuf) if self._buffer is not None: self._buffer.set_modified(True) #========================== # GUI callbacks def _on_image_destroy(self, widget): for key, value in self._widgets.iteritems(): if value == widget: del self._widgets[key] break def _on_clicked(self, widget, event): """Callback for when image is clicked""" if event.button == 1: # left click selects image widget.grab_focus() #self._widgets[None].grab_focus() self.emit("selected") if event.type == gtk.gdk._2BUTTON_PRESS: # double left click activates image self.emit("activated") return True elif event.button == 3: # right click presents popup menu self.emit("selected") self.emit("popup-menu", event.button, event.time) return True #============================================================================= # font class RichTextFont (RichTextBaseFont): """Class for representing a font in a simple way""" def __init__(self): RichTextBaseFont.__init__(self) # TODO: remove hard-coding self.mods = {} self.justify = "left" self.family = "Sans" self.size = 10 self.fg_color = "" self.bg_color = "" self.indent = 0 self.par_type = "none" self.link = None def set_font(self, attr, tags, current_tags, tag_table): # set basic font attr RichTextBaseFont.set_font(self, attr, tags, current_tags, tag_table) font = attr.font if font: # get font family self.family = font.get_family() # get size in points (get_size() returns pango units) #PIXELS_PER_PANGO_UNIT = 1024 #self.size = font.get_size() // PIXELS_PER_PANGO_UNIT self.size = get_attr_size(attr) weight = font.get_weight() style = font.get_style() else: # TODO: replace this hard-coding self.family = "Sans" self.size = 10 weight = pango.WEIGHT_NORMAL style = pango.STYLE_NORMAL # get colors self.fg_color = color_to_string(attr.fg_color) self.bg_color = color_to_string(attr.bg_color) mod_class = tag_table.get_tag_class("mod") tag_set = set(tags) # set modifications (current tags override) self.mods = {} for tag in mod_class.tags: self.mods[tag.get_property("name")] = (tag in current_tags or tag in tag_set) self.mods["tt"] = (self.mods["tt"] or self.family == "Monospace") # set justification self.justify = RichTextJustifyTag.justify2name[attr.justification] # current tags override for family and size for tag in current_tags: if isinstance(tag, RichTextJustifyTag): self.justify = tag.get_justify() elif isinstance(tag, RichTextFamilyTag): self.family = tag.get_family() elif isinstance(tag, RichTextSizeTag): self.size = tag.get_size() elif isinstance(tag, RichTextFGColorTag): self.fg_color = tag.get_color() elif isinstance(tag, RichTextBGColorTag): self.bg_color = tag.get_color() # set indentation info for tag in chain(tags, current_tags): if isinstance(tag, RichTextIndentTag): self.indent = tag.get_indent() self.par_type = tag.get_par_indent() elif isinstance(tag, RichTextLinkTag): self.link = tag #============================================================================= class RichTextBuffer (RichTextBaseBuffer): """ TextBuffer specialized for rich text editing It builds upon the features of RichTextBaseBuffer - maintains undo/redo stacks Additional Features - manages specific child widget actions - images - horizontal rule - manages editing of indentation levels and bullet point lists - manages "current font" behavior """ def __init__(self, table=RichTextTagTable()): RichTextBaseBuffer.__init__(self, table) # indentation handler self._indent = IndentHandler(self) self.connect("ending-user-action", lambda w: self._indent.update_indentation()) # font handler self.font_handler = FontHandler(self) self.font_handler.set_font_class(RichTextFont) self.font_handler.connect("font-change", lambda w, font: self.emit("font-change", font)) # set of all anchors in buffer self._anchors = set() self._anchors_highlighted = set() #self._child_uninit = set() # anchors that still need to be added, # they are defferred because textview was not available at insert-time self._anchors_deferred = set() def clear(self): """Clear buffer contents""" RichTextBaseBuffer.clear(self) self._anchors.clear() self._anchors_highlighted.clear() self._anchors_deferred.clear() def insert_contents(self, contents, it=None): """Inserts a content stream into the TextBuffer at iter 'it'""" if it is None: it = self.get_insert_iter() self.begin_user_action() insert_buffer_contents(self, it, contents, add_child_to_buffer, lookup_tag=lambda name: self.tag_table.lookup(name)) self.end_user_action() def copy_contents(self, start, end): """Return a content stream for copying from iter start and end""" contents = iter(iter_buffer_contents(self, start, end, ignore_tag)) # remove regions that can't be copied for item in contents: # NOTE: item = (kind, it, param) if item[0] == "begin" and not item[2].can_be_copied(): end_tag = item[2] while not (item[0] == "end" and item[2] == end_tag): item = contents.next() if item[0] not in ("text", "anchor") and \ item[2] != end_tag: yield item continue yield item def on_selection_changed(self): """Callback for when selection changes""" self.highlight_children() def on_paragraph_split(self, start, end): """Callback for when paragraphs split""" if self.is_interactive(): self._indent.on_paragraph_split(start, end) def on_paragraph_merge(self, start, end): """Callback for when paragraphs merge""" if self.is_interactive(): self._indent.on_paragraph_merge(start, end) def on_paragraph_change(self, start, end): """Callback for when paragraph type changes""" if self.is_interactive(): self._indent.on_paragraph_change(start, end) def is_insert_allowed(self, it, text=""): """Returns True if insertion is allowed at iter 'it'""" # ask the indentation manager whether the insert is allowed return self._indent.is_insert_allowed(it, text) and \ it.can_insert(True) def _on_delete_range(self, textbuffer, start, end): # TODO: should I add something like this back? # let indent manager prepare the delete #if self.is_interactive(): # self._indent.prepare_delete_range(start, end) # call super class RichTextBaseBuffer._on_delete_range(self, textbuffer, start, end) # deregister any deleted anchors for kind, offset, param in iter_buffer_contents( self, start, end, ignore_tag): if kind == "anchor": child = param[0] self._anchors.remove(child) if child in self._anchors_highlighted: self._anchors_highlighted.remove(child) #========================================= # indentation interface def indent(self, start=None, end=None): """Indent paragraph level""" self._indent.change_indent(start, end, 1) def unindent(self, start=None, end=None): """Unindent paragraph level""" self._indent.change_indent(start, end, -1) def starts_par(self, it): """Returns True if iter 'it' starts a paragraph""" return self._indent.starts_par(it) def toggle_bullet_list(self, par_type=None): """Toggle the state of a bullet list""" self._indent.toggle_bullet_list(par_type) def get_indent(self, it=None): return self._indent.get_indent(it) #=============================================== # font handler interface def update_current_tags(self, action): return self.font_handler.update_current_tags(action) def set_default_attr(self, attr): return self.font_handler.set_default_attr(attr) def get_default_attr(self): return self.font_handler.get_default_attr() def get_current_tags(self): return self.font_handler.get_current_tags() def set_current_tags(self, tags): return self.font_handler.set_current_tags(tags) def can_be_current_tag(self, tag): return self.font_handler.can_be_current_tag(tag) def toggle_tag_selected(self, tag, start=None, end=None): return self.font_handler.toggle_tag_selected(tag, start, end) def apply_tag_selected(self, tag, start=None, end=None): return self.font_handler.apply_tag_selected(tag, start, end) def remove_tag_selected(self, tag, start=None, end=None): return self.font_handler.remove_tag_selected(tag, start, end) def remove_tag_class_selected(self, tag, start=None, end=None): return self.font_handler.remove_tag_class_selected(tag, start, end) def clear_tag_class(self, tag, start, end): return self.font_handler.clear_tag_class(tag, start, end) def clear_current_tag_class(self, tag): return self.font_handler.clear_current_tag_class(tag) def get_font(self, font=None): return self.font_handler.get_font(font) #============================================================ # child actions def add_child(self, it, child): # preprocess child if isinstance(child, RichTextImage): self._determine_image_name(child) # setup child self._anchors.add(child) child.set_buffer(self) child.connect("activated", self._on_child_activated) child.connect("selected", self._on_child_selected) child.connect("popup-menu", self._on_child_popup_menu) self.insert_child_anchor(it, child) # let textview, if attached know we added a child self._anchors_deferred.add(child) self.emit("child-added", child) def add_deferred_anchors(self, textview): """Add anchors that were deferred""" for child in self._anchors_deferred: # only add anchor if it is still present (hasn't been deleted) if child in self._anchors: self._add_child_at_anchor(child, textview) self._anchors_deferred.clear() def _add_child_at_anchor(self, child, textview): # skip children whose insertion was rejected if child.get_deleted(): return # TODO: eventually use real view widget = child.add_view(textview) textview.add_child_at_anchor(widget, child) child.show() def insert_image(self, image, filename="image.png"): """Inserts an image into the textbuffer at current position""" # set default filename if image.get_filename() is None: image.set_filename(filename) # insert image into buffer self.begin_user_action() it = self.get_insert_iter() self.add_child(it, image) image.show() self.end_user_action() def insert_hr(self): """Insert Horizontal Rule""" self.begin_user_action() it = self.get_insert_iter() hr = RichTextHorizontalRule() self.add_child(it, hr) self.end_user_action() #=================================== # Image management def get_image_filenames(self): filenames = [] for child in self._anchors: if isinstance(child, RichTextImage): filenames.append(child.get_filename()) return filenames def _determine_image_name(self, image): """Determines image filename""" if self._is_new_pixbuf(image.get_original_pixbuf()): filename, ext = os.path.splitext(image.get_filename()) filenames = self.get_image_filenames() filename2 = keepnote.get_unique_filename_list(filenames, filename, ext) image.set_filename(filename2) image.set_save_needed(True) def _is_new_pixbuf(self, pixbuf): # cannot tell if pixbuf is new because it is not loaded if pixbuf is None: return False for child in self._anchors: if isinstance(child, RichTextImage): if pixbuf == child.get_original_pixbuf(): return False return True #============================================= # links def get_tag_region(self, it, tag): """ Get the start and end TextIters for tag occuring at TextIter it Assumes tag occurs at TextIter it """ # get bounds of link tag start = it.copy() if tag not in it.get_toggled_tags(True): start.backward_to_tag_toggle(tag) end = it.copy() if tag not in it.get_toggled_tags(False): end.forward_to_tag_toggle(tag) return start, end def get_link(self, it=None): if it is None: # use cursor sel = self.get_selection_bounds() if len(sel) > 0: it = sel[0] else: it = self.get_insert_iter() for tag in chain(it.get_tags(), it.get_toggled_tags(False)): if isinstance(tag, RichTextLinkTag): start, end = self.get_tag_region(it, tag) return tag, start, end return None, None, None def set_link(self, url, start, end): if url is None: tag = self.tag_table.lookup(RichTextLinkTag.tag_name("")) self.font_handler.clear_tag_class(tag, start, end) return None else: tag = self.tag_table.lookup(RichTextLinkTag.tag_name(url)) self.font_handler.apply_tag_selected(tag, start, end) return tag #============================================== # Child callbacks def _on_child_selected(self, child): """Callback for when child object is selected Make sure buffer knows the selection """ it = self.get_iter_at_child_anchor(child) end = it.copy() end.forward_char() self.select_range(it, end) def _on_child_activated(self, child): """Callback for when child is activated (e.g. double-clicked)""" # forward callback to listeners (textview) self.emit("child-activated", child) def _on_child_popup_menu(self, child, button, activate_time): """Callback for when child's menu is visible""" # forward callback to listeners (textview) self.emit("child-menu", child, button, activate_time) def highlight_children(self): """Highlight any children that are within selection range""" sel = self.get_selection_bounds() focus = None if len(sel) > 0: highlight = set(x[2][0] for x in iter_buffer_anchors(self, sel[0], sel[1])) for child in self._anchors_highlighted: if child not in highlight: child.unhighlight() for child in highlight: child.highlight() self._anchors_highlighted = highlight else: # no selection, unselect all children for child in self._anchors_highlighted: child.unhighlight() self._anchors_highlighted.clear() gobject.type_register(RichTextBuffer) gobject.signal_new("child-added", RichTextBuffer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) gobject.signal_new("child-activated", RichTextBuffer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) gobject.signal_new("child-menu", RichTextBuffer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object, object, object)) gobject.signal_new("font-change", RichTextBuffer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)) keepnote-0.7.8/keepnote/gui/richtext/richtextbasebuffer.py0000644000175000017500000003076011677423601022210 0ustar razraz""" KeepNote Richtext buffer base class """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import sys # pygtk imports import pygtk pygtk.require('2.0') import gtk, gobject, pango from gtk import gdk # import textbuffer tools from .textbuffer_tools import \ get_paragraph, \ get_paragraphs_selected from .undo_handler import \ UndoHandler, \ Action, \ InsertAction, \ DeleteAction, \ InsertChildAction, \ TagAction # richtext imports from .richtextbase_tags import \ RichTextBaseTagTable, \ RichTextTagClass, \ RichTextTag def add_child_to_buffer(textbuffer, it, anchor): textbuffer.add_child(it, anchor) #============================================================================= class RichTextAnchor (gtk.TextChildAnchor): """Base class of all anchor objects in a RichTextView""" def __init__(self): gtk.TextChildAnchor.__init__(self) self._widgets = {} #None: None} self._buffer = None def add_view(self, view): return None def get_widget(self, view=None): return self._widgets[view] def get_all_widgets(self): return self._widgets def show(self): for widget in self._widgets.itervalues(): if widget: widget.show() def set_buffer(self, buf): self._buffer = buf def get_buffer(self): return self._buffer def copy(self): anchor = RichTextAnchor() anchor.set_buffer(self._buffer) return anchor def highlight(self): for widget in self._widgets.itervalues(): if widget: widget.highlight() def unhighlight(self): for widget in self._widgets.itervalues(): if widget: widget.unhighlight() gobject.type_register(RichTextAnchor) gobject.signal_new("selected", RichTextAnchor, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) gobject.signal_new("activated", RichTextAnchor, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) gobject.signal_new("popup-menu", RichTextAnchor, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (int, object)) gobject.signal_new("init", RichTextAnchor, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) class RichTextBaseBuffer (gtk.TextBuffer): """Basic RichTextBuffer with the following features - maintains undo/redo stacks """ def __init__(self, tag_table=RichTextBaseTagTable()): gtk.TextBuffer.__init__(self, tag_table) tag_table.add_textbuffer(self) # undo handler self._undo_handler = UndoHandler(self) self._undo_handler.after_changed.add(self.on_after_changed) self.undo_stack = self._undo_handler.undo_stack # insert mark tracking self._insert_mark = self.get_insert() self._old_insert_mark = self.create_mark( None, self.get_iter_at_mark(self._insert_mark), True) self._user_action_ending = False self._noninteractive = 0 # setup signals self._signals = [ # local events self.connect("begin_user_action", self._on_begin_user_action), self.connect("end_user_action", self._on_end_user_action), self.connect("mark-set", self._on_mark_set), self.connect("insert-text", self._on_insert_text), self.connect("insert-child-anchor", self._on_insert_child_anchor), self.connect("apply-tag", self._on_apply_tag), self.connect("remove-tag", self._on_remove_tag), self.connect("delete-range", self._on_delete_range), # undo handler events self.connect("insert-text", self._undo_handler.on_insert_text), self.connect("delete-range", self._undo_handler.on_delete_range), self.connect("insert-pixbuf", self._undo_handler.on_insert_pixbuf), self.connect("insert-child-anchor", self._undo_handler.on_insert_child_anchor), self.connect("apply-tag", self._undo_handler.on_apply_tag), self.connect("remove-tag", self._undo_handler.on_remove_tag), self.connect("changed", self._undo_handler.on_changed) ] def block_signals(self): """Block all signal handlers""" for signal in self._signals: self.handler_block(signal) self.undo_stack.suppress() def unblock_signals(self): """Unblock all signal handlers""" for signal in self._signals: self.handler_unblock(signal) self.undo_stack.resume() self.undo_stack.reset() def clear(self, clear_undo=False): """Clear buffer contents""" start = self.get_start_iter() end = self.get_end_iter() if clear_undo: self.undo_stack.suppress() self.begin_user_action() self.remove_all_tags(start, end) self.delete(start, end) self.end_user_action() if clear_undo: self.undo_stack.resume() self.undo_stack.reset() def get_insert_iter(self): """Return TextIter for insert point""" return self.get_iter_at_mark(self.get_insert()) #========================================================== # restrict cursor and insert def is_insert_allowed(self, it, text=""): """Check that insert is allowed at TextIter 'it'""" return it.can_insert(True) def is_cursor_allowed(self, it): """Returns True if cursor is allowed at TextIter 'it'""" return True #====================================== # child widgets def add_child(self, it, child): """Add TextChildAnchor to buffer""" pass def update_child(self, action): if isinstance(action, InsertChildAction): # set buffer of child action.child.set_buffer(self) #====================================== # selection callbacks def on_selection_changed(self): pass #========================================================= # paragraph change callbacks def on_paragraph_split(self, start, end): pass def on_paragraph_merge(self, start, end): pass def on_paragraph_change(self, start, end): pass def update_paragraphs(self, action): if isinstance(action, InsertAction): # detect paragraph spliting if "\n" in action.text: par_start = self.get_iter_at_offset(action.pos) par_end = par_start.copy() par_start.backward_line() par_end.forward_chars(action.length) par_end.forward_line() self.on_paragraph_split(par_start, par_end) elif isinstance(action, DeleteAction): # detect paragraph merging if "\n" in action.text: par_start, par_end = get_paragraph( self.get_iter_at_offset(action.start_offset)) self.on_paragraph_merge(par_start, par_end) #================================== # tag apply/remove ''' def apply_tag(self, tag, start, end): if isinstance(tag, RichTextTag): tag.on_apply() gtk.TextBuffer.apply_tag(self, tag, start, end) ''' def remove_tag(self, tag, start, end): #assert self.get_tag_table().lookup(tag.get_property("name")) is not None, tag.get_property("name") gtk.TextBuffer.remove_tag(self, tag, start, end) #=========================================================== # callbacks def _on_mark_set(self, textbuffer, it, mark): """Callback for mark movement""" if mark is self._insert_mark: # if cursor is not allowed here, move it back old_insert = self.get_iter_at_mark(self._old_insert_mark) if not self.get_iter_at_mark(mark).equal(old_insert) and \ not self.is_cursor_allowed(it): self.place_cursor(old_insert) return # when cursor moves, selection changes self.on_selection_changed() # keep track of cursor position self.move_mark(self._old_insert_mark, it) def _on_insert_text(self, textbuffer, it, text, length): """Callback for text insert""" # NOTE: GTK does not give us a proper UTF string, so fix it text = unicode(text, "utf_8") length = len(text) # check to see if insert is allowed if textbuffer.is_interactive() and \ not self.is_insert_allowed(it, text): textbuffer.stop_emission("insert_text") def _on_insert_child_anchor(self, textbuffer, it, anchor): """Callback for inserting a child anchor""" if not self.is_insert_allowed(it, ""): self.stop_emission("insert_child_anchor") def _on_apply_tag(self, textbuffer, tag, start, end): """Callback for tag apply""" if not isinstance(tag, RichTextTag): # do not process tags that are not rich text # i.e. gtkspell tags (ignored by undo/redo) return if tag.is_par_related(): self.on_paragraph_change(start, end) def _on_remove_tag(self, textbuffer, tag, start, end): """Callback for tag remove""" if not isinstance(tag, RichTextTag): # do not process tags that are not rich text # i.e. gtkspell tags (ignored by undo/redo) return if tag.is_par_related(): self.on_paragraph_change(start, end) def _on_delete_range(self, textbuffer, start, end): pass def on_after_changed(self, action): """ Callback after content change has occurred Fix up textbuffer to restore consistent state (paragraph tags, current font application) """ self.begin_user_action() self.update_current_tags(action) self.update_paragraphs(action) self.update_child(action) self.end_user_action() #================================================================== # records whether text insert is currently user interactive, or is # automated def begin_noninteractive(self): """Begins a noninteractive mode""" self._noninteractive += 1 def end_noninteractive(self): """Ends a noninteractive mode""" self._noninteractive -= 1 def is_interactive(self): """Returns True when insert is currently interactive""" return self._noninteractive == 0 #===================================================================== # undo/redo methods def undo(self): """Undo the last action in the RichTextView""" self.begin_noninteractive() self.undo_stack.undo() self.end_noninteractive() def redo(self): """Redo the last action in the RichTextView""" self.begin_noninteractive() self.undo_stack.redo() self.end_noninteractive() def _on_begin_user_action(self, textbuffer): """Begin a composite undo/redo action""" self.undo_stack.begin_action() def _on_end_user_action(self, textbuffer): """End a composite undo/redo action""" if not self.undo_stack.is_in_progress() and \ not self._user_action_ending: self._user_action_ending = True self.emit("ending-user-action") self._user_action_ending = False self.undo_stack.end_action() gobject.type_register(RichTextBaseBuffer) gobject.signal_new("ending-user-action", RichTextBaseBuffer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) keepnote-0.7.8/keepnote/gui/richtext/richtext_html.py0000644000175000017500000012030411655576212021204 0ustar razraz""" KeepNote HTML reader/writer for RichText """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import sys import re from HTMLParser import HTMLParser from xml.sax.saxutils import escape import urllib # keepnote imports from keepnote import log_error, log_message from .textbuffer_tools import \ iter_buffer_contents, \ buffer_contents_iter_to_offset, \ normalize_tags, \ insert_buffer_contents, \ sanitize_text, \ buffer_contents_apply_tags, \ TextBufferDom, \ TextDom, \ AnchorDom, \ TagDom, \ TagNameDom from .richtextbuffer import \ RichTextBuffer, \ RichTextImage, \ RichTextHorizontalRule from .richtext_tags import \ RichTextTag, \ RichTextModTag, \ RichTextFamilyTag, \ RichTextSizeTag, \ RichTextJustifyTag, \ RichTextFGColorTag, \ RichTextBGColorTag, \ RichTextIndentTag, \ RichTextBulletTag, \ RichTextLinkTag # NOTE: leave this out in order to make my XHTML compatiable to HTML browsers # # constants XHTML_HEADER = """\ """ XHTML_FOOTER = "" HTML_HEADER = """ """ HTML_FOOTER = "" BULLET_STR = u"\u2022 " JUSTIFY_VALUES = set([ "left", "center", "right", "fill", "justify"]) def tagcolor_to_html(c): assert len(c) == 13 return c[0] + c[1] + c[2] + c[5] + c[6] + c[9] + c[10] def nest_indent_tags(contents, tag_table): """Convert indent tags so that they nest like HTML tags""" indent = 0 indent_closing = False # loop through contents stream for item in contents: # if we are in the middle of a indent closing event, then the next # item determines what we should do if indent_closing: if item[0] == "anchor" or item[0] == "text": # if we see "content" (anchors or text) (instead of # immediately opening a new indent) then we must close all # indents (i.e. indent=0) while indent > 0: yield ("end", None, tag_table.lookup( RichTextIndentTag.tag_name(indent))) indent -= 1 indent_closing = False elif item[0] == "begin": # if we see a begining tag then check to see if its an # indentation tag tag = item[2] if isinstance(tag, RichTextIndentTag): # (A) if it is a new indentation that is of lower indent # close all indents until we match next_indent = tag.get_indent() while indent > next_indent: yield ("end", None, tag_table.lookup( RichTextIndentTag.tag_name(indent))) indent -= 1 indent_closing = False else: # do nothing pass # yield items if item[0] == "begin" and \ isinstance(item[2], RichTextIndentTag): # if item is a begining indent, open indents until we match tag = item[2] next_indent = tag.get_indent() # should be true since (A) should have executed assert next_indent >= indent while indent < next_indent: # open new indents until we match level indent += 1 assert indent > 0 yield ("begin", None, tag_table.lookup( RichTextIndentTag.tag_name(indent))) elif item[0] == "end" and \ isinstance(item[2], RichTextIndentTag): next_indent = item[2].get_indent() indent_closing = True else: yield item # close all remaining indents while indent > 0: yield ("end", None, tag_table.lookup( RichTextIndentTag.tag_name(indent))) indent -= 1 def unnest_indent_tags(contents): """Convert nested indents to unnested""" indent = 0 # level of indent li_stack = [] # stack of open indents for item in contents: kind, pos, param = item if kind == "beginstr": if param == "ol": # increase indent indent += 1 elif param.startswith("li "): # close open indents if len(li_stack) > 0: yield ("endstr", None, li_stack[-1]) # start new indent par_type = param[3:] tagstr = "indent %d %s" % (indent, par_type) yield ("beginstr", None, tagstr) li_stack.append(tagstr) # add bullet points if needed if par_type == "bullet": yield ("beginstr", None, "bullet") yield ("text", None, BULLET_STR) yield ("endstr", None, "bullet") else: yield item elif kind == "endstr": if param == "ol": # decrease indent indent -= 1 elif param.startswith("li "): # stop indent par_type = param[3:] li_stack.pop() yield ("endstr", None, "indent %d %s" % (indent, par_type)) # resume previous indent if len(li_stack) > 0: yield ("beginstr", None, li_stack[-1]) else: yield item else: yield item def find_paragraphs(contents): """Wrap each paragraph with a pair of tags""" within_par = False others = [] par_type = "none" pars = {"none": P_TAG, "bullet": P_BULLET_TAG} par_stack = [] for item in contents: if item[0] == "text": for item2 in others: yield item2 others = [] if not within_par: # starting paragraph within_par = True yield ("begin", None, pars[par_type]) par_stack.append(pars[par_type]) text = item[2] i = 0 for j, c in enumerate(text): if not within_par: within_par = True yield ("begin", None, pars[par_type]) par_stack.append(pars[par_type]) if c == "\n": yield ("text", None, text[i:j+1]) yield ("end", None, par_stack.pop()) within_par = False i = j+1 # yield remaining text if i < j+1: if not within_par: within_par = True yield ("begin", None, pars[par_type]) par_stack.append(pars[par_type]) yield ("text", None, text[i:j+1]) elif item[0] == "anchor": for item2 in others: yield item2 others = [] if not within_par: # starting paragraph within_par = True yield ("begin", None, pars[par_type]) par_stack.append(pars[par_type]) # yield anchor yield item else: # pass other items through if item[0] == "begin" and \ isinstance(item[2], RichTextIndentTag): par_type = item[2].get_par_indent() others.append(item) if within_par: yield ("end", None, par_stack.pop()) for item in others: yield item def parse_css_style(stylestr): # TODO: this parsing may be too simplistic for statement in stylestr.split(";"): statement = statement.strip() try: if statement.startswith("font-size"): # font size size = int(float( "".join(filter(lambda x: x.isdigit() or x == ".", statement.split(":")[1])))) yield "size " + str(size) elif statement.startswith("font-family"): # font family family = statement.split(":")[1].strip() family = [x.replace('"', '').replace("'", "") for x in family.split(",")][0] yield "family " + family elif statement.startswith("text-align"): # text justification align = statement.split(":")[1].strip() #if align not in JUSTIFY_VALUES: # raise HtmlError("unknown justification '%s'" % align) if align == "justify": yield "fill" else: yield align elif statement.startswith("color"): # foreground color fg_color = statement.split(":")[1].strip() if fg_color.startswith("#"): if len(fg_color) == 4: x, a, b, c = fg_color fg_color = x + a + a + b + b+ c + c if len(fg_color) == 7: yield "fg_color " + fg_color elif statement.startswith("background-color"): # background color bg_color = statement.split(":")[1].strip() if bg_color.startswith("#"): if len(bg_color) == 4: x, a, b, c = bg_color bg_color = x + a + a + b + b + c + c if len(bg_color) == 7: yield "bg_color " + bg_color except: # ignore css statements that we cannot parse pass class HtmlTagDom (TagDom): def __init__(self, tag): TagDom.__init__(self, tag) class RichTextParTag (RichTextTag): def __init__(self, kind): RichTextTag.__init__(self, "p") self.kind = kind class RichTextLiTag (RichTextTag): def __init__(self): RichTextTag.__init__(self, "li ") LI_TAG = RichTextLiTag() P_TAG = RichTextParTag("none") P_BULLET_TAG = RichTextParTag("bullet") class LiHtmlTagDom (HtmlTagDom): def __init__(self, kind): HtmlTagDom.__init__(self, LI_TAG) self.kind = kind class HtmlError (StandardError): """Error for HTML parsing""" pass #============================================================================= # tag input/output class HtmlTagReader (object): def __init__(self, io, htmltag): self._io = io self.htmltag = htmltag def parse_starttag(self, htmltag, attrs): pass def parse_endtag(self, htmltag): pass class HtmlTagWriter (object): def __init__(self, io, tagclass): self._io = io self.tagclass = tagclass def write_tag_begin(self, out, dom, xhtml): pass def write_tag_end(self, out, dom, xhtml): pass def write(self, out, dom, xhtml): pass class HtmlTagModReader (HtmlTagReader): """simple font modifications (b/i/u)""" html2buffer_tag = { "b": "bold", "strong": "bold", "i": "italic", "em": "italic", "u": "underline", "strike": "strike", "tt": "tt", "nobr": "nowrap"} def parse_starttag(self, htmltag, attrs): tagstr = self.html2buffer_tag[htmltag] self._io.append_child(TagNameDom(tagstr), True) class HtmlTagModWriter (HtmlTagWriter): buffer_tag2html = { "bold": "b", "italic": "i", "underline": "u", "strike": "strike", "tt": "tt", "nowrap": "nobr" } def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextModTag) def write_tag_begin(self, out, dom, xhtml): out.write("<%s>" % self.buffer_tag2html[dom.tag.get_property("name")]) def write_tag_end(self, out, dom, xhtml): out.write("" % self.buffer_tag2html[dom.tag.get_property("name")]) class HtmlTagLinkReader (HtmlTagReader): def __init__(self, io): HtmlTagReader.__init__(self, io, "a") def parse_starttag(self, htmltag, attrs): for key, value in attrs: if key == "href": tag = TagNameDom("link " + value) self._io.append_child(tag, True) break class HtmlTagLinkWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextLinkTag) def write_tag_begin(self, out, dom, xhtml): tag = dom.tag out.write('' % tag.get_href()) def write_tag_end(self, out, dom, xhtml): out.write("") class HtmlTagSpanReader (HtmlTagReader): """ tags""" def __init__(self, io): HtmlTagReader.__init__(self, io, "span") def parse_starttag(self, htmltag, attrs): for key, value in attrs: if key == "style": for tagstr in parse_css_style(value): self._io.append_child(TagNameDom(tagstr), True) class HtmlTagFontReader (HtmlTagReader): """ tags""" def __init__(self, io): HtmlTagReader.__init__(self, io, "font") def parse_starttag(self, htmltag, attrs): for key, value in attrs: if key == "style": for tagstr in parse_css_style(value): self._io.append_child(TagNameDom(tagstr), True) elif key == "face": self._io.append_child(TagNameDom("family " + value), True) elif key == "size": size = int(value) if size < 0: sizept = 4 else: sizept = 4 + size * 2 #(default should be 10pt) self._io.append_child(TagNameDom("size " + str(sizept)), True) class HtmlTagCenterReader (HtmlTagReader): """
tags""" def __init__(self, io): HtmlTagReader.__init__(self, io, "center") def parse_starttag(self, htmltag, attrs): self._io.append_child(TagNameDom("center"), True) class HtmlTagDivReader (HtmlTagReader): def __init__(self, io): HtmlTagReader.__init__(self, io, "div") def parse_starttag(self, htmltag, attrs): for key, value in attrs: if key == "style": for tagstr in parse_css_style(value): self._io.append_child(TagNameDom(tagstr), True) class HtmlTagSizeWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextSizeTag) def write_tag_begin(self, out, dom, xhtml): tag = dom.tag out.write('' % tag.get_size()) def write_tag_end(self, out, dom, xhtml): out.write("") class HtmlTagFamilyWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextFamilyTag) def write_tag_begin(self, out, dom, xhtml): tag = dom.tag out.write('' % tag.get_family()) def write_tag_end(self, out, dom, xhtml): out.write("") class HtmlTagFGColorWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextFGColorTag) def write_tag_begin(self, out, dom, xhtml): tag = dom.tag out.write('' % tagcolor_to_html(tag.get_color())) def write_tag_end(self, out, dom, xhtml): out.write("") class HtmlTagBGColorWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextBGColorTag) def write_tag_begin(self, out, dom, xhtml): tag = dom.tag out.write('' % tagcolor_to_html(tag.get_color())) def write_tag_end(self, out, dom, xhtml): out.write("") class HtmlTagAlignWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextJustifyTag) def write_tag_begin(self, out, dom, xhtml): tagname = dom.tag.get_property("name") if tagname == "fill": text = "justify" else: text = tagname out.write('
' % text) def write_tag_end(self, out, dom, xhtml): out.write("
") class HtmlTagParReader (HtmlTagReader): # paragraph # NOTE: this tag is currently not used by KeepNote, but if pasting # text from another HTML source, KeepNote will interpret it as # a newline char def __init__(self, io): HtmlTagReader.__init__(self, io, "p") def parse_starttag(self, htmltag, attrs): # do not create a newline if one already exists last_child = self._io.last_child() #print last_child #if isinstance(last_child, TextDom): # print last_child.lst if (not isinstance(last_child, TextDom) or not last_child.get().endswith("\n")): self._io.append_text("\n") for key, value in attrs: if key == "style": for tagstr in parse_css_style(value): self._io.append_child(TagNameDom(tagstr), True) elif key == "align": if value.lower() in ("left", "right", "center"): self._io.append_child(TagNameDom(value.lower()), True) def parse_endtag(self, htmltag): self._io.append_text("\n") class HtmlTagListItemReader (HtmlTagReader): def __init__(self, io): HtmlTagReader.__init__(self, io, "li") def parse_starttag(self, htmltag, attrs): par_type = "bullet" for key, value in attrs: if key == "style": for statement in value.split(";"): tokens = statement.split(":") if len(tokens) == 2: key2, value2 = tokens value2 = value2.strip() if key2.strip() == "list-style-type": if value2 == "disc": par_type = "bullet" elif value2 == "none": par_type = "none" tag = TagNameDom("li %s" % par_type) self._io.append_child(tag, True) class HtmlTagListItemWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextLiTag) def write_tag_begin(self, out, dom, xhtml): if dom.kind == "bullet": #self._out.write('
  • ') out.write('
  • ') else: out.write('
  • ') def write_tag_end(self, out, dom, xhtml): out.write("
  • \n") class HtmlTagUnorderedListReader (HtmlTagReader): def __init__(self, io): HtmlTagReader.__init__(self, io, "ul") def parse_starttag(self, htmltag, attrs): self._io.append_child(TagNameDom("ol"), True) class HtmlTagOrderedListReader (HtmlTagReader): def __init__(self, io): HtmlTagReader.__init__(self, io, "ol") def parse_starttag(self, htmltag, attrs): self._io.append_child(TagNameDom("ol"), True) class HtmlTagUnorderedListWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextIndentTag) def write_tag_begin(self, out, dom, xhtml): out.write("
      ") def write_tag_end(self, out, dom, xhtml): out.write("
    \n") class HtmlTagBulletWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextBulletTag) class HtmlTagHrReader (HtmlTagReader): def __init__(self, io): HtmlTagReader.__init__(self, io, "hr") def parse_starttag(self, htmltag, attrs): # horizontal break hr = RichTextHorizontalRule() self._io.append_text("\n") self._io.append_child(AnchorDom(hr), False) self._io.append_text("\n") class HtmlTagHrWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextHorizontalRule) def write(self, out, dom, xhtml): if xhtml: out.write("
    ") else: out.write("
    ") class HtmlTagImgReader (HtmlTagReader): def __init__(self, io): HtmlTagReader.__init__(self, io, "img") def parse_starttag(self, htmltag, attrs): """Parse image tag""" img = RichTextImage() width, height = None, None for key, value in attrs: if key == "src": img.set_filename(value) elif key == "width": try: width = int(value) except ValueError, e: # ignore width if we cannot parse it pass elif key == "height": try: height = int(value) except ValueError, e: # ignore height if we cannot parse it pass else: # ignore other attributes pass img.scale(width, height) self._io.append_child(AnchorDom(img), False) class HtmlTagImgWriter (HtmlTagWriter): def __init__(self, io): HtmlTagWriter.__init__(self, io, RichTextImage) def write(self, out, dom, xhtml): # write image size_str = "" anchor = dom.anchor size = anchor.get_size() if size[0] is not None: size_str += " width=\"%d\"" % size[0] if size[1] is not None: size_str += " height=\"%d\"" % size[1] if xhtml: out.write("" % (anchor.get_filename(), size_str)) else: out.write("" % (anchor.get_filename(), size_str)) #============================================================================= # TODO: may need to include support for ignoring information between # and """) def walk(node): nodeid = node.get_attr("nodeid") expand = node.get_attr("expanded", False) if len(node.get_children()) > 0: out.write(u"""+ """ % (nodeid, [u"false", u"true"][int(expand)])) else: out.write(u"  ") if node.get_attr("content_type") == notebooklib.CONTENT_TYPE_DIR: out.write(u"%s
    \n" % escape(node.get_title())) else: out.write(u"%s
    \n" % (nodeid2html_link(notebook, rootpath, nodeid), escape(node.get_title()))) if len(node.get_children()) > 0: out.write(u"
    " % (nodeid, [u"_collapsed", ""][int(expand)])) for child in node.get_children(): walk(child) out.write(u"
    \n") walk(node) out.write(u"""""") out.close() def export_notebook(notebook, filename, task): """Export notebook to HTML filename -- filename of export to create """ if task is None: # create dummy task if needed task = tasklib.Task() if os.path.exists(filename): raise NoteBookError("File '%s' already exists" % filename) # make sure all modifications are saved first try: notebook.save() except Exception, e: raise NoteBookError("Could not save notebook before archiving", e) # first count # of files nnodes = [0] def walk(node): nnodes[0] += 1 for child in node.get_children(): walk(child) walk(notebook) task.set_message(("text", "Exporting %d notes..." % nnodes[0])) nnodes2 = [0] def export_page(node, path, arcname): filename = os.path.join(path, "page.html") filename2 = os.path.join(arcname, "page.html") try: dom = minidom.parse(filename) except Exception, e: # error parsing file, use simple file export export_files(filename, filename2) else: translate_links(notebook, path, dom.documentElement) # avoid writing header # (provides compatiability with browsers) out = codecs.open(filename2, "wb", "utf-8") if dom.doctype: dom.doctype.writexml(out) dom.documentElement.writexml(out) out.close() def export_node(node, path, arcname, index=False): # look for aborted export if task.aborted(): raise NoteBookError("Backup canceled") # report progresss nnodes2[0] += 1 task.set_message(("detail", truncate_filename(path))) task.set_percent(nnodes2[0] / float(nnodes[0])) skipfiles = set(child.get_basename() for child in node.get_children()) # make node directory os.mkdir(arcname) if index: write_index(notebook, node, arcname) if node.get_attr("content_type") == "text/xhtml+xml": skipfiles.add("page.html") # export xhtml export_page(node, path, arcname) # recurse files for f in os.listdir(path): if not os.path.islink(f) and f not in skipfiles: export_files(os.path.join(path, f), os.path.join(arcname, f)) # recurse nodes for child in node.get_children(): f = child.get_basename() export_node(child, os.path.join(path, f), os.path.join(arcname, f)) def export_files(path, arcname): # look for aborted export if task.aborted(): raise NoteBookError("Backup canceled") if os.path.isfile(path): # copy files shutil.copy(path, arcname) if os.path.isdir(path): # export directory os.mkdir(arcname) # recurse for f in os.listdir(path): if not os.path.islink(f): export_files(os.path.join(path, f), os.path.join(arcname, f)) export_node(notebook, notebook.get_path(), filename, True) task.set_message(("text", "Closing export...")) task.set_message(("detail", "")) if task: task.finish() keepnote-0.7.8/keepnote/extensions/export_html/__init__.pyc0000644000175000017500000003346111700652110022344 0ustar razraz 'Nc@sdZddkZddkZddkZddkZddkZddkZddkZddkZ ddkl Z ddk l Z eiZ ddkZddklZddklZddklZddklZdd klZdd klZlZyEddkZeid dd klZddkZddkZWnej onXd ei fdYZ ddZ!dZ"dZ#dZ$dZ%dZ&dS(s* KeepNote Export HTML Extension iN(tminidom(tescape(t unicode_gtk(t NoteBookError(tnotebook(ttasklib(ttarfile(t extensiontFileChooserDialogs2.0(tgdkt ExtensioncBs8eZdZdZdZdZddZRS(cCs tii||||_dS(sInitialize extensionN(RR t__init__tapp(tselfR ((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyR EscCsdddfgS(Ntkeepnotes>=iii(iii((R ((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyt get_dependsLscs6iddfdiddS(s,Initialize extension for a particular windows Export HTMLs_HTML...csiiS((ton_export_notebookt get_notebook(tw(R twindow(s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pytUss& N(t add_actiontadd_ui(R R((R Rs[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyt on_add_uiPs c CsJ|djodStd|dtiddtidtifd|idd }tit i i |i d }|ii d }|o/t i i|oti||d d }n|}|it i i ||i}|tijoC|io6t|i}|i|i||d |n |idS(s*Callback from gui for exporting a notebookNsExport NotebooktactiontbuttonstCanceltExportR tpersistent_pathtarchive_notebook_paths -%Y-%m-%dtt.R(tNoneRtgtktFILE_CHOOSER_ACTION_SAVEtRESPONSE_CANCELt RESPONSE_OKR ttimetstrftimetostpathtbasenametget_pathtget_default_pathtexistst notebooklibtget_unique_filenametset_current_nametrunt get_filenameRtdestroytexport_notebook(R RRtdialogR)R(tfilenametresponse((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyRfs,         c sdjodS|otifd}|idtiid|y;|i\}}}|o |n|idt SWqt j o1}|id|i d|i ||t Stj o*}|id|i d||t SXntddS(Ncst|S((R3(ttask(RR5(s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyRssExporting to '%s'...sBeginning export...sNotebook exportedRs"Error while exporting notebook: %ss unknown error(R RtTaskt wait_dialogR'R(R)texc_infot set_statustTrueRterrortmsgtFalset ExceptionR3( R RR5RR7ttyR=ttracebkte((R5Rs[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyR3s0       N(t__name__t __module__R RRRR R3(((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyR Cs     #idcCs.t||jod||d }n|S(Ns...i(tlen(R5tmaxsize((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyttruncate_filenamescCs|d}}|d}}g}g}x||jo|djp |djokt|t|jo)tii|\}}|i|q)tii|\}}|idq)W|it|di|S(NRu..u/( R RFR'R(tsplittappendtextendtreversedtjoin(R(tstarttheadttailthead2ttail2treltrel2((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pytrelpaths  'cCs|i|}|ot|i|}|iddjodi|df}n3|ido"di||idf}nti|idSdSdS(Nt content_typestext/xhtml+xmlu/u page.htmltpayload_filenametutf8R( tget_node_by_idRUR*tget_attrRMthas_attrturllibtquotetencode(RR(tnodeidtnotetnewpath((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pytnodeid2html_links"cs#fd|dS(Ncs|i|ijo|idjoo|id}ti|oLti|\}}t|}|djo|id|qqnx|i D]}|qWdS(NtathrefR( tnodeTypet ELEMENT_NODEttagNamet getAttributeR-t is_node_urltparse_node_urlRbt setAttributet childNodes(tnodeturlthostR_turl2tchild(RtwalkR((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyRrs#  ((RR(Rm((R(RRrs[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyttranslate_linksscs|itii|d}tii|d}ti|ddidt|ii ti|ddidfd|idi dS( Ns index.htmls tree.htmltwbsutf-8u %s u csQ|id}|idt}t|idjo+id|ddgt|fnid|idtijo!id t|i n0id t |t|i ft|idjoYid |d d gt|fx|iD]}|q(WidndS(NR_texpandediuH+ ufalseutrueu  RVu%s
    u0%s
    u
    u _collapsedRu
    ( RZR?RFt get_childrentwritetintR-tCONTENT_TYPE_DIRRt get_titleRb(RmR_texpandRq(RtrootpathRrtout(s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyRrhs$ " !   u( R*R'R(RMtcodecstopenRwRRztclose(RRmR(t index_filet tree_file((R|RrRR}s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyt write_indexs   b  c sVd jotintii|otd|nyiWn$tj o}td|nXdgfdi dddfdgfdt fdfd i |t i di doi nd S(sMExport notebook to HTML filename -- filename of export to create sFile '%s' already existss(Could not save notebook before archivingics5dcd7s RVstext/xhtml+xmls page.html(tabortedRt set_messageRHt set_percenttfloattsetRvR'tmkdirRRZtaddtlistdirR(tislinkRMR(RmR(Rtindext skipfilestfRq(R7RRt export_nodeRRtnnodes2(s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyRs0    !  csiotdntii|oti||ntii|omti|x]ti |D]H}tii |p/tii ||tii ||qtqtWndS(NsBackup canceled( RRR'R(tisfiletshutiltcopytisdirRRRRM(R(RR(R7R(s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyRs  sClosing export...RRN(stextsClosing export...(sdetailR(R RR8R'R(R,RtsaveR@RR?R*R<tfinish(RR5R7RC((RR7RRRRRrRs[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pyR3s*    $'  ('t__doc__R~tgettextR'tsysR%RR\txml.domtxmlRtxml.sax.saxutilsRt_RRtkeepnote.notebookRRR-RRt keepnote.guiRRtpygtktrequireR!R t gtk.gladetgobjectt ImportErrorR RHRURbRsRR3(((s[/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/export_html/__init__.pytsB             m     keepnote-0.7.8/keepnote/extensions/command_basics/0000755000175000017500000000000011727170645020477 5ustar razrazkeepnote-0.7.8/keepnote/extensions/command_basics/info.xml0000644000175000017500000000071711677423601022156 0ustar razraz version 1.0 name Basic Commands author Matt Rasmussen email rasmus@alum.mit.edu website http://keepnote.org description Adds basic command line options to KeepNote keepnote-0.7.8/keepnote/extensions/command_basics/__init__.py0000644000175000017500000002371211677423601022612 0ustar razraz""" KeepNote Extension backup_tar Command-line basic commands """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports import os import sys # gtk imports import gobject # keepnote imports import keepnote from keepnote import AppCommand import keepnote.notebook import keepnote.notebook.update import keepnote.extension import keepnote.gui.extension class Extension (keepnote.gui.extension.Extension): def __init__(self, app): """Initialize extension""" keepnote.gui.extension.Extension.__init__(self, app) self.app = app self.enabled.add(self.on_enabled) self.commands = [ # window commands AppCommand("focus", lambda app, args: app.focus_windows(), help="focus all open windows"), AppCommand("minimize", self.on_minimize_windows, help="minimize all windows"), AppCommand("toggle-windows", self.on_toggle_windows, help="toggle all windows"), # extension commands AppCommand("install", self.on_install_extension, metavar="FILENAME", help="install a new extension"), AppCommand("uninstall", self.on_uninstall_extension, metavar="EXTENSION_NAME", help="uninstall an extension"), AppCommand("tmp_ext", self.on_temp_extension, metavar="FILENAME", help="add an extension just for this session"), AppCommand("ext_path", self.on_extension_path, metavar="PATH", help="add an extension path for this session"), AppCommand("quit", lambda app, args: gobject.idle_add(app.quit), help="close all KeepNote windows"), # notebook commands AppCommand("view", self.on_view_note, metavar="NOTE_URL", help="view a note"), AppCommand("new", self.on_new_note, metavar="PARENT_URL", help="add a new note"), AppCommand("search-titles", self.on_search_titles, metavar="TEXT", help="search notes by title"), AppCommand("upgrade", self.on_upgrade_notebook, metavar="[v VERSION] NOTEBOOK...", help="upgrade a notebook"), # misc AppCommand("screenshot", self.on_screenshot, help="insert a new screenshot"), ] def get_depends(self): return [("keepnote", ">=", (0, 6, 4))] def on_enabled(self, enabled): if enabled: for command in self.commands: if self.app.get_command(command.name): continue try: self.app.add_command(command) except Exception, e: self.app.error("Could not add command '%s'" % command.name, e, sys.exc_info()[2]) else: for command in self.commands: self.app.remove_command(command.name) #==================================================== # commands def on_minimize_windows(self, app, args): for window in app.get_windows(): window.iconify() def on_toggle_windows(self, app, args): for window in app.get_windows(): if window.is_active(): self.on_minimize_windows(app, args) return app.focus_windows() def on_uninstall_extension(self, app, args): for extname in args[1:]: app.uninstall_extension(extname) def on_install_extension(self, app, args): for filename in args[1:]: app.install_extension(filename) def on_temp_extension(self, app, args): for filename in args[1:]: entry = app.add_extension(filename, "temp") ext = app.get_extension(entry.get_key()) if ext: app.init_extensions_windows(windows=None, exts=[ext]) ext.enable(True) def on_extension_path(self, app, args): exts = [] for extensions_dir in args[1:]: for filename in keepnote.extension.iter_extensions(extensions_dir): entry = app.add_extension_entry(filename, "temp") ext = app.get_extension(entry.get_key()) if ext: exts.append(ext) app.init_extensions_windows(windows=None, exts=exts) for ext in exts: ext.enable(True) def on_screenshot(self, app, args): window = app.get_current_window() if window: editor = window.get_viewer().get_editor() if hasattr(editor, "get_editor"): editor = editor.get_editor() if hasattr(editor, "on_screenshot"): editor.on_screenshot() def on_view_note(self, app, args): if len(args) < 1: self.error("Must specify note url") return app.focus_windows() nodeurl = args[1] if keepnote.notebook.is_node_url(nodeurl): host, nodeid = keepnote.notebook.parse_node_url(nodeurl) self.view_nodeid(app, nodeid) else: # do text search window = self.app.get_current_window() if window is None: return notebook = window.get_notebook() if notebook is None: return results = list(notebook.search_node_titles(nodeurl)) if len(results) == 1: self.view_nodeid(app, results[0][0]) else: viewer = window.get_viewer() viewer.start_search_result() for nodeid, title in results: node = notebook.get_node_by_id(nodeid) if node: viewer.add_search_result(node) def on_new_note(self, app, args): if len(args) < 1: self.error("Must specify note url") return app.focus_windows() nodeurl = args[1] window, notebook = self.get_window_notebook() nodeid = self.get_nodeid(nodeurl) if notebook and nodeid: node = notebook.get_node_by_id(nodeid) if node: window.get_viewer().new_node( keepnote.notebook.CONTENT_TYPE_PAGE, "child", node) def on_search_titles(self, app, args): if len(args) < 1: self.error("Must specify text to search") return # get window and notebook window = self.app.get_current_window() if window is None: return notebook = window.get_notebook() if notebook is None: return # do search text = args[1] nodes = list(notebook.search_node_titles(text)) for nodeid, title in nodes: print "%s\t%s" % (title, keepnote.notebook.get_node_url(nodeid)) def view_nodeid(self, app, nodeid): for window in app.get_windows(): notebook = window.get_notebook() if not notebook: continue node = notebook.get_node_by_id(nodeid) if node: window.get_viewer().goto_node(node) break def get_nodeid(self, text): if keepnote.notebook.is_node_url(text): host, nodeid = keepnote.notebook.parse_node_url(text) return nodeid else: # do text search window = self.app.get_current_window() if window is None: return None notebook = window.get_notebook() if notebook is None: return None results = list(notebook.search_node_titles(text)) if len(results) == 1: return results[0][0] else: for nodeid, title in results: if title == text: return nodeid return None def get_window_notebook(self): window = self.app.get_current_window() if window is None: return None, None notebook = window.get_notebook() return window, notebook def on_upgrade_notebook(self, app, args): version = keepnote.notebook.NOTEBOOK_FORMAT_VERSION i = 1 while i < len(args): if args[i] == "v": try: version = int(args[i+1]) i += 2 except: raise Exception("excepted version number") else: break files = args[i:] for filename in files: keepnote.log_message("upgrading notebook to version %d: %s\n" % (version, filename)) keepnote.notebook.update.update_notebook(filename, version, verify=True) keepnote-0.7.8/keepnote/extensions/command_basics/__init__.pyc0000644000175000017500000002373511700652107022752 0ustar razraz 'Nc @sdZddkZddkZddkZddkZddklZddkZddkZddkZddk Zdei i i fdYZ dS(sI KeepNote Extension backup_tar Command-line basic commands iN(t AppCommandt ExtensioncBseZdZdZdZdZdZdZdZdZ dZ d Z d Z d Z d Zd ZdZdZdZRS(cCstiiii||||_|ii|it ddddt d|i ddt d|i ddt d |i d d dd t d |i d dddt d|id d ddt d|id dddt ddddt d|id dddt d|id dddt d|id ddd t d!|id d"dd#t d$|idd%g |_d&S('sInitialize extensiontfocuscSs |iS((t focus_windows(tapptargs((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyt7sthelpsfocus all open windowstminimizesminimize all windowsstoggle-windowsstoggle all windowstinstalltmetavartFILENAMEsinstall a new extensiont uninstalltEXTENSION_NAMEsuninstall an extensionttmp_exts&add an extension just for this sessiontext_pathtPATHs&add an extension path for this sessiontquitcSsti|iS((tgobjecttidle_addR(RR((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyRKssclose all KeepNote windowstviewtNOTE_URLs view a notetnewt PARENT_URLsadd a new notes search-titlestTEXTssearch notes by titletupgrades[v VERSION] NOTEBOOK...supgrade a notebookt screenshotsinsert a new screenshotN(tkeepnotetguit extensionRt__init__Rtenabledtaddt on_enabledRton_minimize_windowston_toggle_windowston_install_extensionton_uninstall_extensionton_temp_extensionton_extension_patht on_view_notet on_new_noteton_search_titleston_upgrade_notebookt on_screenshottcommands(tselfR((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR.sJ cCsdddfgS(NRs>=iii(iii((R.((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyt get_dependsescCs|ox|iD]v}|ii|ioqny|ii|Wqtj o0}|iid|i|tidqXqWn(x$|iD]}|ii |iqWdS(NsCould not add command '%s'i( R-Rt get_commandtnamet add_commandt Exceptionterrortsystexc_infotremove_command(R.Rtcommandte((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR!is ! cCs%x|iD]}|iq WdS(N(t get_windowsticonify(R.RRtwindow((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR"~s cCsGx6|iD](}|io|i||dSq W|idS(N(R:t is_activeR"R(R.RRR<((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR#s    cCs&x|dD]}|i|q WdS(Ni(tuninstall_extension(R.RRtextname((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR%s cCs&x|dD]}|i|q WdS(Ni(tinstall_extension(R.RRtfilename((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR$s cCsqxj|dD]^}|i|d}|i|i}|o*|iddd|g|itq q WdS(Nittemptwindowstexts(t add_extensiont get_extensiontget_keytinit_extensions_windowstNonetenabletTrue(R.RRRAtentrytext((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR&s cCsg}xn|dD]b}xYtii|D]E}|i|d}|i|i}|o|i|q*q*WqW|iddd|x|D]}|i t qWdS(NiRBRCRD( RRtiter_extensionstadd_extension_entryRFRGtappendRHRIRJRK(R.RRRDtextensions_dirRARLRM((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR's cCsk|i}|oT|ii}t|do|i}nt|do|iqgndS(Nt get_editorR,(tget_current_windowt get_viewerRRthasattrR,(R.RRR<teditor((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR,s c CsSt|djo|iddS|i|d}tii|o,tii|\}}|i||n|ii }|djodS|i }|djodSt |i |}t|djo|i||ddnU|i} | ix;|D]3\}} |i|} | o| i| qqWdS(NisMust specify note urli(tlenR4RRtnotebookt is_node_urltparse_node_urlt view_nodeidRRSRIt get_notebooktlisttsearch_node_titlesRTtstart_search_resulttget_node_by_idtadd_search_result( R.RRtnodeurlthosttnodeidR<RXtresultstviewerttitletnode((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR(s0         cCst|djo|iddS|i|d}|i\}}|i|}|oD|o=|i|}|o#|iiti i d|qndS(NisMust specify note urltchild( RWR4Rtget_window_notebookt get_nodeidR`RTtnew_nodeRRXtCONTENT_TYPE_PAGE(R.RRRbR<RXRdRh((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR)s    c Cst|djo|iddS|ii}|djodS|i}|djodS|d}t|i|}x/|D]'\}}d|ti i |fGHqWdS(NisMust specify text to searchs%s %s( RWR4RRSRIR\R]R^RRXt get_node_url( R.RRR<RXttexttnodesRdRg((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR*s      cCscx\|iD]N}|i}|pq n|i|}|o|ii|Pq q WdS(N(R:R\R`RTt goto_node(R.RRdR<RXRh((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR[s  cCstii|otii|\}}|S|ii}|djodS|i}|djodSt|i |}t |djo |ddSx&|D]\}}||jo|SqWdSdS(Nii( RRXRYRZRRSRIR\R]R^RW(R.RoRcRdR<RXReRg((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyRks"       cCs7|ii}|djodS|i}||fS(N(NN(RRSRIR\(R.R<RX((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyRj-s   cCstii}d}xf|t|joR||djo<y"t||d}|d7}WqvtdqvXqPqW||}xA|D]9}tid||ftiii||dt qWdS(Nitvisexcepted version numbers%upgrading notebook to version %d: %s tverify( RRXtNOTEBOOK_FORMAT_VERSIONRWtintR3t log_messagetupdatetupdate_notebookRK(R.RRtversiontitfilesRA((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR+5s$   (t__name__t __module__RR/R!R"R#R%R$R&R'R,R(R)R*R[RkRjR+(((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyR,s" 7       #    ( t__doc__tosR5RRRtkeepnote.notebooktkeepnote.notebook.updatetkeepnote.extensiontkeepnote.gui.extensionRRR(((s^/mnt/big/archive/projects/keepnote/keepnote-dev/keepnote/extensions/command_basics/__init__.pyts        keepnote-0.7.8/keepnote/safefile.py0000644000175000017500000000613711677423606015477 0ustar razraz""" KeepNote Safely write to a tempfile before replacing previous file. """ # # KeepNote # Copyright (c) 2008-2009 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # import os, tempfile, codecs, sys # NOTE: bypass easy_install's monkey patching of file # easy_install does not correctly emulate 'file' if type(file) != type: # HACK: this works as long as sys.stdout is not patched file = type(sys.stdout) def open(filename, mode="r", tmp=None, codec=None): """ Opens a file that writes to a temp location and replaces existing file on close. filename -- filename to open mode -- write mode (default: 'w') tmp -- specify tempfile codec -- preferred encoding """ stream = SafeFile(filename, mode, tmp) if "b" not in mode and codec: if "r" in mode: stream = codecs.getreader(codec)(stream) elif "w" in mode: stream = codecs.getwriter(codec)(stream) return stream class SafeFile (file): def __init__(self, filename, mode="r", tmp=None): """ filename -- filename to open mode -- write mode (default: 'w') tmp -- specify tempfile """ # set tempfile if "w" in mode and tmp is None: f, tmp = tempfile.mkstemp(".tmp", filename+"_", dir=".") os.close(f) self._tmp = tmp self._filename = filename # open file if self._tmp: file.__init__(self, self._tmp, mode) else: file.__init__(self, filename, mode) def close(self): """Closes file and moves temp file to final location""" try: self.flush() os.fsync(self.fileno()) except: pass file.close(self) if self._tmp: # NOTE: windows will not allow rename when destination file exists if sys.platform.startswith("win"): if os.path.exists(self._filename): os.remove(self._filename) os.rename(self._tmp, self._filename) self._tmp = None def discard(self): """ Close and discard written data. Temp file does not replace existing file """ file.close(self) if self._tmp: os.remove(self._tmp) self._tmp = None def get_tempfile(self): """Returns tempfile filename""" return self._tmp keepnote-0.7.8/keepnote/plist.py0000644000175000017500000001324411677423605015050 0ustar razraz""" KeepNote extended plist module Apple's property list xml serialization - added null type """ # # KeepNote # Copyright (c) 2008-2011 Matt Rasmussen # Author: Matt Rasmussen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # # python imports try: import xml.etree.cElementTree as ET except ImportError: import xml.etree.elementtree.ElementTree as ET from StringIO import StringIO from xml.sax.saxutils import escape import base64, datetime, re import sys try: from .orderdict import OrderDict except (ImportError, ValueError): OrderDict = dict class Data (object): def __init__(self, text): self.text = text # date format: # ISO 8601 (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. # Smaller units may be omitted with a loss of precision _unmarshallers = { # collections "array": lambda x: [v.text for v in x], "dict": lambda x: OrderDict((x[i].text, x[i+1].text) for i in range(0, len(x), 2)), "key": lambda x: x.text or u"", # simple types "string": lambda x: x.text or u"", "data": lambda x: Data(base64.decodestring(x.text or u"")), "date": lambda x: datetime.datetime(*map(int, re.findall("\d+", x.text))), "true": lambda x: True, "false": lambda x: False, "real": lambda x: float(x.text), "integer": lambda x: int(x.text), "null": lambda x: None } def load(infile=sys.stdin): parser = ET.iterparse(infile) for action, elem in parser: unmarshal = _unmarshallers.get(elem.tag) if unmarshal: data = unmarshal(elem) elem.clear() elem.text = data elif elem.tag != "plist": raise IOError("unknown plist type: %r" % elem.tag) return parser.root.text def loads(string): return load(StringIO(string)) def load_etree(elm): for child in elm: load_etree(child) unmarshal = _unmarshallers.get(elm.tag) if unmarshal: data = unmarshal(elm) elm.clear() elm.text = data elif elm.tag != "plist": raise IOError("unknown plist type: %r" % elm.tag) return elm.text def dump(elm, out=sys.stdout, indent=0, depth=0, suppress=False): if indent and not suppress: out.write(" " * depth) if isinstance(elm, dict): out.write(u"") if indent: out.write(u"\n") for key, val in elm.iteritems(): if indent: out.write(" " * (depth + indent)) out.write(u"%s" % key) dump(val, out, indent, depth+indent, suppress=True) if indent: out.write(" " * depth) out.write(u"") elif isinstance(elm, (list, tuple)): out.write(u"") if indent: out.write(u"\n") for item in elm: dump(item, out, indent, depth+indent) if indent: out.write(" " * depth) out.write(u"") elif isinstance(elm, basestring): out.write(u"%s" % escape(elm)) elif isinstance(elm, bool): if elm: out.write(u"") else: out.write(u"") elif isinstance(elm, (int, long)): out.write(u"%d" % elm) elif isinstance(elm, float): out.write(u"%f" % elm) elif elm is None: out.write(u"") elif isinstance(elm, Data): out.write(u"") base64.encode(StringIO(elm), out) out.write(u"") elif isinstance(elm, datetime.datetime): raise Exception("not implemented") else: raise Exception("unknown data type '%s' for value '%s'" % (str(type(elm)), str(elm))) if indent: out.write(u"\n") def dumps(elm, indent=0): s = StringIO() dump(elm, s, indent) return s.getvalue() def dump_etree(elm): if isinstance(elm, dict): elm2 = ET.Element("dict") for key, val in elm.iteritems(): key2 = ET.Element("key") key2.text = key elm2.append(key2) elm2.append(dump_etree(val)) elif isinstance(elm, (list, tuple)): elm2 = ET.Element("array") for item in elm: elm2.append(dump_etree(item)) elif isinstance(elm, basestring): elm2 = ET.Element("string") elm2.text = elm elif isinstance(elm, bool): if elm: elm2 = ET.Element("true") else: elm2 = ET.Element("false") elif isinstance(elm, int): elm2 = ET.Element("integer") elm2.text = str(elm) elif isinstance(elm, float): elm2 = ET.Element("real") elm2.text = str(elm) elif elm is None: elm2 = ET.Element("null") elif isinstance(elm, Data): elm2 = ET.Element("data") elm2.text = base64.encodestring(elm) elif isinstance(elm, datetime.datetime): raise Exception("not implemented") else: raise Exception("unknown data type '%s' for value '%s'" % (str(type(elm)), str(elm))) return elm2 keepnote-0.7.8/keepnote/BeautifulSoup.py0000664000175000017500000023262611365577416016521 0ustar razraz"""Beautiful Soup Elixir and Tonic "The Screen-Scraper's Friend" http://www.crummy.com/software/BeautifulSoup/ Beautiful Soup parses a (possibly invalid) XML or HTML document into a tree representation. It provides methods and Pythonic idioms that make it easy to navigate, search, and modify the tree. A well-formed XML/HTML document yields a well-formed data structure. An ill-formed XML/HTML document yields a correspondingly ill-formed data structure. If your document is only locally well-formed, you can use this library to find and process the well-formed part of it. Beautiful Soup works with Python 2.2 and up. It has no external dependencies, but you'll have more success at converting data to UTF-8 if you also install these three packages: * chardet, for auto-detecting character encodings http://chardet.feedparser.org/ * cjkcodecs and iconv_codec, which add more encodings to the ones supported by stock Python. http://cjkpython.i18n.org/ Beautiful Soup defines classes for two main parsing strategies: * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific language that kind of looks like XML. * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid or invalid. This class has web browser-like heuristics for obtaining a sensible parse tree in the face of common HTML errors. Beautiful Soup also defines a class (UnicodeDammit) for autodetecting the encoding of an HTML or XML document, and converting it to Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser. For more than you ever wanted to know about Beautiful Soup, see the documentation: http://www.crummy.com/software/BeautifulSoup/documentation.html Here, have some legalese: Copyright (c) 2004-2009, Leonard Richardson All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the the Beautiful Soup Consortium and All Night Kosher Bakery nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT. """ from __future__ import generators __author__ = "Leonard Richardson (leonardr@segfault.org)" __version__ = "3.0.8" __copyright__ = "Copyright (c) 2004-2009 Leonard Richardson" __license__ = "New-style BSD" from sgmllib import SGMLParser, SGMLParseError import codecs import markupbase import types import re import sgmllib try: from htmlentitydefs import name2codepoint except ImportError: name2codepoint = {} try: set except NameError: from sets import Set as set #These hacks make Beautiful Soup able to parse XML with namespaces sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') markupbase._declname_match = re.compile(r'[a-zA-Z][-_.:a-zA-Z0-9]*\s*').match DEFAULT_OUTPUT_ENCODING = "utf-8" def _match_css_class(str): """Build a RE to match the given CSS class.""" return re.compile(r"(^|.*\s)%s($|\s)" % str) # First, the classes that represent markup elements. class PageElement(object): """Contains the navigational information for some part of the page (either a tag or a piece of text)""" def setup(self, parent=None, previous=None): """Sets up the initial relations between this element and other elements.""" self.parent = parent self.previous = previous self.next = None self.previousSibling = None self.nextSibling = None if self.parent and self.parent.contents: self.previousSibling = self.parent.contents[-1] self.previousSibling.nextSibling = self def replaceWith(self, replaceWith): oldParent = self.parent myIndex = self.parent.index(self) if hasattr(replaceWith, "parent")\ and replaceWith.parent is self.parent: # We're replacing this element with one of its siblings. index = replaceWith.parent.index(replaceWith) if index and index < myIndex: # Furthermore, it comes before this element. That # means that when we extract it, the index of this # element will change. myIndex = myIndex - 1 self.extract() oldParent.insert(myIndex, replaceWith) def replaceWithChildren(self): myParent = self.parent myIndex = self.parent.index(self) self.extract() reversedChildren = list(self.contents) reversedChildren.reverse() for child in reversedChildren: myParent.insert(myIndex, child) def extract(self): """Destructively rips this element out of the tree.""" if self.parent: try: del self.parent.contents[self.parent.index(self)] except ValueError: pass #Find the two elements that would be next to each other if #this element (and any children) hadn't been parsed. Connect #the two. lastChild = self._lastRecursiveChild() nextElement = lastChild.next if self.previous: self.previous.next = nextElement if nextElement: nextElement.previous = self.previous self.previous = None lastChild.next = None self.parent = None if self.previousSibling: self.previousSibling.nextSibling = self.nextSibling if self.nextSibling: self.nextSibling.previousSibling = self.previousSibling self.previousSibling = self.nextSibling = None return self def _lastRecursiveChild(self): "Finds the last element beneath this object to be parsed." lastChild = self while hasattr(lastChild, 'contents') and lastChild.contents: lastChild = lastChild.contents[-1] return lastChild def insert(self, position, newChild): if isinstance(newChild, basestring) \ and not isinstance(newChild, NavigableString): newChild = NavigableString(newChild) position = min(position, len(self.contents)) if hasattr(newChild, 'parent') and newChild.parent is not None: # We're 'inserting' an element that's already one # of this object's children. if newChild.parent is self: index = self.index(newChild) if index > position: # Furthermore we're moving it further down the # list of this object's children. That means that # when we extract this element, our target index # will jump down one. position = position - 1 newChild.extract() newChild.parent = self previousChild = None if position == 0: newChild.previousSibling = None newChild.previous = self else: previousChild = self.contents[position-1] newChild.previousSibling = previousChild newChild.previousSibling.nextSibling = newChild newChild.previous = previousChild._lastRecursiveChild() if newChild.previous: newChild.previous.next = newChild newChildsLastElement = newChild._lastRecursiveChild() if position >= len(self.contents): newChild.nextSibling = None parent = self parentsNextSibling = None while not parentsNextSibling: parentsNextSibling = parent.nextSibling parent = parent.parent if not parent: # This is the last element in the document. break if parentsNextSibling: newChildsLastElement.next = parentsNextSibling else: newChildsLastElement.next = None else: nextChild = self.contents[position] newChild.nextSibling = nextChild if newChild.nextSibling: newChild.nextSibling.previousSibling = newChild newChildsLastElement.next = nextChild if newChildsLastElement.next: newChildsLastElement.next.previous = newChildsLastElement self.contents.insert(position, newChild) def append(self, tag): """Appends the given tag to the contents of this tag.""" self.insert(len(self.contents), tag) def findNext(self, name=None, attrs={}, text=None, **kwargs): """Returns the first item that matches the given criteria and appears after this Tag in the document.""" return self._findOne(self.findAllNext, name, attrs, text, **kwargs) def findAllNext(self, name=None, attrs={}, text=None, limit=None, **kwargs): """Returns all items that match the given criteria and appear after this Tag in the document.""" return self._findAll(name, attrs, text, limit, self.nextGenerator, **kwargs) def findNextSibling(self, name=None, attrs={}, text=None, **kwargs): """Returns the closest sibling to this Tag that matches the given criteria and appears after this Tag in the document.""" return self._findOne(self.findNextSiblings, name, attrs, text, **kwargs) def findNextSiblings(self, name=None, attrs={}, text=None, limit=None, **kwargs): """Returns the siblings of this Tag that match the given criteria and appear after this Tag in the document.""" return self._findAll(name, attrs, text, limit, self.nextSiblingGenerator, **kwargs) fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x def findPrevious(self, name=None, attrs={}, text=None, **kwargs): """Returns the first item that matches the given criteria and appears before this Tag in the document.""" return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs) def findAllPrevious(self, name=None, attrs={}, text=None, limit=None, **kwargs): """Returns all items that match the given criteria and appear before this Tag in the document.""" return self._findAll(name, attrs, text, limit, self.previousGenerator, **kwargs) fetchPrevious = findAllPrevious # Compatibility with pre-3.x def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs): """Returns the closest sibling to this Tag that matches the given criteria and appears before this Tag in the document.""" return self._findOne(self.findPreviousSiblings, name, attrs, text, **kwargs) def findPreviousSiblings(self, name=None, attrs={}, text=None, limit=None, **kwargs): """Returns the siblings of this Tag that match the given criteria and appear before this Tag in the document.""" return self._findAll(name, attrs, text, limit, self.previousSiblingGenerator, **kwargs) fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x def findParent(self, name=None, attrs={}, **kwargs): """Returns the closest parent of this Tag that matches the given criteria.""" # NOTE: We can't use _findOne because findParents takes a different # set of arguments. r = None l = self.findParents(name, attrs, 1) if l: r = l[0] return r def findParents(self, name=None, attrs={}, limit=None, **kwargs): """Returns the parents of this Tag that match the given criteria.""" return self._findAll(name, attrs, None, limit, self.parentGenerator, **kwargs) fetchParents = findParents # Compatibility with pre-3.x #These methods do the real heavy lifting. def _findOne(self, method, name, attrs, text, **kwargs): r = None l = method(name, attrs, text, 1, **kwargs) if l: r = l[0] return r def _findAll(self, name, attrs, text, limit, generator, **kwargs): "Iterates over a generator looking for things that match." if isinstance(name, SoupStrainer): strainer = name # Special case some findAll* searches # findAll*(True) elif not limit and name is True and not attrs and not kwargs: return [element for element in generator() if isinstance(element, Tag)] # findAll*('tag-name') elif not limit and isinstance(name, basestring) and not attrs \ and not kwargs: return [element for element in generator() if isinstance(element, Tag) and element.name == name] # Build a SoupStrainer else: strainer = SoupStrainer(name, attrs, text, **kwargs) results = ResultSet(strainer) g = generator() while True: try: i = g.next() except StopIteration: break if i: found = strainer.search(i) if found: results.append(found) if limit and len(results) >= limit: break return results #These Generators can be used to navigate starting from both #NavigableStrings and Tags. def nextGenerator(self): i = self while i is not None: i = i.next yield i def nextSiblingGenerator(self): i = self while i is not None: i = i.nextSibling yield i def previousGenerator(self): i = self while i is not None: i = i.previous yield i def previousSiblingGenerator(self): i = self while i is not None: i = i.previousSibling yield i def parentGenerator(self): i = self while i is not None: i = i.parent yield i # Utility methods def substituteEncoding(self, str, encoding=None): encoding = encoding or "utf-8" return str.replace("%SOUP-ENCODING%", encoding) def toEncoding(self, s, encoding=None): """Encodes an object to a string in some encoding, or to Unicode. .""" if isinstance(s, unicode): if encoding: s = s.encode(encoding) elif isinstance(s, str): if encoding: s = s.encode(encoding) else: s = unicode(s) else: if encoding: s = self.toEncoding(str(s), encoding) else: s = unicode(s) return s class NavigableString(unicode, PageElement): def __new__(cls, value): """Create a new NavigableString. When unpickling a NavigableString, this method is called with the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be passed in to the superclass's __new__ or the superclass won't know how to handle non-ASCII characters. """ if isinstance(value, unicode): return unicode.__new__(cls, value) return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING) def __getnewargs__(self): return (NavigableString.__str__(self),) def __getattr__(self, attr): """text.string gives you text. This is for backwards compatibility for Navigable*String, but for CData* it lets you get the string without the CData wrapper.""" if attr == 'string': return self else: raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr) def __unicode__(self): return str(self).decode(DEFAULT_OUTPUT_ENCODING) def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): if encoding: return self.encode(encoding) else: return self class CData(NavigableString): def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): return "" % NavigableString.__str__(self, encoding) class ProcessingInstruction(NavigableString): def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): output = self if "%SOUP-ENCODING%" in output: output = self.substituteEncoding(output, encoding) return "" % self.toEncoding(output, encoding) class Comment(NavigableString): def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): return "" % NavigableString.__str__(self, encoding) class Declaration(NavigableString): def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): return "" % NavigableString.__str__(self, encoding) class Tag(PageElement): """Represents a found HTML tag with its attributes and contents.""" def _invert(h): "Cheap function to invert a hash." i = {} for k,v in h.items(): i[v] = k return i XML_ENTITIES_TO_SPECIAL_CHARS = { "apos" : "'", "quot" : '"', "amp" : "&", "lt" : "<", "gt" : ">" } XML_SPECIAL_CHARS_TO_ENTITIES = _invert(XML_ENTITIES_TO_SPECIAL_CHARS) def _convertEntities(self, match): """Used in a call to re.sub to replace HTML, XML, and numeric entities with the appropriate Unicode characters. If HTML entities are being converted, any unrecognized entities are escaped.""" x = match.group(1) if self.convertHTMLEntities and x in name2codepoint: return unichr(name2codepoint[x]) elif x in self.XML_ENTITIES_TO_SPECIAL_CHARS: if self.convertXMLEntities: return self.XML_ENTITIES_TO_SPECIAL_CHARS[x] else: return u'&%s;' % x elif len(x) > 0 and x[0] == '#': # Handle numeric entities if len(x) > 1 and x[1] == 'x': return unichr(int(x[2:], 16)) else: return unichr(int(x[1:])) elif self.escapeUnrecognizedEntities: return u'&%s;' % x else: return u'&%s;' % x def __init__(self, parser, name, attrs=None, parent=None, previous=None): "Basic constructor." # We don't actually store the parser object: that lets extracted # chunks be garbage-collected self.parserClass = parser.__class__ self.isSelfClosing = parser.isSelfClosingTag(name) self.name = name if attrs is None: attrs = [] self.attrs = attrs self.contents = [] self.setup(parent, previous) self.hidden = False self.containsSubstitutions = False self.convertHTMLEntities = parser.convertHTMLEntities self.convertXMLEntities = parser.convertXMLEntities self.escapeUnrecognizedEntities = parser.escapeUnrecognizedEntities # Convert any HTML, XML, or numeric entities in the attribute values. convert = lambda(k, val): (k, re.sub("&(#\d+|#x[0-9a-fA-F]+|\w+);", self._convertEntities, val)) self.attrs = map(convert, self.attrs) def getString(self): if (len(self.contents) == 1 and isinstance(self.contents[0], NavigableString)): return self.contents[0] def setString(self, string): """Replace the contents of the tag with a string""" self.clear() self.append(string) string = property(getString, setString) def getText(self, separator=u""): if not len(self.contents): return u"" stopNode = self._lastRecursiveChild().next strings = [] current = self.contents[0] while current is not stopNode: if isinstance(current, NavigableString): strings.append(current.strip()) current = current.next return separator.join(strings) text = property(getText) def get(self, key, default=None): """Returns the value of the 'key' attribute for the tag, or the value given for 'default' if it doesn't have that attribute.""" return self._getAttrMap().get(key, default) def clear(self): """Extract all children.""" for child in self.contents[:]: child.extract() def index(self, element): for i, child in enumerate(self.contents): if child is element: return i raise ValueError("Tag.index: element not in tag") def has_key(self, key): return self._getAttrMap().has_key(key) def __getitem__(self, key): """tag[key] returns the value of the 'key' attribute for the tag, and throws an exception if it's not there.""" return self._getAttrMap()[key] def __iter__(self): "Iterating over a tag iterates over its contents." return iter(self.contents) def __len__(self): "The length of a tag is the length of its list of contents." return len(self.contents) def __contains__(self, x): return x in self.contents def __nonzero__(self): "A tag is non-None even if it has no contents." return True def __setitem__(self, key, value): """Setting tag[key] sets the value of the 'key' attribute for the tag.""" self._getAttrMap() self.attrMap[key] = value found = False for i in range(0, len(self.attrs)): if self.attrs[i][0] == key: self.attrs[i] = (key, value) found = True if not found: self.attrs.append((key, value)) self._getAttrMap()[key] = value def __delitem__(self, key): "Deleting tag[key] deletes all 'key' attributes for the tag." for item in self.attrs: if item[0] == key: self.attrs.remove(item) #We don't break because bad HTML can define the same #attribute multiple times. self._getAttrMap() if self.attrMap.has_key(key): del self.attrMap[key] def __call__(self, *args, **kwargs): """Calling a tag like a function is the same as calling its findAll() method. Eg. tag('a') returns a list of all the A tags found within this tag.""" return apply(self.findAll, args, kwargs) def __getattr__(self, tag): #print "Getattr %s.%s" % (self.__class__, tag) if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3: return self.find(tag[:-3]) elif tag.find('__') != 0: return self.find(tag) raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__, tag) def __eq__(self, other): """Returns true iff this tag has the same name, the same attributes, and the same contents (recursively) as the given tag. NOTE: right now this will return false if two tags have the same attributes in a different order. Should this be fixed?""" if other is self: return True if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other): return False for i in range(0, len(self.contents)): if self.contents[i] != other.contents[i]: return False return True def __ne__(self, other): """Returns true iff this tag is not identical to the other tag, as defined in __eq__.""" return not self == other def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING): """Renders this tag as a string.""" return self.__str__(encoding) def __unicode__(self): return self.__str__(None) BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|" + "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)" + ")") def _sub_entity(self, x): """Used with a regular expression to substitute the appropriate XML entity for an XML special character.""" return "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";" def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING, prettyPrint=False, indentLevel=0): """Returns a string or Unicode representation of this tag and its contents. To get Unicode, pass None for encoding. NOTE: since Python's HTML parser consumes whitespace, this method is not certain to reproduce the whitespace present in the original string.""" encodedName = self.toEncoding(self.name, encoding) attrs = [] if self.attrs: for key, val in self.attrs: fmt = '%s="%s"' if isinstance(val, basestring): if self.containsSubstitutions and '%SOUP-ENCODING%' in val: val = self.substituteEncoding(val, encoding) # The attribute value either: # # * Contains no embedded double quotes or single quotes. # No problem: we enclose it in double quotes. # * Contains embedded single quotes. No problem: # double quotes work here too. # * Contains embedded double quotes. No problem: # we enclose it in single quotes. # * Embeds both single _and_ double quotes. This # can't happen naturally, but it can happen if # you modify an attribute value after parsing # the document. Now we have a bit of a # problem. We solve it by enclosing the # attribute in single quotes, and escaping any # embedded single quotes to XML entities. if '"' in val: fmt = "%s='%s'" if "'" in val: # TODO: replace with apos when # appropriate. val = val.replace("'", "&squot;") # Now we're okay w/r/t quotes. But the attribute # value might also contain angle brackets, or # ampersands that aren't part of entities. We need # to escape those to XML entities too. val = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, val) attrs.append(fmt % (self.toEncoding(key, encoding), self.toEncoding(val, encoding))) close = '' closeTag = '' if self.isSelfClosing: close = ' /' else: closeTag = '' % encodedName indentTag, indentContents = 0, 0 if prettyPrint: indentTag = indentLevel space = (' ' * (indentTag-1)) indentContents = indentTag + 1 contents = self.renderContents(encoding, prettyPrint, indentContents) if self.hidden: s = contents else: s = [] attributeString = '' if attrs: attributeString = ' ' + ' '.join(attrs) if prettyPrint: s.append(space) s.append('<%s%s%s>' % (encodedName, attributeString, close)) if prettyPrint: s.append("\n") s.append(contents) if prettyPrint and contents and contents[-1] != "\n": s.append("\n") if prettyPrint and closeTag: s.append(space) s.append(closeTag) if prettyPrint and closeTag and self.nextSibling: s.append("\n") s = ''.join(s) return s def decompose(self): """Recursively destroys the contents of this tree.""" self.extract() if len(self.contents) == 0: return current = self.contents[0] while current is not None: next = current.next if isinstance(current, Tag): del current.contents[:] current.parent = None current.previous = None current.previousSibling = None current.next = None current.nextSibling = None current = next def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING): return self.__str__(encoding, True) def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, prettyPrint=False, indentLevel=0): """Renders the contents of this tag as a string in the given encoding. If encoding is None, returns a Unicode string..""" s=[] for c in self: text = None if isinstance(c, NavigableString): text = c.__str__(encoding) elif isinstance(c, Tag): s.append(c.__str__(encoding, prettyPrint, indentLevel)) if text and prettyPrint: text = text.strip() if text: if prettyPrint: s.append(" " * (indentLevel-1)) s.append(text) if prettyPrint: s.append("\n") return ''.join(s) #Soup methods def find(self, name=None, attrs={}, recursive=True, text=None, **kwargs): """Return only the first child of this Tag matching the given criteria.""" r = None l = self.findAll(name, attrs, recursive, text, 1, **kwargs) if l: r = l[0] return r findChild = find def findAll(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs): """Extracts a list of Tag objects that match the given criteria. You can specify the name of the Tag and any attributes you want the Tag to have. The value of a key-value pair in the 'attrs' map can be a string, a list of strings, a regular expression object, or a callable that takes a string and returns whether or not the string matches for some custom definition of 'matches'. The same is true of the tag name.""" generator = self.recursiveChildGenerator if not recursive: generator = self.childGenerator return self._findAll(name, attrs, text, limit, generator, **kwargs) findChildren = findAll # Pre-3.x compatibility methods first = find fetch = findAll def fetchText(self, text=None, recursive=True, limit=None): return self.findAll(text=text, recursive=recursive, limit=limit) def firstText(self, text=None, recursive=True): return self.find(text=text, recursive=recursive) #Private methods def _getAttrMap(self): """Initializes a map representation of this tag's attributes, if not already initialized.""" if not getattr(self, 'attrMap'): self.attrMap = {} for (key, value) in self.attrs: self.attrMap[key] = value return self.attrMap #Generator methods def childGenerator(self): # Just use the iterator from the contents return iter(self.contents) def recursiveChildGenerator(self): if not len(self.contents): raise StopIteration stopNode = self._lastRecursiveChild().next current = self.contents[0] while current is not stopNode: yield current current = current.next # Next, a couple classes to represent queries and their results. class SoupStrainer: """Encapsulates a number of ways of matching a markup element (tag or text).""" def __init__(self, name=None, attrs={}, text=None, **kwargs): self.name = name if isinstance(attrs, basestring): kwargs['class'] = _match_css_class(attrs) attrs = None if kwargs: if attrs: attrs = attrs.copy() attrs.update(kwargs) else: attrs = kwargs self.attrs = attrs self.text = text def __str__(self): if self.text: return self.text else: return "%s|%s" % (self.name, self.attrs) def searchTag(self, markupName=None, markupAttrs={}): found = None markup = None if isinstance(markupName, Tag): markup = markupName markupAttrs = markup callFunctionWithTagData = callable(self.name) \ and not isinstance(markupName, Tag) if (not self.name) \ or callFunctionWithTagData \ or (markup and self._matches(markup, self.name)) \ or (not markup and self._matches(markupName, self.name)): if callFunctionWithTagData: match = self.name(markupName, markupAttrs) else: match = True markupAttrMap = None for attr, matchAgainst in self.attrs.items(): if not markupAttrMap: if hasattr(markupAttrs, 'get'): markupAttrMap = markupAttrs else: markupAttrMap = {} for k,v in markupAttrs: markupAttrMap[k] = v attrValue = markupAttrMap.get(attr) if not self._matches(attrValue, matchAgainst): match = False break if match: if markup: found = markup else: found = markupName return found def search(self, markup): #print 'looking for %s in %s' % (self, markup) found = None # If given a list of items, scan it for a text element that # matches. if hasattr(markup, "__iter__") \ and not isinstance(markup, Tag): for element in markup: if isinstance(element, NavigableString) \ and self.search(element): found = element break # If it's a Tag, make sure its name or attributes match. # Don't bother with Tags if we're searching for text. elif isinstance(markup, Tag): if not self.text: found = self.searchTag(markup) # If it's text, make sure the text matches. elif isinstance(markup, NavigableString) or \ isinstance(markup, basestring): if self._matches(markup, self.text): found = markup else: raise Exception, "I don't know how to match against a %s" \ % markup.__class__ return found def _matches(self, markup, matchAgainst): #print "Matching %s against %s" % (markup, matchAgainst) result = False if matchAgainst is True: result = markup is not None elif callable(matchAgainst): result = matchAgainst(markup) else: #Custom match methods take the tag as an argument, but all #other ways of matching match the tag name as a string. if isinstance(markup, Tag): markup = markup.name if markup and not isinstance(markup, basestring): markup = unicode(markup) #Now we know that chunk is either a string, or None. if hasattr(matchAgainst, 'match'): # It's a regexp object. result = markup and matchAgainst.search(markup) elif hasattr(matchAgainst, '__iter__'): # list-like result = markup in matchAgainst elif hasattr(matchAgainst, 'items'): result = markup.has_key(matchAgainst) elif matchAgainst and isinstance(markup, basestring): if isinstance(markup, unicode): matchAgainst = unicode(matchAgainst) else: matchAgainst = str(matchAgainst) if not result: result = matchAgainst == markup return result class ResultSet(list): """A ResultSet is just a list that keeps track of the SoupStrainer that created it.""" def __init__(self, source): list.__init__([]) self.source = source # Now, some helper functions. def buildTagMap(default, *args): """Turns a list of maps, lists, or scalars into a single map. Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and NESTING_RESET_TAGS maps out of lists and partial maps.""" built = {} for portion in args: if hasattr(portion, 'items'): #It's a map. Merge it. for k,v in portion.items(): built[k] = v elif hasattr(portion, '__iter__'): # is a list #It's a list. Map each item to the default. for k in portion: built[k] = default else: #It's a scalar. Map it to the default. built[portion] = default return built # Now, the parser classes. class BeautifulStoneSoup(Tag, SGMLParser): """This class contains the basic parser and search code. It defines a parser that knows nothing about tag behavior except for the following: You can't close a tag without closing all the tags it encloses. That is, "" actually means "". [Another possible explanation is "", but since this class defines no SELF_CLOSING_TAGS, it will never use that explanation.] This class is useful for parsing XML or made-up markup languages, or when BeautifulSoup makes an assumption counter to what you were expecting.""" SELF_CLOSING_TAGS = {} NESTABLE_TAGS = {} RESET_NESTING_TAGS = {} QUOTE_TAGS = {} PRESERVE_WHITESPACE_TAGS = [] MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'), lambda x: x.group(1) + ' />'), (re.compile(']*)>'), lambda x: '') ] ROOT_TAG_NAME = u'[document]' HTML_ENTITIES = "html" XML_ENTITIES = "xml" XHTML_ENTITIES = "xhtml" # TODO: This only exists for backwards-compatibility ALL_ENTITIES = XHTML_ENTITIES # Used when determining whether a text node is all whitespace and # can be replaced with a single space. A text node that contains # fancy Unicode spaces (usually non-breaking) should be left # alone. STRIP_ASCII_SPACES = { 9: None, 10: None, 12: None, 13: None, 32: None, } def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None, markupMassage=True, smartQuotesTo=XML_ENTITIES, convertEntities=None, selfClosingTags=None, isHTML=False): """The Soup object is initialized as the 'root tag', and the provided markup (which can be a string or a file-like object) is fed into the underlying parser. sgmllib will process most bad HTML, and the BeautifulSoup class has some tricks for dealing with some HTML that kills sgmllib, but Beautiful Soup can nonetheless choke or lose data if your data uses self-closing tags or declarations incorrectly. By default, Beautiful Soup uses regexes to sanitize input, avoiding the vast majority of these problems. If the problems don't apply to you, pass in False for markupMassage, and you'll get better performance. The default parser massage techniques fix the two most common instances of invalid HTML that choke sgmllib:
    (No space between name of closing tag and tag close) (Extraneous whitespace in declaration) You can pass in a custom list of (RE object, replace method) tuples to get Beautiful Soup to scrub your input the way you want.""" self.parseOnlyThese = parseOnlyThese self.fromEncoding = fromEncoding self.smartQuotesTo = smartQuotesTo self.convertEntities = convertEntities # Set the rules for how we'll deal with the entities we # encounter if self.convertEntities: # It doesn't make sense to convert encoded characters to # entities even while you're converting entities to Unicode. # Just convert it all to Unicode. self.smartQuotesTo = None if convertEntities == self.HTML_ENTITIES: self.convertXMLEntities = False self.convertHTMLEntities = True self.escapeUnrecognizedEntities = True elif convertEntities == self.XHTML_ENTITIES: self.convertXMLEntities = True self.convertHTMLEntities = True self.escapeUnrecognizedEntities = False elif convertEntities == self.XML_ENTITIES: self.convertXMLEntities = True self.convertHTMLEntities = False self.escapeUnrecognizedEntities = False else: self.convertXMLEntities = False self.convertHTMLEntities = False self.escapeUnrecognizedEntities = False self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags) SGMLParser.__init__(self) if hasattr(markup, 'read'): # It's a file-type object. markup = markup.read() self.markup = markup self.markupMassage = markupMassage try: self._feed(isHTML=isHTML) except StopParsing: pass self.markup = None # The markup can now be GCed def convert_charref(self, name): """This method fixes a bug in Python's SGMLParser.""" try: n = int(name) except ValueError: return if not 0 <= n <= 127 : # ASCII ends at 127, not 255 return return self.convert_codepoint(n) def _feed(self, inDocumentEncoding=None, isHTML=False): # Convert the document to Unicode. markup = self.markup if isinstance(markup, unicode): if not hasattr(self, 'originalEncoding'): self.originalEncoding = None else: dammit = UnicodeDammit\ (markup, [self.fromEncoding, inDocumentEncoding], smartQuotesTo=self.smartQuotesTo, isHTML=isHTML) markup = dammit.unicode self.originalEncoding = dammit.originalEncoding self.declaredHTMLEncoding = dammit.declaredHTMLEncoding if markup: if self.markupMassage: if not hasattr(self.markupMassage, "__iter__"): self.markupMassage = self.MARKUP_MASSAGE for fix, m in self.markupMassage: markup = fix.sub(m, markup) # TODO: We get rid of markupMassage so that the # soup object can be deepcopied later on. Some # Python installations can't copy regexes. If anyone # was relying on the existence of markupMassage, this # might cause problems. del(self.markupMassage) self.reset() SGMLParser.feed(self, markup) # Close out any unfinished strings and close all the open tags. self.endData() while self.currentTag.name != self.ROOT_TAG_NAME: self.popTag() def __getattr__(self, methodName): """This method routes method call requests to either the SGMLParser superclass or the Tag superclass, depending on the method name.""" #print "__getattr__ called on %s.%s" % (self.__class__, methodName) if methodName.startswith('start_') or methodName.startswith('end_') \ or methodName.startswith('do_'): return SGMLParser.__getattr__(self, methodName) elif not methodName.startswith('__'): return Tag.__getattr__(self, methodName) else: raise AttributeError def isSelfClosingTag(self, name): """Returns true iff the given string is the name of a self-closing tag according to this parser.""" return self.SELF_CLOSING_TAGS.has_key(name) \ or self.instanceSelfClosingTags.has_key(name) def reset(self): Tag.__init__(self, self, self.ROOT_TAG_NAME) self.hidden = 1 SGMLParser.reset(self) self.currentData = [] self.currentTag = None self.tagStack = [] self.quoteStack = [] self.pushTag(self) def popTag(self): tag = self.tagStack.pop() #print "Pop", tag.name if self.tagStack: self.currentTag = self.tagStack[-1] return self.currentTag def pushTag(self, tag): #print "Push", tag.name if self.currentTag: self.currentTag.contents.append(tag) self.tagStack.append(tag) self.currentTag = self.tagStack[-1] def endData(self, containerClass=NavigableString): if self.currentData: currentData = u''.join(self.currentData) if (currentData.translate(self.STRIP_ASCII_SPACES) == '' and not set([tag.name for tag in self.tagStack]).intersection( self.PRESERVE_WHITESPACE_TAGS)): if '\n' in currentData: currentData = '\n' else: currentData = ' ' self.currentData = [] if self.parseOnlyThese and len(self.tagStack) <= 1 and \ (not self.parseOnlyThese.text or \ not self.parseOnlyThese.search(currentData)): return o = containerClass(currentData) o.setup(self.currentTag, self.previous) if self.previous: self.previous.next = o self.previous = o self.currentTag.contents.append(o) def _popToTag(self, name, inclusivePop=True): """Pops the tag stack up to and including the most recent instance of the given tag. If inclusivePop is false, pops the tag stack up to but *not* including the most recent instqance of the given tag.""" #print "Popping to %s" % name if name == self.ROOT_TAG_NAME: return numPops = 0 mostRecentTag = None for i in range(len(self.tagStack)-1, 0, -1): if name == self.tagStack[i].name: numPops = len(self.tagStack)-i break if not inclusivePop: numPops = numPops - 1 for i in range(0, numPops): mostRecentTag = self.popTag() return mostRecentTag def _smartPop(self, name): """We need to pop up to the previous tag of this type, unless one of this tag's nesting reset triggers comes between this tag and the previous tag of this type, OR unless this tag is a generic nesting trigger and another generic nesting trigger comes between this tag and the previous tag of this type. Examples:

    FooBar *

    * should pop to 'p', not 'b'.

    FooBar *

    * should pop to 'table', not 'p'.

    Foo

    Bar *

    * should pop to 'tr', not 'p'.

    • *
    • * should pop to 'ul', not the first 'li'.
  • ** should pop to 'table', not the first 'tr' tag should implicitly close the previous tag within the same
    ** should pop to 'tr', not the first 'td' """ nestingResetTriggers = self.NESTABLE_TAGS.get(name) isNestable = nestingResetTriggers != None isResetNesting = self.RESET_NESTING_TAGS.has_key(name) popTo = None inclusive = True for i in range(len(self.tagStack)-1, 0, -1): p = self.tagStack[i] if (not p or p.name == name) and not isNestable: #Non-nestable tags get popped to the top or to their #last occurance. popTo = name break if (nestingResetTriggers is not None and p.name in nestingResetTriggers) \ or (nestingResetTriggers is None and isResetNesting and self.RESET_NESTING_TAGS.has_key(p.name)): #If we encounter one of the nesting reset triggers #peculiar to this tag, or we encounter another tag #that causes nesting to reset, pop up to but not #including that tag. popTo = p.name inclusive = False break p = p.parent if popTo: self._popToTag(popTo, inclusive) def unknown_starttag(self, name, attrs, selfClosing=0): #print "Start tag %s: %s" % (name, attrs) if self.quoteStack: #This is not a real tag. #print "<%s> is not real!" % name attrs = ''.join([' %s="%s"' % (x, y) for x, y in attrs]) self.handle_data('<%s%s>' % (name, attrs)) return self.endData() if not self.isSelfClosingTag(name) and not selfClosing: self._smartPop(name) if self.parseOnlyThese and len(self.tagStack) <= 1 \ and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)): return tag = Tag(self, name, attrs, self.currentTag, self.previous) if self.previous: self.previous.next = tag self.previous = tag self.pushTag(tag) if selfClosing or self.isSelfClosingTag(name): self.popTag() if name in self.QUOTE_TAGS: #print "Beginning quote (%s)" % name self.quoteStack.append(name) self.literal = 1 return tag def unknown_endtag(self, name): #print "End tag %s" % name if self.quoteStack and self.quoteStack[-1] != name: #This is not a real end tag. #print " is not real!" % name self.handle_data('' % name) return self.endData() self._popToTag(name) if self.quoteStack and self.quoteStack[-1] == name: self.quoteStack.pop() self.literal = (len(self.quoteStack) > 0) def handle_data(self, data): self.currentData.append(data) def _toStringSubclass(self, text, subclass): """Adds a certain piece of text to the tree as a NavigableString subclass.""" self.endData() self.handle_data(text) self.endData(subclass) def handle_pi(self, text): """Handle a processing instruction as a ProcessingInstruction object, possibly one with a %SOUP-ENCODING% slot into which an encoding will be plugged later.""" if text[:3] == "xml": text = u"xml version='1.0' encoding='%SOUP-ENCODING%'" self._toStringSubclass(text, ProcessingInstruction) def handle_comment(self, text): "Handle comments as Comment objects." self._toStringSubclass(text, Comment) def handle_charref(self, ref): "Handle character references as data." if self.convertEntities: data = unichr(int(ref)) else: data = '&#%s;' % ref self.handle_data(data) def handle_entityref(self, ref): """Handle entity references as data, possibly converting known HTML and/or XML entity references to the corresponding Unicode characters.""" data = None if self.convertHTMLEntities: try: data = unichr(name2codepoint[ref]) except KeyError: pass if not data and self.convertXMLEntities: data = self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref) if not data and self.convertHTMLEntities and \ not self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref): # TODO: We've got a problem here. We're told this is # an entity reference, but it's not an XML entity # reference or an HTML entity reference. Nonetheless, # the logical thing to do is to pass it through as an # unrecognized entity reference. # # Except: when the input is "&carol;" this function # will be called with input "carol". When the input is # "AT&T", this function will be called with input # "T". We have no way of knowing whether a semicolon # was present originally, so we don't know whether # this is an unknown entity or just a misplaced # ampersand. # # The more common case is a misplaced ampersand, so I # escape the ampersand and omit the trailing semicolon. data = "&%s" % ref if not data: # This case is different from the one above, because we # haven't already gone through a supposedly comprehensive # mapping of entities to Unicode characters. We might not # have gone through any mapping at all. So the chances are # very high that this is a real entity, and not a # misplaced ampersand. data = "&%s;" % ref self.handle_data(data) def handle_decl(self, data): "Handle DOCTYPEs and the like as Declaration objects." self._toStringSubclass(data, Declaration) def parse_declaration(self, i): """Treat a bogus SGML declaration as raw data. Treat a CDATA declaration as a CData object.""" j = None if self.rawdata[i:i+9] == '', i) if k == -1: k = len(self.rawdata) data = self.rawdata[i+9:k] j = k+3 self._toStringSubclass(data, CData) else: try: j = SGMLParser.parse_declaration(self, i) except SGMLParseError: toHandle = self.rawdata[i:] self.handle_data(toHandle) j = i + len(toHandle) return j class BeautifulSoup(BeautifulStoneSoup): """This parser knows the following facts about HTML: * Some tags have no closing tag and should be interpreted as being closed as soon as they are encountered. * The text inside some tags (ie. 'script') may contain tags which are not really part of the document and which should be parsed as text, not tags. If you want to parse the text as tags, you can always fetch it and parse it explicitly. * Tag nesting rules: Most tags can't be nested at all. For instance, the occurance of a

    tag should implicitly close the previous

    tag.

    Para1

    Para2 should be transformed into:

    Para1

    Para2 Some tags can be nested arbitrarily. For instance, the occurance of a

    tag should _not_ implicitly close the previous
    tag. Alice said:
    Bob said:
    Blah should NOT be transformed into: Alice said:
    Bob said:
    Blah Some tags can be nested, but the nesting is reset by the interposition of other tags. For instance, a
    , but not close a tag in another table.
    BlahBlah should be transformed into:
    BlahBlah but, Blah
    Blah should NOT be transformed into Blah
    Blah Differing assumptions about tag nesting rules are a major source of problems with the BeautifulSoup class. If BeautifulSoup is not treating as nestable a tag your page author treats as nestable, try ICantBelieveItsBeautifulSoup, MinimalSoup, or BeautifulStoneSoup before writing your own subclass.""" def __init__(self, *args, **kwargs): if not kwargs.has_key('smartQuotesTo'): kwargs['smartQuotesTo'] = self.HTML_ENTITIES kwargs['isHTML'] = True BeautifulStoneSoup.__init__(self, *args, **kwargs) SELF_CLOSING_TAGS = buildTagMap(None, ('br' , 'hr', 'input', 'img', 'meta', 'spacer', 'link', 'frame', 'base', 'col')) PRESERVE_WHITESPACE_TAGS = set(['pre', 'textarea']) QUOTE_TAGS = {'script' : None, 'textarea' : None} #According to the HTML standard, each of these inline tags can #contain another tag of the same type. Furthermore, it's common #to actually use these tags this way. NESTABLE_INLINE_TAGS = ('span', 'font', 'q', 'object', 'bdo', 'sub', 'sup', 'center') #According to the HTML standard, these block tags can contain #another tag of the same type. Furthermore, it's common #to actually use these tags this way. NESTABLE_BLOCK_TAGS = ('blockquote', 'div', 'fieldset', 'ins', 'del') #Lists can contain other lists, but there are restrictions. NESTABLE_LIST_TAGS = { 'ol' : [], 'ul' : [], 'li' : ['ul', 'ol'], 'dl' : [], 'dd' : ['dl'], 'dt' : ['dl'] } #Tables can contain other tables, but there are restrictions. NESTABLE_TABLE_TAGS = {'table' : [], 'tr' : ['table', 'tbody', 'tfoot', 'thead'], 'td' : ['tr'], 'th' : ['tr'], 'thead' : ['table'], 'tbody' : ['table'], 'tfoot' : ['table'], } NON_NESTABLE_BLOCK_TAGS = ('address', 'form', 'p', 'pre') #If one of these tags is encountered, all tags up to the next tag of #this type are popped. RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript', NON_NESTABLE_BLOCK_TAGS, NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS, NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) # Used to detect the charset in a META tag; see start_meta CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M) def start_meta(self, attrs): """Beautiful Soup can detect a charset included in a META tag, try to convert the document to that charset, and re-parse the document from the beginning.""" httpEquiv = None contentType = None contentTypeIndex = None tagNeedsEncodingSubstitution = False for i in range(0, len(attrs)): key, value = attrs[i] key = key.lower() if key == 'http-equiv': httpEquiv = value elif key == 'content': contentType = value contentTypeIndex = i if httpEquiv and contentType: # It's an interesting meta tag. match = self.CHARSET_RE.search(contentType) if match: if (self.declaredHTMLEncoding is not None or self.originalEncoding == self.fromEncoding): # An HTML encoding was sniffed while converting # the document to Unicode, or an HTML encoding was # sniffed during a previous pass through the # document, or an encoding was specified # explicitly and it worked. Rewrite the meta tag. def rewrite(match): return match.group(1) + "%SOUP-ENCODING%" newAttr = self.CHARSET_RE.sub(rewrite, contentType) attrs[contentTypeIndex] = (attrs[contentTypeIndex][0], newAttr) tagNeedsEncodingSubstitution = True else: # This is our first pass through the document. # Go through it again with the encoding information. newCharset = match.group(3) if newCharset and newCharset != self.originalEncoding: self.declaredHTMLEncoding = newCharset self._feed(self.declaredHTMLEncoding) raise StopParsing pass tag = self.unknown_starttag("meta", attrs) if tag and tagNeedsEncodingSubstitution: tag.containsSubstitutions = True class StopParsing(Exception): pass class ICantBelieveItsBeautifulSoup(BeautifulSoup): """The BeautifulSoup class is oriented towards skipping over common HTML errors like unclosed tags. However, sometimes it makes errors of its own. For instance, consider this fragment: FooBar This is perfectly valid (if bizarre) HTML. However, the BeautifulSoup class will implicitly close the first b tag when it encounters the second 'b'. It will think the author wrote "FooBar", and didn't close the first 'b' tag, because there's no real-world reason to bold something that's already bold. When it encounters '' it will close two more 'b' tags, for a grand total of three tags closed instead of two. This can throw off the rest of your document structure. The same is true of a number of other tags, listed below. It's much more common for someone to forget to close a 'b' tag than to actually use nested 'b' tags, and the BeautifulSoup class handles the common case. This class handles the not-co-common case: where you can't believe someone wrote what they did, but it's valid HTML and BeautifulSoup screwed up by assuming it wouldn't be.""" I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \ ('em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong', 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b', 'big') I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ('noscript') NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS, I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS, I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS) class MinimalSoup(BeautifulSoup): """The MinimalSoup class is for parsing HTML that contains pathologically bad markup. It makes no assumptions about tag nesting, but it does know which tags are self-closing, that