nvpy-0.9.5~git20130806.orig/0000755000000000000000000000000012224270617013707 5ustar rootrootnvpy-0.9.5~git20130806.orig/README.rst0000644000000000000000000002174512224270617015407 0ustar rootroot==== nvPY ==== nvpy is a simplenote-syncing note-taking tool inspired by Notational Velocity (and a little bit by nvALT too) on OSX and ResophNotes on Windows. It is significantly uglier, but it is cross-platform. Yes, you heard right, you can run this on Linux (tested), Windows (tested) and OS X (lightly tested). It was written by Charl Botha, who needed a simplenote client on Linux and doesn't mind ugliness (that much). Sjaak Westdijk has contributed significantly to the codebase since right after the 0.8.5 release. * nvpy lives happily at https://github.com/cpbotha/nvpy * For news and discussion, join the `public nvpy google group `_ or subscribe to its `RSS topic feed `_. DISCLAIMER ========== If nvpy blows up your computer, loses your job or just deletes all your notes, I am NOT liable for anything. Also see the liability clause at the end of the new BSD licence text in the COPYRIGHT file. That being said, I use nvpy daily on my own precious notes database and it hasn't disappointed me (yet). Screenshots and screencasts =========================== * Screenshot taken shortly after 0.9.3 release: .. image:: https://lh4.googleusercontent.com/-ASCgH2VhYmc/UIOlIWLvYVI/AAAAAAAAQ8s/0ccEQLHXKIg/s800/nvpy_post_0.9.2_screenshot.png * Screencast of nvpy's inter-note linking (May 27, 2012): http://youtu.be/NXuVMZr31SI * Screencast of nvpy's gstyle search mode (October 18, 2012): http://youtu.be/dzILoLC5vRM * `Picasa Web album containing various screenshots over time `_. A note on automatic syncing =========================== * When nvPY starts up, it automatically performs a full sync. When you start it up for the first time, this can take quite a while. On subsquent startups, it's much faster, as it maintains its own database on disk. * While running, nvPY automatically and continuously saves and syncs any changes to disk and to simplenote. You don't have to do anything besides typing your notes. * If you edit the same note simultaneously in nvPY and for example the web interface, these changes will be merged as you work. * If you add or delete notes from a completely different location, nvPY will not pick this up until your next full sync. In the future, this will also happen automatically. * In short: You usually don't have to worry about syncing and saving, simplenote takes care of this. If you have any more questions, please post them in the `nvpy google group `_. Installation ============ nvPY works best on Python 2.7.x. It does not work on Python 3.x yet. To install the latest development version from github, do:: pip install git+https://github.com/cpbotha/nvpy.git#egg=nvpy OR, to install the version currently on pypi, do:: pip install nvpy If already have nvpy installed, but you want to upgrade, try the following:: sudo pip uninstall nvpy sudo pip install --upgrade --ignore-installed --no-deps nvpy OR, you can of course use easy\_install instead:: easy_install nvpy github always has the latest development version, whereas I upload tagged snapshots (v0.9 for example) to pypi. For more detailed installation recipes, also for beginners, and for instructions on how to integrate nvPY with your Linux desktop environment, see the `nvPY installation guide `_. How to run for the first time ============================= Create a file called .nvpy.cfg in your home directory that looks like the following:: [nvpy] sn_username = your_simplenote_username sn_password = your_simplenote_password If you installed this via pip install, you should now be able to start the application by typing "nvpy". The first time you run it, it will take a while as it downloads all of your simplenote notes. Subsequent runs are much faster as it uses the database it stores in your home directory. If you prefer to run from your git clone, you can just invoke python on nvpy.py, or on the nvpy package directory. The `example nvpy.cfg `_ shows how you can configure the font family and size, configure nvpy to save and load notes as clear text, disable simplenote syncing, and so forth. Keyboard handling ================= nvPY was designed for lightning-speed note-taking and management with the keyboard. As you type words in the search bar, the list of notes found will be refined. In the default search mode ("gstyle"), it finds notes that contain all the words you enter. For example:: t:work t:leads python imaging "exact phrase" Will find all notes tagged with both "work" and "leads" containing the words "python" and "imaging" (anywhere, and in any order) and the exact phrase "exact phrase". The default is to search with case-sensitivity. This can be changed with the CS checkbox. Remember though that case-sensitivity has a significant effect on search speed. By editing the config file, or by toggling the search mode option menu, you can use regular expression search mode. This is of course much more powerful, but is much slower than gstyle. The difference is noticeable on large note collections. Here's a summary of the different shortcut keys that you can use in nvPY: ========== ========== Key combo Action ========== ========== Ctrl-? Display these key-bindings. Ctrl-A Select all text when in the note editor. Ctrl-D Move note to trash. This can be easily recovered using the simplenote webapp. Ctrl-F Start real-time incremental regular expression search. As you type, notes list is filtered. Up / down cursor keys go to previous / next note. Ctrl-G Edit tags for currently selected note. Press ESC to return to note editing. Ctrl-M Render Markdown note to HTML and open browser window. Ctrl-N Create new note. Ctrl-Q Exit nvPY. Ctrl-R Render reStructuredText (reST) note to HTML and open browser window. Ctrl-S Force sync of current note with simplenote server. Saving to disc and syncing to server also happen continuously in the background. Ctrl-Y Redo note edits. Ctrl-Z Undo note edits. Ctrl-SPACE In search box, autocomplete tag under cursor. Keep on pressing for more alternatives. Ctrl-+/- Increase or decrease the font size. ESC Go from edit mode to notes list. ENTER Start editing currently selected note. If there's a search string but no notes in the list, ENTER creates a new note with that search string as its title. ========== ========== Features ======== * Syncs with simplenote. * Support for simplenote tags and note pinning. * Partial syncs (whilst notes are being edited) are done by a background thread so you can keep on working at light speed. * Can be used offline, also without simplenote account. * Search box does realtime gstyle or regular expression searching in all your notes. All occurrences of the search string are also highlighted in currently active note. * Markdown rendering to browser. * Continuous rendering mode: If you activate this before starting the markdown rendering, nvpy will render new html of the currently open note every few seconds. Due to the refresh tag in the generated HTML, the browser will refresh every few seconds. MAGIC UPDATES! * reStructuredText (reST) rendering to browser. Yes, you can use nvPY as your reST previewer. * Automatic hyperlink highlighting in text widget. * KickAss(tm) inter-note linking with [[note name]]. If note name is not found in current list of notes, assumes it's a regular expression and sets it in the search bar. See the `screencast `_. Planned features ================ * Full(ish) screen mode. * Full syncs also in background thread. At the moment does a full sync at startup, which can take a while. nvpy already does background thread saving and syncing while you work, so everything you type is backed up within a few seconds of you typing it. * Prettiness. Bugs and feedback ================= * Report bugs with `the github issue tracker `_. * It's an even better idea to clone, fix and then send me a pull request. * If you have questions, or would like to discuss nvpy-related matters, please do so via the `nvpy google discussion group / mailing list `_. * If you really like nvpy, you could make me and you even happier by `tipping me with paypal `_! Credits ======= * Sjaak Westdijk made significant contributions to the code starting after the 0.8.5 release. * nvpy uses the `fantastic simplenote.py library by mrtazz `_. * The brilliant application icon, a blue mini car (not as fast as the notational velocity rocket, get it?), is by `Cemagraphics `_. * Thanks for the tips! stfa and https://github.com/gudnm nvpy-0.9.5~git20130806.orig/setup.py0000644000000000000000000000243612224270617015426 0ustar rootrootimport os from setuptools import setup from nvpy import nvpy # Utility function to read the README file. # Used for the long_description. It's nice, because now 1) we have a top level # README file and 2) it's easier to type in the README file than to put a raw # string in below ... def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name = "nvpy", version = nvpy.VERSION, author = "Charl P. Botha", author_email = "cpbotha@vxlabs.com", description = "A cross-platform simplenote-syncing note-taking app inspired by Notational Velocity.", license = "BSD", keywords = "simplenote note-taking tkinter nvalt markdown", url = "https://github.com/cpbotha/nvpy", packages=['nvpy'], long_description=read('README.rst'), install_requires = ['Markdown', 'docutils'], entry_points = { 'gui_scripts' : ['nvpy = nvpy.nvpy:main'] }, # use MANIFEST.in file # because package_data is ignored during sdist include_package_data=True, classifiers=[ "Development Status :: 3 - Alpha", "Environment :: X11 Applications", "Environment :: MacOS X", "Environment :: Win32 (MS Windows)", "Topic :: Utilities", "License :: OSI Approved :: BSD License", ], ) nvpy-0.9.5~git20130806.orig/nvpy/0000755000000000000000000000000012224270617014703 5ustar rootrootnvpy-0.9.5~git20130806.orig/nvpy/view.py0000644000000000000000000015464712224270617016250 0ustar rootroot# nvPY: cross-platform note-taking app with simplenote syncing # copyright 2012 by Charl P. Botha # new BSD license import copy import logging import os import re import search_entry import sys import tk import tkFont import tkMessageBox import utils import webbrowser from datetime import datetime class WidgetRedirector: """Support for redirecting arbitrary widget subcommands.""" def __init__(self, widget): self.dict = {} self.widget = widget self.tk = tk = widget.tk w = widget._w self.orig = w + "_orig" tk.call("rename", w, self.orig) tk.createcommand(w, self.dispatch) def __repr__(self): return "WidgetRedirector(%s<%s>)" % (self.widget.__class__.__name__, self.widget._w) def close(self): for name in self.dict.keys(): self.unregister(name) widget = self.widget; del self.widget orig = self.orig; del self.orig tk = widget.tk w = widget._w tk.deletecommand(w) tk.call("rename", orig, w) def register(self, name, function): if self.dict.has_key(name): previous = dict[name] else: previous = OriginalCommand(self, name) self.dict[name] = function setattr(self.widget, name, function) return previous def unregister(self, name): if self.dict.has_key(name): function = self.dict[name] del self.dict[name] if hasattr(self.widget, name): delattr(self.widget, name) return function else: return None def dispatch(self, cmd, *args): m = self.dict.get(cmd) try: if m: return m(*args) else: return self.tk.call((self.orig, cmd) + args) except tk.TclError: return "" class OriginalCommand: def __init__(self, redir, name): self.redir = redir self.name = name self.tk = redir.tk self.orig = redir.orig self.tk_call = self.tk.call self.orig_and_name = (self.orig, self.name) def __repr__(self): return "OriginalCommand(%r, %r)" % (self.redir, self.name) def __call__(self, *args): return self.tk_call(self.orig_and_name + args) ######################################################################### class RedirectedText(tk.Text): """We would like to know when the Text widget's contents change. We can't just override the insert method, we have to make use of some Tk magic. This magic is encapsulated in the idlelib.WidgetRedirector class which we use here. """ def __init__(self, master=None, cnf={}, **kw): tk.Text.__init__(self, master, cnf, **kw) # now attach the redirector self.redir = WidgetRedirector(self) self.orig_insert = self.redir.register("insert", self.new_insert) self.orig_delete = self.redir.register("delete", self.new_delete) self.fonts = [kw['font']] def new_insert(self, *args): self.orig_insert(*args) self.event_generate('<>') def new_delete(self, *args): self.orig_delete(*args) self.event_generate('<>') class HelpBindings(tk.Toplevel): def __init__(self, parent=None): tk.Toplevel.__init__(self, parent) self.title("Help | Bindings") import bindings msg = tk.Text(self, width=80, wrap=tk.NONE) msg.insert(tk.END, bindings.description) msg.config(state=tk.DISABLED) msg.pack() button = tk.Button(self, text="Dismiss", command=self.destroy) button.pack() ######################################################################### class StatusBar(tk.Frame): """Adapted from the tkinterbook. """ # actions # global status # note status # http://colorbrewer2.org/index.php?type=sequential&scheme=OrRd&n=3 # from light to dark orange; colorblind-safe scheme #NOTE_STATUS_COLORS = ["#FEE8C8", "#FDBB84", "#E34A33"] # http://colorbrewer2.org/index.php?type=diverging&scheme=RdYlBu&n=5 # diverging red to blue; colorblind-safe scheme # red, lighter red, light yellow, light blue, dark blue NOTE_STATUS_COLORS = ["#D7191C", "#FDAE61", "#FFFFBF", "#ABD9E9", "#2C7BB6"] # 0 - saved and synced - light blue - 3 # 1 - saved - light yellow - 2 # 2 - modified - lighter red - 1 NOTE_STATUS_LUT = {0 : 3, 1 : 2, 2 : 1} def __init__(self, master): tk.Frame.__init__(self, master) self.status = tk.Label(self, relief=tk.SUNKEN, anchor=tk.W, width=40) self.status.pack(side=tk.LEFT, fill=tk.X, expand=1) self.centre_status = tk.Label(self, relief=tk.SUNKEN, anchor=tk.W, width=35) self.centre_status.pack(side=tk.LEFT, fill=tk.X, padx=5) self.note_status = tk.Label(self, relief=tk.SUNKEN, anchor=tk.W, width=25) self.note_status.pack(side=tk.LEFT, fill=tk.X) def set_centre_status(self, fmt, *args): self.centre_status.config(text=fmt % args) self.centre_status.update_idletasks() def set_note_status(self, fmt, *args): """ *.. .s. .sS """ self.note_status.config(text=fmt % args) self.note_status.update_idletasks() def set_note_status_color(self, status_idx): """ @param status_idx: 0 - saved and synced; 1 - saved; 2 - modified """ color_idx = self.NOTE_STATUS_LUT[status_idx] self.note_status.config(background=self.NOTE_STATUS_COLORS[color_idx]) def set_status(self, fmt, *args): self.status.config(text=fmt % args) self.status.update_idletasks() def clear_status(self): self.status.config(text="") self.status.update_idletasks() class NotesList(tk.Frame): """ @ivar note_headers: list containing tuples with each note's title, tags, modified date and so forth. Always in sync with what is displayed. """ TITLE_COL = 0 TAGS_COL = 1 MODIFYDATE_COL = 2 PINNED_COL = 3 def __init__(self, master, font_family, font_size, config): tk.Frame.__init__(self, master) yscrollbar = tk.Scrollbar(self) yscrollbar.pack(side=tk.RIGHT, fill=tk.Y) f = tkFont.Font(family=font_family, size=font_size) # tkFont.families(root) returns list of available font family names # this determines the width of the complete interface (yes) # size=-self.config.font_size self.text = tk.Text(self, height=25, width=30, wrap=tk.NONE, font=f, yscrollcommand=yscrollbar.set, undo=True, background = config.background_color) # change default font at runtime with: #text.config(font=f) self.text.config(cursor="arrow") self.disable_text() self.text.pack(fill=tk.BOTH, expand=1) # tags for all kinds of styling ############################ ############################################################ self.text.tag_config("selected", background="light blue") self.text.tag_config("pinned", foreground="dark gray") # next two lines from: # http://stackoverflow.com/a/9901862/532513 bold_font = tkFont.Font(self.text, self.text.cget("font")) bold_font.configure(weight="bold") self.text.tag_config("title", font=bold_font) italic_font = tkFont.Font(self.text, self.text.cget("font")) italic_font.configure(slant="italic") self.text.tag_config("tags", font=italic_font, foreground="dark gray") self.text.tag_config("found", font=italic_font, foreground="dark gray", background="lightyellow") self.text.tag_config("modifydate", foreground="dark gray") yscrollbar.config(command=self.text.yview) self._bind_events() self.selected_idx = -1 # list containing tuples with each note's title, tags, self.note_headers = [] self.layout=config.layout self.print_columns=config.print_columns if bold_font.measure(' ') > f.measure(' '): self.cwidth = bold_font.measure(' ') else: self.cwidth = f.measure(' ') self.fonts = [f, italic_font, bold_font] def append(self, note, config): """ @param note: The complete note dictionary. """ title = utils.get_note_title(note) tags = note.get('tags') modifydate = float(note.get('modifydate')) pinned = utils.note_pinned(note) self.note_headers.append((title, tags, modifydate, pinned)) self.enable_text() if self.layout == "vertical" and self.print_columns == 1: nrchars, rem = divmod((self.text.winfo_width()), self.cwidth) cellwidth = (int(nrchars) - 8)/2 if pinned: title += ' *' self.text.insert(tk.END, u'{0:<{w}}'.format(title[:cellwidth-1], w=cellwidth), ("title,")) if tags > 0: if config.tagfound: self.text.insert(tk.END, u'{0:<{w}}'.format(','.join(tags)[:cellwidth-1], w=cellwidth), ("found",)) else: self.text.insert(tk.END, u'{0:<{w}}'.format(','.join(tags)[:cellwidth-1], w=cellwidth), ("tags",)) self.text.insert(tk.END, ' ' + utils.human_date(modifydate), ("modifydate",)) # tags can be None (newly created note) or [] or ['tag1', 'tag2'] else: self.text.insert(tk.END, title, ("title,")) if pinned: self.text.insert(tk.END, ' *', ("pinned",)) self.text.insert(tk.END, ' ' + utils.human_date(modifydate), ("modifydate",)) # tags can be None (newly created note) or [] or ['tag1', 'tag2'] if tags > 0: if config.tagfound: self.text.insert(tk.END, ' ' + ','.join(tags), ("found",)) else: self.text.insert(tk.END, ' ' + ','.join(tags), ("tags",)) self.text.insert(tk.END, '\n') self.disable_text() def _bind_events(self): # Text widget events ########################################## self.text.bind("