#!/usr/bin/python # -*- coding: utf-8 -*- # Tonto.py # 2005 Nov 02 . ccr from __future__ import division import os MAIN = None GPL_NAME = '~/.Tonto.GNU General Public License.txt' COPYRIGHT = \ u'''Tonto is a personal address-list, calendar, notepad, and so much more.

Copyright © 2006 Charles Curtis Rhode 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 St, Fifth Floor, Boston, MA 02110-1301 USA Charles Curtis Rhode, CRhode@LacusVeris, 1518 N 3rd, Sheboygan, WI 53081
''' % \ os.path.expanduser(GPL_NAME) # 2009 Mar 25 . ccr . Double-angle brackets are not in popular fonts. # Use double-angle quotation marks instead. # 2009 Feb 23 . ccr . 'unicode-escape' decoding works only on ASCII # strings. I use 'raw-unicode-escape' encoding to # coerce unicode to ASCII. In *python* 2.5 # 'unicode-escape' encoding protects input escape # strings so they are invariant after a trip # through encode/decode. I want to transform # them. # 2008 May 27 . ccr . Fix clipping events on last day of month. # 2008 Mar 28 . ccr . No start_time, no alarm. # 2008 Jan 21 . ccr . Encode Unicode mailto mnemonic names. # 2007 Nov 08 . ccr . Trap situation where Float is not a string or a number. # 2007 Oct 01 . ccr . Freeform entry must preserve contents of non-standard fields. # 2007 Sep 30 . ccr . "Now" button should transfer focus to edit box. # 2007 Sep 20 . ccr . Allow non-numeric default values. # . . Don't scroll cursor to bottom row. # 2007 Aug 15 . ccr . Quote names in eMail addresses. # 2007 May 14 . ccr . Do conjure_cols before set_dirty when changing # display columns. The set_dirty method of # calendar relations does conjure_rows, but this # may choke if the matrix is not set up for the # right number of columns first. # 2007 Mar 20 . ccr . Keep case of HREFs. # 2006 Feb 26 . ccr . Try a little optimization, refreshing TreeViews. import warnings warnings.simplefilter('ignore', DeprecationWarning) import sys import codecs import pygtk GTK_VERSION = '2.0' pygtk.require(GTK_VERSION) import gtk # gtk fixes Unicode character-set handling in csv, too. Neat, huh? import gobject import pango import webbrowser from urllib import url2pathname from urllib import quote as url_quote # 2007 Aug 14 import urlparse import htmlentitydefs import string import csv import ConfigParser import xml.dom.minidom import xml.parsers.expat import time import datetime import re import base64 import StringIO import calendar calendar.setfirstweekday(calendar.SUNDAY) import locale ZERO = 0 SPACE = ' ' NULL = '' NUL = '\x00' NA = -1 VERSION = (1, 2) # 2009 Mar 25 RELEASE_DATE = datetime.date(2006, 12, 30) URL_SCRIPT = 'http://www.LacusVeris.com/Tonto/Tonto.python' URL_UNICODE_TABLE = 'http://www.lacusveris.com/Tonto/UnicodeTable.shtml' URL_COLOR_NAMES = 'http://www.w3.org/TR/css3-color/#svg-color' DATE_EDIT_FORMAT = '%Y/%m/%d' TIME_EDIT_FORMAT = '%H:%M' DATE_TIME_EDIT_FORMAT = '%s %s' % (DATE_EDIT_FORMAT, TIME_EDIT_FORMAT) DATE_DISPLAY_FORMAT = '%a, %b %d, %Y' TIME_DISPLAY_FORMAT = '%I:%M %p' DATE_TIME_DISPLAY_FORMAT = '%s %s' % (DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT) NOT_AVAILABLE = '#N/A' BLANK = [NULL, NOT_AVAILABLE, None] INI_FILE_NAME = '~/.Tonto.ini' HELP_HTML_NAME = '~/.Tonto.html' TEMP_NAME = '~/.Tonto.tmp' ICO_NAME = '~/.Tonto.ico' GIF_NAME = '~/.Tonto.gif' TONTO_ICON = gtk.STOCK_DIALOG_INFO WINDOW_PADDING = 3 TAB_PADDING = 4 TEXT_PADDING = 15 BUTTON_WIDTH = 75 LABEL_WIDTH = 150 ENTRY_WIDTH = 200 FORM_HEIGHT = 400 RESPONSE_PREV = 99 RESPONSE_NEXT = 101 USER_FILE_DEFAULTS = {'file_name': '~/.Tonto.user', 'name': 'User-Defined', 'tag': '_User'} ELTS = [('YEAR', 64, 63, 32), ('ERA', 32, 95, 64), ('MONTH', 16, 15, 96), ('DAY', 8, 7, 112), ('HOURS', 4, 0, 123), ('MINUTES', 2, 4, 121), ('SECONDS', 1, 6, 120)] BIBLIO_TIME_ELTS = [] BIBLIO_TIME_FLAG = {} BIBLIO_TIME_SHOW = {} for (ELT_NAME, FLAG, BEFORE, AFTER) in ELTS: BIBLIO_TIME_ELTS.append(ELT_NAME) BIBLIO_TIME_FLAG[ELT_NAME] = FLAG BIBLIO_TIME_SHOW[FLAG] = (BEFORE, AFTER) DAY_NAMES = [(locale.nl_langinfo(locale.ABDAY_1), locale.nl_langinfo(locale.DAY_1)), (locale.nl_langinfo(locale.ABDAY_2), locale.nl_langinfo(locale.DAY_2)), (locale.nl_langinfo(locale.ABDAY_3), locale.nl_langinfo(locale.DAY_3)), (locale.nl_langinfo(locale.ABDAY_4), locale.nl_langinfo(locale.DAY_4)), (locale.nl_langinfo(locale.ABDAY_5), locale.nl_langinfo(locale.DAY_5)), (locale.nl_langinfo(locale.ABDAY_6), locale.nl_langinfo(locale.DAY_6)), (locale.nl_langinfo(locale.ABDAY_7), locale.nl_langinfo(locale.DAY_7))] MONTH_NAMES = [ (locale.nl_langinfo(locale.ABMON_1), locale.nl_langinfo(locale.MON_1)), (locale.nl_langinfo(locale.ABMON_2), locale.nl_langinfo(locale.MON_2)), (locale.nl_langinfo(locale.ABMON_3), locale.nl_langinfo(locale.MON_3)), (locale.nl_langinfo(locale.ABMON_4), locale.nl_langinfo(locale.MON_4)), (locale.nl_langinfo(locale.ABMON_5), locale.nl_langinfo(locale.MON_5)), (locale.nl_langinfo(locale.ABMON_6), locale.nl_langinfo(locale.MON_6)), (locale.nl_langinfo(locale.ABMON_7), locale.nl_langinfo(locale.MON_7)), (locale.nl_langinfo(locale.ABMON_8), locale.nl_langinfo(locale.MON_8)), (locale.nl_langinfo(locale.ABMON_9), locale.nl_langinfo(locale.MON_9)), (locale.nl_langinfo(locale.ABMON_10), locale.nl_langinfo(locale.MON_10)), (locale.nl_langinfo(locale.ABMON_11), locale.nl_langinfo(locale.MON_11)), (locale.nl_langinfo(locale.ABMON_12), locale.nl_langinfo(locale.MON_12)), ] BIBLIO_MONTH = [ 'Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June', 'July', 'Aug.', 'Sept.', 'Oct.', 'Nov.', 'Dec.', ] BIBLIO_MONTH_ABBREV = [ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', ] def re_alt(*calling_sequence): tokens = [] for word in calling_sequence: token = word.replace('.', '\\.') title = token.title() all_caps = token.upper() if token not in tokens: tokens.append(token) if title not in tokens: tokens.append(title) if all_caps not in tokens: tokens.append(all_caps) non_grp = '(?:%s)' alts = [non_grp % token for token in tokens] return non_grp % ('|').join(alts) PARSE_WHITE_BEFORE = '(?:^|(?<=\\s))' PARSE_WHITE_AFTER = '(?:(?=\\s)|$)' PARSE_DATE_DT = ',?(?:[./\\-\\s])?' PARSE_TIME_DT = '(?:[.:])' PARSE_MONTH_9 = '(?:(?:10)|(?:11)|(?:12)|(?:0?[1-9]))' PARSE_MONTH_X = re_alt( 'january', 'jan[.]?', 'february', 'feb[.]?', 'march', 'mar[.]?', 'april', 'apr[.]?', 'may', 'june', 'jun[.]?', 'july', 'jul[.]?', 'august', 'aug[.]?', 'september', 'sept?[.]?', 'october', 'oct[.]?', 'november', 'nov[.]?', 'december', 'dec[.]?', ) PARSE_DAY = '(?:(?:10)|(?:20)|(?:30)|(?:31)|(?:[0-2]?[1-9]))' PARSE_ERA_LEADING = re_alt('ad[.]?', 'a[.]\\s*d[.]') PARSE_ERA_TRAILING = re_alt('ad[.]?', 'a[.]\\s*d[.]', 'bce[.]?', 'b[.]\\s*c[.]\\s*e[.]', 'bc[.]?', 'b[.]\\s*c[.]', 'ce[.]?', 'c[.]\\s*e[.]') PARSE_YEAR_ERA = '(?:(?:%s\\s*)?\\d{4}(?:\\s*%s)?)' % (PARSE_ERA_LEADING, PARSE_ERA_TRAILING) PARSE_MERIDIAN = re_alt('p[.]\\s*m[.]', 'pm?[.]?', 'a[.]\\s*m[.]', 'am?[.]?', 'noon', 'n', 'midnight', 'm') PARSE_SIGN = '(?:[+-]?)' PARSE_MIN_SEC = '(?:[0-5][0-9])' PARSE_HOUR_12 = '(?:(?:10)|(?:11)|(?:12)|(?:0?\\d))' PARSE_HOUR_24 = '(?:\\d*)' PARSE_TIME_12 = re.compile('(?:%s(%s)(?:%s(%s)(?:%s(%s))?)?\\s?(%s)?%s)' % (PARSE_WHITE_BEFORE, PARSE_HOUR_12, PARSE_TIME_DT, PARSE_MIN_SEC, PARSE_TIME_DT, PARSE_MIN_SEC, PARSE_MERIDIAN, PARSE_WHITE_AFTER)) PARSE_TIME_24 = re.compile('(?:%s(%s)(%s)%s(%s)(?:%s(%s))?%s)' % (PARSE_WHITE_BEFORE, PARSE_SIGN, PARSE_HOUR_24, PARSE_TIME_DT, PARSE_MIN_SEC, PARSE_TIME_DT, PARSE_MIN_SEC, PARSE_WHITE_AFTER)) PARSE_DATE_MDY = re.compile('(?:%s(%s|%s)%s(%s)%s(%s)%s)' % (PARSE_WHITE_BEFORE, PARSE_MONTH_X, PARSE_MONTH_9, PARSE_DATE_DT, PARSE_DAY, PARSE_DATE_DT, PARSE_YEAR_ERA, PARSE_WHITE_AFTER)) PARSE_DATE_DMY = re.compile('(?:%s(%s)%s(%s|%s)%s(%s)%s)' % (PARSE_WHITE_BEFORE, PARSE_DAY, PARSE_DATE_DT, PARSE_MONTH_X, PARSE_MONTH_9, PARSE_DATE_DT, PARSE_YEAR_ERA, PARSE_WHITE_AFTER)) PARSE_DATE_YMD = re.compile('(?:%s(%s)%s(%s|%s)%s(%s)%s)' % (PARSE_WHITE_BEFORE, PARSE_YEAR_ERA, PARSE_DATE_DT, PARSE_MONTH_X, PARSE_MONTH_9, PARSE_DATE_DT, PARSE_DAY, PARSE_WHITE_AFTER)) PARSE_DATE_MY = re.compile('(?:%s(%s|%s)%s(%s)%s)' % (PARSE_WHITE_BEFORE, PARSE_MONTH_X, PARSE_MONTH_9, PARSE_DATE_DT, PARSE_YEAR_ERA, PARSE_WHITE_AFTER)) PARSE_DATE_YM = re.compile('(?:%s(%s)%s(%s|%s)%s)' % (PARSE_WHITE_BEFORE, PARSE_YEAR_ERA, PARSE_DATE_DT, PARSE_MONTH_X, PARSE_MONTH_9, PARSE_WHITE_AFTER)) PARSE_DATE_Y = re.compile('(?:%s(%s)%s)' % (PARSE_WHITE_BEFORE, PARSE_YEAR_ERA, PARSE_WHITE_AFTER)) HTML_ENTITY = re.compile('&(.{,8}?);', re.DOTALL) MATCHED_BRACKETS = '(?:<[^<>]*>)' XML_TAG = re.compile('(%s)' % MATCHED_BRACKETS) NDX_QUOTES = '["\']' NDX_TAG = re.compile('' % (NDX_QUOTES, NDX_QUOTES, NDX_QUOTES, NDX_QUOTES), re.DOTALL) TRAILING_PUNCT = '[.!?]?' TRAILING_QUOTE = '["\\]]*' BREAK = re.compile('\\n\\s*?\\n') HREF = re.compile(r'(?:(?:href)|(?:HREF)|(?:Href))="(.*?[^\\])"', re.DOTALL) # 2007 Mar 20 FOREGROUND = re.compile('(?:(?:foreground)|(?:color))="(.*?)"') BACKGROUND = re.compile('background="(.*?)"') TITLE_SEP = re.compile('((?:(?:[-~\\s"]+)|%s)*)' % MATCHED_BRACKETS) DIAL_SEQUENCE = re.compile('sequence="(.*?)"', re.DOTALL) U_LEFT_CHEVRON = u'\u00ab' # 2009 Mar 25 U_RIGHT_CHEVRON = u'\u00bb' UNCASE_WORDS = [ 'a', 'about', 'above', 'across', 'after', 'against', 'along', 'among', 'an', 'and', 'around', 'as', 'at', 'before', 'behind', 'below', 'beneath', 'beside', 'between', 'beyond', 'but', 'by', 'despite', 'down', 'during', 'except', 'for', 'from', 'in', 'inside', 'into', 'like', 'near', 'nor', 'of', 'off', 'on', 'onto', 'or', 'out', 'outside', 'over', 'past', 'since', 'so', 'the', 'through', 'throughout', 'till', 'to', 'toward', 'under', 'underneath', 'until', 'up', 'upon', 'with', 'within', 'without', 'yet', ] ALL_TARGETS = ['x-special/gnome-icon-list', 'text/x-moz-url', '_NETSCAPE_URL', 'text/uri-list', 'text/unicode', 'text/plain'] CLIPBOARD_TARGETS = ['x-special/gnome-copied-files', 'UTF8_STRING', 'STRING'] WIDE_BYTE_TARGETS = ['text/x-moz-url', 'text/html', 'text/unicode'] OMIT_WIDE_BYTE_TARGETS = True DND_TARGETS = [(class_of, ZERO, ndx) for (ndx, class_of) in enumerate(ALL_TARGETS) if not (OMIT_WIDE_BYTE_TARGETS and class_of in WIDE_BYTE_TARGETS)] AUTO_DIAL_SQUIB = \ ''' If you have both a conventional dial-out MODEM and a telephone on the same extension, Tonto can use the MODEM as an autodialer to place outgoing calls for you to phone numbers in your address list.''' MARKUP_SQUIB = \ ''' Text may contain markup: <i>italic</i>, <b>bold</b>, <u>underline</u>, <tt>monospace</tt>, <big>larger</big>, or <small>smaller</small>.''' COLOR_NAMES = [ ((255, 235, 205), 'blanched almond'), ((127, 255, 212), 'aquamarine'), ((102, 205, 170), 'medium aquamarine'), ((127, 255, 212), 'aquamarine1'), ((118, 238, 198), 'aquamarine2'), ((102, 205, 170), 'aquamarine3'), ((69, 139, 116), 'aquamarine4'), ((240, 255, 255), 'azure'), ((240, 255, 255), 'azure1'), ((224, 238, 238), 'azure2'), ((193, 205, 205), 'azure3'), ((131, 139, 139), 'azure4'), ((245, 245, 220), 'beige'), ((255, 228, 196), 'bisque'), ((255, 228, 196), 'bisque1'), ((238, 213, 183), 'bisque2'), ((205, 183, 158), 'bisque3'), ((139, 125, 107), 'bisque4'), ((0, 0, 0), 'black'), ((0, 0, 255), 'blue'), ((240, 248, 255), 'alice blue'), ((95, 158, 160), 'cadet blue'), ((100, 149, 237), 'cornflower blue'), ((0, 0, 139), 'dark blue'), ((30, 144, 255), 'dodger blue'), ((173, 216, 230), 'light blue'), ((0, 0, 205), 'medium blue'), ((25, 25, 112), 'midnight blue'), ((0, 0, 128), 'navy blue'), ((176, 224, 230), 'powder blue'), ((65, 105, 225), 'royal blue'), ((135, 206, 235), 'sky blue'), ((0, 191, 255), 'deep sky blue'), ((135, 206, 250), 'light sky blue'), ((106, 90, 205), 'slate blue'), ((72, 61, 139), 'dark slate blue'), ((132, 112, 255), 'light slate blue'), ((123, 104, 238), 'medium slate blue'), ((70, 130, 180), 'steel blue'), ((176, 196, 222), 'light steel blue'), ((0, 0, 255), 'blue1'), ((0, 0, 238), 'blue2'), ((0, 0, 205), 'blue3'), ((0, 0, 139), 'blue4'), ((255, 240, 245), 'lavender blush'), ((165, 42, 42), 'brown'), ((188, 143, 143), 'rosy brown'), ((139, 69, 19), 'saddle brown'), ((244, 164, 96), 'sandy brown'), ((255, 64, 64), 'brown1'), ((238, 59, 59), 'brown2'), ((205, 51, 51), 'brown3'), ((139, 35, 35), 'brown4'), ((222, 184, 135), 'burlywood'), ((255, 211, 155), 'burlywood1'), ((238, 197, 145), 'burlywood2'), ((205, 170, 125), 'burlywood3'), ((139, 115, 85), 'burlywood4'), ((127, 255, 0), 'chartreuse'), ((127, 255, 0), 'chartreuse1'), ((118, 238, 0), 'chartreuse2'), ((102, 205, 0), 'chartreuse3'), ((69, 139, 0), 'chartreuse4'), ((255, 250, 205), 'lemon chiffon'), ((210, 105, 30), 'chocolate'), ((255, 127, 36), 'chocolate1'), ((238, 118, 33), 'chocolate2'), ((205, 102, 29), 'chocolate3'), ((139, 69, 19), 'chocolate4'), ((255, 127, 80), 'coral'), ((240, 128, 128), 'light coral'), ((255, 114, 86), 'coral1'), ((238, 106, 80), 'coral2'), ((205, 91, 69), 'coral3'), ((139, 62, 47), 'coral4'), ((255, 248, 220), 'cornsilk'), ((255, 248, 220), 'cornsilk1'), ((238, 232, 205), 'cornsilk2'), ((205, 200, 177), 'cornsilk3'), ((139, 136, 120), 'cornsilk4'), ((245, 255, 250), 'mint cream'), ((0, 255, 255), 'cyan'), ((0, 139, 139), 'dark cyan'), ((224, 255, 255), 'light cyan'), ((0, 255, 255), 'cyan1'), ((0, 238, 238), 'cyan2'), ((0, 205, 205), 'cyan3'), ((0, 139, 139), 'cyan4'), ((107, 142, 35), 'olive drab'), ((178, 34, 34), 'firebrick'), ((255, 48, 48), 'firebrick1'), ((238, 44, 44), 'firebrick2'), ((205, 38, 38), 'firebrick3'), ((139, 26, 26), 'firebrick4'), ((220, 220, 220), 'gainsboro'), ((255, 215, 0), 'gold'), ((255, 215, 0), 'gold1'), ((238, 201, 0), 'gold2'), ((205, 173, 0), 'gold3'), ((139, 117, 0), 'gold4'), ((218, 165, 32), 'goldenrod'), ((184, 134, 11), 'dark goldenrod'), ((238, 221, 130), 'light goldenrod'), ((238, 232, 170), 'pale goldenrod'), ((255, 193, 37), 'goldenrod1'), ((238, 180, 34), 'goldenrod2'), ((205, 155, 29), 'goldenrod3'), ((139, 105, 20), 'goldenrod4'), ((190, 190, 190), 'gray'), ((169, 169, 169), 'dark gray'), ((105, 105, 105), 'dim gray'), ((211, 211, 211), 'light gray'), ((112, 128, 144), 'slate gray'), ((47, 79, 79), 'dark slate gray'), ((119, 136, 153), 'light slate gray'), ((0, 255, 0), 'green'), ((0, 100, 0), 'dark green'), ((34, 139, 34), 'forest green'), ((124, 252, 0), 'lawn green'), ((144, 238, 144), 'light green'), ((50, 205, 50), 'lime green'), ((85, 107, 47), 'dark olive green'), ((152, 251, 152), 'pale green'), ((46, 139, 87), 'sea green'), ((143, 188, 143), 'dark sea green'), ((32, 178, 170), 'light sea green'), ((60, 179, 113), 'medium sea green'), ((0, 255, 127), 'spring green'), ((0, 250, 154), 'medium spring green'), ((154, 205, 50), 'yellow green'), ((0, 255, 0), 'green1'), ((0, 238, 0), 'green2'), ((0, 205, 0), 'green3'), ((0, 139, 0), 'green4'), ((190, 190, 190), 'grey'), ((169, 169, 169), 'dark grey'), ((105, 105, 105), 'dim grey'), ((211, 211, 211), 'light grey'), ((112, 128, 144), 'slate grey'), ((47, 79, 79), 'dark slate grey'), ((119, 136, 153), 'light slate grey'), ((240, 255, 240), 'honeydew'), ((240, 255, 240), 'honeydew1'), ((224, 238, 224), 'honeydew2'), ((193, 205, 193), 'honeydew3'), ((131, 139, 131), 'honeydew4'), ((255, 255, 240), 'ivory'), ((255, 255, 240), 'ivory1'), ((238, 238, 224), 'ivory2'), ((205, 205, 193), 'ivory3'), ((139, 139, 131), 'ivory4'), ((240, 230, 140), 'khaki'), ((189, 183, 107), 'dark khaki'), ((255, 246, 143), 'khaki1'), ((238, 230, 133), 'khaki2'), ((205, 198, 115), 'khaki3'), ((139, 134, 78), 'khaki4'), ((253, 245, 230), 'old lace'), ((230, 230, 250), 'lavender'), ((250, 240, 230), 'linen'), ((255, 0, 255), 'magenta'), ((139, 0, 139), 'dark magenta'), ((255, 0, 255), 'magenta1'), ((238, 0, 238), 'magenta2'), ((205, 0, 205), 'magenta3'), ((139, 0, 139), 'magenta4'), ((176, 48, 96), 'maroon'), ((255, 52, 179), 'maroon1'), ((238, 48, 167), 'maroon2'), ((205, 41, 144), 'maroon3'), ((139, 28, 98), 'maroon4'), ((255, 228, 181), 'moccasin'), ((0, 0, 128), 'navy'), ((255, 165, 0), 'orange'), ((255, 140, 0), 'dark orange'), ((255, 165, 0), 'orange1'), ((238, 154, 0), 'orange2'), ((205, 133, 0), 'orange3'), ((139, 90, 0), 'orange4'), ((218, 112, 214), 'orchid'), ((153, 50, 204), 'dark orchid'), ((186, 85, 211), 'medium orchid'), ((255, 131, 250), 'orchid1'), ((238, 122, 233), 'orchid2'), ((205, 105, 201), 'orchid3'), ((139, 71, 137), 'orchid4'), ((205, 133, 63), 'peru'), ((255, 192, 203), 'pink'), ((255, 20, 147), 'deep pink'), ((255, 105, 180), 'hot pink'), ((255, 182, 193), 'light pink'), ((255, 181, 197), 'pink1'), ((238, 169, 184), 'pink2'), ((205, 145, 158), 'pink3'), ((139, 99, 108), 'pink4'), ((221, 160, 221), 'plum'), ((255, 187, 255), 'plum1'), ((238, 174, 238), 'plum2'), ((205, 150, 205), 'plum3'), ((139, 102, 139), 'plum4'), ((255, 218, 185), 'peach puff'), ((160, 32, 240), 'purple'), ((147, 112, 219), 'medium purple'), ((155, 48, 255), 'purple1'), ((145, 44, 238), 'purple2'), ((125, 38, 205), 'purple3'), ((85, 26, 139), 'purple4'), ((255, 0, 0), 'red'), ((139, 0, 0), 'dark red'), ((205, 92, 92), 'indian red'), ((255, 69, 0), 'orange red'), ((208, 32, 144), 'violet red'), ((199, 21, 133), 'medium violet red'), ((219, 112, 147), 'pale violet red'), ((255, 0, 0), 'red1'), ((238, 0, 0), 'red2'), ((205, 0, 0), 'red3'), ((139, 0, 0), 'red4'), ((255, 228, 225), 'misty rose'), ((250, 128, 114), 'salmon'), ((233, 150, 122), 'dark salmon'), ((255, 160, 122), 'light salmon'), ((255, 140, 105), 'salmon1'), ((238, 130, 98), 'salmon2'), ((205, 112, 84), 'salmon3'), ((139, 76, 57), 'salmon4'), ((255, 245, 238), 'seashell'), ((255, 245, 238), 'seashell1'), ((238, 229, 222), 'seashell2'), ((205, 197, 191), 'seashell3'), ((139, 134, 130), 'seashell4'), ((160, 82, 45), 'sienna'), ((255, 130, 71), 'sienna1'), ((238, 121, 66), 'sienna2'), ((205, 104, 57), 'sienna3'), ((139, 71, 38), 'sienna4'), ((245, 245, 245), 'white smoke'), ((255, 250, 250), 'snow'), ((255, 250, 250), 'snow1'), ((238, 233, 233), 'snow2'), ((205, 201, 201), 'snow3'), ((139, 137, 137), 'snow4'), ((210, 180, 140), 'tan'), ((255, 165, 79), 'tan1'), ((238, 154, 73), 'tan2'), ((205, 133, 63), 'tan3'), ((139, 90, 43), 'tan4'), ((216, 191, 216), 'thistle'), ((255, 225, 255), 'thistle1'), ((238, 210, 238), 'thistle2'), ((205, 181, 205), 'thistle3'), ((139, 123, 139), 'thistle4'), ((255, 99, 71), 'tomato'), ((255, 99, 71), 'tomato1'), ((238, 92, 66), 'tomato2'), ((205, 79, 57), 'tomato3'), ((139, 54, 38), 'tomato4'), ((64, 224, 208), 'turquoise'), ((0, 206, 209), 'dark turquoise'), ((72, 209, 204), 'medium turquoise'), ((175, 238, 238), 'pale turquoise'), ((0, 245, 255), 'turquoise1'), ((0, 229, 238), 'turquoise2'), ((0, 197, 205), 'turquoise3'), ((0, 134, 139), 'turquoise4'), ((238, 130, 238), 'violet'), ((138, 43, 226), 'blue violet'), ((148, 0, 211), 'dark violet'), ((245, 222, 179), 'wheat'), ((255, 231, 186), 'wheat1'), ((238, 216, 174), 'wheat2'), ((205, 186, 150), 'wheat3'), ((139, 126, 102), 'wheat4'), ((255, 239, 213), 'papaya whip'), ((255, 255, 255), 'white'), ((250, 235, 215), 'antique white'), ((255, 250, 240), 'floral white'), ((248, 248, 255), 'ghost white'), ((255, 222, 173), 'navajo white'), ((255, 255, 0), 'yellow'), ((250, 250, 210), 'light goldenrod yellow'), ((173, 255, 47), 'green yellow'), ((255, 255, 224), 'light yellow'), ((255, 255, 0), 'yellow1'), ((238, 238, 0), 'yellow2'), ((205, 205, 0), 'yellow3'), ((139, 139, 0), 'yellow4'), ] class MainColors(dict): """Emulate X11 named colors. See http://www.w3.org/TR/css3-color/#svg-color. """ def load(self, main_window): self.color_map = main_window.get_colormap() for ((red, green, blue), name) in COLOR_NAMES: self.insert(red, green, blue, name) return self def insert(self, red, green, blue, name): red *= 257 green *= 257 blue *= 257 self[name] = self.color_map.alloc_color(red, green, blue) return self def get_name(self, name, default_name=None): if name in self: return name else: return default_name def get_color(self, name, default_name=None, default_color=None): return self.get(self.get_name(name, default_name=default_name), default_color) def get_rgb(self, name, default_name=None, default_color=None): color = self.get_color(name, default_name=default_name, default_color=default_color) return (color.red, color.green, color.blue) def get_hex(self, name, default_name=None, default_color=None): color = self.get_rgb(name, default_name=default_name, default_color=default_color) color = tuple1(*[component >> 8 for component in color]) return '#%02x%02x%02x' % color class Error(Exception): def __init__(self, msg): self.msg = msg return def __str__(self): return self.msg class ErrorWidth(Error): pass class ErrorCodeTable(Error): pass class ErrorNonNumeric(Error): pass class ErrorValue(Error): pass class ErrorRange(Error): pass class ErrorDateTime(Error): pass class ErrorNoColHeaders(Error): pass class ErrorBadDictionary(Error): pass class ErrorBadData(Error): pass class ErrorDuplicateTag(Error): pass class ErrorDuplicateFile(Error): pass def tuple1(*arg): """Return a tuple of parameters. This works when parameters are only one or none. """ return tuple(arg) def enscape(buf): if buf is None: buf = NULL return buf.replace('&', '&').replace('<', '<').replace('>', '>') def unscape(buf): if buf is None: buf = NULL escape = HTML_ENTITY.split(buf) groups = [] groups.append(escape.pop(ZERO)) while len(escape) > ZERO: entity = escape.pop(ZERO) ndx = htmlentitydefs.name2codepoint.get(entity) if ndx is None: try: ndx = int(entity[1:5], 10) groups.append(unichr(ndx)) except ValueError: groups.append('&%s;' % entity) else: groups.append(unichr(ndx)) groups.append(escape.pop(ZERO)) result = NULL.join(groups).encode('raw-unicode-escape', 'replace') # 2009 Feb 23 return result.decode('unicode-escape', 'replace') QUOTE_AS = { ord(unscape('“')): u'"', ord(unscape('”')): u'"', ord(unscape(u'`')): u"'", ord(unscape('‘')): u"'", ord(unscape('’')): u"'", ord(unscape('–')): u'-', ord(unscape('—')): u'~', ord(unscape('−')): u'-', ord(unscape('…')): u'...', } def quoted(buf): for markup in ['', '', '', '', '', '']: buf = buf.replace(markup.lower(), '*') buf = buf.replace(markup.upper(), '*') for markup in ['', '']: buf = buf.replace(markup.lower(), '"') buf = buf.replace(markup.upper(), '"') for markup in ['', '']: buf = buf.replace(markup.lower(), '_') buf = buf.replace(markup.upper(), '_') for (ord, replacement) in QUOTE_AS.iteritems(): chr = unichr(ord) buf = buf.replace(chr, replacement) return buf def paragraph_align(msg, strip_markup=False, break_=''' '''): if strip_markup: msg = quoted(msg) msg = XML_TAG.sub(NULL, msg) msg = unscape(msg) paragraphs = BREAK.split(msg) for (ndx, paragraph) in enumerate(paragraphs): lines = paragraph.splitlines() lines = [line.strip() for line in lines] paragraphs[ndx] = SPACE.join(lines) result = break_.join(paragraphs) return result.strip() def strip_selected_file_name_extensions(file_name_with): (file_name_without, ext) = os.path.splitext(file_name_with) if ext.lower() in ['.dd', '.csv']: return file_name_without else: return file_name_with def get_python_version(): # version = sys.version # pos = version.find(SPACE) # return version[:pos] return '2.5' def open_url(url=None, relation=None, row_ndx=None): if url is None: result = False else: dlg = DlgAssertion(parent=MAIN, msg=''' Opening Browser Window Please wait. ''') dlg.show() try: webbrowser.open(url, new=2, autoraise=True) result = True except webbrowser.Error: result = False dlg.destroy() if result: pass else: dlg = DlgAssertion(parent=MAIN, msg=""" Can't Find Default Browser %s is not available on this computer. """ % url) dlg.run() dlg.destroy() if relation is None: pass else: tag = '_Traversed_Date' fld = relation.column_list.find(tag) if fld is None: pass else: row = relation[row_ndx] row[tag] = fld.set_now().get_as_edited() relation.set_dirty() return result def call_back_after_realize_to_set_text_view_base_color(from_widget, to_widgets, *calling_sequence): """Colors are not final until after realization perhaps due to tweaking during theme rendition. """ for (ndx, color) in enumerate(from_widget.style.bg): for to_widget in to_widgets: to_widget.modify_base(ndx, color) return False def take_adeep_breath(): while gtk.events_pending(): gtk.main_iteration(False) return def set_tip(widget, help): if help is None: pass else: MAIN.tool_tips.set_tip(widget, paragraph_align(help, strip_markup=True).encode('utf-8')) return def quarrantine_invalid_data(dlg, method, method_calling_sequence, title): done = False while not done: result = dlg.run() if result in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_NONE, gtk.RESPONSE_DELETE_EVENT]: done = True else: try: method(*method_calling_sequence) done = True except (ErrorWidth, ErrorCodeTable, ErrorNonNumeric, ErrorValue, ErrorRange, ErrorDateTime), __Error: diagnostic = DlgAssertion(parent=MAIN, msg= ''' %s %s Please re-enter the data in this field. ''' % (title, __Error)) diagnostic.run() diagnostic.destroy() except (ValueError, TypeError): diagnostic = DlgAssertion(parent=MAIN, msg= """ %s There is a general problem with some data on this page. It doesn't match the type expected, or it has a value that is outside the range allowed. """ % title) diagnostic.run() diagnostic.destroy() return result class Index(dict): def __init__(self): self.seq = ZERO return def stuff(self, term, heading, emph=None): term = SPACE.join(term.split()) # Swallow line endings. list = self.setdefault(term, []) list.append([self.seq, heading, emph]) self.seq += 1 return self.seq def get_index(self): lines = [] list = [(key.lower(), key, refs) for (key, refs) in self.iteritems()] list.sort() for (sort, key, refs) in list: line = '%s: ' % key refs.sort() for (ndx, (seq, heading, emph)) in enumerate(refs): if emph is None: emph_open = NULL emph_close = NULL else: emph_open = '<%s>' % emph emph_close = '' % emph if ndx == ZERO: sep = NULL else: sep = '; ' line += '%s%s%s%s' % (sep, emph_open, seq, heading, emph_close) line += '
' lines.append(line) return ('\n').join(lines) class Section(list): def __init__(self, heading=NULL, text=NULL): self.set_heading(heading) self.set_text(text) return def set_heading(self, heading): self.heading = heading return self def get_heading(self): return self.heading def get_text(self): return self.text def set_text(self, text): self.text = paragraph_align(text, break_='''

''') return self def append(self, section): list.append(self, section) return section def index(self, index): self.index_text(index) for section in self: section.index(index) return index def index_text(self, index): list = NDX_TAG.split(self.get_text()) result = [] result.append(list.pop(ZERO)) while len(list) > ZERO: name = list.pop(ZERO) emph = list.pop(ZERO) seq = index.stuff(name, self.get_heading(), emph) result.append('' % seq) result.append(list.pop(ZERO)) self.text = NULL.join(result) return self def get_toc(self, path=NULL): lines = [] if path == NULL: pass else: lines.append('%s %s
' % (path, path, self.get_heading())) for (ndx, section) in enumerate(self): sub_path = '%s%i.' % (path, ndx + 1) lines.append(section.get_toc(path=sub_path)) return ('\n').join(lines) def get_body(self, path=NULL): lines = [] if path == NULL: pass else: lines.append('' % path) lines.append('

%s %s

' % (path, self.get_heading())) lines.append(self.get_text()) for (ndx, section) in enumerate(self): sub_path = '%s%i.' % (path, ndx + 1) lines.append(section.get_body(path=sub_path)) return ('\n').join(lines) class UsersManual(Section): def __init__(self): Section.__init__(self) self.set_text('''Try Tonto. Do you like it? Is it good for you? If so, let me know. Please send a picture postcard of your hometown to Chuck Rhode at the above address. Thanks. -ccr-
''') sect0 = self.append(Section()) sect0.set_heading('Philosophy') sect0.set_text("""In everyday life, everyone makes lists: grocery lists, to-do lists, address lists. Tonto raises everyday lists to a new level by automating them. By running Tonto, you keep your commonly-used lists handy. Tonto keeps several lists open at once — each under its own tab. When you first start Tonto, you have three tabs: Addresses, 3x5 Cards, and Calendar. The first two are empty. The Calendar starts out listing the standard US holidays. You may add rows on a tab by selecting the Edit/Insert Row menu item. You may add information to the fields on a row by clicking on the row to select it and then clicking the Edit/Field Entry menu item. When you exit Tonto, the new information is saved to a file. By default, Tonto files are all in your home file folder (your root directory). Their names start with .Tonto. To uninstall Tonto, delete these files. It takes two files to save each Tonto tab: a .csv file and a .dd file. The .dd file contains a data dictionary of field names that provides Tonto the structure of of the data. The .csv file is a comma-separated-value file of fields arranged in rows. You can import the .csv files into spreadsheet programs to print reports, and you can merge them with word-processing documents to print form letters. You can open other new and existing .csv files in their own tabs. Tonto saves the names of the tabs in .Tonto.ini. When you restart Tonto, it will bring up the same set of tabs as before.""") sect0 = self.append(Section()) sect0.set_heading('Conventions') sect0.set_text("""Tonto is very easy to use. This can be a good thing because you will usually achieve some kind of result. For example, Tonto is not particular about dates. All these amount to the same:
  • 01/13/2006
  • 13/01/2006
  • 2006/01/13
  • jan 13 2006
Tonto seldom complains about the information you give it. On the other hand, this flexibility can be a bad thing because the result may not be exactly what you intend. For example, 01/12/2006 is 12 Jan. 2006, but 12/01/2006 is 01 Dec. 2006. Remember, if it doesn't look right to you, it probably looks wrong to Tonto. Please review all your work! Once it gets going, Tonto is quick. This can be a good thing because it frees you to think about what comes next. When you delete data, it's gone. Tonto doesn't interrupt you to ask whether you really meant to do what you've just asked it to. On the other hand, speed can be a bad thing when you're being careless. At the end of a session, when you quit Tonto, it writes the changes you've made back to files on your hard drive without asking permission. Up to that point, you can discard at once all the changes you've made on a tab by selecting the File/Revert menu item. Alternatively, you may write these changes any time without quitting Tonto by selecting the File/Checkpoint menu item. It is known and acknowledged that the Revert menu item is very close — perhaps too close — to the Checkpoint menu item. Please use Checkpoint often so you have less at risk when you flub up! It's always, always good to keep extra copies of your work, too.""") sect0 = self.append(Section()) sect0.set_heading('Markup') sect0.set_text("""Most Tonto text fields accept normal character-string input. Also, they accept markup. In markup, the angle bracket characters (<>) and the ampersand (&) are special. If you don't want to use markup, you must avoid these characters. Tonto provides a View/As Text menu item for any tab. It shows a formatted view of the information for one row at a time. If you want parts of the text to show with special emphasis, you can surround those portions with markup tags.""") sect1 = sect0.append(Section()) sect1.set_heading('Emphasis') sect1.set_text("""Here are samples of emphasis:
How You Edit It How You View It
<i>Italic</i> Text Italic Text
<b>Bold</b> Text Bold Text
<u>Underlined</u> Text Underlined Text
<tt>Monospace</tt> Text Monospace Text
An <samp>Example</samp> An Example
A <var>Variable</var> A Variable
<big>Big</big> Text Big Text
<small>Small</small> Text Small Text
""") sect1 = sect0.append(Section()) sect1.set_heading('Color') sect1.set_text('''Color may be used for emphasis, too. Only the foreground color may be manipulated. It is not possible, with markup, completely to fill a window with a background color. Sorry! The World Wide Web Consortium recognizes a bunch of weird color names.
How You Edit It How You View It
<font color=indianred>Indian Red</font> Text Indian Red Text
''' % URL_COLOR_NAMES) sect1 = sect0.append(Section()) sect1.set_heading('Entities') sect1.set_text('''In print, there are many more character glyphs than keys on a PC keyboard. Tonto lets you enter additional glyphs by their HTML entity names.
How You Edit It How You View It
1.000,00 &euro; 1.000,00 €
1.000,00 &#8364; 1.000,00 €
1.000,00 € 1.000,00 €
''' % URL_UNICODE_TABLE) sect1 = sect0.append(Section()) sect1.set_heading('Unicode') sect1.set_text(u'''Tonto is Unicode aware. Are you? If you were, you\'d know it. Suffice it to say then that all of Tonto\'s I/O is UTF-8 encoded. Tonto lets you enter glyphs by their Unicode names.
How You Edit It How You View It
1.000,00 \\N{EURO SIGN} 1.000,00 \u20ac
1.000,00 \\u20ac 1.000,00 \u20ac
''' % URL_UNICODE_TABLE) sect0 = self.append(Section()) sect0.set_heading('Menu') sect0.set_text("""Tonto's main menu is all divided into five parts: File, Edit, View, Mark, and Help. There is context help in the form of tool-tip pop-ups for the following menu items. You can see them by moving the mouse cursor over a menu item and hovering there.""") sect1 = sect0.append(Section()) sect1.set_heading('File Menu') sect1.set_text("""The File menu is all divided into nine parts: Open, Close, Checkpoint, Revert, Merge From, Export To, Add Remove Fields, Choose Display Columns, and Quit.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Open Menu Item') sect2.set_text("""This menu item opens a new tab. You are asked to supply a short title, a long title, and a data type for the tab as well as a corresponding .csv file name. The data displayed on a tab is called a relation because the fields on a row are related to the row. That is, they have a relationship to each other by being part of a row.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Close Menu Item') sect2.set_text("""This menu item closes a tab. You must first select a tab by clicking on it; otherwise, Close won't do anything. The data on the tab will be saved as-is to the corresponding file if you've made any changes.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Checkpoint Menu Item') sect2.set_text("""This menu item writes any changes you've made on a selected tab back to the corresponding file. The tab remains open. Checkpoint works on only one tab at a time. It is a good idea to save your work often.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Revert Menu Item') sect2.set_text("""This menu item refreshes the contents of a tab from the corresponding file. It does this without first saving any changes you may have made. Effectively, it discards any such changes. Revert works on only one tab at a time.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Merge-From Menu Item') sect2.set_text("""This menu item adds the contents of another file to a tab. It does not change the tab's corresponding file until the tab is Closed or Checkpointed. Any fields from the other file that don't already exist on the tab are added to it.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Export-To Menu Item') sect2.set_text("""This menu item saves the contents of a tab to another file beside the corresponding file. In other words, this procedure makes a backup copy in a different place under a different name. It is a good idea, too.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Add-Remove-Fields Menu Item') sect2.set_text("""This menu item lets you change the fields in the relation displayed on the selected tab. It shows you a list of fields. You can move one at a time up or down by selecting and dragging it with the mouse. You can always add new fields. Some fields cannot be changed or deleted, however. These are shown in gray.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Choose-Display-Columns Menu Item') sect2.set_text("""This menu item lets you pick which fields to display on the tab because not all need to show. It gives you two lists. Double click on a field in the right list to select it for display. Double click on a field in the left list to remove it from the display. You can move one field at a time up or down in a list by selecting and dragging it with the mouse. Chosen fields become columns on the tab. You can see a brief pop-up description of a column by moving the mouse cursor to the column head and hovering there. You can sort the rows based on the values in a column by clicking on the column head. Successive clicks toggle the sort order from ascending to descending.""") sect2 = sect1.append(Section()) sect2.set_heading('File/Quit Menu Item') sect2.set_text("""This menu item automatically saves all your changes and exits Tonto.""") sect1 = sect0.append(Section()) sect1.set_heading('Edit Menu') sect1.set_text("""The Edit menu is all divided into ten parts: Search Replace, Go to Row, Insert Row, Insert After Row, Freeform Entry, Field Entry, Cut Row, Copy Row, Paste, and Preferences.""") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Search-Replace Menu Item') sect2.set_text("""This menu item allows you to type a target string. Then it searches the relation associated with the selected tab columnwise for a field containing the target. Then you can search further, mark the row, or replace the target.""") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Go-to-Row Menu Item') sect2.set_text("""This menu item asks\n for a row number and scrolls the tab to that row.""") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Insert-Row Menu Item') sect2.set_text("""This menu item creates a new blank row above the selected row. If there is no selected row, it creates the blank before the first row.""") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Insert-After-Row Menu Item') sect2.set_text("""This menu item creates a new blank row below the selected row. If there is no selected row, it creates the blank after the last row.""") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Freeform-Entry Menu Item') sect2.set_text("""This menu item parses a blob of text automatically to extract interesting information. Currently, only Address List relations allow this kind of data entry. It gives two windows. The one on the right shows open/close tags for all fields. The one on the left is empty. Type an address block into the left window. Instead of typing, you may paste freeform text from another application, for example, a business-card scanner or a Web browser. Click Parse. Tonto will attempt to identify parts of the address by their position in context. It will assign tags from the other window to those parts that are unambiguous. This process is not perfect. Not all tags will be assigned (Not all need to be assigned.), and some pertinent information will not be correctly identified. Plese drag the tags into their correct positions and make additions and corrections to the data before clicking OK. """) sect2 = sect1.append(Section()) sect2.set_heading('Edit/Field-Entry Menu Item') sect2.set_text("""This menu item presents blanks to be filled for all fields in the relation. You can see a brief pop-up description of a field by moving the mouse cursor to the field name and hovering there. If you double click a row on a tab, the Field Entry dialog opens. Some relations, such as 3x5 Note Cards and Bibliographies, support a Paste button to update their Web field. You can cut a link from your browser and paste it, clicking here. Alternatively, you can drag the link from your browser and drop it here. Tonto will put the URL into the Web field and the linked text into the Title. Also, you may drag the thumb out of your browser's location bar and drop it here. In this case, Tonto sets Title to the title of the Web page your browser is displaying. Finally, you may drag a file name from your File Manager and drop it here to create a link to a local file on your hard disk. Tonto opens and reads .desktop and .url files dropped here to extract link information from them.""") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Cut-Row Menu Item') sect2.set_text("This menu item removes the selected row.") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Copy-Row Menu Item') sect2.set_text("""This menu item remembers the selected row for use in a subsequent Paste operation.""") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Paste Menu Item') sect2.set_text("""This menu item inserts the row (or rows) from the preceding Cut or Copy above the currently selected row. If no row is selected, Paste won't do anything.""") sect2 = sect1.append(Section()) sect2.set_heading('Edit/Preferences Menu Item') sect2.set_text("""This menu item leads to the Select MODEM dialog. %s You must indicate which MODEM, if any, to use.""" % AUTO_DIAL_SQUIB) sect1 = sect0.append(Section()) sect1.set_heading('View Menu') sect1.set_text("""The View menu is all divided into three parts: View as Text, View Calendar Month, and View Alarms. For most relation types (except Calendar), the View Calendar Month and View Alarms items are disabled.""") sect2 = sect1.append(Section()) sect2.set_heading('View-as-Text Menu Item') sect2.set_text("""This menu item shows the selected row as a blob of text. The display of most relation types (except Passwords) provides these options: Preview, Plain, Markup, Quoted, and All Caps.""") sect3 = sect2.append(Section()) sect3.set_heading('Preview') sect3.set_text("""This button starts the Web browser with a view of the text. Tonto doesn't know about printers, but your browser probably does. If you want to print from Tonto, you can copy text from Tonto and paste it into an editor or word processor, or you can print from the browser.""") sect3 = sect2.append(Section()) sect3.set_heading('Plain') sect3.set_text("""This radio push button displays text without any special interpretation except markup.""") sect3 = sect2.append(Section()) sect3.set_heading('Markup') sect3.set_text("""This radio push button displays text without any interpretation at all. Even markup shows as-is. You may wish to view markup so you can cut and paste it into markup-aware programs like HTML editors.""") sect3 = sect2.append(Section()) sect3.set_heading('Quoted') sect3.set_text("""This radio push button displays text with character-based interpretations of markup, eg: Emphasis is rendered with surrounding asterisks. You may wish to quote marked up text for pasting into plain-text programs like eMail user agents.""") sect3 = sect2.append(Section()) sect3.set_heading('All Caps') sect3.set_text("""This radio push button displays text in all upper-case characters. For Address relations in particular, this is useful to prepare text for envelopes. US Postal regulations suggest using upper case with a minimum of punctuation.""") sect2 = sect1.append(Section()) sect2.set_heading('View-Calendar-Month Menu Item') sect2.set_text("""This menu item shows events scheduled during the current month. Buttons allow you to scroll up and down by months and years and return to the current date.""") sect2 = sect1.append(Section()) sect2.set_heading('View-Alarms Menu Item') sect2.set_text("""This menu item shows today's events that require an audible alarm.""") sect1 = sect0.append(Section()) sect1.set_heading('Mark Menu') sect1.set_text("""The Mark menu is all divided into five parts: Toggle Mark, Mark All Rows, Toggle All Rows, Cut Marked Rows, and Copy Marked Rows.This menu item changes the bullet at the beginning of the selected row to a check mark. This is the same as clicking on the bullet.""") sect2 = sect1.append(Section()) sect2.set_heading('Mark/Mark-All-Rows Menu Item') sect2.set_text("This menu item\n sets the check mark on each row.") sect2 = sect1.append(Section()) sect2.set_heading('Mark/Toggle-All-Rows Menu Item') sect2.set_text("This menu item\n unchecks the checked rows and checks the unchecked rows.") sect2 = sect1.append(Section()) sect2.set_heading('Mark/Cut-Marked-Rows Menu Item') sect2.set_text("""This menu item removes the checked rows. To move rows from one tab to another, check them, cut them, select the destination tab, select the row just past the place where you want the insert to occur, and paste the remembered rows at that position.""") sect2 = sect1.append(Section()) sect2.set_heading('Mark/Copy-Marked-Rows Menu Item') sect2.set_text("""This menu item remembers the checked rows for use in a subsequent Paste operation.""") sect1 = sect0.append(Section()) sect1.set_heading('Help Menu') sect1.set_text("""The Help menu is all divided into three parts: Contents, GNU General Public License, and About.""") sect2 = sect1.append(Section()) sect2.set_heading('Help/Contents Menu Item') sect2.set_text("This menu item opens\n the Users' Manual in the Web browser.") sect2 = sect1.append(Section()) sect2.set_heading('Help/GNU General Public License Menu Item') sect2.set_text('''This menu item opens a copy of the GNU General Public License in the Web browser.''' % os.path.expanduser(GPL_NAME)) sect2 = sect1.append(Section()) sect2.set_heading('Help/About Menu Item') sect2.set_text("This menu item presents\n an abstract of Tonto.") sect0 = self.append(Section()) sect0.set_heading('Relations') sect0.set_text("""Tonto provides six relation types: User Defined, Calendar, Bibliography, Password List, 3x5 Note Cards, and Address List. All except User Defined have predefined fields, and a few of these fields are selected for display on the tab. All relations are empty when first opened except Calendar, which always starts out with rows for the standard US holidays. With the File/Add Remove Fields menu item, you can change some of the predefined fields. With the File/Choose Display Columns menu item, you can add fields to the display.""") sect1 = sect0.append(Section()) sect1.set_heading('User-Defined Relation') sect1.set_text("""This relation type has no predefined fields and no display columns. To use it, you have to add these things. This relation type is provided as a starting point for you to define your own peculiar needs not immediately met by starting from one of the other predefined relation types.""") sect1 = sect0.append(Section()) sect1.set_heading('Calendar Relation') sect1.set_text("""Each row in a Calendar specifies an event. There are three types of events: Scheduled, Recurring, and Dependent. Recurring events are a special kind of scheduled event. Scheduled and recurring events show on the calendar month. Dependent events do not.""") sect2 = sect1.append(Section()) sect2.set_heading('Scheduled Events') sect2.set_text("A\n scheduled event has a start date.") sect2 = sect1.append(Section()) sect2.set_heading('Recurring Events') sect2.set_text("""A recurring event has a day or month offset beside a start date.""") sect2 = sect1.append(Section()) sect2.set_heading('Dependent Events') sect2.set_text("""A dependent event depends on one or more scheduled or other dependent events. A dependent event has float, the leeway in days between its earliest-start date and its latest-finish date less the number of days it takes to complete. Tonto calculates this for events that depend upon scheduled events. A dependent event that has a start date has zero float. An event's earliest-start date is the earliest date on which all its predecessors can possibly be finished. Likewise, an event's latest-finish date is the latest date on which its successors need to be started so they can all be finished on time.""") sect2 = sect1.append(Section()) sect2.set_heading('Alarms') sect2.set_text("""Audible alarms may be set for scheduled and recurring events by specifying an advance in hours and minutes. You can specify an egg timer by setting its advance to a negative three minutes, its start date to the current date, and its start time to the current time. It's accurate only to the nearest minute, though.""") sect1 = sect0.append(Section()) sect1.set_heading('Bibliography Relation') sect1.set_text("""Each row in a Bibliography specifies a literary work referred to in a research paper. Tonto formats entries for the list of works cited according to a commonly accepted standard.""") sect1 = sect0.append(Section()) sect1.set_heading('Password List') sect1.set_text("""Each row in a Password List specifies a pass code used to access some vendor's service. The list helps you associate identifying information with the vendor, his service, and your account.""") sect1 = sect0.append(Section()) sect1.set_heading('3x5 Note Cards') sect1.set_text("""Each card is a virtualization of a sticky note. A card may hold a Web bookmark, so a 3x5 file can be used as an Internet favorites list. The Remark field has a log feature that inserts the current date and time automatically. Thus, a 3x5 file can be used as a diary.""") sect1 = sect0.append(Section()) sect1.set_heading('Address List') sect1.set_text("""Each row in an Address List is a contact for a correspondent. It may contain one mailing address, one eMail address, and a series of phone numbers.""") return class Stretch(object): def __init__(self): self.set_fill() self.set_expand() self.set_align() self.set_padding() return def set_fill(self, value=True): self.set_hor_fill(value) self.set_ver_fill(value) return self def set_hor_fill(self, value=True): self.hor_fill = value return self def set_ver_fill(self, value=True): self.ver_fill = value return self def set_expand(self, value=True): self.set_hor_expand(value) self.set_ver_expand(value) return self def set_hor_expand(self, value=True): self.hor_expand = value return self def set_ver_expand(self, value=True): self.ver_expand = value return self def set_align(self, value=0.5): self.set_hor_align(value) self.set_ver_align(value) return self def set_ver_align(self, value=0.5): self.ver_align = value return self def set_hor_align(self, value=0.5): self.hor_align = value return self def set_padding(self, value=WINDOW_PADDING): self.set_hor_padding(value) self.set_ver_padding(value) return self def set_hor_padding(self, value=WINDOW_PADDING): self.hor_padding = value return self def set_ver_padding(self, value=WINDOW_PADDING): self.ver_padding = value return self def get_hor_flags(self): result = ZERO if self.hor_fill: result |= gtk.FILL if self.hor_expand: result |= gtk.EXPAND return result def get_ver_flags(self): result = ZERO if self.ver_fill: result |= gtk.FILL if self.ver_expand: result |= gtk.EXPAND return result def get_align(self): return (self.hor_align, self.ver_align) def get_padding(self): return (self.hor_padding, self.ver_padding) def get_hor(self): return (self.hor_expand, self.hor_fill, self.hor_padding) def get_ver(self): return (self.ver_expand, self.ver_fill, self.ver_padding) def has_align(self): return self.get_align() != (0.5, 0.5) def create_container_widget(class_of=None, cols=1, rows=1): if class_of in [gtk.HBox, None]: return ContainerWidgetHbox() elif class_of in [gtk.VBox]: return ContainerWidgetVbox() elif class_of in [gtk.Table]: return ContainerWidgetTable(cols=cols, rows=rows) else: return None class ContainerWidgetAbstract(object): def set_pack(self, pack=gtk.PACK_START): self.pack = pack return self def wrap_with_alignment(self, widget, stretch): alignment = stretch.get_align() result = gtk.Alignment(*alignment) widget.show() result.add(widget) return result def wrap_with_scroll_box(self, widget, scrolled_horizontally, scrolled_vertically): result = gtk.ScrolledWindow() widget.show() result.add(widget) result.set_shadow_type(gtk.SHADOW_IN) if scrolled_horizontally: policy_hor = gtk.POLICY_AUTOMATIC else: policy_hor = gtk.POLICY_NEVER if scrolled_vertically: policy_ver = gtk.POLICY_AUTOMATIC else: policy_ver = gtk.POLICY_NEVER result.set_policy(policy_hor, policy_ver) return result def get_stretch(self, stretch, hor=True): if hor: return stretch.get_hor() else: return stretch.get_ver() def stuff(self, atom, stretch=None, scrolled=False, scrolled_horizontally=False, scrolled_vertically=False): a_widget = atom if stretch is None: stretch = Stretch() if stretch.has_align(): a_widget = self.wrap_with_alignment(a_widget, stretch) if scrolled: scrolled_horizontally = True scrolled_vertically = True if scrolled_horizontally or scrolled_vertically: a_widget = self.wrap_with_scroll_box(a_widget, scrolled_horizontally, scrolled_vertically) a_widget.show() (expand, fill, padding) = self.get_stretch(stretch) if self.pack in [gtk.PACK_END]: self.pack_end(a_widget, expand=expand, fill=fill, padding= padding) else: self.pack_start(a_widget, expand=expand, fill=fill, padding= padding) return atom def clear(self): for child in self.get_children(): self.remove(child) return self class ContainerWidgetHbox(gtk.HBox, ContainerWidgetAbstract): def __init__(self): gtk.HBox.__init__(self) self.set_pack() return class ContainerWidgetVbox(gtk.VBox, ContainerWidgetAbstract): def __init__(self): gtk.VBox.__init__(self) self.set_pack() return def get_stretch(self, stretch, hor=False): return ContainerWidgetAbstract.get_stretch(self, stretch=stretch, hor=hor) class ContainerWidgetTable(gtk.Table, ContainerWidgetAbstract): def __init__(self, cols=1, rows=1): self.cols = cols self.rows = rows gtk.Table.__init__(self, columns=self.cols, rows=self.rows) self.at_row() self.set_pack() return def set_pack(self, pack=gtk.PACK_START): ContainerWidgetAbstract.set_pack(self, pack=pack) self.at_col() return self def bump_col(self, inc=None): if inc is None: if self.pack in [gtk.PACK_END]: inc = -1 else: inc = 1 self.col_ndx += inc self.audit_col() return self def audit_col(self): if self.col_ndx >= self.cols: self.cols = self.col_ndx + 1 self.resize(columns=self.cols, rows=self.rows) elif self.col_ndx < ZERO: self.col_ndx += self.cols return self def at_col(self, col_ndx=None): if col_ndx is None: if self.pack in [gtk.PACK_END]: col_ndx = -1 else: col_ndx = ZERO self.col_ndx = col_ndx self.audit_col() return self def bump_row(self, inc=1): self.row_ndx += inc self.audit_row() self.at_col() return self def audit_row(self): if self.row_ndx >= self.rows: self.rows = self.row_ndx + 1 self.resize(columns=self.cols, rows=self.rows) elif self.row_ndx < ZERO: self.row_ndx += self.rows return self def at_row(self, row_ndx=ZERO): self.row_ndx = row_ndx self.audit_row() return self def stuff(self, atom, stretch=None, scrolled=False, scrolled_horizontally=False, scrolled_vertically=False, span_cols=1, span_rows=1): a_widget = atom if stretch is None: stretch = Stretch() if stretch.has_align(): a_widget = self.wrap_with_alignment(a_widget, stretch) if scrolled: scrolled_horizontally = True scrolled_vertically = True if scrolled_horizontally or scrolled_vertically: a_widget = self.wrap_with_scroll_box(a_widget, scrolled_horizontally, scrolled_vertically) a_widget.show() (xpad, ypad) = stretch.get_padding() self.attach( child=a_widget, left_attach=self.col_ndx, right_attach=self.col_ndx + span_cols, top_attach=self.row_ndx, bottom_attach=self.row_ndx + span_rows, xoptions=stretch.get_hor_flags(), yoptions=stretch.get_ver_flags(), xpadding=xpad, ypadding=ypad, ) self.bump_col() return atom def clear(self): ContainerWidgetAbstract.clear(self) self.at_row() self.at_col() return self class Mnode(list): def __init__(self, name=None, help=None): self.set_parent() if name is None: self.name = None self.mnemonic = None else: self.name = name.replace('_', NULL, 1) pos = name.find('_') if pos == NA: self.mnemonic = None else: self.mnemonic = name[pos + 1] self.set_help(help) self.factory = None self.method = None return def set_parent(self, parent=None): self.parent = parent return self def set_help(self, help=None): self.help = help return self def get_help(self): return self.help def stuff(self, menu_node): menu_node.set_parent(self) if menu_node.name is None: menu_node.name = 'NoName%i' % len(self) self.append(menu_node) return menu_node def get_name_with_mnemonic(self): if self.mnemonic is None: return self.name else: return self.name.replace(self.mnemonic, '_' + self.mnemonic, 1) def get_path(self, parent_path=NULL, with_=True): if with_: return '%s/%s' % (parent_path, self.get_name_with_mnemonic()) else: return '%s/%s' % (parent_path, self.name) def get_item_factory_entry(self, path): result = [(self.get_path(path, with_=True), None, None, ZERO, '')] result.extend(self.get_child_item_factory_entries(self.get_path(path, with_=False))) return result def get_child_item_factory_entries(self, path=NULL): result = [] for node in self: result.extend(node.get_item_factory_entry(path)) return result def compile(self, accel_group=None, class_of=gtk.Menu, obj=None): raise Error('Abstract') def disable(self, main, path): self.disable_unimplemented_children(main, self.get_path(path, with_=False)) return self def disable_unimplemented_children(self, main, path=NULL): for node in self: node.disable(main, path) return self def conjure_tip(self, main, path): path = self.get_path(path, with_=False) widget = main.find_item(path) help = self.get_help() set_tip(widget, help) self.conjure_child_tips(main, path) return self def conjure_child_tips(self, main, path=NULL): for node in self: node.conjure_tip(main, path) return self class MenuMain(Mnode): def compile(self, accel_group=None, class_of=gtk.Menu, obj=None): self.factory = gtk.ItemFactory(class_of, '
', accel_group) entries = self.get_child_item_factory_entries() self.factory.create_items(entries, obj) self.conjure_child_tips(self) self.disable_unimplemented_children(self) return self.find_main() def find_item(self, path): return self.factory.get_item(path) def find_sub(self, path): return self.factory.get_widget(path) def find_main(self): return self.find_sub('
') def set_sensitive(self, path, sensitive=True): widget = self.find_item(path) if widget is None: pass else: widget.set_sensitive(sensitive) return self class MenuSub(MenuMain): def __init__(self, name=None, help=None, id=None): MenuMain.__init__(self, name=name, help=help) self.id = id return def insert(self, menu, pos): self.item = gtk.ImageMenuItem(stock_id=self.id) self.item.set_submenu(self.find_main()) self.set_item_label() self.item.show() menu.insert(self.item, pos) return self.item def set_item_label(self, label=None): if label is None: label = self.get_name_with_mnemonic() label_widget = self.item.get_children()[ZERO] label_widget.set_markup_with_mnemonic(label.encode('utf-8')) return self def set_item_image(self, id): image_widget = self.item.get_children()[1] image_widget.set_from_stock(id, gtk.ICON_SIZE_MENU) return self class MenuMainBar(MenuMain): def compile(self, accel_group=None, class_of=gtk.MenuBar, obj=None): return MenuMain.compile(self, accel_group=accel_group, class_of= class_of, obj=obj) class Mitem(Mnode): def __init__(self, name=None, help=None, c=None, method=None, method_int_parm=ZERO, id=None): Mnode.__init__(self, name=name, help=help) self.c = c self.method = method self.method_int_parm = method_int_parm self.id = id return def stuff(self, menu_node): raise Error('Abstract') def get_item_factory_entry(self, path): if self.id is None: return [(self.get_path(path), self.c, self.method, self.method_int_parm, None)] else: return [(self.get_path(path), self.c, self.method, self.method_int_parm, '', self.id)] def disable(self, main, path): if self.method is None: main.set_sensitive(self.get_path(path, with_=False), sensitive=False) Mnode.disable(self, main, path) return self class Msep(Mitem): def __init__(self, name=None): Mitem.__init__(self, name=name) return def get_item_factory_entry(self, path): return [(self.get_path(path), self.c, self.method, self.method_int_parm, '')] def disable(self, main, path): Mnode.disable(self, main, path) return self class Config(dict): def __init__(self, main_window): dict.__init__(self) self.main_window = main_window self['GLOBAL'] = {'tontoversion': VERSION, 'on1': '1'} self['MODEM'] = {'repertoire': '-None-,/dev/modem,COM1:,COM2:,COM3:,COM4:,/dev/ttyS0,/dev/ttyS1,/dev/ttyS2,/dev/ttyS3', 'port': '-None-'} self.parser = ConfigParser.ConfigParser() self.parser.read([os.path.expanduser(INI_FILE_NAME)]) for sect in self.parser.sections(): for (opt, val) in self.parser.items(sect): self.set(sect, opt, val, checkpoint=False) if self.get('GLOBAL', 'on1') == '1': self['_Addresses'] = {'seq': '!0', 'file': '~/.Tonto.Adr', 'name': 'Address List', 'type': 'Address List'} self['_3x5 Cards'] = {'seq': '!1', 'file': '~/.Tonto.3x5', 'name': '3x5 Note Cards', 'type': '3x5 Note Cards'} self['_Calendar'] = {'seq': '!4', 'file': '~/.Tonto.Calendar', 'name': 'Calendar', 'type': 'Calendar'} self.splash_copyright() # self.checkpoint() return def get(self, section, item, default=None): return self.setdefault(section, {}).get(item, default) def set(self, section, item, value, checkpoint=True): self.setdefault(section, {})[item] = value if checkpoint: self.checkpoint() return self def remove(self, section, checkpoint=True): if section in self: del self[section] if checkpoint: self.checkpoint() return self def checkpoint(self): self.set('GLOBAL', 'tontoversion', VERSION, checkpoint=False) self.set('GLOBAL', 'on1', '0', checkpoint=False) for sect in self.parser.sections(): if sect in self: pass else: self.parser.remove_section(sect) for sect in self: if self.parser.has_section(sect): pass else: self.parser.add_section(sect) for (opt, val) in self[sect].iteritems(): self.parser.set(sect, opt, val) unit = file(os.path.expanduser(INI_FILE_NAME), 'wb') self.parser.write(unit) unit.close() return def dump_gif(self, name): unit_in = StringIO.StringIO('''R0lGODlhMAAwAPcAAAAAAKelkNze79ji69vc7u7nz9Xg6Onr9bqnZ+np9eLNgejp9LSzt/bv2vX2 +tPu6dPs6fT0+dTk6pSantja7tba7OXl9ODn79vn6tvj6vr47vHy+fDw+O/w99Xa7tPc7O7u9u3u 9enq8f39/szBmfv7/NzGd97f8Nzd7reod+zs99fHk+rq9aSTV+jo8/f3+9Tt6fX1+dTr6dnb7tvg 5r69uSwsLe7kvevZmeTm8sfFxeTk8t/k7fLz+fHx+NPh69Pf6/TqxtTb7MzLzf7+/svMwuDg8d7e 79rk68q6guzt9uvr9TEuJE1NUKCkqNrQrPj4+8HBxcjHuPPjqtvg79Xo6dvc78/Eoefn9OTp8eLn 7/T0+vLy+NXc7M65bs7MuuPj887Aj+Hj8cvIt+nXj9/h79zj7NLe7NTa7u7u9+zs9fv7/fn5+8zI u8iza9zj7/LiqNjj69jf65WGUNfb6unq9XFxcufq8+bq8ubm8rKgX8/U2PX1+tPp6dPn6aWYZs/E rdjb7tTh6tfb7dXd6+Xm9Ozq5+Tk8+Lm8SQkJp2NVOLi8d/k7qqruvTt0fHx+UNDQfDx+O/v99TZ 7ezt9Pj04tDT6fz8/fr6+9/k8fDir93g793e79jm6srM2dfi6dfg6evr9urr9efn8tXV3Yh6TL/A zvj4/Jyepfb2+vLy9tXg6uXp8+Xl8+ngwuHp7/HktuPl8ePj8d3p6/Ly+cW5jvDw99Pg69ba7r2q ZaKrpxkZGrq4uP////39/eHh8ionGt/f8FZWWeXj4h4fItnMntDTytLc7e3u9+zs9uvs9erq9Ojs 8vn5/Pz69ff3+uDRntTp6Nrf7tXl6drb7ubasQwKBtLb5uvaoubm8+LSmfX1+9DX0GdnafPz+fLz +NPn6sTCt/Hx99Pj6sGtaPj16pOVnsy5djg6PtHDkm5kPtvm6+/v+O7v97i4wu3t9tfd3b7F1Xx9 h/r6/AICAungvf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAPYALAAAAAAwADAA QAj+AO0JHEiwoMGDCBMqHMiAljcoay5dWvOClg9J71gsKATGCCcrBALNCGQOwMKE7yT5oBXhBRQo bGISIdKs14te3npEUlGojLRVQD6gmWTy5EEnsWLNWrpUFxIkAwbIkVPEmAE/Mh5A+HGmpNGv9qZg gzNlCg4yCkx4cdOiVFGwcO2V0lagXLkCruq5epKkhbW3cU8yeGfLhw9bkpa4yLZDlpETnAR4DVzQ iQ9vfKDMu9Tr0jw2L/hE4MIuTagEhcRsCjSoSwUPHuwA/mouwTJRlEDY2vCNW04uPiJ1aKdkGCtE ZtZVqQLu1gd5swMDQEXAUg1e4WqEk/LlS7gxQ/b+nEEFIDplgw2CVFtxJcy5XHr0kCChyPz5hbu6 2SFGz/59hcJMEAUpZexwxB7uyIPOfwk1QYMPETSzxhpQRCCJGiwkkM0hiwQjAAFWUONJIgzqgBkU zcxTwmehecNBGsmwgEUrv5xgBWuDVIBLI/4p9CBmLqm4BiZsQPFCKqJZJEkyCYhBhRyEENKFB/H0 iBA8aqQhiTgR8JGKIc88Q8RNN0XQAxcihJIDI3FIIEFQnliJkDt5aPFKFnjkOcwdfN7BSg6IaOGE GUh0IoMMEIxzBo//2bDHCSdk8FRVA3xiKQYTTINVH1x5QoyccbkCyBVtFKGDDm2M+sQV9TG4EA7+ QWjgjDOVBHHDDXvl4parCt2gjSaaYKONAl6slYsi6oD6Hz2Q/JHCH3OUog4TwPTH67XYZuuqHVGw wMIyLCzWWIdHNNKEsmA1QYok3qRiJB+2LJGAC63sUKMAVlhhCom8kvIIH81cMsIlp3gjSRoYztiR jQTMMIMp6B5kxyPtzjPhGvOk4s0jtqQhbzZg1GjFSBQEQsGClAlzGR8vSDhRM0eOxgEyp3FkhACs VaAjLsRQNsTKLUeEcTPvbmEROyrUYYFPcnQhxJSMxrWMLRRnBgUm83zGxilGprKNN7RwoOYmPxEi lCURCzRKGiCsFEMqz/giNxS+xJ2K3FwYEkr+IZnE8YkgQKCBdmDdqpGSD1zEoIohDvARQwTeeLNB JMOIkg0j63RShQTORQ1XE9m4kAUzzChDSQhRgKC6EsqIcoAo8DBCaCd99NHcMT2nHEsOr/TuuzGI XHBBJm+8wYMTn0wTDQwP+PHDMeikTRAAnoiRAQbYz6KL35+AYkAGRQgyjQwwJAoExNIbZIMpAqBA ByjXOMHN/NxgasA4BgABRDyfZgsAOoDgBQMYUIMaSKENX2hDG6QQgOilzyjYCEI5NHCXAhTAEdUo Ri6Spa2CVKIB2FhBOpJwDnLkIgW1CEOrOjgQWGgDDrDQxFlMoBZyQOuBXyEDLMrhDAo24AZCekkH AnbFQoE0oAD1uAIJkoCA+LQghXPAoVGAkQRtQAMaKziHG0zYAibQo4gGAYAN5GGHROyiPGBMSHnQ mMY22iMgADs= ''') unit_out = file(os.path.expanduser(name), 'wb') base64.decode(unit_in, unit_out) unit_out.close() unit_in.close() return self def dump_ico(self, name): unit_in = StringIO.StringIO('''AAABAAMAMDAAAAEACACoDgAANgAAACAgEAABAAQA6AIAAN4OAAAQEBAAAQAEACgBAADGEQAAKAAA ADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAAAAAAAAACQpacA797cAOvi2ADu3NsA z+fuAOjg1QD16+kAZ6e6APXp6QCBzeIA9OnoALeztADa7/YA+vb1AOnu0wDp7NMA+fT0AOrk1ACe mpQA7trYAOza1gD05eUA7+fgAOrn2wDq49sA7vj6APny8QD48PAA9/DvAO7a1QDs3NMA9u7uAPXu 7QDx6ukA/v39AJnBzAD8+/sAd8bcAPDf3gDu3dwAd6i3APfs7ACTx9cA9erqAFeTpADz6OgA+/f3 AOnt1AD59fUA6evUAO7b2QDm4NsAub2+AC0sLAC95O4AmdnrAPLm5ADFxccA8uTkAO3k3wD58/IA +PHxAOvh0wDr39MAxur0AOzb1ADNy8wA/v7+AMLMywDx4OAA797eAOvk2gCCusoA9u3sAPXr6wAk LjEAUE1NAKikoACs0NoA+/j4AMXBwQC4x8gAquPzAO/g2wDp6NUA79zbAKHEzwD05+cA8enkAO/n 4gD69PQA+PLyAOzc1QBuuc4AuszOAPPj4wCPwM4A8ePhALfIywCP1+kA7+HfAOzj3ADs3tIA7trU APfu7gD17OwA/fv7APv5+QC7yMwAa7PIAO/j3ACo4vIA6+PYAOvf2ABQhpUA6tvXAPXq6QBycXEA 8+rnAPLq5gDy5uYAX6CyANjUzwD69fUA6enTAOnn0wBmmKUArcTPAO7b2ADq4dQA7dvXAOvd1QD0 5uUA5+rsAPPk5ADx5uIAJiQkAFSNnQDx4uIA7uTfALqrqgDR7fQA+fHxAEFDQwD48fAA9+/vAO3Z 1AD07ewA4vT4AOnT0AD9/PwA+/r6APHk3wCv4vAA7+DdAO/e3QDq5tgA2czKAOni1wDp4NcA9uvr APXr6gDy5+cA3dXVAEx6iADOwL8A/Pj4AKWenAD69vYA9vLyAOrg1QDz6eUA8+XlAMLg6QDv6eEA tuTxAPHl4wDx4+MA6+ndAPny8gCOucUA9/DwAOvg0wDu2tYAZaq9AKerogAaGRkAuLi6AP///wD9 /f0A8uHhABonKgDw398AWVZWAOLj5QAiHx4AnszZAMrT0ADt3NIA9+7tAPbs7AD17OsA9OrqAPLs 6AD8+fkA9fr8APr39wCe0eAA6OnUAO7f2gDp5dUA7tvaALHa5gAGCgwA5tvSAKLa6wDz5uYAmdLi APv19QDQ19AAaWdnAPnz8wD48/IA6ufTALfCxAD38fEA6uPTAGitwQDq9fgAnpWTAHa5zAA+OjgA ksPRAD5kbgDr5tsA+O/vAPfv7gDCuLgA9u3tAN3d1wDVxb4Ah318APz6+gACAgIAveDpAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvddsT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAADbydom7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAA9PSQfyl/c6XqTMD0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwEna 0NAr527kuS1M9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApdoF5eUFrvWuT0kt 1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQX1VyRJCHp6LbVhcwAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN9qamtjaCl5ebrmK6gAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAZLDlzhrlDTf1rukIpQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAADUHVK1dh57l6eiQkigAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA sNpwsJo4ZCYmXuR/cwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOEEazs6VQTc3 rk+5pQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlQ3YK+lJ5+S5KbVhigAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU9hwU1M4ZAomXm4tpQAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAA2EHlGuUFBQWO1cW56gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAroBXbUU6Om2AV09XigAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AADogLwMDDU1Um1fbW1SAegAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgEljW84TXh Ul9f4WNDe2eoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2pgIodKDXTtzc3NwYEwbjBkBA 8cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADZ7JycZSEhFxgOfn58YE9N+Mn0/Z57EAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAJ5iGRgYGLO6cZ+foAYZRYLTMjAQ40CmAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAATrGxs7Ozs7O6SEgDA3JyRcYGfjIPED9n5gAAAAAAAAAAAAAAAAAAAAAAAAAAAADC sTmvr6+vr6/GiBcXmW9vPE6f09EwD34/x+gAAAAAAAAAAAAAAAAAAAAAAAAAAADueVqvWXh4eMN3 d3d3rDmIWk5mSJ0yMhDjZ40AAAAAAAAAAAAAAAAAAAAAAAAAAE3ZLlnMzMqUIVEgICBKyqIHovCM ZkidfX3gt8fEAAAAAAAAAAAAAAAAAAAAAAAAAOYJy6KUILYb39zePVw+kR3tSsOsiGbrVVXgtx/y AAAAAAAAAAAAAAAAAAAAAAAAAFEsau+SPlwxqoYOfDER3t4bkcOi2YzrnVUStx+NAAAAAAAAAAAA AAAAAAAAAAAAAPBqaZLiEXyphs/PRC+9L70RPVwioTmMcRISQB+eAAAAAAAAAAAAAAAAAAAAAAAA AKNpID60ManPvr6+UL7Pvqm+vlyGoYWZcZ+CQGiWAAAAAAAAAAAAAAAAAAAAAAAAAO+SPrQRL1BQ bGxsRETNvS+93j2RKoVl0qtAH2iTAAAAAAAAAAAAAAAAAAAAAAAAAMu2j958UFCY8/NsbKdQL6nb 3rQcoTmb0quEH2iWAAAAAAAAAAAAAAAAAAAAAAAAADQ+3nwvUPMla5hsUC+pfBG0PpLJCWJUcoSE XR7xAAAAAAAAAAAAAAAAAAAAAAAAAEM+3nwvzWuXa/PNUC98W7Q+7Cp1FmXScl1CXR6NAAAAAAAA AAAAAAAAAAAAAAAAAE4+3nxQ85e9l/NsL3wRXOxpoQmFYpuBg10VHh52AAAAAAAAAAAAAAAAAAAA AAAAAMI+3nwvzWuXa80vqRFcHMihCYVgRgKBgxUVuLjEAAAAAAAAAAAAAAAAAAAAAAAAAAA63nxQ zfMl82wvfN4cacksWK2/J1aBg4MVuI0AAAAAAAAAAAAAAAAAAAAAAAAAAAB2j96p82tra/Op3o+2 aUsJ2WC/J1YzgRSBFOgAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLTeUGuXl2svtD6S7ywLhWBGnFYE gTOB5gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKSPfM2XI5en3pJpaixYrWBGJ1YEMzOmAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAE00PhHNa2tQEZJqLAnZh4vBAgRW1J6JAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAABNpJLeqVAvfLZLCS6tO78nAlZWpokAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAzvtj4+tpJLLtk7skYnnALmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2USws yywu2Tuyi8FHjU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMITUaRlO0d77vLo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAD///////8AAP///////wAA///4f///AAD///A///8AAP//4B///wAA//+AB///AAD//wAH//8A AP//AAP//wAA//8AA///AAD//wAD//8AAP//AAP//wAA//8AA///AAD//wAD//8AAP//AAP//wAA //8AA///AAD//wAD//8AAP//AAP//wAA//8AA///AAD//AAA//8AAP/4AAB//wAA//AAAD//AAD/ 4AAAH/8AAP/AAAAP/wAA/8AAAA//AAD/gAAAB/8AAP+AAAAH/wAA/wAAAAf/AAD/AAAAA/8AAP8A AAAD/wAA/wAAAAP/AAD/AAAAA/8AAP8AAAAD/wAA/wAAAAP/AAD/AAAAA/8AAP8AAAAD/wAA/wAA AAP/AAD/AAAAB/8AAP+AAAAH/wAA/4AAAA//AAD/wAAAD/8AAP/gAAAf/wAA/+AAAD//AAD/8AAA f/8AAP/8AAD//wAA//4AAf//AAD//4AP//8AAP///////wAA////////AAAoAAAAIAAAAEAAAAAB AAQAAAAAAAAIAAAAAAAAAAAAABAAAAAAAAAAAAAAAO3c1wC3tbEA9u7uAPz7+wAdHiIAep6mAO3j 2ACRytkA9fLyAPLq5gDv5OMA2NrdAPr39wBnYWUAuNfdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAABeUAAAAAAAAAAAAAAAAAAO5uVQAAAAAAAAAAAAAAAFb/iIZQAAAAAAAA AAAAAADv/4hmZQAAAAAAAAAAAAAAb//4iGUAAAAAAAAAAAAAAG//iIhlAAAAAAAAAAAAAABv/PiI ZQAAAAAAAAAAAAAAbP+IiGUAAAAAAAAAAAAAAG//+IhlAAAAAAAAAAAAAADv/P//ZQAAAAAAAAAA AAAFYiIv/yZQAAAAAAAAAAAA4hwi//LBJQAAAAAAAAAADhd3IXfMd3FQAAAAAAAAAOK3dydxzHd3 JQAAAAAAAAAruqvKu7J3dxYAAAAAAAAFGqqsM6qsd3dxUAAAAAAABqoznJmZO6t3ceAAAAAAAAIz mZndRJOqd3FgAAAAAAAMM53URE1NqncRYAAAAAAADDndRETd2TpxESAAAAAAAAKd1ETd2ZOrERFg AAAAAAACndRE3ZMzqxER4AAAAAAADq3URN0zqrEREVAAAAAAAAAp1ETZM6uxERYAAAAAAAAA7JRE 2TqrsRElAAAAAAAAAA6tRNM6u7ER4AAAAAAAAAAA7J3TqrsRLgAAAAAAAAAAAA4rM6u7FlAAAAAA AAAAAAAABWIiJlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////8////+D///+Af///gD///wA// /8AP///AD///wA///8AP///AD///wA///4AH//8AA//+AAD//AAA//wAAH/8AAB/+AAAf/gAAH/4 AAA/+AAAP/gAAH/4AAB//AAAf/wAAP/+AAD//gAB//8AA///wAf///Af//////8oAAAAEAAAACAA AAABAAQAAAAAAAACAAAAAAAAAAAAABAAAAAAAAAAAAAAAHuAggD79/cA6uXVAPjx8QAVFBQA6uPg AMrW2wDl29UA8+rqAJavtADx5OIAQk1SAJ/M1wBlXlwAycjCAAAAAAAAAAAAAAAAzlAAAAAAAAp9 rAAAAAAADX2sAAAAAAANfd4AAAAAAAp33AAAAAAA7///4AAAAAFm8/M+AAAAWLa7gz8AAAAZSSKb M+AAAKQiIik44AAApCIkS4jgAADGIkSbjwAAAApCSbaOAAAAABZJv+AAAAAABRoVAAAA//8AAPx/ AAD4PwAA+D8AAPg/AAD4PwAA8B8AAOAPAADgDwAA4AcAAMAHAADgBwAA4A8AAOAPAADwHwAA/n8A AA== ''') unit_out = file(os.path.expanduser(name), 'wb') base64.decode(unit_in, unit_out) unit_out.close() unit_in.close() return self def dump_html(self, htmlfile_name): htmlfile_name = os.path.expanduser(htmlfile_name) html = codecs.open(htmlfile_name, 'wb', 'utf-8') html.write(''' Tonto GTK Stock Dialog Info Icon

Tonto v%s.%s (%s)

%s Tonto is a graphical application written in python (version %s) using GTK (version %s). It is shipped to environments such as Windows and Linux as a single executable file. (The symbolic script is available.) ''' % (MAIN_COLORS.get_hex('light gray'), VERSION[ZERO], VERSION[1], RELEASE_DATE.strftime(DATE_EDIT_FORMAT), paragraph_align(COPYRIGHT, break_='

\n\n'), get_python_version(), GTK_VERSION, URL_SCRIPT, )) manual = UsersManual() index = manual.index(Index()) html.write('
\n

Table of Contents

') html.write(manual.get_toc()) html.write('
\n') html.write(manual.get_body()) html.write('
\n

Index

') html.write(index.get_index()) html.write(''' ''') html.close() return def dump_gpl(self, gplfile_name): gplfile_name = os.path.expanduser(gplfile_name) gpl = file(gplfile_name, 'wb') gpl.write('''\t\t GNU GENERAL PUBLIC LICENSE \t\t Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St 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. \t\t\t 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 Library 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. \t\t 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. \t\t\t 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. \t\t END OF TERMS AND CONDITIONS \t 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 St, 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 Library General Public License instead of this License. ''') gpl.close() return def splash_copyright(self): self.dump_gif(GIF_NAME) self.dump_ico(ICO_NAME) self.dump_html(HELP_HTML_NAME) self.dump_gpl(GPL_NAME) self.main_window.call_back_menu_help_about() return self class TaggedStream(object): def __init__(self, name, mode): self.name = name self.mode = mode self.unit = codecs.open(os.path.expanduser(self.name), self.mode, 'utf-8') if mode.lower().startswith('r'): self.version = self.get() else: self.put('Tonto Version', VERSION) return def put(self, tag, value): line = '%s=%s\n' % (tag, repr(value)) self.unit.write(line) return def get(self): line = self.unit.readline()[:-1] group = line.split('=', 1) return eval(group[1]) def close(self): self.unit.close() return self class Repertoire(dict): def append(self, tag, method, icon=NULL, help=None): self[tag] = (method, icon, help) return self class RegistryEntry(object): def __init__(self, name, class_of, description): self.name = name self.class_of = class_of if description is None: self.description = None else: self.description = paragraph_align(description, strip_markup= True) return def get_list_item(self): label = Label() label.set_unicode(self.name) label.show() label.set_alignment(ZERO, ZERO) list_item = gtk.ListItem() list_item.add(label) list_item.show() set_tip(list_item, self.description) return list_item class Registry(list): def stuff(self, name, class_of, description): self.append(RegistryEntry(name, class_of, description)) return self def name_by_type(self): result = {} for entry in self: result[entry.class_of] = entry.name return result def type_by_name(self): result = {} for entry in self: result[entry.name] = entry.class_of return result def conjure_combo_box(self): result = ComboRestricted() result.list.clear_items(ZERO, -1) for entry in self: list_item = entry.get_list_item() result.list.append_items([list_item]) return result class Code(object): def __init__(self, as_stored=None, as_edited=None, as_displayed=None, alternates=None, help=None): self.set_as_stored(as_stored) self.set_as_edited(as_edited) self.set_as_displayed(as_displayed) self.set_alternates(alternates) self.set_help(help) return def set_as_stored(self, as_stored=None): self.as_stored = as_stored return self def get_as_stored(self): return self.as_stored def set_as_edited(self, as_edited=None): self.as_edited = as_edited if self.as_edited in BLANK: self.as_edited = self.as_stored return self def get_as_edited(self): return self.as_edited def set_as_displayed(self, as_displayed=None): self.as_displayed = as_displayed if self.as_displayed in BLANK: self.as_displayed = self.as_edited return self def get_as_displayed(self): return self.as_displayed def set_alternates(self, alternates=None): self.alternates = alternates if self.alternates is None: if self.get_as_edited() is None: self.alternates = [] else: self.alternates = [self.get_as_edited()] return self def get_alternates(self): return self.alternates def set_help(self, help=None): if help is None: self.help = None else: self.help = paragraph_align(help, strip_markup=True) return self def get_help(self): return self.help def get_list_item(self): label = Label() label.set_unicode(self.get_as_displayed()) label.show() label.set_alignment(ZERO, ZERO) list_item = gtk.ListItem() list_item.add(label) list_item.show() set_tip(list_item, self.help) return list_item def make_update_dlg(self, parent, width=LABEL_WIDTH + ENTRY_WIDTH + BUTTON_WIDTH + 20, height=FORM_HEIGHT): button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) dlg = gtk.Dialog(parent=parent, title='Code', flags=gtk.DIALOG_MODAL, buttons=button_list) self.box = create_container_widget(gtk.Table) self.box.show() scrolled_window = gtk.ScrolledWindow() scrolled_window.add_with_viewport(self.box) scrolled_window.show() scrolled_window.set_size_request(width=width, height=height) scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) # Avoid row shrink probs. dlg.vbox.pack_start(scrolled_window) self.align_label = Stretch().set_hor_align(1.0).set_ver_align(ZERO).set_expand(False) self.align_entry = Stretch().set_ver_expand(False) self.align_button = Stretch().set_expand(False) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_as_stored = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('As _Stored') label.add_help('The Coded Value') label.set_mnemonic_widget(self.entry_as_stored) if self.get_as_stored() is None: pass else: self.entry_as_stored.set_unicode(self.get_as_stored()) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_as_edited = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('As _Edited') label.add_help('A Shorthand Notation for the Code') label.set_mnemonic_widget(self.entry_as_edited) if self.get_as_edited() is None: pass else: self.entry_as_edited.set_unicode(self.get_as_edited()) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.list_alternates = self.box.stuff(List(), self.align_entry) box = self.box.stuff(create_container_widget(gtk.VBox), self.align_button) button_add = box.stuff(Button()) button_add.add_stock_label(gtk.STOCK_ADD, 'A_dd') button_add.add_help('Add an alternative notation.') button_add.connect('clicked', self.call_back_button_clicked_add, dlg) button_remove = box.stuff(Button()) button_remove.add_stock_label(gtk.STOCK_REMOVE, '_Remove') button_remove.add_help('Remove an alternative notation.') button_remove.connect('clicked', self.call_back_button_clicked_remove, dlg) self.box.bump_row() label.set_unicode('_Alternates') label.add_help(''' Alternative Notations for the Code. Any of these may be specified during input in place of As Edited.''') label.set_mnemonic_widget(self.list_alternates) item_list = [] for value in self.alternates: item_list.append(ListItem(markup=value)) self.list_alternates.set_list(item_list) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_as_displayed = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('As Dis_played') label.add_help('A Longhand Notation for the Code') label.set_mnemonic_widget(self.entry_as_displayed) if self.get_as_displayed() is None: pass else: self.entry_as_displayed.set_unicode(self.get_as_displayed()) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_help = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('_Help') label.add_help('A Short Description of the Meaning of the Code') label.set_mnemonic_widget(self.entry_help) if self.help is None: pass else: self.entry_help.set_unicode(self.help) return dlg def load_from_update_dlg(self, dlg): self.set_as_stored(self.entry_as_stored.get_unicode()) self.set_as_edited(self.entry_as_edited.get_unicode()) self.set_alternates() item_list = self.list_alternates.get_list() for item in item_list: alternate = item.get_markup() if alternate in self.alternates: pass else: self.alternates.append(alternate) self.set_as_displayed(self.entry_as_displayed.get_unicode()) self.set_help(self.entry_help.get_unicode()) return self def run_update_dlg(self, parent): dlg = self.make_update_dlg(parent) response = dlg.run() if response in [gtk.RESPONSE_OK]: self.load_from_update_dlg(dlg) dlg.destroy() return response def load(self, ddfile): self.set_as_stored(ddfile.get()) self.set_as_edited(ddfile.get()) self.set_as_displayed(ddfile.get()) self.alternates = [] alts = ddfile.get() while alts > ZERO: self.alternates.append(ddfile.get()) alts -= 1 self.set_help(ddfile.get()) return self def save(self, ddfile): ddfile.put(' AsStored', self.get_as_stored()) ddfile.put(' AsEdited', self.get_as_edited()) ddfile.put(' AsDisplayed', self.get_as_displayed()) ddfile.put(' List', len(self.get_alternates())) for alt in self.get_alternates(): ddfile.put(' Alt', alt) ddfile.put(' Help', self.get_help()) return self def call_back_button_clicked_add(self, widget, parent, *calling_sequence): dlg = DlgQuery(parent=parent, msg='Add Code Alternate', label_text='Notation', initial_text=NULL) response = dlg.run() if response in [gtk.RESPONSE_OK]: alternate = dlg.response.get_unicode() row_ndx = self.list_alternates.get_row_ndx() if row_ndx is None: row_ndx = ZERO self.list_alternates.insert(row_ndx, ListItem(markup= alternate)) dlg.destroy() return False def call_back_button_clicked_remove(self, widget, parent, *calling_sequence): row_ndx = self.list_alternates.get_row_ndx() if row_ndx is None: pass else: self.list_alternates.remove(row_ndx) return False class CodeColor(Code): def get_list_item(self): button = gtk.EventBox() button.modify_bg(gtk.STATE_NORMAL, MAIN_COLORS.get_color(self.get_as_stored())) label = Label() label.set_unicode(' ') label.show() button.add(label) label = Label() label.set_unicode(self.get_as_displayed()) box = create_container_widget() stretch=Stretch().set_expand(False).set_align(ZERO) box.stuff(button, stretch=stretch) box.stuff(label, stretch=stretch) box.show() list_item = gtk.ListItem() list_item.add(box) list_item.show() set_tip(list_item, self.help) return list_item class CodeSet(list): def __init__(self): self.encode_table = {} self.decode_table = {} self.display_table = {} return def conjure_combo_box(self, enforce_list=False): if enforce_list: result = ComboRestricted() else: result = Combo() result.list.clear_items(ZERO, -1) for code in self: list_item = code.get_list_item() result.list.append_items([list_item]) mnemonic = code.get_as_edited() result.set_item_string(list_item, mnemonic) return result def append(self, code): list.append(self, code) for value in code.alternates: (self.encode_table)[value] = code.get_as_stored() (self.decode_table)[code.as_stored] = code.get_as_edited() (self.display_table)[code.as_stored] = code.get_as_displayed() return self def get_as_displayed(self, as_stored, default=None): return self.display_table.get(as_stored, default) def get_as_edited(self, as_stored, default=None): return self.decode_table.get(as_stored, default) def get_as_stored(self, as_edited, default=None): return self.encode_table.get(as_edited, default) def has_encoding(self, as_edited): return as_edited in self.encode_table def load(self, ddfile): codes = ddfile.get() while codes > ZERO: self.append(Code().load(ddfile)) codes -= 1 return self def save(self, ddfile): ddfile.put(' List', len(self)) for code in self: code.save(ddfile) return self class Field(object): def __init__(self, name=None, tag=None, default=NULL, edit_format='%s', display_format=None): self.widget = None self.set_name(name) self.set_tag(tag) self.default = default self.edit_format = edit_format self.set_display_format(display_format) self.clear() self.set_help() self.repertoire = Repertoire() self.set_enabled() self.set_mod_lock() return def set_name(self, name=None): self.name = name return self def get_name(self): return self.name def set_tag(self, tag=None): if tag in BLANK: if self.name in BLANK: self.tag = None else: self.tag = self.name.replace(SPACE, '_').replace('-', '_') else: self.tag = tag return self def get_tag(self): return self.tag def set_display_format(self, display_format=None): if display_format in BLANK: self.display_format = self.edit_format else: self.display_format = display_format return self def get_renderer(self): return (gtk.CellRendererText(), 'markup') def make_widget(self): result = Text() if self.is_enabled(): pass else: result.set_editable(False) result.set_cursor_visible(False) result.set_sensitive(False) self.widget = result return result def stage_widget(self): if self.widget is None: pass else: if self.get_as_edited() is None: pass else: self.widget.set_unicode(self.get_as_edited()) return self def destage_widget(self): if self.widget is None: pass else: self.set_as_edited(self.widget.get_unicode()) return self def make_update_dlg(self, parent, width=LABEL_WIDTH + ENTRY_WIDTH + 20, height=FORM_HEIGHT): button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) dlg = gtk.Dialog(parent=parent, title='Field', flags=gtk.DIALOG_MODAL, buttons=button_list) self.box = create_container_widget(gtk.Table) self.box.show() scrolled_window = gtk.ScrolledWindow() scrolled_window.add_with_viewport(self.box) scrolled_window.show() scrolled_window.set_size_request(width=width, height=height) scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) # Avoid row shrink probs. dlg.vbox.pack_start(scrolled_window) self.align_label = Stretch().set_hor_align(1.0).set_ver_align(ZERO).set_expand(False) self.align_entry = Stretch().set_ver_expand(False) self.align_button = Stretch().set_expand(False) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_name = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('_Name') label.add_help('A Long Name for This Field') label.set_mnemonic_widget(self.entry_name) text = self.get_name() if text is None: pass else: self.entry_name.set_unicode(text) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_tag = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('_Tag') label.add_help('A Short Name for This Field — Defaults to Name') label.set_mnemonic_widget(self.entry_tag) text = self.get_tag() if text is None: pass else: self.entry_tag.set_unicode(text) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_default_value = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('De_fault Value') label.add_help('The Initial Value for This Field') label.set_mnemonic_widget(self.entry_default_value) self.clear() text = self.get_as_edited() if text is None: pass else: self.entry_default_value.set_unicode(text) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_edit_format = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('_Edit Format') label.add_help('The Presentation Layout for Updating') label.set_mnemonic_widget(self.entry_edit_format) self.entry_edit_format.set_unicode(self.edit_format) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_display_format = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('_Display Format') label.add_help('The Presentation Layout for Viewing') label.set_mnemonic_widget(self.entry_display_format) self.entry_display_format.set_unicode(self.display_format) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_help = self.box.stuff(Text()) self.box.bump_row() label.set_unicode('_Help') label.add_help('A Short Description of the Contents of This Field') label.set_mnemonic_widget(self.entry_help) text = self.get_help() if text is None: pass else: self.entry_help.set_unicode(text) return dlg def load_from_update_dlg(self, dlg): self.set_name(self.entry_name.get_unicode()) self.set_tag(self.entry_tag.get_unicode()) text = self.entry_default_value.get_unicode() if text in BLANK: self.default = NOT_AVAILABLE else: try: self.default = self.set_as_edited(text).get() except Error: self.default = NOT_AVAILABLE self.clear() self.edit_format = self.entry_edit_format.get_unicode() self.set_display_format(self.entry_display_format.get_unicode()) self.set_help(self.entry_help.get_unicode()) return self def run_update_dlg(self, parent): dlg = self.make_update_dlg(parent) response = dlg.run() if response in [gtk.RESPONSE_OK]: self.load_from_update_dlg(dlg) dlg.destroy() return response def clear(self): self.value = self.default return self def set(self, text): if text in [NOT_AVAILABLE, None]: self.clear() else: self.value = text return self def get(self): if self.value is None: if self.default is None: return NOT_AVAILABLE else: return self.default else: return self.value def is_clear(self): return self.get() in [self.default] def is_not_available(self): return self.get() in [NOT_AVAILABLE, None] def set_as_edited(self, text=None): self.set(unscape(text)) return self def get_as_edited(self): if self.is_not_available(): result = NOT_AVAILABLE else: result = self.edit_format % self.get() # print '"%s" %% <%s> = <%s>' % (self.edit_format, self.get(), result) return result def get_as_displayed(self): if self.is_not_available(): result = NOT_AVAILABLE else: result = self.display_format % self.get() return result def set_help(self, help=None): if help in BLANK: self.help = None else: self.help = help return self def get_help(self): return self.help def set_enabled(self, enabled=True): self.enabled = enabled return self def is_enabled(self): return self.enabled def set_mod_lock(self, mod_lock=False): self.mod_lock = mod_lock return self def is_mod_lock(self): return self.mod_lock def load(self, ddfile): self.tag = ddfile.get() self.default = ddfile.get() self.clear() self.edit_format = ddfile.get() self.display_format = ddfile.get() self.set_help(ddfile.get()) self.set_enabled(ddfile.get()) self.set_mod_lock(ddfile.get()) return self def save(self, ddfile): ddfile.put(' Tag', self.tag) ddfile.put(' Default', self.default) ddfile.put(' Edit Format', self.edit_format) ddfile.put(' Display Format', self.display_format) ddfile.put(' Description', self.get_help()) ddfile.put(' Can Edit', self.is_enabled()) ddfile.put(' Lock Mod', self.is_mod_lock()) return self FIELD_CLASS_REGISTRY = Registry() class FieldString(Field): pass FIELD_CLASS_REGISTRY.stuff('String', FieldString, 'String is any sequence of characters.') class FieldStringNoMarkup(Field): def get_as_edited(self): return enscape(Field.get_as_edited(self)) FIELD_CLASS_REGISTRY.stuff('String without Markup', FieldStringNoMarkup, 'String does not contain any markup tags or entities.') class FieldLog(FieldString): def __init__(self, name=None, tag=None, default=NULL, edit_format='%s', display_format=None): FieldString.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) self.repertoire.append('Now', self.call_back_field_button_now, icon=gtk.STOCK_ADD, help= ''' Insert time-stamp.''') return def call_back_field_button_now(self, another_self, relation, *calling_sequence): buffer = self.widget.get_buffer() buffer.insert_at_cursor(FieldDateTime().set_now().get_as_displayed()) self.widget.grab_focus() # 2007 Sep 30 return False FIELD_CLASS_REGISTRY.stuff('Log', FieldLog, 'Log is a String with a time-stamp feature.') class FieldTitle(FieldString): def __init__(self, name=None, tag=None, default=NULL, edit_format='%s', display_format=None): FieldString.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) self.repertoire.append('Caps', self.call_back_field_button_caps, icon=gtk.STOCK_BOLD, help= ''' Emulate title capitalization. This procedure is not perfect. Please, audit your results.''') return def call_back_field_button_caps(self, another_self, relation, *calling_sequence): def initial_cap(word): """This function tries to avoid problems with apostrophes in possessives and contractions that plague the string method. """ return word[:1].upper() + word[1:].lower() def un_case_word(word): word = word.lower() if word in UNCASE_WORDS: return word else: return initial_cap(word) def un_case_phrase(phrase): words = TITLE_SEP.split(phrase) result = [un_case_word(words.pop(ZERO))] while len(words) > ZERO: result.append(words.pop(ZERO)) result.append(un_case_word(words.pop(ZERO))) ndx = ZERO try: while result[ndx] == NULL: ndx += 2 except IndexError: ndx = ZERO result[ndx] = initial_cap(result[ndx]) ndx = -1 try: while result[ndx] == NULL: ndx -= 2 except IndexError: ndx = -1 result[ndx] = initial_cap(result[ndx]) return NULL.join(result) self.destage_widget() phrases = self.get().split(':') result = [un_case_phrase(phrase) for phrase in phrases] self.set((':').join(result)) self.stage_widget() return False FIELD_CLASS_REGISTRY.stuff('Title', FieldTitle, 'Title contains capitalized words.') class FieldStringWidth(FieldString): def __init__(self, name=None, tag=None, default=NULL, edit_format='%s', display_format=None, min_width=None, max_width=None): FieldString.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) self.min_width = min_width self.max_width = max_width return def set_as_edited(self, text=None): if text is None: result = FieldString.set_as_edited(self, text) else: if self.max_width is not None and len(text) > self.max_width: raise ErrorWidth('Too many characters in %s field.' % self.get_tag()) if self.min_width is not None and len(text) < self.min_width: raise ErrorWidth('Not enough characters in %s field.' % self.get_tag()) result = FieldString.set_as_edited(self, ('%-*s' % (self.min_width, text))[:self.max_width]) return result def make_update_dlg(self, parent, width=LABEL_WIDTH + ENTRY_WIDTH + 20, height=FORM_HEIGHT): dlg = FieldString.make_update_dlg(self, parent=parent, width= width, height=height) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_min_width = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('Mi_n Width') label.add_help('The Smallest Number of Characters Allowed') label.set_mnemonic_widget(self.entry_min_width) if self.min_width is None: pass else: self.entry_min_width.set_unicode(str(self.min_width)) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.entry_max_width = self.box.stuff(Text(), self.align_entry) self.box.bump_row() label.set_unicode('Ma_x Width') label.add_help('The Largest Number of Characters Allowed') label.set_mnemonic_widget(self.entry_max_width) if self.max_width is None: pass else: self.entry_max_width.set_unicode(str(self.max_width)) return dlg def load_from_update_dlg(self, dlg): FieldString.load_from_update_dlg(self, dlg) try: self.min_width = int(self.entry_min_width.get_unicode(), 10) self.max_width = int(self.entry_max_width.get_unicode(), 10) except (ValueError, TypeError): # 2007 Nov 08 pass return self def load(self, ddfile): FieldString.load(self, ddfile) self.min_width = ddfile.get() self.max_width = ddfile.get() return self def save(self, ddfile): FieldString.save(self, ddfile) ddfile.put(' Min Width', self.min_width) ddfile.put(' Max Width', self.max_width) return self FIELD_CLASS_REGISTRY.stuff('Fixed-Length String', FieldStringWidth, '''Fixed-Length String is a sequence of an exact number of characters.''') class FieldNumeric(FieldStringWidth): def __init__(self, name=None, tag=None, default=None, edit_format='%s', display_format=None, min_width=None, max_width=None): FieldStringWidth.__init__(self, name=name, tag=tag, default= default, edit_format=edit_format, display_format=display_format, min_width=min_width, max_width= max_width) return def set_as_edited(self, text=None): if text is None: result = FieldString.set_as_edited(self, text) else: if not text.isdigit(): raise ErrorNonNumeric('Non-numeric character in %s field.' % self.get_tag()) if self.max_width is not None and len(text) > self.max_width: raise ErrorWidth('Too many characters in %s field.' % self.get_tag()) if self.min_width is not None and len(text) < self.min_width: raise ErrorWidth('Not enough characters in %s field.' % self.get_tag()) result = FieldString.set_as_edited(self, ('%0*s' % (self.min_width, text))[:self.max_width]) return result FIELD_CLASS_REGISTRY.stuff('Numeric String', FieldNumeric, '''Numeric String is a sequence of an exact number of digits.''') class FieldChoice(FieldString): def __init__(self, name=None, tag=None, default=None, edit_format='%s', display_format=None, code_set=None): FieldString.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) self.code_set = code_set if self.code_set is None: self.code_set = CodeSet() return def make_update_dlg(self, parent, width=LABEL_WIDTH + ENTRY_WIDTH + BUTTON_WIDTH + 20, height=FORM_HEIGHT): dlg = FieldString.make_update_dlg(self, parent=parent, width= width, height=height) label = self.box.stuff(LabelWithToolTip(), self.align_label) self.list_code_set = self.box.stuff(List(), self.align_entry) self.list_code_set.connect('row-activated', self.call_back_row_activated, dlg) box = self.box.stuff(create_container_widget(gtk.VBox), self.align_button) button_add = box.stuff(Button()) button_add.add_stock_label(gtk.STOCK_ADD, 'A_dd') button_add.add_help('Add a Code to the Code Set') button_add.connect('clicked', self.call_back_button_clicked_add, dlg) button_update = box.stuff(Button()) button_update.add_stock_label(gtk.STOCK_JUMP_TO, '_Update') button_update.add_help('Update a Code in the Code Set.') button_update.connect('clicked', self.call_back_button_clicked_update, dlg) button_remove = box.stuff(Button()) button_remove.add_stock_label(gtk.STOCK_REMOVE, '_Remove') button_remove.add_help('Remove a Code from the Code Set.') button_remove.connect('clicked', self.call_back_button_clicked_remove, dlg) self.box.bump_row() label.set_unicode('_Code Set') label.add_help(''' List of Values. The value of this field may/must be one of these.''') label.set_mnemonic_widget(self.list_code_set) self.set_list() return dlg def set_list(self): item_list = [] for code in self.code_set: item_list.append(ListItem(object=code, markup=code.get_as_displayed())) self.list_code_set.set_list(item_list) return self def load_from_update_dlg(self, dlg): FieldString.load_from_update_dlg(self, dlg) item_list = self.list_code_set.get_list() self.code_set = CodeSet() for item in item_list: self.code_set.append(item.get_object()) return self def load(self, ddfile): FieldString.load(self, ddfile) self.code_set = CodeSet().load(ddfile) return self def save(self, ddfile): FieldString.save(self, ddfile) self.code_set.save(ddfile) return self def stage_widget(self): if self.widget is None: pass else: value = self.get_as_edited() recode = self.code_set.get_as_edited(self.get()) if recode is None: self.widget.set_unicode(value) else: self.widget.set_unicode(recode) return self def call_back_button_clicked_add(self, widget, parent, *calling_sequence): code = Code() response = code.run_update_dlg(parent=parent) if response in [gtk.RESPONSE_OK]: row_ndx = self.list_code_set.get_row_ndx() if row_ndx is None: row_ndx = ZERO self.list_code_set.insert(row_ndx, ListItem(object=code, markup=code.get_as_displayed())) return False def call_back_button_clicked_update(self, widget, parent, *calling_sequence): row_ndx = self.list_code_set.get_row_ndx() if row_ndx is None: pass else: code = self.list_code_set.get_object(row_ndx) code.run_update_dlg(parent=parent) self.set_list() return False def call_back_button_clicked_remove(self, widget, parent, *calling_sequence): row_ndx = self.list_code_set.get_row_ndx() if row_ndx is None: pass else: self.list_code_set.remove(row_ndx) return False def call_back_row_activated(self, widget, path, column, parent, *calling_sequence): self.call_back_button_clicked_update(widget, parent, *calling_sequence) return False class FieldCoded(FieldChoice): def set_as_edited(self, text=None): if not self.code_set.has_encoding(text): raise ErrorCodeTable('"%s" is not valid in %s field.' % (text, self.get_tag())) self.set(self.code_set.get_as_stored(text)) return self def get_as_edited(self): result = self.code_set.get_as_edited(self.get()) if result is None: result = NOT_AVAILABLE return result def get_as_displayed(self): result = self.code_set.get_as_displayed(self.get()) if result is None: result = NOT_AVAILABLE return result def make_widget(self, enforce_list=True): self.widget = self.code_set.conjure_combo_box(enforce_list= enforce_list) return self.widget FIELD_CLASS_REGISTRY.stuff('Code', FieldCoded, 'Code contains one value from a table of possible values.') BOOL_ALTERNATES = {True: [ 'T', 't', 'True', 'TRUE', 'true', 'Y', 'y', 'Yes', 'YES', 'yes', '1', 'On', '+', True, ], False: [ 'F', 'f', 'False', 'FALSE', 'false', 'N', 'n', 'No', 'NO', 'no', '0', 'Off', '-', False, ], None: [ '*', 'Unspecified', 'UNSPECIFIED', 'unspecified', 'None', 'X', 'x', SPACE, NULL, NOT_AVAILABLE, None, ]} class FieldBoolean(FieldCoded): def __init__(self, name=None, tag=None, default=None): code_set = CodeSet() code_set.append(Code(True, as_edited='T', as_displayed='True', alternates=BOOL_ALTERNATES[True])) (code_set.append(Code(False, as_edited='F', as_displayed='False', alternates=BOOL_ALTERNATES[False])), ) code_set.append(Code(None, as_edited='*', as_displayed= 'Unspecified', alternates=BOOL_ALTERNATES[None])) FieldCoded.__init__(self, name=name, tag=tag, default=default, code_set=code_set) return def make_update_dlg(self, parent, width=LABEL_WIDTH + ENTRY_WIDTH + 20, height=FORM_HEIGHT): dlg = FieldString.make_update_dlg(self, parent=parent, width= width, height=height) return dlg def load_from_update_dlg(self, dlg): FieldString.load_from_update_dlg(self, dlg) return self def load(self, ddfile): FieldString.load(self, ddfile) # Skip loading code tables. return self def save(self, ddfile): FieldString.save(self, ddfile) # Skip saving code tables. return self FIELD_CLASS_REGISTRY.stuff('Boolean', FieldBoolean, 'Boolean contains true or false.') class FieldCheckButton(FieldBoolean): def __init__(self, name=None, tag=None, default=None): FieldBoolean.__init__(self, name=name, tag=tag, default=default) self.code_set = CodeSet() self.code_set.append(Code(True, as_edited=gtk.STOCK_APPLY, alternates=BOOL_ALTERNATES[True] + [gtk.STOCK_APPLY])) self.code_set.append(Code(False, as_edited=gtk.STOCK_YES, alternates=BOOL_ALTERNATES[False] + [gtk.STOCK_YES])) self.code_set.append(Code(None, as_edited=gtk.STOCK_REMOVE, alternates=BOOL_ALTERNATES[None] + [gtk.STOCK_REMOVE])) return def get_renderer(self): return (gtk.CellRendererPixbuf(), 'stock-id') FIELD_CLASS_REGISTRY.stuff('CheckButton', FieldCheckButton, 'CheckButton displays a checkmark for true.') class FieldSelected(FieldChoice): def make_widget(self, enforce_list=False): self.widget = self.code_set.conjure_combo_box(enforce_list= enforce_list) return self.widget FIELD_CLASS_REGISTRY.stuff('Selection', FieldSelected, 'Selection contains a value from a list of suggested values — or not.') class FieldColorName(FieldString): """It is not practical to allow this to descend from cFieldSelected because that requires the creation of a huge widget in this instance. """ def __init__(self, name=None, tag=None, default=NULL): FieldString.__init__(self, name=name, tag=tag, default=default) self.repertoire.append('Pick', self.call_back_field_button_pick, icon=gtk.STOCK_INDEX, help= ''' Choose a standard color name.''') return def call_back_field_button_pick(self, another_self, relation, parent, *calling_sequence): dlg = DlgColorSelection(parent=parent) if dlg.run() in [gtk.RESPONSE_OK]: self.set(dlg.combo_box.get_unicode()) self.stage_widget() dlg.destroy() return False FIELD_CLASS_REGISTRY.stuff('Color Names', FieldColorName, 'Color Names contains a value from a suggested list.') class FieldStateProvince(FieldSelected): def __init__(self, name=None, tag=None, default=NULL): code_set = CodeSet() code_set.append(Code('AB', as_displayed='AB:\tAlberta')) code_set.append(Code('AK', as_displayed='AK:\tAlaska')) code_set.append(Code('AL', as_displayed='AL:\tAlabama')) code_set.append(Code('AR', as_displayed='AR:\tArkansas')) code_set.append(Code('AS', as_displayed='AS:\tAmerican Samoa')) code_set.append(Code('AZ', as_displayed='AZ:\tArizona')) code_set.append(Code('BC', as_displayed='BC:\tBritish Columbia')) code_set.append(Code('CA', as_displayed='CA:\tCalifornia')) code_set.append(Code('CO', as_displayed='CO:\tColorado')) code_set.append(Code('CT', as_displayed='CT:\tConnecticut')) code_set.append(Code('DC', as_displayed= 'DC:\tDistrict of Columbia')) code_set.append(Code('DE', as_displayed='DE:\tDelaware')) code_set.append(Code('FL', as_displayed='FL:\tFlorida')) code_set.append(Code('FM', as_displayed= 'FM:\tFederated States of Micronesia')) code_set.append(Code('GA', as_displayed='GA:\tGeorgia')) code_set.append(Code('GU', as_displayed='GU:\tGuam')) code_set.append(Code('HI', as_displayed='HI:\tHawaii')) code_set.append(Code('IA', as_displayed='IA:\tIowa')) code_set.append(Code('ID', as_displayed='ID:\tIdaho')) code_set.append(Code('IL', as_displayed='IL:\tIllinois')) code_set.append(Code('IN', as_displayed='IN:\tIndiana')) code_set.append(Code('KS', as_displayed='KS:\tKansas')) code_set.append(Code('KY', as_displayed='KY:\tKentucky')) code_set.append(Code('LA', as_displayed='LA:\tLouisiana')) code_set.append(Code('MA', as_displayed='MA:\tMassachusetts')) code_set.append(Code('MB', as_displayed='MB:\tManitoba')) code_set.append(Code('MD', as_displayed='MD:\tMaryland')) code_set.append(Code('ME', as_displayed='ME:\tMaine')) code_set.append(Code('MH', as_displayed='MH:\tMarshall Islands')) code_set.append(Code('MI', as_displayed='MI:\tMichigan')) code_set.append(Code('MN', as_displayed='MN:\tMinnesota')) code_set.append(Code('MO', as_displayed='MO:\tMissouri')) code_set.append(Code('MP', as_displayed= 'MP:\tNorthern Mariana Islands')) code_set.append(Code('MS', as_displayed='MS:\tMississippi')) code_set.append(Code('MT', as_displayed='MT:\tMontana')) code_set.append(Code('NB', as_displayed='NB:\tNew Brunswick')) code_set.append(Code('NC', as_displayed='NC:\tNorth Carolina')) code_set.append(Code('ND', as_displayed='ND:\tNorth Dakota')) code_set.append(Code('NE', as_displayed='NE:\tNebraska')) code_set.append(Code('NH', as_displayed='NH:\tNew Hampshire')) code_set.append(Code('NJ', as_displayed='NJ:\tNew Jersey')) code_set.append(Code('NL', as_displayed='NL:\tLabrador')) code_set.append(Code('NL', as_displayed='NL:\tNewfoundland')) code_set.append(Code('NM', as_displayed='NM:\tNew Mexico')) code_set.append(Code('NS', as_displayed='NS:\tNova Scotia')) code_set.append(Code('NT', as_displayed= 'NT:\tNorthwest Territories')) code_set.append(Code('NU', as_displayed='NU:\tNunavut')) code_set.append(Code('NV', as_displayed='NV:\tNevada')) code_set.append(Code('NY', as_displayed='NY:\tNew York')) code_set.append(Code('OH', as_displayed='OH:\tOhio')) code_set.append(Code('OK', as_displayed='OK:\tOklahoma')) code_set.append(Code('ON', as_displayed='ON:\tOntario')) code_set.append(Code('OR', as_displayed='OR:\tOregon')) code_set.append(Code('PA', as_displayed='PA:\tPennsylvania')) code_set.append(Code('PE', as_displayed= 'PE:\tPrince Edward Island')) code_set.append(Code('PR', as_displayed='PR:\tPuerto Rico')) code_set.append(Code('PW', as_displayed='PW:\tPalau')) code_set.append(Code('QC', as_displayed=u'QC:\tQu\xe9bec')) code_set.append(Code('RI', as_displayed='RI:\tRhode Island')) code_set.append(Code('SC', as_displayed='SC:\tSouth Carolina')) code_set.append(Code('SD', as_displayed='SD:\tSouth Dakota')) code_set.append(Code('SK', as_displayed='SK:\tSaskatchewan')) code_set.append(Code('TN', as_displayed='TN:\tTennessee')) code_set.append(Code('TX', as_displayed='TX:\tTexas')) code_set.append(Code('UT', as_displayed='UT:\tUtah')) code_set.append(Code('VA', as_displayed='VA:\tVirginia')) code_set.append(Code('VI', as_displayed='VI:\tVirgin Islands')) code_set.append(Code('VT', as_displayed='VT:\tVermont')) code_set.append(Code('WA', as_displayed='WA:\tWashington')) code_set.append(Code('WI', as_displayed='WI:\tWisconsin')) code_set.append(Code('WV', as_displayed='WV:\tWest Virginia')) code_set.append(Code('WY', as_displayed='WY:\tWyoming')) code_set.append(Code('YT', as_displayed='YT:\tYukon')) FieldSelected.__init__(self, name=name, tag=tag, default=default, code_set=code_set) return def load(self, ddfile): FieldString.load(self, ddfile) # Skip loading code tables. return self def save(self, ddfile): FieldString.save(self, ddfile) # Skip saving code tables. return self def make_update_dlg(self, parent, width=LABEL_WIDTH + ENTRY_WIDTH + 20, height=FORM_HEIGHT): dlg = FieldString.make_update_dlg(self, parent=parent, width= width, height=height) return dlg def load_from_update_dlg(self, dlg): FieldString.load_from_update_dlg(self, dlg) return self FIELD_CLASS_REGISTRY.stuff('State Province', FieldStateProvince, 'State Province contains a postal state or province code.') class FieldInteger(Field): def __init__(self, name=None, tag=None, default=None, edit_format='%i', display_format=None, min_value=None, max_value=None): Field.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) self.min_value = min_value self.max_value = max_value return def set_as_edited(self, text=None): try: self.set(int(text, 10)) except (ValueError, TypeError): self.set(None) return self.edit_range() def edit_range(self): if self.is_not_available(): pass else: if self.min_value is not None and self.get() < self.min_value: raise ErrorRange('Value too small for %s field.' % self.get_tag()) if self.max_value is not None and self.get() > self.max_value: raise ErrorRange('Value too large for %s field.' % self.get_tag()) return self def load(self, ddfile): Field.load(self, ddfile) self.min_value = ddfile.get() self.max_value = ddfile.get() return self def save(self, ddfile): Field.save(self, ddfile) ddfile.put(' Min Value', self.min_value) ddfile.put(' Max Value', self.max_value) return self def make_update_dlg(self, parent, width=LABEL_WIDTH + ENTRY_WIDTH + 20, height=FORM_HEIGHT): dlg = Field.make_update_dlg(self, parent=parent, width=width, height=height) return dlg def load_from_update_dlg(self, dlg): Field.load_from_update_dlg(self, dlg) return self FIELD_CLASS_REGISTRY.stuff('Integer', FieldInteger, 'Integer contains a whole number.') class FieldFloat(FieldInteger): def __init__(self, name=None, tag=None, default=None, edit_format='%f', display_format=None, min_value=None, max_value=None): FieldInteger.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format, min_value=min_value, max_value=max_value) return def set_as_edited(self, text=None): try: self.set(float(text)) except (ValueError, TypeError): # 2007 Nov 08 self.set(None) return self.edit_range() FIELD_CLASS_REGISTRY.stuff('Real', FieldFloat, 'Real contains a number with a decimal fraction.') class FieldDollars(FieldFloat): def __init__(self, name=None, tag=None, default=None, edit_format='%f', display_format='$%.2f', min_value=None, max_value=None): FieldFloat.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format, min_value=min_value, max_value=max_value) return FIELD_CLASS_REGISTRY.stuff('Dollars', FieldDollars, 'Dollars contains dollars and cents. Other\n currencies are not available at this time.') class FieldDateTimeAbstract(Field): def __init__(self, name=None, tag=None, default=None, edit_format=DATE_TIME_EDIT_FORMAT, display_format=DATE_TIME_DISPLAY_FORMAT): Field.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) return def get_as_edited(self): if self.is_not_available(): return NOT_AVAILABLE else: return self.get().strftime(str(self.edit_format)) def get_as_displayed(self): if self.is_not_available(): return NOT_AVAILABLE else: return self.get().strftime(str(self.display_format)) def set_now(self): raise Error('Abstract') class FieldDate(FieldDateTimeAbstract): def __init__(self, name=None, tag=None, default=None, edit_format=DATE_EDIT_FORMAT, display_format=DATE_DISPLAY_FORMAT): FieldDateTimeAbstract.__init__(self, name=name, tag=tag, default= default, edit_format=edit_format, display_format= display_format) self.repertoire.append('Pick', self.call_back_field_button_pick, icon=gtk.STOCK_INDEX, help= ''' Choose a date from a monthly calendar.''') if default is None: self.set_now() return def call_back_field_button_pick(self, another_self, relation, parent, *calling_sequence): date = self.get() if date in BLANK: date = datetime.date.today() dlg = DlgDateSelection(parent=parent, year=date.year, month=date.month, day=date.day, relation=relation) if dlg.run() in [gtk.RESPONSE_OK]: self.set(dlg.date) self.stage_widget() dlg.destroy() return False def set_as_edited(self, text=None): if text in BLANK: self.set(None) else: biblio = BiblioTime().set_as_edited(text) value = biblio.get_as_date() if value in BLANK: raise ErrorDateTime('Invalid value for %s%s%s%s%s%s%s.' % ('<<', self.get_tag(), '>>', text, '<<', self.get_tag(), '>>')) self.set(value) return self def set_now(self): self.set(datetime.date.today()) self.stage_widget() return self FIELD_CLASS_REGISTRY.stuff('Date', FieldDate, 'Date contains year/month/day.') class FieldBiblioDateRange(FieldString): def __init__(self, name=None, tag=None, default=NULL, edit_format='%s', display_format=None): FieldString.__init__(self, name=name, tag=tag, default=NULL, edit_format='%s', display_format=None) self.repertoire.append('Fix', self.call_back_field_button_fix, icon=gtk.STOCK_REFRESH, help= ''' Put the date range into regulation format. This procedure is not perfect. Please audit your results.''') return def call_back_field_button_fix(self, another_self, relation, *calling_sequence): self.destage_widget() self.set(str(BiblioTimeRange().set_as_edited(self.get()))) self.stage_widget() return False def set_now(self): now = datetime.date.today().strftime('%m/%d/%Y') self.set(str(BiblioTimeRange().set_as_edited(now))) self.stage_widget() return self FIELD_CLASS_REGISTRY.stuff('Bibliography Date Range', FieldBiblioDateRange, 'Bibliography Date Range contains year, month year, day\n month year, or a beginning and ending date.') class FieldDateTime(FieldDateTimeAbstract): def __init__(self, name=None, tag=None, default=None, edit_format=DATE_TIME_EDIT_FORMAT, display_format=DATE_TIME_DISPLAY_FORMAT): FieldDateTimeAbstract.__init__(self, name=name, tag=tag, default= default, edit_format=edit_format, display_format= display_format) if default is None: self.set_now() return def set_as_edited(self, text=None): if text in BLANK: self.set(None) else: biblio = BiblioTime().set_as_edited(text) value = biblio.get_as_date_time() if value in BLANK: raise ErrorDateTime('Invalid value for %s field.' % self.get_tag()) self.set(value) return self def set_now(self): self.set(datetime.datetime.now()) self.stage_widget() return self FIELD_CLASS_REGISTRY.stuff('Date and Time', FieldDateTime, 'Date and Time pinpoints an event.') class FieldTime(FieldDateTimeAbstract): def __init__(self, name=None, tag=None, default=None, edit_format=TIME_EDIT_FORMAT, display_format=TIME_DISPLAY_FORMAT): FieldDateTimeAbstract.__init__(self, name=name, tag=tag, default= default, edit_format=edit_format, display_format= display_format) self.repertoire.append('Pick', self.call_back_field_button_pick, icon=gtk.STOCK_INDEX, help= ''' Choose the nearest quarter hour.''') if default is None: self.set_now() return def call_back_field_button_pick(self, another_self, relation, parent, *calling_sequence): dlg = DlgTimeSelection(parent=parent) if dlg.run() in [gtk.RESPONSE_OK]: self.set(dlg.time) self.stage_widget() dlg.destroy() return False def set_as_edited(self, text=None): if text in BLANK: self.set(None) else: biblio = BiblioTime().set_as_edited(text) value = biblio.get_as_time() if value in BLANK: raise ErrorDateTime('Invalid value for %s field.' % self.get_tag()) self.set(value) return self def set_now(self): self.set(datetime.datetime.now().time()) self.stage_widget() return self FIELD_CLASS_REGISTRY.stuff('Time of Day', FieldTime, 'Time of Day contains hours:minutes:seconds.') class FieldTimeDelta(FieldDateTimeAbstract): def __init__(self, name=None, tag=None, default=None, edit_format='%1s%02i:%02i', display_format=None): FieldDateTimeAbstract.__init__(self, name=name, tag=tag, default= default, edit_format=edit_format, display_format= display_format) return def set_as_edited(self, text=None): if text in BLANK: self.set(None) else: biblio = BiblioTime().set_as_edited(text) value = biblio.get_as_time_delta() if value in BLANK: raise ErrorDateTime('Invalid value for %s field.' % self.get_tag()) self.set(value) return self def get_hhmmss(self): is_negative = self.get() < datetime.timedelta() if is_negative: delta = -self.get() sign = '-' else: delta = self.get() sign = NULL days = delta.days seconds = delta.seconds microseconds = delta.microseconds seconds = (days * 24) * 3600 + seconds + microseconds / 1000.0 (hours, seconds) = divmod(seconds, 3600) (minutes, seconds) = divmod(seconds, 60) seconds = int(seconds + 0.5) return (sign, hours, minutes, seconds) def get_as_edited(self): if self.is_not_available(): return NOT_AVAILABLE else: return self.edit_format % self.get_hhmmss()[:self.edit_format.count('%')] def get_as_displayed(self): if self.is_not_available(): return NOT_AVAILABLE else: return self.display_format % self.get_hhmmss()[:self.display_format.count('%')] FIELD_CLASS_REGISTRY.stuff('Time Difference', FieldTimeDelta, 'Time Difference holds the hours:minutes:seconds between events.') class FieldLink(FieldString): def __init__(self, name=None, tag=None, default=NULL, edit_format='%s', display_format=None): FieldString.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) self.repertoire.append('Open', self.call_back_field_button_link, icon=gtk.STOCK_GO_FORWARD, help= ''' Tonto will try to open a Web-browser window to retrieve this URL.''') return def call_back_field_button_link(self, another_self, relation, *calling_sequence): open_url(self.get(), relation=relation, row_ndx=relation.current_row) return False def get_as_displayed(self): if self.is_not_available(): return NOT_AVAILABLE elif self.get() in [NULL]: return NULL else: return '%s' % (self.get(), FieldString.get_as_displayed(self)) FIELD_CLASS_REGISTRY.stuff('Hot Link', FieldLink, 'Hot Link is an Internet URL.') class FieldEmailAddr(FieldString): def __init__(self, name=None, tag=None, default=NULL, edit_format='%s', display_format=None): FieldString.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) self.repertoire.append('Send', self.call_back_field_button_mail, icon=gtk.STOCK_GO_FORWARD, help= '''Tonto will try to open a Web-browser window to handle the mailto protocol.''') return def call_back_field_button_mail(self, another_self, relation, *calling_sequence): open_url(self.get()) return False def get_as_displayed(self, contact_name=None): # 2007 Aug 14 if self.is_not_available(): return NOT_AVAILABLE elif self.get() in [NULL]: return NULL else: result = self.get_quoted() if result.startswith('mailto:'): result = result[7:] if contact_name in BLANK: # 2007 Aug 14 pass else: result = '"%s" <%s>' % (contact_name, result) return 'mailto:%s' % ( url_quote(result.encode('UTF-8')), result) # 2007 Aug 14 # 2007 Jan 21 def get_quoted(self): # 2007 Jan 18 result = self.get().replace(r'\\',r'\\\\').replace('"',r'\\"') return result FIELD_CLASS_REGISTRY.stuff('eMail Address', FieldEmailAddr, 'eMail Address is a mailto destination.') class FieldPhone(FieldString): def __init__(self, name=None, tag=None, default=NULL, edit_format='%s', display_format=None): FieldString.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format) self.repertoire.append('Dial', self.call_back_field_button_dial, icon=gtk.STOCK_GO_FORWARD, help= AUTO_DIAL_SQUIB) return def call_back_field_button_dial(self, another_self, relation, parent, *calling_sequence): dlg = DlgDial(parent=MAIN, phone_number=self.get()) dlg.run() dlg.destroy() return False def get_as_displayed(self): if self.is_not_available(): return NOT_AVAILABLE elif self.get() in [NULL]: return NULL else: return '%s' % (self.get(), FieldString.get_as_displayed(self)) FIELD_CLASS_REGISTRY.stuff('Phone', FieldPhone, 'Phone is any sequence of characters that can be dialed.') class FieldLatitude(FieldFloat): def __init__(self, name=None, tag=None, default=None, edit_format='%f', display_format='%+011.7f', min_value=-90.0, max_value=90.0): FieldFloat.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format, min_value=min_value, max_value=max_value) return FIELD_CLASS_REGISTRY.stuff('Latitude', FieldLatitude, 'Latitude contains decimal degrees.') class FieldLongitude(FieldFloat): def __init__(self, name=None, tag=None, default=None, edit_format='%f', display_format='%+012.7f', min_value=-180.0, max_value=180.0): FieldFloat.__init__(self, name=name, tag=tag, default=default, edit_format=edit_format, display_format= display_format, min_value=min_value, max_value=max_value) return FIELD_CLASS_REGISTRY.stuff('Longitude', FieldLongitude, 'Longitude contains decimal degrees.') FIELD_CLASS_NAME = FIELD_CLASS_REGISTRY.name_by_type() FIELD_CLASS = FIELD_CLASS_REGISTRY.type_by_name() class Dstr(list): def __init__( self, ls=None, px=NULL, dt=SPACE, cj=None, cj2=None, sx=NULL, tp=TRAILING_PUNCT, tq=TRAILING_QUOTE, ): '''Concatenate a list of strings. DelimitedString=cDStr(aLs=[Initial Strings], aPx=Prefix,aDt=Delimiter,aCj=Conjunction,aCj2=Conjunction,aSx=Suffix, aTp=PunctuationRegex,aTq=QuotationRegex) >>> print cDStr([\'rock\',\'paper\',\'scissors\']).GetAsString() rock paper scissors >>> print cDStr([\'rock\',\'paper\',\'scissors\'],aDt=\', \').GetAsString() rock, paper, scissors >>> print cDStr([\'rock\',\'paper\',\'scissors\'],aDt=\', \',aCj=\', and \',aCj2=\' and \').GetAsString() rock, paper, and scissors >>> print cDStr([\'cat\',NULL,\'dog\'],aDt=\', \',aCj=\', and \',aCj2=\' and \').GetAsString() cat and dog >>> print cDStr([cDStr([\'2005\',\'01\',\'23\'],aDt=\'/\'),cDStr([\'22\',\'41\'],aDt=\':\')]).GetAsString() 2005/01/23 22:41 >>> print cDStr([\'To be\',\'or not to be?\'],aSx=\'.\').GetAsString() To be or not to be? >>> print cDStr([\'"That is\',\'the question"\'],aSx=\'.\').GetAsString() "That is the question." >>> print cDStr([\'or, by opposing, end them\'],aSx=\'.\').GetAsString() or, by opposing, end them. >>> print cDStr([\'thousand Naturall shockes That Flesh is heyre too?\'],aSx=\'.\').GetAsString() thousand Naturall shockes That Flesh is heyre too? >>> print "<%s>"%cDStr(aSx=".").GetAsString() <> >>> print cDStr([\'1984\'],aPx="<",aSx=">").GetAsString() <1984> ''' self.px = px self.dt = dt if cj is None: self.cj = self.dt else: self.cj = cj if cj2 is None: self.cj2 = self.cj else: self.cj2 = cj2 self.sx = sx if tp is None: tp = NULL if tq is None: tq = NULL self.trailing_punctuation_pattern = re.compile('(%s)(%s)(%s*)$' % (tp, tq, MATCHED_BRACKETS)) if ls is None: pass else: self.extend(ls) return def get_as_string(self): def convert2list(text): if isinstance(text, Dstr): return [text.get_as_string()] elif isinstance(text, str): return [text] elif isinstance(text, list): result = [] for elt in text: result.extend(convert2list(elt)) return result else: return [str(text)] strings = [] for string in self: strings.extend(convert2list(string)) strings = [string for string in strings if string != NULL] length = len(strings) result = NULL if length == ZERO: pass elif length == 1: result = strings[ZERO] elif length == 2: result = self.cj2.join(strings) else: result = self.dt.join(strings[:-1]) + self.cj + strings[-1] if result == NULL: return NULL match = self.trailing_punctuation_pattern.search(result) tp = match.group(1) tq = match.group(2) ttags = match.group(3) if ttags is None: ttags = NULL if tp in [None, NULL]: if tq in [None, NULL]: tail = ttags + self.sx else: tail = self.sx + tq + ttags else: tail = tp + tq + ttags return self.px + result[:match.start(1)] + tail class BiblioTime(dict): def get_flags_set(self): result = ZERO for flag in BIBLIO_TIME_ELTS: if flag in self: result |= BIBLIO_TIME_FLAG[flag] return result def get_as_date(self): try: result = datetime.date(self.get('YEAR'), self.get('MONTH'), self.get('DAY')) except (ValueError, TypeError): result = None return result def get_as_time(self): try: result = datetime.time(self.get('HOURS'), self.get('MINUTES', ZERO), self.get('SECONDS', ZERO)) except (ValueError, TypeError): result = None return result def get_as_date_time(self): try: result = datetime.datetime(self.get('YEAR'), self.get('MONTH'), self.get('DAY'), self.get('HOURS'), self.get('MINUTES', ZERO), self.get('SECONDS', ZERO)) except (ValueError, TypeError): result = None return result def get_as_time_delta(self): try: result = datetime.timedelta(hours=self.get('HOURS', ZERO), minutes=self.get('MINUTES', ZERO), seconds=self.get('SECONDS', ZERO)) * self.get('SIGN', 1) except (ValueError, TypeError): # 2007 Nov 08 result = None return result def get_era(self): if 'ERA' in self: result = self['ERA'] result = result.replace('.', NULL) result = result.replace(SPACE, NULL) result = result.upper() if result in ['AD']: return (result, NULL) else: return (NULL, result) else: return (NULL, NULL) def __str__(self): def get_str(elt, fmt): if elt in self: return fmt % self[elt] else: return NULL def get_month(): if 'MONTH' in self: return BIBLIO_MONTH[self['MONTH'] - 1] else: return NULL def get_sign(): if 'SIGN' in self: if self['SIGN'] > ZERO: return NULL else: return '-' else: return NULL (era1, era2) = self.get_era() year = get_str('YEAR', '%s') # Allow two-digit string in range of years. (See below.) month = get_month() day = get_str('DAY', '%i') hours = get_str('HOURS', '%02i') minutes = get_str('MINUTES', '%02i') seconds = get_str('SECONDS', '%02i') sign = get_sign() bhours = Dstr([sign, hours], dt=NULL) btime = Dstr([bhours, minutes, seconds], dt=':') byear = Dstr([era1, year, era2]) bdate = Dstr([day, month, byear]) bdate_time = Dstr([btime, bdate]) return bdate_time.get_as_string() def set_as_edited(self, text): """Set date and time from a string. The flexibility of the string is nearly unlimited. Almost anything will be valid. The price of this flexibility is nearly eternal vigilance. Audit results closely. >>> print cBiblioTime().SetAsEdited('02102005 22.11') 22:11 10 Feb. 2005 >>> print cBiblioTime().SetAsEdited('20060210 12.11am') 00:11 20 June 210 >>> print cBiblioTime().SetAsEdited('2006/02/10 12/11 a.m.') 10 Feb. 2006 >>> print cBiblioTime().SetAsEdited('2006:02:10 12:11 a.m.') 00:11 >>> print cBiblioTime().SetAsEdited('2006/02/10 12:11 p.m.') 12:11 10 Feb. 2006 >>> print cBiblioTime().SetAsEdited('2006.02.10 00:11 p.m.') 12:11 10 Feb. 2006 >>> print cBiblioTime().SetAsEdited('10 02 2006 12:11 a.m.') 00:11 2 Oct. 2006 >>> print cBiblioTime().SetAsEdited('15 02 2006 12:11 midnight') 00:11 15 Feb. 2006 >>> print cBiblioTime().SetAsEdited('10 Feb 2006 00:11 noon') 12:11 10 Feb. 2006 >>> print cBiblioTime().SetAsEdited('February 10, 2006 00:11:05') 00:11:05 10 Feb. 2006 >>> print cBiblioTime().SetAsEdited('-00:11:05') -00:11:05 >>> print cBiblioTime().SetAsEdited('Aug ad 0079') Aug. AD 79 >>> print cBiblioTime().SetAsEdited('1066ce Sep') Sept. 1066 CE >>> print cBiblioTime().SetAsEdited('4713 BCE') 4713 BCE >>> print '<%s>'%cBiblioTime().SetAsEdited('') <> """ def whack(text, rex, elts): match = rex.search(text) if match: for (ndx, elt) in enumerate(elts): self[elt] = match.group(ndx + 1) text = text[:match.start()] + text[match.end():] result = True else: result = False return (result, text) def to_int(elt): if elt in self: if self[elt] in [None, NULL]: self.pop(elt) else: self[elt] = int(self[elt]) return def month_to_int(): if 'MONTH' in self: month = (self['MONTH'])[:3].lower() if month in BIBLIO_MONTH_ABBREV: self['MONTH'] = BIBLIO_MONTH_ABBREV.index(month) + 1 else: to_int('MONTH') return def year_to_int(): def match(rex): if isinstance(rex, str): rex = re.compile(rex) year = self['YEAR'] match = rex.search(year) if match is None: return self['ERA'] = match.group(1) self['YEAR'] = year[:match.start()] + year[match.end():] return if 'YEAR' in self: match('(%s)' % PARSE_ERA_LEADING) match('(%s)' % PARSE_ERA_TRAILING) to_int('YEAR') if 'ERA' in self: era = self['ERA'] era = era.replace('.', NULL) era = era.replace(SPACE, NULL) era = era.upper() self['ERA'] = era return def sign_to_int(): if 'SIGN' in self: if self['SIGN'] in ['-']: self['SIGN'] = -1 return self['SIGN'] = 1 return def hours_to_int(): to_int('HOURS') if 'MERIDIAN' in self: if 'HOURS' in self: meridian = self['MERIDIAN'] if meridian in [None, NULL]: pass else: meridian = meridian[:1].lower() if meridian in ['a', 'm']: if self['HOURS'] == 12: self['HOURS'] = ZERO elif meridian in ['p', 'n']: if self['HOURS'] == 12: pass else: self['HOURS'] += 12 self.pop('MERIDIAN') is_found = False if not is_found: (is_found, text) = whack(text, PARSE_DATE_MDY, ['MONTH', 'DAY', 'YEAR']) if not is_found: (is_found, text) = whack(text, PARSE_DATE_DMY, ['DAY', 'MONTH', 'YEAR']) if not is_found: (is_found, text) = whack(text, PARSE_DATE_YMD, ['YEAR', 'MONTH', 'DAY']) if not is_found: (is_found, text) = whack(text, PARSE_DATE_MY, ['MONTH', 'YEAR']) if not is_found: (is_found, text) = whack(text, PARSE_DATE_YM, ['YEAR', 'MONTH']) is_found = False if not is_found: (is_found, text) = whack(text, PARSE_TIME_12, ['HOURS', 'MINUTES', 'SECONDS', 'MERIDIAN']) if not is_found: (is_found, text) = whack(text, PARSE_TIME_24, ['SIGN', 'HOURS', 'MINUTES', 'SECONDS']) is_found = 'YEAR' in self if not is_found: (is_found, text) = whack(text, PARSE_DATE_Y, ['YEAR']) sign_to_int() hours_to_int() to_int('MINUTES') to_int('SECONDS') year_to_int() month_to_int() to_int('DAY') return self class BiblioTimeRange(list): def __str__(self): def fix_era(lo, hi): flo = lo.get_flags_set() fhi = hi.get_flags_set() if BIBLIO_TIME_FLAG['ERA'] & flo and BIBLIO_TIME_FLAG['ERA'] & \ fhi: if BIBLIO_TIME_FLAG['MONTH'] & flo or BIBLIO_TIME_FLAG['MONTH'] & \ fhi: pass else: if lo['ERA'] == hi['ERA']: (era1, era2) = lo.get_era() if era1 is None: lo.remove('ERA') else: lo['ERA'] = era1 if era2 is None: hi.remove('ERA') else: hi['ERA'] = era2 return def fix_years(lo, hi): flo = lo.get_flags_set() fhi = hi.get_flags_set() if BIBLIO_TIME_FLAG['YEAR'] & flo and not BIBLIO_TIME_FLAG['YEAR'] ^ \ fhi: year_lo = str(lo['YEAR']) year_hi = str(hi['YEAR']) if year_lo[:2] == year_hi[:2] and len(year_hi) == 4: hi['YEAR'] = year_hi[2:] return time_lo = BiblioTime() time_hi = BiblioTime() flags_lo = self[ZERO].get_flags_set() flags_hi = self[1].get_flags_set() flags_both = flags_lo & flags_hi flag_diff = ZERO for elt in BIBLIO_TIME_ELTS: flag = BIBLIO_TIME_FLAG[elt] if flag & flags_both: lo = self[ZERO][elt] hi = self[1][elt] if lo == hi: pass else: flag_diff = flag break (flag_before, flag_after) = BIBLIO_TIME_SHOW.get(flag_diff, (255, ZERO)) for elt in BIBLIO_TIME_ELTS: flag = BIBLIO_TIME_FLAG[elt] if flag > flag_diff: if flag & flags_lo: if flag & flag_before: time_lo[elt] = self[ZERO][elt] else: time_hi[elt] = self[ZERO][elt] elif flag & flags_hi: if flag & flag_before: time_lo[elt] = self[1][elt] else: time_hi[elt] = self[1][elt] else: if flag & flags_both: time_lo[elt] = self[ZERO][elt] time_hi[elt] = self[1][elt] elif flag & flags_lo: time_lo[elt] = self[ZERO][elt] time_hi[elt] = self[ZERO][elt] elif flag & flags_hi: time_lo[elt] = self[1][elt] time_hi[elt] = self[1][elt] fix_era(time_lo, time_hi) fix_years(time_lo, time_hi) result = Dstr([time_lo, time_hi], dt='-').get_as_string() return result def set_as_edited(self, text): """Set a date/time range from a string. >>> print cBiblioTimeRange().SetAsEdited('02102005 22.11-12 midnight') 22:11-00:11 10 Feb. 2005 >>> print cBiblioTimeRange().SetAsEdited('02102005 22.11') 22:11 10 Feb. 2005 >>> print cBiblioTimeRange().SetAsEdited('02102005 22.00-22.15') 22:00-15 10 Feb. 2005 >>> print cBiblioTimeRange().SetAsEdited('02/10/2005-02/11/2005 22:11') 22:11 10-22:11 11 Feb. 2005 >>> print cBiblioTimeRange().SetAsEdited('02/10/2005-02/11/2005') 10-11 Feb. 2005 >>> print cBiblioTimeRange().SetAsEdited('2 Oct 2005-2 Nov 2005') 2 Oct.-2 Nov. 2005 >>> print cBiblioTimeRange().SetAsEdited('Oct 2005-Nov 2005 CE') Oct.-Nov. 2005 CE >>> print cBiblioTimeRange().SetAsEdited('12 July 0100 BC - March 15 0044 BC') 12 July 100 BC-15 Mar. 44 BC >>> print cBiblioTimeRange().SetAsEdited('0100 BC - 0044 BC') 100-44 BC >>> print cBiblioTimeRange().SetAsEdited('23 September 0063 BC - 19 August AD 0014') 23 Sept. 63 BC-19 Aug. AD 14 >>> print cBiblioTimeRange().SetAsEdited('0063 BC - AD 0014') 63 BC-AD 14 >>> print cBiblioTimeRange().SetAsEdited('December 15, 0037 AD - June 9, 0068 AD') 15 Dec. AD 37-9 June AD 68 >>> print cBiblioTimeRange().SetAsEdited('0037 AD - 0068 AD') AD 37-68 >>> print cBiblioTimeRange().SetAsEdited('1956-1958') 1956-58 """ lo = BiblioTime() hi = BiblioTime() list = text.split('-', 1) lo.set_as_edited(list[ZERO]) if len(list) > 1: hi.set_as_edited(list[1]) del self[:] self.append(lo) self.append(hi) return self class ColumnList(list): def get_tags(self, include_insensitive=True): result = [] for fld in self.get_next_fld(): if include_insensitive or fld.is_enabled(): result.append(fld.get_tag()) return result def get_next_fld(self): for page in self: for fld in page: yield fld return def load(self, ddfile, mask_mod_lock=True): self.clear() pages = ddfile.get() while pages > ZERO: self.bump_page() cols = ddfile.get() while cols > ZERO: class_of = FIELD_CLASS[ddfile.get()] name = ddfile.get() col = class_of(name) col.load(ddfile) col.set_mod_lock(col.is_mod_lock() and mask_mod_lock) self.append(col) cols -= 1 pages -= 1 return self def merge(self, ddfile, mask_mod_lock=True): pages = ddfile.get() while pages > ZERO: cols = ddfile.get() while cols > ZERO: class_of = FIELD_CLASS[ddfile.get()] name = ddfile.get() col = class_of(name) col.load(ddfile) col.set_mod_lock(col.is_mod_lock() and mask_mod_lock) tag = col.get_tag() if self.find(tag) is None: self.append(col) cols -= 1 pages -= 1 return self def save(self, ddfile): ddfile.put('Pages', len(self)) for page in self: ddfile.put('Columns', len(page)) for col in page: ddfile.put('Column', FIELD_CLASS_NAME[type(col)]) ddfile.put(' Name', col.get_name()) col.save(ddfile) return self def find(self, tag): for col in self.get_next_fld(): if col.get_tag() == tag: return col return None def bump_page(self): list.append(self, []) return self def append(self, field): self[-1].append(field) return self def copy(self): return [page[:] for page in self] def clear(self): del self[:] return self def get_list_widget(self): items = [] for page in self: for field in page: items.append(ListItem(object=field, markup=field.get_name(), is_insensitive=field.is_mod_lock())) items.append(ListSep()) if len(items) > ZERO: del items[-1] result = List() result.set_list(items) return result def load_from_list_widget(self, widget): items = widget.get_list() self.clear() self.bump_page() for item in items: field = item.get_object() if field is None: self.bump_page() else: self.append(field) return self class Iterator(object): def __init__(self, list, start_ndx=ZERO, reversed=False): self.list = list if reversed: self.range = range(start_ndx + 1, len(self.list)) + range(ZERO, start_ndx + 1) self.range.reverse() else: self.range = range(start_ndx, len(self.list)) + range(ZERO, start_ndx) return def __iter__(self): return self.generator() def generator(self): for ndx in self.range: yield (ndx, (self.list)[ndx]) return class DlgHot(gtk.Dialog): def __init__( self, parent=None, relation=None, row_ndx=None, stock_id=None, title=None, markup=None, button_response_list=None, text_width=450, text_height=300, ): self.parent_ = parent self.relation = relation self.row_ndx = row_ndx if stock_id is None: label = None else: lookup = gtk.stock_lookup(stock_id) if lookup is None: label = None else: (id, label, modifier, key_val, translation_domain) = \ lookup if self.parent_ is None: parent_title = None else: try: parent_title = self.parent_.get_title() except AttributeError: self.parent_ = None parent_title = NULL if parent_title == NULL: parent_title = None title_repertoire = [title, label, parent_title, 'Tonto'] for title in title_repertoire: if title is None: continue break if button_response_list is None: button_response_list = tuple1() gtk.Dialog.__init__(self, title=title, parent=self.parent_, flags=gtk.DIALOG_MODAL, buttons= button_response_list) self.box = create_container_widget(gtk.Table) if stock_id is None: pass else: icon = self.box.stuff(gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_DIALOG), stretch=Stretch().set_ver_align(ZERO)) self.conjure_controls(markup, width=text_width, height= text_height) self.box.show() self.vbox.pack_start(self.box) return def conjure_controls(self, markup, width=-1, height=-1): self.text = self.box.stuff(LabelHot(markup, width=width, height= height, relation=self.relation, row_ndx=self.row_ndx), scrolled=True) self.handler_id_realize = self.connect_after('realize', call_back_after_realize_to_set_text_view_base_color, [self.text]) return self class DlgAssertion(DlgHot): def __init__(self, parent, msg=None): if msg is None: msg = NULL else: msg = paragraph_align(msg) button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) DlgHot.__init__(self, parent=parent, stock_id=gtk.STOCK_DIALOG_INFO, markup=msg, button_response_list=button_list) return class DlgQuery(DlgHot): def __init__(self, parent, msg=None, label_text=None, initial_text=NULL): if msg is None: msg = NULL else: msg = paragraph_align(msg) self.label_text = label_text self.initial_text = initial_text button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) DlgHot.__init__(self, parent=parent, stock_id=gtk.STOCK_DIALOG_QUESTION, markup=msg, button_response_list=button_list, text_width=-1, text_height=-1) self.set_default_response(gtk.RESPONSE_OK) self.response.grab_focus() self.response.set_activates_default(True) return def conjure_controls(self, markup, width=-1, height=-1): DlgHot.conjure_controls(self, markup=markup, width=width, height= height) self.box.bump_row().bump_col() box = self.box.stuff(create_container_widget(gtk.HBox), stretch= Stretch().set_expand(False)) label = box.stuff(Label()) label.set_unicode(self.label_text) self.response = box.stuff(Entry()) self.response.set_unicode(self.initial_text) self.response.set_flags(gtk.CAN_FOCUS) return self class DlgDial(DlgHot): def __init__(self, parent, phone_number): self.phone_number = phone_number DlgHot.__init__(self, parent=parent, title='Dial', text_width= 700, text_height=350) self.conjure_buttons() port = parent.config.get('MODEM', 'port', '-None-') if port in ['-None-']: self.modem = NoneModem(port) else: self.modem = Modem(port) self.modem.set_progress_window(self.text) self.modem.open() return def destroy(self): self.modem.close() return DlgHot.destroy(self) def conjure_buttons(self): self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) button_dial = Button().add_stock_label(gtk.STOCK_ADD, '_Dial').add_help(''' Dial takes the MODEM off-hook and dials the above number. Wait until dialing is complete. Then pick up your extension phone and be ready to talk. Click the Squelch button before your party answers.''') button_dial.connect('clicked', self.call_back_button_clicked_dial) button_dial.show() self.action_area.pack_end(button_dial) button_on_hook = Button().add_stock_label(gtk.STOCK_REMOVE, '_Squelch').add_help(''' Squelch puts the MODEM back on-hook. Be sure to pick up your extension phone first; otherwise, you will break the outgoing connection.''') button_on_hook.connect('clicked', self.call_back_button_clicked_on_hook) button_on_hook.show() self.action_area.pack_end(button_on_hook) return def conjure_controls(self, markup, width=-1, height=-1): box = self.box.stuff(create_container_widget(gtk.HBox)) label = box.stuff(Label(), stretch=Stretch().set_align(1.0)) label.set_unicode('_Phone') self.number_to_dial = box.stuff(Entry()) self.number_to_dial.set_unicode('1,%s' % self.phone_number) label.set_mnemonic_widget(self.number_to_dial) self.box.bump_row() DlgHot.conjure_controls(self, markup=markup, width=width, height= height) return self def call_back_button_clicked_dial(self, *calling_sequence): self.modem.dial(self.number_to_dial.get_unicode()) return False def call_back_button_clicked_on_hook(self, *calling_sequence): self.modem.hang_up() return False class DlgChooseModem(DlgHot): def __init__(self, parent): DlgHot.__init__(self, parent=parent, title='Select MODEM', text_width=700, text_height=400) self.conjure_buttons() return def conjure_buttons(self): self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) button_auto_detect = Button().add_stock_label(gtk.STOCK_EXECUTE, '_Auto-Detect').add_help(''' Test the ports in the above list and choose the first that acts like a MODEM. WARNING: This probe puts data on the serial ports and will derange the operation of any equipment connected to the ports such as a serial mouse.''') button_auto_detect.connect('clicked', self.call_back_button_clicked_auto_detect) button_auto_detect.show() self.action_area.pack_end(button_auto_detect) button_test = Button().add_stock_label(gtk.STOCK_APPLY, '_Test').add_help(""" Test the selected port to be sure it acts like a MODEM. If you don't want Tonto to have any dial-out capability, select -None-. The test of -None- is always successful.""") button_test.connect('clicked', self.call_back_button_clicked_test) button_test.show() self.action_area.pack_end(button_test) self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) self.button_ok = self.action_area.get_children()[ZERO] self.button_ok.set_sensitive(False) return self def conjure_controls(self, markup, width=-1, height=-1): self.combo_box = Combo() self.list = [] label_port = Label() label_port.set_unicode('_Port') label_port.set_mnemonic_widget(self.combo_box.entry) box = create_container_widget() box.stuff(label_port) box.stuff(self.combo_box) self.box.stuff(box) self.box.bump_row() DlgHot.conjure_controls(self, markup=markup, width=width, height= height) self.text.set_wrap_mode = gtk.WRAP_NONE self.combo_box.list.clear_items(ZERO, -1) ports = self.parent_.config.get('MODEM', 'repertoire', '-None-') for port in ports.split(','): if port in ['-None-']: modem = NoneModem(port) else: modem = Modem(port) modem.set_progress_window(self.text) modem.show() list_item = gtk.ListItem() list_item.add(modem) list_item.show() self.combo_box.list.append_items([list_item]) self.combo_box.set_item_string(list_item, modem.get_port_name()) self.list.append(modem) self.combo_box.set_unicode(self.parent_.config.get('MODEM', 'port', '-None-')) return self def call_back_button_clicked_auto_detect(self, *calling_sequence): list = (self.list)[:] # ... a copy. list.reverse() for modem in list[:-1]: # Skip -None-. if modem.open(): modem.test() modem.close() if modem.is_active: self.combo_box.set_unicode(modem.get_port_name()) self.button_ok.set_sensitive(True) return False def call_back_button_clicked_test(self, *calling_sequence): port = self.combo_box.get_unicode() if port in ['-None-']: modem = NoneModem(port) else: modem = Modem(port) modem.set_progress_window(self.text) if modem.open(): modem.test() modem.close() self.button_ok.set_sensitive(modem.is_active) return False class DlgFind(gtk.Dialog): def __init__(self, parent, title='Find and Replace', matrix=None): self.matrix = matrix self.relation = self.matrix.relation self.column_list = self.relation.column_list self.tags = self.column_list.get_tags() self.tag_ndx = ZERO button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) gtk.Dialog.__init__(self, parent=parent, title=title, flags=gtk.DIALOG_MODAL, buttons=button_list) self.set_has_separator(False) self.box = create_container_widget(gtk.Table, cols=2) self.conjure_controls() self.box.show() self.vbox.pack_start(self.box) self.button_find.grab_default() self.restart_iter_fields() return def conjure_controls(self, button_width=125): stretch = Stretch().set_hor_align(1.0).set_expand(False) self.box.set_pack(gtk.PACK_END) self.target = self.box.stuff(Entry()) self.target.set_activates_default(True) self.target.set_unicode(LATEST_TARGET) self.target_value = unscape(self.target.get_unicode()) label = self.box.stuff(LabelWithToolTip(), stretch=stretch) label.set_unicode('_Target') label.add_help('Target is a string of characters to search for.') label.set_mnemonic_widget(self.target) self.box.bump_row() row = self.box.stuff(create_container_widget(), stretch=Stretch().set_padding(ZERO).set_ver_expand(False)) row.set_pack(gtk.PACK_END) self.button_find = row.stuff(Button().add_stock_label(stock_id= gtk.STOCK_FIND, label_text='Go _Next').add_help(''' Find the next row with a field that contains the Target.'''), stretch=stretch) self.button_find.set_size_request(width=button_width, height=-1) self.button_find.connect('clicked', self.call_back_clicked_find_next) self.button_find.set_flags(gtk.CAN_DEFAULT) self.search_backward = row.stuff(gtk.CheckButton('Search _Backward'), stretch=stretch) set_tip(self.search_backward, u''' Seek in the reverse direction — up instead of down.''') self.search_backward.connect('clicked', self.call_back_clicked_search_backward) self.box.bump_row() self.old_field_value = self.box.stuff(LabelHot(), span_cols=2) self.old_field_value.set_sensitive(False) self.box.bump_row() row = self.box.stuff(create_container_widget(), stretch=Stretch().set_padding(ZERO).set_ver_expand(False)) row.set_pack(gtk.PACK_END) button = row.stuff(Button().add_stock_label(stock_id=gtk.STOCK_APPLY, label_text='Mark & _Go').add_help(''' Set the Mark on the current row and then search for the next matching row.'''), stretch=stretch) button.set_size_request(width=button_width, height=-1) button.connect('clicked', self.call_back_clicked_mark_find) button = row.stuff(Button().add_label(label_text='Mark _All').add_help(''' Find all matching rows and set their Marks.'''), stretch=stretch) button.set_size_request(width=button_width, height=-1) button.connect('clicked', self.call_back_clicked_mark_all) button = row.stuff(Button().add_label(label_text='Toggle _Mark').add_help(''' Flip the Mark on the current row.'''), stretch=stretch) button.set_size_request(width=button_width, height=-1) button.connect('clicked', self.call_back_clicked_mark_rec) button = row.stuff(Button().add_label(label_text='S_kip Field').add_help(''' Ignore this column in all rows.'''), stretch=stretch) button.set_size_request(width=button_width, height=-1) button.connect('clicked', self.call_back_clicked_skip_field) self.box.bump_row() self.replacement = self.box.stuff(Entry()) self.replacement.set_activates_default(True) self.replacement.set_unicode(LATEST_REPLACEMENT) label = self.box.stuff(LabelWithToolTip(), stretch=stretch) label.set_unicode('_Replacement') label.add_help(''' Replacement is a string of characters to substitute for Target.''') label.set_mnemonic_widget(self.replacement) self.box.bump_row() self.new_field_value = self.box.stuff(LabelHot(), span_cols=2) self.new_field_value.set_sensitive(False) self.box.bump_row() row = self.box.stuff(create_container_widget(), stretch=Stretch().set_padding(ZERO).set_ver_expand(False)) row.set_pack(gtk.PACK_END) button = row.stuff(Button().add_stock_label(stock_id=gtk.STOCK_FIND_AND_REPLACE, label_text='Update & G_o').add_help(''' Replace the current field in the current row and then search for the next matching field.'''), stretch=stretch) button.set_size_request(width=button_width, height=-1) button.connect('clicked', self.call_back_clicked_update_find) button = row.stuff(Button().add_label(label_text='Update A_ll').add_help(''' Find all matching fields in all rowss and replace them.'''), stretch=stretch) button.set_size_request(width=button_width, height=-1) button.connect('clicked', self.call_back_clicked_update_all) button = row.stuff(Button().add_label(label_text='U_pdate').add_help(''' Replace the curent field in the current row.'''), stretch=stretch) button.set_size_request(width=button_width, height=-1) button.connect('clicked', self.call_back_clicked_update_rec) self.box.bump_row() self.handler_id_realize = self.connect_after('realize', call_back_after_realize_to_set_text_view_base_color, [self.old_field_value, self.new_field_value]) return self def get_row_ndx(self): row_ndx = self.matrix.get_row_ndx() if row_ndx is None: row_ndx = ZERO return row_ndx def has_target_changed(self): global LATEST_TARGET, LATEST_REPLACEMENT new_target_value = unscape(self.target.get_unicode()) result = new_target_value != self.target_value LATEST_TARGET = self.target_value = new_target_value LATEST_REPLACEMENT = self.replacement_value = self.replacement.get_unicode() return result def is_target_case_insensitive(self): return self.target_value.islower() def is_found(self, fld): if self.is_target_case_insensitive(): return fld.get_as_edited().lower().find(self.target_value) != \ NA else: return fld.get_as_edited().find(self.target_value) != NA def get_new_as_edited_value(self, fld): def find_positions(text, target): if self.is_target_case_insensitive(): str = text.lower() else: str = text result = [] length = len(target) tail = ZERO head = str.find(target, tail) while head not in [NA]: tail = head + length for (case_test, case_method) in [(unicode.istitle, unicode.title), (unicode.islower, unicode.lower), (unicode.isupper, unicode.upper)]: if case_test(text[head:tail]): break else: case_method = None result.append([head, case_method]) head = str.find(target, tail) return result def split(text, target): length = len(target) result = [] left = ZERO for (right, case_method) in find_positions(text, target): result.append([text[left:right], case_method]) left = right + length result.append([text[left:], None]) return result is_replacement_case_sensitive = not self.replacement_value.islower() list = split(unicode(fld.get_as_edited()), self.target_value) result = NULL for (str, case_method) in list[:-1]: result += str if case_method is None or is_replacement_case_sensitive: result += self.replacement_value else: result += case_method(self.replacement_value) for (str, case_method) in list[-1:]: result += str return result def restart_iter_fields(self): self.iter_fields = self.get_next_field() self.old_field_value.clear() self.new_field_value.clear() return self def get_next_field(self): self.iter_tags = Iterator(list=self.tags, start_ndx=self.tag_ndx, reversed=self.search_backward.get_active()).__iter__() for (self.tag_ndx, tag) in self.iter_tags: self.col = self.column_list.find(tag) self.iter_rows = Iterator(list=self.relation, start_ndx=self.get_row_ndx(), reversed=self.search_backward.get_active()).__iter__() for (row_ndx, row) in self.iter_rows: self.relation.stage_col(row, self.col) yield (row_ndx, self.col) return def bump_iter_tags(self): on1 = True while on1: try: (self.tag_ndx, tag) = self.iter_tags.next() on1 = False except StopIteration: self.iter_tags = Iterator(list=self.tags, start_ndx=self.tag_ndx, reversed=self.search_backward.get_active()).__iter__() self.col = self.column_list.find(tag) self.iter_rows = Iterator(list=self.relation, start_ndx=self.get_row_ndx(), reversed=self.search_backward.get_active()).__iter__() return self def find_field(self): for (self.row_ndx, self.field) in self.iter_fields: if self.is_found(self.field): break else: self.row_ndx = NA self.field = None return self def call_back_clicked_find_next(self, *calling_sequence): if self.has_target_changed(): self.restart_iter_fields() if self.target_value in [NULL]: pass else: self.find_field() if self.row_ndx == NA: dlg = DlgAssertion(parent=MAIN, msg= ''' Searching %s Target "%s" not found. ''' % (self.relation.name, self.target_value)) dlg.run() dlg.destroy() self.restart_iter_fields() else: self.old_field_value.clear() self.old_field_value.insert_markup('%s Row %i: %s%s%s%s%s/%s%s' % ( self.relation.name, self.row_ndx + 1, U_LEFT_CHEVRON, self.field.name, U_RIGHT_CHEVRON, self.field.get_as_displayed(), U_LEFT_CHEVRON, self.field.name, U_RIGHT_CHEVRON, )) self.old_field_value.highlight_cartouches() self.new_field_value.clear() if False: # if self.fReplacementValue in [NULL]: pass else: try: self.field.set_as_edited(self.get_new_as_edited_value(self.field)) except Error: pass self.new_field_value.insert_markup('%s Row %i: %s%s%s%s%s/%s%s' % ( self.relation.name, self.row_ndx + 1, U_LEFT_CHEVRON, self.field.name, U_RIGHT_CHEVRON, self.field.get_as_displayed(), U_LEFT_CHEVRON, self.field.name, U_RIGHT_CHEVRON, )) self.new_field_value.highlight_cartouches() self.matrix.scroll(self.row_ndx) return False def call_back_clicked_search_backward(self, *calling_sequence): self.has_target_changed() self.restart_iter_fields() return False def call_back_clicked_mark_rec(self, *calling_sequence): self.matrix.toggle_mark() return False def call_back_clicked_mark_all(self, *calling_sequence): self.has_target_changed() self.restart_iter_fields() if self.target_value in [NULL]: pass else: count = ZERO self.find_field() while self.row_ndx not in [NA]: iter = self.matrix.get_iter(self.row_ndx) if self.matrix.get_mark(iter): pass else: count += 1 self.matrix.toggle_mark(iter, value=True) self.find_field() dlg = DlgAssertion(parent=MAIN, msg= ''' Mark All %i marks set. ''' % count) dlg.run() dlg.destroy() self.restart_iter_fields() return False def call_back_clicked_mark_find(self, *calling_sequence): self.matrix.toggle_mark(value=True) self.call_back_clicked_find_next(*calling_sequence) return False def call_back_clicked_skip_field(self, *calling_sequence): self.bump_iter_tags() self.call_back_clicked_find_next(*calling_sequence) return False def call_back_clicked_update_rec(self, *calling_sequence): if None in [self.row_ndx, self.field]: pass else: self.relation.destage_col((self.relation)[self.row_ndx], self.field) self.matrix.refresh_row(self.row_ndx) self.relation.set_dirty() return False def call_back_clicked_update_all(self, *calling_sequence): self.has_target_changed() self.restart_iter_fields() if NULL in [self.target_value]: pass else: count = ZERO self.find_field() while self.row_ndx not in [NA]: try: self.field.set_as_edited(self.get_new_as_edited_value(self.field)) self.call_back_clicked_update_rec(*calling_sequence) count += 1 except Error: pass self.find_field() dlg = DlgAssertion(parent=MAIN, msg= ''' Update All %i fields replaced. ''' % count) dlg.run() dlg.destroy() self.restart_iter_fields() return False def call_back_clicked_update_find(self, *calling_sequence): self.call_back_clicked_update_rec(*calling_sequence) self.call_back_clicked_find_next(*calling_sequence) return False class DlgDateSelection(gtk.Dialog): def __init__(self, parent, year, month, day=None, title='Calendar', relation=None): self.year = year self.month = month self.day = day self.relation = relation button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) gtk.Dialog.__init__(self, parent=parent, title=title, flags=gtk.DIALOG_MODAL, buttons=button_list) self.set_has_separator(False) self.stretch = Stretch().set_padding(ZERO) self.box = create_container_widget(gtk.Table) self.conjure_controls() self.box.show() self.vbox.pack_start(self.box) self.focus = None self.connect_after('realize', self.call_back_after_realize_set_focus, self.focus) return def conjure_spinner(self, label_text, call_back, col, span=1): self.box.at_col(col) button = Button().add_stock(gtk.STOCK_GO_BACK) button.set_relief(gtk.RELIEF_NONE) button.connect('clicked', call_back, -1) self.box.stuff(button, stretch=Stretch().set_hor_align(1.0)) label = Label() label.set_unicode(label_text) self.box.stuff(label, span_cols=span) self.box.bump_col(span - 1) button = Button().add_stock(gtk.STOCK_GO_FORWARD) button.set_relief(gtk.RELIEF_NONE) button.connect('clicked', call_back, +1) self.box.stuff(button, stretch=Stretch().set_hor_align(ZERO)) return label def conjure_cell(self, date, is_current_month=True): def get_fg(is_sunday, is_current_month, fg): if is_current_month: if is_sunday: return MAIN_COLORS.get_hex('red') else: return MAIN_COLORS.get_hex(fg, default_name='blue') else: return MAIN_COLORS.get_hex('gray') event_box = Button() event_box.show() event_box.connect('clicked', self.call_back_cell_selected, date) table = create_container_widget(gtk.VBox) label = LabelWrapMarkup() label.set_size_request(width=90, height=50) event_list = self.events.get(date.strftime('%Y%m%d'), [None]) event_lo = event_list[ZERO] event_hi = event_list[-1] if event_lo is None: fg = None else: fg = event_lo.fg fg = get_fg(date.weekday() == 6, is_current_month, fg) if event_hi is None: bg = None else: bg = MAIN_COLORS.get_color(event_hi.bg) text = \ '%2i' % \ ('serif', 'xx-large', 'bold', fg, date.day) for event in self.events.get(date.strftime('%Y%m%d'), []): foreground = MAIN_COLORS.get_hex(event.fg, default_name= 'black') text += '\n%s' % ('x-small', foreground, event.title) # print text label.set_unicode(text) if bg is None: pass else: event_box.modify_bg(gtk.STATE_NORMAL, bg) table.stuff(label) table.show() event_box.add_widget(table) self.box.stuff(event_box, stretch=self.stretch) return event_box def get_calendar(self, year, month): def extrapolate(year, month, row, inc): result = [] for day in row: is_current_month = day != ZERO if is_current_month: date = datetime.date(year=year, month=month, day=day) else: date = None result.append([date, is_current_month]) for (ndx, (date, is_current_month)) in enumerate(result): if date is None: date = result[ndx - 1][ZERO] + inc result[ndx] = [date, is_current_month] return result cal = calendar.monthcalendar(year, month) weeks = [] for week in cal[:1]: week.reverse() day_tuples = extrapolate(year, month, week, datetime.timedelta(days= -1)) day_tuples.reverse() weeks.append(day_tuples) for week in cal[1:]: day_tuples = extrapolate(year, month, week, datetime.timedelta(days= +1)) weeks.append(day_tuples) return weeks def dummy_row(self): self.box.stuff(Label()) # Dummy spacing. self.box.bump_row() return self def conjure_controls(self): self.dummy_row() self.label_month = self.conjure_spinner(label_text=MONTH_NAMES[self.month - 1][ZERO], call_back=self.call_back_spin_month, col=ZERO) self.label_year = self.conjure_spinner(label_text=str(self.year), call_back=self.call_back_spin_year, col=3) now_button = Button().add_label('Today').add_help("Go back to today's date.") now_button.connect('clicked', self.call_back_spin_today) self.box.stuff(now_button) self.box.bump_row() self.dummy_row() for (day_abbrev, day_name) in DAY_NAMES: label = Label() label.set_unicode('%s' % (MAIN_COLORS.get_hex('white'), day_abbrev)) label.show() box = gtk.EventBox() box.modify_bg(gtk.STATE_NORMAL, MAIN_COLORS.get_color('gray')) box.add(label) self.box.stuff(box) try: date = datetime.date(year=self.year, month=self.month, day= self.day) except (ValueError, TypeError): # 2007 Nov 08 date = None self.focus = self.get_focus() cal = self.get_calendar(self.year, self.month) date_lo = cal[ZERO][ZERO][ZERO] date_hi = cal[-1][-1][ZERO] if hasattr(self.relation, 'get_occurrences'): self.events = self.relation.get_occurrences(date_lo, date_hi) else: self.events = {} # for (date, events) in self.events.iteritems(): # events = [event.title for event in events] # print date, events for row in cal: self.box.bump_row() for (col, is_current_month) in row: button = self.conjure_cell(col, is_current_month= is_current_month) if col == date: self.focus = button self.call_back_after_realize_set_focus() return self def call_back_cell_selected(self, widget, date, *calling_sequence): self.date = date self.response(gtk.RESPONSE_OK) return False def call_back_spin_month(self, button, inc, *calling_sequence): self.month += inc if self.month < 1: self.year -= 1 self.month = 12 elif self.month > 12: self.year += 1 self.month = 1 self.box.clear() self.conjure_controls() return False def call_back_spin_year(self, button, inc, *calling_sequence): self.year += inc self.box.clear() self.conjure_controls() return False def call_back_spin_today(self, button, *calling_sequence): today = datetime.date.today() self.year = today.year self.month = today.month self.day = today.day self.box.clear() self.conjure_controls() return False def call_back_after_realize_set_focus(self, *calling_sequence): self.set_focus(self.focus) return False class DlgTimeSelection(gtk.Dialog): def __init__(self, parent, title='Time Selection'): button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) gtk.Dialog.__init__(self, parent=parent, title=title, flags=gtk.DIALOG_MODAL, buttons=button_list) self.set_has_separator(False) self.box = create_container_widget(gtk.Table) self.conjure_controls() self.box.show() self.vbox.pack_start(self.box) return def conjure_controls(self): def make_button(time): label = time.strftime(TIME_DISPLAY_FORMAT) button = Button().add_label(label) button.connect('clicked', self.call_back_button_clicked, time) if 6 <= time.hour < 18: button.modify_bg(gtk.STATE_NORMAL, MAIN_COLORS.get_color('white')) return button now = self.box.stuff(Label(), stretch=Stretch().set_hor_align(1.0)) now.set_unicode('Now') self.box.stuff(make_button(datetime.datetime.now().time())) self.box.bump_row() for hour in [ ZERO, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ]: for minutes in [ZERO, 15, 30, 45]: self.box.stuff(make_button(datetime.time(hour, minutes))) for minutes in [ZERO, 15, 30, 45]: self.box.stuff(make_button(datetime.time(hour + 12, minutes))) self.box.bump_row() return self def call_back_button_clicked(self, button, label, *calling_sequence): self.time = label self.response(gtk.RESPONSE_OK) return False class DlgColorSelection(gtk.Dialog): def __init__(self, parent, title='Color Selection'): gtk.Dialog.__init__(self, parent=parent, title=title, flags=gtk.DIALOG_MODAL) self.box = create_container_widget(gtk.Table) self.conjure_buttons() self.conjure_controls() self.box.show() self.vbox.pack_start(self.box) return def conjure_buttons(self): self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) return self def conjure_controls(self): label = self.box.stuff(Label()) label.set_unicode('Color Name') code_set = CodeSet() for ((red, green, blue), name) in COLOR_NAMES: code_set.append(CodeColor(name)) self.combo_box = self.box.stuff(code_set.conjure_combo_box()) return self class DlgNewFieldSelectType(gtk.Dialog): def __init__(self, parent, title='Select Field Type'): button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) gtk.Dialog.__init__(self, parent=parent, title=title, flags=gtk.DIALOG_MODAL, buttons=button_list) self.box = create_container_widget(gtk.Table) self.conjure_controls() self.box.show() self.vbox.pack_start(self.box) return def conjure_controls(self): label = self.box.stuff(LabelWithToolTip(), stretch=Stretch().set_align(1.0)) label.set_unicode('Type') label.add_help(''' The kind of data the new field is to contain.''') self.field_type = self.box.stuff(FIELD_CLASS_REGISTRY.conjure_combo_box()) self.field_type.set_unicode(FIELD_CLASS_NAME[FieldStringNoMarkup]) return self class DlgFieldsAddRemove(gtk.Dialog): def __init__(self, parent, relation, title='Add and Remove Fields'): self.parent_ = parent self.relation = relation self.column_list = self.relation.column_list button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) gtk.Dialog.__init__(self, parent=self.parent_, title=title, flags=gtk.DIALOG_MODAL, buttons=button_list) self.box = create_container_widget(gtk.Table) self.conjure_controls() self.box.show() self.vbox.pack_start(self.box) return def conjure_controls(self, list_width=200, list_height=350): self.list = self.box.stuff(self.column_list.get_list_widget(), scrolled_vertically=True) self.list.set_size_request(width=list_width, height=list_height) set_tip(self.list, ''' You may change the order of fields by dragging them around.''') self.list.connect('row-activated', self.call_back_button_update) stretch = Stretch().set_align(ZERO) box = self.box.stuff(create_container_widget(gtk.VBox), stretch= stretch) self.button_add = box.stuff(Button()) self.button_add.add_stock_label(stock_id=gtk.STOCK_ADD, label_text='_Add...') self.button_add.add_help('Insert a new field.') self.button_add.connect('clicked', self.call_back_button_add) self.button_update = box.stuff(Button()) self.button_update.add_stock_label(stock_id=gtk.STOCK_JUMP_TO, label_text='_Update...') self.button_update.add_help('Update selected field.') self.button_update.connect('clicked', self.call_back_button_update) self.button_remove = box.stuff(Button()) self.button_remove.add_stock_label(stock_id=gtk.STOCK_REMOVE, label_text='_Remove') self.button_remove.add_help('Remove selected field or separator.') self.button_remove.connect('clicked', self.call_back_button_remove) self.button_break = box.stuff(Button()) self.button_break.add_stock_label(stock_id=gtk.STOCK_NEW, label_text='_Separate') self.button_break.add_help('Insert a break between groups of fields.') self.button_break.connect('clicked', self.call_back_button_break) return self def update_field(self, field): return field.run_update_dlg(self.parent_) def call_back_button_add(self, *calling_sequence): type_dlg = DlgNewFieldSelectType(parent=self.parent_, title= 'Select Field Type') done = False while not done: response = type_dlg.run() if response in [gtk.RESPONSE_OK]: new_field_type = FIELD_CLASS[type_dlg.field_type.get_unicode()] new_field = new_field_type() if self.update_field(new_field) in [gtk.RESPONSE_OK]: row_ndx = self.list.get_row_ndx() if row_ndx is None: row_ndx = ZERO self.list.insert(row_ndx, ListItem(object=new_field, markup=new_field.get_name())) done = True elif response in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_NONE, gtk.RESPONSE_DELETE_EVENT]: done = True type_dlg.destroy() return False def call_back_button_update(self, *calling_sequence): row_ndx = self.list.get_row_ndx() field = self.list.get_object(row_ndx) if field is None: pass elif field.is_mod_lock(): pass else: self.update_field(field) self.list.scroll(row_ndx) return False def call_back_button_remove(self, *calling_sequence): row_ndx = self.list.get_row_ndx() field = self.list.get_object(row_ndx) if field is None: self.list.remove(row_ndx) elif field.is_mod_lock(): pass else: self.list.remove(row_ndx) return False def call_back_button_break(self, *calling_sequence): row_ndx = self.list.get_row_ndx() if row_ndx is None: row_ndx = ZERO self.list.insert(row_ndx, ListSep()) return False class DlgDisplayColumnsChoose(gtk.Dialog): def __init__(self, parent, relation, title='Choose Display Columns'): self.relation = relation self.column_list = self.relation.column_list button_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) gtk.Dialog.__init__(self, parent=parent, title=title, flags=gtk.DIALOG_MODAL, buttons=button_list) self.box = create_container_widget(gtk.Table) self.conjure_controls() self.box.show() self.vbox.pack_start(self.box) return def conjure_controls(self, list_width=200, list_height=350): help = \ ''' You may change the order of fields by dragging them around.''' stretch = Stretch().set_align(ZERO) display_tags = (self.relation.display_tags)[:] available_tags = self.relation.column_list.get_tags() for tag in display_tags: ndx = available_tags.index(tag) if ndx == NA: pass else: available_tags.pop(ndx) self.box.stuff(Label().set_unicode('Display Columns')) self.box.stuff(Label()) # Dummy. self.box.stuff(Label().set_unicode('Available Fields')) self.box.bump_row() self.display_columns = self.box.stuff(List(), scrolled_vertically=True) self.display_columns.set_list([ListItem(markup=tag) for tag in display_tags]) self.display_columns.set_size_request(width=list_width, height= list_height) set_tip(self.display_columns, help) self.display_columns.connect('row-activated', self.call_back_button_clicked_remove) box = self.box.stuff(create_container_widget(gtk.VBox), stretch= stretch) self.button_select = box.stuff(Button()) self.button_select.add_stock_label(stock_id=gtk.STOCK_GO_BACK, label_text='_Select') self.button_select.add_help('Choose a Display Column from Available Fields.') self.button_select.connect('clicked', self.call_back_button_clicked_select) self.button_remove = box.stuff(Button()) self.button_remove.add_stock_label(stock_id=gtk.STOCK_GO_FORWARD, label_text='_Remove') self.button_remove.add_help('Delete a Display Column.') self.button_remove.connect('clicked', self.call_back_button_clicked_remove) self.available_fields = self.box.stuff(List(), scrolled_vertically=True) self.available_fields.set_list([ListItem(markup=tag) for tag in available_tags]) self.available_fields.set_size_request(width=list_width, height= list_height) set_tip(self.available_fields, help) self.available_fields.connect('row-activated', self.call_back_button_clicked_select) return self def call_back_button_clicked_select(self, *calling_sequence): dest_row_ndx = self.display_columns.get_row_ndx() from_row_ndx = self.available_fields.get_row_ndx() if dest_row_ndx is None: dest_row_ndx = ZERO if from_row_ndx is None: pass else: self.display_columns.insert(dest_row_ndx, self.available_fields.remove(from_row_ndx)) return False def call_back_button_clicked_remove(self, *calling_sequence): dest_row_ndx = self.available_fields.get_row_ndx() from_row_ndx = self.display_columns.get_row_ndx() if dest_row_ndx is None: dest_row_ndx = ZERO if from_row_ndx is None: pass else: self.available_fields.insert(dest_row_ndx, self.display_columns.remove(from_row_ndx)) return False class DlgOpenRelation(gtk.FileSelection): def __init__(self, title='Open', class_of=None, file_name=USER_FILE_DEFAULTS['file_name'], name=USER_FILE_DEFAULTS['name'], tag=USER_FILE_DEFAULTS['tag']): def set_tool_tip(widget, help): set_tip(widget, help) return gtk.FileSelection.__init__(self, title=title) set_tool_tip(self.dir_list, ''' Select a path.''') set_tool_tip(self.file_list, ''' Select a .csv file and click OK, or simply double click the file name.''') set_tool_tip(self.selection_entry, ''' Type a partial name or a wildcard and press Tab.''') set_tool_tip(self.history_pulldown, ''' This is the current path. Click to pull down a list of paths you have already traversed.''') self.hide_fileop_buttons() self.set_select_multiple(False) self.set_filename(os.path.expanduser(file_name)) self.box = create_container_widget(gtk.Table) self.conjure_controls(name=name, tag=tag, class_of=class_of) self.box.show() self.vbox.pack_start(self.box) self.vbox.reorder_child(self.box, ZERO) self.show_all() self.complete('.Tonto.*.csv') # Yes, this method needs to be called late. return def conjure_controls(self, name, tag, class_of): label = self.box.stuff(LabelWithToolTip(), stretch=Stretch().set_align(1.0)) label.set_unicode('Relation _Name') label.add_help(''' A Long Title for This Data''') self.relation_name = self.box.stuff(Entry()) label.set_mnemonic_widget(self.relation_name) self.relation_name.set_unicode(name) self.box.bump_row() label = self.box.stuff(LabelWithToolTip(), stretch=Stretch().set_align(1.0)) label.set_unicode('_Tag') label.add_help(''' A Short Title for This Data''') self.relation_tag = self.box.stuff(Entry()) label.set_mnemonic_widget(self.relation_tag) self.relation_tag.set_unicode(tag) self.box.bump_row() label = self.box.stuff(LabelWithToolTip(), stretch=Stretch().set_align(1.0)) label.set_unicode('Type') label.add_help(''' The Kind of Data''') self.relation_type = self.box.stuff(RELATION_CLASS_REGISTRY.conjure_combo_box()) if class_of is None: class_of = RelationUserDefined self.relation_type.set_unicode(RELATION_CLASS_NAME[class_of]) return self class DlgFieldEntry(gtk.Dialog): def __init__(self, parent, page_ndx, page_count, width, height, title=None): gtk.Dialog.__init__(self, parent=parent, title=title, flags=gtk.DIALOG_MODAL) self.page_ndx = page_ndx self.page_count = page_count self.conjure_buttons() self.conjure_controls(width, height) return def conjure_controls(self, width, height): self.box = create_container_widget(gtk.Table, cols=3) self.box.show() self.ScrolledWindow = gtk.ScrolledWindow() self.ScrolledWindow.add_with_viewport(self.box) self.ScrolledWindow.set_size_request(width=width, height=height) self.ScrolledWindow.show() self.ScrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) # Avoid row shrink probs. self.box.connect('size-allocate', self.call_back_resize) self.vbox.pack_start(self.ScrolledWindow) return self def conjure_buttons(self): page_number = Label() page_number.set_unicode('Page %i of %i' % (self.page_ndx + 1, self.page_count)) page_number.show() self.action_area.pack_start(page_number) self.add_buttons(*self.conjure_buttons_prev_next()) buttons = self.action_area.get_children() for button in buttons: button.set_sensitive(self.sensitivity.get(button.get_label(), True)) return self def conjure_buttons_prev_next(self): prev_label = gtk.STOCK_GO_BACK # '<< Previous' next_label = gtk.STOCK_GO_FORWARD # 'Next >>' self.sensitivity = {} if self.page_count == 1: button_response_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) elif self.page_ndx == ZERO: button_response_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, prev_label, RESPONSE_PREV, next_label, RESPONSE_NEXT, gtk.STOCK_OK, gtk.RESPONSE_OK) elif self.page_ndx == self.page_count - 1: button_response_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, prev_label, RESPONSE_PREV, next_label, RESPONSE_NEXT, gtk.STOCK_OK, gtk.RESPONSE_OK) else: button_response_list = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, prev_label, RESPONSE_PREV, next_label, RESPONSE_NEXT, gtk.STOCK_OK, gtk.RESPONSE_OK) return button_response_list def call_back_resize(self, *calling_sequence): vscroll_adjustment = self.ScrolledWindow.get_vadjustment() for child in self.box.get_children(): if child.is_focus() and isinstance(child, Text): rect_child = child.get_allocation() iter = child.get_insert_iter() rect_cursor = child.get_buffer_location(iter) y = rect_child.y + rect_cursor.y vscroll_adjustment.clamp_page(y, y + 30) return False class DlgFreeFormEntry(DlgHot): def __init__( self, parent=None, relation=None, row_ndx=None, title='Freeform Entry', markup=None, text_width=450, tag_width=200, text_height=300, ): self.tag_width = tag_width self.caps_repertoire = {ZERO: ('Title C_aps', 'Capitalize every word.', 1), 1: ('Upperc_ase', 'All caps.', 2), 2: ('Lowerc_ase', 'No caps.', ZERO)} DlgHot.__init__(self, parent=parent, relation=relation, row_ndx= row_ndx, title=title, markup=markup, text_width= text_width, text_height=text_height) self.conjure_buttons() return def conjure_buttons(self, description): self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) (caps_button_label, caps_button_tool_tip, self.caps_ndx) = (self.caps_repertoire)[ZERO] self.button_title_caps = Button().add_stock_label(gtk.STOCK_BOLD, caps_button_label) self.button_title_caps.add_help(caps_button_tool_tip) self.button_title_caps.connect('clicked', self.call_back_button_clicked_title_caps) self.button_title_caps.show() self.action_area.pack_end(self.button_title_caps) self.button_parse = Button().add_stock_label(gtk.STOCK_CONVERT, '_Parse').add_help(description) self.button_parse.connect('clicked', self.call_back_button_clicked_parse) self.button_parse.show() self.action_area.pack_end(self.button_parse) self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) self.button_ok = self.action_area.get_children()[ZERO] return self def conjure_controls(self, markup, width=-1, height=-1): self.scratchpad = self.box.stuff(LabelHot(markup, width=width, height=height, write_enabled=True, relation=self.relation), scrolled=True) self.scratchpad.buffer.connect('changed', self.call_back_dim_title_caps) self.tag_list = self.box.stuff(LabelHot(width=self.tag_width, write_enabled=True, relation=self.relation), scrolled= True) self.tag_list.set_wrap_mode(gtk.WRAP_NONE) self.tags = self.relation.column_list.get_tags(include_insensitive= False) self.stage_tag_list(self.tags) self.handler_id_realize = self.connect_after('realize', call_back_after_realize_to_set_text_view_base_color, [self.tag_list]) return self def get_cartouche_open(self, tag_name): return '%s%s%s' % (U_LEFT_CHEVRON, tag_name, U_RIGHT_CHEVRON) def get_cartouche_close(self, tag_name): return '%s/%s%s' % (U_LEFT_CHEVRON, tag_name, U_RIGHT_CHEVRON) def insert_cartouche_open(self, label_hot, tag_name): label_hot.insert_unicode(self.get_cartouche_open(tag_name)) return self def insert_cartouche_close(self, label_hot, tag_name): label_hot.insert_unicode(self.get_cartouche_close(tag_name)) return self def stage_tag_list(self, tags): for tag in tags: self.insert_cartouche_open(self.tag_list, tag) self.insert_cartouche_close(self.tag_list, tag) self.tag_list.insert_unicode('\n') self.tag_list.highlight_cartouches() return self def destage_tag_list(self): def destage_fld(fld): match = W_FLD.match(fld) if match is None: pass else: tag_name = match.group(1) if tag_name == match.group(3): tag = self.relation.column_list.find(tag_name) if tag is None: pass else: tag.set_as_edited(match.group(2)) return scratchpad = self.scratchpad.get_unicode() + self.tag_list.get_unicode() # Both! residue = WO_FLD.split(scratchpad) residue.pop() # Take last first. while len(residue) > ZERO: destage_fld(residue.pop()) residue.pop() return self def parse(self): raise Error('Abstract') def call_back_button_clicked_parse(self, *calling_sequence): self.parse() return False def call_back_button_clicked_title_caps(self, *calling_sequence): scratchpad = self.scratchpad.get_unicode() self.scratchpad.clear() if self.caps_ndx in [1]: self.scratchpad.insert_unicode(scratchpad.title()) elif self.caps_ndx in [2]: self.scratchpad.insert_unicode(scratchpad.upper()) elif self.caps_ndx in [ZERO]: self.scratchpad.insert_unicode(scratchpad.lower()) (caps_button_label, caps_button_tool_tip, self.caps_ndx) = (self.caps_repertoire)[self.caps_ndx] self.button_title_caps.add_stock_label(gtk.STOCK_BOLD, caps_button_label) self.button_title_caps.add_help(caps_button_tool_tip) return False def call_back_dim_title_caps(self, *calling_sequence): scratchpad = self.scratchpad.get_unicode() self.button_title_caps.set_sensitive(WO_FLD.search(scratchpad) is None) return False WO_OPEN_CARTOUCHE = U_LEFT_CHEVRON + '.*?' + U_RIGHT_CHEVRON WO_CLOSE_CARTOUCHE = U_LEFT_CHEVRON + '/.*?' + U_RIGHT_CHEVRON WO_FLD = re.compile('(' + WO_OPEN_CARTOUCHE + '.*?' + WO_CLOSE_CARTOUCHE + ')', re.DOTALL) W_OPEN_CARTOUCHE = U_LEFT_CHEVRON + '(.*?)' + U_RIGHT_CHEVRON W_CLOSE_CARTOUCHE = U_LEFT_CHEVRON + '/(.*?)' + U_RIGHT_CHEVRON W_FLD = re.compile(W_OPEN_CARTOUCHE + '(.*?)' + W_CLOSE_CARTOUCHE, re.DOTALL) REX_LEFT_MARGIN = '(?:^(?:[>:\\s]*)|(?:.*,\\s+))' REX_RIGHT_MARGIN = '(?:,|$)' PAT_GREETING = re.compile(re_alt('Dear') + '\\s(.*?)(?:' + REX_RIGHT_MARGIN + '|:)', re.MULTILINE) ALT_POLITE_MODE = re_alt('Mrs.?', 'Mr.? and Mrs.?', 'Mr.? & Mrs.?', 'Mr.?', 'Miss', 'Ms.?', 'Dr.?', 'Rev.?') ALT_TITLE = re_alt( 'Jr.?', 'Sr.?', 'M.?d.?', 'D.?d.?s.?', 'P.?h.?d.?', 'Secretary', 'Secy?.?', 'Treasurer', 'Treas.?', 'Director', 'Dir.?', 'Vice President', 'Vice Pres.?', 'V.?P.?', 'President', 'Pres.?', 'Superintendent', 'Supt.?', 'Assistant', 'Asst.?', 'Administrative', 'Administrator', 'Admin.?', 'Adm.?', 'Staff', 'Coordinator', 'Coord.?', 'Specialist', 'Associate', 'Manager', 'Mgr.?', 'Officer', 'CEO', 'CFO', 'Chairman', ) REX_TITLE = '(?:' + ALT_TITLE + SPACE + '+.*?)|(?:[^,]*?\\s+' + \ ALT_TITLE + ')|' + ALT_TITLE PAT_TITLE = re.compile(REX_LEFT_MARGIN + '(' + REX_TITLE + ')' + REX_RIGHT_MARGIN, re.MULTILINE) PAT_NAME = re.compile(REX_LEFT_MARGIN + '(' + ALT_POLITE_MODE + ')\\s+(.*?)' + SPACE + '+(\\S+)' + REX_RIGHT_MARGIN, re.MULTILINE) ALT_DEPT = re_alt('Department', 'Dept.?', 'd.?b.?a.?') REX_DEPT = '(?:' + ALT_DEPT + '\\s+.*?)|(?:.*\\s+' + ALT_DEPT + ')' ALT_ROOM = re_alt('M.?s.?', 'Room', 'Rm.?', 'Apartment', 'Apt.?', 'Building', 'Bldg.?') REX_ROOM = '(?:' + ALT_ROOM + '\\s*\\S+)' PAT_DEPARTMENT = re.compile(REX_LEFT_MARGIN + '(' + REX_DEPT + '|' + REX_ROOM + ')' + REX_RIGHT_MARGIN, re.MULTILINE) ALT_INC = re_alt('Incorporated', 'Inc.?', 'Limited', 'Ltd.?', 'Llc.?') ALT_CORP = re_alt('Corporation', 'Corp.?', 'Company', 'Co.', 'University', 'Univ.?', 'Organization', 'Org.?') PAT_COMPANY = re.compile(REX_LEFT_MARGIN + '(.*?(?:(?:,\\s+' + ALT_INC + ')|(?:' + ALT_CORP + '.*?)))' + REX_RIGHT_MARGIN, re.MULTILINE) ALT_BOX = re_alt('P.?\\s?O.?\\s?Box', 'Box') ALT_LOCUS = re_alt('Route', 'Rt.?', 'Suite', 'Ste.?', 'Building', 'Bldg.?') REX_STREET_NUMBER = '(?:[NEWS\\s]?\\d{1,5}[NEWS\\s]?\\d{,5}\\w{,2}\\s)|' + \ ALT_LOCUS PAT_STREET = re.compile(REX_LEFT_MARGIN + '((?:' + ALT_BOX + '|' + REX_STREET_NUMBER + ').*?)' + REX_RIGHT_MARGIN, re.MULTILINE) PAT_LOCUS = re.compile(REX_LEFT_MARGIN + '((?:' + REX_STREET_NUMBER + ').*?)' + REX_RIGHT_MARGIN, re.MULTILINE) REX_ZIP = '((?:\\d{5}(?:-\\d{4})?)|(?:\\w\\d\\w\\s?\\d\\w\\d))' PAT_CITY_STATE_ZIP = re.compile(REX_LEFT_MARGIN + '(.*?),\\s*(\\w{2})\\s+(' + REX_ZIP + ')', re.MULTILINE) PAT_COUNTRY = re.compile(REX_LEFT_MARGIN + '(' + re_alt('United States of America', 'United States', 'Usa', 'Canada') + ')' + REX_RIGHT_MARGIN, re.MULTILINE) PAT_LAT_LON = re.compile('([-+]?\\d{2,3}.\\d{5,6})') ALT_PHONE_TYPE = re_alt('home', 'work', 'cell', 'fax', 'pager') REX_PHONE_NUMBER = \ '((?:1[.(\\-\\s]?)?\\d{3}[.)\\-\\s]?\\s?\\d{3}[.\\-\\s]?\\d{4}' + \ '(?:(:?(?:\\s*[xX]\\s*)|(?:\\s*[eE]xt\\.?\\s*))\\d+)?)' PAT_PHONE = re.compile('(?:(' + ALT_PHONE_TYPE + ')(?:\\s+' + re_alt('Number', 'No.?') + ')?:?\\s+)?' + REX_PHONE_NUMBER) PAT_EMAIL = re.compile('((?:".*?"\\s*)?\\S+?@\\S+?\\.\\S+)') PAT_WEB = re.compile('(' + re_alt('http://', 'www.') + '\\S+?\\.\\S+)') class DlgAddressFreeFormEntry(DlgFreeFormEntry): def conjure_buttons(self): description = \ ''' First, type or paste contact info like a business card into the space above. Parse scans your info, identifying obvious components such as zip codes, phone numbers, and Web sites. It brackets these fields with tags from the list on the right. Not all tags will be assigned, and some pertinent information will not be correctly identified. Plese drag the tags into their correct positions before clicking OK.''' DlgFreeFormEntry.conjure_buttons(self, description=description) return self def stage_tag_list(self, tags): def get_as_edited(tag): return self.relation.column_list.find(tag).get_as_edited() def insert(tag): text = get_as_edited(tag) if tag in tags: self.insert_cartouche_open(self.tag_list, tag) # 2007 Oct 01 if text in BLANK: pass else: self.tag_list.insert_unicode(text) self.insert_cartouche_close(self.tag_list, tag) self.tag_list.insert_unicode('\n') # tags.remove(tag) # 2007 Oct 01 else: self.tag_list.insert_unicode(get_as_edited(tag)) self.tag_list.insert_unicode('\n') return tags = tags[:] # ... a copy. # insert('Listing_Type') # 2007 Oct 01 # insert('Greeting') # insert('Polite_Mode') # insert('First_Name') # insert('Last_Name') # insert('Title') # insert('Dept_Mail_Stop') # insert('Company') # insert('Locus') # insert('Street') # insert('City') # insert('State') # insert('Zip') # insert('Country') # insert('Latitude') # insert('Longitude') # for ndx in [1, 2, 3, 4]: # insert('Phone_Type_%i' % ndx) # insert('Phone_%i' % ndx) # insert('eMail') # insert('Web') # insert('Keywords') # insert('Remarks') # # self.unallocated = tags # 2007 Oct 01 # DlgFreeFormEntry.stage_tag_list(self, self.unallocated) for tag in tags: # 2007 Oct 01 insert(tag) return self def parse(self): def crack(tag_names, pattern, scratchpad): def match(tag_names, pattern, text): match = pattern.search(text) if match is None: return (False, text) else: ndx = len(tag_names) while ndx > ZERO: (start, end) = match.span(ndx) if NA in [start, end]: return (False, text) ndx -= 1 tag = tag_names[ndx] if tag in tags: tags.remove(tag) text = '%s%s%s%s\n%s' % (text[:start], self.get_cartouche_open(tag), text[start:end], self.get_cartouche_close(tag), text[end:]) return (True, text) residue = WO_FLD.split(scratchpad) (done, result) = match(tag_names, pattern, residue.pop(ZERO)) while not done: if len(residue) == ZERO: break result += residue.pop(ZERO) (done, text) = match(tag_names, pattern, residue.pop(ZERO)) result += text result += NULL.join(residue) return result def strip_white_space(pgraph): lines = pgraph.splitlines() return ('\n').join([line.strip() for line in lines]) tags = (self.tags)[:] # ... a copy. scratchpad = strip_white_space(self.scratchpad.get_unicode()) scratchpad = crack(['Greeting'], PAT_GREETING, scratchpad) scratchpad = crack(['Polite_Mode', 'First_Name', 'Last_Name'], PAT_NAME, scratchpad) scratchpad = crack(['Title'], PAT_TITLE, scratchpad) scratchpad = crack(['Dept_Mail_Stop'], PAT_DEPARTMENT, scratchpad) scratchpad = crack(['Company'], PAT_COMPANY, scratchpad) scratchpad = crack(['City', 'State', 'Zip'], PAT_CITY_STATE_ZIP, scratchpad) scratchpad = crack(['Street'], PAT_STREET, scratchpad) scratchpad = crack(['Locus'], PAT_LOCUS, scratchpad) scratchpad = crack(['Country'], PAT_COUNTRY, scratchpad) scratchpad = crack(['Latitude'], PAT_LAT_LON, scratchpad) scratchpad = crack(['Longitude'], PAT_LAT_LON, scratchpad) for fld_ndx in [1, 2, 3, 4]: phone_fld = 'Phone_%i' % fld_ndx type_fld = 'Phone_Type_%i' % fld_ndx scratchpad = crack([type_fld, phone_fld], PAT_PHONE, scratchpad) scratchpad = crack(['eMail'], PAT_EMAIL, scratchpad) scratchpad = crack(['Web'], PAT_WEB, scratchpad) self.scratchpad.clear() self.scratchpad.insert_unicode(scratchpad) self.tag_list.clear() self.stage_tag_list(tags) return self class DlgViewAsText(DlgAssertion): def __init__(self, parent=None, relation=None, row_ndx=None): msg = NULL for fld in relation.column_list.get_next_fld(): name = fld.get_name() value = fld.get_as_displayed() msg += '%s:\t%s\n' % (name, value) DlgAssertion.__init__(self, parent) tabs = pango.TabArray(1, positions_in_pixels=True) # False doesn't seem to set a tab. tabs.set_tab(ZERO, pango.TAB_LEFT, 200) self.text.set_tabs(tabs) self.text.set_wrap_mode(gtk.WRAP_NONE) self.text.insert_markup(msg) return class DlgViewAsTextHot(DlgHot): def __init__(self, parent=None, relation=None, row_ndx=None, title=None, text_width=450, text_height=300): DlgHot.__init__(self, parent=parent, relation=relation, row_ndx= row_ndx, title=title, text_width=text_width, text_height=text_height) self.text.set_wrap_mode(gtk.WRAP_WORD) self.modes = [('_Plain', ''' Interpret any embedded markup. '''), ('_Markup', ''' Show any embedded markup without interpreting it. '''), ('_Quoted', ''' Format for pasting into a USENET posting. Interpret embedded markup as asterisks. '''), ('_All Caps', ''' Format without punctuation. ''')] self.conjure_buttons() self.call_back_view_text_mode(None, '_Plain') return def conjure_push_button(self, mode, sibling): (label, tool_tip) = mode result = gtk.RadioButton(group=sibling, label=label.encode('UTF-8')) set_tip(result, tool_tip) result.connect('clicked', self.call_back_view_text_mode, label) if label in ['_Plain']: result.set_active(True) result.show() return result def conjure_buttons(self): self.button_print = Button().add_stock_label(gtk.STOCK_PRINT_PREVIEW, 'Previe_w').add_help(''' Open a browser instance to print from.''') self.button_print.connect('clicked', self.call_back_button_clicked_print) self.button_print.show() self.action_area.pack_start(self.button_print) sibling = None for mode in self.modes: button = self.conjure_push_button(mode, sibling) self.action_area.pack_start(button) sibling = button self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) return def view_text_mode(self, mode): raise Error('Abstract') def dump_tmp(self, htmlfile_name=TEMP_NAME): htmlfile_name = os.path.expanduser(htmlfile_name) html = codecs.open(htmlfile_name, 'wb', 'utf-8') html.write(''' %s %s ''' % (self.relation.get_name(), MAIN_COLORS.get_hex('light gray'), self.view_text_mode('_Plain'))) html.close() open_url(htmlfile_name) return self def call_back_view_text_mode(self, widget, mode, *calling_sequence): result = self.view_text_mode(mode) self.text.hide() self.text.clear() if mode in ['_Markup']: self.text.insert_unicode_with_tags(result) elif mode in ['_Quoted']: self.text.insert_markup('') self.text.insert_markup(quoted(result)) self.text.insert_markup('') elif mode in ['_All Caps']: self.text.insert_markup(result, all_caps=True) else: self.text.insert_markup(result) self.text.show() self.button_print.set_sensitive(mode in ['_Plain']) return False def call_back_button_clicked_print(self, *calling_sequence): self.dump_tmp() return False def is_not_available(self, tag): tag = self.relation.column_list.find(tag) return tag is None or tag.is_not_available() def get_as_displayed(self, mode, tag, **attribs): # 2007 Aug 14 if self.is_not_available(tag): return NULL tag = self.relation.column_list.find(tag) result = tag.get_as_displayed(**attribs) # 2007 Aug 14 if mode in ['_All Caps']: result = result.replace('.', NULL) # Postal regs deprecate non-essential punctuation. return result def get_as_edited(self, tag): if self.is_not_available(tag): return NOT_AVAILABLE tag = self.relation.column_list.find(tag) result = tag.get_as_edited() return result class DlgCalendarViewAsText(DlgViewAsTextHot): def view_text_mode(self, mode): def label(text): result = '%s: ' % text result = result.ljust(20) return result.replace(SPACE, ' ') mark_break = '
' mark_rule = '

' if mode in ['_Markup']: mark_break += '\n' mark_rule += '\n' if mode in ['_Quoted']: mark_break += '> ' mark_rule += '> ' card = Dstr(px='> ', dt=mark_rule) else: card = Dstr(dt=mark_rule) handle = self.get_as_displayed(mode, '_Handle') title = self.get_as_displayed(mode, 'Title') priority = self.get_as_displayed(mode, 'Priority') status = self.get_as_displayed(mode, '_Status') project = self.get_as_displayed(mode, 'Project') keywords = self.get_as_displayed(mode, 'Keywords') remark = self.get_as_displayed(mode, 'Remark').replace('\n', mark_break) accession_date = self.get_as_displayed(mode, '_Accession_Date') update_date = self.get_as_displayed(mode, '_Update_Date') reqs = [] for ndx in [1, 2, 3, 4]: reqs.append(self.get_as_displayed(mode, '_Req%i' % ndx)) start_date = self.get_as_displayed(mode, '_Start_Date') start_time = self.get_as_displayed(mode, 'Start_Time') finish_time = self.get_as_displayed(mode, 'Finish_Time') advance_alarm = self.get_as_displayed(mode, '_Advance_Alarm') earliest_start = self.get_as_displayed(mode, '_Earliest_Start') latest_finish = self.get_as_displayed(mode, '_Latest_Finish') days = self.get_as_displayed(mode, '_Days') float = self.get_as_displayed(mode, '_Float') offset = self.get_as_displayed(mode, '_Offset') offset_type = self.get_as_displayed(mode, '_Offset_Type') month_offset = self.get_as_displayed(mode, '_Offset_in_Month') month_offset_type = self.get_as_displayed(mode, '_Offset_in_Month_Type') last_occurrence_date = self.get_as_displayed(mode, '_Last_Occurrence_Date') foreground = self.get_as_displayed(mode, 'Foreground_Color') background = self.get_as_displayed(mode, 'Background_Color') fg = Dstr([foreground], px='color="', sx='"') bg = Dstr() px = '' % (fg.get_as_string(), bg.get_as_string()) sx = '' if mode in ['_Quoted']: px = NULL sx = NULL if title == handle: title = NULL line_title = Dstr([handle, title], px=px, dt=': ', sx=sx) line_priority = Dstr([priority], px=label('Priority')) line_status = Dstr([status], px=label('Status')) line_project = Dstr([project], px=label('Project')) line_keywords = Dstr([keywords], px=label('Keywords')) pgraph_abstract = Dstr(dt=mark_break, px='', sx='') pgraph_abstract.extend([line_title, line_priority, line_status, line_project, line_keywords]) if finish_time in BLANK: sched_from = Dstr([start_time], px='at ') sched_to = NULL else: sched_from = Dstr([start_time], px='from ') sched_to = Dstr([finish_time], px='to ') line_sched = Dstr([start_date, sched_from, sched_to], px=label('Sched')) line_alarm = Dstr([advance_alarm], px=label('Alarm')) line_reqs = Dstr(reqs, px=label('Prereqs'), dt='; ') line_esd = Dstr([earliest_start], px=label('After')) line_lfd = Dstr([latest_finish], px=label('Before')) line_days = Dstr([days], px=label('Duration')) line_float = Dstr([float], px=label('Float')) if offset == '1': resched_offset = Dstr([offset_type[:-1]], px='Every ') else: resched_offset = Dstr([offset, offset_type], px='Every ') resched_month = Dstr([month_offset_type, month_offset], px='in ', sx=' of the Month') resched_until = Dstr([last_occurrence_date], px='until ') line_resched = Dstr([resched_offset, resched_month, resched_until], px=label('Resched')) pgraph_sched = Dstr(dt=mark_break, px='', sx='') pgraph_sched.extend([line_sched, line_alarm, line_reqs, line_esd, line_lfd, line_days, line_float, line_resched]) created = Dstr([accession_date], px='Created: ', sx= '. ') updated = Dstr([update_date], px='Updated: ', sx='. ') pgraph_dates = Dstr([created, updated], px='', dt=SPACE * 2, sx='') card.extend([pgraph_abstract, remark, pgraph_sched, pgraph_dates]) result = card.get_as_string() return result def conjure_controls(self, markup, width=-1, height=-1): DlgViewAsTextHot.conjure_controls(self, markup, width=width, height=height) background = self.get_as_displayed(None, 'Background_Color') if background in BLANK: pass else: self.disconnect(self.handler_id_realize) self.text.modify_base(gtk.STATE_NORMAL, MAIN_COLORS.get_color(background)) return self class DlgBiblioViewAsText(DlgViewAsTextHot): def view_text_mode(self, mode): tm = {'sx': '.'} agency = Dstr([Dstr([self.get_as_displayed(mode, 'Government')], **tm), Dstr([self.get_as_displayed(mode, 'Agency_1')], **tm), Dstr([self.get_as_displayed(mode, 'Agency_2')], **tm), Dstr([self.get_as_displayed(mode, 'Agency_3')], **tm)]) last_name2 = self.get_as_displayed(mode, 'Last_Name_2') if last_name2 in ['et al', 'et al.']: author_names = Dstr([Dstr([self.get_as_displayed(mode, 'Last_Name_1'), self.get_as_displayed(mode, 'First_Name_1')], dt=', '), 'et al.'], dt=', ') else: author_names = Dstr([Dstr([self.get_as_displayed(mode, 'Last_Name_1'), self.get_as_displayed(mode, 'First_Name_1')], dt=', '), Dstr([self.get_as_displayed(mode, 'First_Name_2'), last_name2]), Dstr([self.get_as_displayed(mode, 'First_Name_3'), self.get_as_displayed(mode, 'Last_Name_3')])], dt=', ', cj=', and ') author_type = self.get_as_displayed(mode, 'Author_Type').lower() if author_type in ['by']: author_type = NULL authors = Dstr([author_names, author_type], dt=', ', **tm) article_title = self.get_as_displayed(mode, '_Title') if article_title == '""': article_title = NULL article = Dstr([article_title], **tm) article_date = Dstr([self.get_as_displayed(mode, 'When_Article_Orig')], **tm) article_editors = Dstr([self.get_as_displayed(mode, 'Article_Editors')], **tm) fragment = Dstr([self.get_as_displayed(mode, 'Fragment')], **tm) work_title = self.get_as_displayed(mode, 'Work') if work_title == '': work_title = NULL work = Dstr([work_title], **tm) work_date = Dstr([self.get_as_displayed(mode, 'When_Work_Orig')], **tm) work_editors = self.get_as_displayed(mode, 'Work_Editors') if work_editors.startswith('By '): work_editors1 = Dstr([work_editors], **tm) work_editors2 = NULL else: work_editors1 = NULL work_editors2 = Dstr([work_editors], **tm) convention = Dstr([self.get_as_displayed(mode, 'Conference')], **tm) series = Dstr([self.get_as_displayed(mode, 'Series')], **tm) series_functionaries = Dstr([self.get_as_displayed(mode, 'Series_Functionaries')], **tm) sponsor = Dstr([self.get_as_displayed(mode, 'Sponsor')], **tm) access_info = Dstr([self.get_as_displayed(mode, '_Traversed_Date'), Dstr([self.get_as_displayed(mode, '_Web')], px='<', sx='>', tq=None)], **tm) path = Dstr([self.get_as_displayed(mode, 'Path')], **tm) remark = Dstr([self.get_as_displayed(mode, 'Remark')], **tm) entry_type = self.get_as_edited('Entry_Type') if entry_type in ['Book']: edition = Dstr([self.get_as_displayed(mode, 'Edition')], **tm) vol = Dstr([self.get_as_displayed(mode, 'Volume')], **tm) dissertation = Dstr([self.get_as_displayed(mode, 'Dissertation_Type'), self.get_as_displayed(mode, 'Dissertation')], **tm) medium = Dstr([self.get_as_displayed(mode, 'Medium')], **tm) pieces = Dstr([self.get_as_displayed(mode, 'Pieces')], **tm) publication = Dstr([self.get_as_displayed(mode, 'City_Publisher'), self.get_as_displayed(mode, 'When_Published')], dt=', ', **tm) pages = Dstr([self.get_as_displayed(mode, 'Cross_Reference'), self.get_as_displayed(mode, 'Pages')], **tm) result = Dstr([ agency, authors, article, article_date, article_editors, fragment, work, work_editors1, work_date, work_editors2, edition, vol, convention, dissertation, series, series_functionaries, medium, pieces, publication, sponsor, pages, remark, access_info, path, ]) elif entry_type in ['Journal']: when = Dstr([self.get_as_displayed(mode, 'When_Published')], px='(', sx=')') pages = Dstr([self.get_as_displayed(mode, 'Pages')]) date_pages = Dstr([when, pages], dt=': ') volume_issue = Dstr([self.get_as_displayed(mode, 'Volume'), self.get_as_displayed(mode, 'Issue')], dt='.') publication = Dstr([self.get_as_displayed(mode, 'Work'), volume_issue, date_pages], **tm) result = Dstr([ authors, article, article_date, article_editors, fragment, publication, sponsor, remark, access_info, path, ]) elif entry_type in ['NewsMag']: when = Dstr([self.get_as_displayed(mode, 'When_Published'), self.get_as_displayed(mode, 'Edition')], dt=', ') pages = Dstr([self.get_as_displayed(mode, 'Pages')]) date_pages = Dstr([when, pages], dt=': ') publication = Dstr([self.get_as_displayed(mode, 'Work'), date_pages], **tm) result = Dstr([ authors, article, article_date, article_editors, fragment, publication, sponsor, remark, access_info, path, ]) elif entry_type in ['Performance']: edition = Dstr([self.get_as_displayed(mode, 'Edition')], **tm) manufacturer = Dstr([self.get_as_displayed(mode, 'Manufacturer')], **tm) network = Dstr([self.get_as_displayed(mode, 'Network')], **tm) venue_city = Dstr([self.get_as_displayed(mode, 'Venue_City')], **tm) medium_type = self.get_as_displayed(mode, 'Medium') if medium_type in ['CD', 'Film']: medium = NULL else: medium = Dstr([medium_type], **tm) recording_date = Dstr([self.get_as_displayed(mode, 'When_Recorded')], **tm) pieces = Dstr([self.get_as_displayed(mode, 'Pieces')], **tm) city_publisher = self.get_as_displayed(mode, 'City_Publisher') if city_publisher in [None, NULL]: if medium_type in ['CD-ROM']: publication = Dstr([self.get_as_displayed(mode, 'Manufacturer'), self.get_as_displayed(mode, 'When_Published')], dt='. ', **tm) else: publication = Dstr([self.get_as_displayed(mode, 'Manufacturer'), self.get_as_displayed(mode, 'When_Published')], dt=', ', **tm) else: publication = Dstr([city_publisher, self.get_as_displayed(mode, 'When_Published')], dt=', ', **tm) result = Dstr([ authors, article, article_date, article_editors, fragment, work, work_editors1, work_date, work_editors2, convention, series, series_functionaries, recording_date, network, venue_city, medium, pieces, edition, publication, sponsor, remark, access_info, path, ]) else: result = Dstr(['Unrecognized Entry Type', entry_type], dt= ': ', **tm) collection_type = self.get_as_displayed(mode, 'Collection_Type') if collection_type in ['Link to']: collection_type = NULL collection_link = self.get_as_displayed(None, 'Collection_Link') if collection_link in [None, NULL]: pass else: ndx = self.relation.find_row('_Key', collection_link) if ndx == NA: result = Dstr([result, collection_type, U_LEFT_CHEVRON, collection_link, U_RIGHT_CHEVRON, 'does not exist.']) else: save_row_ndx = self.relation.current_row self.relation.stage_row(ndx) result = Dstr([result, collection_type, self.view_text_mode(mode)]) self.relation.stage_row(save_row_ndx) return result.get_as_string() class DlgAddressViewAsText(DlgViewAsTextHot): def view_text_mode(self, mode): mark_break = '
' mark_rule = '

' if mode in ['_Markup']: mark_break += '\n' mark_rule += '\n' if mode in ['_Quoted']: mark_break += '> ' mark_rule += '> ' address = Dstr(px='> ', dt=mark_break * 2) else: address = Dstr(dt=mark_break * 2) greeting = self.get_as_displayed(mode, 'Greeting') polite_mode = self.get_as_displayed(mode, 'Polite_Mode') first_name = self.get_as_displayed(mode, 'First_Name') last_name = self.get_as_displayed(mode, 'Last_Name') title = self.get_as_displayed(mode, 'Title') dept_mail_stop = self.get_as_displayed(mode, 'Dept_Mail_Stop') company = self.get_as_displayed(mode, 'Company') line_address = [] line_address.append(self.get_as_displayed(mode, 'Locus')) line_address.append(self.get_as_displayed(mode, 'Street')) city = self.get_as_displayed(mode, 'City') state = self.get_as_displayed(mode, 'State') zip = self.get_as_displayed(mode, 'Zip') country = self.get_as_displayed(mode, 'Country') latitude = self.get_as_displayed(mode, 'Latitude') longitude = self.get_as_displayed(mode, 'Longitude') remark = self.get_as_displayed(mode, 'Remarks').replace('\n', mark_break) line_phone = [] for ndx in [1, 2, 3, 4]: type = self.get_as_displayed(mode, 'Phone_Type_%i' % ndx) number = self.get_as_displayed(mode, 'Phone_%i' % ndx) line_phone.append(Dstr([type, number], dt=': ')) web = self.get_as_displayed(mode, 'Web') pgraph_greeting = Dstr([greeting], px='Dear ', dt=mark_break) pgraph_address = Dstr(dt=mark_break) line_name = Dstr([Dstr([polite_mode, first_name, last_name]), title], dt=', ') e_mail = self.get_as_displayed(mode, 'eMail', contact_name=line_name.get_as_string()) # 2007 Aug 14 line_city_state_zip = Dstr([Dstr([city, state], dt=', '), zip], dt=' ') pgraph_address.extend([line_name, company, dept_mail_stop, line_address, line_city_state_zip, country]) pgraph_lat_lon = Dstr(dt=', ') pgraph_lat_lon.append(Dstr([latitude], px='Latitude: ', dt=', ')) pgraph_lat_lon.append(Dstr([longitude], px='Longitude: ', dt= ', ')) pgraph_phone = Dstr([line_phone], dt=mark_break) pgraph_net = Dstr([e_mail, web], dt=mark_break) pgraph_remark = Dstr([remark], dt=mark_break) if mode in ['_All Caps']: address.extend([pgraph_address]) else: address.extend([pgraph_greeting, pgraph_address, pgraph_lat_lon, pgraph_phone, pgraph_net, pgraph_remark]) result = address.get_as_string() return result class Dlg3X5ViewAsText(DlgViewAsTextHot): def view_text_mode(self, mode): mark_break = '
' mark_rule = '

' if mode in ['_Markup']: mark_break += '\n' mark_rule += '\n' if mode in ['_Quoted']: mark_break += '> ' mark_rule += '> ' card = Dstr(px='> ', dt=mark_rule) else: card = Dstr(dt=mark_rule) category = self.get_as_displayed(mode, 'Category') title = self.get_as_displayed(mode, '_Title') web = self.get_as_displayed(mode, '_Web') traversed_date = self.get_as_displayed(mode, '_Traversed_Date') remark = self.get_as_displayed(mode, 'Remark').replace('\n', mark_break) accession_date = self.get_as_displayed(mode, '_Accession_Date') update_date = self.get_as_displayed(mode, '_Update_Date') keywords = self.get_as_displayed(mode, 'Keywords') if mode in ['_Quoted']: line_title = Dstr([title], dt=mark_break) else: line_title = Dstr([title], px='', dt=mark_break, sx= '') line_category = Dstr([category], px='Category: ', dt= mark_break) line_url = Dstr([web], px='URL: ', dt=mark_break) line_keywords = Dstr([keywords], px='Keywords: ', dt= mark_break) pgraph_abstract = Dstr(dt=mark_break) pgraph_abstract.extend([line_title, line_category, line_url, line_keywords]) pgraph_remark = Dstr([remark], dt=mark_break) created = Dstr([accession_date], px='Created: ', sx= '. ') updated = Dstr([update_date], px='Updated: ', sx='. ') traversed = Dstr([traversed_date], px='Traversed: ', sx= '. ') pgraph_dates = Dstr([created, updated, traversed], px='', dt=SPACE * 2, sx='') card.extend([pgraph_abstract, pgraph_remark, pgraph_dates]) result = card.get_as_string() return result class EditLink(object): def __init__(self, url=NULL, title=NULL): self.title = NULL self.set_urltitle([url, title]) return def set_urltitle(self, lines): lines = lines[:] lines.append(NULL) (self.url, title) = lines[:2] if title in [None, NULL]: pass else: self.title = title return self def get_urltitle(self): return (self.url, self.title) def load_from_config(self, buffer, title): def get_safe(config, section, option, default): if config.has_section(section): if config.has_option(section, option): return config.get(section, option) else: return default else: return default result = False buffer = StringIO.StringIO(buffer) try: url = NULL if title == NULL: file_name = '' else: file_name = title short_cut = ConfigParser.ConfigParser() short_cut.readfp(buffer, file_name) url = get_safe(short_cut, 'InternetShortcut', 'URL', url) url = get_safe(short_cut, 'Desktop Entry', 'URL', url) title = get_safe(short_cut, 'Desktop Entry', 'Comment', title) title = get_safe(short_cut, 'Desktop Entry', 'Name', title) if url == NULL: pass else: self.set_urltitle([url, title]) result = True except ConfigParser.ParsingError: pass return result def load_from_xml(self, buffer, title): def get_text(node): result = NULL for child in node.childNodes: if child.nodeType in [child.TEXT_NODE]: result += child.data.strip() + SPACE else: result += get_text(child) return result.strip() result = False try: url = NULL dom = xml.dom.minidom.parse_string(buffer) anchors = dom.get_elements_by_tag_name('a') if len(anchors) > ZERO: anchor = anchors[ZERO] url = anchor.getAttribute('href') title = get_text(anchor) dom.unlink() if url == NULL: pass else: self.set_urltitle([url, title]) result = True except (TypeError, AttributeError, xml.parsers.expat.expat_error): pass return result def load_from(self, file_name=None): try: (protocol, domain, path, parms, query, frag) = urlparse.urlparse(file_name) file_name = url2pathname(path) title = file_name unit = open(file_name, 'rb') buffer = unit.read() unit.close() if self.load_from_config(buffer, title): pass else: if buffer.startswith('>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=01,day=01), ... None, ... datetime.timedelta(hours=ZERO,minutes=30), ... ZERO, ... None, ... ZERO, ... None, ... None, ... ).GetOccurrences(datetime.date(2004,12,27),datetime.date(2005,2,6)) [datetime.datetime(2005, 1, 1, 0, 0)] >>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=01,day=01), ... None, ... datetime.timedelta(hours=ZERO,minutes=30), ... ZERO, ... None, ... ZERO, ... None, ... None, ... ).GetAlarms(datetime.date(2004,12,27),datetime.date(2005,2,6)) [datetime.datetime(2004, 12, 31, 23, 30)] >>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=01,day=01), ... None, ... None, ... 1, ... 'Months', ... None, ... None, ... None, ... ).GetOccurrences(datetime.date(2004,12,27),datetime.date(2005,2,6)) [datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2005, 2, 1, 0, 0)] >>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=01,day=01), ... None, ... None, ... 1, ... 'Months', ... 2, ... 'Day', ... None, ... ).GetOccurrences(datetime.date(2004,12,27),datetime.date(2005,2,6)) [datetime.datetime(2005, 1, 2, 0, 0), datetime.datetime(2005, 2, 2, 0, 0)] >>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=01,day=17), ... None, ... None, ... 1, ... 'Months', ... 3, ... 'Week', ... datetime.date(year=2005,month=02,day=26), ... ).GetOccurrences(datetime.date(2004,12,27),datetime.date(2005,3,6)) [datetime.datetime(2005, 1, 17, 0, 0), datetime.datetime(2005, 2, 21, 0, 0)] >>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=01,day=01), ... None, ... None, ... 1, ... 'Months', ... -1, ... 'Day', ... None, ... ).GetOccurrences(datetime.date(2004,12,27),datetime.date(2005,2,6)) [datetime.datetime(2005, 1, 31, 0, 0)] >>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=01,day=01), ... None, ... None, ... 1, ... 'Months', ... -1, ... 'Week', ... None, ... ).GetOccurrences(datetime.date(2004,12,27),datetime.date(2005,3,6)) [datetime.datetime(2005, 1, 29, 0, 0), datetime.datetime(2005, 2, 26, 0, 0)] >>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=11,day=24), ... None, ... None, ... 12, ... 'Months', ... 4, ... 'Week', ... None, ... ).GetOccurrences(datetime.date(2005,1,1),datetime.date(2007,1,1)) [datetime.datetime(2005, 11, 24, 0, 0), datetime.datetime(2006, 11, 23, 0, 0)] >>> print CreateEvent(None,None,None,None,None, ... datetime.date(year=2005,month=01,day=01), ... None, ... None, ... 28, ... 'Days', ... ZERO, ... None, ... None, ... ).GetOccurrences(datetime.date(2004,12,27),datetime.date(2005,2,6)) [datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2005, 1, 29, 0, 0)] """ if start_date in BLANK: return None if start_time in BLANK: start_time = datetime.time() start_date_time = datetime.datetime.combine(start_date, start_time) if last_occurrence_date in BLANK: last_occurrence_date_time = None else: last_occurrence_date_time = datetime.datetime.combine(last_occurrence_date, start_time) if offset in BLANK + [ZERO]: return Event(relation, row, title, priority, fg, bg, start_date_time, advance_alarm) if offset_type in ['Months']: if month_offset_type in ['Week']: return EventRepeatMonthWeek( relation, row, title, priority, fg, bg, start_date_time, advance_alarm, offset, month_offset, last_occurrence_date_time, ) else: return EventRepeatMonthDay( relation, row, title, priority, fg, bg, start_date_time, advance_alarm, offset, month_offset, last_occurrence_date_time, ) else: return EventRepeatDay( relation, row, title, priority, fg, bg, start_date_time, advance_alarm, offset, last_occurrence_date_time, ) return None class Event(object): def __init__( self, relation, row, title, priority, fg, bg, start_date_time, advance_alarm, ): self.relation = relation self.row = row self.title = title self.priority = priority self.fg = fg self.bg = bg self.start_date_time = start_date_time self.advance_alarm = advance_alarm self.last_occurrence_date_time = None return def get_sort_key(self, start_date_time=None): if start_date_time is None: start_date_time = self.start_date_time priority = self.priority if priority in BLANK: priority = '~' result = '%s %s %s %s' % ( start_date_time.strftime('%Y%m%d'), priority.ljust(12), start_date_time.strftime('%H%M'), self.title, ) return (result, start_date_time, self) def get_date_range(self, date_lo, date_hi): date_lo = max(date_lo, self.start_date_time) if self.last_occurrence_date_time is None: date_hi = datetime.datetime.combine(date_hi, datetime.time()) else: date_hi = min(date_hi, self.last_occurrence_date_time) return (date_lo, date_hi) def is_in_range(self, date_time, date_lo, date_hi): (date_lo, date_hi) = self.get_date_range(date_lo, date_hi) return date_lo <= date_time <= date_hi def make_date_time(self, date): if isinstance(date, datetime.datetime): return date else: return datetime.datetime.combine(date, datetime.time()) def get_occurrences(self, date_lo, date_hi): date_lo = self.make_date_time(date_lo) date_hi = self.make_date_time(date_hi) + datetime.timedelta(1) # 2008 May 27 try: is_in_range = self.is_in_range(self.start_date_time, date_lo, date_hi) except TypeError: is_in_range = False if is_in_range: return [self.start_date_time] else: return [] def get_alarms(self, date_lo, date_hi): date_lo = self.make_date_time(date_lo) date_hi = self.make_date_time(date_hi) + datetime.timedelta(1) # 2008 May 27 result = [] try: date_lo += self.advance_alarm date_hi += self.advance_alarm for date in self.get_occurrences(date_lo, date_hi): result.append(date - self.advance_alarm) except TypeError: pass return result class EventRepeatDay(Event): def __init__( self, relation, row, title, priority, fg, bg, start_date_time, advance_alarm, offset, last_occurrence_date_time, ): Event.__init__( self, relation, row, title, priority, fg, bg, start_date_time, advance_alarm, ) self.offset = offset self.last_occurrence_date_time = last_occurrence_date_time return def get_dates_in_range(self, date_time, date_lo, date_hi): result = [] (date_lo, date_hi) = self.get_date_range(date_lo, date_hi) period_lo = (date_lo - date_time).days // self.offset period_hi = (date_hi - date_time).days // self.offset + 1 for period in range(period_lo, period_hi): days = datetime.timedelta(days=period * self.offset) result.append(date_time + days) return result def get_occurrences(self, date_lo, date_hi): date_lo = self.make_date_time(date_lo) date_hi = self.make_date_time(date_hi) + datetime.timedelta(1) # 2008 May 27 try: result = self.get_dates_in_range(self.start_date_time, date_lo, date_hi) except TypeError: result = [] return result class EventRepeatMonthDay(EventRepeatDay): def __init__( self, relation, row, title, priority, fg, bg, start_date_time, advance_alarm, offset, month_offset, last_occurrence_date_time, ): EventRepeatDay.__init__( self, relation, row, title, priority, fg, bg, start_date_time, advance_alarm, offset, last_occurrence_date_time, ) self.month_offset = month_offset return def find_day_of_month(self, year, month, day): if day >= ZERO: return day dom = 32 while day < ZERO: dom -= 1 try: datetime.date(year=year, month=month, day=dom) day += 1 except (ValueError, TypeError): # 2007 Nov 08 pass return dom def diff_months(self, date_hi, date_lo): year_lo = date_lo.year month_lo = date_lo.month year_hi = date_hi.year month_hi = date_hi.month result = (year_hi - year_lo) * 12 + month_hi - month_lo return result def sum_months(self, date, months, offset=None): time_stamp = date.time() year = date.year month = date.month month -= 1 (years, month) = divmod(month + months, 12) month += 1 year += years if offset in BLANK: day = date.day else: day = self.find_day_of_month(year, month, offset) return datetime.datetime.combine(datetime.date(year=year, month= month, day=day), time_stamp) def get_dates_in_range(self, date_time, date_lo, date_hi): result = [] (date_lo, date_hi) = self.get_date_range(date_lo, date_hi) period_lo = self.diff_months(date_lo, date_time) // self.offset period_hi = self.diff_months(date_hi, date_time) // self.offset + \ 1 for period in range(period_lo, period_hi): date_time = self.sum_months(self.start_date_time, months= period * self.offset, offset=self.month_offset) if date_lo <= date_time <= date_hi: result.append(date_time) return result class EventRepeatMonthWeek(EventRepeatMonthDay): def find_week_of_month(self, year, month, day_of_week, week): if week < ZERO: inc = -1 day = self.find_day_of_month(year, month, -1) else: inc = +1 day = 1 date = datetime.date(year=year, month=month, day=day) while date.weekday() != day_of_week: day += inc date = datetime.date(year=year, month=month, day=day) day += 7 * (week - inc) return day def sum_months(self, date, months, offset): time_stamp = date.time() year = date.year month = date.month month -= 1 (years, month) = divmod(month + months, 12) month += 1 year += years day = self.find_week_of_month(year, month, date.weekday(), offset) return datetime.datetime.combine(datetime.date(year=year, month= month, day=day), time_stamp) class TopoItem(object): def __init__(self): self.pred_count = ZERO self.succ = [] return def bump_pred_count(self, inc=1): self.pred_count += inc return self def push_succ(self, topo_item): self.succ.insert(ZERO, topo_item) return self def has_no_pred(self): return self.pred_count == ZERO def list_succ(self): return self.succ class Topo(dict): '''Knuth, Donald E. "Program T." _Fundamental Algorithms_. 2nd ed. Vol. 1 of _The Art of Computer Programming_. Reading: Addison, 1969. 265-65. ''' def __init__(self): self.has_loops = False return def insert(self, item): self[item] = TopoItem() return self def make_comparison(self, lo_item, hi_item): self[hi_item].bump_pred_count() self[lo_item].push_succ(hi_item) return self def list_no_pred(self): result = [] for (item, topo_item) in self.iteritems(): if topo_item.has_no_pred(): result.append(item) return result def sort(self): result = [] no_pred = self.list_no_pred() while len(no_pred) > ZERO: item = no_pred.pop(ZERO) result.append(item) for ndx in self[item].list_succ(): succ = self[ndx] succ.bump_pred_count(-1) if succ.has_no_pred(): no_pred.append(ndx) self.has_loops = len(result) != len(self) return result class Task(object): def __init__(self, relation, row_ndx, preds): days = relation.get_row_value(row_ndx=row_ndx, tag='_Days', default_value=ZERO) state = relation.get_row_value(row_ndx=row_ndx, tag='_Status') self.is_finished = state in ['Closed', 'Finished'] if self.is_finished: self.duration = datetime.timedelta() else: self.duration = datetime.timedelta(days=days) self.preds = preds self.esd = None self.lfd = None return def get_efd(self): if self.esd is None: return None else: return self.esd + self.duration def get_lsd(self): if self.lfd is None: return None else: return self.lfd - self.duration def get_float(self): if None in [self.esd, self.lfd]: return None else: result = (self.lfd - self.esd) - self.duration return result.days def get_preds(self): return self.preds def set_esd(self, esd, today): if self.esd is None: self.esd = esd elif esd is None: esd = today else: esd = max(esd, today) self.esd = max(self.esd, esd) return self def set_lfd(self, lfd, today): if self.lfd is None: self.lfd = lfd elif lfd is None: lfd = today else: lfd = max(lfd, today) self.lfd = min(self.lfd, lfd) return self def update(self, relation, row_ndx): relation.set_row_value(row_ndx=row_ndx, tag='_Earliest_Start', value=self.esd) relation.set_row_value(row_ndx=row_ndx, tag='_Latest_Finish', value=self.lfd) relation.set_row_value(row_ndx=row_ndx, tag='_Float', value=self.get_float()) return self class TaskScheduled(Task): def __init__(self, relation, row_ndx, preds): Task.__init__(self, relation=relation, row_ndx=row_ndx, preds= preds) self.esd = relation.get_row_value(row_ndx=row_ndx, tag= '_Start_Date') self.lfd = self.esd + self.duration return def set_esd(self, esd, today): return self def set_lfd(self, lfd, today): return self class TaskList(dict): def __init__(self, relation): dict.__init__(self) self.relation = relation self.today = datetime.date.today() return def insert(self, row_ndx, preds): if self.has_start_date(row_ndx): self[row_ndx] = TaskScheduled(self.relation, row_ndx, preds) else: self[row_ndx] = Task(self.relation, row_ndx, preds) return self def has_start_date(self, row_ndx): return self.relation.get_row_value(row_ndx=row_ndx, tag= '_Start_Date') not in BLANK def calc_esd(self, row_ndx): task = self[row_ndx] for pred_row in task.get_preds(): pred_task = self[pred_row] task.set_esd(pred_task.get_efd(), self.today) return self def calc_lfd(self, row_ndx): task = self[row_ndx] for pred_row in task.get_preds(): pred_task = self[pred_row] pred_task.set_lfd(task.get_lsd(), self.today) return self def update(self, row_ndx): self[row_ndx].update(self.relation, row_ndx) return self def load_relation( file_name=USER_FILE_DEFAULTS['file_name'], name=USER_FILE_DEFAULTS['name'], tag=USER_FILE_DEFAULTS['tag'], class_of=None, must_exist=False, ): def open_dd(file_name): try: ddfile = TaggedStream(file_name + '.dd', 'rb') except IOError: ddfile = None return ddfile if class_of is None: class_of = RelationUserDefined ddfile = open_dd(file_name) if ddfile is None: result = class_of(name=name, tag=tag, file_name=file_name) else: try: class_ = RELATION_CLASS.get(ddfile.get(), class_of) result = class_(name=name, tag=tag, file_name=file_name) result.load_dictionary(ddfile) ddfile.close() except (KeyError, TypeError): raise ErrorBadDictionary('Error loading file %s.dd.' % file_name) result.load_data(file_name, must_exist=must_exist) return result class Relation(list): def __init__(self, name, tag=None, file_name=None, column_list=None, display_tags=None, parent_matrix=None): list.__init__(self) self.name = name if tag is None: self.tag = self.name else: self.tag = tag self.file_name = file_name if column_list is None: self.column_list = ColumnList() else: self.column_list = column_list.copy() if display_tags is None: self.display_tags = self.column_list.get_tags() else: self.display_tags = display_tags[:] self.parent_matrix = parent_matrix self.icon = None self.set_help() self.accession_date = datetime.date.today() self.set_dirty(False) self.current_row = None return def destroy(self): return self def get_name(self): return self.name def get_tag(self): return self.tag def set_dirty(self, dirty=True): self.dirty = dirty return def is_dirty(self): return self.dirty def set_help(self, help=None): self.help = help return self def get_help(self): return self.help def eval_row(self, dict): result = {} for (key, value) in dict.iteritems(): if value is None: result[key] = None else: try: result[key] = value.replace('\\n', '\n') except AttributeError: result[key] = NOT_AVAILABLE return result def repr_row(self, dict): result = {} for (key, value) in dict.iteritems(): if value is None: result[key] = None else: result[key] = value.replace('\n', '\\n') return result def merge(self, file_name): try: ddfile = TaggedStream(file_name + '.dd', 'rb') except IOError: ddfile = None if ddfile is None: pass else: try: file_type = ddfile.get() # not used self.load_dictionary(ddfile, mask_mod_lock=False, merge=True) ddfile.close() except (KeyError, TypeError): raise ErrorBadDictionary('Error loading file %s.dd.' % file_name) self.load_data(file_name, must_exist=True) self.set_dirty(True) return self def load_dictionary(self, ddfile, mask_mod_lock=True, merge=False): if merge: self.column_list.merge(ddfile, mask_mod_lock=mask_mod_lock) else: self.column_list.load(ddfile, mask_mod_lock=mask_mod_lock) self.display_tags = [] col_tags = self.column_list.get_tags() display_cols = ddfile.get() while display_cols > ZERO: tag = ddfile.get() if tag in col_tags and tag not in self.display_tags: self.display_tags.append(tag) display_cols -= 1 self.icon = ddfile.get() if ddfile.version <= (1, 0): pass else: self.set_help(ddfile.get()) self.acccession_date = ddfile.get() return self def load_data(self, file_name, must_exist=False): try: csvfile = codecs.open(os.path.expanduser(file_name + '.csv'), 'rb', 'utf-8') except IOError: csvfile = None if must_exist: diagnostic = DlgAssertion(parent=MAIN, msg= ''' Load This program encountered an error trying to open a data file called %s. Perhaps the file no longer exists. Perhaps the name does not end in .csv. ''' % file_name) diagnostic.run() diagnostic.destroy() if csvfile is None: pass else: try: lines = csvfile.read().splitlines() except UnicodeDecodeError: csvfile = None lines=[] diagnostic = DlgAssertion(parent=MAIN, msg= ''' Load This program encountered a Unicode error trying to open a data file called %s. The file is probably corrupted and the last good backup of it should be restored. ''' % file_name) diagnostic.run() diagnostic.destroy() if csvfile is None: self.init_data() else: csvfile.close() try: has_header = csv.Sniffer().has_header(lines[ZERO].encode('utf-8')) except (csv.Error, TypeError, IndexError): has_header = False if has_header: tags_expected = self.column_list.get_tags() tags_actual = csv.reader(lines[:1]).next() expand_display = len(self.display_tags) == ZERO on1 = True for tag in tags_actual: if tag in tags_expected: pass else: if on1: self.column_list.bump_page() on1 = False self.column_list.append(FieldStringNoMarkup(tag)) if expand_display: self.display_tags.append(tag) try: reader = csv.DictReader(lines[1:], tags_actual) for row in reader: self.append(self.eval_row(row)) except (KeyError, TypeError): raise ErrorBadData('Error loading file %s.csv.' % file_name) else: raise ErrorNoColHeaders('Column headers missing or invalid in file %s.csv.' % file_name) return self def init_data(self): self.set_dirty() return self def save(self, file_name=None, rename=False): if file_name is None: file_name = self.file_name if rename: self.file_name = file_name try: ddfile = TaggedStream(file_name + '.dd', 'wb') ddfile.put('File Type', RELATION_CLASS_NAME[type(self)]) self.save_dictionary(ddfile) ddfile.close() except (TypeError, ValueError): raise ErrorBadDictionary('Error saving file %s.dd.' % file_name) try: self.save_data(file_name) except (TypeError, ValueError): raise ErrorBadData('Error saving file %s.csv.' % file_name) if rename: self.set_dirty(False) return self def save_dictionary(self, ddfile): self.column_list.save(ddfile) ddfile.put('Display Tags', len(self.display_tags)) for tag in self.display_tags: ddfile.put(' Tag', tag) ddfile.put('Icon', self.icon) ddfile.put('Description', self.get_help()) ddfile.put('AccessionDate', self.accession_date) return self def save_data(self, file_name): csvfile = codecs.open(os.path.expanduser(file_name + '.csv'), 'wb', 'utf-8') writer = csv.DictWriter(csvfile, self.column_list.get_tags(), extrasaction='ignore') header = {} for col in self.column_list.get_tags(): header[col] = col writer.writerow(header) for row in self: writer.writerow(self.repr_row(row)) csvfile.close() return self def sort(self, col_tag, descending=False): if descending: length = len(self) list = [('%s%9i' % (row.get(col_tag, NULL), length - ndx), row) for (ndx, row) in enumerate(self)] list.sort() list.reverse() else: list = [('%s%9i' % (row.get(col_tag, NULL), ndx), row) for (ndx, row) in enumerate(self)] list.sort() self[:] = [row for (key, row) in list] self.set_dirty() return self def get_row_display(self, row_ndx): result = [] for tag in self.display_tags: value = self.get_row_value(row_ndx, tag, default_value=NULL, method='GetAsEdited') result.append(enscape(value)) return result def get_next_display(self): for (ndx, row) in enumerate(self): yield self.get_row_display(ndx) return def remove_row(self, row_ndx): result = self.pop(row_ndx) self.set_dirty() return result def insert_new_row(self, row_ndx): self.insert(row_ndx, {}) self.set_dirty() return self def paste_rows(self, row_ndx, rows): self[row_ndx:row_ndx] = rows self.set_dirty() return self def view_as_text(self, parent, row_ndx, dlg_type=DlgViewAsText): self.stage_row(row_ndx) dlg = dlg_type(parent=parent, relation=self, row_ndx=row_ndx) dlg.run() dlg.destroy() def stage_col(self, row, col): tag = col.get_tag() try: col.set_as_edited(row.get(tag, col.default)) except Error, __ErrorInstance: dlg = DlgAssertion(parent=MAIN, msg=str(__ErrorInstance)) dlg.run() dlg.destroy() col.set(col.default) return self def stage_row(self, row_ndx): self.current_row = row_ndx row = self[self.current_row] for col in self.column_list.get_next_fld(): self.stage_col(row, col) return self def destage_col(self, row, col): tag = col.get_tag() row[tag] = col.get_as_edited() return self def destage_row(self, row_ndx): accession_date = self.column_list.find('_Accession_Date') if accession_date is None: pass elif accession_date.is_not_available(): accession_date.set_now() if self.is_dirty(): update_date = self.column_list.find('_Update_Date') if update_date is None: pass else: update_date.set_now() row = self[row_ndx] for col in self.column_list.get_next_fld(): self.destage_col(row, col) self.current_row = None return self def field_entry(self, parent, row_ndx, button_width=BUTTON_WIDTH, label_width=LABEL_WIDTH, entry_width=ENTRY_WIDTH, height=FORM_HEIGHT): self.stage_row(row_ndx) page_count = len(self.column_list) page_ndx = ZERO while True: if ZERO <= page_ndx: pass else: page_ndx = page_count - 1 if page_ndx < page_count: pass else: page_ndx = ZERO dlg = DlgFieldEntry(parent=parent, page_ndx=page_ndx, page_count=page_count, width=label_width + entry_width + 100, height=height, title= self.get_name()) self.stage_page(parent, dlg, page_ndx, button_width= button_width, label_width=label_width, entry_width=entry_width, height=height) result = quarrantine_invalid_data(dlg=dlg, method=self.destage_page, method_calling_sequence=[page_ndx], title= 'Field Entry') dlg.destroy() if result in [gtk.RESPONSE_OK]: self.destage_row(row_ndx) self.set_dirty() break elif result in [RESPONSE_PREV]: page_ndx -= 1 elif result in [RESPONSE_NEXT]: page_ndx += 1 else: break return self def stage_page(self, parent, dlg, page_ndx, button_width=BUTTON_WIDTH, label_width=LABEL_WIDTH, entry_width=ENTRY_WIDTH, height=FORM_HEIGHT): insensitive_flds = [] if page_ndx == ZERO: self.conjure_quick_field(parent, dlg.box) if ZERO <= page_ndx < len(self.column_list): for fld in (self.column_list)[page_ndx]: self.conjure_field( parent, dlg, fld, insensitive_flds, page_ndx, button_width=button_width, label_width=label_width, entry_width=entry_width, height=height, ) self.handler_id_realize = dlg.connect_after('realize', call_back_after_realize_to_set_text_view_base_color, insensitive_flds) return self def destage_page(self, page_ndx): if ZERO <= page_ndx < len(self.column_list): page = (self.column_list)[page_ndx] else: page = [] for fld in page: fld.destage_widget() return self def conjure_quick_field(self, parent, box): return self def conjure_field( self, parent, dlg, col, insensitive_flds, page_ndx, button_width=BUTTON_WIDTH, label_width=LABEL_WIDTH, entry_width=ENTRY_WIDTH, height=FORM_HEIGHT, ): box = dlg.box width = ZERO box.set_pack(gtk.PACK_END) box.bump_row() no_stretch = Stretch().set_expand(False) hor_stretch = Stretch().set_expand(False).set_hor_expand(True) if col.is_enabled(): for (tag, (method, icon, help)) in col.repertoire.items()[:1]: # Allow only one for now. button = box.stuff(Button().add_stock_label(icon, tag), stretch=no_stretch).add_help(help) button.connect('clicked', self.call_back_repertoire_button_clicked, col, method, page_ndx, dlg) button.set_size_request(width=button_width, height=-1) width += button_width + WINDOW_PADDING * 2 box.set_pack(gtk.PACK_START) label = box.stuff(LabelWithToolTip(), stretch=no_stretch) label.set_unicode('%s:' % col.get_name()) label.add_help(col.get_help()) label.set_size_request(width=label_width, height=-1) label.set_alignment(xalign=1.0, yalign=ZERO) entry = col.make_widget() box.stuff(entry, stretch=hor_stretch) if col.is_enabled(): pass else: insensitive_flds.append(entry) entry.set_size_request(width=entry_width - width, height=-1) col.stage_widget() return self def call_back_repertoire_button_clicked(self, button, field, method, page_ndx, parent, *calling_sequence): try: self.destage_page(page_ndx) except Error: pass method(field, self, parent) return False def find_row(self, tag, value): for (ndx, row) in enumerate(self): if row.get(tag) == value: return ndx return NA def set_row_value(self, row_ndx, tag, value=None, method='Set'): row = self[row_ndx] fld = self.column_list.find(tag) if fld is None: row[tag] = str(value) else: temp = fld.get() if method in ['Set']: fld.set(value) row[tag] = fld.get_as_edited() elif method in ['SetAsEdited']: fld.set_as_edited(value) row[tag] = fld.get_as_edited() fld.set(temp) # self.set_dirty() return self def get_row_value(self, row_ndx, tag, default_value=None, method='Get'): row = self[row_ndx] if tag in row: fld = self.column_list.find(tag) if fld is None: result = row[tag] else: temp = fld.get() try: fld.set_as_edited(row[tag]) except Error: fld.clear() try: if method in ['Get']: result = fld.get() elif method in ['GetAsDisplayed']: result = fld.get_as_displayed() elif method in ['GetAsEdited']: result = fld.get_as_edited() else: result = None except (Error, ValueError, TypeError): result = None fld.set(temp) if result in BLANK: result = default_value else: result = default_value return result def audit_display_tags(self): result = [] for tag in self.display_tags: if self.column_list.find(tag) is None: pass else: result.append(tag) return result class RelationWithLink(Relation): def conjure_quick_field(self, parent, box): self.main = parent box.bump_row().at_col(1) self.EventBox = box.stuff(gtk.EventBox()) button = Button().add_stock_label(gtk.STOCK_PASTE, 'Paste Link').add_help(""" Drag a link from your browser and drop it here. Tonto will put the URL into the Web field and the linked text into the Title. Alternatively, you may drag the thumb out of your browser's location bar and drop it here. In this case, Tonto sets Title to the title of the Web page your browser is displaying. Finally, you may drag a file name from your File Manager and drop it here to create a link to a local file on your hard disk. Tonto opens and reads .desktop and .url files dropped here to extract link titles from them.""") button.show() self.EventBox.add(button) self.EventBox.connect('drag-data-received', self.call_back_drag_data_received) self.EventBox.drag_dest_set(gtk.DEST_DEFAULT_ALL, DND_TARGETS, gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY) button.connect('clicked', self.call_back_button_paste_clicked) return self def split_data(self, data): ndx = (data + NUL).find(NUL) return data[:ndx].splitlines() def call_back_drag_data_received( self, widget, context, x, y, selection, target_type, time, *calling_sequence ): web_fld = self.column_list.find('_Web') title_fld = self.column_list.find('_Title') when_accessed_fld = self.column_list.find('_Traversed_Date') if None in [web_fld, title_fld, when_accessed_fld]: return False link = EditLink(web_fld.widget.get_unicode(), title_fld.widget.get_unicode()) mime_type = ALL_TARGETS[target_type] data = selection.data if mime_type in WIDE_BYTE_TARGETS: # Encoding of these targets is controversial. data = data.decode('utf_16').encode('utf_8') lines = self.split_data(data) if len(lines) > ZERO: if mime_type in ['x-special/gnome-icon-list', 'text/uri-list', 'text/unicode', 'text/plain']: link.set_urltitle(lines[:1]) link.load_from(lines[ZERO]) elif mime_type in ['_NETSCAPE_URL', 'text/x-moz-url']: link.set_urltitle(lines[:2]) (url, title) = link.get_urltitle() web_fld.widget.set_unicode(url) title_fld.widget.set_unicode(self.quote_title(title)) when_accessed_fld.set_now() return False def call_back_button_paste_clicked(self, *calling_sequence): web_fld = self.column_list.find('_Web') title_fld = self.column_list.find('_Title') when_accessed_fld = self.column_list.find('_Traversed_Date') if None in [web_fld, title_fld, when_accessed_fld]: return False selection_data = self.main.xclipboard_import.get_target(CLIPBOARD_TARGETS, self.main.xclipboard_export) if selection_data is None: pass else: link = EditLink(web_fld.widget.get_unicode(), title_fld.widget.get_unicode()) lines = self.split_data(selection_data) target = self.main.xclipboard_import.target if target in ['UTF8_STRING', 'STRING']: link.set_urltitle(lines[:1]) link.load_from(lines[ZERO]) elif target in ['x-special/gnome-copied-files'] and len(lines) > \ 1: link.set_urltitle(lines[1:2]) link.load_from(lines[1]) (url, title) = link.get_urltitle() web_fld.widget.set_unicode(url) title_fld.widget.set_unicode(self.quote_title(title)) when_accessed_fld.set_now() return False def quote_title(self, title): if title in BLANK: pass elif title[ZERO] in ['"']: pass else: title = '"%s"' % title return title def traverse_link(self, row_ndx): self.stage_row(row_ndx) fld = self.column_list.find('_Web') if fld is None: pass else: fld.call_back_field_button_link(self, self) return self RELATION_CLASS_REGISTRY = Registry() class RelationUserDefined(Relation): def __init__(self, file_name=USER_FILE_DEFAULTS['file_name'], name=USER_FILE_DEFAULTS['name'], tag=USER_FILE_DEFAULTS['tag'], parent_matrix=None): Relation.__init__(self, name, tag=tag, file_name=file_name, parent_matrix=parent_matrix) self.set_help('User Defined') return RELATION_CLASS_REGISTRY.stuff('User Defined', RelationUserDefined, 'User Defined files may have any columns you wish.') class RelationCalendar(Relation): def __init__(self, name='Calendar', tag='Calendar', file_name='~/.Tonto.Calendar', parent_matrix=None): Relation.__init__(self, name, tag=tag, file_name=file_name, parent_matrix=parent_matrix) self.set_help('Personal Appointment Calendar and To-Do List') project = FieldString('Project').set_help('This field distiguishes appointments and to-do tasks by\n the Project they belong to, if any.') title = FieldString('Title').set_help('Each task may have a Title. This is what shows\n on the calendar layout. Brevity is essential.') handle = FieldString('Handle', tag='_Handle').set_mod_lock(True).set_help('In addition to Title, a task may have a short\n Handle. Dependent tasks may refer to this.') days = FieldInteger('Days', tag='_Days', min_value=ZERO).set_mod_lock(True).set_help('Days is the number of days required from start\n to finish. Normally, this is one.') req1 = FieldString('Prerequisite 1', tag='_Req1').set_mod_lock(True).set_help('''Prerequisite is the Handle of some other task that must be finished before this one may be started. Specifying a Prerequisite for this task makes this task dependent on the Prerequisite.''') req2 = FieldString('Prerequisite 2', tag='_Req2').set_mod_lock(True).set_help(req1.help) req3 = FieldString('Prerequisite 3', tag='_Req3').set_mod_lock(True).set_help(req1.help) req4 = FieldString('Prerequisite 4', tag='_Req4').set_mod_lock(True).set_help(req1.help) start_date = FieldDate('Start Date', tag='_Start_Date').set_mod_lock(True).set_help('Start Date is the scheduled date, if any.') start_time = FieldTime('Start Time').set_help('Start Time is the scheduled time, if any.') finish_time = FieldTime('Finish Time').set_help('Finish Time is the scheduled time, if any.') advance_alarm = FieldTimeDelta('Advance Alarm', tag= '_Advance_Alarm').set_mod_lock(True).set_help('''Advance Alarm tells how far in advance (hours and minutes) of the Start Time to schedule a pop-up alarm.''') earliest_start = FieldDate('Earliest Start', tag= '_Earliest_Start').set_mod_lock(True).set_enabled(False).set_help('''Earliest Start is the first date that all prerequisite tasks can be complete. This is a calculated value.''') latest_finish = FieldDate('Latest Finish', tag='_Latest_Finish').set_mod_lock(True).set_enabled(False).set_help('Latest Finish is the last date to begin\n dependent tasks. This is a calculated value.') float = FieldInteger('Float', tag='_Float').set_mod_lock(True).set_enabled(False).set_help('''Float is the leeway in days allotted to a task. This is a calculated value. It is Latest Finish less Earliest Start less Days.''') priority = FieldString('Priority').set_help('''Priority is an indication of the importance of this task. It may be anything. Tasks that represent "red-letter" days should have Priority=zero, so that they sort first on a given day. That way, they specify the correct Foreground Color. The Background Color of a cell on the monthly calendar is taken from the task that sorts last on a given day.''') remark = FieldLog('Remark').set_help('''Remark is any freeform text. The other fields in this entry cover "when" well enough. Specify "who, what, where, why, and how much" here.''') status = FieldSelected('Status', tag='_Status').set_help('Status may be Active, Inactive, Pending,\n Finished, or Closed, but it could be anything.') status.code_set = CodeSet() status.code_set.append(Code('Active')) status.code_set.append(Code('Inactive')) status.code_set.append(Code('Pending')) status.code_set.append(Code('Finished')) status.code_set.append(Code('Closed')) offset = FieldInteger('Offset', tag='_Offset').set_mod_lock(True).set_help('Offset causes the task to be rescheduled. It is\n a number of days or months. See Offset Type.') offset_type = FieldCoded('Offset Type', tag='_Offset_Type', default=NOT_AVAILABLE).set_mod_lock(True).set_help('Offset Type is a word. It must be either days\n or months.') offset_type.code_set = CodeSet() offset_type.code_set.append(Code(NOT_AVAILABLE)) offset_type.code_set.append(Code('Days')) offset_type.code_set.append(Code('Months')) month_offset = FieldInteger('Offset in Month', tag= '_Offset_in_Month').set_mod_lock(True).set_help('''Offset In Month goes with Offset Type=Months. It may be a day or a week number. See Offset In Month Type. It may be -1 to reschedule the task on the last day of a month or in the last week. If Offset in Month is not specified, the task is rescheduled on the same day of the month as it was originally.''') month_offset_type = FieldCoded('Offset in Month Type', tag= '_Offset_in_Month_Type', default=NOT_AVAILABLE).set_mod_lock(True).set_help('''Offset In Month Type must be day or week. If it is week, the task is rescheduled on the same day of the week as it was originally.''') month_offset_type.code_set = CodeSet() month_offset_type.code_set.append(Code(NOT_AVAILABLE)) month_offset_type.code_set.append(Code('Day')) month_offset_type.code_set.append(Code('Week')) last_occurrence_date = FieldDate('Last Occurrence Date', tag= '_Last_Occurrence_Date').set_mod_lock(True).set_help('The task will not be rescheduled beyond the Last\n Occurrence Date, if any.') foreground = FieldColorName('Foreground Color').set_mod_lock(True).set_help('''When this task appears on the monthly calendar, set the day of the month digits to this Foreground Color. Normally, this is reserved for tasks that represent "red-letter" days.''') background = FieldColorName('Background Color', default='white').set_mod_lock(True).set_help('When this task appears on the monthly calendar, set the\n cell for the day to this Background Color.') accession_date = FieldDateTime('Accession Date', tag= '_Accession_Date').set_mod_lock(True).set_enabled(False).set_help('''Accession Date cannot be changed manually. Instead, automatically, it keeps track of when the entry was made.''') update_date = FieldDateTime('Update Date', tag='_Update_Date').set_mod_lock(True).set_enabled(False).set_help('''Update Date cannot be changed manually. Instead, automatically, it keeps track of when the entry was last changed.''') keywords = FieldString('Keywords').set_help('''Keywords is a list of word forms that you want to remember this entry by. You can search by Keyword to find this entry. You may wish to separate Keywords with semicolons.''') self.column_list.bump_page() self.column_list.append(project) self.column_list.append(handle) self.column_list.append(title) self.column_list.append(priority) self.column_list.append(status) self.column_list.append(remark) self.column_list.append(foreground) self.column_list.append(background) self.column_list.bump_page() self.column_list.append(start_date) self.column_list.append(start_time) self.column_list.append(finish_time) self.column_list.append(advance_alarm) self.column_list.append(offset) self.column_list.append(offset_type) self.column_list.append(month_offset) self.column_list.append(month_offset_type) self.column_list.append(last_occurrence_date) self.column_list.bump_page() self.column_list.append(days) self.column_list.append(req1) self.column_list.append(req2) self.column_list.append(req3) self.column_list.append(req4) self.column_list.append(earliest_start) self.column_list.append(latest_finish) self.column_list.append(float) self.column_list.append(accession_date) self.column_list.append(update_date) self.column_list.append(keywords) self.display_tags.append(project.get_tag()) self.display_tags.append(handle.get_tag()) self.display_tags.append(float.get_tag()) self.display_tags.append(priority.get_tag()) self.display_tags.append(earliest_start.get_tag()) self.display_tags.append(start_time.get_tag()) self.alarms = [] self.conjure_alarm_dlg() self.time_out_check_alarms_id = None self.time_out_ring_bell_id = None self.call_back_check_alarms() return def destroy(self): self.cancel_time_out_check_alarms() Relation.destroy(self) return self def view_as_text(self, parent, row_ndx, dlg_type=DlgCalendarViewAsText): Relation.view_as_text(self, parent=parent, row_ndx=row_ndx, dlg_type=dlg_type) return self def get_occurrences(self, date_lo, date_hi): events = [] for (ndx, row) in enumerate(self): try: event = create_event( relation=self, row=ndx, title=self.get_row_value(ndx, 'Title', None), priority=self.get_row_value(ndx, 'Priority', None), fg=self.get_row_value(ndx, 'Foreground_Color', None), bg=self.get_row_value(ndx, 'Background_Color', None), start_date=self.get_row_value(ndx, '_Start_Date', None), start_time=self.get_row_value(ndx, 'Start_Time', None), advance_alarm=self.get_row_value(ndx, '_Advance_Alarm', None), offset=self.get_row_value(ndx, '_Offset', None), offset_type=self.get_row_value(ndx, '_Offset_Type', None), month_offset=self.get_row_value(ndx, '_Offset_in_Month', None), month_offset_type=self.get_row_value(ndx, '_Offset_in_Month_Type', None), last_occurrence_date=self.get_row_value(ndx, '_Last_Occurrence_Date', None), ) except Error: event = None if event is None: pass else: for date in event.get_occurrences(date_lo, date_hi): events.append(event.get_sort_key(date)) events.sort() result = {} for (key, date, event) in events: list = result.setdefault(date.strftime('%Y%m%d'), []) list.append(event) return result def get_alarms(self, date_lo, date_hi): events = [] for (ndx, row) in enumerate(self): try: event = create_event( relation=self, row=ndx, title=self.get_row_value(ndx, 'Title', None), priority=self.get_row_value(ndx, 'Priority', None), fg=self.get_row_value(ndx, 'Foreground_Color', None), bg=self.get_row_value(ndx, 'Background_Color', None), start_date=self.get_row_value(ndx, '_Start_Date', None), start_time=self.get_row_value(ndx, 'Start_Time', None), advance_alarm=self.get_row_value(ndx, '_Advance_Alarm', None), offset=self.get_row_value(ndx, '_Offset', None), offset_type=self.get_row_value(ndx, '_Offset_Type', None), month_offset=self.get_row_value(ndx, '_Offset_in_Month', None), month_offset_type=self.get_row_value(ndx, '_Offset_in_Month_Type', None), last_occurrence_date=self.get_row_value(ndx, '_Last_Occurrence_Date', None), ) except Error: event = None if event is None: pass else: for date in event.get_alarms(date_lo, date_hi): events.append(event.get_sort_key(date)) events.sort() return [[date_time, event.row, False] for (key, date_time, event) in events] def init_data(self): Relation.init_data(self) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': "New Year's Day", '_Accession_Date': '2006/02/17 02:21 PM', '_Handle': "New Year's Day", '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '1', '_Offset_in_Month_Type': 'Day', '_Start_Date': '2005/01/01', '_Update_Date': '2006/02/26 08:18 AM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': 'ML King Day', '_Accession_Date': '2006/02/24 05:40 PM', '_Handle': 'ML King Day', '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '3', '_Offset_in_Month_Type': 'Week', '_Start_Date': '2005/01/17', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': NULL, 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': "Valentine's Day", '_Accession_Date': '2006/02/25 01:16 PM', '_Handle': "Valentine's Day", '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '14', '_Offset_in_Month_Type': 'Day', '_Start_Date': '2005/02/14', '_Update_Date': '2006/02/25 03:09 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': "Presidents' Day", '_Accession_Date': '2006/02/25 01:49 PM', '_Handle': "Presidents' Day", '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '3', '_Offset_in_Month_Type': 'Week', '_Start_Date': '2005/02/21', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': NULL, 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': "St Patrick's Day", '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': "St Patrick's Day", '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '17', '_Offset_in_Month_Type': 'Day', '_Start_Date': '2005/03/17', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': NULL, 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': "April Fool's Day", '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': "April Fool's Day", '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '1', '_Offset_in_Month_Type': 'Day', '_Start_Date': '2005/04/01', '_Update_Date': '2006/02/25 02:24 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': NULL, 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': "Mothers' Day", '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': "Mothers' Day", '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '2', '_Offset_in_Month_Type': 'Week', '_Start_Date': '2005/05/08', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': 'Memorial Day', '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': 'Memorial Day', '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '-1', '_Offset_in_Month_Type': 'Week', '_Start_Date': '2005/05/30', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': NULL, 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': "Fathers' Day", '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': "Fathers' Day", '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '3', '_Offset_in_Month_Type': 'Week', '_Start_Date': '2005/06/19', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': 'Independence Day', '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': 'Independence Day', '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '4', '_Offset_in_Month_Type': 'Day', '_Start_Date': '2005/07/04', '_Update_Date': '2006/02/25 02:29 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': 'Labor Day', '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': 'Labor Day', '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '1', '_Offset_in_Month_Type': 'Week', '_Start_Date': '2005/09/05', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': 'Columbus Day', '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': 'Columbus Day', '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '2', '_Offset_in_Month_Type': 'Week', '_Start_Date': '2005/10/10', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': NULL, 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': 'Halloween', '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': 'Halloween', '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '31', '_Offset_in_Month_Type': 'Day', '_Start_Date': '2005/10/31', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': "Veterans' Day", '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': "Veterans' Day", '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '11', '_Offset_in_Month_Type': 'Day', '_Start_Date': '2005/11/11', '_Update_Date': '2006/02/25 02:23 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': 'Thanksgiving', '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': 'Thanksgiving', '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '4', '_Offset_in_Month_Type': 'Week', '_Start_Date': '2005/11/24', '_Update_Date': '2006/02/25 02:57 PM', }) self.append({ 'Background_Color': NULL, 'Foreground_Color': 'red', 'Priority': '0', 'Project': '~Holidays', '_Status': 'Inactive', 'Title': 'Christmas', '_Accession_Date': '2006/02/25 01:53 PM', '_Handle': 'Christmas', '_Offset': '12', '_Offset_Type': 'Months', '_Offset_in_Month': '25', '_Offset_in_Month_Type': 'Day', '_Start_Date': '2005/12/25', '_Update_Date': '2006/02/25 03:10 PM', }) return self def list_prerequisite_row_indices(self, row_ndx): result = [] for tag in ['_Req1', '_Req2', '_Req3', '_Req4']: pred = self.get_row_value(row_ndx, tag) if pred in BLANK: pass else: ndx = self.find_row('_Handle', pred) if ndx == NA: pass else: result.append(ndx) return result def calc_float(self): dlg = DlgAssertion(parent=MAIN, msg= ''' Calculating Float Please wait. ''') dlg.show() topo = Topo() for (row_ndx, row) in enumerate(self): topo.insert(row_ndx) for (row_ndx, row) in enumerate(self): for pred_ndx in self.list_prerequisite_row_indices(row_ndx): topo.make_comparison(pred_ndx, row_ndx) list = topo.sort() if topo.has_loops: err = DlgAssertion(parent=MAIN, msg= ''' Sorting Prerequisites There are prerequisite loops. Some tasks depend directly or indirectly on their successors. ''') err.run() err.destroy() del topo # Save space? Give garbage collection something to do? tasks = TaskList(self) non_recurring = [] recurring = [] for row_ndx in list: if self.get_row_value(row_ndx, '_Offset') in BLANK: non_recurring.append(row_ndx) else: recurring.append(row_ndx) for row_ndx in non_recurring: preds = self.list_prerequisite_row_indices(row_ndx) tasks.insert(row_ndx, preds) tasks.calc_esd(row_ndx) non_recurring.reverse() for row_ndx in non_recurring: tasks.calc_lfd(row_ndx) tasks.update(row_ndx) next_occurrences = self.calc_next_occurrences() for row_ndx in recurring: if row_ndx in next_occurrences: self.set_row_value(row_ndx=row_ndx, tag= '_Earliest_Start', value= next_occurrences[row_ndx]) now = datetime.datetime.now() time_lo = now - datetime.timedelta(seconds=600) time_hi = now + datetime.timedelta(days=1) self.alarms = self.get_alarms(time_lo, time_hi) dlg.destroy() return self def calc_next_occurrences(self): date_lo = datetime.date.today() date_hi = date_lo + datetime.timedelta(days=1000) occurrences = self.get_occurrences(date_lo, date_hi) date_row = [] for (year_mo_da, events) in occurrences.iteritems(): for event in events: date_row.append([year_mo_da, event.row]) date_row.sort() result = {} for (year_mo_da, row_ndx) in date_row: if row_ndx in result: pass else: year = int(year_mo_da[ZERO:4], 10) month = int(year_mo_da[4:6], 10) day = int(year_mo_da[6:8], 10) date = datetime.date(year=year, month=month, day=day) result[row_ndx] = date return result def set_dirty(self, dirty=True): Relation.set_dirty(self, dirty=dirty) if self.is_dirty(): self.calc_float() if self.parent_matrix is None: pass else: row_ndx = self.parent_matrix.get_row_ndx() self.parent_matrix.conjure_rows(row_ndx) return self def load_data(self, file_name, must_exist=False): Relation.load_data(self, file_name, must_exist=must_exist) self.calc_float() return self def conjure_alarm_dlg(self): self.dlg_alarms = DlgAssertion(parent=MAIN) return self def cancel_time_out_check_alarms(self): if self.time_out_check_alarms_id is None: pass else: gobject.source_remove(self.time_out_check_alarms_id) self.time_out_check_alarms_id = None return self def call_back_check_alarms(self, *calling_sequence): now = datetime.datetime.now() half_minute = datetime.timedelta(seconds=30) time_lo = now - half_minute time_hi = now + half_minute on1 = True raise_alarm = False text = self.dlg_alarms.text text.clear() display_format = DATE_TIME_EDIT_FORMAT text.insert_markup('''Alarms %s ''' % now.strftime(display_format)) for (ndx, (time_stamp, row_ndx, has_rung)) in enumerate(self.alarms): earliest_start = self.get_row_value(row_ndx=row_ndx, tag= '_Earliest_Start') start_time = self.get_row_value(row_ndx=row_ndx, tag= 'Start_Time') if None in [earliest_start, start_time]: # 2008 Mar 28 pass else: sched = datetime.datetime.combine(earliest_start, start_time) handle = self.get_row_value(row_ndx=row_ndx, tag='_Handle', method='GetAsDisplayed') markup = 'At %s for\n %s — %s' % (time_stamp.strftime(display_format), sched.strftime(display_format), handle) if time_lo <= time_stamp <= time_hi and not has_rung and on1: (self.alarms)[ndx] = [time_stamp, row_ndx, True] on1 = False raise_alarm = True markup = '%s' % markup text.insert_markup('%s\n' % markup) if raise_alarm: self.sound_alarm() self.dlg_alarms.run() self.dlg_alarms.hide() self.squelch_alarm() self.cancel_time_out_check_alarms() self.time_out_check_alarms_id = gobject.timeout_add(30000, self.call_back_check_alarms) return False def sound_alarm(self, half_life=1400): self.call_back_ring_bell(half_life) return self def squelch_alarm(self): if self.time_out_ring_bell_id is None: pass else: gobject.source_remove(self.time_out_ring_bell_id) self.time_out_ring_bell_id = None return self def call_back_ring_bell(self, half_life, *calling_sequence): count = 5 while count > ZERO: gtk.gdk.beep() take_adeep_breath() time.sleep((half_life / 5) / 1000) count -= 1 self.squelch_alarm() self.time_out_ring_bell_id = gobject.timeout_add((half_life * 3) // 2, self.call_back_ring_bell, half_life) return False RELATION_CLASS_REGISTRY.stuff('Calendar', RelationCalendar, 'A Calendar is an appointments and to-do list.') class RelationBibliography(RelationWithLink): def __init__(self, name='Bibliography', tag='Biblio', file_name='~/.Tonto.Biblio', parent_matrix=None): RelationWithLink.__init__(self, name, tag=tag, file_name= file_name, parent_matrix=parent_matrix) self.set_help('Reference list of works cited') date_squib = \ 'Dates are year, month year, or day\n month year.' project = FieldString('Project').set_mod_lock(True).set_help('''This file may combine bibliographies from several research Projects. This field distiguishes each entry by the Project that generated it.''') library = FieldString('Library').set_mod_lock(True).set_help('''Library is the name of the collection where you found the reference. Specify this so you can remember and relocate the source.''') call_number = FieldString('Call Number').set_mod_lock(True).set_help("Call Number is the Library's\n catalog number for the source.") sort_key = FieldString('Sort Key', tag='_Key').set_mod_lock(True).set_help("""Sort Key is not part of the bibliographic reference but orders the references in the List of Works Cited. It may be left empty to be filled later. When filled, it ought to be unique because other entries may cross-reference it. Generally it is not long but contains enough of the author's Last Name followed by enough of the title to be unique. Omit leading words like A and The to sort titles correctly.""") entry_type = FieldCoded('Entry Type', default='Book').set_mod_lock(True).set_help('Entry Type tells what kind of\n Work this entry refers to.') entry_type.code_set = CodeSet() entry_type.code_set.append(Code('Book', as_displayed= 'Book, Pamphlet, Chart, Map, or Web Site')) entry_type.code_set.append(Code('Journal', as_displayed= 'Scholarly Journal')) entry_type.code_set.append(Code('NewsMag', as_displayed= 'Newspaper or Magazine')) entry_type.code_set.append(Code('Performance', as_displayed= 'Performance, Recording, Exhibition, Film, Software, Database, etc.')) article = FieldTitle('Article', tag='_Title', default='""').set_mod_lock(True).set_help('''Article is the significant minor portion (if any) of a larger Work from which the reference is drawn. It may be an article (but not a periodical), a TV episode (but not a program), a track (but not an album), or a Web page (but not a site). A subtitle may be included after a colon. Normally, this must be enclosed in quotes. Exceptions are an Article that was published independently, which is enclosed in <u></u> or a review (The title begins with Rev. of followed by the underlined Work followed by a comma and a space followed by the Work Editors.).''') fragment = FieldSelected('Fragment').set_mod_lock(True).set_help('''Fragment specifies that the Article is a specific part of the larger Work whether or not it has its own title.''') fragment.code_set = CodeSet() fragment.code_set.append(Code('Introduction', help= 'Introduction to a Book')) fragment.code_set.append(Code('Preface', help= 'Preface to a Book')) fragment.code_set.append(Code('Foreword', help= 'Foreword to a Book')) fragment.code_set.append(Code('Afterword', help= 'Afterword to a Book')) fragment.code_set.append(Code('Editorial', help= 'Editorial in a Periodical')) fragment.code_set.append(Code('Letter', help= 'Letter to the Editor of a Periodical or to You')) fragment.code_set.append(Code('Letter[ to recipient]')) fragment.code_set.append(Code('E-mail[ to recipient]', help= 'Electronic Mail')) fragment.code_set.append(Code('Online posting', help= 'Electronic Mail to a Discussion Group or Blog')) fragment.code_set.append(Code('Fwd. by [sender]. Online posting', help= 'Electronic Mail to a Discussion Group or Blog')) fragment.code_set.append(Code('Reply to letter of [subscriber]', 'Published Reply to a Letter to the Editor')) fragment.code_set.append(Code('Libretto', help= 'Lyrics to a Musical or Opera')) fragment.code_set.append(Code('Booklet', help= 'Explanatory Material Shrinkwrapped with a Recording')) fragment.code_set.append(Code('Liner notes', help= 'Explanatory Material Printed on the Jacket of a Recording')) fragment.code_set.append(Code('Interview')) fragment.code_set.append(Code('Interview with [interviewer]')) fragment.code_set.append(Code('Cartoon')) fragment.code_set.append(Code('Comic Strip')) fragment.code_set.append(Code('Advertisement')) fragment.code_set.append(Code('Abstract')) fragment.code_set.append(Code('Map')) fragment.code_set.append(Code('Chart')) fragment.code_set.append(Code('Address', help='A Speech')) fragment.code_set.append(Code('Lecture', help='A Speech')) fragment.code_set.append(Code('Keynote speech', help= 'The Opening Speech of a Conference or Convention')) fragment.code_set.append(Code('Reading of [Article]', help= 'A Performance of a Written Work')) web = FieldLink('Web', tag='_Web').set_mod_lock(True).set_help('''Web is the URL of a World-Wide Web page, if any, that is the reference source. If the source is a Usenet news group, precede the name of the group with news:.''') path = FieldString('Path').set_mod_lock(True).set_help('''Path may be Path: followed by a sequence of hypertext links (separated by semicolons) to follow (if any) from the Web URL. Alternatively, it may be Keyword: follwed by a single word used to access the source from a subscription service.''') when_accessed = FieldBiblioDateRange('When Accessed', tag= '_Traversed_Date').set_mod_lock(True).set_help('When Accessed is the date that the\n Web page was referenced. %s' % date_squib) government = FieldSelected('Government').set_mod_lock(True).set_help('For government publications, spell out the issuing\n Government.') government.code_set = CodeSet() government.code_set.append(Code('United States')) government.code_set.append(Code('Great Britain')) government.code_set.append(Code('Canada')) government.code_set.append(Code('United Nations')) government.code_set.append(Code('California')) government.code_set.append(Code('New York State')) agency1 = FieldSelected('Agency 1').set_mod_lock(True).set_help('''For government publications, provide short names for the various levels of issuing agencies, starting with the most inclusive Agency.''') agency1.code_set = CodeSet() agency1.code_set.append(Code('Cong.')) agency1.code_set.append(Code('Dept. of the Interior')) agency1.code_set.append(Code('Dept. of Commerce')) agency1.code_set.append(Code('Dept. of Labor')) agency1.code_set.append(Code('Dept. of State')) agency1.code_set.append(Code('Dept. of Health and Human Services')) agency2 = FieldSelected('Agency 2').set_mod_lock(True).set_help(agency1.get_help()) agency2.code_set = CodeSet() agency2.code_set.append(Code('Senate')) agency2.code_set.append(Code('House')) agency2.code_set.append(Code('Geological Survey')) agency2.code_set.append(Code('Census Bureau')) agency2.code_set.append(Code('National Oceanic and Atmospheric Administration (NOAA)')) agency2.code_set.append(Code('Centers for Disease Control and Prevention (CDC)')) agency3 = FieldString('Agency 3').set_mod_lock(True).set_help(agency1.get_help()) author_type = FieldSelected('Author Type').set_mod_lock(True).set_help('Author Type is the principal function of the\n author.') author_type.code_set = CodeSet() author_type.code_set.append(Code('By', as_displayed='By')) author_type.code_set.append(Code('Ed.', as_displayed= 'Edited by or editor')) author_type.code_set.append(Code('Trans.', as_displayed= 'Translated by or translator')) author_type.code_set.append(Code('Comp.', as_displayed= 'Compiled by or compiler')) author_type.code_set.append(Code('Music by', as_displayed= 'Composed by')) author_type.code_set.append(Code('Screenplay by', as_displayed= 'Screenplay by')) author_type.code_set.append(Code('Perf.', as_displayed= 'Performed by or performer')) author_type.code_set.append(Code('Narr.', as_displayed= 'Narrated by or narrator')) author_type.code_set.append(Code('Adapt.', as_displayed= 'Adaptation by or adapter')) author_type.code_set.append(Code('Introd.', as_displayed= 'Introduction by or introducer')) author_type.code_set.append(Code('Writ.', as_displayed= 'Written by or writer (of a performed piece)')) author_type.code_set.append(Code('Dir.', as_displayed= 'Directed by or director')) author_type.code_set.append(Code('Cond.', as_displayed= 'Conducted by or conductor')) author_type.code_set.append(Code('Orch.', as_displayed= 'Orchestrated by')) author_type.code_set.append(Code('Chor.', as_displayed= 'Choreographed by')) function_codes = Dstr(dt=', ') function_codes.extend([code.get_as_stored() for code in author_type.code_set]) author_type.code_set.append(Code('eds.', as_displayed='editors')) author_type.code_set.append(Code('gen. ed.', as_displayed= 'general editor')) author_type.code_set.append(Code('composer', as_displayed= 'composer')) author_type.code_set.append(Code('adapts.', as_displayed= 'adapters')) author_type.code_set.append(Code('et al.', as_displayed= 'more than three')) last_name1 = FieldTitle('Last Name 1').set_mod_lock(True).set_help("""This is the author's Last Name. If the author is an organization, use the corporate name, omit the leading article (A, An, The) if any, and leave First Name empty.""") first_name1 = FieldTitle('First Name 1').set_mod_lock(True).set_help("""This is the author's First Name. Famous classical authors don't always have a first name. Suffixes to the name (Jr.) appear after a comma after the First Name. If the work was published under a pseudonym, you may enclose the author's actual name in square brackets after his First Name.""") last_name2 = FieldTitle('Last Name 2').set_mod_lock(True).set_help("This is the second author's Last Name. If there\n are more than three, this should be et al.") first_name2 = FieldTitle('First Name 2').set_mod_lock(True).set_help("This is the author's First Name.") last_name3 = FieldTitle('Last Name 3').set_mod_lock(True).set_help("This is the third author's Last Name.") first_name3 = FieldTitle('First Name 3').set_mod_lock(True).set_help(first_name2.get_help()) article_editors = FieldTitle('Article Editors').set_mod_lock(True).set_help("""This is the first and last names of the editor(s) of the Article, if any. Prefix the name with an abbreviation of the editor's function: (%s). Editors that have the same function must be joined by the conjuction and (or by commas and the conjuction if there are more than two). Periods must separate editors that have different functions.""" % function_codes.get_as_string()) when_article_orig = FieldBiblioDateRange('When Article Orig').set_mod_lock(True).set_help('If significant, When Article Orig may specify\n the year that the Article was originally published.') cross_reference = FieldString('Cross-Reference').set_mod_lock(True).set_help("""A Cross-Reference to a collective work (anthology) with its own entry is the collective work's editor's last name (with leading initial or first name or trailing Work title if required to make the reference unique). The collective work must be alphabetized under the editor's last name.""") work = FieldTitle('Work', default='').set_mod_lock(True).set_help('''Work is the large source from which the reference is drawn. It may be a short name of a periodical (but not an article), a TV program (but not an episode), an album (but not a track), or a Web site (but not a page). A subtitle may be included after a colon. Normally, this must be enclosed in <u></u>, but there are exceptions: a musical composition identified by form, number, and key (not generally underlined unless you refer to its score); Home page; a course of study (followed by a period and a space followed by Course home page); an academic department (followed by a period and a space followed by Dept. home page); a trial case; a law or statute; a patent; an unpublished work; a journal published in more than one series (2nd ser. or 3rd ser. follows the underlined short name.); a local newspaper (City in square brackets follows the underlined name of the paper unless the name already includes the city.).''') conference = FieldString('Conference').set_mod_lock(True).set_help('''If this entry is from the proceedings of a conference, this is a short title of the Conference, its date, and its location. Be sure to provide only the parts that are left out of the title of the conference proceedings. If this entry is a legislative document, this is the convention number and legislature name, session, and document number.''') work_editors = FieldTitle('Work Editors').set_mod_lock(True).set_help("""This is the first and last names of the editor(s) of the Work, if any. Prefix the name with an abbreviation of the editor's function (%s). An exception is gen. ed. that follows a comma after the name. Editors that have the same function must be joined by the conjuction and (or by commas and the conjuction if there are more than two). Periods must separate editors that have different functions. Omit this altogether for standard reference works.""" % function_codes.get_as_string()) when_work_orig = FieldBiblioDateRange('When Work Orig').set_mod_lock(True).set_help('If significant, When Work Orig may specify the\n year that the Work was originally published.') edition = FieldSelected('Edition').set_mod_lock(True).set_help('''Edition designates a revision of a book, a version of a Web page, or a morning or afternoon edition of a newspaper. If the same Edition of a newpaper contains duplicate page numbers in separate sections, add a comma and a space followed by sec. followed by the section number.''') edition.code_set = CodeSet() edition.code_set.append(Code('2nd ed.', help='Second Edition')) edition.code_set.append(Code('3rd ed.', help='Third Edition')) edition.code_set.append(Code('4th ed.', help='Fourth Edition')) edition.code_set.append(Code('Rev. ed.', help='Revised Edition')) edition.code_set.append(Code('Abr. ed.', help='Abridged Edition')) edition.code_set.append(Code('Vers. 1.1', help='Version 1.1')) edition.code_set.append(Code('natl. ed.', help= 'National Edition')) edition.code_set.append(Code('late ed.', help='Late Edition')) volume = FieldSelected('Volume').set_mod_lock(True).set_help('''For a multivolume work, Volume tells which portion the reference is from. If refering to more than one volume, provide the number of volumes. For a journal, it indicates what date range the pages are in. This does not apply to other periodicals. Omit this for encyclopedias and dictionaries. ''') volume.code_set = CodeSet() volume.code_set.append(Code('Vol. 1')) volume.code_set.append(Code('Vol. 2')) volume.code_set.append(Code('Vol. 3')) volume.code_set.append(Code('2 vols.')) volume.code_set.append(Code('3 vols.')) volume.code_set.append(Code('4 vols.')) issue = FieldString('Issue').set_mod_lock(True).set_help('''Issue number must appear in addition to Volume only if a journal is not continuously paginated.''') dissertation_type = FieldSelected('Dissertation Type').set_mod_lock(True).set_help('Dissertation Type is the type of paper.') dissertation_type.code_set = CodeSet() dissertation_type.code_set.append(Code('Diss.')) dissertation_type.code_set.append(Code('MA thesis')) dissertation_type.code_set.append(Code('MS thesis')) dissertation = FieldString('Dissertation').set_mod_lock(True).set_help('''Dissertation is the name of the degree-granting university followed by a comma and a space followed by the year of the Dissertation.''') series = FieldTitle('Series').set_mod_lock(True).set_help('''If the Work is part of a published Series, this is the short Series name followed by the number of the Work in the Series. If the Work is part of a Series of performances, this is the short Series name or the name of the group that is performing. If the group is listed as author, Series may be Concert. If the Work is supplied by a subscription service, Series may be the name of the service.''') series_functionaries = FieldTitle('Series Functionaries').set_mod_lock(True).set_help('''This is the first and last names of those who serve as functionaries of the Series, if any. Prefix the name with an abbreviation of the function (%s).''' % function_codes.get_as_string()) city_publisher = FieldTitle('City Publisher').set_mod_lock(True).set_help("""City Publisher for print sources is a short city name followed by a colon and a space followed by a short company name. If the city or publisher is known but not in the source, place the name in square brackets. If the city or publisher is controversial, place ? in the brackets after the name. If the city is unknown, use N.p. before the colon. If the publisher is unknown, use n.p. after the colon. The colon and publisher may be omitted for Works published before 1900. A publisher's imprint may precede a hyphen before the publisher's name. Omit this altogether for standard reference works. Semicolons must separate multiple City Publishers.""") medium = FieldSelected('Medium').set_mod_lock(True).set_help('''Medium for recordings is the kind of recording. Also, it may indicate a peculiar kind of printed material. For software or data, Rel. precedes a version or release number.''') medium.code_set = CodeSet() medium.code_set.append(Code('CD', help='Audio Compact Disc')) medium.code_set.append(Code('CD-ROM', help= 'Software Compact Disc Read-Only Memory')) medium.code_set.append(Code('Film')) medium.code_set.append(Code('DVD', help='Digital Video Disc')) medium.code_set.append(Code('Audiocassette')) medium.code_set.append(Code('Audiotape')) medium.code_set.append(Code('Videocassette')) medium.code_set.append(Code('LP', help= 'Long-Play Phonograph Record')) medium.code_set.append(Code('Slide program')) medium.code_set.append(Code('Laser disc', help='Video Disc')) medium.code_set.append(Code('Filmstrip')) medium.code_set.append(Code('Sound filmstrip')) medium.code_set.append(Code('Chart')) medium.code_set.append(Code('Map')) medium.code_set.append(Code('Electronic')) pieces = FieldSelected('Pieces').set_mod_lock(True).set_help('''If more than one, specify the number of Pieces of a medium that comprise the Work or the number of the piece used.''') pieces.code_set = CodeSet() pieces.code_set.append(Code('2 discs')) pieces.code_set.append(Code('Disk 2')) manufacturer = FieldTitle('Manufacturer').set_mod_lock(True).set_help('Manufacturer for recordings is the short name of\n the publisher.') network = FieldTitle('Network').set_mod_lock(True).set_help('Network for a broadcast performance is a\n short network name.') venue_city = FieldTitle('Venue City').set_mod_lock(True).set_help("""Venue City for broadcast performances is a TV or radio station's call letters followed by a comma and a space followed by a short city name. Place the broadcast date in When Published. For live performances and exhibitions, it is a venue followed by a comma and a space followed by a short city name if the city is not already part of the venu name. Place the performance date in When Published.""") sponsor = FieldTitle('Sponsor').set_mod_lock(True).set_help('Sponsor for electronic sources is a short\n organization name.') when_recorded = FieldBiblioDateRange('When Recorded').set_mod_lock(True).set_help('''If significant, When Recorded may specify the date of a recording. Proceed this with Rec. for audio recordings.''') when_published = FieldBiblioDateRange('When Published').set_mod_lock(True).set_help('''When Published is a date. %s Give only the year for a book or continuously paginated journal. If the date is known but not in the source, place the date in square brackets. If the date is approximate, place c. in the brackets before the date. If the date is controversial, place ? in the brackets after the date. If the date is unknown, place n.d. in this field. For a multivolume Work, this may be a range of years separated by a hyphen. For a performed program, this may be a day-day, day month-day month, or day month year-day month year range.''' % date_squib) pages = FieldString('Pages').set_mod_lock(True).set_help('''Pages is the number of the page on which an Article begins, a hyphen, and the number of the page on which the Article ends. Suppress the portion of the ending page that is the same as the beginning page except the last two digits. If the page numbers are not consecutive, follow the beginning page with a + and omit the ending page. Commas separate page ranges for each installment of a serialized Article by the same author under the same title. When a subsequent installment crosses into a different Volume of a journal, set off the Volume by a semicolon. Follow it by the Publication Date in parens followed by a colon and a space followed by the page range for that installment. If the Work is not paginated, use N.pag.. If referring to an item in an abstracts journal, insert item before the number. If referring to an article on microfiche, use, for example, fiche 1, grids A8-11. If referring to an article in a loose-leaf collection, insert Art. before the number. Omit this for entries that are not Articles if the Work is paginated. Omit this for encyclopedias and dictionaries.''') collection_type = FieldSelected('Collection Type').set_mod_lock(True).set_help("""Some kinds of entries depend on links to others. Collection Type specifies the kind of link. Collection Link specifies the linked entry. An article reprinted in a more recent collection (Rpt. in) depends on an anthology. An article reprinted under a new name (Rpt. of) depends on the original. A translation (Trans. of) depends on an original. A named volume (Vol. x of) includes its own publisher info but depends on an entry for the complete work, which specifies the number of Volumes and their inclusive dates. A published letter (Letter [item number] of) entitled 'To [Recipient]' depends on a collective work. Its When Published should contain the date of the letter. A special issue of a journal (Spec. issue of) depends on an entry for the journal publication info. Other entries (Link to ) depend on collective works: abstracts; articles in loose-leaf, microforms, or online collections; photos of paintings or sculpture (which should cite the exhibition of the original and link to the collective work that is the source of the photo).""") collection_type.code_set = CodeSet() collection_type.code_set.append(Code('Rpt. in', help= 'Reprinted In')) collection_type.code_set.append(Code('Rpt. of', help= 'Reprint Of')) collection_type.code_set.append(Code('Trans. of', help= 'Translation Of')) collection_type.code_set.append(Code('Vol. 1 of')) collection_type.code_set.append(Code('Vol. 2 of')) collection_type.code_set.append(Code('Vol. 3 of')) collection_type.code_set.append(Code('Link to', help= 'Continuation of Entry')) collection_type.code_set.append(Code('Spec. issue of', help= 'Link to Periodical')) collection_type.code_set.append(Code('Letter [item number] of', help='Link to Collected Letters')) collection_link = FieldString('Collection Link').set_mod_lock(True).set_help('''Collection Link is the Sort Key of a separate entry for a Work, which should have no author information. Be sure that Work has a different Project so that it is not itself included in the List of Works Cited with this entry.''') remark = FieldString('Remark').set_mod_lock(True).set_help('''Remark is an annotation to a bibliographic entry. Use Remark to denote that an Article in a periodical is part of a Series. For example: Pt. 2 of a series, Series Title Not Underlined, begun 9 Sep. 1999. Use it to denote that the entry is a Transcript of a performance. Use it to record the catalog number and location of a manuscript. For example: Ms 91. Dean and Chapter Lib., Lincoln, Eng.%s''' % MARKUP_SQUIB) accession_date = FieldDateTime('Accession Date', tag= '_Accession_Date').set_mod_lock(True).set_enabled(False).set_help('''Accession Date cannot be changed manually. Instead, automatically, it keeps track of how long this reference has been part of the Project.''') update_date = FieldDateTime('Update Date', tag='_Update_Date').set_mod_lock(True).set_enabled(False).set_help('''Update Date cannot be changed manually. Instead, automatically, it keeps track of when the last change to this entry was made.''') keywords = FieldString('Keywords').set_mod_lock(True).set_help('''Keywords is a list of word forms that do not exactly occur in any of the other parts of this entry but that you want to remember this citation by. You can search by Keyword to find this entry. You may wish to separate Keywords with semicolons.''') self.column_list.bump_page() self.column_list.append(project) self.column_list.append(library) self.column_list.append(call_number) self.column_list.append(sort_key) self.column_list.append(entry_type) self.column_list.append(article) self.column_list.append(fragment) self.column_list.append(web) self.column_list.append(path) self.column_list.append(when_accessed) self.column_list.bump_page() self.column_list.append(government) self.column_list.append(agency1) self.column_list.append(agency2) self.column_list.append(agency3) self.column_list.append(author_type) self.column_list.append(last_name1) self.column_list.append(first_name1) self.column_list.append(last_name2) self.column_list.append(first_name2) self.column_list.append(last_name3) self.column_list.append(first_name3) self.column_list.append(article_editors) self.column_list.append(when_article_orig) self.column_list.bump_page() self.column_list.append(cross_reference) self.column_list.append(work) self.column_list.append(conference) self.column_list.append(work_editors) self.column_list.append(when_work_orig) self.column_list.append(edition) self.column_list.append(volume) self.column_list.append(issue) self.column_list.append(dissertation_type) self.column_list.append(dissertation) self.column_list.append(series) self.column_list.append(series_functionaries) self.column_list.bump_page() self.column_list.append(city_publisher) self.column_list.append(medium) self.column_list.append(pieces) self.column_list.append(manufacturer) self.column_list.append(network) self.column_list.append(venue_city) self.column_list.append(sponsor) self.column_list.append(when_recorded) self.column_list.append(when_published) self.column_list.bump_page() self.column_list.append(pages) self.column_list.append(collection_type) self.column_list.append(collection_link) self.column_list.append(remark) self.column_list.append(accession_date) self.column_list.append(update_date) self.column_list.append(keywords) self.display_tags.append(project.get_tag()) self.display_tags.append(sort_key.get_tag()) self.display_tags.append(keywords.get_tag()) return def view_as_text(self, parent, row_ndx, dlg_type=DlgBiblioViewAsText): Relation.view_as_text(self, parent=parent, row_ndx=row_ndx, dlg_type=dlg_type) return self RELATION_CLASS_REGISTRY.stuff('Bibliography', RelationBibliography, 'A Bibliography is a\n reference list of works cited.') class RelationPasswords(Relation): def __init__(self, name='Password List', tag='Passwords', file_name='~/.Tonto.PasswordList', parent_matrix=None): Relation.__init__(self, name, tag=tag, file_name=file_name, parent_matrix=parent_matrix) self.set_help('Repository of Passwords and User IDs used in other applications') vendor = FieldString('Vendor').set_help('Vendor is the manufacturer, publisher, or\n provider of a Service that demands a password.') service = FieldString('Service').set_help("""Service is the segment of a Vendor's product that demands a Password. For example, an Internet Service Provider (ISP) may require separate Passwords to dial up and to retrieve eMail.""") web = FieldLink('Web').set_help("""Web is the URL of the Vendor's Account-Maintenance Web page (if any) where you would go to change your User Id, Password, or Account.""") e_mail = FieldEmailAddr('eMail').set_help('''eMail is the electronic mail address (if any) where you expect to receive correspondence about your User ID or Account. Many people have more than one address. When one of your eMail addresses changes, you can then find Vendors you need to notify. When you receive eMail from an unexpected source, you can trace it to the Vendor who leaked the address.''') user_id = FieldString('User ID').set_help('''User ID is your identity while using the Service. The Vendor combines your User ID with your Password to verify that you are who you say you are before allowing you to use his Service.''') password_type = FieldSelected('Password Type').set_help('''Password Type tells how to use the following Password. Usually, it is Password, Customer-ID, or PIN, but it could be anything.''') password_type.code_set = CodeSet() password_type.code_set.append(Code('Password')) password_type.code_set.append(Code('Customer-ID')) password_type.code_set.append(Code('PIN')) password = FieldString('Password').set_help("""Password is a secret code or pass phrase demanded by the Service. Here it is stored in all its plaintext, unhashed glory so you can read it easily in case you forget it. Further, you don't have to remember a hash code and provide that to do so. Obviously, this file needs to be kept away from prying eyes. Your operating environment may provide the means to protect this file from unauthorized access or even to encrypt it, but these techniques are far beyond the scope of Tonto. Consider saving this file on removable media and storing it in a safe place.""") account = FieldString('Account').set_help("""Account is the identity of your client (if any). The Vendor bills the Account for his service. Usually, though, you and the client are the same, and you get the bill directly. In this case, your User ID is good enough, and you don't have to provide a separate Account Number.""") project_type = FieldString('Project Type').set_help('''Project Type tells how to use the following Project ID. Usually, a Project is as a refinement of Account, but it could be called anything.''') project = FieldString('Project').set_help('''Project is any subdivision of Account Number. Perhaps you are working on multiple Projects for a client and billing him for the time you spend online. Perhaps he wants his Internet bill itemized by Project.''') challenge = FieldString('Challenge').set_help("""Some secure login schemes will, from time to time, challenge a client to provide additional information to confirm identity. This takes the form of a question that only the client can answer, such as What is your mother's maiden name? What is your cat's birthday? What is your favorite color? The login scheme is constrained by the client's pre-arranged choice of a Challenge question, which he provides during registration with a Vendor and which can be recorded here. Alternatively, this field may record the type of any other piece of identifying information beside User ID, Password, Account, and Project that a login scheme may require.""") response = FieldString('Response').set_help('''Response is the pre-arranged, correct answer to the Challenge question or any other additional piece of identifying information that a login scheme may require beyond User ID, Password, Account, or Project.''') accession_date = FieldDateTime('Accession Date', tag= '_Accession_Date').set_mod_lock(True).set_enabled(False).set_help('''Accession Date cannot be changed manually. Instead, automatically, it keeps track of how old this Password is.''') update_date = FieldDateTime('Update Date', tag='_Update_Date').set_mod_lock(True).set_enabled(False).set_help('''Update Date cannot be changed manually. Instead, automatically, it keeps track of when the last change to this Password was made.''') expiration_date = FieldDate('Expiration Date').set_help('Expiration Date, if any, keeps track of when the\n Password must be renewed.') account_status = FieldSelected('Account Status').set_help('Account Status may be Active, Inactive, or\n Closed, but it could be anything.') account_status.code_set = CodeSet() account_status.code_set.append(Code('Active')) account_status.code_set.append(Code('Inactive')) account_status.code_set.append(Code('Closed')) keywords = FieldString('Keywords').set_help('''Keywords is a list of word forms that do not exactly occur in any of the other parts of this entry but that you want to remember this Password by. You can search by Keyword to find this Password. You may wish to separate Keywords with semicolons.''') remarks = FieldLog('Remarks').set_help('Remarks is any freeform text that applies to\n the User ID, Password, or Account.') self.column_list.bump_page() self.column_list.append(vendor) self.column_list.append(service) self.column_list.append(web) self.column_list.append(e_mail) self.column_list.append(user_id) self.column_list.append(password_type) self.column_list.append(password) self.column_list.bump_page() self.column_list.append(account) self.column_list.append(project_type) self.column_list.append(project) self.column_list.append(challenge) self.column_list.append(response) self.column_list.bump_page() self.column_list.append(accession_date) self.column_list.append(update_date) self.column_list.append(expiration_date) self.column_list.append(account_status) self.column_list.append(keywords) self.column_list.append(remarks) self.display_tags.append(vendor.get_tag()) self.display_tags.append(service.get_tag()) self.display_tags.append(user_id.get_tag()) return RELATION_CLASS_REGISTRY.stuff('Password List', RelationPasswords, '''A Password List tracks User IDs and Accounts that authorize Services from Vendors.''') class Relation3X5Cards(RelationWithLink): def __init__(self, name='3x5 Note Cards', tag='3x5 Cards', file_name='~/.Tonto.3x5', parent_matrix=None): RelationWithLink.__init__(self, name, tag=tag, file_name= file_name, parent_matrix=parent_matrix) self.set_help('3x5 Note-Card File') category = FieldString('Category').set_help('Each card in the list belongs in a Category, but\n Category may be anything.') title = FieldTitle('Title', tag='_Title').set_help('Each card may have a Title.') web = FieldLink('Web', tag='_Web').set_mod_lock(True).set_help('''Web is the URL of a World-Wide Web page, if any, that the card describes or that is the authority for the card.''') traversed = FieldDateTime('Traversed Date', tag= '_Traversed_Date').set_mod_lock(True).set_enabled(False).set_help('''The Traversed Date cannot be changed manually. Instead, it is updated automatically whenever a traversal of the Web link is made.''') remark = FieldLog('Remark').set_help('Remark is any freeform text.%s' % MARKUP_SQUIB) accession_date = FieldDateTime('Accession Date', tag= '_Accession_Date').set_mod_lock(True).set_enabled(False).set_help('''Accession Date cannot be changed manually. Instead, automatically, it keeps track of how old this card is.''') update_date = FieldDateTime('Update Date', tag='_Update_Date').set_mod_lock(True).set_enabled(False).set_help('''Update Date cannot be changed manually. Instead, automatically, it keeps track of when the last change to this card was made.''') keywords = FieldString('Keywords').set_help('''Keywords is a list of word forms that do not exactly occur in any of the other parts of this card but that you want to remember this notation by. You can search by Keyword to find this card. You may wish to separate Keywords with semicolons.''') self.column_list.bump_page() self.column_list.append(category) self.column_list.append(title) self.column_list.append(web) self.column_list.append(traversed) self.column_list.append(remark) self.column_list.append(accession_date) self.column_list.append(update_date) self.column_list.append(keywords) self.display_tags.append(category.get_tag()) self.display_tags.append(update_date.get_tag()) self.display_tags.append(title.get_tag()) return def view_as_text(self, parent, row_ndx, dlg_type=Dlg3X5ViewAsText): Relation.view_as_text(self, parent=parent, row_ndx=row_ndx, dlg_type=dlg_type) return self RELATION_CLASS_REGISTRY.stuff('3x5 Note Cards', Relation3X5Cards, '''A 3x5 Note-Card File emulates a shoebox full of notes scribbled on individual scraps of paper. Each note may have an Internet Uniform Resource Locator (URL) and serve as a browser bookmark or an HTML favorite.''') class RelationAddressList(Relation): def __init__(self, name='Address List', tag='_Addresses', file_name='~/.Tonto.adr', parent_matrix=None): Relation.__init__(self, name, tag=tag, file_name=file_name, parent_matrix=parent_matrix) self.set_help('Address and Phone List') listing_type = FieldSelected('Listing Type').set_help('Listing Type tells how to use the following\n address.') listing_type.code_set = CodeSet() listing_type.code_set.append(Code('Work')) listing_type.code_set.append(Code('Home')) greeting = FieldString('Greeting').set_mod_lock(True).set_help('''Greeting is the polite title by which a person is addressed (after the word Dear) within a letter — formally Mr. or Ms. and his Last Name — informally his First Name or nickname only. When writing to an unknown person within a Company, it may be Sirs, Sir, or Madam. This will be useful if you export your address list to a word processor for generating form letters; otherwise, you may leave it blank.''') polite_mode = FieldSelected('Polite Mode').set_mod_lock(True).set_help("""Polite Mode is the method of polite address written before the person's full name on an envelope. """) polite_mode.code_set = CodeSet() polite_mode.code_set.append(Code('Mr.')) polite_mode.code_set.append(Code('Ms.')) polite_mode.code_set.append(Code('Mrs.')) polite_mode.code_set.append(Code('Miss')) polite_mode.code_set.append(Code('Dr.')) polite_mode.code_set.append(Code('Rev.')) first_name = FieldString('First Name').set_mod_lock(True).set_help("""First Name is all a person's names except his surname. You may include first and middle, first and middle initial, or first only. Families are a special case. First Name may be a couple like Dick and Jane. On the other hand, First Name may be just the head of household. Then you can append Family to the Last Name.""") last_name = FieldString('Last Name').set_mod_lock(True).set_help("""Last Name is a person's surname. When you sort the list, you may wish to use this as a key along with Company.""") title = FieldString('Title').set_mod_lock(True).set_help('''Title may be a professional degree such as MD, but it is commonly the name of the position the person holds within a Company. Try to include it when writing to a Company so your mail will be directed appropriately even if the person no longer works there.''') company = FieldString('Company').set_mod_lock(True).set_help("""Company is the name of the business where the person works. Try to include it when writing to the person at his office. When writing to an unknown person at a Company, omit the person's names but include his Title if possible. When writing to a person at home, omit the Company.""") dept_mail_stop = FieldString('Dept / Mail Stop', tag= 'Dept_Mail_Stop').set_mod_lock(True).set_help('''Dept / Mail Stop is required by organizations large enough to occupy several buildings within a campus. Usually, it is a building and room number.''') locus = FieldString('Locus').set_mod_lock(True).set_help('''Locus may be an additional organizational identifier such as the name of a university when the name of the department where the person works occupies the Company slot.''') street = FieldString('Street').set_mod_lock(True).set_help('''Street is the last line of the address before City. According to postal regulation and custom, the mailman is required to look only one line above the City and no higher. Any lines above that are purely decorative so far as the Post Office is concerned, and interpretation of such extraneous routing information is up to clerks and secretaries within the destination organization, not the postal service.''') city = FieldString('City').set_mod_lock(True).set_help('City is the name of the destination Post Office.') state = FieldStateProvince('State').set_mod_lock(True).set_help('''State is the postal-service approved, two-character state code. There are approved codes for US possessions and protectorates and Canadian provinces and territories. Other countries have their own district codes that may or may not be two characters.''') zip = FieldString('Zip').set_mod_lock(True).set_help('''Zip is a postal-service routing code. In the US, this may be five or nine digits or more. In Canada, it is two groups of three alphanumeric characters. Other countries have their own schemes. When printing mail, you may wish to sort on this key.''') country = FieldString('Country').set_mod_lock(True).set_help('Country is the destination nation. It is\n usually left blank on domestic mail.') latitude = FieldString('Latitude').set_help('Latitude is in decimal degrees, if known.') longitude = FieldString('Longitude').set_help('Longitude is in decimal degrees, if known.') e_mail = FieldEmailAddr('eMail').set_mod_lock(True).set_help("eMail is the person's electronic mail address, if\n he has one.") web = FieldLink('Web').set_mod_lock(True).set_help("Web is the URL of the person's World-Wide Web\n site, if any.") phone_type1 = FieldSelected('Phone Type 1').set_mod_lock(True).set_help('''Phone Type tells how to use the following Phone number. Usually, it is home, work, cell, or FAX, depending on what type of device it connects with and whether it is for personal or business use, but it could be anything.''') phone_type1.code_set = CodeSet() phone_type1.code_set.append(Code('Home')) phone_type1.code_set.append(Code('Work')) phone_type1.code_set.append(Code('Cell')) phone_type1.code_set.append(Code('FAX')) phone_type1.code_set.append(Code('Pager')) phone1 = FieldPhone('Phone 1').set_mod_lock(True).set_help('''Phone is the number to dial. It may be any length and may include hyphens and parentheses for readability. It should include leading long-distance and foreign access codes and trailing PBX extensions. It may contain delay characters (such as commas) for pacing autodialers.''') phone_type2 = FieldSelected('Phone Type 2').set_mod_lock(True).set_help(phone_type1.get_help()) phone_type2.code_set = phone_type1.code_set # Same list, not a copy. phone2 = FieldPhone('Phone 2').set_mod_lock(True).set_help(phone1.get_help()) phone_type3 = FieldSelected('Phone Type 3').set_mod_lock(True).set_help(phone_type1.get_help()) phone_type3.code_set = phone_type1.code_set phone3 = FieldPhone('Phone 3').set_mod_lock(True).set_help(phone1.get_help()) phone_type4 = FieldSelected('Phone Type 4').set_mod_lock(True).set_help(phone_type1.get_help()) phone_type4.code_set = phone_type1.code_set phone4 = FieldPhone('Phone 4').set_mod_lock(True).set_help(phone1.get_help()) accession_date = FieldDateTime('Accession Date', tag= '_Accession_Date').set_mod_lock(True).set_enabled(False).set_help('''Accession Date cannot be changed manually. Instead, automatically, it keeps track of how old this address is.''') update_date = FieldDateTime('Update Date', tag='_Update_Date').set_mod_lock(True).set_enabled(False).set_help('''Update Date cannot be changed manually. Instead, automatically, it keeps track of when the last change to this address was made.''') keywords = FieldString('Keywords').set_help('''Keywords is a list of word forms that do not exactly occur in any of the other parts of this address but that you want to remember this address by. You can search by keyword to find this address. You may wish to separate Keywords with semicolons.''') remarks = FieldLog('Remarks').set_help('Remarks is any freeform text that applies to\n the address.') self.column_list.bump_page() self.column_list.append(listing_type) self.column_list.append(greeting) self.column_list.append(polite_mode) self.column_list.append(first_name) self.column_list.append(last_name) self.column_list.append(title) self.column_list.bump_page() self.column_list.append(company) self.column_list.append(dept_mail_stop) self.column_list.append(locus) self.column_list.append(street) self.column_list.append(city) self.column_list.append(state) self.column_list.append(zip) self.column_list.append(country) self.column_list.append(latitude) self.column_list.append(longitude) self.column_list.bump_page() self.column_list.append(phone_type1) self.column_list.append(phone1) self.column_list.append(phone_type2) self.column_list.append(phone2) self.column_list.append(phone_type3) self.column_list.append(phone3) self.column_list.append(phone_type4) self.column_list.append(phone4) self.column_list.bump_page() self.column_list.append(e_mail) self.column_list.append(web) self.column_list.append(accession_date) self.column_list.append(update_date) self.column_list.append(keywords) self.column_list.append(remarks) self.display_tags.append(last_name.get_tag()) self.display_tags.append(first_name.get_tag()) self.display_tags.append(company.get_tag()) self.display_tags.append(phone1.get_tag()) return def view_as_text(self, parent, row_ndx, dlg_type=DlgAddressViewAsText): Relation.view_as_text(self, parent=parent, row_ndx=row_ndx, dlg_type=dlg_type) return self def free_form_entry(self, parent, row_ndx): self.stage_row(row_ndx) dlg = DlgAddressFreeFormEntry(parent=parent, relation=self, markup='''Paste contact info here. ''') result = quarrantine_invalid_data(dlg=dlg, method=dlg.destage_tag_list, method_calling_sequence=[], title='Freeform Entry') dlg.destroy() if result in [gtk.RESPONSE_OK]: self.destage_row(row_ndx) self.set_dirty() return self RELATION_CLASS_REGISTRY.stuff('Address List', RelationAddressList, '''An Address List has predefined fields such as city, state, zip, and phone number.''') RELATION_CLASS_NAME = RELATION_CLASS_REGISTRY.name_by_type() RELATION_CLASS = RELATION_CLASS_REGISTRY.type_by_name() class RelationList(list): def __init__(self, config): list.__init__(self) self.config = config on1 = self.config.get('GLOBAL', 'on1') == '1' tags = [(self.config.get(tag, 'seq'), tag) for tag in self.config if tag not in ['GLOBAL', 'MODEM']] tags.sort() for (key, tag) in tags: file_name = self.config.get(tag, 'file') name = self.config.get(tag, 'name') class_of = RELATION_CLASS[self.config.get(tag, 'type', RelationUserDefined)] try: self.open( file_name=file_name, name=name, tag=tag, class_of=class_of, mod_config=False, on1=on1, ) except (ErrorNoColHeaders, ErrorBadDictionary, ErrorBadData), \ __Error: dlg = DlgAssertion(parent=None, msg= ''' Recovering Configuration %s This program encountered an error trying to load a data file. The data was missing, incomplete, damaged, or otherwise bad, invalid or illegible. The file was skipped. ''' % __Error) dlg.run() dlg.destroy() return def get_tags(self): result = [relation.get_tag() for relation in self] return result def find(self, tag): for relation in self: if relation.get_tag() == tag: return relation return None def open(self, file_name=USER_FILE_DEFAULTS['file_name'], name=USER_FILE_DEFAULTS['name'], tag=USER_FILE_DEFAULTS['tag'], class_of=RelationUserDefined, mod_config=True, on1=False, ): def is_file_open(file_name): for tag in self.config: if file_name == self.config.get(tag, 'file'): return True return False relation = load_relation( file_name=file_name, name=name, tag=tag, class_of=class_of, must_exist=not on1, ) if mod_config: file_name = relation.file_name name = relation.name tag = relation.tag class_of = RELATION_CLASS_NAME[type(relation)] if tag in self.config: raise ErrorDuplicateTag('Tag "%s" is already open.' % tag) if is_file_open(file_name): raise ErrorDuplicateFile('File "%s" is already open.' % file_name) self.config.set(tag, 'seq', datetime.datetime.now().strftime('%Y/%m/%d-%H:%M:%S')) self.config.set(tag, 'file', file_name, checkpoint=False) self.config.set(tag, 'name', name, checkpoint=False) self.config.set(tag, 'type', class_of) self.append(relation) return self def revert(self, ndx): rel = self[ndx] self[ndx] = load_relation( file_name=rel.file_name, name=rel.name, tag=rel.tag, class_of=type(rel), must_exist=True, ) return self def close(self, ndx, mod_config=True): relation = self.pop(ndx) if relation.is_dirty(): relation.save() if mod_config: tag = relation.get_tag() self.config.remove(tag, checkpoint=True) relation.destroy() return self def close_all(self, mod_config=False): while len(self) > ZERO: self.close(ZERO, mod_config=mod_config) return self def save_as(self, ndx, file_name, rename=False): self[ndx].save(file_name=file_name, rename=rename) if rename: tag = self[ndx].get_tag() self.config.set(tag, 'file', __FileName) return self class TagHot(gtk.TextTag): def call_back_enter_notify(self, text_view, event, *calling_sequence): return False def call_back_leave_notify(self, text_view, event, *calling_sequence): return False class TagItalic(TagHot): def __init__(self, name=None, iter=None): TagHot.__init__(self, name=name) self.set_property('style', pango.STYLE_ITALIC) return class TagBold(TagHot): def __init__(self, name=None, iter=None): TagHot.__init__(self, name=name) self.set_property('weight', pango.WEIGHT_BOLD) return class TagUnderline(TagHot): def __init__(self, name=None, iter=None): TagHot.__init__(self, name=name) self.set_property('underline', pango.UNDERLINE_SINGLE) return class TagMonospace(TagHot): def __init__(self, name=None, iter=None): TagHot.__init__(self, name=name) self.set_property('family', 'Monospace') return class TagAnchor(TagHot): def __init__(self, name=None, iter=None, href=None, relation=None, row_ndx=None): self.href = href self.relation = relation self.row_ndx = row_ndx TagHot.__init__(self, name=name) self.set_property('foreground-gdk', MAIN_COLORS.get_color('blue')) self.set_property('underline', pango.UNDERLINE_SINGLE) self.connect('event', self.call_back_event) return def call_back_event(self, tag, text_view, event, iter, *calling_sequence): if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: open_url(self.href, relation=self.relation, row_ndx=self.row_ndx) return True return False def call_back_enter_notify(self, text_view, event, *calling_sequence): text_view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) return False def call_back_leave_notify(self, text_view, event, *calling_sequence): text_view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(None) return False class TagDial(TagHot): def __init__(self, name=None, iter=None, sequence=None): self.sequence = sequence TagHot.__init__(self, name=name) self.set_property('foreground-gdk', MAIN_COLORS.get_color('green4')) self.connect('event', self.call_back_event) return def call_back_event(self, tag, text_view, event, iter, *calling_sequence): if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: dlg = DlgDial(parent=MAIN, phone_number=self.sequence) dlg.run() dlg.destroy() return True return False def call_back_enter_notify(self, text_view, event, *calling_sequence): text_view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) return False def call_back_leave_notify(self, text_view, event, *calling_sequence): text_view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(None) return False class TagScale(TagHot): def __init__(self, name=None, iter=None, factor=1.0): TagHot.__init__(self, name=name) tags = iter.get_tags() for tag in tags: if tag.get_property('scale-set'): factor *= tag.get_property('scale') self.set_property('scale', factor) return class TagBig(TagScale): def __init__(self, name=None, iter=None, factor=1.2): TagScale.__init__(self, name=name, iter=iter, factor=factor) return class TagSmall(TagScale): def __init__(self, name=None, iter=None, factor=0.83299999999999996): TagScale.__init__(self, name=name, iter=iter, factor=factor) return class TagCartouche(TagHot): def __init__(self, name=None, iter=None, factor=0.83299999999999996): TagHot.__init__(self, name=name) self.set_property('scale', factor) self.set_property('foreground-gdk', MAIN_COLORS.get_color('indian red')) return class TagSpan(TagHot): def __init__(self, name=None, fg=None, bg=None): TagHot.__init__(self, name=name) if fg is None: pass else: self.set_property('foreground', fg) if bg is None: pass else: self.set_property('background', bg) return class Selection(object): def __init__(self, buffer): self.buffer = buffer self.checkpoint() return def set_as_mark(self, mark_cursor, mark_bound): self.mark_cursor = mark_cursor self.mark_bound = mark_bound return self def get_as_mark(self): return (self.mark_cursor, self.mark_bound) def set_as_iter(self, iter_cursor, iter_bound): mark_cursor = self.buffer.create_mark(None, iter_cursor) mark_bound = self.buffer.create_mark(None, iter_bound) self.set_as_mark(mark_cursor, mark_bound) return self def checkpoint(self): buffer = self.buffer iter_cursor = buffer.get_iter_at_mark(buffer.get_mark('insert')) iter_bound = buffer.get_iter_at_mark(buffer.get_mark('selection_bound')) self.set_as_iter(iter_cursor, iter_bound) return self def restore(self): (iter_cursor, iter_bound) = self.get_as_iter() self.buffer.place_cursor(iter_cursor) self.buffer.create_mark('selection_bound', iter_bound) return self def get_as_iter(self): iter_cursor = self.buffer.get_iter_at_mark(self.mark_cursor) iter_bound = self.buffer.get_iter_at_mark(self.mark_bound) return (iter_cursor, iter_bound) def get_as_offset(self): (iter_cursor, iter_bound) = self.get_as_iter() return (iter_cursor.get_offset(), iter_bound.get_offset()) def __str__(self): return '[%i,%i]' % self.get_as_offset() def cut(self): self.restore() self.buffer.delete_selection(False, True) return self class ListItem(object): def __init__(self, object=None, markup=NULL, is_insensitive=False): self.set_tuple(object, markup, is_insensitive) self.fg = MAIN_COLORS.get_color('gray') return def get_tuple(self): return [self.object, self.markup.encode('utf-8'), self.is_insensitive, self.fg] def set_tuple(self, object, markup, is_insensitive, color=None): self.object = object self.markup = markup self.is_insensitive = is_insensitive return self def get_object(self): return self.object def get_markup(self): return self.markup class ListSep(ListItem): def __init__(self): ListItem.__init__(self, object=None, markup='--', is_insensitive= False) return class TreeView(gtk.TreeView): def __len__(self): return len(self.list) def conjure_list(self): self.list = self.get_model() self.column_range = range(self.list.get_n_columns()) return self def clear_list(self): self.list.clear() return self def get_row_ndx(self): (path, col) = self.get_cursor() if path is None: row_ndx = None else: row_ndx = path[ZERO] return row_ndx def get_iter(self, row_ndx=None): if row_ndx is None: return None else: return self.list.get_iter(tuple1(row_ndx)) def get_row(self, iter): return [self.list.get_value(iter, col_ndx) for col_ndx in self.column_range] def scroll(self, row_ndx=None): if row_ndx is None: pass elif row_ndx >= len(self): pass else: take_adeep_breath() self.scroll_to_cell(path=tuple1(row_ndx), use_align=True, row_align=0.5, col_align=0.5) # 2007 Sep 20 selection = self.get_selection() if selection is None: pass else: selection.select_path(tuple1(row_ndx)) self.set_cursor(tuple1(row_ndx)) self.grab_focus() return self def get_next_row(self): iter = self.list.get_iter_first() while iter is not None: yield self.get_row(iter) iter = self.list.iter_next(iter) return class List(TreeView): def __init__(self): TreeView.__init__(self) col = gtk.TreeViewColumn() self.append_column(col) renderer = gtk.CellRendererText() col.pack_start(renderer, expand=True) col.add_attribute(renderer, 'markup', 1) col.add_attribute(renderer, 'foreground-set', 2) col.add_attribute(renderer, 'foreground-gdk', 3) list = gtk.ListStore(object, str, int, gtk.gdk.Color) self.set_model(list) self.conjure_list() self.set_reorderable(True) self.set_enable_search(True) self.set_headers_visible(False) return def set_list(self, item_list): self.set_model() # 2006 Feb 26 self.clear_list() for (row_ndx, item) in enumerate(item_list): tuple = item.get_tuple() self.list.append(tuple) self.set_model(self.list) # 2006 Feb 26 return self def get_list(self): result = [] for row in self.get_next_row(): (object, markup, is_sensitive) = row[:3] result.append(ListItem(object, markup, is_sensitive)) return result def get_item(self, row_ndx): iter = self.get_iter(row_ndx) if iter is None: return None return ListItem().set_tuple(*self.get_row(iter)) def get_object(self, row_ndx): item = self.get_item(row_ndx) if item is None: return None else: return item.get_object() def get_markup(self, row_ndx): item = self.get_item(row_ndx) if item is None: return None else: return item.get_markup() def insert(self, row_ndx, list_item): self.list.insert(row_ndx, list_item.get_tuple()) self.scroll(row_ndx) return self def remove(self, row_ndx): result = self.get_item(row_ndx) iter = self.get_iter(row_ndx) if iter is None: pass else: self.list.remove(iter) self.scroll(row_ndx) return result class Combo(gtk.Combo): def __init__(self): gtk.Combo.__init__(self) self.set_use_arrows(True) return def get_unicode(self): return self.entry.get_text().decode('utf-8') def set_unicode(self, text): return self.entry.set_text(text.encode('utf-8')) class ComboRestricted(Combo): def __init__(self): Combo.__init__(self) self.entry.set_sensitive(False) self.set_value_in_list(val=True, ok_if_empty=False) return class Button(gtk.Button): def __init__(self): gtk.Button.__init__(self) return def clear(self): for child in self.get_children(): self.remove(child) return self def add_widget(self, widget): self.clear() widget.show() self.add(widget) return self def add_stock(self, stock_id): self.add_widget(gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)) return self def add_label(self, label_text): label = Label() label.set_unicode(label_text) self.add_widget(label) return self def add_stock_label(self, stock_id, label_text=None): image = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON) label = Label() if label_text is None: (stock_id, label_text, modifier, key_val, i8n) = gtk.stock_lookup(stock_id) label.set_unicode(label_text) stretch = Stretch().set_expand(False) box = create_container_widget() box.stuff(image, stretch=stretch) box.stuff(label, stretch=stretch) box.show() alignment = gtk.Alignment(xalign=0.5, yalign=0.5) alignment.add(box) self.add_widget(alignment) return self def add_help(self, help): set_tip(self, help) return self class Entry(gtk.Entry): def set_unicode(self, text): self.set_text(text.encode('utf-8')) return self def get_unicode(self): return self.get_text().decode('utf-8') class Label(gtk.Label): def set_unicode(self, text): self.set_markup_with_mnemonic(text.encode('utf-8')) return self class LabelWrapMarkup(gtk.Image): def __init__(self, str=''): """cLabelWrapMarkup(str=NULL) a gtk Label-like widget that wraps its text when resized. """ gtk.Image.__init__(self) self.layout = self.create_pango_layout(text=str) self.set_justify() self.connect('expose-event', self.call_back_expose_event) return def set_justify(self, jtype=gtk.JUSTIFY_LEFT): """cLabelWrapMarkup.set_justify(jtype=gtk.JUSTIFY_LEFT) sets justification mode to LEFT, RIGHT, or CENTER. FILL is the same as LEFT. """ if jtype in [gtk.JUSTIFY_LEFT]: self.layout.set_justify(False) self.layout.set_alignment(pango.ALIGN_LEFT) elif jtype in [gtk.JUSTIFY_RIGHT]: self.layout.set_justify(False) self.layout.set_alignment(pango.ALIGN_RIGHT) elif jtype in [gtk.JUSTIFY_CENTER]: self.layout.set_justify(False) self.layout.set_alignment(pango.ALIGN_CENTER) elif jtype in [gtk.JUSTIFY_FILL]: self.layout.set_justify(True) self.layout.set_alignment(pango.ALIGN_LEFT) return self def set_unicode(self, str=NULL): """cLabelWrapMarkup.SetUnicode(str=NULL) changes the text of the Label-like widget. The text may contain Pango markup. """ self.layout.set_markup(str.encode('utf-8')) self.call_back_expose_event() return self def call_back_expose_event(self, *calling_sequence): """cLabelWrapMarkup.CallBackExposeEvent is automatically connected to the expose-event signal always to redraw the text at the then-current width. """ rect = self.get_allocation() self.layout.set_width(rect.width * 1000) parent = self.get_parent() if parent is None: return False window = parent.window if window is None: return False state = self.state context = (parent.get_style().fg_gc)[state] drawable = gtk.gdk.Pixmap(window, rect.width, rect.height) drawable.draw_drawable(context, window, rect.x, rect.y, ZERO, ZERO, rect.width, rect.height) drawable.draw_layout(context, ZERO, ZERO, self.layout) window.draw_drawable(context, drawable, ZERO, ZERO, rect.x, rect.y, rect.width, rect.height) return False class LabelWithToolTip(gtk.EventBox): def __init__(self): gtk.EventBox.__init__(self) self.label = Label() self.label.show() self.add(self.label) return def set_unicode(self, text): self.label.set_unicode(text) return self def set_alignment(self, **calling_sequence): return self.label.set_alignment(**calling_sequence) def set_mnemonic_widget(self, widget): self.label.set_mnemonic_widget(widget) return def add_help(self, help): set_tip(self, help) return self class LabelOnAbuttonWithToolTip(Label): def __init__(self, label_with_mnemonic, description=None): Label.__init__(self) self.set_unicode(label_with_mnemonic) self.description = description self.connect_after('realize', self.call_back_after_realize) return def call_back_after_realize(self, *calling_sequence): parent = self.get_parent() while not isinstance(parent, gtk.Button): parent = parent.get_parent() set_tip(parent, self.description) return False class Text(gtk.TextView): def __init__(self, buffer=None, paragraph_above=3, paragraph_below=3): gtk.TextView.__init__(self, buffer=buffer) self.buffer = self.get_buffer() self.set_wrap_mode(gtk.WRAP_WORD) self.set_pixels_above_lines(paragraph_above) self.set_pixels_below_lines(paragraph_below) self.connect('key-press-event', self.call_back_key_press_event) return def get_start_iter(self): return self.buffer.get_start_iter() def get_end_iter(self): return self.buffer.get_end_iter() def get_insert_mark(self): return self.buffer.get_insert() def get_iter_at_mark(self, mark): return self.buffer.get_iter_at_mark(mark) def get_insert_iter(self): return self.get_iter_at_mark(self.get_insert_mark()) def get_buffer_location(self, iter): return self.get_iter_location(iter) def get_window_location(self, x, y): return self.buffer_to_window_coords(gtk.TEXT_WINDOW_WIDGET, x, y) def set_unicode(self, text): self.buffer.set_text(text.encode('utf-8')) return self def get_unicode(self): return self.buffer.get_text(self.get_start_iter(), self.get_end_iter()).decode('utf-8') def insert_unicode(self, text): self.buffer.insert(self.get_end_iter(), text.encode('utf-8')) return self def call_back_key_press_event(self, widget, event, *calling_sequence): if event.keyval in [gtk.keysyms.Tab]: self.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD) return True return False class LabelHot(Text): def __init__(self, markup=None, width=-1, height=-1, write_enabled=False, relation=None, row_ndx=None): Text.__init__(self, paragraph_above=ZERO, paragraph_below=ZERO) self.add_events(gtk.gdk.EXPOSURE_MASK | gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK) self.tag_table = self.buffer.get_tag_table() self.tag_cartouche = TagCartouche(name='-Tonto-cartouche') self.tag_table.add(self.tag_cartouche) self.cursor_selection = Selection(self.buffer) self.drag_source_selection = Selection(self.buffer) self.relation = relation self.row_ndx = row_ndx self.set_editable(write_enabled) self.set_cursor_visible(write_enabled) self.set_wrap_mode(gtk.WRAP_WORD) self.set_size_request(width, height) self.set_left_margin(TEXT_PADDING) self.set_right_margin(TEXT_PADDING) self.connect('drag-begin', self.call_back_drag_begin) self.connect('drag-end', self.call_back_drag_end) self.connect('motion-notify-event', self.call_back_motion_notify_event) self.buffer.connect('changed', self.highlight_cartouches) self.latch = False self.buffer.connect_after('mark-set', self.call_back_after_mark_set) self.stack = [] self.mouse_in_tags = [] if markup is None: pass else: self.insert_markup(markup) return def dump_targets(self, list): for (target, flags, id) in list: flag_names = NULL if flags & gtk.TARGET_SAME_APP: flag_names += '' if flags & gtk.TARGET_SAME_WIDGET: flag_names += '' print '%3i %s:%s' % (id, target, flag_names) return self def call_back_drag_begin(self, *calling_sequence): """Modifying drag-and-drop behavior of textview by subclassing its methods and intercepting signals is not effective. Thus, a move is emulated by saving the source selection, allowing the default copy operation (to be changed in GTK+ 2.6) to proceed, and then deleting the selection afterward. """ self.drag_source_selection.checkpoint() return False def call_back_drag_end(self, *calling_sequence): self.drag_source_selection.cut() return False def call_back_motion_notify_event(self, text_view, event, *calling_sequence): if event.is_hint: (x, y, state) = self.window.get_pointer() # Reenable motion events. else: (x, y) = (event.x, event.y) (char, line) = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, int(x), int(y)) pointer = self.get_iter_at_location(char, line) entering_tags = pointer.get_tags() for tag in self.mouse_in_tags: if tag in entering_tags: pass else: tag.call_back_leave_notify(text_view=text_view, event= event, *calling_sequence) for tag in entering_tags: if tag in self.mouse_in_tags: pass else: tag.call_back_enter_notify(text_view=text_view, event= event, *calling_sequence) self.mouse_in_tags = entering_tags return False def call_back_after_mark_set(self, text_buffer, iter, mark, *calling_sequence): name = mark.get_name() if name in ['insert', 'selection_bound']: if self.latch: pass else: self.latch = True self.expand_selection() self.latch = False return False def expand_selection(self): def search_left(cursor, target=U_LEFT_CHEVRON, method=gtk.TextIter.backward_search, gravity=ZERO): return search(cursor, target, method, gravity) def search_right(cursor, target=U_RIGHT_CHEVRON, method=gtk.TextIter.forward_search, gravity=1): return search(cursor, target, method, gravity) def search(cursor, target, method, gravity): next = cursor.copy() if gravity == 1: next.backward_char() if next.get_char() == target: return cursor range = method(cursor, target, ZERO) if range is None: return cursor return range[gravity] is_extended = False (prev_cursor, prev_bound) = self.cursor_selection.get_as_iter() self.cursor_selection.checkpoint() (cursor, bound) = self.cursor_selection.get_as_iter() if cursor.has_tag(self.tag_cartouche) and not cursor.toggles_tag(self.tag_cartouche): is_extended = True if cursor.compare(bound) < ZERO: cursor = search_left(cursor) elif cursor.compare(bound) == ZERO: if cursor.compare(prev_cursor) <= ZERO: cursor = search_left(cursor) else: cursor = search_right(cursor) else: cursor = search_right(cursor) if bound.has_tag(self.tag_cartouche): is_extended = True if bound.compare(cursor) <= ZERO: bound = search_left(bound) else: bound = search_right(bound) if is_extended: self.cursor_selection.set_as_iter(cursor, bound) self.cursor_selection.restore() return False def yank_tag(self, tag_name): for (ndx, tag) in enumerate(self.stack): group = tag.get_property('name').split('-', 1) name = group[ZERO] if name == tag_name: self.stack.pop(ndx) break return self def new_tag(self, class_of=None, name=None, **calling_sequence): result = self.tag_table.lookup(name) if result is None: result = class_of(name=name, **calling_sequence) self.tag_table.add(result) self.stack.insert(ZERO, result) return result def get_unique_tag_name(self, tag): return '%s-%i' % (tag, self.tag_table.get_size()) def stack_tag(self, tag_as_is): # 2007 Mar 20 tag = tag_as_is.lower() if tag in ['']: self.new_tag(TagItalic, name='i') elif tag in ['']: self.new_tag(TagBold, name='b') elif tag in ['']: self.new_tag(TagUnderline, name='u') elif tag in ['']: self.new_tag(TagMonospace, name='tt') elif tag in ['']: self.new_tag(TagMonospace, name='samp') elif tag in ['']: self.new_tag(TagItalic, name='var') elif tag in ['']: self.new_tag(TagBig, name=self.get_unique_tag_name('big'), iter=self.get_end_iter()) elif tag in ['']: self.new_tag(TagSmall, name=self.get_unique_tag_name('small'), iter=self.get_end_iter()) elif tag.startswith('', '
']: self.insert_unicode_with_tags('\n') elif tag in ['

']: self.insert_unicode_with_tags(''' ''') elif tag.startswith('']: self.yank_tag('i') elif tag in ['']: self.yank_tag('b') elif tag in ['']: self.yank_tag('u') elif tag in ['']: self.yank_tag('tt') elif tag in ['']: self.yank_tag('samp') elif tag in ['']: self.yank_tag('var') elif tag in ['']: self.yank_tag('big') elif tag in ['']: self.yank_tag('small') elif tag in ['', '']: self.yank_tag('font') elif tag in ['

']: pass elif tag in ['']: self.yank_tag('a') elif tag in ['']: self.yank_tag('dial') else: self.insert_unicode_with_tags(tag) return self def insert_unicode_with_tags(self, text): def scroll_smoothly(): flags = self.flags() if flags & gtk.REALIZED: self.scroll_to_mark(self.get_insert_mark(), within_margin=0.1) return def scroll_end(): parent = self.get_parent() if parent is None: pass elif isinstance(parent, gtk.ScrolledWindow): vadj = parent.get_vadjustment() vadj.value = vadj.upper return self.buffer.insert_with_tags(self.get_end_iter(), text.encode('utf-8'), *self.stack) scroll_smoothly() take_adeep_breath() return self def insert_markup(self, text, all_caps=False): text = unscape(text) if all_caps: text = text.upper() strings = XML_TAG.split(text) self.insert_unicode_with_tags(strings.pop(ZERO)) while len(strings) > ZERO: self.stack_tag(strings.pop(ZERO)) self.insert_unicode_with_tags(strings.pop(ZERO)) return self def highlight_cartouches(self, *calling_sequence): buffer = self.buffer top = self.get_start_iter() bottom = self.get_end_iter() buffer.remove_tag(self.tag_cartouche, top, bottom) tail = top range = tail.forward_search(U_LEFT_CHEVRON, ZERO) while range is not None: (start, tail) = range range = tail.forward_search(U_RIGHT_CHEVRON, ZERO) if range is None: break (end, tail) = range buffer.apply_tag(self.tag_cartouche, start, tail) range = tail.forward_search(U_LEFT_CHEVRON, ZERO) return self def clear(self): self.buffer.delete(self.get_start_iter(), self.get_end_iter()) return self class Column(gtk.TreeViewColumn): def __init__(self, title, description=None): gtk.TreeViewColumn.__init__(self, title) self.set_sort_descending() label = LabelOnAbuttonWithToolTip(title, description=description) label.show() self.set_widget(label) self.set_resizable(True) return def is_sort_descending(self): return self.SORT_DESCENDING def set_sort_descending(self, descending=False): self.SORT_DESCENDING = descending if descending: self.set_sort_order(gtk.SORT_DESCENDING) else: self.set_sort_order(gtk.SORT_ASCENDING) return self class Matrix(TreeView): def __init__(self, parent, relation, width=450, height=300): self.parent_ = parent self.set_relation(relation) TreeView.__init__(self) self.set_size_request(width=width, height=height) self.mark = FieldCheckButton(name='Mark') self.mark.set_help('The checkmarks select rows for removal.') self.conjure_cols() self.connect_after('row-activated', self.call_back_row_activated) self.connect('button-release-event', self.call_back_button_release_event) self.connect('key-press-event', self.call_back_key_press_event) return def set_relation(self, relation): self.relation = relation self.relation.parent_matrix = self return self def conjure_cols(self): col_types = [gobject.TYPE_STRING] * (len(self.relation.display_tags) + 1) list = gtk.ListStore(*col_types) self.set_model(list) self.conjure_list() for col in self.get_columns(): self.remove_column(col) self.relation.display_tags = self.relation.audit_display_tags() self.append_column(self.conjure_col(self.mark, ZERO)) for (col_ndx, tag) in enumerate(self.relation.display_tags): self.append_column(self.conjure_col(self.relation.column_list.find(tag), col_ndx + 1)) self.set_headers_clickable(True) self.conjure_rows() return self def conjure_rows(self, row_ndx=None): self.mark.set(False) self.set_model() # 2006 Feb 26 self.clear_list() for (ndx, row) in enumerate(self.relation.get_next_display()): row.insert(ZERO, self.mark.get_as_displayed()) try: self.list.append(row) except (ValueError, TypeError): # 2007 Nov 08 diagnostic = DlgAssertion(parent=MAIN, msg= ''' Fields/Headers Mismatch The number of fields on a row does not match the number of columns in the header of the file. The error occurred on row %i of relation "%s." Row is lost. ''' % (ndx, self.relation.name)) diagnostic.run() diagnostic.destroy() self.set_model(self.list) # 2006 Feb 26 self.scroll(row_ndx) return self def refresh_row(self, row_ndx, iter=None): if iter is None: iter = self.get_iter(row_ndx) calling_sequence = [] row = self.relation.get_row_display(row_ndx) for (col_ndx, col) in enumerate(row): calling_sequence.append(col_ndx + 1) calling_sequence.append(col) self.list.set(iter, *calling_sequence) return self def conjure_col(self, field, col_ndx): header = field.get_name() help = field.get_help() (renderer, attribute) = field.get_renderer() result = Column(title=header, description=help) result.pack_start(renderer, False) result.add_attribute(renderer, attribute, col_ndx) result.connect('clicked', self.call_back_col_head_clicked, col_ndx) return result def get_mark(self, iter=None): if iter is None: iter = self.get_iter(self.get_row_ndx()) if iter is None: return None else: self.mark.set_as_edited(self.list.get_value(iter, ZERO)) return self.mark.get() def toggle_mark(self, iter=None, value=None): if iter is None: iter = self.get_iter(self.get_row_ndx()) if iter is None: pass else: if value is None: value = not self.get_mark(iter) self.mark.set(value) self.list.set_value(iter, ZERO, self.mark.get_as_displayed()) return self def call_back_col_head_clicked(self, col, col_ndx, *calling_sequence): if col_ndx > ZERO: for column in self.get_columns(): column.set_sort_indicator(False) col.set_sort_indicator(True) col.set_sort_descending(not col.is_sort_descending()) tag = (self.relation.display_tags)[col_ndx - 1] self.relation.sort(tag, col.is_sort_descending()) self.conjure_rows() return False def call_back_button_release_event(self, widget, event, *calling_sequence): if event.button in [1]: (path, col) = self.get_cursor() if path is None: pass else: if col.get_title() in ['Mark']: self.call_back_menu_mark_toggle() elif event.button in [3]: MAIN.popup.find_main().popup(None, None, None, event.button, event.time) return False def call_back_key_press_event(self, widget, event, *calling_sequence): if event.keyval in [gtk.keysyms.F3, gtk.keysyms.slash]: self.call_back_menu_edit_search_replace() return True return False def call_back_menu_file_add_remove_fields(self, *calling_sequence): dlg = DlgFieldsAddRemove(parent=MAIN, relation=self.relation) if dlg.run() in [gtk.RESPONSE_OK]: self.relation.column_list.load_from_list_widget(dlg.list) self.relation.display_tags = self.relation.audit_display_tags() self.relation.set_dirty() dlg.destroy() return False def call_back_menu_file_choose_display_columns(self, *calling_sequence): dlg = DlgDisplayColumnsChoose(parent=MAIN, relation=self.relation) if dlg.run() in [gtk.RESPONSE_OK]: tags = [item.get_markup() for item in dlg.display_columns.get_list()] self.relation.display_tags = tags self.conjure_cols() # 2007 May 14 self.relation.set_dirty() dlg.destroy() return False def call_back_menu_edit_search_replace(self, *calling_sequence): dlg = DlgFind(parent=MAIN, matrix=self) dlg.run() dlg.destroy() return False def call_back_menu_edit_go_to(self, *calling_sequence): dlg = DlgQuery(parent=MAIN, msg='Go To Row', label_text='Line Number') result = dlg.run() if result in [gtk.RESPONSE_OK]: ndx = FieldInteger(default=1).set_as_edited(dlg.response.get_unicode()).get() self.scroll(ndx - 1) dlg.destroy() return False def call_back_menu_edit_insert_row(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: row_ndx = ZERO self.relation.insert_new_row(row_ndx) self.conjure_rows(row_ndx) return False def call_back_menu_edit_insert_after_row(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: row_ndx = len(self.relation) - 1 row_ndx += 1 self.relation.insert_new_row(row_ndx) self.conjure_rows(row_ndx) return False def call_back_menu_edit_insert_at_bottom(self, *calling_sequence): row_ndx = len(self.relation) self.relation.insert_new_row(row_ndx) self.conjure_rows(row_ndx) return False def call_back_menu_edit_insert_at_top(self, *calling_sequence): row_ndx = ZERO self.relation.insert_new_row(row_ndx) self.conjure_rows(row_ndx) return False def call_back_menu_edit_free_form_entry(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: pass else: self.relation.free_form_entry(self.parent_, row_ndx) path = tuple1(row_ndx) self.refresh_row(row_ndx, self.list.get_iter(path)) return False def call_back_menu_edit_field_entry(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: pass else: self.relation.field_entry(self.parent_, row_ndx) path = tuple1(row_ndx) self.refresh_row(row_ndx, self.list.get_iter(path)) return False def call_back_menu_edit_remove_row(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: pass else: as_stored = [self.relation.remove_row(row_ndx)] targets = ['-Tonto-row-list'] self.parent_.xclipboard_export.clear() self.parent_.xclipboard_export.set_content(targets, repr(as_stored)) self.conjure_rows(row_ndx) return False def call_back_menu_edit_copy_row(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: pass else: as_stored = [(self.relation)[row_ndx]] targets = ['-Tonto-row-list'] self.parent_.xclipboard_export.clear() self.parent_.xclipboard_export.set_content(targets, repr(as_stored)) return False def call_back_menu_edit_paste_row(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: row_ndx = ZERO as_stored = self.parent_.xclipboard_import.get_target(['-Tonto-row-list'], self.parent_.xclipboard_export) if as_stored is None: pass else: self.relation.paste_rows(row_ndx, eval(as_stored)) self.conjure_rows(row_ndx) return False def call_back_menu_view_as_text(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: pass else: self.relation.view_as_text(self.parent_, row_ndx) return False def call_back_menu_view_traverse_link(self, *calling_sequence): row_ndx = self.get_row_ndx() if row_ndx is None: pass else: self.relation.traverse_link(row_ndx) return False def call_back_menu_view_calendar_month(self, *calling_sequence): date = datetime.date.today() dlg = DlgDateSelection(parent=MAIN, year=date.year, month=date.month, day=date.day, relation=self.relation) dlg.run() dlg.destroy() return False def call_back_menu_view_alarms(self, *calling_sequence): self.relation.call_back_check_alarms() dlg = self.relation.dlg_alarms dlg.run() dlg.hide() return False def call_back_menu_edit_calc_calendar_float(self, *calling_sequence): self.relation.calc_float() row_ndx = self.get_row_ndx() self.conjure_rows(row_ndx) return False def call_back_menu_mark_toggle(self, *calling_sequence): self.toggle_mark() return False def call_back_menu_mark_all(self, *calling_sequence): iter = self.list.get_iter_first() while iter is not None: self.toggle_mark(iter, value=True) iter = self.list.iter_next(iter) return False def call_back_menu_mark_toggle_all(self, *calling_sequence): iter = self.list.get_iter_first() while iter is not None: self.toggle_mark(iter) iter = self.list.iter_next(iter) return False def call_back_menu_mark_cut(self, *calling_sequence): as_stored = [] row_ndx = len(self.relation) - 1 while row_ndx >= ZERO: path = tuple1(row_ndx) iter = self.list.get_iter(path) self.mark.set_as_edited(self.list.get_value(iter, ZERO)) is_marked = self.mark.get() if is_marked: as_stored.insert(ZERO, self.relation.pop(row_ndx)) row_ndx -= 1 if len(as_stored) > ZERO: targets = ['-Tonto-row-list'] self.parent_.xclipboard_export.clear() self.parent_.xclipboard_export.set_content(targets, repr(as_stored)) self.conjure_rows() self.relation.set_dirty() return False def call_back_menu_mark_copy(self, *calling_sequence): as_stored = [] row_ndx = len(self.relation) - 1 while row_ndx >= ZERO: path = tuple1(row_ndx) iter = self.list.get_iter(path) self.mark.set_as_edited(self.list.get_value(iter, ZERO)) is_marked = self.mark.get() if is_marked: as_stored.insert(ZERO, (self.relation)[row_ndx]) row_ndx -= 1 if len(as_stored) > ZERO: targets = ['-Tonto-row-list'] self.parent_.xclipboard_export.clear() self.parent_.xclipboard_export.set_content(targets, repr(as_stored)) return False def call_back_row_activated(self, another_self, path, col, *calling_sequence): return self.call_back_menu_edit_field_entry(another_self, path, col, *calling_sequence) class XclipboardExport(object): def __init__(self, parent, clipboard='CLIPBOARD'): self.parent_ = parent self.clipboard = clipboard self.parent_.connect('selection_clear_event', self.call_back_selection_clear_event) self.parent_.connect('selection_get', self.call_back_selection_get) self.clear() return def clear(self): self.set_clipboard_mine(False) self.content = [] self.targets = {} self.parent_.selection_clear_targets(self.clipboard) return self def set_clipboard_mine(self, clipboard_mine=True): self.clipboard_mine = clipboard_mine return self def is_clipboard_mine(self): return self.clipboard_mine def call_back_selection_clear_event(self, *calling_sequence): self.set_clipboard_mine(False) return True def call_back_selection_get(self, widget, data, ndx, time): text = self.get_content(ndx=ndx) data.set('STRING', 8, text) return True def set_content(self, targets, content): for target in targets: ndx = len(self.content) self.parent_.selection_add_target(self.clipboard, target, ndx) self.content.append(content) (self.targets)[target] = ndx self.set_clipboard_mine(self.parent_.selection_owner_set(self.clipboard)) return self def get_content(self, target=None, ndx=None): if target is None: pass else: ndx = self.targets.get(target, None) if ndx is None: return None elif ZERO <= ndx < len(self.content): return (self.content)[ndx] else: return None class XclipboardImport(object): def __init__(self, parent, clipboard='CLIPBOARD'): self.parent_ = parent self.clipboard = clipboard self.parent_.connect('selection_received', self.call_back_selection_received) self.set_lock(False) self.targets_offered = None self.target = None self.data = None return def call_back_selection_received(self, widget, data, sentinel, *calling_sequence): type = str(data.type) target = str(data.target) if type in ['ATOM']: self.targets_offered = [str(t) for t in data.get_targets() if t is not None] elif target in [self.target]: self.data = data.data self.set_lock(False) return False def set_lock(self, lock=True): self.lock = lock return def is_locked(self): return self.lock def wait(self, cut_off=3.0): time_stamp = time.time() while self.is_locked(): gtk.main_iteration() if time_stamp - time.time() > cut_off: break return self def get_targets_offered(self): self.targets_offered = None self.set_lock() self.parent_.selection_convert(self.clipboard, 'TARGETS') self.wait() return self.targets_offered def select_target(self, targets_requested, targets_offered, default=None): if targets_offered is None: return None for target in targets_requested: if target in targets_offered: return target return default def get_target(self, targets_requested, clipboard_export=None): self.target = self.select_target(targets_requested, self.get_targets_offered()) if self.target is None: result = None else: self.data = None self.set_lock() self.parent_.selection_convert(self.clipboard, self.target) self.wait() result = self.data if result is None: if clipboard_export is None: pass else: result = self.get_target_direct(targets_requested, clipboard_export) return result def get_target_direct(self, targets_requested, clipboard_export): self.target = self.select_target(targets_requested, clipboard_export.targets) if self.target is None: result = None else: result = clipboard_export.get_content(target=self.target) return result def is_target_offered(self, targets_requested, clipboard_export=None): result = self.select_target(targets_requested, self.get_targets_offered()) if result: pass else: if clipboard_export is None: pass else: result = self.select_target(targets_requested, clipboard_export.targets) return result is not None class Buffer(object): def __init__(self, data=NULL): self.data = data return def __len__(self): return len(self.data) def __str__(self): return self.data def push(self, data): self.data += data return self def pop(self, char_count=None): if char_count is None: result = self.data self.data = NULL else: result = (self.data)[:char_count] self.data = (self.data)[char_count:] return result class Port(object): def __init__(self, port, blk_size=2000): self.name = port self.unit = os.open(self.name, os.O_RDWR + os.O_NDELAY) self.blk_size = blk_size self.buffer = Buffer() self.eof = False self.closed = False self.encoding = None self.newlines = None self.softspace = False return def __iter__(self): return self def next(self): result = self.readline() if result == NULL: raise StopIteration return result def close(self): os.close(self.fileno()) self.closed = True return self def fileno(self): return self.unit def load_buffer(self): try: blk = os.read(self.fileno(), self.blk_size) if blk in ['\n']: pass else: self.buffer.push(blk) self.eof = len(blk) != self.blk_size except OSError: self.eof = True return self def load_buffer_size(self, size): while len(self.buffer) < size: self.load_buffer() if self.eof: break return self def get_buffer_len(self): return len(self.buffer) def unload_buffer(self): return self.buffer.pop() def unload_buffer_size(self, size): self.load_buffer_size(size) return self.buffer.pop(size) def read(self, size=None): if size is None: self.load_buffer() while not self.eof: self.load_buffer() return self.unload_buffer() else: return self.unload_buffer(size) def readline(self, size=None): if size is None: self.load_buffer() pos = self.buffer.find('\n') while pos == NA: if self.eof: return self.unload_buffer() self.load_buffer() pos = self.buffer.find('\n') return self.unload_buffer(pos + 1) else: self.load_buffer_size(size) pos = (self.buffer)[:size].find('\n') if pos == NA: return self.unload_buffer(size) else: return self.un_load_buffer(pos + 1) def readlines(self, *calling_sequence): return [line for line in self] def write(self, str): try: result = os.write(self.fileno(), str) except OSError: result = ZERO return result def writelines(self, sequence): for line in sequence: self.write(line) return self class NoneModem(gtk.HBox): def __init__(self, port_name): self.port_name = port_name gtk.HBox.__init__(self) self.image = gtk.Image() self.set_active() self.image.show() label = Label() label.set_unicode(self.get_port_name()) label.show() self.pack_start(self.image, expand=False, fill=False) self.pack_start(label, expand=False, fill=False) return def get_port_name(self): return self.port_name def open(self): self.show('Open Port %s' % self.get_port_name()) return True def close(self): return self def set_active(self, is_active=False): self.is_active = is_active self.set_image() return self def set_image(self): if self.is_active: self.image.set_from_stock(gtk.STOCK_APPLY, gtk.ICON_SIZE_MENU) else: self.image.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_MENU) return self def set_progress_window(self, progress_window=None): self.progress_window = progress_window return self def show(self, text=NULL, emphasis='b'): text = text.replace('\r', NULL) if self.progress_window is None: pass else: self.progress_window.insert_markup(''' <%s>%s ''' % (emphasis, text, emphasis)) return self def dial(self, phone): self.show('No MODEM') return self def show_progress(self): return self def hang_up(self): return self def test(self): self.set_active(True) self.show('Success') return self class Modem(NoneModem): def __init__(self, port_name): NoneModem.__init__(self, port_name) self.attention = { 'Attention': 'AT', 'Reset': 'Z', 'Dial': 'D', 'TouchTone': 'T', 'Pulse': 'P', 'Echo': 'E', 'On': '1', 'Off': '0', 'OnHook': 'H0', 'OffHook': 'H1', 'Speaker': 'M', 'Volume': 'L', 'Low': '1', 'Medium': '2', 'High': '3', 'Quiet': 'Q', 'Verbose': 'V', 'Pause': ',', 'DialExitToCommand': ';', 'DialAlpha': '"', 'FlashHook': '!', 'LongPause': '/', 'WaitForDialTone': 'W', 'ShowProductCode': 'I0', 'ShowProductID': 'I3', 'ShowAttentionCommands': '$', 'ShowAmpersandCommands': '&$', 'ShowDialCommands': 'D$', 'ShowProgress': 'X0', 'Interrupt': '+', 'Send': '\r', } return def open(self): NoneModem.open(self) try: self.port = Port(self.get_port_name()) except OSError: self.port = None self.show('No Port') return self.port is not None def close(self): if self.port is None: pass else: self.port.close() return self def command(self, attention_codes): result = NULL for atn in attention_codes: result += (self.attention)[atn] return result def execute(self, command=None, sleep=0.1, pause=0.1, time_out=1.0, cut_off=3.0): def do_time_out(): begin_time = time.time() char_count = self.port.get_buffer_len() while char_count == self.port.get_buffer_len(): time.sleep(sleep) self.port.load_buffer() if time.time() - begin_time > time_out: return True return False def do_cut_off(): begin_time = time.time() old_count = ZERO new_count = self.port.get_buffer_len() while old_count < new_count: time.sleep(sleep) self.port.load_buffer() if time.time() - begin_time > cut_off: return True old_count = new_count new_count = self.port.get_buffer_len() return False if command is None: time.sleep(pause) else: self.port.read() # Drain port. time.sleep(pause) self.port.write('%s' % command) time.sleep(pause) do_time_out() do_cut_off() return self.asciionly(self.port.read()) def asciionly(self, text): result = NULL for c in text: if ord(c) < 128: result += c else: result += '?' return result def do_reset(self): command = self.command(['Attention', 'Reset', 'Send']) return self.execute(command) def do_init(self): command = self.command([ 'Attention', 'Quiet', 'Off', 'Verbose', 'On', 'Echo', 'On', 'ShowProgress', 'Speaker', 'On', 'Volume', 'Medium', 'Send', ]) return self.execute(command) def do_handshake(self): command = self.command(['Attention', 'Send']) return self.execute(command) def show_product_id(self): command = self.command(['Attention', 'ShowProductID', 'ShowProductCode', 'Send']) return self.execute(command) def show_attention_commands(self): result = NULL command = self.command(['Attention', 'ShowAttentionCommands', 'Send']) result += self.execute(command) command = self.command(['Send']) for count in [1, 2, 3]: result += self.execute(command) command = self.command(['Attention', 'ShowAmpersandCommands', 'Send']) result += self.execute(command) command = self.command(['Send']) for count in [1, 2, 3]: result += self.execute(command) return result def show_dial_commands(self): result = NULL command = self.command(['Attention', 'ShowDialCommands', 'Send']) result += self.execute(command) command = self.command(['Send']) for count in [1, 2, 3]: result += self.execute(command) return result def do_dial(self, phone): command = self.command(['Attention', 'Dial', 'TouchTone', 'DialAlpha']) command += phone command += self.command(['DialExitToCommand', 'Send']) return self.execute(command) def do_hang_up(self): command = self.command(['Interrupt']) result = NULL for count in [1, 2, 3]: result += self.execute(command, sleep=0.25, time_out=ZERO) command = self.command(['Attention', 'OnHook', 'Send']) result += self.execute(command) return result def dial(self, phone): self.show('Reset') self.show(self.do_reset(), emphasis='tt') self.show('Initialize') self.show(self.do_init(), emphasis='tt') self.show('Dial') self.show(self.do_dial(phone), emphasis='tt') return self def show_progress(self): self.show(self.execute(), emphasis='tt') return self def hang_up(self): self.show('HangUp') self.show(self.do_hang_up(), emphasis='tt') return self def test(self): self.show('Reset') self.show(self.do_reset(), emphasis='tt') self.show('Initialize') self.show(self.do_init(), emphasis='tt') self.show('Ack') result = self.do_handshake() self.set_active(result.find('OK') != NA) self.show(result, emphasis='tt') if self.is_active: self.show('Success') self.show('Modem Model') self.show(self.show_product_id(), emphasis='tt') # self.show('Summary of Attention Codes') # self.show(self.show_attention_commands(), emphasis='tt') self.show('Summary of Dialing Characters') self.show(self.show_dial_commands(), emphasis='tt') else: self.show('Failure') return self class MainWindow(gtk.Window): def __init__(self): global MAIN gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) MAIN = self self.conjure_colors() self.config = Config(self) self.tool_tips = gtk.Tooltips() self.relation_list = RelationList(self.config) self.set_title('Tonto') self.set_icon_list(*self.conjure_icon_set()) self.set_border_width(WINDOW_PADDING) self.menu_accelerator_keys = gtk.AccelGroup() self.add_accel_group(self.menu_accelerator_keys) self.add(self.conjure_controls()) self.conjure_popup() self.connect('destroy', self.call_back_destroy) self.connect('delete-event', self.call_back_delete_event) self.show() return def conjure_icon_set(self): result = [] for size in [gtk.ICON_SIZE_MENU, gtk.ICON_SIZE_SMALL_TOOLBAR, gtk.ICON_SIZE_LARGE_TOOLBAR, gtk.ICON_SIZE_BUTTON, gtk.ICON_SIZE_DND, gtk.ICON_SIZE_DIALOG]: result.append(self.render_icon(TONTO_ICON, size)) return result def conjure_controls(self): result = create_container_widget(gtk.VBox) stretch = Stretch().set_ver_expand(False) menu_main = result.stuff(self.conjure_menu(), stretch=stretch) result.stuff(gtk.HBox(), stretch=stretch).set_size_request(height= TAB_PADDING, width=NA) self.tabs = [] self.notebook = result.stuff(gtk.Notebook()) # self.notebook.modify_bg(gtk.STATE_NORMAL, MAIN_COLORS.get_color('navajowhite')) self.notebook.popup_enable() for relation in self.relation_list: self.append_tab(relation) result.show() self.xclipboard_export = XclipboardExport(self) self.xclipboard_import = XclipboardImport(self) return result def append_tab(self, relation): tab_label = LabelWithToolTip() tab_label.set_unicode(relation.get_tag()) tab_label.add_help(relation.get_help()) menu_label = Label() menu_label.set_unicode(relation.get_tag()) tab_content = self.conjure_tab(relation) self.notebook.append_page_menu(tab_content, tab_label, menu_label) self.notebook.set_tab_label_packing(tab_content, expand=True, fill=False, pack_type=gtk.PACK_START) self.notebook.set_current_page(NA) return self def remove_tab(self, rel_ndx): del self.tabs[rel_ndx] self.notebook.remove_page(rel_ndx) return self def conjure_tab(self, relation): result = create_container_widget() matrix = result.stuff(Matrix(self, relation), scrolled=True, stretch=Stretch().set_padding(TAB_PADDING)) self.tabs.append(matrix) result.show() return result def conjure_colors(self): global MAIN_COLORS MAIN_COLORS = MainColors().load(self) return self def conjure_menu_items(self): file = self.menu.stuff(MenuSub('_File', help= 'Create/Remove Tabs')) file.stuff(Mitem('_Open...', id=gtk.STOCK_OPEN, c='O', method=self.call_back_menu_file_open, help= 'Create a Tab')) file.stuff(Mitem('_Close', id=gtk.STOCK_CLOSE, c='W', method=self.call_back_menu_file_close, help= 'Remove a Tab')) file.stuff(Mitem('Chec_kpoint', id=gtk.STOCK_SAVE, c='S', method=self.call_back_menu_file_checkpoint, help= 'Save Changes')) file.stuff(Msep()) file.stuff(Mitem('_Revert', id=gtk.STOCK_REFRESH, c='Z', method=self.call_back_menu_file_revert, help= 'Discard Changes')) file.stuff(Mitem('_Merge From...', method=self.call_back_menu_file_merge, help='Append a File')) file.stuff(Mitem('_Export To....', id=gtk.STOCK_SAVE_AS, c= 'S', method=self.call_back_menu_file_export, help='Save to a File')) file.stuff(Msep()) file.stuff(Mitem('Add Remove _Fields', id=gtk.STOCK_ADD, method= self.call_back_menu_file_add_remove_fields, help= 'Change What Data is Kept')) file.stuff(Mitem('Choose _Display Columns', method=self.call_back_menu_file_choose_display_columns, help='Change Appearance of the List')) file.stuff(Msep()) file.stuff(Mitem('_Quit', id=gtk.STOCK_QUIT, c='Q', method= self.call_back_menu_file_quit, help= 'Save Changes and Exit')) edit = self.menu.stuff(MenuSub('_Edit', help='Update a Row')) edit.stuff(Mitem('_Search Replace...', id=gtk.STOCK_FIND_AND_REPLACE, c='F', method=self.call_back_menu_edit_search_replace, help='Find Words and Change Them')) edit.stuff(Mitem('_Go to Row...', id=gtk.STOCK_JUMP_TO, method= self.call_back_menu_edit_go_to, help= 'Choose a Line Number')) edit.stuff(Msep()) edit.stuff(Mitem('_Insert Row', id=gtk.STOCK_ADD, c='I', method=self.call_back_menu_edit_insert_row, help= 'Create a Blank Line Above')) edit.stuff(Mitem('Insert _After Row', c='I', method= self.call_back_menu_edit_insert_after_row, help= 'Create a Blank Line Below')) edit.stuff(Mitem('I_nsert at Top', method= self.call_back_menu_edit_insert_at_top, help= 'Create a Blank Line at the Beginning')) edit.stuff(Mitem('Insert at _Bottom', method= self.call_back_menu_edit_insert_at_bottom, help= 'Create a Blank Line at the End')) edit.stuff(Msep()) edit.stuff(Mitem('_Freeform Entry', method=self.call_back_menu_edit_free_form_entry, help='Paste-in a Blob of Text')) edit.stuff(Mitem('Fie_ld Entry', method=self.call_back_menu_edit_field_entry, help='Fill-in Blanks')) edit.stuff(Msep()) edit.stuff(Mitem('Cu_t Row', id=gtk.STOCK_CUT, c='X', method=self.call_back_menu_edit_remove_row, help= 'Remove Row to Clipboard')) edit.stuff(Mitem('_Copy Row', id=gtk.STOCK_COPY, c='C', method=self.call_back_menu_edit_copy_row, help= 'Copy Row to Clipboard')) edit.stuff(Mitem('_Paste', id=gtk.STOCK_PASTE, c='V', method=self.call_back_menu_edit_paste_row, help= 'Insert Row(s) from Clipboard')) edit.stuff(Msep()) preferences = edit.stuff(MenuSub('Pr_eferences', id=gtk.STOCK_PREFERENCES, help= 'Change Tonto Configuration Settings.')) preferences.stuff(Mitem('Select _MODEM', method=self.call_back_menu_edit_preferences_modem, help=AUTO_DIAL_SQUIB)) view = self.menu.stuff(MenuSub('_View', help='Show a Row')) view.stuff(Mitem('_View as Text', method=self.call_back_menu_view_as_text, help= 'Show Contents of Selected Row as Blob(s) of Text')) view.stuff(Mitem('Traverse _Link', method=self.call_back_menu_view_traverse_link, help='Open Web Page in Browser')) view.stuff(Mitem('View Calendar _Month', method=self.call_back_menu_view_calendar_month, help= 'Show Contents of Selected Tab as a Monthly Calendar')) view.stuff(Mitem('View _Alarms', method=self.call_back_menu_view_alarms, help='Show Alarms Set to Go Off Today')) mark = self.menu.stuff(MenuSub('_Mark', help= 'Check/Uncheck Rows')) mark.stuff(Mitem('Toggle _Mark', id=gtk.STOCK_APPLY, method=self.call_back_menu_mark_toggle, help='Checked vs Unchecked')) mark.stuff(Mitem('Mark _All Rows', c='A', method=self.call_back_menu_mark_all, help='Check All')) mark.stuff(Mitem('Togg_le All Rows', c='A', method= self.call_back_menu_mark_toggle_all, help= 'Reverse All Checkmarks')) mark.stuff(Mitem('Cu_t Marked Rows', method=self.call_back_menu_mark_cut, help='Remove Checked to Clipboard')) mark.stuff(Mitem('_Copy Marked Rows', method=self.call_back_menu_mark_copy, help='Copy Checked to Clipboard')) help = self.menu.stuff(MenuSub('_Help', id=gtk.STOCK_HELP, help= 'Instructions')) help.stuff(Mitem('_Contents', id=gtk.STOCK_HELP, c='H', method=self.call_back_menu_help_contents, help= 'Browse Tonto Manual')) help.stuff(Mitem('GNU General Public _License', method=self.call_back_menu_help_gpl, help='Statement of Copying Permission')) help.stuff(Mitem('_About', method=self.call_back_menu_help_about, help='Show Version Number')) return self def conjure_menu(self): self.menu = MenuMainBar() self.conjure_menu_items() result = self.menu.compile(accel_group=self.menu_accelerator_keys) paste = self.menu.find_item('/Edit/Paste') free_form = self.menu.find_item('/Edit/Freeform Entry') traverse_link = self.menu.find_item('/View/Traverse Link') cal_month = self.menu.find_item('/View/View Calendar Month') alarms = self.menu.find_item('/View/View Alarms') edit = self.menu.find_sub('/Edit') edit.connect('expose-event', self.call_back_menu_edit_expose_event, paste, free_form) view = self.menu.find_sub('/View') view.connect('expose-event', self.call_back_menu_view_expose_event, cal_month, alarms, traverse_link) return result def conjure_popup_items(self): self.popup.stuff(Mitem('View as Text', method=self.call_back_menu_view_as_text, help='Show Contents of Selected Row as Blob(s) of Text', )) self.popup.stuff(Mitem('Traverse Link', method=self.call_back_menu_view_traverse_link, help='Open Web Page in Browser', )) self.popup.stuff(Msep()) self.popup.stuff(Mitem('Insert Row', id=gtk.STOCK_ADD, method=self.call_back_menu_edit_insert_row, help='Create a Blank Line Above', )) self.popup.stuff(Mitem('Insert After Row', method=self.call_back_menu_edit_insert_after_row, help='Create a Blank Line Below', )) self.popup.stuff(Mitem('Insert at Top', method=self.call_back_menu_edit_insert_at_top, help='Create a Blank Line at the Beginning', )) self.popup.stuff(Mitem('Insert at Bottom', method=self.call_back_menu_edit_insert_at_bottom, help='Create a Blank Line at the End', )) return self def conjure_popup(self): self.popup = MenuMain() self.conjure_popup_items() result = self.popup.compile() traverse_link = self.popup.find_item('/Traverse Link') result.connect('expose-event', self.call_back_popup_expose_event, traverse_link) return result def get_matrix(self): rel_ndx = self.notebook.get_current_page() return (self.tabs)[rel_ndx] def tab_mux(self, method, *calling_sequence): return method(self.get_matrix(), *calling_sequence) def get_relation(self): rel_ndx = self.notebook.get_current_page() return (self.relation_list)[rel_ndx] def rel_mux(self, method, *calling_sequence): return method(self.get_relation(), *calling_sequence) def call_back_destroy(self, *calling_sequence): self.config.checkpoint() try: self.relation_list.close_all() except (ErrorBadData, ErrorBadDictionary), __Error: diagnostic = DlgAssertion(parent=MAIN, msg= ''' Close %s This program encountered an error trying to save a data file. The file is now corrupted. The data is missing, incomplete, damaged, or otherwise bad, invalid or illegible. ''' % __Error) diagnostic.run() diagnostic.destroy() gtk.main_quit() return def call_back_delete_event(self, *calling_sequence): """Return True to block termination. """ return False def call_back_menu_file_open(self, *calling_sequence): dlg = DlgOpenRelation() done = False while not done: result = dlg.run() if result in [gtk.RESPONSE_OK]: file_name = strip_selected_file_name_extensions(dlg.get_filename()) try: self.relation_list.open( file_name=file_name, name=dlg.relation_name.get_unicode(), tag=dlg.relation_tag.get_unicode(), class_of=RELATION_CLASS[dlg.relation_type.get_unicode()], mod_config=True, on1=True, ) self.append_tab((self.relation_list)[-1]) done = True except (ErrorDuplicateTag, ErrorDuplicateFile), __Error: diagnostic = DlgAssertion(parent=MAIN, msg= ''' Open %s ''' % __Error) diagnostic.run() diagnostic.destroy() except (ErrorNoColHeaders, ErrorBadDictionary, ErrorBadData), __Error: diagnostic = DlgAssertion(parent=MAIN, msg= ''' Open %s This program encountered an error trying to load a data file. The data was missing, incomplete, damaged, or otherwise bad, invalid or illegible. The file was skipped. ''' % __Error) diagnostic.run() diagnostic.destroy() elif result in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_NONE, gtk.RESPONSE_DELETE_EVENT]: done = True dlg.destroy() return False def call_back_menu_file_close(self, *calling_sequence): rel_ndx = self.notebook.get_current_page() self.remove_tab(rel_ndx) try: self.relation_list.close(rel_ndx) except (ErrorBadData, ErrorBadDictionary), __Error: diagnostic = DlgAssertion(parent=MAIN, msg= ''' Close %s This program encountered an error trying to save a data file. The file is now corrupted. The data is missing, incomplete, damaged, or otherwise bad, invalid or illegible. ''' % __Error) return False def call_back_menu_file_checkpoint(self, *calling_sequence): self.rel_mux(Relation.save) return False def call_back_menu_file_revert(self, *calling_sequence): rel_ndx = self.notebook.get_current_page() self.relation_list.revert(rel_ndx) self.tab_mux(Matrix.set_relation, (self.relation_list)[rel_ndx]) self.tab_mux(Matrix.conjure_cols) return False def call_back_menu_file_merge(self, *calling_sequence): dlg = gtk.FileSelection(title='Merge') dlg.hide_fileop_buttons() dlg.set_select_multiple(False) dlg.set_filename(os.path.expanduser('~/.Tonto.*.csv')) dlg.show_all() dlg.complete('.Tonto.*.csv') # Yes, this method needs to be called late. if dlg.run() in [gtk.RESPONSE_OK]: file_name = strip_selected_file_name_extensions(dlg.get_filename()) try: self.rel_mux(Relation.merge, file_name) self.tab_mux(Matrix.conjure_cols) except (ErrorNoColHeaders, ErrorBadDictionary, ErrorBadData), \ __Error: diagnostic = DlgAssertion(parent=MAIN, msg= ''' Merge %s This program encountered an error trying to merge a data file. The data was missing, incomplete, damaged, or otherwise bad, invalid or illegible. This tab may have been corrupted, too. You can look at lines near the bottom to see. You may Revert it to the latest version. ''' % __Error) diagnostic.run() diagnostic.destroy() dlg.destroy() return False def call_back_menu_file_export(self, *calling_sequence): dlg = gtk.FileSelection(title='Export') dlg.hide_fileop_buttons() dlg.set_select_multiple(False) dlg.set_filename(os.path.expanduser('~/.Tonto.*.csv')) dlg.show_all() dlg.complete('.Tonto.*.csv') # Yes, this method needs to be called late. if dlg.run() in [gtk.RESPONSE_OK]: rel_ndx = self.notebook.get_current_page() file_name = strip_selected_file_name_extensions(dlg.get_filename()) self.relation_list.save_as(rel_ndx, file_name) dlg.destroy() return False def call_back_menu_file_add_remove_fields(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_file_add_remove_fields, *calling_sequence) return False def call_back_menu_file_choose_display_columns(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_file_choose_display_columns, *calling_sequence) return False def call_back_menu_file_quit(self, *calling_sequence): if not self.call_back_delete_event(): self.call_back_destroy() return False def call_back_menu_edit_expose_event(self, widget, event, menu_edit_paste, menu_edit_free_form, *calling_sequence): menu_edit_paste.set_sensitive(self.xclipboard_import.is_target_offered(['-Tonto-row-list'])) menu_edit_free_form.set_sensitive(hasattr(self.get_matrix().relation, 'free_form_entry')) return False def call_back_menu_view_expose_event(self, widget, event, menu_view_calendar_month, menu_view_alarms, menu_view_traverse_link, *calling_sequence): is_calendar = isinstance(self.get_matrix().relation, RelationCalendar) menu_view_traverse_link.set_sensitive(hasattr(self.get_matrix().relation, 'traverse_link')) menu_view_calendar_month.set_sensitive(is_calendar) menu_view_alarms.set_sensitive(is_calendar) return False def call_back_popup_expose_event(self, widget, event, popup_traverse_link, *calling_sequence): popup_traverse_link.set_sensitive(hasattr(self.get_matrix().relation, 'traverse_link')) return False def call_back_menu_edit_search_replace(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_search_replace, *calling_sequence) return False def call_back_menu_edit_go_to(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_go_to, *calling_sequence) return False def call_back_menu_edit_insert_row(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_insert_row, *calling_sequence) return False def call_back_menu_edit_insert_after_row(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_insert_after_row, *calling_sequence) return False def call_back_menu_edit_insert_at_bottom(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_insert_at_bottom, *calling_sequence) return False def call_back_menu_edit_insert_at_top(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_insert_at_top, *calling_sequence) return False def call_back_menu_edit_free_form_entry(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_free_form_entry, *calling_sequence) return False def call_back_menu_edit_field_entry(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_field_entry, *calling_sequence) return False def call_back_menu_edit_calc_calendar_float(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_calc_calendar_float, *calling_sequence) return False def call_back_menu_edit_remove_row(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_remove_row, *calling_sequence) return False def call_back_menu_edit_copy_row(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_copy_row, *calling_sequence) return False def call_back_menu_edit_paste_row(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_edit_paste_row, *calling_sequence) return False def call_back_menu_edit_preferences_modem(self, *calling_sequence): dlg = DlgChooseModem(self) if dlg.run() in [gtk.RESPONSE_OK]: self.config.set('MODEM', 'port', dlg.combo_box.get_unicode()) dlg.destroy() return False def call_back_menu_view_as_text(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_view_as_text, *calling_sequence) return False def call_back_menu_view_traverse_link(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_view_traverse_link, *calling_sequence) return False def call_back_menu_view_calendar_month(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_view_calendar_month, *calling_sequence) return False def call_back_menu_view_alarms(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_view_alarms, *calling_sequence) return False def call_back_menu_mark_toggle(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_mark_toggle, *calling_sequence) return False def call_back_menu_mark_all(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_mark_all, *calling_sequence) return False def call_back_menu_mark_toggle_all(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_mark_toggle_all, *calling_sequence) return False def call_back_menu_mark_cut(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_mark_cut, *calling_sequence) return False def call_back_menu_mark_copy(self, *calling_sequence): self.tab_mux(Matrix.call_back_menu_mark_copy, *calling_sequence) return False def call_back_menu_help_contents(self, *calling_sequence): self.config.dump_gpl(GPL_NAME) self.config.dump_html(HELP_HTML_NAME) htmlfile_name = os.path.expanduser(HELP_HTML_NAME) open_url(htmlfile_name) return False def call_back_menu_help_gpl(self, *calling_sequence): self.config.dump_gpl(GPL_NAME) txt_file_name = os.path.expanduser(GPL_NAME) open_url(txt_file_name) return False def call_back_menu_help_about(self, *calling_sequence): dlg = DlgAssertion(parent=self, msg= ''' Tonto v%s.%s (%s) %s ''' % (VERSION[ZERO], VERSION[1], RELEASE_DATE.strftime(DATE_EDIT_FORMAT), paragraph_align(COPYRIGHT, strip_markup=False))) dlg.run() dlg.destroy() return False LATEST_TARGET = LATEST_REPLACEMENT = NULL # import doctest # doctest.testmod() MainWindow() gtk.main() # Fin