#!/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.
''')
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 €
1.000,00 €
1.000,00 €
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 monthoffset 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
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_='
')
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 WorkEditors.).''')
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 Workis 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 ['