From: paul Date: Tue, 11 Jan 2011 07:31:59 +0000 (+0000) Subject: initial code commit X-Git-Url: http://git.plutz.net/?p=tabnote;a=commitdiff_plain;h=ec23a37bec609ac89ccb4356eab7433b859babbc initial code commit svn path=/trunk/; revision=2 --- ec23a37bec609ac89ccb4356eab7433b859babbc diff --git a/tabnote b/tabnote new file mode 100755 index 0000000..21de6bb --- /dev/null +++ b/tabnote @@ -0,0 +1,296 @@ +#!/usr/bin/python +#encoding: utf-8 + +# TabNote - Note Taking Application +# Copyright 2011 Paul Hänsch +# Contact: haensch.paul@gmail.com +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from Tkinter import * +from pysqlite2 import dbapi2 +import os +import Pmw + +class Persistence(): + """ + Database storage layer + """ + def __init__(self): + """ + Constructor + Open a database, create and initialize the database if needed + """ + self.dbcon = dbapi2.connect(os.getenv('HOME') + os.sep + '.tabnote.sqlite') + self.dbcur = self.dbcon.cursor() + initnote = ('Welcome to TabNote!\n\n' + + 'Click the "+"-Tab to open a new note.\n' + + 'Double Click on the selected tab to relabel or delete it.\n' + + 'Notes are stored when you exit the program.\n\n' + + 'Shortcuts:\n' + + 'Ctrl Left - switch to previous tab\n' + + 'Ctrl Right - switch to next tab\n' + + 'Ctrl Return - bring up relabel/delete dialog\n' + + 'Ctrl t - create new tab\n' + + 'Ctrl q - save and quit') + try: + self.dbcur.execute('CREATE TABLE settings (key TEXT, value TEXT, UNIQUE (key))') + self.dbcur.execute('INSERT INTO settings (key, value) VALUES ("version", "1.0");') + self.dbcur.execute('INSERT INTO settings (key, value) VALUES ("geometry", "420x300");') + self.dbcur.execute('CREATE TABLE notes (id INTEGER PRIMARY KEY, label TEXT, content TEXT);') + self.dbcur.execute('INSERT INTO notes (label, content) VALUES ("Introduction", "' + + self.escape(initnote) + '");') + except: pass + + def getSetting(self, key): + self.dbcur.execute('SELECT value FROM settings WHERE key = "' + self.escape(key) + '";') + try: return self.dbcur.fetchall()[0][0] + except: return '' + + def setSetting(self, key, value): + try: + self.dbcur.execute('INSERT INTO settings (key, value) VALUES ("' + self.escape(key) + + '", "' + self.escape(value) + '");') + except: + self.dbcur.execute('UPDATE settings SET value = "' + self.escape(value) + + '" WHERE key = "' + self.escape(key) + '";') + + def escape(self, string): + """ + Return sanitized string for use in SQL-Statements + """ + ret = '' + for c in string: + if c == '"': ret += '""' + else: ret += c + return ret + + def allRecords(self): + """ + Return list of all recorded notes + Each item is an array of the format [id, label, textcontent] + """ + self.dbcur.execute('SELECT id, label, content FROM notes ORDER BY id;') + return self.dbcur.fetchall() + + def setRecord(self, rid, content = None, label = None): + """ + Alter an existing record + """ + if content: + self.dbcur.execute('UPDATE notes SET content = "' + self.escape(content) + + '" WHERE id = ' + str(rid) + ';') + if label: + self.dbcur.execute('UPDATE notes SET label = "' + self.escape(label) + + '" WHERE id = ' + str(rid) + ';') + + def addRecord(self, label = '', content = ''): + """ + Create a new record, return the new record id + """ + self.dbcur.execute('INSERT INTO notes (label, content) VALUES ("' + self.escape(label) + + '", "' + self.escape(content) + '");') + return self.dbcur.lastrowid + + def delRecord(self, rid): + """ + Delete record by ID + """ + self.dbcur.execute('DELETE FROM notes WHERE id = ' + str(rid) + ';') + + def close(self): + """ + Flush and close databese + """ + self.dbcur.close() + self.dbcon.commit() + self.dbcon.close() + +class Main(Tk): + """ + Main application class + """ + def __init__(self): + """ + Open Database, create Main application window, and retrieve stored data + """ + Tk.__init__(self) + self.storage = Persistence() + self.protocol('WM_DELETE_WINDOW', self.quit) + self.title('TabNote') + + self.relabelWin = False + self.contents = [] + + self.tablist = Pmw.NoteBook(self, raisecommand = self.tabselect, lowercommand = self.tabunselect) + self.tablist.add('') + self.tablist.pack(expand = True, fill = BOTH) + for tab in self.storage.allRecords(): + self.addTab(tab[0], tab[1], tab[2]) + self.tablist.delete(Pmw.END) + self.tablist.add('+') + + self.bind_all('', self.previouspage) + self.bind_all('', self.nextpage) + self.bind_all('', self.newTab) + self.bind_all('', self.quit) + self.bind_all('', self.tabedit) + + try: + self.geometry(self.storage.getSetting('geometry')) + self.tablist.selectpage(self.storage.getSetting('activeTab')) + except: pass + + def newTab(self, junk): + """ + Event wrapper for new tab + """ + self.tablist.selectpage('+') + + def previouspage(self, junk): + """ + Event wrapper for tab switching + """ + if self.tablist.getcurselection() == self.tablist.pagenames()[0]: + self.tablist.selectpage(self.tablist.pagenames()[-2]) + else: + self.tablist.previouspage() + + def nextpage(self, junk): + """ + Event wrapper for tab switching + """ + if self.tablist.getcurselection() == self.tablist.pagenames()[-2]: + self.tablist.selectpage(0) + else: + self.tablist.nextpage() + + def closeRelabel(self, junk = None): + """ + Close relabeling dialog window + """ + self.relabelWin.destroy() + self.relabelWin = False + + def delTab(self, junk = None): + """ + Delete a tab (and its record) + Called by relabeling dialog + """ + for tab in self.contents: + if tab[1] == self.tablist.page(self.relabel): + self.storage.delRecord(tab[0]) + self.contents.remove(tab) + self.tablist.previouspage() + self.tablist.delete(self.relabel) + break + self.closeRelabel() + + def relabelTab(self, junk = None): + """ + Change the name of a tab + Called by relabeling dialog + """ + for tab in self.contents: + if tab[1] == self.tablist.page(self.relabel) and self.relabel != self.relabelText.get(): + label = self.newName(self.relabelText.get()) + content = tab[2].getvalue()[:-1] + + tab[1] = self.tablist.insert(label, before = self.tablist.index(self.relabel)) + self.tablist.tab(label).bind(sequence = '', func = self.tabedit) + tab[2] = Pmw.ScrolledText(tab[1]) + tab[2].setvalue(content) + tab[2].pack(expand = True, fill = BOTH) + + self.tablist.previouspage() + self.tablist.delete(self.relabel) + self.storage.setRecord(tab[0], label = label) + break + self.closeRelabel() + + def tabedit(self, junk): + """ + Bring up relabeling dialog + """ + if not self.relabelWin: + self.relabel = self.tablist.getcurselection() + self.relabelWin = Tk() + self.relabelWin.bind('', self.closeRelabel) + self.relabelWin.protocol('WM_DELETE_WINDOW', self.closeRelabel) + self.relabelText = Entry(self.relabelWin) + self.relabelText.insert(0, self.relabel) + self.relabelText.focus() + self.relabelText.bind('', self.relabelTab) + self.relabelText.pack(side = TOP, fill = X, expand = True) + relabelButton = Button(self.relabelWin, text='Relabel', command=self.relabelTab) + relabelButton.bind('', self.relabelTab) + relabelButton.pack(side = LEFT, fill = BOTH, expand = True) + deleteButton = Button(self.relabelWin, text='Delete', command=self.delTab) + deleteButton.bind('', self.delTab) + deleteButton.pack(side = LEFT, fill = BOTH, expand = True) + + def newName(self, basename = 'New'): + """ + Return a unique name for a tab by appending a number to the given basename + """ + if not basename in self.tablist.pagenames(): return basename + incrementor = 1 + while (basename + str(incrementor)) in self.tablist.pagenames(): incrementor += 1 + return (basename + str(incrementor)) + + def tabunselect(self, name): + """ + Called when a tab becomes unselected, write the DB record for this tab + """ + for tab in self.contents: + if tab[1] == self.tablist.page(name): + self.storage.setRecord(tab[0], content=tab[2].getvalue()[:-1]) + + def addTab(self, rid, label = 'New', content = ""): + """ + Create a new tab + """ + label = self.newName(label) + self.contents.append([rid, self.tablist.insert(label, before = self.tablist.index(Pmw.END)), None]) + self.tablist.tab(label).bind(sequence = '', func = self.tabedit) + self.contents[-1][2] = Pmw.ScrolledText(self.contents[-1][1]) + self.contents[-1][2].setvalue(content) + self.contents[-1][2].pack(expand = True, fill = BOTH) + + def tabselect(self, name): + """ + Called when a tab is selected + Normally sets focus to the text editor widget + If the + tab is selected, this function creates a new record and tab + """ + if name == '+': + self.addTab(self.storage.addRecord('New', ''), 'New', ''); + self.tablist.previouspage(); + else: + for tab in self.contents: + if tab[1] == self.tablist.page(name): + tab[2].component('text').focus() + + def quit(self, junk = None): + """ + Store all data and terminate program + """ + self.storage.setSetting('activeTab', self.tablist.getcurselection()) + self.storage.setSetting('geometry', str(self.geometry())) + self.tabunselect(self.tablist.getcurselection()) + self.storage.close() + self.destroy() + +win = Main() +win.mainloop()