pfm-2.0.8/0000755000175000017500000000000012110715404010450 5ustar wimwimpfm-2.0.8/pfm.conf0000644000175000017500000000350511476502020012106 0ustar wimwim# pfm.conf # This is the configuration file of Postgres Forms (pfm) on UNIX platforms. # Written by Willem Herremans. # pfm.conf is installed either in pfm's installation directory or in # /etc. If pfm.tcl does not find it in its installation directory, it # looks for it in /etc. # The syntax of this file is the following: # # - Every empty line is ignored; # # - Every line starting with a "#" is a comment line; # # - Every other line should be a Tcl list (i.e. a space separated list) # containg 2 items where the first item is the name of a configuration # parameter and the second item its value. If a value contains spaces # enclose it in { and }. # # docDir: the directory in which the package documentation # is installed. If it is a relative pathname, it is assumed to be # relative to the installation directory. docDir doc # licenseDir: the directory which contains the text of the GNU licence # and the help texts for the options. If it is a relative pathname, # it is assumed to be relative to the installation directory. licenseDir license # exampleDir: the directory which contains the example databases. If # it is a relative pathname, it is assumed to be relative to the # installation directory. exampleDir examples # languagdeDir: the directory whcih contains the user interface # strings for all languages. languageDir msgs # defaultBrowser: the default value for the "browser" option. defaultBrowser {firefox %s} # defaultPrintcmd: the default value for the "printcmd" option. defaultPrintcmd {{a2ps --output=%ps --$(portrait_or_landscape=portrait) --rows=$(nr-of-rows=1) --columns=$(nr-of-columns=1) --major=rows --chars-per-line=$(nr_of_chars_per_line=90) --center-title=$(title=Report) %txt} {ps2pdf -sPAPERSIZE=a4 %ps %pdf} {evince %pdf}} pfm-2.0.8/CHANGE_LOG.txt0000644000175000017500000013221712110715265012652 0ustar wimwimChange log: ----------- Version 2.0.8 (2013-02-18): Bugs fixed: 1. Nr : 1011307 Title : pfm refuses to start on Tcl/Tk 8.6 Description : The problem comes from the statement 'package require Itcl 3.4'. Tcl/Tk 8.6 has version 4.0 instead of 3.4. I thought that 'package require Itcl 3.4' means 'version 3.4 or higher', but, looking at the Tcl documentation now, I must admit that this is wrong. What it actually means is 'version 3.4 or higher, but not as high as 4.0'. So, 3.5 and 3.7.11 would be OK, but 4.0, 4.2.7 or 5.0 are not OK. See Tcl documentation for package require and package vsatisfies. Especially, look at rule 3: 'A "min-bounded" requirement is a "bounded" requirement in disguise, with the max part implicitly specified as the next higher major version number of the min part. A version satisfies it per the rules above.' In my opinion, this makes it impractical to specify version numbers with the 'package require' statements. The best way forward is probably to remove all version specifications from the package require statements. 2. Nr : 1011308 Title : Reports with paramaters fails if parameter name consists of more than 1 word Description: Reports with paramater(s) fails if parameter name consists of more than 1 word. This is caused by a bug in Itcl (see http://sourceforge.net/p/incrtcl/bugs/211/ and http://sourceforge.net/p/incrtcl/bugs/238/). I have worked around this bug by introducing a translation table for the variables bound to entry widgets in class ParmObject in source report.tcl. A similar solution was already introduced for forms (see bug http://pgfoundry.org/tracker/index.php?func=detail&aid=1011062&group_id=1000236&atid=1662). Version 2.0.7 (2011-09-27): New version of pgintcl: 3.4.0 Bugs fixed: 1. Nr : 1071 Title : Tables in schemas other than public Description : Tables in schemas other than the 'public' schema cannot be accessed by pfm forms. This is caused by the way pfm encloses tablenames in double quotes when constructing SQL statements. If the form definition has myschema.mytable as tablename, pfm uses "myschema.mytable" in the SQL statements it constructs. This should be "myschema"."mytable" instead. Type : error Priority : medium Against : 2.0.6 Promised : 2.0.7 Status : reported Solution : The way pfm quotes tablenames is modified as follows. If the tablename in pfm_form already contains double quotes, pfm does not touch it. If the tablename in pfm_form does not contain double quotes, pfm encloses every dot in double quotes and puts double quotes around the whole tablename. Example 1: tablename in pfm_form: "first.last" The tablename in pfm_form contains double quotes. So pfm does not touch it. SQL statement generated in update statement UPDATE "first.last" SET "Name" = 'Jones' WHERE "Nr" = 5 Example 2: tablename in pfm_form: myschema.mytable The tablename in pfm_from does not contain double quotes. So, pfm considers the dot as a word separator. SQL statement genereated in update statement UPDATE "myschema"."mytable" SET "phoneNumber" = '06 874 89 90' WHERE ("id" = 1072) Originator : Martin Hoffmann Date : 2011-09-27 2. Nr : 1072 Title : pfm ignores the orderby attribute in pfm_link Description : pfm ignores the orderby attribute in pfm_link. Type : error Priority : medium Against : 2.0.6 Promised : 2.0.7 Status : reported Solution : method 'followLink' of class 'FormTab' is corrected. Originator : wim Date : 2011-09-27 3. Nr : 1073 Title : Tcl run time error when opening empty listbox Description : Class ListBox in source misc.tcl causes a Tcl run time error when displaying an empty ListBox. Type : error Priority : medium Against : 2.0.6 Promised : 2.0.7 Status : reported Solution : Constructor of class ListBox tests for empty valuelist. If valuelist empty, pfm does not attempt to set focus to a list item. Originator : wim Date : 2011-09-27 Version 2.0.6 (2011-07-03): - Bugfix: Nr : 1070 Title : Attribute names with more than one word Description : If a table has a column name consisting of more than one word, the corresponding values are not displayed on the form and they cannot be modified. Type : error Priority : medium Against : 2.0.5 Promised : 2.0.6 Status : reported Solution : This is caused by a bug in Itcl (Version 3.4b1, patchLevel 3.4.0). If an object instance variable is an array element with a name consisting of two words and if that variable is bound to an entry widget, the array element's value is not displayed in the entry widget. If a global variable is an array element with a name consisting of two words and if that variable is bound to an entry widget, the array element's value is displayed allright in the entry widget. A work around is implemented in pfm. Instead of binding record($attrib) directly to the form's entry widgets, record($recordIdx($attrib)) is bound to the form's entry widgets where recordIdx acts as a conversion table which maps the attribute names ($attrib) to integers. Originator : Mike Ashworth Date : 2011-07-03 Version 2.0.5 (2010-11-21): - The aggregate function STDDEV has been modified to be in line with the definition used by PostgreSQL. In previous versions, pfm used the "population standard deviation" SQRT (SUM( (value_i - AVG(value))**2 ) / N ) whereas PostgreSQL used the "sample standard deviation" SQRT (SUM( (value_i - AVG(value))**2 ) / (N - 1) ) From now on pfm also uses the "sample standard deviation" - The default printcmd option has been slightly modified on Linux systems: the option "-sPAPERSIZE=a4" has been added to the ps2pdf subcommand. This is necessary on some systems where ghostscript implicitly assumes papersize "letter". - The included Tclkit and Pgintcl have been upgraded to versions 8.5.9 and 3.2.1 respectively. - Since version 2.0.5, I don't include binaries for the 64-bit architectures anymore. The reason is that the 64-bit binaries of the Tclkit for linux are not published by the Equi4 anymore. Also, I don't have a 64-bit computer to test the 64-bit binaries. I am reasonably confident that the 32-bit binaries work without problem on a 64 bit machine. If you insist on running a 64-bit version, you can easily replace the 32-bit binaries of tclkit with their 64-bit variants, AFTER installation. To do so: - On Linux replace the installed 32 bit binary tclkit in the pfm installation directory with its 64 bit variant. Make sure that it is still called tclkit and that it is executable. - On Windows replace the installed 32 bit binaries tclkit.exe and tclkitsh.exe in the pfm installation directory with their 64 bit variants. Make sure that they are still called tclkit.exe and tcklkitsh.exe. Version 2.0.4 (2008-10-09): Bugfix: Nr : 1069 Title : Run time error in get parameters for report Description : If a report has more than one parameter that has to be provided at run time, a fatal error occurs at run time. Type : error Priority : high Against : 2.0.3 Promised : 2.0.4 Status : promised Solution : This is caused by an error in the constructor for class ParmObject in source report.tcl. There is a loop which defines a label and an entry for each parameter, but this loop lacks an "incr idx", which causes all labels and entries to be defined with the same name. Adding an "incr idx" ate the end of the loop, solves this problem. Originator : wim Date : 2008-10-09 Version 2.0.3 (2008-05-04): Bugfixes: Nr : 1067 Title : Connection with psql fails on PostgreSQL 8.3 Description : The connection with 'psql' fails on the Windows version with PostgreSQL 8.3. Since version 8.3, psql no longer accepts the command line option '--user'. It has to be '--username'. Type : error Priority : high Against : 2.0.2 Promised : 2.0.3 Status : promised Solution : This error is corrected in method 'connect_sql' in postgresql.tcl. Originator : Peter ZhaoKai Date : 2008-05-04 Nr : 1068 Title : Background colours wrong on some systems Description : On Ubuntu Linux 8.04, the background colours of entry and text widgets are gray instead of white. Type : error Priority : low Against : 2.0.2 Promised : 2.0.3 Status : promised Solution : This is corrected in procedure installTheme by adding: option add *Entry.background {White} option add *Text.background {White} Originator : wim Date : 2008-05-04 Version 2.0.2 (2008-04-28): Enhancement: Nr : 1065 Title : Navigation buttons on form Description : It would be nice to have buttons on the form for navigating through the internal buffer. The current solution with the "Go to" menu is not so obvious. Type : enhancement Priority : low Against : 2.0.1 Promised : 2.0.2 Status : promised Solution : Introduce arrow-buttons on the statusbar left and right of the record number indication. Originator : Gerd König Date : 2008-04-24 Bugfixes: Nr : 1063 Title : No error message when report query fails Description : If the user runs a report and the report's query has an SQL syntax error, nothing is printed: no report header and no error message. Type : error Priority : medium Against : 2.0.1 Promised : 2.0.2 Status : promised Solution : The runQuery method of class Report should also return the errMsg. If runQuery fails, the constructor of class Report should print the report header and the errMsg returned by runQuery. Originator : wim Date : 2008-04-22 Nr : 1064 Title : An error occurs when a report query is succesfull but returns no data Description : If the user runs a report and the report's query is syntactically OK but returns no data, an error occurs. This situation must be more clearly communicated to the user. Type : error Priority : medium Against : 2.0.1 Promised : 2.0.2 Status : promised Solution : The runQuery method of class Report should not return success when the query does not return any data. It should return an error message that can be printed following the report header. Originator : wim Date : 2008-04-22 Nr : 1066 Title : Problem with "AltUnderlined" keyboard shortcuts Description : The keyboard shortcuts and on the forms window for the Expand and Select buttons let Tk completely derail on the Windows version: it goes in an endless loop with high processor load. It is not clear what causes this problem. It does not occur on the Linux version. Type : error Priority : medium Against : 2.0.1 Promised : 2.0.2 Status : promised Solution : Replacing the "after idle" with "after 200" in the bind statement of defineButton proc seems to solve the problem. Removing the "after" statement altogether also solves the problem, but introduces another problem which only occurs on the Linux version: a keyboard shortcut that destroys a text widget causes Tcl/Tk to raise an error because it still tries to do something with the text widget after it has already been destroyed. Originator : wim Date : 2008-04-28 Version 2.0.1 (2008-04-20): Bugfixes: - 1061: Help file for non-English locales If the "Help -> Help file" menu is invoked from a non-English locale and if no translation for that locale exists the "doc/index.html" page is displayed. The expected behaviour is that the English version is displayed as fallback: "doc/en/index.html". - 1062: Error when pressing open or run on empty listbox of main window If "Open" is pressed on a the "Forms" or "Design" tab of the main window when thare are no forms in the listbox, an error occurs. If "Run" is pressed on a the "Reports" tab of the main window when thare are no reports in the listbox, an error occurs. The "Open" and "Run" buttons should be disabled in these cases. Version 2.0.0 (2008-04-18): - Complete redesign: pfm now requires Tcl/Tk version 8.5 and Itcl, the extension for object oriented programming. The version of the pfm_tables remains 1.5.0, i.e. no database conversion is required when upgrading from a 1.5.* version. - New features: Most new features have to do with an improved look of the application, but internally the changes are much more extensive. - New widgets such as notebook, combobox and treeview - Themes (choosable under options) - Antialiased instead of bitmapped fonts under Linux - It is now possible to have more than 1 form open simultaneously. - When following a link to another form, a new tab is created on the form window such that the form and record from which the link originates, remains available by activating its tab. - The psql function is more convenient now. There is less need to press the Run button: - if you press the Return key just after a ';', the command is automatically sent to psql - if you press the Return key when the first character in the SQL statement area is a '\' the command is automatically sent to psql. - All user interface texts are now controlled by the Tcls's msgcat package. This makes it easier to translate pfm to other languages. No translations have been done yet. - The printcmd option has changed. It is now a list of commands and printing now always uses a temporary file, instead of a command pipe. - The options file has a new structure and is renamed to .pfm2 on UNIX paltforms and to APPDATA\pfm\pfm2.conf on Windows platforms. - A new, much more advanced installer script for the Linux variant which can also install a menu entry and a desktop icon if the xdg-utils package is installed. - A new NSIS installer for the Windows variant (see http://nsis.sourceforge.net for more details). It also installs a desktop icon and a Start Menu entry under 'Programs'. - Dropped features: - It is no longer possible to change the fonts used for the application, but it is possible to increase (decrease) the font size. - pgintcl version 1.5.0 is no longer included. This was only necessary for PostgreSQL versions before 7.4. Version 1.5.4 (2007-10-29): - Bug 1060: Nr : 1060 Title : Run time error in aggregate functions Description : A run time error in the aggregate functions of the report generator like SUM, AVG and STDDEV occurs in the following situation: - the report is based on a LEFT OUTER JOIN; - there is no record for the right table, i.e. nulls are returned for the right table; - the aggregate function is applied to an attribute of the right table. Type : error Priority : medium Against : 1.5.3 Promised : 1.5.4 Solution : Modification on aggregate functions SUM, AVG and STDDEV such that null-values are ignored. So, for AVG and STDDEV they are also ignored for counting the number of values. Originator : wim Date : 2007-10-29 Version 1.5.3 (2007-09-22): - Feature request 1059: Nr : 1059 Title : Readonly attribute with default initial value Description : It would be nice to have the possibility to set an atrribute "tgReadOnly" and still have it assigned the initial default value defined in the form definition. At the moment that is not possible because "tgReadOnly" attributes are not considered attributes of the form's main table. Type : enhancement Priority : medium Against : 1.5.2 Promised : 1.5.3 Solution : Change 1 statement in proc getAttributes: statement in 1.5.2: if { ![string equal $typeofget {tgReadOnly}] } then { lappend tableAttribList $attribute } statement in 1.5.3: if { ($typeofget ne {tgReadOnly}) || ($defVal ne {}) } then { lappend tableAttribList $attribute } Originator : Mark Hindley Date : 2007-09-18 - Correction: In the ::gen:: module there where a number of statements with "upvar 0". Although I have never seen the procedures ListBox or TextEdit fail in pfm, I think now, after reading the tcl documentation again, that it should be "upvar #0" instead of "upvar 0". - New feature: The TextEdit window now also has a "Find string" function. - tclkit 8.4.13 has been upgraded to version 8.4.15 Version 1.5.2 (2007-08-21): - Bug 1057: convert_from_1.2.0.sql script fails on PostgreSQL 7.4 The convert_from_1.2.0.sql script fails on PostgreSQL version 7.4. on two statements: 1. UPDATE pfm_form SET help = E'The data returned by the report''s SQL SELECT statement may be considered as a table with a column for each ''field'' specified after the ....' 2. ALTER TABLE ONLY pfm_form ADD COLUMN sqlorderby text, ADD COLUMN sqllimit text; Solution: 1. Instead of using the E'....' syntax, convert_from_1.2.0 now uses the standard SQL syntax: string is delimited by single quotes, single quotes within the string are doubled and strings of more than 1 line are also written as several lines instead of using "\n". Example: UPDATE test SET poem = 'Wee, sleekit, cow''rin, tim''rous beastie, O, what a panic''s in thy breastie!'; This makes it necessary to change the ConvertToUTF-8 procedure in pfm.tcl as well. When pfm runs the convert_from_1.2.0.sql script, it creates a temporary file which is the result of converting the character encoding to UTF-8. This temporary file is made using the standard tcl procedures "open" and "puts". These procedures normally use the platform specific conventions for creating text files. Hence, on Windows the temporary file uses CR LF as line endings. When this temporary file is offered to psql with "i", psql recognizes the LF as line ending, but interprets the CR as an extra character of the string. To avoid this problem, the procedure "ConvertToUTF-8" in pfm.tcl is changed. The statement fconfigure $outFile -encoding utf-8 is changed to fconfigure $outFile -encoding utf-8 -translation lf 2. Make two statements: ALTER TABLE ONLY pfm_form ADD COLUMN sqlorderby text; ALTER TABLE ONLY pfm_form ADD COLUMN sqllimit text; - Bug 1058: If the user switches mode (normal / design) while a form is open and the user then does any operation on the open form, a run time error occurs. This is caused by the fact that the global variable formsArray is only filled with the forms that also appear in the listbox on the main window. To avoid this problem, the procedure refreshFormsList is modified such that formsArray is filled with all the forms, regardless of the mode (normal/design). Version 1.5.1 (2007-02-27): - Bug 1054: Error in locateRecord: When returning from a link and when there are no records left in the form that is about to be opened, "locateRecord" fails because "match" is a non-existent variable when executing "if {$match}" just after the for loop. A "set match 0" has been added before the for loop. - Bug 1055: Focus not visible in Windows version: In the Windows version, it is not clear which entry has the input focus. This makes it impossible to to use the form without using the mouse. The option "highlightThickness" has been set to 1. This was the default on UNIX platforms, but not on Windows. - Bug 1056: Run time error when closing Reports window. Sometimes closing the "Reports" window causes a run time error at set ::report::windowSize(.report) \ [string map {{+0+0} {}} [wm geometry .report]] in proc cmdReportSQL. It is not reproducable. I assume that it is caused by the fact that this statement, which is bound to the event on toplevel .report, is executed sometimes after .report has already been destroyed. Therefore a "catch" has been added, to avoid raising a run time error. The same has been done for all statements boud to events. Version 1.5.0 (2007-02-13): - The designer of a form can define a default ORDER BY clause which is displayed in teh "Open form" window when the user opens a form. - The designer of a form can limit the number of records loaded into the form's internal buffer. This makes it possible to handle large tables without using a lot of memory. - When working with forms, it is possible to open several "Expand" windows simultaneously. - The options editor has been made more user friendly. - The report generator can now handle mult-line data in a table layout also. - You can specify a maximum line length for data displayed in a report. When data exceed that maximum, one or more line breaks are inserted before printing the report. This feature, together with the previous one makes the report generator much more usable. - There is an automatic update on the pfm_tables when you start using version 1.5.0 on a database that has already been used with a previous version of pfm. - Many windows have resizable panes now. - You can use the mouse wheel to scroll the form canvas now. Version 1.4.3 (2007-01-10): - Bug 1052 solved: pfm hangs when quiting, in some versions of Tcl/Tk. - Version 3.0.1 of pgintcl replaces version 3.0.0 - Version 8.4.13 of Tclkit replaces version 8.4.11 - The pfm project has moved to pgFoundry at http://pgfoundry.org/projects/pfm/ - There is a new option, called 'fontstyle', which determines whether the fonts for menus, buttons, labels and listboxes are 'bold' or 'normal'. The default is 'normal'. So, UNIX users will notice a difference with respect to earlier versions. They can go back to bold fonts by setting this option to 'bold'. - The options editor now has a font chooser, which makes it easier to modify the 'fontmonospace' and 'fontproportional' options. Version 1.4.2 (2006-07-05): - Bug 1049 solved: When the form for a view is displayed, the Record menu is disabled now. - Feature request 1050: When pressing [Update] or [Add] on a form, the input focus should stay on the same attribute entry. Similarly, when pressing [OK] or [Cancel] when updating or adding a record, the input focus should stay on the same attribute entry. - Feature request 1051: The open form window should also have a Form -> Close menu. - The "relief" of the attribute name labels on the form window has been changed to "sunken". - I have finally discovered how to get a meaningful result from the canvas "bbox" command. First call "update" before calling the canvas "bbox" command. That has been used to get the correct size for the canvas scrollregion in displayAttribLabels and displayAttribEntries. - The scrollForm procedure has been reworked. The attribute with input focus now stays nicely in the visible part of the canvas. Version 1.4.1 (2006-07-01): - The user interface has been modified to make it possible to operate pfm without using the mouse. See text file mouseless-operation.txt in the doc sub-directory. Acknowledgement: I hereby thank Mark Hindley, who has suggested --------------- to improve pfm in this area. Many of his ideas have found their way into this version. - The window that used to be called "Query mode" of form, has been renamed to "Open form" window, and the so called "Paste buttons" have been moved to 2 menus which can be invoked by 2 buttons: o paste attribute Names; and o paste attribute Valuse. - The help file has been updated accordingly. - The help file's layout and structure has been reworked. It now uses a less poor html. On browsers that support frames, the table of contents is displayed in the left frame and the body of the document is displayed in the right frame. Version 1.4.0 (2006-06-13): - This a preliminary version for evaluation and testing. - The user interface has been modified such that there is less need for to user to use the mouse. See text file mouseless-operation.txt in the doc sub-directory. - The help file has not been updated yet. - This version is only available for UNIX platforms. Version 1.3.0 (2006-05-29): - On UNIX and Linux systems, pfm now uses the UNIX "cat" utility instead of the "cat.tcl" script. This removes the necessity to have the tclkit installed on UNIX systems on which pfm runs in the system's native Tcl/Tk environment. The README.txt file has been updated accordingly. - On UNIX and Linux systems, pfm now has a configuration file named pfm.conf which defines values for some configuration parameters. It resides either in pfm's installations directory or in /etc. - The files in the distribution have been moved to other subdirectories to ease the derivation of a Debian package. - There are 2 new menu items in the Tools menu: o install pfm_* tables o install example database. - The user is no longer asked whether to install the pfm_* tables when a database is opened that does not contains them yet. Instead, a warrning that the pfm_* tables are not installed is issued and a hint is given to use the "Tools -> Install pfm_* tables" or the "Tools -> Install example database" menu. - The Tools -> Install example database menu eases the installation of the example databases. - When importing an SQL-script in the RunSQL window, the user can now also select the character encoding with which the SQL-script was made. - The help text has been enhanced and corrected on several locations: o help text for pfm_form was corrected, also the help text inside pfm_form. In particular, the statement that a form has a one-to-one relation with just one table was wrong. A form can only administer the data of one table, but it is possible that there are more forms for the same table. o Modifications to document the new features. - Some menu items have become context aware: o Database -> Close is disabled when no database is open. o Database -> Open ... is disabled when a database is open. o Install Tools -> Install pfm_* tables and Tools -> Install example database are disabled when a database that already contains the pfm_* tables is open. - pfm no longer touches the pfm_* tables when it finds an older, compatible version. In this way older, compatible versions of pfm can continue accessing databases that have been accessed by this version of pfm. Similarly, when installing the pfm_tables or an example database and when converting a database of versions before 1.2.0, pfm marks the pfm_tables as belonging to version 1.2.0 (the earliest compatible version). In this way, older, compatible versions of pfm can access databases that have been made with this version of pfm. - E-mail address for sending bug reports has been changed in README.txt and it has also been added in the 'Help -> About' response. Version 1.2.5 (2006-01-04): - Bugfix: The layout of a multi-section report was wrong if 2 consecutive records have the same values for the fields of a higher numbered section but different values for the fields of a lower numbered section. Version 1.2.4 (2005-12-28): - The default value for the option "browser" has been modified to "firefox %s" in the case of UNIX. - On UNIX, the browser command is now implicitly run in the background so that the pfm user interface is not blocked by calling the browser. So, it is no longer necessary to end the browser command with a "&". On Windows, that was already the case before. - Version 8.4.9 of the tclkit has been replaced with version 8.4.11. - The ".gif" files of the on-line help have been converted to ".png" format. Version 1.2.3 (2005-04-18): - Version 2.2.0 of pgintcl has been replaced with version 3.0.0. This new version of pgintcl allows the use of database character encodings other than LATIN1. Version 1.2.2 (2005-02-06): - Bug solved: Application error when trying to scroll the text widget of the SELECT statement in the open form window. Version 1.2.1 (2005-01-22): - Corrections in help text. - Modification in database conversion from 1.2.0 to 1.2.1 such that it does not use the keyword "DEFAULT" as value for INSERT ... INTO. This to be compatible with older versions of PostgreSQL. - On Windows platforms the ~/.pgpass file is moved and renamed to %APPDATA%\postgresql\pgpass.conf where APPDATA is the environment variable which points to the user's application data. Usually that is C:\Documents and Settings\\Application Data - Similarly on Windows platforms, the ~/.pfmrc file is moved and renamed to: %APPDATA%\pfm\pfm.conf Version 1.2.0 (2005-01-10): - Previous versions of pfm relied upon the 'oid' for updating records. However, according to the PostgreSQL documentation the uniqueness of the 'oid' is not guaranteed, even not within a table. To solve this problem, a new attribute 'pkey' is introduced in pfm_form. This attribute should be filled with the space separated list of primary key attributes of the form's main table. If that table does not have a primary key, then the table cannot be modified. For databases already managed with a previous version of pfm, the database conversion script fills 'pkey' with 'oid'. This means that pfm continues to work with the oid if you do not change it manually. You can continue to use the oid as pkey, but then it is recommended to add a UNIQUE constraint on the oid. - In the previous versions of pfm you could assign the default value to an attribute when adding a record by typing an '=' sign for the attribute value. This feature has been replaced by another, more advanced feature. You can now define a default value for an attribute in pfm_attribute. If that value begins with an '=' sign, it must be an SQL SELECT statement that returns exactly one value. This replacement was done, because the old one did not work properly with attributes that were mentioned in the 'pkey' list. Note: The automatic conversion script does not automatically fill the 'default' attribute of pfm_attribute. You may need to fill out this attribute manually after the conversion script has run. - Enhancement of the report generator. It is now possible to print summaries at the end of the report or report sections. These summaries are calculated by means of aggregate operators COUNT, SUM, AVG, STDDEV, MIN, or MAX applied on the fields of the report. - The "Import SQL" feature can now also use the psql '\i' command. This was necessary because the SQL script for converting to the new format of the pfm_* tables turned out to be too long to run from the SQL text window. - There are a few new options: o 'tempdir' defines a directory where pfm can write temporary files. The default value is '\tmp'. o 'psql' defines where pfm can find the 'psql' executable. o 'fontmonospace' defines the monospace font famlily and size o 'fontproportional' defines the proportional font family and size. - There is a context menu which pops up when the right mouse button is clicked. It has the classic 'Copy', 'Cut' and 'Paste' menu items and can be used to move/copy text to/from the clipboard. - pfm now fully supports the ./pgpass file, including the '\:' and the '\\' escapes. - the buttons on the left of the form, labeled with the attribute names, can be used to search the internal buffer for records with a particular value for the corresponding attribute; Note: Only the internal buffer is searched. If the record you are looking for exists in the database but is not loaded in the internal buffer, it cannot be found in this way. Version 1.1.1 (2004-11-24): - Wrong status of "After Last" after adding a record After adding a record the status of the dummy record after the last one is put to "After last". However, all the cases in which pfm checks for the status of a record, it only checks for "After Last". The result is that the "Update" button is not disabled when the dummy "After Last" record is displayed. If that button is pressed, a run-time error occurs. Solution: The spelling of the statuses has been reviewed. They are now all with only the first word capitalised. They are : Not modified Updated Added Not added Deleted After last Adding Updating The last 2 ones have been added to remind the user what he is doing after having pressed [Add] or [Updated] respectively. - Links are still enabled when in Updating or Adding mode: When updating or adding a record, the link buttons are still enabled. Although nothing wrong seems to result from it, it would be cleaner if they were disbaled. - Problem with pfm.kit when glibc has version 2.2.5: The pfm.kit included in pfm 1.1.0. does not work with glibc 2.2.5. It requires glibc 2.3. Solution: pfm has been reworked such that it does not require Iwidgets anymore. pfm used Iwidgets mainly for getting scrollable text and listbox widgets. These can be made, with a little extra effort, in pure Tcl/Tk. A workaround has been implemented for simulating a scrollable frame which is absent in pure Tcl/Tk. The on-line help which used the scrolledhtml widget has been replaced with a call to an external html-browser. There is a new option, which allows the user to choose the external browser. The advantage is that pfm.kit no longer contains binary files, which enhances its portatbility. Version 1.1.0 (2004-11-11): - Option "usePGPASSWORD" has been added. It determines whether pfm uses the environment variable PGPASSWORD to store the password entered by the user during the short time between clicking 'OK' on the 'Open database' window and the actual opening of the database. If this option is 'no', the user is not prompted for a password when opening a database, but then a properly configured ~/.pgpass file is required. Notes: o This option was added because, according to the postgreSQL documentation, the use of the PGPASSWORD environment variable is deprecated for security reasons. o The default value for this options is still 'yes', for backwards compatibilty and for getting started more easily. Consider changing this option to 'no'. o For connecting to postgreSQL via the Tcl interfaces Pgtcl or pgin.tcl, pfm reads the .pgpass file and supplies the password to postgreSQL via the "password" connection parameter. This was necessary because pgin.tcl ignores the pgpass file. o pfm supports the "\:" escape sequence in ~/.pgpass, but not the "\\" escape sequence. - The connection parameter and option "hostaddr" have been dropped because psql does not have a matching "--hostaddr" option, which led to a behaviour that was difficult to understand and which was unnecessary complicated. - Behaviour of 'Run SQL' command history was improved again. In particular, the input screen is cleared automatically after pressing 'Run'. This saves the user pressing 'Clear' after every 'Run'. In the case that he wants to see and reuse the command, he can do so by pressing 'Back'. - It has been reported (see bug 935) that pfm does not work with older versions of postgreSQL. The problem is probably only install_pfm.sql, which has been generated by pg_dump of 7.3.2., but which is not understood by older versions of postgreSQL. install_pfm has been modified such that it probably also works with version 7.2.1. of postgreSQL. Note: I cannot test this myself, because I don't have access to a version 7.2.1. I would be quite happy to receive feedback on this. The views pfm_table, pfm_table_def and pfm_table_report have been dropped. - The help file has been improved on serveral points. - The report definition has changed significantly. The attributes 'table_or_view', 'sqlwhere', and 'orderby' of pfm_report have been combined into one attribute 'sqlselect' which may contain parameters that are entered by the user at run-time. This enhances the user's possibilty to introduce run-time parameters for the report, and there is no need anymore to create a view for designing a report. - The menu-item "Tools -> Install pfm-* tables" has been dropped. Instead, the user is prompted to install the pfm_* tables when opening a database that does not contain them yet. - When opening a database containing the pfm_tables generated by an older version of pfm, the user is prompted to convert them to the new format. - An addressbook sample database has been added. The sample database of version 1.0.4. has been renamed to customerdb. - The groupby_having attribute of pfm_form has been renamed to groupby. The reason is that no HAVING CLAUSE should be specified in the definition of the form. The WHERE clause specified by the user when opening the form is automatically converted to a HAVING clause if there is a GROUP BY clause in the form definition. - pgintcl versions 2.1.0 and 1.5.0 have been included in the distribution. - Tclkit for linux on PC is included in the distribution. - A GUI install script is included in the distribution (only for Linux on PC). - Bug 1048 Encoding problems: When pfm uses pgintcl to communicate with postgreSQL, the special characters like ë, é, etc., typed on the Run SQL input screen are not correctly entered in the database. This is the situation in version 1.0.4: If Pgtcl is loaded, it sets the environment variable PGCLIENTENCODING to "UNICODE". When pfm subsequently calls 'psql' for the 'Run SQL' feature of pfm, 'psql' also assumes client_encoding "UNICODE". Therefore, pfm sends data with encoding UTF-8 to 'psql'. If pgintcl is loaded, the environment variable PGCLIENTENCODING is not set. When pfm subsequently calls 'psql' for the 'Run SQL' feature of pfm, 'psql' assumes the same encoding as the database, e.g. LATIN1, but since pfm still sends the data with encoding UTF-8, there is an encoding mismatch between pfm and psql. Solution in version 1.1.0: After loading pgintcl, pfm also sets the environment variable PGCLIENTENCODING to "UNICODE". Version 1.0.4. (2004-02-19): - Bugs solved: o 690: pfm hangs when psql exits o 691: The' Run SQL' command history behaves strangely sometimes o 692: Printing fails if title contains several words - Feature request implemented: o 693: Character encoding for sending text to print command Version 1.0.3. (2004-02-16): - Bug 686 solved: Connecting to psql fails in some cases: Database name, host or hostaddr, port, user and password specified by the user at database open are reused for invoking psql. - psql is invoked in a different way now: it is invoked when a database is opened and it is closed when the database is closed. The result is that the state of psql is no longer cleared after each command sent to psql. - The 'dblist' option is no longer read from the pg_database table when the options are reset to their default values. The default is now a list containing only the database with the same name as the user's logon name. - When opening a database, the user can either type the database name directly or select it from the 'dblist' option. When the database is successfully opened, the name is added to the 'dblist' option if it was not already in it. The name of the last opened database becomes the default database for the next 'open database'. Version 1.0.2. (2004-02-12) - Bug 679 solved: Transaction kept open for too long. - Bug 680 solved: Updating a record already deleted by another user. - Contact information in copy right notices changed. Version 1.0.1. (2004-02-06) - E-mail address in copy right notices changed back to personal E-mail because pfm-comments mailing list was out of order. Version 1.0.0. (2004-02-05) - First published version pfm-2.0.8/forms.tcl0000644000175000017500000021671011640353305012316 0ustar wimwim# forms.tcl image create bitmap ::img::arrow_left \ -file [file join $::config::installDir arrow-left.xbm] \ -foreground #000000 image create bitmap ::img::arrow_right \ -file [file join $::config::installDir arrow-right.xbm] \ -foreground #000000 image create bitmap ::img::arrow_home \ -file [file join $::config::installDir arrow-home.xbm] \ -foreground #000000 image create bitmap ::img::arrow_end \ -file [file join $::config::installDir arrow-end.xbm] \ -foreground #000000 class FormWindow { protected common windowList {} public variable window {} protected variable parent public variable formName protected variable noteBook protected variable formDef protected variable attribDef protected variable attribList protected variable modAttribList protected variable tabList {} protected variable tabObject proc closeAllWindows {} { foreach window $windowList { destroy $window } return } constructor {c_parent c_formName} { set parent $c_parent set formName $c_formName setupWindow return } destructor { foreach tab $tabList { delete object $tab } set indexDeleted [lsearch -exact $windowList $window] set windowList [lreplace $windowList $indexDeleted $indexDeleted] return } protected method setupWindow {} { set window [toplevel [appendToPath $parent [namespace tail $this]]] lappend windowList $window wm geometry $window [join $::geometry::form {x}] set noteBook [ttk::notebook $window.nb -takefocus 0] set tabOpen [OpenTab "#auto" $noteBook $this] addNotebookTab $noteBook [$tabOpen cget -widget] tabOpen lappend tabList $tabOpen set tabObject([$tabOpen cget -widget]) $tabOpen pack $noteBook -side top -expand 1 -fill both pack [ttk::sizegrip ${window}.sg] -side top -anchor e ttk::notebook::enableTraversal $noteBook bind $noteBook <> [list $this onTabChange] set tpOnly [bindToplevelOnly $window [list delete object $this]] bind $tpOnly {set ::geometry::form {%w %h}} return } public method removeTab {removedTab} { set indexDeleted [lsearch -exact $tabList $removedTab] if {$indexDeleted >= 0} then { set tabList [lreplace $tabList $indexDeleted $indexDeleted] } delete object $removedTab if {[llength $tabList] == 0} then { destroy $window } else { [lindex $tabList end] setTabLock 0 } return } public method deleteTabsAbove {tab} { set thisTabIdx [lsearch -exact $tabList $tab] foreach tabAbove [lrange $tabList [expr $thisTabIdx + 1] end] { delete object $tabAbove } set tabList [lrange $tabList 0 $thisTabIdx] return } public method newFormTab {newFormName formDefDict history} { [lindex $tabList end] setTabLock 1 set newTab [FormTab "#auto" $noteBook $this $newFormName \ $formDefDict $history] lappend tabList $newTab set widget [$newTab cget -widget] set tabObject($widget) $newTab $noteBook add $widget -text $newFormName $noteBook select $widget return } public method getTabList {} { return $tabList } public method onTabChange {} { set selectedTab $tabObject([$noteBook select]) focus [$noteBook select] $selectedTab tabSelected return } public method setMenubar {menubar} { $window configure -menu $menubar return } public method setTitle {title} { wm title $window $title return } } class GenTab { public variable widget protected variable parent protected variable formWindow public variable formWinPath protected variable formName protected variable menubar protected variable mnForm protected variable title protected variable tabLock 0 protected variable tabStatus protected variable btnCloseTab protected variable btnUnlockTab protected variable tabStatBar constructor {c_parent c_formWindow c_formName} { set parent $c_parent set formWindow $c_formWindow set formName $c_formName set formWinPath [$formWindow cget -window] set widget [ttk::frame [appendToPath $parent [namespace tail $this]] \ -takefocus 0] set tabStatBar [ttk::frame $widget.sb] set tabStatus [ttk::label $tabStatBar.lbl -text {} \ -foreground {red3}] set btnCloseTab [defineButton $tabStatBar.btnClose $widget \ btnCloseTab [list $this closeTab]] $btnCloseTab configure -style SButton set btnUnlockTab [defineButton $tabStatBar.btnUnlock $widget \ btnUnlockTab [list $this unlockTab]] $btnUnlockTab configure -style SButton $btnUnlockTab state {disabled} grid $tabStatus -column 0 -row 0 grid $btnUnlockTab -column 1 -row 0 -pady {5 5} grid $btnCloseTab -column 2 -row 0 -pady {5 5} grid columnconfigure $tabStatBar 0 -weight 1 pack $tabStatBar -side top -fill x set menubar [menu [appendToPath $formWinPath \ mb[namespace tail $this]] -tearoff 0] set mnForm [menu $menubar.mnForm -tearoff 0] addMenuItem $mnForm mnuUnlockTab command [list $this unlockTab] $mnForm entryconfigure 0 -state disabled -accelerator {Esc} addMenuItem $mnForm mnuCloseTab command [list $this closeTab] $mnForm entryconfigure 1 -accelerator {Esc} $mnForm add separator addMenuItem $mnForm mnuCloseForm command [list destroy $formWinPath] addMenuItem $menubar mnuForm cascade $mnForm set title "pfm - [$::dbObject cget -dbname]: $formName" bind $widget [list $this onEscape] return } destructor { # $formWindow tabDestroyed [namespace tail $this] destroy $widget return } public method tabSelected {} { $formWindow setMenubar $menubar $formWindow setTitle $title return } public method onEscape {} { if {!$tabLock} then { closeTab } else { unlockTab } return } public method unlockTab {} { $formWindow deleteTabsAbove [namespace tail $this] setTabLock 0 return } public method closeTab {} { $formWindow removeTab [namespace tail $this] return } public method setTabLock {state} { set tabLock $state if {$state} then { $tabStatus configure -text [mc lbTabLocked] $btnCloseTab state {disabled} $btnUnlockTab state {!disabled} $mnForm entryconfigure 0 -state normal $mnForm entryconfigure 1 -state disabled } else { $tabStatus configure -text {} $btnCloseTab state {!disabled} $btnUnlockTab state {disabled} $mnForm entryconfigure 0 -state disabled $mnForm entryconfigure 1 -state normal } $this propagateTabLock $state return } } class OpenTab { inherit GenTab protected variable formDef protected variable attribDef protected variable attribList protected variable modAttribList protected variable whereSelected 1 protected variable txt protected variable btnRun protected variable rbWhere protected variable rbOrderby protected variable pasteValueMenu 0 protected variable listBoxList {} constructor {c_parent c_formWindow} \ {GenTab::constructor $c_parent $c_formWindow [$c_formWindow cget -formName]} { getFormDef $::dbObject $formName $formWinPath formDef getAttribDef $::dbObject $formName $formWinPath \ attribDef attribList modAttribList setupWidget } destructor { deleteAllListBoxes } protected method setupWidget {} { # define menus set mnPasteName [menu $menubar.mnPasteName -tearoff 0] foreach attrib $attribList { $mnPasteName add command -label $attrib \ -command [list $this pasteName $attrib] } set mnPasteValue [menu $menubar.mnPasteValue -tearoff 0] set pasteValueMenu 0 foreach attrib $attribList { if {$attribDef($attrib,typeofget) in {tgLink tgList}} then { $mnPasteValue add command -label $attrib \ -command [list $this pasteValue $attrib] set pasteValueMenu 1 } } addMenuItem $menubar mnuPasteName cascade $mnPasteName addMenuItem $menubar mnuPasteValue cascade $mnPasteValue if {!$pasteValueMenu} then { $menubar entryconfigure 2 -state disabled } set frmQuery [ttk::frame $widget.frmquery] foreach field {Query Where Orderby} { set txt($field) [text $frmQuery.txt$field -width 1 -height 1 \ -wrap word] set vsb($field) [ttk::scrollbar $frmQuery.vsb$field \ -orient vertical -command [list $txt($field) yview]] $txt($field) configure -yscrollcommand [list $vsb($field) set] } $txt(Query) configure -takefocus 0 $txt(Query) insert end "SELECT $formDef(sqlselect)\n" $txt(Query) insert end "FROM $formDef(sqlfrom)\n" if {$formDef(groupby) ne {}} then { $txt(Query) insert end "GROUP BY $formDef(groupby)\n" } if {$formDef(sqllimit) ne {}} then { $txt(Query) insert end "LIMIT $formDef(sqllimit)\n" } $txt(Query) configure -state disabled \ -background $::readonlyBackground if {$formDef(sqlorderby) ne {}} then { $txt(Orderby) insert end $formDef(sqlorderby) } set rbWhere [ttk::radiobutton $frmQuery.rbWhere \ -text WHERE -underline 0 -variable [scope whereSelected] \ -value 1 -command [list focus $txt(Where)] -takefocus 0] if {$formDef(groupby) ne {}} then { $rbWhere configure -text HAVING } set rbOrderby [ttk::radiobutton $frmQuery.rbOrderby \ -text {ORDER BY} -underline 0 -variable [scope whereSelected] \ -value 0 -command [list focus $txt(Orderby)] -takefocus 0] grid $txt(Query) -column 0 -columnspan 2 -row 0 -sticky wens grid $vsb(Query) -column 2 -row 0 -sticky ns grid $rbWhere -column 0 -row 1 -sticky n grid $txt(Where) -column 1 -row 1 -sticky wens grid $vsb(Where) -column 2 -row 1 -sticky ns grid $rbOrderby -column 0 -row 2 -sticky n grid $txt(Orderby) -column 1 -row 2 -sticky wens grid $vsb(Orderby) -column 2 -row 2 -sticky ns grid columnconfigure $frmQuery 1 -weight 1 grid rowconfigure $frmQuery 0 -weight 2 foreach row {1 2} { grid rowconfigure $frmQuery $row -weight 1 } bind $txt(Where) [list set [scope whereSelected] 1] bind $txt(Orderby) [list set [scope whereSelected] 0] focus $txt(Where) set btnRun [defineButton $frmQuery.btnRun $widget btnRun \ [list $this onRun]] grid $btnRun -column 0 -columnspan 3 -row 3 -sticky e \ -pady {10 10} -padx {10 10} pack $frmQuery -side top -expand 1 -fill both # The following makes $widget receive all events that are sent # to its children. This makes it possible to bind keyboard # shortcuts that apply to the frame of this tab only recursiveAppendTag $widget $widget if {$formDef(groupby) ne {}} then { bind $widget \ [list $rbWhere instate {!disabled} [list $rbWhere invoke]] } else { bind $widget \ [list $rbWhere instate {!disabled} [list $rbWhere invoke]] } bind $widget \ [list $rbOrderby instate {!disabled} [list $rbOrderby invoke]] return } public method propagateTabLock {state} { if {$state} then { $rbWhere state {disabled} $rbOrderby state {disabled} $txt(Where) configure -state disabled $txt(Orderby) configure -state disabled $btnRun state {disabled} $menubar entryconfigure 1 -state disabled $menubar entryconfigure 2 -state disabled } else { $rbWhere state {!disabled} $rbOrderby state {!disabled} $txt(Where) configure -state normal $txt(Orderby) configure -state normal $btnRun state {!disabled} $menubar entryconfigure 1 -state normal if {$pasteValueMenu} then { $menubar entryconfigure 2 -state normal } } return } public method onRun {} { deleteAllListBoxes set formDef(sqlwhere) [string trim [$txt(Where) get 1.0 end]] set formDef(sqlorderby) [string trim [$txt(Orderby) get 1.0 end]] setTabLock 1 set history [dict create from {} link {} to [mc histOpenForm $formName]] $formWindow newFormTab $formName [array get formDef] [list $history] return } public method pasteName {attrib} { if {$whereSelected} then { $txt(Where) insert insert "\"${attrib}\"" } else { $txt(Orderby) insert insert "\"${attrib}\"" } return } public method pasteValue {attrib} { switch -- $attribDef($attrib,typeofget) { tgLink { set query $attribDef($attrib,sqlselect) } tgList { set query "SELECT value, description FROM pfm_value " append query "WHERE valuelist = '$attribDef($attrib,valuelist)' " append query "ORDER BY value" } default { set query {} } } if {[$::dbObject select_query_list $query numTuples headerList valueList errMsg]} then { set lsbTitle [mc selectValueFor $attrib] set lsb [ListBox "#auto" $formWinPath $lsbTitle $headerList \ $valueList 0] lappend listBoxList [list $lsb $attrib] if {[$lsb wait result]} then { if {$attribDef($attrib,typeofattrib) eq {taQuoted}} then { set value "'[lindex $result 0]'" } else { set value [lindex $result 0] } if {$whereSelected} then { $txt(Where) insert insert $value } else { $txt(Orderby) insert insert $value } } set lsbToRemove [lsearch -exact -index 0 $listBoxList $lsb] set listBoxList [lreplace $listBoxList $lsbToRemove $lsbToRemove] } else { pfm_message $errMsg $formWinPath } return } public method deleteAllListBoxes {} { foreach lsb $listBoxList { [lindex $lsb 0] destroyWindow } return } } class FormTab { inherit GenTab protected variable formDef protected variable history protected variable attribDef protected variable attribList protected variable modAttribList protected variable linkDef protected variable lastLink protected variable frmHistory protected variable txtHistory protected variable canvas protected variable frmForm protected variable frmButtons protected variable btnArray protected variable btnSelect protected variable txtMessages protected variable control protected variable formState {browse} protected variable statusbar protected variable sbNr protected variable sbStatus protected variable buffer protected variable record protected variable recordIdx protected variable textEditList protected variable listBoxList protected variable matchCase 0 protected variable searchPattern protected variable searchAttribute protected variable searchFrame {} constructor {c_parent c_formWindow c_formName formDefDict c_history} \ {GenTab::constructor $c_parent $c_formWindow $c_formName} { array set formDef $formDefDict set history $c_history set textEditList(0) {} set textEditList(1) {} set listBoxList {} getAttribDef $::dbObject $formName $formWinPath \ attribDef attribList modAttribList getLinkDef $::dbObject $formName $formWinPath linkDef lastLink # Bug 1070: # "record" is a Tcl array which always contains the current # record. Originally, the array elements record($attrib) were # bound to the entry widgets of the form and the element names # were the attribute names. However, a bug in Itcl caused the # attribute value not to be displayed if the attribute name # consists of more than one word. Therefore, the following work # around is implemented. Instead of record($attrib), # record($recordIdx($attrib)) is bound to the form's entry # widgets where recordIdx acts as a conversion table which maps # the attribute names ($attrib) to integers. # recordIdx is initialised here. set index 1 foreach attrib $attribList { set recordIdx($attrib) $index incr index } setupWidget set buffer [FormBuf "#auto" [array get formDef] [array get attribDef] \ $this $attribList $modAttribList] $buffer getFirstRecord record recNr status updateStatusBar $recNr $status return } destructor { delete object $buffer deleteAllTextEdits 0 deleteAllTextEdits 1 deleteAllListBoxes return } public method propagateTabLock {state} { if {$state} then { if {$formDef(view) eq {f}} then { foreach op {Update Add Delete} { $btnArray($op) state {disabled} } } disableBrowseMenus for {set link 0} {$link <= $lastLink} {incr link} { $btnArray($link) state {disabled} } bind $widget {} bind $widget {} bind $widget {} bind $widget {} bind $widget {} bind $widget {} foreach btn {Home Prev Next End} { $btnArray($btn) state {disabled} } bind $widget {} if {$searchFrame ne {}} then { foreach op {FindNext Hide} { $btnArray($op) state {disabled} } } } else { if {$formDef(view) eq {f}} then { foreach op {Update Add Delete} { $btnArray($op) state {!disabled} } } for {set link 0} {$link <= $lastLink} {incr link} { $btnArray($link) state {!disabled} } enableBrowseMenus bind $widget [list $this nextRecord] bind $widget [list $this prevRecord] bind $widget [list $this nextRecord] bind $widget [list $this prevRecord] bind $widget [list $this firstRecord] bind $widget [list $this lastRecord] foreach btn {Home Prev Next End} { $btnArray($btn) state {!disabled} } bind $widget [list $this searchForRecord] if {$searchFrame ne {}} then { foreach op {FindNext Hide} { $btnArray($op) state {!disabled} } } } return } public method mouseWheel {platform arg} { switch $platform { windows { if {$arg < 0} then { set direction 1 } else { set direction -1 } } unix { set direction $arg } } set mouse_x [winfo pointerx $formWinPath] set mouse_y [winfo pointery $formWinPath] set x1 [winfo rootx $frmForm] set y1 [winfo rooty $frmForm] set x2 [expr $x1 + [winfo width $frmForm]] set y2 [expr $y1 + [winfo height $frmForm]] if {($x1 <= $mouse_x) && ($mouse_x <= $x2) && \ ($y1 <= $mouse_y) && ($mouse_y <= $y2)} then { $canvas yview scroll $direction unit } return } public method scrollForm {i} { # Scroll form such that attribute i is visible in the middle # of the form. # # Let: n1 be index of first visible attribute before scrolling # n2 be index of last visible attribute before scrolling # nn1 be index of first visible attribute after scrolling # nn2 be index of last visible attribute after scrolling # f1 = 1st fraction returned by yview before scrolling (known) # f2 = 2nd fraction returned by yview before scrolling (known) # nf1 = 1st fraction to be given to yview for scrolling # nf2 = 2nd fraction returned by yview after scrolling # n = nr of attributes on form (known) # i = index of attribute with input focus (arg of function) # s = number of visible attributes on screen # Known data: # n, f1, f2, i # To be calculated: # nf1 # Calculation: # f1 = (y-coord of first visible horizontal line) # /(height of canvas) # = (n1 + 1)/(n + 2) # f2 = (y-coord of last visible horizontal line) # /(height of canvas) # = (n2 + 1)/(n + 2) # -- to take into account the empty space at top and bottom # -- we assume 2 dummy attributes, one at top (index "-1"), # -- one at bottom (index "n") # n1 = (n + 2) * f1 - 1 # n2 = (n + 2) * f2 - 1 # s = n2 - n1 + 1 # = (n + 2) * (f2 - f1) + 1 (1) # Afters scrolling, same relationship: # nn1 = (n + 2) * nf1 - 1 # nn2 = (n + 2) * nf2 - 1 # s = (n + 2) * (nf2 - nf1) + 1 (2) # After scrolling we want i to be in the middle between nn1 and nn2 # i = (nn1 + nn2)/2 # = (nf1 + nf2) * (n + 2)/2 - 1 (3) # s we can calculate with (1) # From (2) and (3) it follows: # nf1 = (2*i - s + 3)/(2 * (n + 2)) set n [llength $attribList] set yviewList [$canvas yview] set f1 [lindex $yviewList 0] set f2 [lindex $yviewList 1] set s [expr ($n + 2) * ($f2 - $f1) + 1.0] set nf1 [expr (2.0*$i - $s + 3.0)/(2.0 * ($n + 2.0))] if {$nf1 < 0} then { set nf1 0 } if {$nf1 > 1} then { set nf1 1 } $canvas yview moveto $nf1 return } public method showError {message} { $txtMessages configure -state normal $txtMessages insert end "${message}\n" red $txtMessages see end $txtMessages configure -state disabled bell return } public method showQuery {intro query result resultColor} { $txtMessages configure -state normal $txtMessages insert end "${intro}\n" blue $txtMessages insert end "${query}\n" $txtMessages insert end "${result}\n" $resultColor $txtMessages see end $txtMessages configure -state disabled if {$resultColor eq {red}} then { bell } return } public method firstRecord {} { if {(!$tabLock) && ($formState eq {browse})} then { $buffer getFirstRecord record recNr status updateStatusBar $recNr $status updateAllTextEdits 1 } return } public method lastRecord {} { if {(!$tabLock) && ($formState eq {browse})} then { $buffer getLastRecord record recNr status updateStatusBar $recNr $status updateAllTextEdits 1 } return } public method nextRecord {} { if {(!$tabLock) && ($formState eq {browse})} then { $buffer getNextRecord record recNr status updateStatusBar $recNr $status updateAllTextEdits 1 } return } public method prevRecord {} { if {(!$tabLock) && ($formState eq {browse})} then { $buffer getPrevRecord record recNr status updateStatusBar $recNr $status updateAllTextEdits 1 } return } public method onExpand {attrib} { if {$formState eq {browse}} then { set readOnly 1 } else { set readOnly 0 } set textEdit [TextEdit "#auto" $formWinPath $attrib \ $record($recordIdx($attrib)) $readOnly] lappend textEditList($readOnly) [list $textEdit $attrib] $textEdit defineCallBack \ [list $this textEditCallBack $textEdit $attrib $readOnly] return } public method textEditCallBack {textEdit attrib readOnly} { $textEdit getText record($recordIdx($attrib)) set itemToRemove [lsearch -exact -index 0 \ $textEditList($readOnly) $textEdit] set textEditList($readOnly) [lreplace $textEditList($readOnly) \ $itemToRemove $itemToRemove] return } public method onUpdate {} { if {[$buffer getStatus] ni {statAfterLast statNotAdded statDeleted}} then { # deleteAllTextEdits 1 set reloaded [$buffer reloadRecord record recNr status] updateStatusBar $recNr $status updateAllTextEdits 1 if {$reloaded} then { setFormState update } else { pfm_message [mc reloadFailed] $formWinPath } } return } public method onAdd {} { # deleteAllTextEdits 1 foreach attrib $modAttribList { if {$attribDef($attrib,default) ne {}} then { if {[string index $attribDef($attrib,default) 0] eq {=}} then { set query [string range $attribDef($attrib,default) 1 end] if {[$::dbObject select_query_list $query numTuples \ namesList tuples errMsg]} then { showQuery [mc getDefaultValue $attrib] \ $query [mc queryOK] green if {$numTuples == 1} then { set record($recordIdx($attrib)) [lindex $tuples 0 0] } else { showError [mc defaultNumTuplesErr $attrib $numTuples] } } else { showQuery [mc getDefaultValue $attrib] \ $query $errMsg red } } else { set record($recordIdx($attrib)) $attribDef($attrib,default) } } } setFormState add return } public method onDelete {} { if {[$buffer getStatus] ni {statAfterLast statNotAdded statDeleted}} then { # deleteAllTextEdits 1 set reloaded [$buffer reloadRecord record recNr status] updateStatusBar $recNr $status if {$status ni {statAfterLast statNotAdded statDeleted}} then { set arg [dict create \ parent $formWinPath \ title [mc questionDeleteTitle] \ message [mc questionDeleteMessage] \ msgWidth 400 \ defaultButton btnNo \ buttonList {btnYes btnNo}] set dlg [GenDialog "#auto" $arg] if {[$dlg wait] eq {btnYes}} then { $buffer deleteRecord record recNr status updateStatusBar $recNr $status updateAllTextEdits 1 } } } return } public method onOK {} { if {[llength $textEditList(0)] > 0} then { set attributes {} foreach item $textEditList(0) { append attributes "[lindex $item 1]\n" } pfm_message [mc editWindowsOpen $attributes] $formWinPath } else { if {[llength $listBoxList] > 0} then { set attributes {} foreach item $listBoxList { append attributes "[lindex $item 1]\n" } pfm_message [mc listBoxOpen $attributes] $formWinPath } else { switch -- $formState { "update" { $buffer updateRecord record $buffer reloadRecord record recNr status updateStatusBar $recNr $status updateAllTextEdits 1 } "add" { if {[$buffer addRecord record]} then { $buffer reloadRecord record recNr status updateStatusBar $recNr $status updateAllTextEdits 1 } else { updateStatusBar {} statNotAdded updateAllTextEdits 1 } } } setFormState browse } } return } public method onCancel {} { deleteAllListBoxes deleteAllTextEdits 0 setFormState browse $buffer getCurRecord record recNr status updateStatusBar $recNr $status return } public method onSelect {attrib} { switch -- $attribDef($attrib,typeofget) { tgLink { set query $attribDef($attrib,sqlselect) } tgList { set query "SELECT value, description FROM pfm_value " append query "WHERE valuelist = '$attribDef($attrib,valuelist)' " append query "ORDER BY value" } default { set query {} } } if {[$::dbObject select_query_list $query numTuples headerList valueList errMsg]} then { set lsbTitle [mc selectValueFor $attrib] set selected [lsearch -exact -index 0 $valueList $record($recordIdx($attrib))] if {$selected < 0} then { set selected 0 } set lsb [ListBox "#auto" $formWinPath $lsbTitle $headerList \ $valueList $selected] lappend listBoxList [list $lsb $attrib] if {[$lsb wait result]} then { set record($recordIdx($attrib)) [lindex $result 0] } set lsbToRemove [lsearch -exact -index 0 $listBoxList $lsb] set listBoxList [lreplace $listBoxList $lsbToRemove $lsbToRemove] } else { pfm_message $errMsg $formWinPath } return } public method followLink {link} { if {!$tabLock && \ ([$buffer getStatus] ni {statDeleted statAfterLast statNotAdded})} then { set from $formName set displayValues {} foreach attrib $linkDef($link,displayattrib) { lappend displayValues $record($recordIdx($attrib)) } set displayValues [join $displayValues {, }] append from " (${displayValues})" set to "$linkDef($link,toform)" set newEntry [dict create from $from link \ $linkDef($link,linkname) to $linkDef($link,toform)] set newhistory $history lappend newhistory $newEntry array unset toformDef if {[getFormDef $::dbObject $linkDef($link,toform) \ $formWinPath toformDef]} then { set toformDef(sqlwhere) [expandSqlWhere $link] # Next statement added for bug 1072 set toformDef(sqlorderby) $linkDef($link,orderby) $formWindow newFormTab $linkDef($link,toform) \ [array get toformDef] $newhistory } } return } public method onSearch {attrib} { if {$searchFrame eq {}} then { set searchFrame [setupsearchbar $frmHistory $attrib] pack $searchFrame -side top -fill x } else { set searchAttribute $attrib } return } public method onSearchHide {} { if {$searchFrame ne {}} then { destroy $searchFrame set searchFrame {} focus [tk_focusNext [focus]] } return } public method searchForRecord {} { if {($formState eq {browse}) && ($searchFrame ne {}) && !$tabLock} then { $buffer searchRecord $searchAttribute \ "*${searchPattern}*" $matchCase $buffer getCurRecord record recNr status updateStatusBar $recNr $status updateAllTextEdits 1 } return } public method onHelp {} { set query "SELECT help FROM pfm_form WHERE name = '${formName}'" if {[$::dbObject select_query_list $query numTuples \ names tuples errMsg]} then { if {$numTuples == 1} then { set txtEdit [TextEdit "#auto" $formWinPath \ [mc formHelp $formName] [lindex $tuples 0 0] 1] } } else { pfm_message $errMsg $formWinPath } return } protected method setupWidget {} { setupMenus set vpanes [ttk::panedwindow $widget.vpanes -orient vertical] set frmHistory [historyPane $vpanes] set hpanes [ttk::panedwindow $vpanes.hpanes -orient horizontal] set frmForm [formPane $hpanes] set frmLink [linkPane $hpanes] $hpanes add $frmForm -weight 6 $hpanes add $frmLink -weight 1 set frmMessages [messagePane $vpanes] $vpanes add $frmHistory -weight 1 $vpanes add $hpanes -weight 3 $vpanes add $frmMessages -weight 1 pack $vpanes -side top -expand 1 -fill both recursiveAppendTag $widget $widget update set bbox [$canvas bbox all] set rightEdge [expr [lindex $bbox 2] + 20] set bottomEdge [expr [lindex $bbox 3] + 20] $canvas configure -scrollregion [list 0 0 $rightEdge $bottomEdge] bind $widget [list $this nextRecord] bind $widget [list $this prevRecord] bind $widget [list $this nextRecord] bind $widget [list $this prevRecord] bind $widget [list $this firstRecord] bind $widget [list $this lastRecord] bind $widget {focus [tk_focusPrev [focus]]} bind $widget {focus [tk_focusNext [focus]]} showHistory return } protected method setupMenus {} { set mnRecord [menu $menubar.record -tearoff 0] foreach op {Update Add Delete} { addMenuItem $mnRecord mnu${op}Record command [list $this on$op] } set mnGoTo [menu $menubar.goto -tearoff 0] foreach dir {next prev first last} { addMenuItem $mnGoTo mnuGoTo$dir command [list $this ${dir}Record] } $mnGoTo entryconfigure 0 -accelerator {PgDn} $mnGoTo entryconfigure 1 -accelerator {PgUp} $mnGoTo entryconfigure 2 -accelerator {Home} $mnGoTo entryconfigure 3 -accelerator {End} set mnSearch [menu $menubar.search -tearoff 0] foreach attrib $attribList { $mnSearch add command -label $attrib \ -command [list $this onSearch $attrib] } $mnSearch add separator $mnSearch add checkbutton \ -label [lindex [mcunderline mnuSearchCase] 0] \ -underline [lindex [mcunderline mnuSearchCase] 1] \ -variable [scope matchCase] -onvalue 1 -offvalue 0 \ -indicatoron 1 -selectcolor black $mnSearch add separator addMenuItem $mnSearch mnuSearchHide command [list $this onSearchHide] addMenuItem $menubar mnuRecordMenu cascade $mnRecord addMenuItem $menubar mnuGoToMenu cascade $mnGoTo addMenuItem $menubar mnuSearchMenu cascade $mnSearch addMenuItem $menubar mnuFormHelp command [list $this onHelp] if {$formDef(view) eq {t}} then { $menubar entryconfigure 1 -state disabled } return } protected method enableBrowseMenus {} { if {$formDef(view) eq {f}} then { $menubar entryconfigure 1 -state normal } foreach item {2 3} { $menubar entryconfigure $item -state normal } return } protected method disableBrowseMenus {} { foreach item {1 2 3} { $menubar entryconfigure $item -state disabled } return } protected method historyPane {parent} { set frm [ttk::frame $parent.frmhistory] set frm2 [ttk::frame $frm.frm2] set txtHistory [text $frm2.txt -width 1 -height 1 \ -takefocus 0 -state disabled] set vsbHistory [ttk::scrollbar $frm2.vsb -orient vertical \ -command [list $txtHistory yview]] $txtHistory configure -yscrollcommand [list $vsbHistory set] $txtHistory tag configure blue -foreground {medium blue} $txtHistory tag configure red -foreground {red3} $txtHistory tag configure green -foreground {green4} pack $txtHistory -side left -expand 1 -fill both pack $vsbHistory -side left -fill y pack $frm2 -side top -expand 1 -fill both return $frm } protected method formPane {parent} { global tcl_platform set frmForm [ttk::frame $parent.frmform -borderwidth 1 -relief raised] # relief modified set statusbar [ttk::frame $frmForm.sb] set frmRecNr [ttk::frame $statusbar.frmRecNr] set btnArray(Home) [ttk::button $frmRecNr.btnHome -image ::img::arrow_home \ -command [list $this firstRecord] -style SButton -takefocus 0] set btnArray(Prev) [ttk::button $frmRecNr.btnPrev -image ::img::arrow_left \ -command [list $this prevRecord] -style SButton -takefocus 0] set sbNr [ttk::label $frmRecNr.sbnr -text {0/0} \ -background white] set btnArray(Next) [ttk::button $frmRecNr.btnNext -image ::img::arrow_right \ -command [list $this nextRecord] -style SButton -takefocus 0] set btnArray(End) [ttk::button $frmRecNr.btnEnd -image ::img::arrow_end \ -command [list $this lastRecord] -style SButton -takefocus 0] pack $btnArray(Home) -side left pack $btnArray(Prev) -side left pack $sbNr -side left -padx {8 8} pack $btnArray(Next) -side left pack $btnArray(End) -side left set sbForm [ttk::label $statusbar.sbform -text $formName \ -foreground {medium blue}] set sbStatus [ttk::label $statusbar.sbStatus -text {state}] grid $frmRecNr -column 0 -row 0 -sticky w grid $sbForm -column 1 -row 0 grid $sbStatus -column 2 -row 0 -sticky e grid columnconfigure $statusbar 0 -weight 1 grid columnconfigure $statusbar 1 -weight 1 grid columnconfigure $statusbar 2 -weight 1 set frmBody [ttk::frame $frmForm.frmbody -borderwidth 2 \ -relief sunken] set canvas [canvas $frmBody.canvas -width 100 -height 100] set canvsb [ttk::scrollbar $frmBody.vsb -orient vertical \ -command [list $canvas yview]] $canvas configure -yscrollcommand [list $canvsb set] set embWindow [formControls $canvas] $canvas create window 20 20 -window $embWindow -anchor nw pack $canvas -side left -expand 1 -fill both pack $canvsb -side left -fill y pack $statusbar -side top -fill x pack $frmBody -side top -expand 1 -fill both if {$formDef(view) eq {f}} then { set frmButtons [ttk::frame $frmForm.frmbtns] foreach op {Update Add Delete OK Cancel} { set btnArray($op) [defineButton $frmButtons.btn$op $widget btn$op \ [list $this on$op]] $btnArray($op) configure -style SButton } set col 0 foreach op {Update Add Delete} { grid $btnArray($op) -column $col -row 0 -pady {5 5} incr col } foreach op {OK Cancel} { $btnArray($op) state {disabled} } grid anchor $frmButtons center pack $frmButtons -side top -fill x } switch -- $tcl_platform(platform) { "windows" { bind $widget [list $this mouseWheel windows %D] } "unix" - default { # On X Window system, mouse wheel sends <4> and <5> events. bind $widget <4> [list $this mouseWheel unix -1] bind $widget <5> [list $this mouseWheel unix 1] } } return $frmForm } protected method formControls {parent} { set frame [ttk::frame $parent.controls] set row 0 foreach attrib $attribList { set lbl [ttk::label $frame.lbl$row -text $attrib -takefocus 0] set record($recordIdx($attrib)) {} set control($attrib) [entry $frame.ent$row -width 35 \ -textvariable [scope record($recordIdx($attrib))]] $control($attrib) configure -state {readonly} bind $control($attrib) [list $this scrollForm $row] set btn [defineButton $frame.btn$row $control($attrib) \ btnExpand [list $this onExpand $attrib]] $btn configure -style SButton if {$attribDef($attrib,typeofget) in {tgList tgLink}} then { set btnSelect($attrib) [defineButton $frame.sel$row \ $control($attrib) btnSelect \ [list $this onSelect $attrib]] $btnSelect($attrib) state {disabled} $btnSelect($attrib) configure -style SButton bind $control($attrib) \ [list $btnSelect($attrib) instate {!disabled} \ [list $btnSelect($attrib) invoke]] grid $lbl -column 0 -row $row -sticky w grid $control($attrib) -column 1 -row $row -sticky wens grid $btnSelect($attrib) -column 2 -row $row -sticky we grid $btn -column 3 -row $row } else { grid $lbl -column 0 -row $row -sticky w grid $control($attrib) -column 1 -columnspan 2 \ -row $row -sticky wens grid $btn -column 3 -row $row } incr row } focus $control([lindex $attribList 0]) return $frame } protected method linkPane {parent} { set frmLink [ttk::frame $parent.frmlink -borderwidth 1 -relief raised] # relief modified set frmTitle [ttk::frame $frmLink.frmtitle] set lbTitle [ttk::label $frmTitle.lbl -text [mc lbLinks]] pack $lbTitle -side top set linksBody [ttk::frame $frmLink.body -borderwidth 2 \ -relief sunken] for {set link 0} {$link<= $lastLink} {incr link} { set btnNr [expr $link + 1] set btnArray($link) [ttk::button $linksBody.btn$btnNr \ -text "${btnNr}: $linkDef($link,linkname)" \ -takefocus 0 -style LButton -underline 0 \ -command [list $this followLink $link]] grid $btnArray($link) -column 0 -row $link -sticky we if {$btnNr < 10} then { bind $widget \ [list after 200 [list $btnArray($link) instate {!disabled} \ [list $btnArray($link) invoke]]] } } grid anchor $linksBody center pack $frmTitle -side top -fill x pack $linksBody -side top -expand 1 -fill both return $frmLink } protected method messagePane {parent} { set frmMessages [ttk::frame $parent.frmmsg] set txtMessages [text $frmMessages.txt -width 1 -height 1 -takefocus 0] set vsb [ttk::scrollbar $frmMessages.vsb -orient vertical \ -command [list $txtMessages yview]] $txtMessages configure -yscrollcommand [list $vsb set] $txtMessages tag configure blue -foreground {medium blue} $txtMessages tag configure red -foreground {red3} $txtMessages tag configure green -foreground {green4} pack $txtMessages -side left -expand 1 -fill both pack $vsb -side left -fill y return $frmMessages } protected method setupsearchbar {parent attrib} { set searchAttribute $attrib set frm [ttk::frame $parent.frmsearch] set searchFor [ttk::label $frm.lb1 -text [mc lbSearchFor]] set searchEntry [entry $frm.entry \ -textvariable [scope searchPattern]] set searchIn [ttk::label $frm.lb2 -text [mc lbSearchIn]] set searchCombo [ttk::combobox $frm.combo -values $attribList \ -textvariable [scope searchAttribute] -takefocus 0] set btnArray(FindNext) [defineButton $frm.btnFindNext $widget \ btnFindNext [list $this searchForRecord]] $btnArray(FindNext) configure -style SButton bind $widget [list $this searchForRecord] set btnCase [defineCheckbutton $frm.btncase $widget \ btnMatchCase {} [scope matchCase] 1 0] set btnArray(Hide) [defineButton $frm.btnHide $widget \ btnHide [list $this onSearchHide]] $btnArray(Hide) configure -style SButton recursiveAppendTag $frm $widget grid $searchIn -column 0 -row 0 -sticky w grid $searchCombo -column 1 -row 0 -sticky we grid $searchFor -column 2 -row 0 -sticky w grid $searchEntry -column 3 -row 0 -sticky we grid $btnArray(FindNext) -column 4 -row 0 -sticky e grid $btnCase -column 5 -row 0 -sticky w grid $btnArray(Hide) -column 6 -row 0 -sticky w grid columnconfigure $frm {1 3} -weight 1 bind $searchEntry [list $this searchForRecord] focus $searchEntry return $frm } protected method updateStatusBar {recNr status} { $sbNr configure -text $recNr setRecordStatus $status return } protected method setRecordStatus {status} { switch -- $status { "statUpdated" - "statAdded" - "statDeleted" { set colour {green4} } "statUpdating" - "statAdding" { set colour {red3} } "statNotAdded" - "statAfterLast" { set colour {medium blue} } "statNotModified" - default { set colour {black} } } $sbStatus configure -text [mc $status] -foreground $colour return } protected method expandSqlWhere {link} { set expandWhere $linkDef($link,sqlwhere) set first [string first "\$(" $expandWhere] while {$first >= 0} { set last [string first ")" $expandWhere $first] set parName [string range $expandWhere [expr $first + 2] [expr $last -1]] if {[info exists record($recordIdx($parName))]} then { set parameter [string map {' ''} $record($recordIdx($parName))] set expandWhere [string replace $expandWhere $first $last $parameter] } else { pfm_message \ [mc expandSqlWhereErr $linkDef($link,linkname) $parName $formName] \ $formWinPath } set first [string first "\$(" $expandWhere $last] } return $expandWhere } protected method showHistory {} { $txtHistory configure -state normal $txtHistory delete 1.0 end set indent {} # first entry doesn't have 'from' and 'link'. Therefore, next loop # starts from 2nd entry foreach entry [lrange $history 1 end] { set from [dict get $entry from] $txtHistory insert end "${indent}${from}\n" blue set link [dict get $entry link] $txtHistory insert end \ "${indent} | ${link}\n${indent} v\n" green append indent { } } # The 'to' only has to be printed for the last entry. Therefoe, # it is outside the previous loop set lastEntry [lindex $history end] $txtHistory insert end "${indent}[dict get $lastEntry to]" blue $txtHistory see end $txtHistory configure -state disabled return } protected method setFormState {newstate} { set formState $newstate switch -- $newstate { "browse" { disableEditButtons disableEditControls enableLinkButtons enableBrowseMenus } "update" { setRecordStatus statUpdating disableLinkButtons enableEditButtons enableEditControls disableBrowseMenus } "add" { setRecordStatus statAdding disableLinkButtons enableEditButtons enableEditControls disableBrowseMenus } } return } protected method disableLinkButtons {} { for {set link 0} {$link <= $lastLink} {incr link} { $btnArray($link) state {disabled} } return } protected method enableLinkButtons {} { for {set link 0} {$link <= $lastLink} {incr link} { $btnArray($link) state {!disabled} } return } protected method disableEditButtons {} { foreach op {OK Cancel} { $btnArray($op) state {disabled} if {$btnArray($op) in [grid slaves $frmButtons]} then { grid forget $btnArray($op) } } bind $widget [list $this onEscape] set col 0 foreach op {Update Add Delete} { $btnArray($op) state {!disabled} if {$btnArray($op) ni [grid slaves $frmButtons]} then { grid $btnArray($op) -column $col -row 0 -pady {5 5} incr col } } for {set link 0} {$link <= $lastLink} {incr link} { $btnArray($link) state {!disabled} } return } protected method enableEditButtons {} { foreach op {Update Add Delete} { $btnArray($op) state {disabled} if {$btnArray($op) in [grid slaves $frmButtons]} then { grid forget $btnArray($op) } } set col 0 foreach op {OK Cancel} { $btnArray($op) state {!disabled} if {$btnArray($op) ni [grid slaves $frmButtons]} then { grid $btnArray($op) -column $col -row 0 -pady {5 5} } incr col } bind $widget [list $this onCancel] for {set link 0} {$link <= $lastLink} {incr link} { $btnArray($link) state {disabled} } return } protected method enableEditControls {} { foreach attrib $modAttribList { if {$attribDef($attrib,typeofget) ni {tgList tgLink tgReadOnly}} { $control($attrib) configure -state normal } else { if {$attribDef($attrib,typeofget) ne {tgReadOnly}} then { $btnSelect($attrib) state {!disabled} } } } return } protected method disableEditControls {} { foreach attrib $modAttribList { if {$attribDef($attrib,typeofget) ni {tgList tgLink tgReadOnly}} { $control($attrib) configure -state readonly } else { if {$attribDef($attrib,typeofget) ne {tgReadOnly}} then { $btnSelect($attrib) state {disabled} } } } return } protected method updateAllTextEdits {readonly} { foreach item $textEditList($readonly) { [lindex $item 0] setText record($recordIdx([lindex $item 1])) } return } protected method deleteAllTextEdits {readOnly} { foreach item $textEditList($readOnly) { [lindex $item 0] destroyWindow } set textEditList($readOnly) {} return } protected method deleteAllListBoxes {} { foreach item $listBoxList { [lindex $item 0] destroyWindow } set listBoxList {} return } } class FormBuf { protected variable formDef protected variable attribDef protected variable attribList protected variable modAttribList protected variable formTab protected variable buffer protected variable status protected variable bufferFilled 0 protected variable curRecord protected variable lastRecord protected variable lastChunk protected variable offset protected variable recordIdx constructor {c_formDef c_attribDef c_formTab c_attribList c_modAttribList} { array set formDef $c_formDef array set attribDef $c_attribDef set attribList $c_attribList set modAttribList $c_modAttribList set formTab $c_formTab # Bug 1070: # The same calculation is done in the constructor of class FormTab. # See there for more information. set index 1 foreach attrib $attribList { set recordIdx($attrib) $index incr index } return } destructor { return } proc quoteIfNecessary {tablename} { # Procedure added for bug 1071 set double "\"" if {[string first $double $tablename] >= 0} then { # If tablename already contains double quotes, just leave it # alone set result $tablename } else { # replace . with "." and enclose everything in double quotes set dot "." set quotedDot "\".\"" set result [string map [list $dot $quotedDot] $tablename] set result "${double}${result}${double}" } # puts stdout $result return $result } public method getFirstRecord {recordName recNrName statusName} { upvar $recordName record upvar $recNrName recNr upvar $statusName recordStatus if {(!$bufferFilled) || ($offset != 0)} then { loadDataChunk 0 } set curRecord 0 getCurRecord record recNr recordStatus return } public method getNextRecord {recordName recNrName statusName} { upvar $recordName record upvar $recNrName recNr upvar $statusName recordStatus if {$curRecord <= [expr $lastRecord - 2]} then { incr curRecord } else { if {!$lastChunk} then { loadDataChunk 1 set curRecord 0 } else { set curRecord $lastRecord } } getCurRecord record recNr recordStatus return } public method getPrevRecord {recordName recNrName statusName} { upvar $recordName record upvar $recNrName recNr upvar $statusName recordStatus if {$curRecord >= 1} then { incr curRecord -1 } else { if {$offset > 0} then { loadDataChunk -1 set curRecord [expr $lastRecord - 1] } } getCurRecord record recNr recordStatus return } public method getLastRecord {recordName recNrName statusName} { upvar $recordName record upvar $recNrName recNr upvar $statusName recordStatus while {!$lastChunk} { loadDataChunk 1 } set curRecord [expr $lastRecord - 1] if {$curRecord < 0} then { set curRecord 0 } getCurRecord record recNr recordStatus return } public method getCurRecord {recordName recNrName statusName} { upvar $recordName record upvar $recNrName recNr upvar $statusName recordStatus array unset record foreach attrib $attribList { set record($recordIdx($attrib)) $buffer($curRecord,$attrib) } set recordStatus $status($curRecord) # set recNr [expr $curRecord + $offset + 1] if {$lastChunk} then { set recNr "[expr $curRecord + $offset + 1]/[expr $lastRecord + $offset]" } else { set recNr "[expr $curRecord + $offset + 1]/?" } return } public method searchRecord {attribute pattern matchCase} { if {($curRecord == $lastRecord) && $lastChunk} then { loadDataChunk 0 set startSearch 0 } else { set startSearch [expr $curRecord + 1] } set searching 1 while {$searching} { set found 0 for {set tuple $startSearch} {$tuple < $lastRecord} {incr tuple} { if {$matchCase} then { set found [string match $pattern \ $buffer($tuple,$attribute)] } else { set found [string match -nocase $pattern \ $buffer($tuple,$attribute)] } if {$found} then { set curRecord $tuple break } } if {$found} then { set searching 0 } else { if {!$lastChunk} then { loadDataChunk 1 set startSearch 0 } else { set searching 0 set curRecord $lastRecord } } } return $found } public method getStatus {} { return $status($curRecord) } public method deleteRecord {recordName recNrName statusName} { upvar $recordName record upvar $recNrName recNr upvar $statusName recordStatus set command "DELETE FROM [quoteIfNecessary $formDef(tablename)]" append command "\nWHERE [identCurRecord 0]" if {[$::dbObject send_command $command errMsg]} then { foreach attrib $attribList { set buffer($curRecord,$attrib) {} set status($curRecord) statDeleted } $formTab showQuery [mc deleteRecord] $command [mc commandOK] green } else { $formTab showQuery [mc deleteRecord] $command $errMsg red } getCurRecord record recNr recordStatus return } public method addRecord {recordName} { upvar $recordName record set query "INSERT INTO [quoteIfNecessary $formDef(tablename)]" set attribSpec {} set valueList {} foreach attrib $modAttribList { lappend attribSpec "\"${attrib}\"" set value $record($recordIdx($attrib)) if {$attribDef($attrib,typeofget) eq {tgExpression}} then { set value [expr $value] } if {$attribDef($attrib,typeofattrib) eq {taQuoted}} then { set value "'[string map {{'} {''}} ${value}]'" } lappend valueList $value } append query " ([join $attribSpec {, }])" append query "\nVALUES ([join $valueList {, }])" if {[$::dbObject send_command $query errMsg]} then { set result 1 $formTab showQuery [mc addRecord] $query [mc commandOK] green set curRecord $lastRecord incr lastRecord foreach attrib $modAttribList { set buffer($curRecord,$attrib) $record($recordIdx($attrib)) } set status($curRecord) statAdded foreach attrib $attribList { set buffer($lastRecord,$attrib) {} } set status($lastRecord) statAfterLast } else { $formTab showQuery [mc addRecord] $query $errMsg red set result 0 } return $result } public method updateRecord {recordName} { upvar $recordName record if {[transactionCommand [mc startTransaction] {START TRANSACTION}]} then { if {[selectForUpdate]} then { if {[basicUpdateRecord record]} then { if {[transactionCommand [mc commitTransaction] {COMMIT}]} then { set result 1 foreach attrib $modAttribList { set buffer($curRecord,$attrib) $record($recordIdx($attrib)) } set status($curRecord) statUpdated } else { set result 0 } } else { set result 0 transactionCommand [mc rollBack] {ROLLBACK} } } else { set result 0 transactionCommand [mc rollBack] {ROLLBACK} } } else { set result 0 } return $result } protected method basicUpdateRecord {recordName} { upvar $recordName record set updateList {} foreach attrib $modAttribList { if {$record($recordIdx($attrib)) ne $buffer($curRecord,$attrib)} then { set value $record($recordIdx($attrib)) if {$attribDef($attrib,typeofget) eq {tgExpression}} then { set value [expr $value] } if {$attribDef($attrib,typeofattrib) eq {taQuoted}} then { set value "'[string map {{'} {''}} ${value}]'" } lappend updateList "\"${attrib}\" = $value" } } if {[llength $updateList] > 0} then { set command "UPDATE [quoteIfNecessary $formDef(tablename)]" append command "\nSET [join $updateList {, }]" append command "\nWHERE [identCurRecord 0]" if {[$::dbObject send_command $command errMsg]} then { set result 1 $formTab showQuery [mc updateRecord] $command \ [mc commandOK] green } else { set result 0 $formTab showQuery [mc updateRecord] $command \ $errMsg red } } else { set result 0 $formTab showError [mc noUpdates] } return $result } protected method selectForUpdate {} { set sqlattrib {} foreach attrib $modAttribList { lappend sqlattrib "\"${attrib}\"" } set sqlattrib [join $sqlattrib {, }] set query "SELECT $sqlattrib" append query "\nFROM [quoteIfNecessary $formDef(tablename)]" append query "\nWHERE [identCurRecord 0] FOR UPDATE" if {[$::dbObject select_query_list $query numTuples namesList \ resultList errMsg]} then { switch -- $numTuples { 1 { set idx 0 set result 1 foreach attrib $namesList { if {$buffer($curRecord,$attrib) ne [lindex $resultList 0 $idx]} then { set result 0 break } incr idx } if {$result} then { $formTab showQuery [mc selectForUpdate] $query [mc queryOK] green } else { set idx 0 foreach attrib $namesList { set buffer($curRecord,$attrib) [lindex $resultList 0 $idx] set status($curRecord) statNotModified incr idx } $formTab showQuery [mc selectForUpdate] $query \ [mc recordModified] red pfm_message [mc recordModified] [$formTab cget -formWinPath] } } 0 { set result 0 $formTab showQuery [mc selectForUpdate] $query \ [mc recordDeleted] red pfm_message [mc recordDeleted] [$formTab cget -formWinPath] } default { set result 0 $formTab showQuery [mc selectForUpdate] $query \ [mc wrongNumTuples $numTuples] red pfm_message [mc wrongNumTuples $numTuples] \ [$formTab cget -formWinPath] } } } else { set result 0 $formTab showQuery [mc selectForUpdate] $query $errMsg red pfm_message $errMsg [$formTab cget -formWinPath] } return $result } protected method transactionCommand {intro command} { if {[$::dbObject send_command $command errMsg]} then { $formTab showQuery $intro $command [mc commandOK] green set result 1 } else { $formTab showQuery $intro $command $errMsg red set result 0 } return $result } public method reloadRecord {recordName recNrName statusName} { upvar $recordName record upvar $recNrName recNr upvar $statusName recordStatus set query "SELECT $formDef(sqlselect)" append query "\nFROM $formDef(sqlfrom) " if {$formDef(groupby) eq {}} then { append query "\nWHERE [identCurRecord 1]" } else { append query "\nGROUP BY $formDef(groupby) " append query "\nHAVING [identCurRecord 1]" } if {[$::dbObject select_query_list $query numTuples namesList \ resultList errMsg]} then { switch -- $numTuples { 1 { set idx 0 foreach attrib $namesList { set buffer($curRecord,$attrib) [lindex $resultList 0 $idx] incr idx } $formTab showQuery [mc reloadRecord] $query [mc queryOK] green set result 1 } 0 { $formTab showQuery [mc reloadRecord] $query \ [mc recordDeleted] red foreach attrib $attribList { set buffer($curRecord,$attrib) {} } set status($curRecord) statDeleted set result 0 } default { $formTab showQuery [mc reloadRecord] $query \ [mc wrongNumTuples $numTuples] red set result 0 } } } else { $formTab showQuery [mc reloadRecord] $query $errMsg red set result 0 } getCurRecord record recNr recordStatus return $result } protected method identCurRecord {withTable} { set whereClause {} set table [quoteIfNecessary $formDef(tablename)] foreach pkey $formDef(pkey) { if {$attribDef($pkey,typeofattrib) eq {taQuoted}} then { set value "'[string map {{'} {''}} $buffer($curRecord,$pkey)]'" } else { set value $buffer($curRecord,$pkey) } if {$withTable} then { lappend whereClause "(${table}.\"${pkey}\" = $value)" } else { lappend whereClause "(\"${pkey}\" = $value)" } } return [join $whereClause { AND }] } protected method loadDataChunk {arg} { switch -- $arg { 0 { set offset 0 } 1 { if {$formDef(sqllimit) ne {}} then { incr offset $formDef(sqllimit) } } -1 { if {$formDef(sqllimit) ne {}} then { incr offset -$formDef(sqllimit) if {$offset < 0} then { set offset 0 } } } } set query "SELECT $formDef(sqlselect)" append query "\nFROM $formDef(sqlfrom)" if {$formDef(groupby) ne {}} then { append query "\nGROUP BY $formDef(groupby)" if {$formDef(sqlwhere) ne {}} then { append query "\nHAVING $formDef(sqlwhere)" } } else { if {$formDef(sqlwhere) ne {}} then { append query "\nWHERE $formDef(sqlwhere)" } } if {$formDef(sqlorderby) ne {}} then { append query "\nORDER BY $formDef(sqlorderby)" } if {$formDef(sqllimit) ne {}} then { append query "\nLIMIT $formDef(sqllimit) OFFSET $offset" } array unset buffer array unset status if {[$::dbObject select_query $query lastRecord buffer errMsg]} then { if {$formDef(sqllimit) ne {}} then { set lastChunk [expr $lastRecord < $formDef(sqllimit)] } else { set lastChunk 1 } if {[checkResult errMsg]} then { $formTab showQuery [mc loadDataChunk] $query [mc queryOK] green } else { $formTab showQuery [mc loadDataChunk] $query $errMsg red } set bufferFilled 1 } else { set lastRecord 0 set lastChunk 1 $formTab showQuery [mc loadDataChunk] $query $errMsg red set bufferFilled 0 } for {set tuple 0} {$tuple < $lastRecord} {incr tuple} { set status($tuple) statNotModified } foreach attrib $attribList { set buffer($lastRecord,$attrib) {} } set status($lastRecord) statAfterLast return } protected method checkResult {errMsgName} { upvar $errMsgName errMsg set result 1 set errMsg {} if {$lastRecord > 0} then { foreach attrib $attribList { if {![info exists buffer(0,$attrib)]} then { set result 0 append errMsg "[mc attribMissing $attrib]\n" for {set tuple 0} {$tuple < $lastRecord} {incr tuple} { set buffer($tuple,$attrib) {} } } } foreach attrib $formDef(pkey) { if {![info exists buffer(0,$attrib)]} then { set result 0 append errMsg "[mc pkeyMissing $attrib]\n" for {set tuple 0} {$tuple < $lastRecord} {incr tuple} { set buffer($tuple,$attrib) {} } } if {$attrib ni $attribList} then { append errMsg "[mc attribDefPkeyMissing $attrib]\n" set attribDef($attrib,typeofattrib) taQuoted set attribbDef($attrib,typeofget) tgDirect } } } return $result } } pfm-2.0.8/arrow-right.xbm0000644000175000017500000000024011004642746013432 0ustar wimwim#define arrow_right_width 5 #define arrow_right_height 9 static unsigned char arrow_right_bits[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; pfm-2.0.8/arrow-end.xbm0000644000175000017500000000023211004643240013052 0ustar wimwim#define arrow_end_width 6 #define arrow_end_height 9 static unsigned char arrow_end_bits[] = { 0x21, 0x23, 0x27, 0x2f, 0x3f, 0x2f, 0x27, 0x23, 0x21 }; pfm-2.0.8/convert_from_1.2.0.sql0000644000175000017500000004452410661776613014445 0ustar wimwim-- convert_from_1.2.0.sql -- The character encoding of this file is iso8859-1 (latin1) -- This script converts the pfm_* tables of versions 1.2.* -- to the format of version 1.5.0 -- From pfm version 1.3.0 on, we do not mark the pfm_tables -- with the last version of pfm, but with the earliest compatible -- version of pfm. In this case that is 1.5.0 and that will stay -- until there is a need to change the format again. INSERT INTO pfm_version (version, "date", comment) VALUES ('1.5.0', CURRENT_DATE, 'convert_from_1.2.0.sql'); -- Update the help text for form pfm_section UPDATE pfm_form SET HELP = 'The data returned by the report''s SQL SELECT statement may be considered as a table with a column for each ''field'' specified after the word ''SELECT'' and with a row for each record. By specifying an ''ORDER BY'' clause in the report''s SQL SELECT statement, it is possible to group rows with the same values for some fields together. The report generator has an "economy" algorithm which avoids printing the same data repeatedly. To control this you have to distribute the fields (columns) of the table over n sections such that section 1 contains the fields that are changing least frequently (when moving from one row to the next), section 2 contains the fields that are changing more frequently, and section n contains the fields that are changing at every row. When the data of the first row of the table are printed, the data of section 1 are printed first. Then, on the following line, indented by one tab stop, the data of section 2 are printed. Then, on the following line, indented by 2 tab stops, data of section 2 are printed, etc. [section 1] <--- row 1 [section 2] <--- row 1 [section 3] <--- row 1 Then, when the next rows are being printed, data of the lower numbered sections are only printed if they are different from the data of the last printed section of the same number: [section 1] [section 2] [section 3] <--- row 1 [section 3] <--- row 2 [section 3] <--- row 3 [section 2] [section 3] <--- row 4 [section 3] <--- row 5 [section 1] [section 2] [section 3] <--- row 6 [section 3] <--- row 7 The report generator also enables you to print a summary at every point where a higher numbered section is about to be followed by a lower numbered section: [section 1] [section 2] [section 3] <--- row 1 [section 3] <--- row 2 [section 3] <--- row 3 [summary 3] [section 2] [section 3] <--- row 4 [section 3] <--- row 5 [summary 3] [summary 2] [section 1] [section 2] [section 3] <--- row 6 [section 3] <--- row 7 [summary 3] [summary 2] [summary 1] A summary i is printed just before a lower numbered section j (j < i). Its data can be calculated: - by applying one of the aggregate funtions: COUNT, SUM, AVG, STDDEV, MIN, MAX; - on the fields of the sections j (j >= i), between the last printed lower numbered section k (k < i), till the next (not yet printed) lower numbered section k (k < i). In particular, summary 1 is printed at the end of the report, is calculated from all the sections of the report and may be calculated from all the fields. A record in pfm_section defines a section and a summary of a report. The table pfm_section has the following attributes: - report: the name of the report to which the section belongs - level: a number 1, 2, 3, 4, ... . The first level must be ''1''. The next levels must be numbered consecutively. In the most simple report, there is only a section with level 1. - layout: can be "row", "column" or "table". - fieldlist: a space separated list of field specifiers, one for each field to be printed in the sections of this level (see below for details). - summary: a space separated list of summary field specifiers (see below for details). The fieldlist is a SPACE separated list of field specifiers field_spec_1 field_spec_2 ... field_spec_N where each field specifier is formatted as follows: {field_i label_i alignment_i max_length_i} where : - field_i is the name of one of the columns returned by the report''s SQL SELECT statement; - label_i is a string which has to be used as label for printing the i-th field of this section; if it consists of more than 1 word, it must be delimited by double quotes (" .... "); - alignment_i is optional; if present, it is either l or r, indicating whether this field should be left or right aligned. - max_length_i is optional: if present, it is the maximum number of characters per line for printing the data of this field; lines longer than max_length_i will be wrapped by inserting one or more line breaks before printing. Notes : o The alignment is optional. If it is left out, left alignment is assumed by default. o The alignment only influences the table layout. Column and row layouts are unaffected by the alignment indicator. o Multi-line fields, i.e. fields containing more than one line of text are only formatted properly in a column or table layout. o For a table layout, pfm automatically calculates the column width that is required to display all data. So, normally you don''t have to worry about column widths. However, sometimes, the data of a few records, make the columns excessively wide. That is where you might consider using "max_length_i" in the field specifier. If the data do not exceed that maximum, it won''t have any effect. o Although ''alignment'' and ''max_length'' are both optional, you have to specify ''alignment'' if you want to specify max_length. For every section, the layout can be defined as: - row: the section''s field labels and field values are printed in one row in a format: label_1 : value_1; label_2 : value_2; ... etc. - column: the section''s field labels are printed in a first column, the section''s field values are printed in a second column. - table: the section''s values are printed in a table with a column per field and a row per record, the section''s field labels are used as column headers for the table. The summary must be formatted as a space separated list of summary specifiers: summary_spec_1 summary_sepc_2 .... summary_sepc_N where each summary_spec is formatted as follows: {field_i aggregate_i format_i} where: - field_i is the name of a field defined in the fieldlist of either this section, or another, higher numbered section; - aggregate_i is one of the aggregate functions: COUNT, SUM, AVG, STDDEV, MIN, MAX (see below for details); and - format_i is an optional ''ANSI C sprintf'' formatting string (see below for details). If it is left out, the number is printed with maximum precision. Aggregate functions: In general, the aggregate functions, use the same "economy" algorithm that is used for printing section data. When all the fields of a section, which is not the highest numbered section of the report, have the same values for a number of consecutive rows, this section''s data are only printed once for these rows. Similarly, these rows are only counted once by the aggregate functions applied to a field of this section. The aggregate functions that can be used in a summary are: - COUNT: Counts the number of rows. In this case, the field_i that is specified only determines which section is counted. - SUM: Calculates the sum of all the values of the specified field. - AVG: Calculates the average of the values of the specified field. - STDDEV: Calculates the standard deviation for the values of the specified field: SQRT (SUM( (value_i - AVG(value))**2 ) / N) where : - value_1, value_2, ... value_N are the values of the considered field; - AVG(value) is the average of the considered values; - N is the number of values. - MIN: Calculates the minimum of the values of the specified field. - MAX: Calculated the maximum of the values of the specified field. ''ANSI C sprintf'' formatting string: Here is a short overview of the ''ANSI C sprintf'' formatting string. In general its form is: %''MinWidth''.''Precision''''Conversion'' where: - ''MinWidth'' is an integer defining the minimum width (as number of characters) for the number to be printed. If the number does not need so much space, spaces are inserted in front of the number, unless MinWidth is negative. In that case, spaces are appended at the end. If the number needs more space than MinWidth, more space is used. - ''Precision'' is an integer defining how many digits to print after the decimal point, or, in the case of g or G conversion, the total number of digits to appear, including those on both sides of the decimal point - ''Conversion'' is one of: o d : convert integer to signed decimal string. In this case, there is no need to define a ''Precision''. Example: %1d prints an integer and uses as many characters as required. o f : convert floating point number to fixed point notation. In this case, ''Precision'' defines the number of digits to print after the decimal point. If there are not enough digits available, trailing zeroes are appended. Example: %1.2f prints a floating point number wiht 2 digits after the decimal point and uses as many characters as required. o e or E : Convert floating-point number to scientific notation in the form x.yyye±zz, where the number of y''s is determined by the ''Precision'' (default: 6). If the precision is 0 then no decimal point is output. If the E form is used then E is printed instead of e. Example: %1.5E prints a floating point number in the form x.yyyyy E±zz o g or G : If the exponent is less than -4 or greater than or equal to the precision, then convert floating-point number as for %e or %E. Otherwise convert as for %f. Trailing zeroes and a trailing decimal point are omitted. In this case the ''Precision'' specifies the total number of digits to appear, including those on both sides of the decimal point Example: %1.4G prints 2345.0 as 2345 prints 234567.0 as 2.346E+05 prints 0.003456 as 0.003456 prints 0.00003456 as 3.456E-05' WHERE name = 'pfm_section'; ALTER TABLE ONLY pfm_form ADD COLUMN sqlorderby text; ALTER TABLE ONLY pfm_form ADD COLUMN sqllimit text; UPDATE pfm_form SET sqlselect = 'name, tablename, sqlselect, sqlfrom, groupby, showform, "view", help, pkey, sqlorderby, sqllimit' WHERE name = 'pfm_form'; UPDATE pfm_attribute SET nr = nr + 2 WHERE (form = 'pfm_form') AND (nr >= 7); INSERT INTO pfm_attribute (attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default") VALUES ('sqlorderby', 'taQuoted', 'tgDirect', '', 7, 'pfm_form', 'none', ''); INSERT INTO pfm_attribute (attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default") VALUES ('sqllimit', 'taQuoted', 'tgDirect', '', 8, 'pfm_form', 'none', ''); UPDATE pfm_form SET sqlorderby = 'form, nr' WHERE name = 'pfm_attribute'; UPDATE pfm_form SET sqlorderby = 'showform DESC, name' WHERE name = 'pfm_form'; UPDATE pfm_form SET sqlorderby = 'fromform, linkname' WHERE name = 'pfm_link'; UPDATE pfm_form SET sqlorderby = 'name' WHERE name = 'pfm_report'; UPDATE pfm_form SET sqlorderby = 'report, "level"' WHERE name = 'pfm_section'; UPDATE pfm_form SET sqlorderby = 'valuelist, value' WHERE name = 'pfm_value'; UPDATE pfm_form SET sqlorderby = 'name' WHERE name = 'pfm_value_list'; UPDATE pfm_form SET help = 'A form allows the user to administer the data of just one table. This table is henceforth referred to as "the form''s main table". However, a form also has an SQL SELECT statement, which generates the data that are displayed on it. In the simplest case the SQL SELECT statement is just: SELECT FROM
In that case, the data which can be administered and the data which are displayed on the form are the same. In more complex cases, the
can be JOINED with other tables, which makes it possible to display data of other related tables as well. These data cannot be modified by means of the form. The table "pfm_form" has the following attributes: - name : the name of the form (usually equal to the name of the form''s table); - tablename : the name of the form''s main table; - pkey : the primary key of the form''s main table, which may consist of more than one attribute. In that case pkey is a SPACE separated list of the attributes of the primary key; Note: If pkey is empty, the form is read-only, since pfm is unable to uniquely identify a record. You can use the ''oid'' as primary key, but according to the PostgreSQL documentation that is not recommended, unless you set a UNIQUE constraint on the ''oid''. - sqlselect : the attribute list of the form''s SQL SELECT statement, not including the word ''SELECT''; - sqlfrom : the FROM clause of the form''s SQL SELECT statement, not including the word ''FROM''; - groupby : an optional ''GROUP BY'' clause, not including the words ''GROUP BY''; - sqlorderby : an optional ''ORDER BY'' clause, not including the words ''ORDER BY''; - sqllimit : an optional ''LIMIT'' clause, only specifying the limit value as a positive integer; Notes: - This enables the designer of the form to avoid excessive memory usage by limiting the number of records loaded in the form''s internal buffer. This may be useful for handling large tables. - If sqllimit is a positive integer, a LIMIT sqllimit OFFSET 0 is added to the form''s SELECT when opening the form. This means that only ''sqllimit'' records are loaded into the form''s internal buffer. When the user moves beyond the last record in the internal buffer, the internal buffer is first cleared and then reloaded with the next ''sqllimit'' records by re-executing the form''s SELECT but now with another OFFSET in the LIMIT clause. - If sqllimit is an empty string, no LIMIT clause is appended to the form''s SELECT. - Always specify an ''sqlorderby'' if you specify an ''sqllimit''. See PostgreSQL documentation of LIMIT-clause in SELECT statement for more details. - showform : a boolean indicating whether the form is shown in "normal mode" (showform = ''true'') or in "design mode" (showform = ''false''). Typically, showform is set ''true'' for user defined forms and ''false'' for the predefined pfm_* forms. - view : a boolean indicating whether or not the "tablename" is a view; - help : a text which is displayed when the user presses the [Help] key on the form. The form''s main table is defined by tablename. Only the data of that table can be administered by using the form. All the data generated by the form''s SQL SELECT statement can be displayed on the form. The SQL SELECT statement is defined by: - the sqlselect, sqlfrom, groupby, sqlorderby and sqllimit attributes of pfm_form; and - the optional WHERE and ORDER BY clauses provided by the user when opening the form. Note: The WHERE clause provided by the user when opening the form, becomes a HAVING clause, if there is a GROUP BY clause. The following rules should be observed when filling out sqlselect and sqlfrom: 1. The form''s main table must appear in ''sqlfrom'', and must not be aliased. Similarly, the main table''s attributes appearing in ''sqlselect'' must not be aliased. The other tables appearing in the ''sqlfrom'' may be aliased. 2. The fields appearing in ''sqlselect'' must have a unique, simple name without the need to precede them with a tablename. So, calculated fields must be given a name by aliasing and attributes of tables other than the main table may need to be aliased in order to have a unique, simple name. 3. The ''sqlfrom'' is either just the name of the form''s main table, or it is a JOIN clause in which the ''LEFT'' table is the form''s main table. Several join clauses can be nested in order to involve more than 2 tables. See examples below. Example 1: the SQL SELECT for the person form of the addressbook database tablename: person pkey: id sqlselect: id, christian_name, name, street, town, "ZIPcode", country, category, description sqlfrom: person groupby: - Example 2: the SQL SELECT for the memberlist form of the addressbook database tablename: memberlist pkey: group person sqlselect: memberlist."group", memberlist.person, p.christian_name, p.name sqlfrom: memberlist LEFT OUTER JOIN person p ON (p.id = memberlist.person) groupby: -' WHERE name = 'pfm_form'; UPDATE pfm_form SET sqlselect = 'pfm_section.report, r.sqlselect, pfm_section."level", pfm_section.fieldlist, pfm_section.layout, pfm_section.summary', sqlfrom = 'pfm_section LEFT OUTER JOIN pfm_report r ON (pfm_section.report = r.name)' WHERE name = 'pfm_section'; UPDATE pfm_attribute set nr = nr + 1 WHERE (form = 'pfm_section') AND (nr >= 2); INSERT INTO pfm_attribute (form, attribute, typeofattrib, typeofget, sqlselect, valuelist, nr, "default") VALUES ('pfm_section', 'sqlselect', 'taQuoted', 'tgReadOnly', '', 'none', 2, ''); pfm-2.0.8/postgresql.tcl0000644000175000017500000002557211473467234013412 0ustar wimwim# postgresql.tcl # Init Pgtcl or pgintcl # Experience has shown that pgintcl has to be sourced at the global # namespace. proc initPgtcl {} { # From version 2.0.5 on pfm also tries to load pgintcl as a tcl # package, if it is not found in the installation directory global env set env(PGCLIENTENCODING) "UNICODE" if {[catch {source [file join $::config::installDir pgin.tcl]} errMsg1]} then { if {[catch {package require pgintcl} pgintclVersion]} then { if {[catch {package require Pgtcl} PgtclVersion]} then { pfm_message [mc no_api $errMsg1 $PgtclVersion] {.} set API {} } else { set API [list Pgtcl $PgtclVersion] } } else { set API [list pgintcl $pgintclVersion] } } else { if {[catch {set pgintclVersion $pgtcl::version} errMsg]} then { set pgintclVersion "???" } set API [list pgintcl $pgintclVersion] } return $API } proc getPostgresqlDefault {option} { global tcl_platform switch $option { "dblist" { set value [list $tcl_platform(user)] } "dbname" { set value $tcl_platform(user) } "host" { set value {} } "port" { set value {5432} } "user" { set value $tcl_platform(user) } "psql" { switch -- $tcl_platform(platform) { "unix" { set value {psql} } "windows" { set value {psql.exe} } default { set value {} } } } "usePGPASSWORD" { set value 1 } default { set value {} } } return $value } class PostgresqlApi { public variable state closed public variable dbname {} protected common psqlCommands { listDatabases {\l} listTables {\d} helpSQL {\h} helpTool {\?} quit {\q} importFile {\i} } protected variable host {} protected variable port {} protected variable user {} protected variable password {} protected variable db {} constructor {} { return } destructor { return } protected method readPgPass {passMatrixName} { upvar $passMatrixName passMatrix global tcl_platform global env # This procedure reads the ~/.pgpass file if it exists and if it # has the right permissions (00600, i.e. rw for owner only). # It parses this file and stores the result in passMatrix. # This procedure supports the backslash escape for : and backslash. # backslash backslash is read as backslash # backslash ':' is read as ':' and not interpreted as entry separator # backslash 'anything else' is read as 'anything else' # (i.e. backslash is dropped) # ':' is interpreted as entry separator # On Windows platforms, the pgpass file is # %APPDATA%\postgresql\pgpass.conf set seqnr 0 if {$tcl_platform(platform) eq {windows}} then { set filename [file join $env(APPDATA) postgresql pgpass.conf] } else { set filename [file normalize "~/.pgpass"] } if {[file exists $filename]} then { if {$tcl_platform(platform) eq {unix}} then { set filePermission [file attributes $filename -permissions] set first [expr [string length $filePermission] - 3] set filePermission [string range $filePermission $first end] } else { set filePermission "600" } if { $filePermission ne "600" } then { set map {0 --- 1 --x 2 -w- 3 -wx 4 r-- 5 r-x 6 rw- 7 rwx} set filePermission [string map $map $filePermission] pfm_message [mc wrongPermissions $filePermission] . } else { if { [catch {open $filename r} pgPass ] } then { pfm_message $pgPass . } else { set argList {host port dbname user password} while {![eof $pgPass]} { if {[gets $pgPass current_line] > 0} then { incr seqnr foreach name $argList { set passMatrix($seqnr,$name) {} } set arg {} set argNr 0 set last [expr [string length $current_line] - 1] for {set i 0} {$i <= $last} {incr i} { set curChar [string index $current_line $i] switch -- $curChar { "\\" { # This is the way to write 1 backslash: # NOT with curly braces. # Skip the backslash and copy the next character incr i append arg [string index $current_line $i] } ":" { # end of an arg set name [lindex $argList $argNr] if {$name ne {}} then { set passMatrix($seqnr,$name) $arg } # puts "$seqnr, $name : $arg" set arg {} incr argNr } default { # just copy the character append arg $curChar } } } # We are at end of line. Just copy the last arg. set name [lindex $argList $argNr] if {$name ne {}} then { set passMatrix($seqnr,$name) $arg } # puts "$seqnr, $name : $arg" set arg {} incr argNr } } close $pgPass } } } return $seqnr } protected method getPasswordFromFile {} { # This procedure tries to get the password from ~/.pgpass # It returns the found password. If it does not find # a password, it returns the empty string. set nr_of_lines [readPgPass passMatrix] set found 0 set password {} for {set seqnr 1} {($seqnr <= $nr_of_lines) && (!$found)} {incr seqnr} { if {(($host eq $passMatrix($seqnr,host)) || \ ({*} eq $passMatrix($seqnr,host))) && \ (($port eq $passMatrix($seqnr,port)) || \ ({*} eq $passMatrix($seqnr,port))) && \ (($dbname eq $passMatrix($seqnr,dbname)) || \ ({*} eq $passMatrix($seqnr,dbname))) && \ (($user eq $passMatrix($seqnr,user)) || \ ({*} eq $passMatrix($seqnr,user)))} then { set found 1 set password $passMatrix($seqnr,password) } } return } public method connect {} { set conninfo {} if {[string length $host]} then { lappend conninfo "host='$host'" } if {[string length $port]} then { lappend conninfo "port=$port" } if {[string length $dbname]} then { lappend conninfo "dbname='$dbname'" } if {[string length $user]} then { lappend conninfo "user='$user'" } if {[string length $password]} then { lappend conninfo "password='$password'" } if {[catch {pg_connect -conninfo [join $conninfo]} db]} then { pfm_message [mc pg_connect_failed $dbname $db] {.} set state closed set status 0 set db {} } else { set state open set status 1 } return $status } protected method registerDatabase {newdb} { set save 0 set dblist [$::pfmOptions getOption postgresql dblist] if {$newdb ni $dblist} then { lappend dblist $newdb set dblist [lsort $dblist] $::pfmOptions setOption postgresql dblist $dblist set save 1 } set lastUsed [$::pfmOptions getOption postgresql dbname] if {$lastUsed ne $newdb} then { $::pfmOptions setOption postgresql dbname $newdb set save 1 } if {$save} then { $::pfmOptions saveOptions } return } public method setConParms {a_host a_port a_dbname a_user a_password} { set host $a_host set port $a_port set dbname $a_dbname set user $a_user set password $a_password return } public method opendb {} { set dataList {} foreach openParm {host port user} { set dataSpec {} dict append dataSpec name $openParm dict append dataSpec type string dict append dataSpec value [$::pfmOptions getOption postgresql $openParm] dict append dataSpec valuelist {} lappend dataList $dataSpec } if {[$::pfmOptions getOption postgresql usePGPASSWORD]} then { set dataSpec {} dict append dataSpec name password dict append dataSpec type password dict append dataSpec value {} dict append dataSpec valuelist {} lappend dataList $dataSpec } set dataSpec {} dict append dataSpec name dbname dict append dataSpec type string dict append dataSpec value [$::pfmOptions getOption postgresql dbname] dict append dataSpec valuelist [$::pfmOptions getOption postgresql dblist] lappend dataList $dataSpec set dlg [GenForm "#auto" . [mc OpenDialog] $dataList] if {[$dlg wait result]} then { foreach parm {host port dbname user} { set $parm $result($parm) } if {[$::pfmOptions getOption postgresql usePGPASSWORD]} then { set password $result(password) } else { getPasswordFromFile } if {[connect]} then { registerDatabase $dbname set status 1 } else { set status 0 } } else { set status 0 } return $status } public method closedb {} { pg_disconnect $db set db {} set state closed set dbname {} set host {} set port {} set user {} set password {} return } public method select_query {query numTuplesName resultArrayName errorMsgName} { upvar $numTuplesName numTuples upvar $resultArrayName resultArray upvar $errorMsgName errorMsg set resHandle [pg_exec $db $query] if {[pg_result $resHandle -status] eq {PGRES_TUPLES_OK}} then { set status 1 set numTuples [pg_result $resHandle -numTuples] pg_result $resHandle -assign resultArray } else { set status 0 set errorMsg [pg_result $resHandle -error] } pg_result $resHandle -clear return $status } public method select_query_list {query numTuplesName namesName \ resultListName errorMsgName} { upvar $numTuplesName numTuples upvar $namesName names upvar $resultListName resultList upvar $errorMsgName errorMsg set resHandle [pg_exec $db $query] if {[pg_result $resHandle -status] eq {PGRES_TUPLES_OK}} then { set status 1 set numTuples [pg_result $resHandle -numTuples] set names [pg_result $resHandle -attributes] set resultList [pg_result $resHandle -llist] } else { set status 0 set errorMsg [pg_result $resHandle -error] } pg_result $resHandle -clear return $status } public method send_command {query errMsgName} { upvar $errMsgName errMsg set resHandle [pg_exec $db $query] if {[pg_result $resHandle -status] eq {PGRES_COMMAND_OK}} then { set status 1 } else { set status 0 set errMsg [pg_result $resHandle -error] } pg_result $resHandle -clear return $status } public method connect_sql {errChan outChan sqlChanName} { upvar $sqlChanName sqlChan global env set cmd [list | [$::pfmOptions getOption postgresql psql]] lappend cmd {--echo-queries} foreach parm {host port dbname user} { if {$parm eq {user}} then { set option {--username} } else { set option "--$parm" } set value [subst $[subst $parm]] if {$value ne {}} then { lappend cmd $option lappend cmd $value } } lappend cmd ">@$outChan" lappend cmd "2>@$errChan" # puts $errChan $cmd if {[$::pfmOptions getOption postgresql usePGPASSWORD]} then { set env(PGPASSWORD) $password } if {[catch {open $cmd WRONLY} sqlChan]} then { unset -nocomplain env(PGPASSWORD) pfm_message [mc psqlFailed $sqlChan] {.} set status 0 } else { unset -nocomplain env(PGPASSWORD) chan configure $sqlChan -encoding utf-8 set status 1 } return $status } public method getSpecialCommand {purpose} { if {[dict exists $psqlCommands $purpose]} then { return [dict get $psqlCommands $purpose] } else { return {} } } } # Main # Init Pgtcl or pgintcl set config::API [initPgtcl] pfm-2.0.8/doc/0000755000175000017500000000000012110362244011215 5ustar wimwimpfm-2.0.8/doc/pfm-logo.png0000644000175000017500000000343010776162072013460 0ustar wimwim‰PNG  IHDRPPseúbKGDÿÿÿ ½§“ pHYs  šœtIMEØ62© tEXtCommentCreated with The GIMPïd%n|IDATxÚí›ÉoUÀ¿7ûx쉗ØÙ§N÷6©ZÊ"¡VUÕ"AKŸp D¹€Ä?޽#. U‚ꉥ¨.,]¡!´Yj'ŽqêuflgáÔ~“¸ Ô)Iú¾ÛÛßoæÛ泌’9%¡àL€ 0&À˜`L€Û$LËÞ¬’Ñ5u=òH^_g¸ë_ëšZÈçÖ#0Bî LTºíܱÄÊ‚GÏpF‰1+ȱû5©w¡sÏFFŽŸúªsáž¡-Êt?µ€^çîwv½¼rº{úlËAQàz"A¼§Z3òEumƺ—Ýò.bÏŽ¹kË HˆŠ€`Àß=¼I+¹Ë#ëX¥ùNa®Ùfþõ©Ø øÿd5µ8ÍLuP›[»^š¯ùZ¾Ñ4A÷t狪¨g=z†*¤Ç©z"ª·P3¼!Ûòª3@Í'¶¡e;êÄ•f¨÷t[Œ€lK¬di« fQ•K™ª48™¶ª^uNÒRŒ©œ\»Jò€ƒè†ç÷èŠWMrµ’Ms5Þ_öÅj¼ýwàpöJ,ñ]£Y’ã£Coh¿2nžeô mÕÀ¦C-ôLE™ŒgIsò?¶®ã»;Ùiãó÷ͱá·Jrœ­«ÛÇ>“KS  L¾ ïÝ>þ¹??.Tsà ªÆûS}‡fûšŒ€;>ýMX¹$iéÅ gÑ|IÞ”xöÖ}¸ý;3u]¬,4š×±åÆÑÙ)Ûlj©mzôù-gü…ñÑ¡7™‘cã ±Éu¼Ÿ²ê‹“y£€÷ËÅ©¾Ù;Š“xlª¹Í“_zÕÔõí¯l½ñEwúW|gÚªòãÅÉß÷¾ Áí±aIKûÊ œ—Î…‘è̉MÇô ©[×üƬ´ô å2åÔC £­sFی͜Oìk0cê6Åü[•È~‹åÒTgönäÑÙŠþ­ùÀv›f•Èã is’ÖôÒuÖ›ìh69o˃غfÑü­Ð°îéòèé`îï?r¬°r4©§àßfÑ| ?æ+'k; år`O{¼t>°slÇ«»Ÿ| m²©Kpæ}ÌÉ•¶9Isø8ÀÞS@ÿv~ù¤©™‘AîåSæ„éyiJ„<½)f¦ÿÈÍø ‹æ³²ë¯OÃÊeüçzSõF„|åÙ'.|€ëöÃÒy]iWXB[_ªŠ¡fâÑ}Ìøó[k짆ÊÉ©K<––˜^±Ž­çX¦§+´\MUÇ`Yë)ÉñÙþ£Í€Éˆ¹àNØA(1ð\Ù[l–}ýe_L.M7ÆY-Ûž€Áy5©wùâø~WjµÛz@Ö¤^ƒ“±se|Ôd$õ6mã…»®dÖÚ\:W:$è„…>«îTŠ\ñDlO›b]é9ë5î`üíQéÆ%ðtWž™h¦S×F'Mq©ržÏ…±T­úçŠT™©¢ZŃß1îs]OÙ¡èFúÑZ«’ZòF©‘â7Õ#›×y(U4ìòÒA«àÀF½Ž¯]2l£©×=îšÅ=½ÉÃ(âñÕÙôŒXãø„›ñãs½›ß};:ÛXâ±hN í‚ûsDÐZþL€®Ç]J«iÞÍp;wRjÝ]¸4¸eÙ&n)ø·¹Ž”Cm®i=„Ââš°á #x£Ëm¸$ošé?ÒüjÃ8Ú í^Ùÿü‘'Wï6—G&ÒJލ4&À«’Z.Ê×ß_ o˜`L€×¬—î‰WVjÚ(«ˆJàG݆ÓJn]'[D¥ 0&ÀÀK{$ï:å¹çÍùK<&À˜`L€ 0&Àø~äEsŒtÁUHIEND®B`‚pfm-2.0.8/doc/open_form.png0000644000175000017500000004234211000334330013704 0ustar wimwim‰PNG  IHDRkà­L pHYsÄÄ•+tIMEØ (cšzâ IDATxÚíÝwXGð¹ÊÑ{G¤ ‚X+ˆkTõ5 ¢1%ÆQ4&jb‹˜b4ŠFj4h4ÆŽFD)F@iÒ¥sÇݽ¬®—+Ë”ïçáÑ»ÙÙÙÙ™½ýÝìÞÍqÈ9¸xB!YéÉŠO9JÑâþDå§ÈÞŠ‘ƒC *ZÜË)AÅÙÖ„ŠTØàÐ#õA!ZàåÈ:µ•eN‡à¯|ݼ´Ä‡GVg¥'óé$©L†^x ²ÿøvÀ”%,3ÿ¾#zÀ”%w2½Úu)j‘ôozà%È;ë>ôöùWTç0eIÒ¿y¯j]BHqÆ­‡GVóÕ2Êÿþž§¥Ý`‰åžU—·8\.€F‘JÙ^Ú¡ÎÏR©Œ>Q¿ªu !|µ¥èÈ*òŠë,±¬8Ï´ãžP„îA]¦ö²5ÓpüÕÍ–P¥ø(¢¹2ÌK_Z5ZN™-|ß‹z]vÿ!c};€:?KerúDýªÖ}3”J= [È€.—Ëår8\.÷éÃÿþ?mþªB¹Œƒ!/ÌŒ>¶Æº‚ÑÓæ§¥Þ%r¹Íˆ-¤bÙ3T†yéK«FË)³…ï»bQÌÅž˜ëK¸.¯Íç0Î`}æ¤ÎÏR™Œ>QÓëÆMwW»JØ·),×¥sª¦¨®û,fÈårÅíqX"D.—+­ Ï‘±®€’–rGÏ­/WK·òþ]ÇÎ-¤nÌ•yiU}jQíürêYyÿJÈò¼CËØ›ûÛRëáŸâåùÒÆÔ9V*{v²mp]:CƒëÆMw³õ!dÿ ×Õ3ˆœp¸\*.p9OBá ù„ÞNe×~‘ˤ#ý, !Ÿ¶º¼V.µíºåøÞÑ0/¥lŠÝU–qS·}÷ÐP/#BÈæ_J¸:‡’ª !£zºVÜ9EäD[È•Ô×gßOÕ²õþWj¿`ÓÑÊÔ3ÒÚrMétÓåd¨6]¨!$vÓ©ÐèàÍ BȰî®·OȉœaUæØƒOì—_R9azäßWÿÑuìÂáòú.9æJ©Éº®©Ùô˰NšZ ±«ißÜ„b¨Ý‘š¬ëÌÍN ­,®MÉYþ©Ž-©ÍÏf]Å ‹”ÆCšïgp‚ƒrð ôÀצš/ïQ¡Žk®@û±vBˆµ¥yÍÃ"[/ž¾¹b6‘½/õ ´ì±¾GW9_‡zZ\R¦ß¾3Gd@uœ¬®J\œ¡e­q®ÉGG>µ]ª˜âÝFr/»væ´…|ꀡ¾¤ä¡¤ä…AWº†|]Ór6Š+Z–R ‹Kô<|yZú“6Æz„ø¸Øg£K‹Úì´Ûxÿp>gJO«Ÿ¿ýR&“§ç•Ǟͼ|+½¾¢@S:sÓ™ê !Å¥¥:î¾å]Bˆ¥¹i]n’v›N ‹¨2çÎzÃ!_nÜœ‘[¬ã܃/2ä›;ñD†]ÆÉkËÕ\È}ŠM¿˜é÷ÖÔì !¤þ›é ™7¡Ø#šæfW< ñòoúµ)i#ïgHŸl×U[ȼNŠkþ ã¯n0l—°½Nþ;¾àp©0"'rČ泶4¯jóŒí,Lô¨sL\S_ñˆ«g¦zÜ<é ¾ohýì©@‹÷ì©\^_§Ô)ý?¿\x,š~Zû Q«Í³ceñpg]Á‡+·üõç‘^=u€ÃáÈ¥imyA¹ØÚH‹®¡¹‰žb±ÌKŸ]勸†ÖrÂ),[iõ>¡¸¤LqH[_^xàjÞÞÝ?Ù K»vòŠ wQ¨Ó°Ä;Bs'MéÌMWR%1ך×ñDfVv„G…Å2I­´²aUæì+¾‰Y¶zñî7¿\̬˜µãDr¹\hÞNýÍ?ÅNCýÂÐì aÞw6› {DÓŽ07{ƒíº~|®ŸÚ ƒÖ]ÿï½h™ÚûØjÜÇV³.½]jCÔÓcQ¶«ñ~¸ô ®b¤ ‚Q¼͹¬9wÖ!1ÐÑïkOùiß!®P[.•(½&•Ÿrxÿ=]ñ? º-Ã.ã¨ë Ô5®ÿv7!„”åg´óŸ÷ñ²g¥Èê\ÍS­¡â™…aéjËåËåò×ò!Q3&[û…Zv±}Û¯P"•,éâÙ­¾v‡¤!¤¶¶¶<ñim¹¦tæ¦û=©òî;£Üú1ÝÚB9Ɉ˜Qe^I¼õц_ä2ùç‘aƒü _K.—ÿ±°ë »2÷#›~ahö…0ï;ÛMpù '}MÍ^]'%„ØÙXá^fó_øR™Œúc¸INý=½­~Ýãsý”þدK°ö*õtÀÚ« Û¥Wç«­ëÇ.œ¸|—p<ý÷Ùý :ñNV‘¾9îj4×Ù —·Ì}ÇÖÚ²¸²îË~Û{ð¨È¶‡+x¾[˜9iZ´úð½ˆú;6¬.*«úùB–âééàµ|iaÚØþŽ|Ñ-·´f÷_ÝLž}<ƒq©ºw¯ùånîÓáhpo±”Üήˆ;}³&ï®açwNÜ*œhéù^g¹œ“”ñhíš/8n}qÖ‰[öjÓ™›îÇóÙõ…÷õ œ0Ƹ¨Ròí³;víZ¸rxL‹èzÞ|P1oÓ¡µ³†/ú__ƒ_ÎL²¨(~¥ÙàÅÍé†x^‡ÍsÙ„¦îØy!{|3êÂW¿Ï.áÅû\ÆA1—Y]ËR7¶x¾ëª-M)ª=™oê¯Ä»ÿÙ™z‰\&m¸"ÂãkÜ oª3Ÿô$„x†j·ñ©+¼/Wq"¡™“ÐÔghÍÕ5%„Ÿøœºå(°éÐØ§ìÕe]«¾I&®âjé‰ì|ªï§Ë‘ÕVÔÜûKü(MV_ÇÓ1Ò²îP}ïË¥ªõ‘ÕUÔþ{±®àžL\Éåòy†6B³v|s¾iÛº‰5oȪJå„ð´DÖn| ¾…›¦tªpMM'«*®N;'.É’Kê¸Z:B¡™×À’§oΰˆ®0ßÂ¥öÁê{çˆL¦ãÔUÛµ/sÛ²ï†`_HûÎzj‹¥kjöª;¿×å$QÃ2Ó ñ*n‚꿸ýàAî#ÖáEZ}ãçA³ró½ªu !õ%žÍ7¥!8\.ËY¤ärÁUÍæŒí„–nòú"“.«gÆÑ6¢:åÙ­×Æ?mD¬<ôõÍI]µ\.#\®ßh"©¥Êáu´ÛõZº‘ú"'„Ç7ô#—Ô°YªZŽ@GäÔMháB$5r"#rG¨ÍÕ1–ˤK7¾¾¹¬®ŠÈ뉜Ãhqõ-ÒéÂÕ6Gd¨íÖG«ª˜Ô×¹œpy\c®®1ó"ºLÂåkÙz ,åâjBäÒŠæ¶eß/ -À¾öõ&4ËÐì"Ç®Zæíä’ZBä’ÜÛ|«öxý6sœÑð›s™ôÉ}…§½öªÖ}6Î8wåtáË÷ç²~Ô8ÃÀ;”gáŠh%j“ Ý‚Yf§žr‘——ójו•çkgÀËÑkÙ©²„ è€ÖÆ!xF£~û(7çÁ+_÷?㌳_E/¼4ÖvmYæÌËÎlëV—<gjü 8ÀË“—VòÚ­KáöèZmâ‰þf:%É|Bȵ#;J ²©$ ;´ Pèèð('3üýÔ|§/-- Íqqq³Â'+¦à§¼€-Ä @ÌÄ @ÌÄ @ÌÄ xžúöí{ûömBH@@@zz:=Õ´Z¼~1#44ÔMÁëx(GDDÜ¿ÿùÂ\fdd¤ÍK¨jhh¨¯¯oYÙ“Ÿ€>vìØ¨Q£^ßãòMí©)S¦lذA1å?þJ1«1¼‰ãŒÕ«WßzÊÓ³áYªZÚ+!99¹ººúù¢©LjßGŒabbòrªÊçó·oßþf—ojO9òðáÊ?Ä}èС¡C‡òx<œŒà Œ@ë)êWÁÓÓÓßy矾}û8p€Ê°aÆaÆ 6ŒzúóÏ?‡„„øøø|þùçyyy&LèØ±ã¤I“ªªªU€€€¯¾újìØ±ƒ š?~]]•þå—_öêÕËÇÇgðàÁýõ•¸víÚnݺùûûýóÏ?Ë—//**š1cFß¾}ûí7BH~~þŒ3üüü¶lÙ¢º9Õb• Q-Si߯-\¼x1$$ÄÏÏO±æEEEÔãU«V­_¿^u+lªJ™4iÒž={èÒö‚e§°Ù(zªQU *++»víõ´¼¼üÌ™3#FŒÐÔ24µ[of¼ð˜¡¤¾¾><<¼W¯^‰‰‰7n\µjUbb"µ(--íàÁƒG¥àû÷ï?räÈÁƒ£¢¢V®\yñâE±X¼gÏžÆnôÞ½{{öì9vìXuuõ¦M›¨DOOÏ#GŽÜ¸qcúôé~øaMMÍÍ›7ãããããã¯]»¶k×.++«åË—›™™mݺ•z•Êd²ððpggçK—.ýú믇:yò¤Ò¶T‹U*Dé©Ú}§:ujÿþýgÏžÍÎΦk®VªJi×®]ppðÖ­[Ü 6Âr£è©FUU$ 4ˆŠ.„øøx'''www†nbÐü>x±1cÙ²e]»víÚµë¸qã!7oÞ¬®®çr¹^^^Æ £_~'NäóùÔX„ºŒk``ЦMÿnݺµmÛVGG'$$$%%¥±57nÇãr¹&Lˆ§CBB 9Έ#ŒÓÒÒD"QmmíÝ»wÅb±•••¥¥¥R9IIIÙÙÙQQQÀÂÂbüøñ'NœPÊ£Z,›*í;j}}ýˆˆºæl°©*åý÷ß?pà@^^›½`îöEO5ªª#GŽ}ÚËËëÖ­[ß|óM“w¡ù}ðbc†¡¡!ýr¢Îùùù2™Œ ¹¹¹ôiâÅÉÉÉqss£P›KOO_·n]\\œ““!dÀ€Ô=ÆqãÆ7®¬¬ìã?Þºuë'Ÿ|¢øŽÒÜÜÜØØøøñãªo3)šŠUʯiuU=¢^ÒùùùtC ‰DB=®¬¬ÔÕÕU-¶Áª*Šˆˆ4h­­-ó^4¨QEO5ªÑFŒñÛo¿edd2· MíÖ›ßG/öÚ”mmíØØX™L–œœ|äÈú-Ï‹³cÇŽòòòŠŠŠ­[·4ˆRUU%‰¨³ä… ²²²!÷îÝKJJ’ÉdºººÚÚÚÔ+ÐÀÀ ;;›®¼¡¡áÆkkk !™™™JŸV[¬R!ªO™k^QQQYY¹mÛ6ªæ„ww÷‹/R7!!Am± VU‘µµõèÑ£¿ÿþ{æ½`Ó¹ì7ŠžjT£1âï¿ÿÞ·o}aªÁnR»õæ÷ÀK|>ëÖ­ ¾¾¾³fÍZ¸p¡ŸŸß‹®qHHȘ1cz÷îmgg7kÖ,BHÇŽƒƒƒCCC'L˜pìØ1gggêE¸hÑ"ÿîÝ»×ÔÔÌœ9“2uêÔ+VøûûïÛ·Ë寯ÆÞ»w¯gÏž¾¾¾sæÌ)--UÜÚb• Q}Ê ((h̘1½zõ²²²¢jNY´hÑöíÛGµxñbÅ Jªª’3fÐý4íEÃGF#7ŠžbßS¶¶¶¾¾¾555ýúõcÙMj·Þü>h,N`®ç.\Rüm×–ü;}?üðƒ‹‹ z®…CO¼§Oão»ªE]˜V’ššŠ6m Ôöúè5ê)t¼.ØÆ Ó-z=вbF qáÂôz ^Ìk ˆ€˜ˆ€˜ˆÐÚ¨ù¬m\\ÚŽiiiaaa®®®¯¶ZÑÑÑaaa耖>Îpuu}åSN………ÅÅÅ!l´(Ü0Ãz åÆ Ä @ÌÄ x]cÆÑ£G‡îååÕ£G%K–<~üm€˜¡ÆÞ½{£££###¯_¿¾ÿþÒÒÒI“&Ñ¿8ÝR©MðÆŒºººuëÖ-]º´_¿~B¡ÐÖÖöË/¿,++ûõ×_ !EEETÎU«V­_¿žzœŸŸ?cÆ ??¿€€€-[¶P‰6l6lذaÃbccgÏžMoeåÊ•«V­B¼Þ1ãöíÛUUUAAAtŠP(ìß¿?ñÉd²ððpggçK—.ýú믇:yò$µ(--íàÁƒÔ•®óçÏ———SÃŽcÇŽ >ýðzÇŒÒÒR]]]---ÅD“ÒÒRM«$%%eggGEE  ‹ñãÇŸ8q‚Z4qâD>ŸÏápÌÍÍýýý©ôsçÎuèÐýÐ’5ü{àÆÆÆUUUuuuŠa£¤¤ÄÈÈHÓ*ùùùb±xÈ!ÔS‰DâîîN:ÛÈ‘#÷ìÙ3vìØÃ‡cð&Ä OOO]]ÝÓ§OÓ1@,ÿñÇÓ¦M#„‰DB¥WVVêêêBÌÍÍ?ÎápJ Z¾|yzzú™3gæÍ›‡Îhá¾6%‰¢¢¢V¬X‘ ‹srræÌ™£««;jÔ(Bˆ»»ûÅ‹ !¹¹¹ Ô*>>>†††7n¬­­%„dffÞ¾}[µd--­9sæxyyÙØØ 3^ûq!dܸqúúúëׯŸ={v}}}·nÝvíÚ%‰!‹-Z¸pá®]»ŒŒŒºwïþ$q¹±±±+W®ìÙ³§L&kÛ¶mTT”Ú’G޹ÿ~|b à͉„¡C‡:”òã?ÆÆÆÒé;v<~ü¸j~KK˯¿þZ)Qõ£VÖÖÖÔh=Ðò5zî‰'vèÐaÓ¦MÏeó?ýôÓ€ôõõÑoÎ8ƒÆápèïè5GMMM@@€¥¥å¶mÛÐ ofÌx^´µµÑ¯Ìk ˆð¼ñ[xý\]]ÑI¯JIAök3ÂÂÂÒÒÒÐg¯D\\ܬðÉŠ)¸6l!fb fÀ«ÂGÀK€Ï@"f4Bkød\\Ü’%KÞŒ=‹‹SMĵ)` 131Z³›7oVUU5j•7nÔÔÔ4s»Ï¥U}ûöUûÓ×/T@@@zz:b¼f®]»VQQA?ÍËËKIIyùÕHOO¯­­}Ñ[9yò䨱c;vìèãã3yòä+W®¼ˆ­DDDÜ¿ÿÅí>7­]UU•T*}¡›ˆ‹‹[»víÒ¥KûôéC¹xñâ±cǺtéòÜ7”œœ\]]˜­Ë7,--KKKëëëuuuÛµkÇáp3ÔÔÔdddTWWóù|[[[sssBˆX,ÎÌ̬¨¨àp8FFFNNNŠ«TVVÞ»wÏÑÑÑÐÐNÌÌÌ”H$iiiÇÎÎÎÌÌ,;;»¨¨H*• …B{{{:syyyzzºD"166nÛ¶-—Ëö:Mmmí_|±lÙ²¡C‡R)!!! Pë,]ºôîÝ»ÆÆÆï¿ÿþ[o½EY»víêë댌֭[×±cÇüüüåË—_½zU$M˜0!""B±åË—͘1C DFFŽ1âË/¿üí·ßÊËËmll-ZÔ³gO*çÅ‹ßÿý¢¢¢þýûGGGkii!fÀë­¦¦ÆÃÃ’’’RTTDEŠ\.OKK377oß¾}uuuJJж¶¶®®njjª¡¡¡³³3‡Ã©¬¬T,­¼¼üþýûÎÎÎzzzŠémÛ¶-++sqqÑÕÕ¥Rttt¼¼¼x<^QQѽ{÷|||x<!¤´´ÔÃÃÃᤥ¥åææÚÙÙ±ï_UU¥$C`}}}xxø˜1cvïÞœœüî»ï:::r¹Üøøøøøx“üü|‡#“ÉÂÃÿþúëÒÒÒ‰':99…„„(ÆŒ„„„Í›7wèÐJñôôœ2eŠÁ¡C‡>üðÃóçÏkkkBN:µÿ~‡¾iÓ¦9sæ°ÜÜÏ€ÊÒÒ’Ãáp8}}}¥ûÏ•••R©ÔÚÚšÃáèêêš™™WUU‰Åb{{{.—K­Eç/--½ÿ¾‹‹‹RÀPËÄÄ„ fff€Þ´••ŸÏçñx666ÅÅÅìw¤¬¬LWW—á½üÍ›7«««ÃÃù\®——×°aÃŽ=*‰jkkïÞ½+‹­¬¬,--“’’²³³£¢¢……ÅøñãOœ8Á¼éCCC‡3bÄcccúˆT Ñ×׈ˆˆÇµ)x (]nRJ¡NÜT¢L&SÌV__/ éÌB¡°²²R,k:/?zôÈÄÄ„I0+(((((¨¯¯'„H$zÓB¡~ ‘HØï¦‘‘QUUU]]¦ê[YYÑ»lllnß¾íîî>{öì5kÖdeeõêÕkÙ²eùùùb±xÈ!T6‰Dâîîμé½{÷îÙ³§´´”Çãз:¬¬¬èEEEˆðuuuô€@, Vg.>_,Ëår*lÔÕÕ …B@ ˜¨¨]»v™™™yyyÖÖÖÌ%WWW?|øÐÓÓS$Bþùç¹\NWOGG§Qõ¤/éêêþþûïôý Bˆb=MMMóóóe26rssÍÌÌ!ãÆ7n\YYÙǼuëÖAƒ?~\uÕÝôôôuëÖÅÅÅQ÷u @ïË£G¨x“ŸŸOm‹%\›€WÆÄÄ$//¯¶¶V.—WTT™˜˜°YQOOÇãåççËåòêêêââb===@-“ɨƒSûöí òóóUKãñxuuuÔcêÄM )?~L§Bòòò¤R©T*ÍÍÍeYOŠH$š;wîŠ+Ž9R^^^^^~òäÉO?ý”Îàã㣭­+“É’““92dÈ{÷î%%%Éd2]]]mmm¹\îããchh¸qãFê“Á™™™ªßí000ÈÎ~òk¬UUU"‘ÈÖÖ–ráÂ…¬¬,:ÛŽ;****++·mÛ6hÐ Œ3à5`kk+“ÉîÞ½+‘HD"‘‘‘›9Ž‹‹KVVVNNŸÏ···§+®®®YYY‰‰‰\.×ÈÈHñ–6¨ïÐWf(ÖÖÖYYYöööæææ&&&·oßZZZÔc:Â%''K$###êDÌÞÛo¿mll»xñb‡Ó©S'Å<ñùü­[·.[¶lóæÍFFF .ôóóûçŸ/^œ““Ãår;wîMMM}î›@Ì€×B³$”à8 fb¼*¸Ÿ/‰Úß—Æž"f(KKK Þ¾îÁצ€-Ä @ÌÄ xUp ‰eŸYq’G¦˜qòøQ´,À&dP(!dÍš5,óGGG³gLž4í ðÆÐ34£'Ümpæ]j‚^ÕtÜ϶3Z;z’yÄ ež^¾‰7n6mÝŠŠ O/_Gg÷AC†«.íѳϙ³âàC¼ŽƒeØh f\½z­O¿&æ6N.í§…Ï¤};w×72§ÿ~9ð+!dèð·6|½Iiuµ9 !6~ãÑ¡“‰¹M÷€Þiié#ß«˜MßÈÜÒÆ¡¶¬¾¾~rRâWëÖª]:w·®®. ²ûç½ë7|Ý`¶úúz}#sBHß çÿºð’÷ôÕnýué#€7l„Á&l0}Ö¶¶¶nÌÛãÌû(þØ¡òÇåGŽ£}»uÓÈOÞÇ …†BTsnÞúmìw?ìÝó“›«ëß/ÖKë÷íÝ-•Ê!vN‡~ýÅÏ×—Ãá¼vMÿÖ¨l²åääæäæâHE´¨€A§0ÿ°Ó8##3£¬ìqøô):ÚÚVV–Ó¦¾G/"‘õÇå2¢šsýú¯—-ùÄÛ«ƒ––°oŸÞíÛÓy!B@$ÒÒÒª-moÜþ=û´sõ{ç£F‡Ù;º^¿žHÉÍË6ÎÚÎÑÝ£ã¾ýrB~ùå ³›§£³û?þD¥¨®N_$Ù´e[÷€Þní½'½7MÓ>nÚ²ÍÓË×ÄÜFñºGzú½¾A­í\Ú¯ú,†’x㦧—ïÆo6ÇíûÅÓË×ÓË·º¦¦Q|ýzb'ÿnÖvŽó|lfiKŸ×T뙚šÖÀ`K¿®§N'PÙ¼;u¾zõõØÁÉ---âÖÞû½©á={õóñëºûç½Í? [O©m:öõTÝ:CÇÍ_¸¨oÐÀv®[¶nÇ™ž—.ê4}œá`o¯¯¯5wþÄ ã¼½½A󫘓››—Ÿß­[Ó QGWçÖ«NÎíÚù}rròÁ_ùùùŽ›0©Kg¿=»~ÌÈÈ âïç«)'!$5-íö?‰)©©!C{´k示º““#µ¹#GŽ?vÈÈÈ(##SS•fE„ÏŠïѳbâªÏbztëšpêxMmíä;„ßN>ÉI‰kÖ~™“›»á«/˜w“Çã}³ñ+Bȼ¹º8;Bd2Ù»ïM‹œ=kê”É«>‹©«+æWªçä)ÓxêÄÑS§&Nšr')ÑØØXÓ¶„Bá_çróòºt ì`oßFuëè#–MǾžª[g踚šÚ3§O¤¦¦öî?yÒDê ÀËÇ3tttŽ;ôyÌÚá£ÆÊd²¨ÈÙó>Š¢ÍŽœ3wÞBêñ©G®+嬗ÖB š\cǶmuuuÍÌÍ\œÛUTTܹ›’’’zëVRü‘_««KpP¿“¿Ÿ666RÍI•0mÊ{"‘–OGïîÝ»: ‘HTW˜ñäkÄŒéFFF„GǶª§–P+'77'7×ÎÖÖß߯±»ÉápÞ82xÐ@*%ùÎÝââ’)ïMâp8ïÏœ³fb~ÅzfçäÜN¾sòø.—2 ¨­ƒýŸçþ1|¨¦m=ŠbcmÝ¥³ß©ÓP›PÚ:úˆMÓôèξžª[gè¸Ño$„¸¹¹ µ´rssé8Ð4Mþ…æéàé±kç÷r¹üÏsçß7±KÿÞ½ !+–/<øÉÙÄÒ‚¡¥œyùù„Çååª_Ig‰ÇåQoÃ)õõõT™={÷§2TWU{z´766RÍIe077£””–¨]ÞœmÓê¹lÙ'K—­èÝ7X¤%Z¾l1uriŽ¢¢"SSêúž‘‘ŸÿŸ¾S¬gqq‰HK‹nassóââb†’ ©†††ÅÅ%Í?[O)5]£ê©ºu†ŽÓÑÖ¦ t+4™Úïë=‡˜A¿çíÓ»Wg¿´´t*fÛÚØ°YW)g;; óK—®PO.—[YY&^½¨tU]5'õ °°ˆ~àïç«vuÅ}WJár9R©´ÁŠÙX[Ç~»…rààoï5ú­‘TQ‡®I£˜™™—Èd2.—[VV¦tâP¬§‰‰qm]]EEuö),,455%„ðù|™LN5Eee%ÿQAõ   0 G÷ç~t¾Á}¤Ôtª§êÖ5uÀs÷B¾^^^þÙçk<|(‹ÿxð°ù•kÔTÕJª«ªB¡»›+!$þø‰¢¢âæ×‡}™ sb³Ÿ¿šeË¿ z4eœAÍö,•J'ÊVzÂøwŠŠŠB‡*-+swsݽë‡FÕ£s·žÕUÕyùùS§Gˆ´DK—, ;úÒ•+3gGVUUÛØX+MUýóÞ8'‘–ÖÄË:ÚÚjWŸ:erÀ¾V––®®.,!ª¦pua_枟wF͙߶;!¤£·×æo6<‰ÕÕß}ÿc'ŸŽÌ?À¾å_P=Ô\"bž ý•Ïöüæa95Zž/Ì…þZÂüÕðú¼¶dõçkTß8á¹\ÎR…ù«à­a¶çE ç·ÀZažmhpm ZYÌðôòM¼q³iëVTTxzù::»2\u)æW‡–¬¸¸¤“·šÚÚ¦­Îpx›YÚæ0Î’€—v‹òÖ˜·Ù|‚ÿ…ÇŒ©Ó#ôÌé¿+W§¥¥Sͬ»t <œÊ™™™5``¨™¥­oçîž;OYýù}#sj7ÊËËM-lG‡km­¯¯Ÿœ”øÕºµj—6szv%C‡¿µáëMJ‰¾»+6ò/~UÛò#ß«˜¢odniã iCªÝqÿ~†¾‘yUU•¡K·À½qû5•©Z%MýÎp2¢òÏšý¡¦*QÕPÜPmmÝ­¤ÛúFæÑ«>£[lÇw?¼‘§{Õi¾µ~ã„qok‹DM«Ã›ýó/ó¥ýÊE~ð~ôÊÏZÄ8#|úÔÂülê¾ôÿ(7+;ëÞøqaïNžJ}elÊôoo¯‡YÿΊÿ¿É„û£Gã !ñÇOZ[[½–Ñ{Ô67Ãsrr32³š¼•o·n¢yÔÈáj[~ßÞÝÔc--á‰øÃ…ùÙ2Ò5¨¶;T1”©¶Jªý®ÉÙ„ß ó³§¼7‰¹Jû£0?›Çã>_˜ŸMÍÉçó÷îÝÏüÍ7€ÚiÔ¾‹Åâ]?ï;vô‹>¼ßH/ç¥ýÒö ÈÊz’’úêcˉ´¨?>ÿÉ=s.—§««;mê{uuâÌÌÌœÜÜ+W®Î©-÷¶¾žÞ©Ó „^=©qÆ¡ÃG‡¬iì§éÖ”“òË/Ý<Ýøñ'*EÓ,ߪ3±«ÕÌéÙE ÐÌårÕ¶<‡"D"---¡ú£\Cw0lWµLµURêw†= …"‘Çc®•" è †…B¡—W‡'o°ÝÔÎϾßU{“’ššÖÀ`K¿®t»±ŸBŸå$ö Ârß !×oèééµ±³kìÖ5Þ—/_íäßͦÓ⥟>—óKëyi¿òC‘Ãát?yê…Ï1ÚôÏÚÖÕ‰Þgjjâææz=ñ†¶Hdmõd$áèØ6=ýuÞñðhÿ÷ÅK<œþᅳJc9M·¦œ„Ô´´Ûÿ$¦¤¦† ìЮÃ,ßJ3±«ÕÌéÙ_¡ôô{ªÝáÓÑû¹®ØïͬCþÉ“&nÝ1dð KV1ž}¿«ö&!dò”郛IìÕ6ˆ³s»FíûíÛÉn*WWXN¡¯zxK¥ÒIïM›÷QÔäI?^MÏéÙL­ç¥ýÊÅöîî·n%½úqÆw?üèàäFýee= Í­ìÌ,m¿X·þÔ‰cúúú555¢§S«B´µE•O¯› 2gîüþO§KÒ„ý4ݪ9©¦MyO$Òòéèݽ{·S§˜gùf3»ZÍœž]“Ù‘sèFNKKghy6º£™URê÷W¥à ~)©iô/w1Pš1¾Qý®Ú›Ù99·“ïDΞÅårCµu°ÿóÜ_Ôºì§Ðg3‰=Cƒ°ß÷²²Çª½À~ }å”|§´¬lÒ»ÿãp8¼ÁóëM[ècƒú›2m^Ú¯üP40Ð/++{õ㌱cF/úøÉm +KËÿ½O)ÌÏ®©©~wò´ßýðùg+µµµkFm55µzººR™”ÜÚôˆ¡Cݹ›ÂxŒÕ4ÝÆÆFª9ŸœÎÌÍè%¥%̳|³™‰]­fNϮɊåKH=¶´°PÛòìKSÛª 3O-¬T¥û÷3TûMe¨­¨­Ó{.w¸w~ܹ«Áò•fŒoT¿«öfqq‰HK‹>›››?¹mÃ~ }6“Ø34û}×××W ½ì§ÐWRRRbffJ]‡411¡/D«¾û?úדÝÑ|¾õ¼´_ù¡XYYÙ¨wr/*fèéꪽSdllüÅšÏzõý0r¶³s»šÚÚüüGVV–„ŒŒÌ©S&§¤¦R»WÿÂ3”hš¦{oÜ~ÕœÔj\꿟/ó,ߪ§K.—ÃæÆcs¦gg`ll¬ÚÈšZ¾Aj»C[[›ºþðäB„Lª££ÝØ*)õ;U>óðBGGGS•˜×8q|ð€!ôwi¢4c|£ú]µ7MLŒkëê***¨×^aa¡©©icÛŸÍ$öÌ ÂrßÝÜ\¾ÝÑ„­«ejjZ^þä³UUU qqÇ÷?®Yû¥Ò¸ð‡ï¶·ò—ö+?ÓÒîµwwõצ¸ººôî¸eÛv;[[¿/×o¨­­ûyo\yEEPÿ¾Í¬Yc§éÞ¾ã»ÚÚºn%]¼ty@pPcWwtt¼›’Úàû²æLÏþäd]__[[GýI$*Q"‘ЉÍÿ¼Úî°´´°³µÝõó^±X|þ¯ <èèÍt‡ƒ¡Jt¿«o¢ßOïÞ³·²²27/ïÏ?Ï{uðÔT%BˆX,®­­#„HÄêâ«ÈÓÓãêµkÌ;«4c|£ú]µ7ÛØÙy´wßðõ&¹\~ò÷Ó÷32œ~¸Á*±ï£Æî{×.óòòéS*ÃÖÙL¡ïéÑ^_OïìŸç!Ô¾5™2ùÝ+—Î+þ}ùEL«zi«mÏW~(þ}éRóO¼/6fBfÍœ±ã»*++w|»%ñÆM[{§µ_|µkçwͯܞŸw^½v½m;w'·˜5ëxÏåêÚ¡£ïˆQcÖÆ¬¦.6ju¯žo‡öèàciãP^^NéÜ­§§—oJjÚÔéž^¾qû~!„\ºr¥[@/+Û¶K—G+MÏ.“Ëœ\<ÜÚ{3njÙ§+ͭ쨿ÉS¦S‰ÓgÌ¢¿Ù¼µùM§Úgç±»výliã0=|Ö†õë~`¼Á*Ñý®º¢µµÕwßïlÛÎÍ׿{ÝÞ«©J„ž½ú›[ÙI¥Ò ÁæVvJacÊäw•RÔ¼qqqéÐÑwÔè0zÆxöý®¶7¿ÿîÛ?ÎZÙ8Ì[ðñß}kjjÒè÷R*UbÙGÝwÑoT:¿«Ýºêúª‡7Çûá»o?^´¤Oÿsçÿ …¯=èéÙÚØ(þ™˜˜´ª—¶ÚŸ$xµ‡âõ뉺::Ô§^¨æBhÉZàŒñ/¹J>|kÌ;ÿ:KÝ~Àú­öPœôÞ´Q#‡Êt£áyÌ…Žym^cömÚùí@ó·^w+£—ÛÙÚ¾„ !f¼Þü$´/'` fÀë­Îÿj«„)ôÑò/æBÄ @ÌÄ @ÌÄ @Ì@ÌÄ @ÌÄ @ÌÄ Ä @ÌÄ x•ð›K­N\\b°²dÉÄ `%--­ÁˆÚ¸‚û€q0ºråŠjb—.]VÁ8 •R Ì1aƒmÀ@Ì@ØèÂ2` fÛ€˜ ñsSz†fh€7ÒsþxȠпñ¯)|X™?>Ëœúúúˆ={o°>}úBÖ¬YÃ2tt4b“éÓ§£à”——WQQA=Æ|SðÂ5+fÄÅÅ5ùæ;´j'žzÎ1#..îR÷R·Á°±mÛ¶©S§ÒO  ôôرc„ÐÐÐÇÓéÇŽ8p õ844ÔMAxx¸Rº¯¯oDDDAAs~P0X†&Æ *`P ;w¾qã†T*%„J$’»wïÒO³²²:wîÜàW¯^}ë©M›6Ñé111wîÜ9|øpqqqLLLƒù@íƒMØhJÌP l†——W}}}JJ !äÚµkݺustt¤ŸÚÛÛ[XX4¸Q@ õŸÿìÖ=—ËåñxvvvÁÁÁ æMA¢Á°Ñè“©jÀ Ã‰‹ S{º÷öö¾zõª§§çÕ«Wýýý-,,è§l ÊÏÏ?}ú´–ØO3ÕÄq†«««Ú€A‡ WWWM5»zõ*5°ð÷÷÷÷÷§Ÿ*ÆŒeË–u}Jéc^Š‹6oÞL§Ï›7ÏÍÍ­wïÞB¡pÁ‚ æ€&kÜ8#--íØæO†Ì\¥vé±ÍŸhúÌoçÎwïÞýøñã’’SSÓ <~ü8==]1fÌ™3'88˜zœ°sçNµ‹ôôôèôµk×:4))iúôéW®\ dÎÏy¾©&„ †€Añññ©¨¨Ø·oŸ¯¯/u·°°Ø·oŸ………ÍÐÐÐÊÊŠ~¬X‚â"%ÇÛÛ{æÌ™111={öäp8ÌùZ¹—:ß”jØ`„‘Häååõý÷ßϘ1ƒJñóóûþûï{ôèÁr£‰¤®®ŽzÌårR†1cÆ|óÍ7gΜéׯ›ü­ÖËþ86X Jç΋‹‹ýüü¨§þþþÅÅÅþþþ,·¸hÑ"ï§F­šA[[{üøñÛ·og™^Æ8C)l°œ5}îܹsçÎ¥Ÿ4hРAŠŽ=ªøtÈ!C† Q»HÓ*‘‘‘‘‘‘ ùàÕÄ öÑÂÍÍ­Á<©©©è €79f°„xðfÀ¼¶€˜ˆ€˜ˆ€˜ˆˆ€˜ˆ€˜ˆ€˜€˜ˆ€˜ˆ€˜ˆ€˜€˜ˆ€˜ˆ€˜ˆOñÑ­M\\b°²dÉÄ `%--­ÁˆÚ¸‚û€q0ºråŠjb—.]VÁ8 •R Ì1aƒmÀ@Ì@ØèÂ2` fÛ€˜€ÏMýG^^ÞxøøsЧOŸŠŠ ´¼ñð=p`eþüù,sêëë#fhtöìY4¼ÁúôéCY³f ËüÑÑшL¦OŸŽF€7R^^}ùóMÀ Ç*fÄÅÅ5ù&;´pj'žjbÌ F(K–,a6Ž=:|øp//¯=z,Y²äñãÇTzhh¨››››››¯¯oDDDAAbº»»»ŸŸßرcwîÜ)•J•V¡„‡‡3¥ˆÎãéé9vìØäädBHxx¸âå¹+W®téÒ¥°°G ´ò€Á2lpÙ ê1›°±wïÞèèèÈÈÈëׯïß¿¿´´tÒ¤Ib±˜ZsçÎÇÇÄÄÐkÅÄÄ$'''$$̘1c×®]sæÌ¡­^½úÖS›6mR\EmQЍ</^ôðð Ê\±bÅáǯ_¿N©­­ýä“O.\hnnŽƒ0Â`6¸,›°QWW·nݺ¥K—öë×O(ÚÚÚ~ùå—eee¿þúë“q¹<ÏÎÎ.888##ãY%¸\ghhد_¿mÛ¶ýþûïIIIÔ"@ õŸÏWZEµ¨ÿì—Ëãñ FõðáCBˆ¥¥åÂ… -ZTWW·aÇQ£Fá  –aƒÏ>`Ða#:::,,LuÑíÛ·«ªª‚‚‚è¡PØ¿ÿ .(æÏÏÏ?}ú´Úí:::º¸¸\»vÍËË«Áf.ŠR]]}äÈ???êé[o½uõêÕÇã €V‹ý4S Œ3\]]¾%¸dÉWWWÕôÒÒR]]]---ÅD“ÒÒRêñ¼yóÜÜÜz÷î- ,X ©|“òòrêñ²e˺>µyóf:›¢¨<:u:|øðG}D§GGGŸ;wnöìÙÖÖÖ8hØS?ÎHKKS3¨&WUUÕÕÕ)†’’cccêñÚµk‡š””4}úô+W®ª-¼¤¤ÄÐÐz`À€:P‹$IÝS‰Du£JE)¢Š­««;yòdqq±ƒƒêLÎLuÖÆjG ~Ý|ܸqúúúëׯÿàƒôôôú÷ï¿sçN¥;„mmíñãÇoß¾½_¿~„ ,X°@WW×ÉÉiüøñãǧs.Z´hÑ¢EÔcww÷C‡1¥ˆ*–ËåÚÙÙ-[¶ÌÛÛ †­'€iIDAT@s4<ß6 ”¡C‡ª½tôèQŧ‘‘‘‘‘‘ªé «4X›u)—/_Fß<ÿ˜Á>Z¸¹¹iZ”ššŠ¶h1ƒ%€7æµÄ @ÌÄ @ÌÄ @Ì@ÌÄ @ÌÄ @ÌÄ Ä @ÌÄ @ÌÄ @ÌÄ Ä @ÌÄ @ÌÄ @ÌxŠ&hmâââ3€•%K– f+iii DÔÆÜÏŒ3€Ñ•+WT»té° Æ­”jx`ˆlbÂF–1Ø Ä h|nê?òòòÐðÆÃ÷ÀŸƒ>}úTTT à‡ï@æNÚà—Àà~ fb fb fbb fÀËïWW×Ö¹ãýN8b@SΞo€¸¸¸°°°F­‚kS€˜ˆ€˜ˆ€˜­ >k ð…††¦§§Btuu»víúé§ŸZXX`œêÅÄÄܹsçðáÃÅÅÅ111¯õ¾ f¼àó,—Ëãñìì삃ƒ322¨D¢¢"êñªU«Ö¯_O=ؼyó˜1c‚‚‚¢¢¢êëë3ZüüüÓ§Oûøø4˜óþýû{÷î=yòd^^Þ¡C‡3Z‘yóæ¹¹¹õîÝ[(.X° ÁüãÇçñx<¯G)))ˆ­ÈÚµkSRRöïߟžž~åÊ•óëééQø|¾X,FÌh]8Ž··÷Ì™3cbbär9!D H$jieeåë²#ˆ/ɘ1c Μ9Cqww¿xñ"!$777!!áuÙ|?à%ÑÖÖ?~üöíÛûõë·hÑ¢… îÚµËÈȨ{÷îˆ@Ž=ªø422222’Ò±cÇãÇ«æ¿páýxæÌ™ˆ­…›››ÚôÔÔÔ×t3^”×76h‚{à€˜ˆ€˜ˆð¦Àç¦!$..€˜а´´´°°0´Cƒpm 31313113àåPó=p|XÅ |(®®®%ÙŠ)¸6êFtt´R"b° ˆ 1`¨Þª@ÌV14 WWWÄ `0pš03@cÀÀ=phbÀ@Ì@ÀÐ0pØ Ü€& Ä  Ü€ç0!œÀ]Ï]¸¤4Û-€¢G9™áï„q°…˜lñéAÚ˜q{tE+ÿ\0GqIEND®B`‚pfm-2.0.8/doc/index.html0000644000175000017500000000135611472150411013220 0ustar wimwim Postgres Forms Help File

Postgres Forms help file

Choose language

pfm-2.0.8/doc/help-styles.css0000644000175000017500000000323411000421632014174 0ustar wimwimbody { font-family: sans-serif; font-size: 11pt; background-color: white; color: black; margin-left: 2em; margin-right: 2em; } body.contents { padding-left: 0; margin-left: 0; margin-right: 0; width: 30em; } div.header { border-bottom: 3px solid #36648B; height: 100px; } div.logo { margin-right: 20px; float: left; } div.logotext { font-size: 140%; font-weight: bold; color: #36648B; /* SteelBlue4 */ } h1 { font-family: sans-serif; margin-top: 1em; font-weight: bold; font-size: 140%; color: #36648B; /* SteelBlue4 */ } h2 { font-family: sans-serif; margin-top: 1em; font-size: 120%; font-weight: bold; color: #36648B; /* SteelBlue4 */ } h3 { font-family: sans-serif; margin-top: 1em; font-size: 100%; font-weight: bold; color: #36648B; /* SteelBlue4 */ } h1.toc { font-family: sans-serif; padding-top: 0em; border-top-style: none; margin-top: 1em; text-align:left; margin-bottom: 0em; margin-left: 0%; font-weight: bold; font-size: 100%; } h2.toc { font-family: sans-serif; margin-top: 0em; margin-bottom: 0em; font-size: 100%; margin-left: 4%; font-weight: bold; } h1.toc a:link, h2.toc a:link { color: #36648B; /* SteelBlue4 */ } h1.toc a:visited h2.toc a:visited { color: #61437D; /* dark purple */ } pre { font-family: monospace; font-size: 10pt; } div.title { text-align: center; font-size: 140%; font-family: sans-serif; font-weight: bold; color: #36648B; /* SteelBlue4 */ } div.note { padding-right: 1em; padding-left: 1em; border-style: solid; border-width: thin; margin-top: 1em; margin-bottom: 1em; color: black; background-color: #f4f3d1; } p.indent { margin-left: 4%; } li { margin-top: 1em; } pfm-2.0.8/doc/options.png0000644000175000017500000005214310777375313013445 0ustar wimwim‰PNG  IHDRýfÒ – pHYsÄÄ•+tIMEØ »0pé IDATxÚì{\ŒÙÀÏܺ×t/é†$¥›j¢²*Q•TËÚè¢~¨•Ôº´T´R!„XkQ±ZIˆ­•V©Pt3º0Ýg¦™ß/ïÎÖÌ4R)Î÷Ó‡wÎ{Î{žsÎ{žç=ç¼ïy0࿨LÕ|¤¶ªŒõ'¦ŸÁø~ÿmXGAùcƒ9«ñÀ 61Õõ­°Ž ‚¢6Q1ˆåÀ 6ãÙ«·°v ä«§öf1U¬|ÐãÆÊ¢×鑵Uex4¨Á€µ @ _7uYǬ=·ñùƉ]ÖžÛÊkÞ°b¡FÉóFX›òuÓx+QÃ~=ïñß·w5ÞJ´ö܆؈–—O^§GâÙ5(÷~Ãñ zE 'o´ ƒÅÂÆ€@ ñB_¯sKˆièëc°Ú<Û«1ڽ⻖F)E8>Ø eŽºøš¹¥E Ʊ¯è‚q%0äÁó’búLVg{'kckk ‹Åb1,ûñð¿ÿ¯Ýñ–ÉÀÀEÈãc1QB˜à´vså³§€ÉTX²s„2’åûÞt‚¾ªQOé¦ÖPþ¸×H¦PyI{ý'=À‚}EýÂëR·ŽœÀÈPF•vŠ—`ù± ôT¿Ä4ô1Œ£ &“ÉzQ Àd2û¥…@† a ²¢\dš%–_¸ãÅCáI†#a3xLc2˜?Çž.¼[{ªÒÞ°ÍË'­û­¸•ÆëºáÒö ‹AŽm¢ ÓvFH`dXF)^Î å€ >šý" ê½ñ=ÏÞl&À`±ˆiÀb>X€ù>žÂ`0Ðl@>ŸA€½W_ºÌ–Ÿ(.ÐÚI»Xðæ¯Â7¬gOr¯æGæþ:—Yëj,/-Â÷ºµç·ÜzmE‘ùZRb‚øÚæîƒ7_•7t|ª+I$„?ŸsGp’á3ºhü…Û;¼\§ÑöߣàEe¸ÈÙOHë½7]1óC^vd.Ÿ=a‘ŽŒ´¡¹ƒvõŸ·É‘®ƒ¤¾V³ÂX^ŽÈßÐÖ{ðfí?¯Û3•D=ç*N–PZ×q± ©à%Þ0ám,=\šºN›Õ` ÿš&±"¬z‹žc`Xÿ ðÑ ƒþ !!€Ü„³„ý¢S­Ü×—úÎSZ Ô‰œµúõaæ»™sìÈ×¢Ÿ{Ê¿ßy6 lß$ÁKÕË~Ú°í×)²B–Ԗן*†*p7ï>ÿ„i8! ~9õ'íÒSÙ”Ò´^.rö²érZ.Ê“+‚*úËäœ31ûA i‰g浫«çLtœÒÃÚ§0ª~ˆH? ,%°Á\¢·ù%“É ±›<]AxÛ¾ãs­8°o÷•Þ^r5¼a ŸÙ×,,‰ÂjQ–Ä?a=Ë:Ú@“s| XìÃkýõÀ` Ñ† #ûh,¹E—›•p³àAœ„¢¬¤¢Ônzû¬ˆ4k4Öû ƒçÃ'üû“Àû÷'“Iïíws~·çÁÛŒ]èÏžWEüJÿö™G/ÞÙÌ15šu§Ž¶º÷°‹ãgöѹËÉIH„ÖNšŒ(Ÿ”„D/N@Z^ðæm ƒÖÓ×ñ–E~ÿÈß×{ñQcòÙ?ùÚfÏš±Á{U¨Ýd‡¢r>™É°ÓA>o´ñÁl\ûI9°ÝWˆþ̘…üü×lüwmƒƒÙ@Æèª–ÕX öÀ%qÈðò“ŸgÌ¥ø„½-•œOÃò 2ûhýn°þ?1¸ÿ\…õ'“ú&¹1Y&yX#üv絡’@àº[âSk0ôéŠâ^–Êï:zŽœ<‡•8– ˆ“ž:Ë/‚•æ.gÊý×'Oœè­/AR ¶°JB£ÓN>xÿG$µµ–IëÅò ñÉM瓞Œ’à"ÿ½Ê·9u¿~Ìèlc€TÑÃ`1°ÓA†q´ÁÕÂôäM*–[5ÿ1,–Ǧ˜L€sTá H(òÉMcÒ»ƒ°8¬ˆ4FP¹9ÑÉ¥!üü$ðR“„Å™Ýï™ :`2‹ƊȲNq’S`Òl~™)LZLZC)^~:«$¢à4 þÎ@ïL&Àâ°BXa &£‹ü¹ixQFo'`Òƒ!ðcEå†P.dàhcP0̃õ–co6 /V|âžK>Þ™X1y &†|ÒÏO#$’èÿ„ôßK±•CÄH(±ö“Ã'ŒãþoBÆ òãQ9œ(7a ˜áP‘~€ošñ©ÏnN²öml¨g;IïBÈcï²÷ý[q¼È pAÅÊç“Ü45Ô¿ê?"‡}2 Zø‹[q¼È p¢±®†Õgß ‘~|“ª º‡@ oÃrT~–ÂÇÌ1Ý%©ë@ ƒ"ÔZ†¤Ÿh%×!AÍïºêêêcYî]»v-_¾ý9Æ¥6oÞ¼fÍxËB ÑGZüÃ>oêk¼ý7±ùÜO]]½²²r,—aùòå)))ˆåPWW¿{÷îWßl‰‰‰Ðr@ /vÜÙ VËñØ VËoY2¶Ì@ Ðl@ š @³ÁKKËÒÒRØZòÅÁóïæÍ›'Nœ¨¨¨`0jjj‹/öððÀápc°H+W®|ùò%@XXX__Ó¦M<¦Ý²e‹¯¯¯ŠŠÊç‹QRR_UU…Ãá&Mšäëë;kÖ@ÿý÷Ù³gkkkEDDÌÌÌ|||ÄÄÄÉ=<<¬­­á} @Æ÷h#99944ÔÕÕ5'''???44´°°°­­mxEéë¶Ý~þùç[·n%&&666>|˜÷„•••ÝÝÝŸ/sOOOPP……EzzúåË—×®]K |p«––çéé™™™yìØ±÷ïßoܸ‘F£Á{|%£žžž}ûöíØ±ÃÎÎ 100000@Ž›ššÂÂÂ=z$ àáááëë ‘Hîîî999mmm3fÌØ»w/çÙÅÅ%++«¯¯/###&&æÒ¥K EAA!44ÔÌÌl(Æ‹ÅápŠŠŠß}÷]VVàÅ‹ÑÑÑUUUâââ«W¯^¸p!àÈ‘#t:H$îØ±ãÚµk---[¶l!žžž ,¨¨¨Ø½{7™Lž1c†””‘Hôññ,^¼ØÁÁ!77·¯¯ï?þxûömttô?ÿüÃÏÏ¿lÙ²•+WÖ××wvv:99!ÖÂÐÐŒJ¥=zô§Ÿ~BÊ%//ææævíÚ5x;B ¯Ál”••uvvZY±Ùh—Á`x{{Ï™3çàÁƒmmm+W®œ:îââÒÛÛÛÔÔD¥R-Z„ĤÑhÈs:@DDäCx<•JEf¨8E–””D³KNNNJJjkkÃápd2¹««k¥òòò²°°@žñeee±ýÊÉÉ={öLMMíÇ<|øp]]ÝìÙ³úé§~+çmmmÒÒÒèOYYÙ~Õ‚¼}û–J¥~ÿý÷h¹ÔÔÔ***¡¡¡€7oÞìÙ³'222>>žH$vuu¡•‰f„,‰C È×`6´µµ…„„²²²lmmQu‰L(ÉÈÈHHH\»v (p‡—ÈUUUûöíKII™½aÆ~ƒ€ææfô'™Lž8qâÀ¼$%%ÅÅÅÏœ9é\rrrK–,ùõ×_Ó¦MÊÍÍýî»ïP3“——çææïEò•LR nß¾ýòåËïß¿ïéé)))iooÇ`0ºººD"ñÀ===€šš._Wð¹³³S@@ÑÎyyyµµµÃRH---sçÎ1ŒgϞݼyó»ï¾«©©yúô)ƒÁ@%ˆ}mllDSa0˜ÔÔT@iiéƒ8]\TTôäÉ“½½½€×¯_#ÓV§OŸ~ýú5N¯««»|ù22´âçç÷òòŠÍËË£ÑhÈ;‚‚‚È=€N§S?B§Óá @Æßhàææ&%%uòäÉmÛ¶1™L55µuëÖ™››c±ØÄÄÄððp333ƒ¡ªªÀÑ@ñYGGÇÊÊÊÎÎNNNNYY™í†BâñQQQÑÑÑ¿ÿþ»˜˜˜¿¿ÿÌ™3ËËË£¢¢ššš0̬Y³~øá¤¤±±±QQQ~~~ööö‘‘‘ÇŸ1c†¥¥%:ÍÕ¯\ÑÑÑû÷ï_¼x1“ÉTRRZ»ví„ jjj6lØÐÒÒ",,l``°~ýz$¾£££°°ðñãÇ·nÝJ§Óõôô<ˆ.íÞ½{÷îÝȱ­­-2Í@ cÌÓÙwòòQ’²Šãb\@JJʶmÛFmÜ­[·Îš5‹õeªÏçüùóIII§N"‰¼Ä¿|ù2Ü;Œ2ƒûÛèÇ´iÓ8zöìÙX+ë»­(¹¹¹C»ZYY™¢¢"‘H,**zðàòÑÆ0âìì\TTtêÔ©~Ë*2fÜlŒAÛÀ…![¶<þ<$$„F£‰Äàà`EEÅaëa0{öìw!ùªÌÆ·Œƒƒü @X§C š Œ ã~’jh{ÂÂB  ¯ÚŽ{³±|ùòñò®0ŒSRRRü¼W³†ÀI*|Ðl@ š @³@ h6 Í@ Ù€@ š @³@ h6 Í@ Ù€@ 4@³@ h6 Í@ Ù€@ 4f@ h6 Í@F<¬ä›E]]VšaãÖ­[° _1€ÊÊÊñ%vJJʶm۠أ&64Ÿ†——¬ÈWIccc{{;¬È€kf@ £l6H$RUU¬ @iiéôéÓ‰Db||¼ªªjAAÁÂj„@x$***::ztòªªª²µµÕ××?sæÌ'%´´´,--kU7:RÁÑÆà:tháÂ…ïß¿÷÷÷ß¹s§ŠŠÊ-ë 6hkk›˜˜444 !çÏŸ‡+.‘ÆÎÎn ÞÞÞãKr }}}—Ó§O÷õõq‰öìYssóÂÂB¡åèëëûâÅ‹‘+ÎåË—ÑŸ ,»£AáÞÃȨeĉ7oÞLŸ>9^¹r¥ŒŒ ëY:ÎKÂ!PPPŸŸ_RRâââ²wï^@{{{ddäž={ ^ƒŒ4‘‘‘O>rèСñ5X)++ËÎÎöññ9sæL`` —ÈÍÍÍS¦LùœìÊÊʺºº¾©{ƒ›Ù¸ÿ¾¾¾þæÍ›{{{‘@‰´ÿ~dˆ·bÅ ]]]KKË‹/.^¼èããƒD¶¶¶^¿~=rlnnþôéSÀÞ½{ æÏŸÿÏ?ÿššš|||ôõõI$Ò‘#GØfô¥XµjÕõë×·lÙ¢ªªZYY‰NRÉËËoß¾]GGGGGPWWgooO$ååå#""&,--533QUUýí·ßÏŸ?—””,**444ÈÈÈô{å·¦¦FKK ƒÁÌœ9³¦¦¶~ýzIII4Npp°ŒŒŒ¸¸ø”)S}ôè‘™™™¬¬ì‹/>|h``€Åbzzzž>}J¥RååååääJJJêêꂬ¬¬»»ûõë×ûe46{Ñúõë ƒyôèÑË—/#""øøøüüüþüóOÖ˜ùùù!!!X,ÖÐÐÐÝÝ=)) °víZ55µÙ³g766"c”~fcÕªU¦¦¦Ož<Ù´iÓúõë8`bbâèèØÖÖ&((ØÕÕU\\ÜÛÛ«¨¨8qâD¨Ú ÃÅŽ;fäðáÃH ‹‹‹ŠŠŠ³³3™L@#»¹¹áp8,ëááê "‘ˆÁ`–,Y"!!~éæîîŽÃáp8œ©©iEE¹‡W­Z…Á`ŒŒŒH$Ò0DRR’B¡pÑ3(œ; ±mÛ6ô™uÐâ°m”aÛç~òòòè:LCš9hii‘——Çb?ØdßÐÐðáǵµµFFFbbb>,..644hhhüïÿûõ×_kkkçλcÇŽ¦¦&*•ºhÑ"ä"4MCC£_Fct‘£®®®··WKK ùI¥R‘™+”7oÞ(**¢¥¢¢RXXˆ¯]»ÖÁÁáØ±cüüü³ DffO:¥££C$ãââþùçŸDFFîÝ»7,,,((¨ººzÁ‚‡–••…ú2,ZYY!Ç"""h¸³³³¯¯ï®]»øøøÐ@iiiä@JJ ÕÉÉÉIIImmm8ŽL&£ èÕðx<•JE4‰‚‚ú€8¼ãæÖÖV"‘ÈEÏ pxì4DvvöéÓ§‘ªA‹Ã¶QFÜl¼y󥩩 •€))©¦¦&ƒ(Ɔ$š‘‘QNNÎëׯ}||DEE‘qú¢‚›››››Û»wïBBBlmm%$$®]»6fGƒ"///--ýôéSNE““«««C+êÕ«Wrrr€ŽŽŽ7zzz†……-[¶Œ“™lkk‹‹‹»sçν{÷tttH$Rxx8`ݺuëÖ­kiiY½zuDDÄþýû¡¾ƒ ÈB]¿À®®®ÈÈH''§ƒ"ÏæHx}}ý´iÓD TUUíÛ·/%%eòäÉkkk&“É)/))©––VE?0ë¡Q[[[UUe``@¥R¹ëN†Äéèèî—p¤kC .##3¨ÚØ(£1IuâĉöööŽŽŽ£GÚÚÚŒ ««+((˜˜˜È`0ÊÊÊÒÓÓëgdd”ŸŸßÛÛ+//o``pçÎwïÞ!ïUWW—””0 aaaAAA&“©««K$8ÐÓÓ¨©©ƒoCsÇÄÄDRRrÇŽÈdheee¿o;Œ…„„~ýõWƒQXXxöìYWWWÀ†  -Z„¾G0ÐÐÐàà`111•ŠŠ ƒQZZª¢¢R^^þèÑ#ƒ!***,,Ì¥[B Ÿ Fëýª7ÃÃõµµ#"",,,¶oßΪ+(J{{;ò èìì@&Nóòòjkk¹ä¥««Ëd2o߾踜œœÏ‘œÁ`ôõõQ(”[·nyyyY[[kkkªg8 ¬¡¡qÿþ}ä±8;;{`vbbbuuu£Ü:¼¨Í2fcþüùÎÎÎsçΕ——÷óóc3TÁã²³³õôôüüü¶lÙ¢¯¯PUU200@¤JJJzzz8i›ÐÐP“îîîuëÖa±ØÄÄÄêêj333==½ÀÀÀ¶¶¶ñÕÁ°Xìµk×ÊÊÊ&L˜ &&æææÖoHH ÒÓÓ/_¾,&&æèè¸oß>33³´´´ëׯ#¯@ÄÄÄ={vàÅ ž?¾bÅ ÀôéÓmlltuuOž<ÚÞÞîéé)...++ÛÕÕ…Î{B ŸOhhèÌ899²²²rssùå@HHHyyyzz:ÙÆÆÆÙÙÙÜÜ\QQÑ:::VVVvvvjjjÜ&=ðøøøøØØXWW×ÐÐP33³Ï‘<88XSSÓÂÂ">>ÞÝÝ=&&é¤Üõ 'CCC?¾téÒ­[·š˜˜ ÌnÍš5;wî4008þühêœAÕæÀF.0sLgßÉËo%°–’²ŠãnÆyTõù*A¶2TWWÿüþN"‘N:5uêÔÑ‘î€;š‚ì€ëçýáÎ7õ5Þþ›à¸„'‰ò~<{ölŒK8åïÅfðĨ׼ãÑ6ŒÇâ@³@>‹¼¼.ñÏž=knn^XXˆ:ú‚Õζ8—/_Ffdd,X°à‹W2Ç%ñ²²²‘vn5Ðõn__²¿úXãêÕë XþùçIGg'Ô€O¥»»ÛËËËÇÇçäÉ“X,öñãÇax³Þ>igg‡Í^Ée°booßÑÑQXX¸gÏžÂÂB.îËš››ÍÍÍÇNµ Ø6š››}||,--/]º ‘HçÎCœ=ìÙ³§±±ÑÃÃCGGç‡~èü¨FI$Rll¬‹‹‹­­íæÍ›{{{Ñjµ··744ôöö¦P(ÇS$iÿþýc³š®^»¾ÐvAÀO›ßÉËWxhÍÐ;—œ˜¢®y<ñä,c9•П·×Õ×/Xè +¯d¿xYGG’vŠºæ/»",ç/Ð72õòñëéé…:ôäÕ«Wííí«V­"‘H³fÍBN555ùøøèëë“H$Äÿ +lÏ644øøøèéé³í³Ÿ @àÿGŠ`hhXVV “ÉÆÆÆÈ€‰Slj‰™;w®®®îÂ… ïÞ½‹j‰Ã‡;;;ÏŸ?? €N§s×CQjX,‡#‰óæÍ;zôè7JJJØÖdpppnnntt´¥¥eMM [555Q÷9qqqÜUå¨Áý¶áÔ(#k6¤¥¥rrrÐ1AVVÖ… ÒÓÓSSSÂÃÃïß¿O¥R“’’ЄÕÕÕIII]]]‡ÐétÿeË–=|øÐÓÓóÊöCTVV¦¦¦r:ûeéìì,,*6Ÿ;'v߯r²²)IgÊJŠÜ\—6^½~++óÁ½;gÎ%ÿðãÚø±/ŸWôöö&žø ½BEEåß™òïvttîù5êÐoeee ‰M›6åää°:@e0ÞÞÞjjjùùùýõWZZZff&÷³}}}k×®UQQ¹wïÞýû÷O‘lûì°!(((((¨»»;$$ÄÑÑÑÈȈSÇhii!¡½¼¼6n܈ø¾¼xñ"99933³±±1--w-1&Mš4uêÔ‚‚¶5E"‘FQUUå$0F¡ÚÙÂý¶áÒ(#h6Øâéé)&&¦¤¤d```llŒ¸ð³±±©¨¨@㸹¹áp8,ëááqõêU@qqqooïªU«0Œ‘‘‰Db{ñ•+Wâñø±éNüï¬3’)??Û³Öû‰DUUSã¹sÌÔÔ¦ /^l_RZ†ÆY»f5R-Þ^žSÿ‚:ôDPPðܹsBBB;wî455õððxýú5 ¤¤¤®®. €@ ÈÊʺ»»_¿~MÅöì“'OÞ¾}»yóf<ŽZ†—;vÌþÈáÇ‘@ggg2™À¥ãgã fÉ’%è×-îîî8‡Ã™šš"ÚƒG-14$%%) ÷zæ.ð—…µ!ÐÌy)ÛF>ás?䀟ŸuhÎÇÇG¥RÑ8h¸””2²kiiQPP@í¢¢"§¦³þêÕë Úp:+''‹ðËÉ~<æçgÊ~ —••!“ßBúm2yòäÝ»w#SL?ÿüó–-[Ξ=ÛÔÔD¥R-Z„Ä¡Ñh¬Ï’Éd…‘^o ´²²BŽEDDÐpggg__ß]»vñññqéø€ää䤤¤¶¶6G&“Ñôjx<Ñ~ü¸±±qàû#Ã;X'‰»jWWWdd¤““ÓÁƒ‘gsN¿ªªjß¾})))“'OX[[3™LNyœ–¨­­­ªª200 R©\ꙋÀF£!q:::„……‡EU­!Ð ç~Ûpj”Ÿ¤«««ûÔË8q‚B¡´··'$$ØÚÚtuu™LæíÛ·ésrrÆWððєɓ¥¤> †Äʼnµµ¯>õ"ûzÿþ=…BÙ»Ôq Ô›ß uuuGŽ©©©¡Óéµµµ)))3fÌ@:‘H>ÞÝÝ=&&fÐzæ"phhèñãÇ—.]ºuëV“áR•Ÿ«¸+ÛF.0sLgßÉËo%°–’²ŠC^"‘H§Nš:uê×ÑÏ3¯]YýÃÊYÆç“ÎLª6äëLQ×LO»¨9}:T_§= JWVVª««³{²ŽrLJ;àŽf£ %~Þ«‘Ÿoêk¼ý7ÁpáqA>¬ÈçƒÌ2äÙ³gãºcJ~N•<¾êyìš d4jkÜá+¨äqQœá4yyyP; äye9¬ÈW ìøßZ£ÀÓ!ò…F_"DiX ÍOØØÚAgŸÞaëuŠ Åþ ÄîÇp¾€ @Æêêê° ÜÙµk|—Wnݺ+òcaa€@@±?uxÍ7¼¼¼`%@¾JÛÛÛa=@†|“ @ Ðl@ dlš Ô±ëh=Ú>òÝ××Ç%þÙ³gÍÍÍ =<<†–㈪8;;»Ë—/£?322,XðÅ+yèK∗˜AÝç–••'YŸ …B144ä§··WXX8--MIIéÒ¥KîîîÚÚÚššš_Vò¢¢¢ŽŽŽQH^PPŸŸ_RR·wïÞØØØöööÈÈH¶^ †µk×—––¢n‹’““-,,DEE?çât:Çs*>r–m•+WKÍsÉbˆŒŒ´³³CŽGÚ“à°Vìíí;:: ÷ìÙSXX¸ÿ~N‘›››ÍÍÍ?'»¯FÅ ÃhƒD"ÅÆÆº¸¸ØÚÚnÞ¼uqJ"‘öïßïàààààÀ:&"‘H‡vvvž?~@@N„……577ûøøXZZ^ºt ÐÐÐàã㣧§‡tuôšçγ±±ÑÕÕݳgOcc£‡‡‡ŽŽÎ?üÐÙÙ‰¶½½½¡¡¡··7…Býšb0Xìª+44TQQQXXXSSñÿ.**3kÖ,iié5kÖ¨««"‘åååþùgccãéÓ§¯\¹q®–‘‘Ÿ2eʃx¦´´ÔÌÌLDDDUUõ·ß~ûð€Ç755!Ç7nDœÇ­[·®©©ÉÁÁAUUq'ÉI“s¡¦¦FKK ƒÁÌœ9³¦¦¹Ö¯_Ïêñwx롤¤ñ?ƒÁ`æÌ™#&&†>”!o—rÏ”m5ÊËËoß¾]GGGGG‡mí¡gÙÞè$/U7¨Ü‹Ï6wyyù]»vÍž={Ê”)®®®¨‡¥¢¢¢™3gJHHØÙÙ œÓ@àÿb½^½zehhXVV “ÉÆÆÆ>ä¢+bbbæÎ«««»pá»wï¢} ¢ÞŽÅbq8‘Hœ7oÞÑ£GoܸQRRhjjòññÑ××'‘HGŽAîºÜÜÜèèhKKËšš¶kjj¢U#""âââXó¨âFÅáEø$UuuuRRRFFFWWסC‡ÐðÊÊÊÔÔÔ+W®ô‹ÿâÅ‹äääÌÌÌÆÆÆ´´4¤N¥¥¥rrr–,YÒ××·víZ•{÷îÝ¿ßÕÕM›••uáÂ…ôôôÔÔÔ€€€ðððû÷ïS©Ô¤¤$ä±ËßßÙ²e>ôôô˜õHÓÞÞ^RRÒozJ__ÿÉ“'[¶lY¾|y¿'ަ¦¦—/_Μ9“uF%//¯¬¬¬££cç΀üüü”””òòòwïÞݾ}›GÊ4ÍÞÞÞÖÖ–B¡\¸paÆ \¶-;|ø°¼¼üåË—kjjЧà’|RrNhjjÒh´»wïNŸ>½¤¤¤  `õêÕg–†¥ºººþþþ•••¨%@(..îçZy`¦\ª‘¼´´”mñѳÜïA«Ž¸ŸSîÏž=»wï^eeå«W¯){æèè¸zõêÖÖÖ   sçÎ ¡(++uww‡„„8::qÑZZZééé?öòòÚ¸qcww7'E1r|Ò¤IS§N-((`0ÞÞÞjjjùùùýõWZZZfffTT‰DÚ´iSNNŽªª*'¹ÐOÅæSìÀâð¨ÀGÖl¸¹¹áp8,ëááqõêUÖ18èÃÖÝ݇Ãáp8SSS¶SºOž¢ìêêBJ§¨¨ˆ8¤”üüüŽŽŽ,khhèîîŽWÞ( ︸¸È`Ñ¢EˆÙXµj•©©é“'O6mÚ´~ýú$$$˜˜˜8::¢ ÃUȉÐÍ›7UUU½¼¼ÐGªÖÖVtäÁ)S.Õ¸~ýzÀÉK3ëY.÷Àç´#w½‘Ââp8++«ââb$¯žžž7b0ssskkëAÅÛ±cÇì>|m}ggg2™À]W ÎÆ1Ì’%K$$$Ð/Ý*Šíà’’’ ¥¤¤¤®®. €@ ÈÊʺ»»_¿~½_LNYXbÛ¶mèƒÅ Åá¤ÀGÖl ^Ë¥¤¤ÐaÒ l㋈ˆ ÓT*u`2™¬  ÀvžTFF9àççGóåããC®ÓÒÒ¢  €ö"ÞŸF‡…7ÖÕÕ•”” ¾ŽQŽ=ª«««¨¨¨ªªúêÕ+t›F£-_¾\DD¤Ÿ…—““CÞ¼yÐÑÑ ’’’rrr"“ɬñ0 ƒ±°°`=~󿢢":c¦¢¢‚\wJÂ;G-À|p‘øèÑ£‹/^¾|YGG‡H$ÆÅÅåä䘘˜DFFo= Ï’çÏŸ÷óó;þüÍ›7<ˆ$C§79eʥѻ‘-¬g9ݼ0dÍ5™1¥oÞ¼QQQA{ФI“½x```ÚGXGKÎÎÎUUUßÿ=w]‘œœ¼xñâ¹sçZZZ644 ƒ¡ŠbD;xkk+‘Hljj¢R©‹-Z°`Á‚ N:5PGqøËÂÚè+p¼‡“ÿ|¸-¸Õ××#î¥êëëQ >Ö'&YYÙÆÆFd-ý“."%%ÕÒÒÂzÈËËfËIKKTVV¢«geee!!!÷îÝC&CÔÕÕ™L&2Üvuuííí½téR¿õÌššdΪ¦¦ÕbëÖ­[·n]KKËêÕ«#""X×î||||||X"¹¹¹uuuèZË«W¯«ñññ¡Ùïß¿G;çÀ‡V¶’ð˜|Íš5ýÌ'ÀÀÀ€õa§­­-..îÎ;÷îÝÓÑÑ ‘HáááÃ[¬Ì;wÍš5åå\›hkkWWW³>´ÌTNNŽm5r¹®…²½xIË»Cν_^¬Æ¸Ÿaf ‘HØËººº"##œœ<ˆ<›sÒUUUûöíKII™''gôm>@`}“¯½½]PPPUUpóæÍªª*@__Ÿ‡‡Gkkë¹sç FOOk’èèèwïÞ½ÿ>22ÒÅÅ™pôèƒÁæÒ¯X166úõ×_ FaaáÙ³g‘U"¬¬,D¥§§£ñ%$$^¾|Éz…’ðžœûh!44488XLLLEE¥¢¢‚Á`”––¢+CÃUÈ¥QEGGÇ7´´´pkkktI“S¦œª±kû=ÀcZÞà²ÞÆ=÷~y1 d¦¢¦¦†—ÅÖûTo†‡‡kkkGDDXXXlß¾‹®èìì@æóòò¸í4¼œÁ`ôõõQ(”[·nyyyY[[kkkëêê‰Ä Ë`555ý–Ž8 ¬¡¡qÿþ}@CCÛwYUܨ1hq†K²Ù°±±qvv677WTTôóóZkÖ¬Ù¹s§Áùóç±Xì±cǪªªŒMLLxŸUÇãññññ±±±®®®¡¡¡fff£o6°X,«:›={¶£££¶¶¶¹¹yrr2ò–íëׯSRRnݺ%!!!(((((ˆN› 3ѳgÏVRRš4iÒåÚÛÛ===ÅÅÅeee»ººÐ‰ËA XzzúåË—ÅÄÄ÷íÛ‡TH\\\TT”¾¾þš5k¾ûî;4~PP¿¿¿¸¸øñãÇ9IÂ{riiiŰNª<þ|ÅŠ€éÓ§#/È^ Ô:::VVVvvvjjj£ÖÁƒƒƒ555-,,âããÝÝÝcbbþ›˜˜X]]mff¦§§ˆ.¶¡Ml =~üøÒ¥K·nÝjbbÂ]Ŧ:â^œáRàìç8mœN"‘N:5uêTð­‚<ª ?×­[‡Á`†üB‚¼¼üßÿ­­­ýÅËõe%‰Ü·lÙ²eËqqqÖÀ€€uuu__ß1Uùcd+CuuõÏ_øe]wÀÍFAîáÆéCÄÇÇy{øÎ;\>‡Œ>nnngœwïÞýE¾ïùŠA&ÊûñìÙ³1.á”s¼š ^™9s&AF¹i Œ ëêêN¼Ü½{—û+CCK5rŒ}Í;mÃx,G³Áå 2È@?ÀþÆ%ù"¹ñÊWTTD>›…T£ÔßZ£ÀÓ!2£  ±±V@³ÁÐk&ä«çSw—bƒbC³Á™×®ÀJ€|õ¨«òþ•̘Š=jìÚµ š ^YýÃJX ñˆñv’€@@±?uxÍò2¨zB´¬(È€oRA š ŒM³:…;DEEEGG—xZ3ôŠÃù"]x¤©ªª²µµÕ××GÜ ŽkÕ7jR}®Ùذaƒ‚‚—¾¾¾/^¼øR•8¨xƒòóÏÁÊJJ¼Çö¬ÒÆÖ^Zn¢ò$õ àtÇiNáNËÝDÅe¿‰ÊSÐë”?}:ßf¡´Üĺ©¥±ÍëpÂ1ÒK é 6A]Fìì즱àíí=¾$×ÐÐÐ××wqq9}ú4«ó‚œ={ÖÜܼ°°ÐÃÃch9ލг³³»|ù2ú3##cÁ‚_¼’‡¾$Žx[Ô}nYYÙt’õùÞ}Ý\—R|ϵ>::3ÿJ=ߨÐè°Äiò¤É¾>k¹„îq]îXü½Ðh4'g7WWçkWÒŠŠŠ/už6M]Ksz¿¼&*(lýyËÅÔKn"##íììãOõ«öÅ+ööö………{öì),,dõúÕææfÔ÷ÚÐø²*î‹Àq´A"‘bcc]\\lmm7oÞŒº. ‘Hû÷ïwppppp`‘H¤Ã‡;;;ÏŸ?? €N§š››}||,--/]ºhjjòññÑ××'‘HGŽA¯90- ¡¡ÁÇÇGOOÏÐÐ088T®X±BWW×ÒÒòâÅ‹hËÙÛÛz{{³î{Ê]<@ii©’ðçŸ84F'©¶íØ©:eÚDå)3t ‘Õ‚Er *Z3ôΜýàºúù ·Ë…§L™liiþâå îá À/ ÀÏÏÿÁËfiYy}CCЦ@0{¶¡Õüygϲñ¾ØÁÎvÁ¿Ö a„@ ðñSùêÕ+CCò²2™L666~øð!]3wî\]]Ý… ¢^³8õDN]x(J ‹ÅápD"qÞ¼yG½qãâe} ò ÎÍÍŽŽ¶´´¬©©a+°¦¦&êQ5"""..Ž5¯*nÔ`«KUà#>IU]]”””‘‘ÑÕÕÅêg¢²²255u ƒ°/^$''gff666¦¥¥!u*--““³dɃáíí­¦¦–ŸŸÿ×_¥¥¥effrJÛ×ׇlT~ïÞ½û÷ï#ŽÏètº··÷ܹs‹ŠŠ8QTTD§Óýýý—-[öðáCOOONžËfA§Óýüü\]]=zäãã“‘‘Á©*=*HM½Tðà^ý«çׯ^V˜¨@£Ñœ—»[ÏŸßX÷òôï'‚‚CóóÚÚœKJéêî~þüÅ­Ûw¬­¬+p ì ß­6MËjÁ¢7³Äüëî±´¬*2È—EYY9(((((¨»»;$$ÄÑÑÑÈȈ‹®ÐÒÒJOOüø±——ׯ»»»¹ôD^ºð˜4iÒÔ©S Ø*Ÿ¨¨(‰´iÓ¦œœUUUNs¡ŸŠµ¶à¢KUà#k6ÜÜÜp8‹õðð@ÜI"¬\¹ÇôaëîîŽÃáp8œ©©iEEÅÀ –””ÔÕÕYYYwwwÔõÀ´Ož>>îº"999))©­­ ‡Ã‘Édt``Oä± ÖÖV"‘ÈEù pxì4DvvöéÓ§¹ëÒAøÈšúúzĽT}}=*Á§Â:(‘‘‘‘¸víÚÀ‘Ê@dee‘…w4PJJª©©‰Á` úº¡¡AZZZJJª¥¥…õ.ábú=† ê}’¨þêIDATµjÊÊÊœ"¯]óãÚ5?¶¶¶ú¬[¿w_Ì’Åõ ¨$¯ëêeee_¾¬©¬¬Zçë% À¯8q¢‹ÓÒó¦þ¸‘Sø€Ùd|ßÇ©^™3þÎüð€°ÌÙÕÄØàùãž?þ5d ‰ûQWWWdd¤““ÓÁƒ‘gsNº¢ªªjß¾})))“'OX[[#S¯lr”ÚÚÚªª**•Ê]ùp˜@  ï=vtt ô#É‹6®†@+œ]:, ü“'©Nœ8A¡PÚÛÛlmm‡–˜˜X]ÝG庺ºD"ñÀ===€šš.¯ëèèHKKÇÄÄôööÒéôÇ#WLLLd0eeeééé‹-ÒÕÕe2™·oßF*(''‡GÙfÍš…Á`Ξ=‹LXåæærŠYQñ¬°è1ƒÁb2™†úB‚Bqû2ŒÇÅÿ¤œÿÓi™ãĉ ÂÂÂÇŽŸ ÑhMMoþLýkªÚ§ð®®®?/þõæ ™B¡œKN¹z-ÓÖÖɱ°èñÛ·Í­­­ûÄÿóäÇÕl6È¢Óé==½}}}}}}==½èê"òùÐh´Þ z3<<\[[;""ÂÂÂbûöí\tEgg§€€Àĉyyyµµµ\òrf ƒÁèëë£P(·nÝòòò²¶¶ÖÖÖTùpXCCãþýûÈCjvv6w7jð¢K‡E²Ù°±±qvv677WTTôóóZkÖ¬Ù¹s§Áùóç±Xlbbbuuµ™™™žž^```[[GɰØcÇŽUUU›˜˜ ;jáñø„„„ììl===??¿-[¶èëëãñøøøøØØXWW×ÐÐÐ~49µðøøøøsçÎ"5ËéEÃŽŽ?ÿ •§LR›ÞÕÕ´‰@ œO9›q-s‚â$7÷U»#všÏ>wæÔ…?S'(ªêšÈËÉEìúÀ)œÉd&;®£g¤2yZ\\ü‘CH¦&HŽW¯éèMÓœ™•}ëjú%¶ƒ‘{~•‘W)!êpzL1:Rq4¾¾¾/^¼€wð'qìøI«ù ¯_x{­Ú\\=*+«`MB¾,vvv¬›æz{{/É544ôõõ]\\NŸ>Ý××Ç%þÙ³gÍÍÍ /ŸC`DU¥ÝåË—ÑŸ ,øâ•ÌqI¼¬¬lŒ8·GÉom¬çÎþùçIGg'¬IÈ'22ÒÎÎ9æäP`ÌVìíí;:: ÷ìÙSXX¸ÿ~N‘›››ÍÍÍ?'»oPU²m„……577ûøøXZZ^ºt ,**Z¸p¡ŽŽN@@ꨩ©ÉÇÇG__ŸD"9r $‘Hçγ±±ÑÕÕݳgOcc£‡‡‡ŽŽÎ?üÐùQ'²M8)þ牱é\E5§ånïÞ¿GÃëœ]Ý”&OQ×Ü ðöñÿûï¬ía»´fèUW?Û1múL¹ ʳIg}p;#.%ÿæÍ‚Á[~ÞÉšWÀO›ßÉËWxhÍÐ;— ¿ò…|IÿGðx<àÕ«W†††eee2™lllüðáC¤¿ÇÆÆº¸¸ØÚÚnÞ¼¹··¹BLLÌܹsuuu.\x÷î]T9>|ØÙÙyþüù¬š¤¬¬ÌÞÞÞÐÐÐÛÛ›B¡|–RÃbq8‘Hœ7oÞÑ£GoܸQRRÂVççææFGG[ZZÖÔÔ°XSSõ¨7¨ª¸«PN2²fCZZ:!!!''gÉ’%HàÍ›7Ï;—ýôéÓ´´4ƒÁðööVSSËÏÏÿ믿ÒÒÒ233‘ÈYYY.\HOOOMM ¿ÿ>•JMJJâžpLA£ÑÜÜWyx¸½®©Ú¸ÞÿÂ…‹H8ƒÁpvq›®¡Qó¼âî줔ó—ÒÒ&ÄÏ›g¹3l[YI‘šÚ”Yº:ù÷n75Ô¬_µzMWw÷ ÙÅîûUNV6%éLYI‘›+Ü`2¶PVV êîî qtt422BNUWW'%%eddtuu:t ÔÒÒJOOüø±——ׯ»?v/^$''gff666"š„N§ûûû/[¶ìáÇžžžW®\.™'Mš4uêÔ‚‚¶:'**ŠD"mÚ´)''GUU•“À\`«*G^T(ÛFA³Á///qqq)))++«ŠŠ @III]]]@@@••uww¿~ý:ÙÓÓSLLLIIÉÀÀÀØØXUUUHHÈÆÆfЄcŠG…=½=~¾Þ ÆŒd:ožåÇ×ãšÚW;¶…òññM—÷Zãy)ír¿´‹ì$$$0Œ›ër))Éò²r¨w ãˆ;vÌþÈáÇ‘@ggg2™€FvssÃápX,ÖÃÃãêÕ«H âlƒÁ,Y²DBBý”ÄÝ݇Ãáp8SSSD!÷öö®Zµ ƒÁ‘H¤a,ˆ¤¤$…BáEçpxì4ú© /ÅaÛ(ÃÂ'|î'!! "£È¦¦&*•ºhÑ"ôÙ\CC9–‘‘AøùùQïç|||T*•{Â1™üVYI uò®ª¢üq†ª‘Jí54þຒJ¥ÎÐÖî—öäo¿'žø­¥¥Ç765Á Èø"00ÐÊÊ 9AÃ}}}wíÚÅÇLJ¢}\JJ ÒINNNJJjkkÃápd2]@¯†Çã…ÐÒÒ¢  €v4EEÅa,Hkk+‘HäEçpxì4DvvöéÓ§yT¡ledÍÚ„\‘‘‘¸ví/‘‡%á(#++óöí¿Õý¶¹ñP/'++%)Uøð'ùŸVTìø%<ëÆUuõ©]ýÙL&ÀG Ðè4$Î{ EXDxÕŒD"Q^^¾_`WWWdd¤““ÓÁƒ‘góRõõˆ+ºúúzD[UUUíÛ·/%%eòäÉkkk¤ °EJJª¥¥…UÑÌzhÔÖÖVUUP©Tî:‡“ÀFûÐg;::„…G»Ï²6ZἨÐ2â“TbbbuuuÜëêê‰Äôôôjjjx|exÈ GC}ƒ‘yãoÀ«W¯¯]¿„HHˆGìŽêîéTW?ï÷ÕEG{‡ €€²²2 ;çÖóçÞÏÓž¡}ûv.àu]z5VÄʼnµµ¯ Î‚|qh4ZïGP½®­­aaa±}ûv4ò‰'(J{{{BB‚­­- ³³S@@yÌÊËË«­­å®˜LæíÛ·—““ó9’3Œ¾¾> …rëÖ-///kkkmmíAu'544îß¿hhhÈÎΚªvxQ¡eÄÍÆš5kvîÜi``pþüyމ±ØÄÄÄêêj333==½ÀÀÀ¶¶6žrjÂQ†@ œ=sj箈אַóß0ÿ;KTþÔ‹)OŸ>SSך 8éÇ5Þ--­¬ ôí환-XèðçÅ¿44>øþuwxLÜ9æßùÿ/ÀÂ|ÎÀ7¬÷ÿ)(x¢ò”S¿ÿ5ä3™ÆÓ†††Îüˆ““ +++77÷—_~„„„”——§§§#‘mllœÍÍÍýüü:::VVVvvvjjjÜ&=ðøøøøØØXWW×ÐÐP33³Ï)upp°¦¦¦……E||¼»»{LL /:‡“À¡¡¡Ç_ºtéÖ­[MLL†¦*‡^TèÀF.0sLgßÉËo%°–’²ŠãnÆ‘ óÚ¸.dœ"B”F{q?;z‡E·2üüþN"‘N:5õÿíÝ_HS}Çñ³gËÆ³ÚŸ0/ò"ˆeÄÖ*ª™Òÿ€ÙŠj¤z‘ O”³4Èìu©C$ B"¼i£‹a&9ûæJ‚Ê@—ÄM³gÂÌ-§Ïʼn!j–ø8{¿®Îùí8öýM~Îïìœßúõ¡©Ž'à†òKŸ€ûO^ޏû¥Çwú==½¥¥E<>!!¡ººúرcÉÉÉf³yttTlÿüùs~~¾Á`ضmÛ… ÄÆ¾¾¾üüü-[¶$$$ܺu‹ØAÚÛÛ½^orrr°%**jß¾}N§SÜmll,))©®®6aT×û÷ï½^ojjêÄF‰D"B|||}}}[[[nnnAAÁ÷ïßÅW?~ühµZGooo]] @àÔ©Sk×®}ùòå«W¯L&“ cccyyyZ­¶µµÕn·×ÕÕ9b@¤ðx< …bùòåW­ZåñxÄíÖÖV­V«Óé«®ÁÁÁ©u‰ÒÒÒT*•D"9|ø°F£ >Â$33S*•J¥R£ÑØÑÑ!ÂÛ·o¿~ýZ\\,—Ëe2ÙæÍ›Ax÷î]ww·Ùl^¶lYLLLfffccãB•É3©„šF£ñz½>Ÿoâ;00 ÑhÄí¢¢"»Ý~æÌ™›7oÊda3L©Õê©u‰¬Vë½{÷<T*íïïÛW¬Xñs,–ÉÄK;ýýýkÖ¬‘J¥ÿ¼¯¯Ïï÷ïß¿_ÜýñãÇÆ‰ ‘">>^¡P>þ«7‰‰‰éíí «^½zµF£yôè‘8åµ°˜¤jr¹Ül6—••={öÌï÷÷ôô®\¹òÈ‘#Ác”Jå;wÜnwQQQ —ºÎ;WVVV__?44444äp8®]»æõzåryll¬ N§óÓ§O3¼‰N§‹ŽŽ¶X,>Ÿott´­­M½^¯R©ªªªFFFAp»Ý¡¿]ƒØ°Nž®EGI%ÕNÓw¦­Å X{¨§ïèÐw é"˜Õ!•ˆd2ÙÈîfý­>Í8aÞ”é³Ý þóE0Õc)¢‡ƒ÷»žü2êë1=Ìçô·N¼÷´ï¨Y¿;>£ŸÕ0[‘L*•Éd+†Ùwl«¼uOß&nߺiH»ŠŠœTé…÷÷öœ²kàäw“æúX±.¥vg|Ï6ŸöµŒ¾u·ÏÈ~?7½¯å¨öåÔ…¤b½Íƒ{:®n_!©ülÙ×»¿þ¬4íÎR AßÞªIá½³ômWFÍé媘W*•Žüñή…ï([/=»Òˆ0ˆ\HÿÙÌÿD8ƒ¿ØhóÙgiÏ…F†Ãútþbx§ƒó{ ÷vÑ6mO1ÐaB ‹ŠÙ|S-c+W¯DÊ!„HŠ_Uªû9Ñ‹ËzÐÿNpø¯¬Ô´tŽÃ‡Ñ™lBˆWWû‚ۿɤU£ÜÍ !ßlÚXT.«²ìµë÷ÿܧ¨KÙ¹÷`Á³dÝŽ¾ú]ý‡u1$„ü´}›„©sên!d´·cñý‹DFx¦¤²R˜öHÛ²ëÓ*›/wž)ySU^¤l8µð­;÷å¾xViÞ-ìb*!dl_Ç’— !ÃÜÌ!{wþPÅ1<ž\LÞÛ±8弌ÈTŒ¢–9näÐUS}^¾) ˜½ðfüߺv=LVe1n½5ªÏD¦æ¿š÷Úò£N¯Î¼ò¨Uíõòþn¦\HWqB߈£ÏDÓÜ~Vpõâ9“ògžnNŸÏšb ¯÷i_‹ã¿GëØzŠ*uµôõt¥l-&WO&“ñy,BHIi™¬R\%*¬ùò°V¯¢¾6¶Z~HWk½éý¬Ú›vãqØÔßw}=É›LÉ› 3ý^„ìW¯u?dëšéXËÏh¦ÏQ1–ò:ï ¿Sw–¶ž–±µ™Ÿr.rï¿wåæfå¿yÖ]÷ÿùb†w›ÃÿÛ&•Êždí½’~ëΓÊâeé٩U3µx…`0µdU•jf÷€on½>»Žþµ<#QÛúß3oÕc]­Eëw]¿à Uñ1 Y•¤ª¼(§Hla¨manZÌ᱌¬Lùò‹U=ößn>6—m`!#Œ×Eâ6†ÚýGä½)ÁXYôúX|vÄ¡_­8ù½ºuY8瓯†ÙO¼Ï1µW6ümôÿ³j3c>åR±¨²øÕ›R‰©ÇÄȨ‚Å´±"„¼z'•”W•¼V1ŠZæç_®ýñÛ5W-fþøÛ_é"-A{†—Œ†Ýwÿ“¡¿á®p¿­ ÿ}ÏPªðýF…ï[Öx¿QÁ¼ôz©Q¿ž ê¦b½Jû»©˜fÒ=ÝLùĦBœÈ¿W šcÛ¤Žý»Z™µsâÛy8õüŸLª*« ³~þ33¿¨dñ¼-¸LRébÅŸÝßF*•í;x”¥kTí;Vª×bÐs’~×aÔ¿jS]i/ÓõÛ{,]±æß·¿¥•Çâ³ !_|6CŸCôuxsúÛȯQõØÿl“-“ÉŽÝÎ&„N·pfÞc”ïø¹{vïÒï2ŒTIBFuèì9à%ÏånƒR^^^”ø[Uy‘²áoï‘ÿ³ê_œbrx²*Éw_B>™8Z‹Y9ÖÓšr,êS‹«zµÌ¸Ä;K~øM&•}³p¼Ÿ‡akãÕ´þî*©”ú§âÍLêßÛ÷ ÏûûîÕþ©?/!dÐwñÔ¯ƒ¾‹W±^zv¶Âmýåìó·P7àoÿûoÿ6=ðþó\=Sôzk‘X:ÓËXð‘=›Í,*«üýÖ“M›BYz™´êUaÅÌí—§¸ëmúücc#&ƒQY%]ºv[rÊC]/G—ZÂ?ÏÈ/ÐgSl͵h ì•mÀÆÓ©s½ôöý°1· ôðçr‰KŽß~Yõúñ¸Ý¢¶xfå‹]Ïèëdüï›ò*ÇÖt,þeQFʘ]Î ü@\ER„Å‘—’EÙ zL<çõì¾æ?í!“1î>{õÝæ- ³2ïùù;6 ‡S ¼rãÖ®/&ZZ˜ç•TlÛ2âø®¥ ƒ©õËŸÂÊש~>}Æå–HþwìʾƒG8fŽ –ªQôv&g/Ýyê»ÏF|5¥¿þo¢…³T3ï»}¿½¥V‹¢{í†WáÒ”ôwKÿózYÚeR–´ªÖíÐ5! &³Ú¼Ð´VD¦Ý:TYú†TVÈd• -¶¾ÏÜ‘¡Í—I+ódßK]vTKRö­—ÃâÙº³t|Aÿ ×óÎSs™&C–סÃ=-/ æBYZ¬T\ÊÔæGžp+Ký“>ÍŽ?¨:*~õXZYÁÒ1åΧ³Nÿ_ENªT\Âd²Ym9‚ö’ש7¥¶1—E™IÒÒ|!,ž!¯]w“qóñk…Ãé…_LÉ«x&—2µ¸\Ë.“vL¾‰¤Rò· ~üu£øÍs™¤‚©­Ã1ïÈØ3tŒTŒ’ßà;ÅÆÌÛ^–zH¥:ö½xŽýq¢jP·úß.”VêóoÏÀ¦šW>»ÿ“Ô &SÍ*%2™” ïNÃèvô•‰ ˆ´JF¤DFƒÉàê±ôÌeÒ*BZ;–Ž‘¬¼(ä×ÿ[:zÅÂÙ÷¾Ú•Ï·b°µeÒ*ƒž“dåEÕ(ÉJa·é¨þhµé¤§gJ*Êd2)a2õÝÇI9uš18:¼ö^s'R)"2BXl÷±2‰H±ô¶Ñ§+CK‡kïÉ1ë@$"‘ƒÁá1uŒdÒ*-s'¶ž©´¢”È*‰ŒÁÐÒfꙫN/œcî$«©”0YL¾€Á3”I«\žÓ‡Ú¥y¤²‚Èd„Ébê1uTú·1™lmË®Zúæ2q!²ªâ¦® NTM»ï®㟠•Òg`SÍKè:‚×âþÆ!lud²Ê×OKîž%L†^÷Y–hhmÊïžæ8 Tsbñ£‹vƒæfg¿hÚy¥E/ÿ­#(S£‡Z–±µ¾ûX™¤„ˆERIƒÅF›@«Òn``žõ"£ÉçUÕg­ƒð "Ài­S¶0½æmTL¬ óþ'»IS ­2¾¿ivóB}½z•wÆñh.tÞÜcBnGí{“#¤›Y¡]4 Ò¯^¤Ï™¿DÁ{S?F3hŽÈÈÈÏæL—‚GM4?Ènd7 »Ù €ìd7 »4QÿþýSRRÞSv'&&Ž7ÎÅÅÅÍÍmìØ±qqqô¨3gÎŒ1¢K—.^^^ÁÁÁ……ÿ<µvذa§OŸ~çS¶XeæÎ›––öΫ«çìÐjU˺³gÏ2D#î»E"ÑìÙ³ {óæÍE‹iiýó覈ˆˆuëÖ-\¸0!!áèÑ£ùùùÓ¦M‹ÅõܲwXì½{÷ÊÊÊÞyõœà½Q7»322Š‹‹?ùä>Ÿ¯££Ó§OŸnݺB***¶nݺzõj‡cii¹mÛ¶‚‚‚'NÔg³T/¶S§N¹¹¹Ô”6l %„„„„äææöïßÿäÉ“„>}ú|ÿý÷ãÆóóó[¶lYEE5‹š³ÔßË—/ÝÝÝûôé³k×®jc•ÅTƒe·‘‘Ñ’%Kbbbòòòèá)))¥¥¥¾¾¾ô‡3`À€7nÔgoßa±!!! ,,,&&fäÈ‘ÔÀÔÔÔððð³gÏ–••íܹSÅÎPR©tΜ9±±±'Nœ8uêÔ… ªM£~L½Kvóx¼Ã‡ëèè¬]»ÖËË+ 33“’ŸŸ¯«««­­-?±±±q~~~}v¸¡;iÒ$‹Åd2Î;‡3 Éš5kz½L ¼{÷®P( ÒÒÒ233›ÜÜÜ ¶oß^^^NIOO¯ù±n…1Õ`ÙÍçóŸ>}úÉ'ŸtíÚu̘1|>Ÿê?¡:kV­ZÚ­[7Ÿâââƒr¹\jìW_}Õõ­U«V©¿Ïôb»wïþñÇ8p€êBùꫯöìÙ3zôèU«VÉßàÏœ9síÚµGŽ¡† ÏÍùå—_öîÝUÏþîѧOŸýû÷wèÐ'h&5cJþ¹9JŸy¦“““Âá=’ÿuêÔ©±±±;wî\¹re}öGÙêj® i5I^©›ÝjnƒÁ¨ùáów€€€æ¢IòŠÝ‚´žßÐØ˜BA€æÙ €ìd7 »ÝÐ|F022íМ²ûñãÇãÇG»h2ô™ »Ù ÍTbbâ¸qã\\\ÜÜÜÆŽG:sæÌˆ#ºtéâåå\XXH 6lØéÓ§ë´–¹s禥¥Õš×$+d7@½ˆD¢Ù³g4(66öæÍ›‹-ÒÒÒ¢FEDD¬[·náÂ… GÍÏÏŸ6mšX,~·Ý»w¯¬¬¬þÓ4¸&Y) »ê%##£¸¸ø“O>áóù:::}úôéÖ­!¤¢¢bëÖ­«W¯öññáp8–––Û¶m+((8qâ„:‹ýî»ï<===<<|}}ÿþûïÜÜÜÀÀÀþýûŸtÍË>}ú>|xðàÁnnnß|óMvvv@@€««ë´iÓJKK©i”ÍøÓO?;Ö××7((ˆz2TÍ• »¡°±±122Z²dILLL^^=<%%¥´´Ô××—Âáp  N)µäääsçÎ;wîöíÛlÓ¦MHHˆ@  ‹‰‰9r$!¤sçÎQQQIII³gÏ^´h‘H$ª6T*3gŽƒƒCllì‰'N:uáÂjù—/_>zôhTTÔñãǃ‚‚Ö¯_ÿ×_‰ÅâððpBˆŠÓÒÒ""".\¸}êÔ)*»«m²šwøðaµk×zyydffBòóóuuu©×ÑŒk>ª&.—[^^þàÁ±XܦMssóšÓ <ØÀÀ€Á`Œ9ÒÈȨæ îÞ½+ ƒ‚‚´´´ÌÌÌ&Ož|þüyjÔŒ3ôõõ­­­=<<<==mmmuttüðáCÕ3Nž<™Åb±X,///jb€÷ úç»ñŨ¿ñãÇÛÛÛSO4ÍÊÊZ¹råòåË:dddTZZZQQ!ßoÞ¼Qç yÎÎΟþùæÍ›Ÿ?Þ¯_¿5kÖ˜˜˜T›&"""<<gÎ.—›žž^RRBR­®3ʯWÔSdddÍ;ôwCÃãóùOŸ>ýä“Oºví:fÌ>ŸOõŸP÷ΫV­ íÖ­›OqqñÁƒ¹\.5ö«¯¾êúu«N+--ýꫯ<<Å}&NNNh€F²téRõ'ÖÓÓS7» !»wïFû4¸9sæB6oÞ¬æôëÖ­«CvBfÏžVh@ÙÙÙTv5¾·¥ð[9ôw·@ £®ßù€æÙ  ä/…ìh6Á­~|מݱ±±={öäp8æææS¦L¡:;;3äDDDB|}}·lÙRmv…SB¾ûî»víÚq8WWׇ2„ñ_tiMeÆŒSëÆÈ/sÕªU>¤~ÖÒÒrqq¡ŠåBÒÒÒúöíËår£££ !!!! ãÊ•+„ÂÂBmmíaÆá €F½ãV3¾kÉîòòrÿ€€€‚‚‚äääÞ½{Ó£8 zkܸq*RsÊ~ø!,,ìÔ©SÅÅÅ[·n­¬¬ŒŠŠ¢&ÐÖÖ¾zõªH$’ÞJ53fÌHHH „deeM:UÅ”„ùóçÓk ¡–””äççòÉ'ãÇýú5•ònnnùùùË—/ÿøã‹ŠŠ!vvvÔ3«¢¢¢¨uÝU¢N|×’ÝOŸ>ÍÏÏŸ?¾ŽŽŽ……U¼¢¥¥Å}‹ÉTµœšSnÞ¼yÆ nnnÚÚÚ¾¾¾...ô4„‡Ãår«=]E^hhèñãǯ\¹²bÅŠuëÖÕ¬Á/ÅbÑk§kвX,>Ÿ?oÞ¼ŠŠŠ´´4¡Pø×_­X±‚ÇãM›6MOOz*Jÿþý©ûîcÇŽ©~x•••Õ¤I“ºwïîèè¸ÿ~jà‹/† F=åðáÃôͶ¶¡¡¡®®®VVV&L „ûì3:OU˜8q"!ÄÒÒÒÓÓóüùóýúõKJJŠŽŽÖÒÒrvv2dȹsç,X@M¼`Áj›Û·oOå¾P( …ÖÖÖ½zõ"„dffÞ¹sçÚµkL&sèСvvvÑÑÑT?u«Þ±cGj.ªP5‡ÃqrrÒ××Ç9 Ð\Ôçyxµ×ìÚµëo¿ý&“É¢££GŽÙ»woBÈ7ß|3|øpjš6mÚ¨XBµ)³²²!………ï4„BÈÔ©SUO”Ô#kkÍî—/_R?¼zõª_¿~FFFÊò´Úwg,--©‘‘‘³fÍš0a‚‰‰IyyyQQß999ôp ehÄïU~ýõ×ÏŸ?‹ÅÑÑÑqqq®®®Ô(‰DRþu‹J©¬¬¤Òýã5§\¼xñÊ•+“““ÅbqLLL­±ØHœ}||¶oßNõT|óÍ7ååå(**U6ïÙ³gsrr¨Î‡Ã`0lll\\\¶lÙ"“ÉÎ;÷ôéÓ?üPÅÚE"Q`` õÎ*´xµd7‡ÃIIIñððÐÑÑ™1cÆúõëé÷§NÊ{ëûï¿§RÕ Pý¿ §\ºtéŒ3üýýuuu-ZÄb±šjÿƒ‚‚¨§&Þ¾}ÛÀÀ`Æ ¿ýöÛ;tÇ;;;ÛÙÙùùùíØ±ƒzTùÉ“'oݺejj*V“¡Ý¼y³k×®zzzË—/?pà50<<ü?þàóù ,W}ß]VV¶{÷nªGZ<ÅÏ«d0»wïF-*õYYY}://ïÛo¿EvC3S^^¾eË–5kÖ >\_____ðàÁ!!!òÓ¼|ù200ÐÝݽOŸ>»ví¢nÛ¶­_¿~nnnC‡½~ý:5°OŸ>?üðÃðáÇ®!;È`0!ÚÚÚÆÆÆ"‘ˆ’””$ SRRîÞ½K‹Å?NHHHJJÊÊÊ"„dgg§¦¦ÒKÈÈÈxþü¹|Ÿ‰H$ºÿþíÛ·“““_¿~MMOß×?þ\(R?gff&&&&$$üý÷ß%%%šsè§M›ž››[mxÍёѣG{÷îBrrr<==ãââBBBrssû÷ïòäÉævL&‹Å²²²8pà³gϨ:u¢dÆ ¡¡¡ô‰ýÓO?;Ö××7((¨²²Ù MìÞ½{¥¥¥ƒ ª™w©T:gÎ‡ØØØ'Nœ:uêÂ… „Î;GEE%%%Íž={Ñ¢ET,B?~|üøñ3gÎhÔnJ¥Ò‚‚mmmêW‘HÔ¹sç.]ºÈd²Çóx¼îÝ»»¸¸äæææç盘˜P/d2Y^^ž‰‰ ½(jCCCww÷:ddd¨Hä’’’¼¼¼.]º¸»»wìØQ£º\Ú·o?pàÀ°°°j UópÛØØ,]ºtéÒ¥"‘hÅŠ£FêÙ³gHHˆ@  ‹‰‰9rd3=ÿ_¾|yéÒ%77·Z§LKK‹ˆˆ¸páBvvö©S§ÝÐÄ tuuéP«éîÝ»B¡0((HKKËÌÌlòäÉçÏŸ'„ <ØÀÀ€Á`Œ9ÒÈȈþîòÔ©SÙl¶|ú7­ôôô¤¤¤„„™LfmmM lÓ¦ µ…¥¥¥VVV CKKËÜÜøà‡óå—_Ö:ýäÉ“Y,‹Åòòòzøð¡Æõ€!ËZCCC*¿”Å÷Ë—/ÅbñG}Dý*‘Hœ !áááùùù,+''‡î÷466Ö¨´²²266f±XLæ¿·&lö?§ºX,–ÉdTç uO­££C¡Y ¼yóF È/°²²’ÃáМ8ŽŠûnKKËÌÌÌòòrCCÃvíÚiT¹µµµ¿¿ÿÎ;{÷î­úpBÆŽ;wîÜuëÖµŒ7l¿ûî;ÿ»wïΞ=;..®oß¾ª§§ÿ~³Ùl±XŒì†&Ö¹sg]]Ý?þøÃßß_¾[€Î&SSS##£ßÿ]þVúÉ“'[·nŒŒ´··'„ 4H&“iæ²ÙlqÉápØlv×®]« 722ÊÈÈ‹ÅoÞ¼éÔ©SµR‰O5HEE•e ƒn©TÊb±¨ŸÍÌÌÌÌÌ*++Ÿ={–mcc£Qí3wî\???KKK‡›RVV¶qãÆ1cÆìرƒzÉU­o­9b0]»v7oÞ·ß~ëííM½ö¢ß´())ÑÕÕm.û‚>“V‡Ëå~ñÅk×®ŠŠ******ºpáÂ×_MOàææf``°}ûöòòrª "%%¥´´”ËåRü7¨·òš#]]]‹% ¥R)!¤¼¼œz7RKKKOOïÙ³gÚÚÚ<¯Úý‹Åzùò%Õ’——G½ÔÐÑÑ),,¤Òœêo!„ˆD¢ÒÒR™LFÝøkà_8 ‹1cÆüüóÏ*7!dýúõ...6løðÃW¯^MM¬¯¯O¿%Û|QA111„ggç¿þú‹’••ÝŒöÙÝM˜0aýúõèÛ·o¿~ý"""†úï9ÁdîÝ»755ÕÛÛ»{÷î‹/ÎÏÏwuu8pà°aÃΞ=ëààÐ|<œœÊËË©>ñÔÔTú#&&&………òïRÒ³tèС   !!áñãÇ666zzz„›ììì{÷§ëëëSWUU¥¥¥%&&&&&VUUµmÛV!00îPx¸/_¾üçŸRÑW¬Xqÿþý¨¨(BÈÌ™3×®]ëááqäÈ‘æ{þóx¼É“'S—üꫯöìÙ3zôèU«VÑýHÍãLîëÕëÚØ79ÿü-ݹûçÖYBW“9::¢š3v;Þ‚w\uýnªeè”~õ"}Îü%èïnuœœœôèQsß5…ßíÙ³'º²ƒÞ2Ž{ë<ç‘Ý­N ¾VÓ­9 [ÛÙ Èn@v » )às&ÍCdd$v;ŽdwsòøñcÕÝÇŽcDZ㭠úLÝ€ìd7²Ý€ì@v²ÞâïUâk©Í/»ƒƒƒÑ4Í&»Ç h¡Žü¯èïh~ÝÈn@v²Ù Èn@vCKpæÌ™#FtéÒÅËË+88¸°°PõôsçÎMKK{çÕÕsvd7‰ˆˆX·nÝÂ… Ž=šŸŸ?mÚ4±X¬b–{÷î•••½óë9;²Z»ŠŠŠ­[·®^½ÚÇLJÃáXZZnÛ¶­  àĉ„N:åææRSnذ!44”’››Ø¿ÿ“'OBúôéóý÷ß7ÎÏÏoÙ²eÔ,j΀쨛”””ÒÒR___z‡Ã0`À7”Í"ÂÂÂbbbFŽI LMM ?{ölYYÙÎ;U¬QáìÈn€:ÈÏÏ×ÕÕÕÖÖ–hllœŸŸ_§åLš4‰Åb1™Ì€€€sçΡa¡¨^‹ Õ_As888”––VTTÈÇ÷›7oŒŒŒê´@@ý`bbB÷“¨—hŽÏæLW•Ý`AcܹsGWW÷Ò¥K}ô5D,_¾|yöìÙ„---‰DB /))ÑÕÕ¥~f0Õ–óâÅ '''ê:ÇÕ™×hŽ79ÂZ²›‚2°  ¸\nPPÐÚµky<ž··÷ëׯ7mÚ¤§§7zôhBˆ³³ó_ý5zô謬¬èèè‰'Rséëë …Bz9ûöíóðð`0aaa~~~Ô@ufÇ…š 22²æmú»A£Mš4iÕªU¡¡¡Ý»wÿøã 8@u¡|õÕW{öì=zôªU«z÷îMÏ2sæÌµk×zxx9r„2xðà±cÇ~ðÁVVVŸ}ö5PýÙ4MÎßßßßß¿æpWW×ßÿ½æð#FŒ1B~HïÞ½§Núγh Üw » ñ¡ÏZ8_äÀ}7ྠåÚ»w¯úëééÕ!»¯\¹‚öhp~ø!!dóæÍjN¿nݺºÝwSß^€†’]\\Lý\ë—¿~+‡ÒÀýÝÏž=[¼xqÏž=¥R©ê‰­¬¬„B¡Š ’““ù|¾T*µ··ÏÌÌôõõ »ÿ>‡Ã)--¥¦™Qjmdêåõmï¿þšjlkk{ìØ1zÞ¤¤$===êXçåå1 ¡P˜™™Éd2‹ŠŠ¨i\]]ûí7BˆƒƒCtt45ÐÈÈ(55•Ò¿ÿŸ~ú©Ú&Íœ9s̘1àôéÓšÜtÛ¶mëß¿?!dùòåcÇŽ•cmm““Ss®6mÚ¤¤¤(5™™]JGG‡úÅbUVVR·!íÚµ«¹U.\puuˆˆÐä¦;sæŒ!ÄÇÇçÂ… t”œœœ©S§8pÀÌÌŒîîînmmmkk›››K—¯FYË«9»²)•Í^ópÔéÒnŒë½™RvÎ+;½¢ªî¨©QÞ«d0L&“~]P3fÌèÓ§!ÄÂÂÂÈÈèáÇ S¾ªªªÖÎÎÎíÚµûã?Ž;væÌ¼ˆ³²²²³³»zõjBBU,[E#“ÿVkôCÈJJJ¨ëßÄĤ¼¼¼¨¨ˆ:•srrè P5YXX<þ¼æð… .Y²¤sçÎG­vK«! ¯_¿~ãÆ 6B***®^½êëë+“ɦL™2cÆ ê–œº)›1cÆÕ«W{ôèA]Øô«½jç§Â–Wv…Sª˜½A.í¿Þ›#eç¼²Ó[Yp;99=zôè½Þw?}útÏž=YYYoÞ¼Y»vmYY™|ˆz²´´ìÚµkll,!¤sçÎÖÖÖ6l‹Å‰äòåËOŸ>¥&kß¾ý½{÷ªùT8pòäÉË–-ÓÓÓsuuEvBüüü¨"Ô™§¢‘«éÒ¥‹žžÞåË— !ô=²‹‹Ë–-[d2Ù¹sçž>}ªâuýÔ©S·lÙBuüݾ};==ýŸÛ 6ÛÐÐpÏž=óæÍ{ùò¥6Ú… LLLD"Qyyyyy¹¿¿TT!ä›o¾‘H$«W¯¦§,--åp8;v$„œ>}úõë×ÊÎO…-¯þì §T1û;_Úz½7 •••åoI$eç|µÓ[;n5ï¾,»µ´´~ýõWggç6mÚœ>}úÌ™3u-²¬Úœ9sÊËË©ŸOž}3¨pà„ >|8a¤6Ýþùç!Cè!ʹ‹±xñâ^½zÅÄÄp8ú•ûüÁçó,X®â¾{úôé àóùsçÎe±Xòc‡êïïOõÂk`‡Éˆ#¨þ"BȨQ£¨—q‘‘‘ñññfff@ äää8;;Ï;·[·nýúõ;s挕••Š“¶fË«?»Â)UÌþΗvc_ïšoÅŠ¼·¨"” Ïùj§·š]%êÄ7£¯W¯k7béʰ;wÿüøñã+W®4öçL¬¬¬bccëz&5ˆŠŠ @pûöí:u05Óþ¦jd4à¸×D}ÎdïÞ½›7oVós&ëÖ­ ¦SúÕ‹ô9ó—´ÒïUîÛ·¯S§N->¸ ¥jÊïU.^¼˜~[ö}²··‹ÅÔ'xZ¼¦jd4à¸×ª>Õkâìn’õ¦¥¥µªW#š4ð¸_¿~þ$Ò;@-*€æ§)³[ï/A³nd77·K—.©žfæÌ™!!!µ.ª¨¨ÈÖÖÖÌÌLÅÇ «Y¿~}```ë5—Y×kX(2Œ“'OfddØÚÚ²Ùì7ÚØØèèè8;;Ÿ={–n¨I“&uïÞÝÑÑqÿþý*6Ia+=zôÈÓÓS__ßÜÜ|Íš5*ö]aË+¤þ2î‘Âí¼y󦓓“Á²eËT¯]á2Õ_QM‰dß¾}ûöíëØ±£@ ðöö&„0 î[‡?~¼¶¶v#÷xyyñù|GGGêîþàÁƒnnnmÚ´1b„ŸŸŸ‰‰‰†fw^^^hhèÊ•+…ÍÔ«W¯¢££;tè  ³²²úôéSë,žžžòßô}òä ŸÏ·²²Z´hQzzz§NªM/‰bcccbb–/_NwÎBÇŒAÕÜ=ztûöíóòò.^¼øùçŸ++qçáᑞžþÅ_Lž<9=====*®äïïO —ŸXÍeªÏÑÑ1###))ÉÊÊ*111##£C‡„¶mÛ^½zµ´´ôÛo¿0a]ÆK[[;111&&&((ˆú꿲MªÙJkÖ¬ñöö.,,|öìÙСCU컲–¯Iýe*Û£jÛYUU5a„ŋ°Ùl³”-SÍ)\`ZZ‡Ã9r䈱±±µµuµ‡ˆÅâC‡}òÉ'õ¿L”÷‰'úúú…††Ž7îÍ›7„]]ÝÔÔÔK—.-X° Ö¿gM–ÝË—/_´h>Ûš››[[[;88,]º´  €b`` fv‹Åb¤¤¤Û·o{zzª˜žê€îر£¶¶6ýŽPJJÊ Aƒ~üñG///BÈýû÷“’’6nܨ¥¥åìì_"‘LŸ>M-ò¸·Æ°­íDl…{]ÿ÷!¼éPv —L˹ï€æ—ÝïVkQYII[[[anµ4¡ %U ´•7ƒƒCllln^³«­ŠKFC³»ªªjÆŒmÚ´áp8žžž µäèèhƒ±qãFêׯ+)Ù°¥/›‹åË—9’þõ»ï¾«¡µúSV–VÓÄÆÆöìÙ“Ãᘛ›O™2å}®ú}ÖV¥T+ïìééIV h=—Lyy9µ×L&³]»v»víjöÙm``pñâE¡PØ»woÿw«ZÓ™3gÜÜÜΜ9ÓØmÑ|K_Ö‡ŸŸßÕ«W¥R)õëåË—ýüü4aÃj–¥ÕÀkØßß?     99¹wïÞ-ø}šŸŸ?þ| ‹yóæÑ£¶nÝjccÃçó{ôè‘™™Iÿ]´··×ÓÓ£›NýF®Y¯U™¯­JQXÞYKK‹*¯ª¥¥ÕÚ²›Åbikk÷ïßßÂÂâÑ£GjžŸš˜Ýò’““Û¶mkaaQÿE=~ü8##cÈ!nnnµ~{ðàÁ³gÏΟ?¿hÑ"ê1*Ê~ž8qâÊ•+B¡pÆ ÊJ_¶ZZZ¾¾¾Tͳ[·nvéÒ…Ô¯§²Z ïP…•.K«þÑT6°ÁkÀÚÚÚêëëÏ›7/..N"‘ÐÃùå—;w^¼x±¤¤äǤ_€ÆÇÇß»wïØ±ctÓ©ßÈ5ëµ*ÓàµU‰òòΟ}öYÛ¶m‡ F…WkSUU““Ó½{w5ÏOÎîüüüÏ?ÿü‡~h¢YgΜéÝ»7—Ëíß¿­Ý&óæÍãr¹Ý»w÷ööV]c“²`Á###BHûöíIëæççS­Ã¤>Õ8Ö­kÖjeißáhÊlŒ°ºººW®\ÉÍÍ}̘1 ÕaâããCñññùé§Ÿ$‰ŠWgfffôyyytMj`ii)uGIi[ý  IDAT¨Â4-À!C‚‚‚$IttôÂ… é¨-[¶äää°X¬ºVãTX Tõá¨éÛo¿9r¤—Ë%„P"ªÓÑ”X×µ«©k×®¿ýö›L&‹ŽŽ9rdïÞ½}||²³³©ÕÐMÑÖÖ¦šNýFÞ¸qã—_~Ù£G.—»iÓ&ª¸²žÃšË¬ÏѤÊ;ïÞ½»Úð>úˆúaß¾}ÉÉÉTý¿VâÁƒNNNOž<:t¨³³³žžÞ{N›†Ìn‰D2fÌww÷U«V5È ¯_¿~ãÆ êŽ¦¢¢âêÕ«¾¾¾DI-Pª(õC¯^½Tר¤«¦É©SÍÉÃÊÊÊÎÎîêÕ« TóÖ³§ÂZ u­ÂZ­,m]fµQV~E èÕ«×Ç}||,,,ž?^ë\ujdeõZßCmÕZË;³Ùl…×c‹Ç`0ýüüΜ9³xñ⺞ŸšÒg"•J§L™b``°iÓ¦òòòòòòúçà… LLLD"µ@ÿ¨¨(j”ÂZ ?ýôSyyyRRÒ7üüüêZöSýrš-²ÛdíÚµÔëâzVãTX ´žUX›vve÷_ýõóçÏÅbqttt\\œ««+!dêÔ©[¶lyüø1õrz¼YMujdeõZßCmU…åsrrNœ8‘››ûúõëeË–™˜˜tëÖ­U]2UUU•••iiiÑÑÑ666q‚½§ìÎÊÊŠŒŒ ×ÑÑáñx<¯þñ>sæÌˆ#¨—Þ„Q£FÑ]Þ kvìØÑÎÎnðàÁ?üðÕïY§²Ÿj–m©ÙýçŸ2„úµžÕ8•Õ­gÖ¦½&‡“’’âáá¡££3cÆŒõë×SOøœ>}z``à€ø|þܹs•½ñS§FVV¯õýÔVU˜\_ýµµµµµµuBB¹sçêÿ ßæÅÅÅ…ÃáôìÙÓÓÓsÉ’%¤qŠ «ºëG Øß‚FFÓæwÔ€h½P¶…C#£é EwÔ€mù'"M-︣Ï ùi~5`¡%5²&2­gÓ¹¹¹]ºt©ñv5`[y.5dv·ÚšÍÚ!CÿÅçó‰\VJDD„²j±5§Tí RÈ´©Š÷š˜˜Üºu‹þU&“ikkS…t!+V¬pvvnÀÝlŒ¦SSttt§Nx<^·nÝè*ù·oßvwwçñxöööGŽi=—LK«KiÍ5!›©¨¨(êikk_½zU$åååQ£8 zkܸq*ªÅV›ò=ïBSïíСCFFýëË—/¥R)]½düøñ-ãó…"‘hìØ±_~ùeYYÙ§Ÿ~úñÇSß_›0aÂСCKKKwîÜÐPuC›‹–S–ÒškB6Sô!#„p8.—KÉ‚Åår™L¦²j±5§T¶.õ«°*›²ZmUÅ{kÖ€}ðà——ŸÏwtt<þ|Ce·P(d0'OžÌÈȰµµe³Ù¡¡¡¶¶¶‡î3Q&..ÎÉÉI__Á‚ôÓ…î»úõZ¼lJJŠD"ùä“O Æüùóóòò¤RizzúèÑ£™L¦ŸŸŸ–––üŸ±Ö ¥Õ€må5![|Ê+¬«>õ«°*œ²fmUÕÅ{«Õ€8q¢¯¯oQQQhhè¸qãÞ¼ySÏqttÌÈÈHJJ²²²JLLÌÈÈ jÕ.Z´(==½S§Nªg—J¥&LX¸paaa¡¡¡a­•rÕ¬×Ú5`«ŠHMMe2™C‡=zô¨X,>}ú´@ hÚ^ÍKË©ÛÊkB¶<³gϼEÙQX-Vá”5©_…UÙ”Êj«*#_633óÎ;K–,¡BÇÎÎŽú#Tÿû¤Y³fÉg·šîÞ½›››È`0‚‚‚jm%5ëµ6x Ø.]ºp¹Ü}ûöUUUíØ±C,SÑÿÃ?DDDhkkìÛ·Ç㵪«£EÕ€må5![žo¾ù†þÚn›6mˆ’j± §¤~xõê!äÉ“'êWaU6¥²ÚªÊÈÞÌÍÍår¹ôw.ÌÌ̪2{çìf0ß|óÍÑ£GëšÝ¯_¿T“‘‘Q­•rÕ©×J¡,—Ë=räÈ矾hÑ¢Q£FQï)//0`ÀÊ•+§L™?|øpêII­çêhQ5`ÿ]hk­ ÙÂWë¡«Y-VÙ”„jo^©¨ÂZílQ6¥²ÚªÊŠ÷ÊÞ411)///**¢â;''G Ô¿Ï$33S&“uèСmÛ¶ñññªŸhSm7MMMsss¥R)“ÉÌÏÏ·J¹ï¡,!dÀ€÷ïß'„TTTXXX¸¹¹Ý¿?##ãÓO?e0}úôqtt¼|ùr«ÊnÒbjÀ¢&dË#‘HÊߢ¢ZµXSV£¢HfµB¦Ê¦TV[Uâ½666...[¶l‘ÉdçÎ{úôiý?­§§Çb±ƒÁèÑ£Gll¬êûîj»Ù¥KêÙª;v쨵•ÔYfcÔ€%„$''çäädggSõ­¬¬˜L毿þ*“É’““ïÞ½kooߪ®Ž–S5![ž©S§òÞúþûïéì–¯«bÊš”ɬYÑWá”Êj«ªY¼7<<ü?þàóù ,¯ÿ}7!ÄÁÁÁÃÃÒ£G6›MuétîÜÙÖÖöþýû¶¶¶‡R¸›L&3""bÛ¶m ªªJ[[›º;«S)Ñ÷SöâÅ‹:tppp(,,¤>¿off¾iÓ&7tèÐ+VT;%Z¼&¯Kúzõ’Édy¯2©k×®•Éd=Ú½{·¬‘YZZR/9ÜÊ›N$1™Ì’’4{‹?îYYY=Zºt©ú³P±L§ôýÄ?ûzõbãܼy³}ûöæææ?ÿüs¯^½tuuÑ&­ Õ¨Bdddpp°ÂQM\G56ÑÈ­¹éîÞ½;jÔ(™LÖ¾}{|—L lË?ÑÛtsæÌ™3gš—Ì;@ X€æ5`[8 ldù⨠iHuÓ÷Ðt3gÎ ÁYŠK¦‰³»¢¢bþüù&&&<oÚ´iõ_àíÛ·å?Óîííýã?âÜjXÊ*»ÖÇÇ©"™ZZZ...§N’+_U¡÷YÝ´¡ÎÏŠŠ Ÿ™3g6Þê£Ô­šËTXÞ¹Á¯÷æBõéýÞ4d÷—_~ù÷ß_¿~ÝÔÔTý/†AÓòóóÛ½{7õí>R£PI}PŸxÛµk×øñã333MMM©áãÇoamXUU5qâDccãÝ»w7ÞZ„Baƒßª¿ÌãÇS'ý±úV~½+;½›ß}·D"Ù·oß¾}û:vì(¼½½o£bcc©ŸéâG Ëi6xÙÏ–GYe×úT ¥°X,>Ÿ?oÞ¼ŠŠ ªø‘Ââ¨ê†ÕXsæÌ)..>|ø0•kêÔ¥Š@Ñâââlllâãã !=¢j†˜››¯Y³†ºÁW¿Ô­š5`U,S¡jåßçõ®™jžÞõ¿dš&»ÓÒÒ8Α#GŒ­­­÷îÝû>ÛQY9Í/ûÙò(«ìZϪ¡ôËêê Ù ‹£ª_V3-[¶,::úĉ§Ö=ªV––’˜˜8f̘ˆˆªÞÈš5k¼½½ Ÿ={FH©S©[5kÀª^fMÕÊ;7íõ®!ªÞê\2š˜ÝEEEEEE™™™ÙÙÙáááóçϧŠ×Ô]\TþùRÕ(,§Ùe?[j·IÍÊ®õ©Jáñx\.wãÆ×¯_WöYõ Ãj¬³gÏæääÐONQ³hûöí©!)))ƒ úñǽ¼¼¨!Tà …B^½zÕºÕ–Y§°jªYÞ¹ñ®÷æ¢æé­Î%£‰Ù­££SYY¹dÉmmmoook×®5È’“ßRQÜJa9ÍÆ(ûÙ" 2äúõëTeW:»ÃÃÃÝÝÝ­­­mmmëZ5”¾ÝÈËËsvv S6 ]òÔÙÙÙÙÙùÚµkª_¹k ‹/®ZµêÓO?¥šHõÕ¬záÂWWWù‡|nܸ‘Íf÷èÑÃÖÖ6<<¼Ö ¨¶L…NÙÑTÓG}äììܹsç}ûö½xñ"99¹ñ®÷æ¢æé­Î%Ó€Ð`ïUÚÚÚ²X,úmw©TZ×:“ÊÐ¥sèפl6›zj¢L&+..&JÊi6FÙÏ©fe×úW ¥ïر£[·nË–-³°°¨9ú…a5VÛ¶m—,Y²iÓ&ÕE\kÖ]¸pá’%K:wî|ôèѱcÇB,-->>!!áƒ>h¤nß¾}\\!äôéÓb±˜()§Ùe?[p·‰|e×úW ¥9;;ûøø({—Fý°šŒÍfïÙ³gÛ¶m‰‰‰u­Êf³ ÷ìÙ3oÞ<ªâ9Õ Cužp8:Õ)u[×°ê,Sayç÷y½k2úônÀKæ}g7!äÿû_JJŠ¡¡á¨Q£vîÜYëóúÞÙêÕ«·oßÞ£GèèhêMa9ÍÆ(ûÙR³[¾²ký«†Ê +..VXUý°š¬gÏžÓ§O—H$ïP tèСþþþ³fÍ"„ܼy³k×®zzzË—/?pà=:¥nëZVe*+ïüÞ®w GÞ––– xɨƒÑ׫׵±orþyÓiçƒƒ?~|åʕٳg7öKõØØØßÉòòr]]Ý¢¢"Tek¼FFÓŽû»ÉÎÎ...Þ»wïæÍ›Õ¬#¸nݺàà`:¥_½HŸ3IË©gróæMêéˆ(§ -^Ë©‹ršï¡‘Ñt€ãŽìnàZ‹(§ùM8î5`šÔ€%DcŠŽj`#Ë×kýÿöÎ=®‰+ïÿ'!@( ÁRHŠ(PÛWU,†­Z[´ŠUkEk½WÛ—µkÛµU±uÕZ[]i­E©½)ëÚ"X‚V.Â*‚­¸Dñ"‚ÜÂå÷Ç<ΓÍLÆ ™@.Ÿ÷_dHNf¾çÌ'““™÷°VÉ ÒY–òTØñ©-á1´òÖQOÛìw ÈîÂÂBÑc¼ûiýúõ"‘è·ß~#„ÔÖÖ:::¾ð ¦¨‚¥HGM»»»¶l ££ÃÑÑñÊ•+ÔCm_+k•l¹t„ßÿ}øðá}ûö5k–€-?Ö”k/¿ürß¾}Y¥ùùùvvv|ðR›'fâ€,»‡Úøˆsçι¹¹¯&„(•ÊŸ~ú‰rôèÑ'žxãFpX^^N?¼uëV{{»R©¤ÆÅÅá$9}455M˜0aæÌ™÷ïß/,,ŒŠŠ°qa+ÿÆoäççß¾}{ñâÅqqqÔÕÈ„öööåË—<½i(õõõ555³gÏŽ‹‹ë‚+qL˜Ý"‘Húˆo¿ý6..NñÊèÑ£©ãîï¿ÿž¾E«k‘èql&%%ùøøÈd²ðððëׯ«Ž žÝ"‘è§Ÿ~*//W(‰„Õ×Êî"k+OYŸÉjÎdÊQ»—«W¯ÖÔÔ,^¼¸G^^^‹-¢ÿÅu„ŒŒŒþýû÷ìÙSû*Sæ í„)W»ž¬Œ3ÆÛÛ»wïÞ¾¾¾ŽŽŽ´XcÏž=QQQôG5àõ8`iZZZ80{ölAZ£¾•œ>}Z۪ϵH>̯¾újçÎéééõõõ;vì <'–.ÿòòò‚‚ooïóçÏ———8èñµò‡£È:ÊS}ÏdÊf™rÔîE¡PôêÕkÑ¢E999†^Î:ê!¹¹¹—.]úþûïuô¹:ƒÖPS®N=õ1gÎ77·„„„ãÇSv¤êêêO?ýtíÚµâÎa%Xš#GŽôîÝ;22R¨'OžüÆoÄÄÄÐK8„–:>ÌäääU«VB"""”J¥HGMqÜ]PP0þ|íì6Ž"ë(O9žÉ”Í*G55ÎÎοýö[UUULLŒ\.§•ÜÌQG-Ÿ?¾““ÓóÏ?ogg§ýkS ÛézrðÉ'Ÿœ?~Ö¬YK—.¥Tno¿ýö²eËp{ç°,Í?þñ„„T©T¥¥¥Ú÷TäZêø0+++}}}µ—XtÔÙ]XX_VV&HvsYGyÊñL¦lÖP9j0dÈÇß»wï‡~øè£(Aï%‹ëêê.]ºôïÿ›N“Ÿþ¹¨¨ˆÏg ±,=Ó÷Ì3Ï<ö8 r-&$$lÙ²…¾äåå©ÕjëŽ HÏž=íììär¹H$ ÿý÷ß¹»Y«¤³£È:ÊSƒŒ©úä¨ÝEmmí_ÿúײ²²–––ÌÌÌœœœ¡C‡²Ž:#߈=Y_~õêÕ/¿üòæÍ›÷îÝÛ°aCCCCTTTbbbÇ#&Mšôþûï#¸;•8`©™>a'LX‹Åßµ8gΜÄÄıcÇÊd²… RGÖ!??¿°°0BHxx¸D"¡¾ï³úZõU‰¹Ûƒª­<åoLÕ'Gí.ŠŠŠÂÂÂzôè1oÞ¼>ø`äÈ‘úFAIÍߔˬ'{{ûýû÷zzz9r$--š^‚,@‘Q:`ýý,Ø.Ý™Ýpl¢È(@¿wëqÀ¥¶Óï˜3ËÃÚ°:æLH2-Eh) B}ÍßQ, WV*•ÚÚ81Q¿³ùƒ>HLL4ßìÎÌÌ4h““ÓÓO?——'H›Ú^ÙÇž‚*¬9Óxûí·µ¯Yݼy³ñÈ.“÷²ÒeZÚ¼¼<úóæææ1cƘè2E¡¶^YA |×ëÖ­£FGGk_ÈZäñãÇÓv<³ËîÆÆÆ©S§®Y³¦¡¡aîܹ/¿ü²P×(~ýõ×´]vÚ´iÜO†³ÔPT*Õ©S§(Á!$##C¥R߬MÉ{ÛÚÚ¦OŸÞ»wïÝ»w›ózÂ++‰dÿþý¬ÇZä°°0Áïë"Xvi4šÙ³g‹D¢Å‹WWWççç Ò²½½=m—‹ÿw…yš3i+É´YFŒÑÞÞ~þüyBˆF£ÉÊÊ¢²ÛH¡%«¼—Udj¤ÖŒ¾ ,¨««ûöÛo©ËpXW‰uåß"¦W–õå¬{Ç™3g\\\V¯^M/äÿî¶ì•utt IKKÓ^ÈZä£G* 777³ž3Ñù2µ[•9“‚§$Ó6±··ŽŽ¦$JçÎsuu &ü„–Ú"Sf³Ly/«ÈÔHl·}W¯^™™ùã?:88p¬ëÊ¿EL¯,ëË™{G[[[||üŠ+îß¿/‘Hhq’AïnË^Ù×_ý‹/¾Ð^ÂA&LP«Õ+W®|Ëîàà`©TºwïÞ¶¶¶íÛ··´´åç{ýõ×åÐÖñ<ÖœI!à)É´åi““'OêL˜ðZrÿ¤£#ïe™é€5£/eY¹xñ"Çf=[ã·HÇ+Ëÿå.\¨©©™?¾H$¢“ÅÐw·e¯ìøñãKJJèkt=‚e·T*MMMMJJruuÍËË£¾ ÒòG}Tø???z9OãI¦Í2~üø¬¬,F“™™Ig7¡¥¶È”õ#A[ÞË*25ÒkFßôôôwß}wîܹT‰ô­«ÀÖø-ÒñÊòyuuuŸ>}¨IHwww‰DÒ‰w·e¯¬X,~õÕW÷ìÙÓ]+ äµ9cÇŽ-..&„477{yy…„„ÒlïÞ½Y`á©‘3T’iƒx{{+•ÊS§NåççGGG“ljvy¢#ïe™^ºtɬ9}ûõë·jÕª”””õë×oܸQß*± lß"þN]¹\N«Äêëë©OGƒ”¼Ú{¢mzeçÍ›7räÈîšÙr¾»°°ðÎ;•••”OËßß_f5MÓ#:±7ò‘d•Jµaư°0êÛ’)„–¬"S#°fbô•H$_~ùåÖ­[ÏŸ?¯o•X¶‚oÿz÷ìÙ3##ƒBÇ+ÿ—Ã+K}2Ä SìÍ4»ÓÓÓèççW[[+`Ÿ%$$8=â“O>ᵬÎR nI&P©T§OŸ?~<õÐ Ñ.XE¦F:`ÍÄè;|øðÄÄÄ9sæh4ÖUb]ySlëË™{‡ÝÁƒW¬XqòäIú‡VžÝ¯,Å‚ è_ì¹#Hpà€µþù¥ÝÛïëׯ¿{÷îÎ; °`´µµegg 5‡l’9CcEFé€u÷û€d2™F£™3gްë ¬õD¥ÝÕ�[ðq7ËËî.pl* ¡Œ†Š59`ù÷¦ î_Ôç ê÷Nf÷Áƒ‡noo¯-X¹uëÖ¸q㜜œ‚ƒƒ±>q»»†äääM›6YñH5…V[ŽJ5jÔŽ;¬¯t]¼™¦Š<ÛäнÚ&2™LÛMÍ­Þ4Q†t2»ÝÝÝß}÷]#í¢E‹<==kjjæÎ;eÊî ¦yÂáZì***h•‡Ub",°ˆ¡È³M}ºW›¥ººš²Rß½{·_¿~³fÍêú édv7nâĉ...ô’ÆÆÆ´´´5kÖH¥Ò7ß|³¶¶öÌ™3Ư«k±¤¤dĈ2™ÌßßÿøñãÚß©uİ„””////¿ü’Z¢Ïr©óò¼¼<…B‘””tàÀ…B¡P(ºÞ•јÈË ÿŽÓ×Gü{“BÛýÛe¶XÖÍd]˜““ЫW¯%K–Ðw®aŠa9†"³t¬Ç\hÐðÖ§{µY)+õ† † 6cÆ Ö.f¹û³›Éµk×Z[[•JåòåË+++ýüüþøãAZfº§OŸýàÁƒO?ýtÚ´i÷îÝ£ÿ¥#†¥ zíÚµãÇ/[¶ìÊ•+„Ór©ýò°°0JÞøÊ+¯¨ÕjµZMÛ‘¬ 9`YáßqúúÈ ÞÔqÿv™-–u3™ ÛÛÛããã—.]Z[[ëêêJÛ¾˜bXS:ÖŽc.4tx3u¯ 77wÿþý»víÒ×ïÌ"›cv?|øÐÞÞþáÇ_ýuiii=êëëiYǵxýúõ .¬ZµJ,ÇÆÆ*•J*w(˜bØE‹I¥ÒaÆ5êøñãÜ–Kž^Yë›61…–6÷ž;wΠŽãè#þ½©ãþ5-–Ïf².¼xñbUUUbb¢H$Z¾|¹ö1ÓËΠeí8}½É¦îÕÆimm?þÆ©+0¹‡·),»5MïÞ½«««GÝÐÐ “É„YÅÿv-VUUI¥Rúäym;S ëááAÿQ]]Ím¹ä镵2L䀥ͽO?ý´AÇÑGü{SÇýk:[,ŸÍd]x÷î]¹\NYXÝÜÜ( +Ñc‹å@gвvœ¾ÞäŽîÙ½uëVWW× ðÉ%S ص9J¥R"‘üñÇAAAmmm¥¥¥B5®íZtwwojjzðàU¦;wîÈårú™L1,åi£þˆˆˆà¶\2_.‰:¡?µ,L䀥”çˆÇqôÿÞÔqÿvÎn*Ôf².ìÓ§OUUU{{»X,®©©¡?Ym±CQ»t¬ÇÑ›†oZ÷jãÁ}íÚµM›6={–.>Çð6Q†tò¸»­­2²Ò899½ð ›7oÖh4Ÿ}öY¯^½¢¢¢„ZKm×¢ÏàÁƒ·lÙÒÑÑqìØ±«W¯rßÄó³Ï>kjj*((ÈÎÎV©TIG©oÄÅÅÅÝx¢K—M›˜ÚË¿ã8úˆoê¸ íwa7“uapp°»»;õ£ëöíÛéXm±<‡"kÇqô&Ÿ6Yu¯6žÝ .\¶lÙ“O>I¹©[ZZ8†·‰2¤“Ù½k×.''§Ï?ÿ|Ïž=NNNÔøÛ¹sgEEE¯^½vïÞ}øða{{{WTÛµ˜’’ò믿Êd²%K–¤¤¤h¾1yê©§”JeLL̶mÛ¨ AþÒQBÈ”)SÚÛÛûöíëíím•ç™ÐÙÝXþ§¯ íMm÷¯Aý.øf2ŠÅâƒnݺU.—·µµ9::R1Í*†å9Y;Ž£7ù´ Ý«ÕÕÕ¿üò˺uëh7õ!C8†·v‘\ 8`­Ù"J×ÔÔäììüàÁgggTÞºûX,ž3gÎܾ}›²oß¾ˆˆ7àO7{áØD‘m¹t/^œû¬ V›Õ#™™9hÐ ''§§Ÿ~švµ ,ÆÓÐаpáB¹\îììÿüóŸþ¹®®.))IﱡÄÅÅYâ9 LKÌ!÷IDATtccãÔ©S׬YÓÐÐ0wîÜ—_~™ÚñM,F2oÞ¼ÒÒÒÂÂÂ;wîL:õÁƒœÝíííjµú¥—^‹Å*•ÊÞÞ¾¼¼ÜøfY°éI“’’|||d2Yxxøõë×Ï;çééI,_¾üÍ7ßD1aS¿ùæ›OOÏI“&©T*ww÷œœÖ…Dp•µ˜ÊS bÓ¦M~øaHHˆ££cttôàÁƒ‰Ý«··÷Œ3† æïœ¬¯ÈL³+ké>ýôS…Báàà 3g¢3æ©—S—%Bär9õý€åyze‰!–`¦Gº¨¨H£ÑÌž=[$-^¼¸ºº:??ßDÁb eee‡úâ‹/¼½½gΜéëëkИ7¯ì¦ìYß}÷]KKË‘#Gär9¥5¦–¿žô«¯¾Ú¹sgzzz}}ýŽ;Z[[#""d2Ùo¿ýFéèè8|øðôéÓ‘ÔLX©ÎÎÎ¥¥¥'NœX²dÉêÕ«©/°¬ õ W™}ÄTžZ 7oÞ9r¤Îr}ª[GGÇóçÏŸr$œ.Pê(á‰'žˆŒŒì„Ž˜§}—uÌëûR˳òyeù[‚u–J¥{÷îmkkÛ¾}{KK õy`¢`1¦ßéNðôôܱcÏ1oŽÙÝÔÔ4vìØµk×677ÿë_ÿŠ‹‹êÞ :Xbˆž´²²’ú:£ÍŒ3~øáF“ššJKÚ€6úŒ©Ô SìA™¹C¸Êì#C•§æuGmm­öB¨««+ýGUU•¡:bžö]Ö1¯o&„gå òÊò·ë •JSSS“’’\]]óòò¨ùÓ‹1ýNOpŸ>}:22²¾¾žç˜p5»6§¸¸¸¼¼|îܹ"‘häÈ‘þþþBi`µ°éI½¼¼ÊÊÊtúúúþúë¯ßÿ½ÎL: ëÆ4¦RbRmX+ßÑÑapUŸòÔüñññéÛ·ovv¶ön.PúG¶Û·o?û쳆êˆù÷sÌK$ê† ´ÁU_åÅb±öÜ…¡^Yc;vlqq1!¤¹¹ÙËË+$$ĤÁÒ9ÂÃÃoÞ¼yýúõ'Ÿ|ÒÃÃC*•Nq±‰ì¸ÛÛÛ[,ïß¿¿£££°°ðâÅ‹ýû÷ªqm¬AzÒ„„„-[¶P—¼¼<úžC¯¼òÊêÕ«{öì9tèP$5!¤µµµéÆHcªA/×§<µV¬X±víÚ–––“'Oq¸@wíÚÕÔÔ”ŸŸîܹ˜˜iiYÇü€¨ß9ÒÒÒÂ]ù\ºt‰þº`¨W–ÿÔ¶ŽGšB¹QYYI‰ýýýM,C©T¾øâ‹‰‰‰”Uª²²ÒÐ1o^Ùíáá‘’’²qãF''§ØØØwÞy‡Š í€5HO:gΜÄÄıcÇÊd²… ÒßYâãã/_¾¬sŸ{[æwÞ¡…–Ô´¬‘ÆTþ/×§<µÞzë­yóæM˜0ÁÙÙyÙ²eÔÓç T*•*•jûöíTYä   …BQ\\|Ø:X”Xe¿Û¢–>ÕF"öFs(Œ`Øe„X°<,Þ+•JÛˆ€’L‹ÃÌ…–æ,µV}.Ç.¬°ýnÒzv2»™þƶ¶¶yóæyzz:88DFFæçç µŠ™™™"‘èoû[§[P’i}¼ýöÛ/¾ø"ýpóæÍ£G6¾Yž’Ì®”Žšˆ‘H$‰^{í5ã[kjj¢Z‹Å¾¾¾»víêÊm1´;’““7mÚ$ì:ðiS_Ú°zeMŸ\2éðîdv3ýmmm...éééQQQ&Lʇ™––ÆT „…J2»•JuêÔ)ê¢;BHFF†J¥2¾Ysdv 999†EQQQcccrròÒ¥K/^¼h¶Û^QQ!ø¯G|ÚÔ—6Ì\2ÆçR÷d7Óßèàà°uëÖàà`·Þz«²²R(ÍnZZÚÿüÏÿäççÓ×n9s& ÀÅÅeõêÕÚÏdš3õI2͈#ÚÛÛÏŸ?OÑh4YYYTvcø4H’É ÓYêíí]RRB?¡££ÃÕÕµ¢¢‚›&ÂÁÁA*•j«*Œ”£BìììGíååEë;˜Ã›¹íúǬ» ÿÒ1m±yyy …"))éÀ …B¡PÐúæzòTÈr´É¬9kÚ0sɤ٭“K¬›ivÙÍMaaa¿~ý¼¼¼ŒoêÏ?ÿ,//?~|HHååjkk‹_±bÅýû÷% mÀ¡Ð1g²J26öööÑÑÑ”ëîܹs®®®”cÓç¡’L>DFFÒ7R!„\¹rE&“y{{Ó¦‰DŽÚÖÖ–™™yçÎaÆéÞÌmgUëÛeø—Ži‹ S«Õ+W®|å•WÔjµZ­¦KÌõä©ån³ ÒÆ˜\âèw‹Éîššš7ß|sÛ¶m‚H³ÒÒÒ¢¢¢¤RéèÑ£©¯'.\¨©©™?¾H$Z¹r¥Îó™æLÀgÚääÉ“:&Æ> •dòÏî–––=zPž9#Û4ÆËQŸzê)‰D2nܸ÷Þ{O[ß¡=¼õm;Sq̺ËT:C=½:»¡A ÙnLcrI¨-ê¶ìnllœ8qâœ9s¦L™"TÆŒC3fÌ/¿ü¢Ñhª««ûôé#‹©é-‰ä¿NQgš3Ác?~|VV–F£ÉÌ̤³Ûç¡’LþÙ}öìÙ   ŒŒ *»lSXh—“ñrÔ’’’ööö’’’½{÷þøã¬Ã[ß¶3Ǭ»ŒA¥3ÔÓ«³¤íÆ´1&—Ù"ƒòÚF3eÊ”ÐÐÐwß}Wkkk³²²²³³©o^ÍÍͧN’Ëå´1¹¾¾^g€Œ»x{{+•ÊS§NåççGGGE»L •dêHG ›³444ôÒ¥Kéééï¿ÿþ®]»îß¿ÿÒK/¹¹¹u±x“#G¨hJŽ*‰üýýU*UZZÚäÉ“™Ã[_=™ŠcÖ]Æ îÐg‹‰D¬[§½ž†*dõµiê´12—ôm³žÝyÜÍô7¶··Ïš5ËÅÅeãÆ”JÔxŸï/¿üâîîÞØØH58a„£G÷ìÙ3##ƒB}7‚L›lذ!,,ŒºØHç¡’Lé(as–:99ùùù¥¦¦FGG·¶¶…††v½xS›cÇŽ}õÕWuuu7nÜÈÈÈ |ÂÆËQ©ý«µµõ?ÿùOff¦¾¯’Û®£8fÝe ê}¶X¹\^\\ÌL†*dù´©/mX½²‚ÚK[Ĭgwf7ÓßxóæÍC‡¥¤¤ôèуR‰ŠwZZÚ¤I“¨ïz„É“'§¥¥ÙÙÙ_›r-~÷ÝwÚOc]Ø•<÷Üs×®]+..†| €ãn’““Ó³gOíë»X]‹ ™¢È’’’#FÈd2ÿãÇBôi?™FJ ‘Hôì³Ïšƒºÿ¸ûÂ… ¦¸ï %Štss£.Æ>}úĉ³²²Ž?>mÚ4µZM釨ËL‰–ö3>>~Ô¨QgÏžmllÔ1Ó _8î&555¦8áQ[yýúõ .¬ZµJ,ÇÆÆ*•ÊÌÌLVí'á4Rº¸¸è»½ØVv÷êÕ«¾¾^ð÷Öž„©ªª’J¥ô'„‡‡GUU«ö“p)ëêêp) ÙM!O=õ”)nG¢-Štwwojj¢¥ÏwîÜ‘ËåÚÚÏÓ§OçççSÙM)oݺõñÇ/X°@[Xxùòå   ô+ÙM¢¢¢nÞ¼II )X]‹Æ}||¼eË–ŽŽŽcÇŽ]½zõ¹çžcÕ~ýFJBHVVVLL ú€ì&ÎÎÎñññÚ²lV×¢‘Æ”””_ýU&“-Y²$%%E.—=ÚO}FÊœœggçáÇ£_Ö _ŸÉÚµkccc-ZDÝ0iñâÅ‹/ÖyëB}¨Õj%AAAô­ÁivìØA¯ÀÚµk©¿?üðCêŽ:lݺuýúõèT²ûñõõ=qâ„ñ·Â1)›7o~òÉ'Ñ©d÷ÿáååeæƒàØð²²²ÝÝÝ€ì€ì€ìd7d7d7 » » » »Ù Ù Ù ÈnÝ‹%€náСCÈn°0Ö­[‡ì ãÏ?ÿ|ì¹¾|Ç|7X8î€î$ €¹ð?þà~Ž» ;aÆôcƒÙ æß|‚Ù fß<ƒÙ æxôì+„ë<“ÊÊJL„I®«|î¹çêêêP\0Â_W¹gÏ”LÄk¯½öØ‹*;yܽiÓ&ÔX4YYY(èFÅý#ƒ›pÏwëkºÄÞø÷Àt:thôèѨè.¸óÓøöqž Xd7ëE÷º>6Åݾ€ø6Iv#¸À¬â[Ü-ï ˆoÓf·A—Øè‚ wË»‚ÛäÙøó nbÐ9‚ˆo0‡à&¸6,®kâ¹ý„ÆØ Àº1uBre7÷E÷‚\’V‰©’=»÷0˜ïd7d7&z«äþ‘4..µ³ËnÂù;©Î/™»wïÎÍÍ¥ïrùüóÏûøøh?\ºté_þò”A˜9“ððð‚‚‚¶¶6BÈÝ»w5MII ý°¬¬,<<\ûùÔ¿tgv·¶¶^¾|™’——©T*é‡>>>„‘#GnÛ¶mâĉ'N$„lݺõÙgŸ ‰¥o ;räÈO>ùdÚ´i*•jõêÕÍÍÍè$0IvÛÛÛ2$77—’››J?Ô>èþóÏ?øá‡´´4BHPPÐÑ£G ^ýõeË–566RÏ)--MIIùç?ÿÙÐаsçNt˜$» !ǧÂ:///,,,,,Œ~¨Ý ‰D$Bbbb\\\D"Ñ‹/¾èææFϡϘ1ÃÎÎN,Ïœ9óرcè$ÐA"TCááᨭ­½w¯¯»»ûš5kjkk¯\¹¢Ý½{÷¦ÿ>xð`JJJMMÝ;w¨år¹œúÃÝݽªª ¦ÊºÔÔÔaÆBd2™‡‡Gjjª‡‡‡··7óùW®\IJJ:tèPÿþý !Ï?ÿ|GGõ¯7nP÷ºqããh›3‘J¥ÁÁÁûöí £–„††îÛ·Oç š‡J¥Ò'žx‚’]VVFÿkïÞ½<¨««ûüóÏU*: LuÜM)J= ûæ›oè(×aèСãÆ{á…úöíëãããççGÿ+&&fêÔ©wïÞ3fÌo¼N¾Ùg¨JpåÊ•+W®¤ªT*£æììlúo‘HôÞ{ï±¶•€¾Ówó¹}=n™æ•ÝÈe°¼ìíy¬À ÖrÜû€åe7î# –”Ý85€Iñ÷÷×Y‚ùn0÷à~ÿý÷‘Ý`ÙÁì næ-‚‘Ý`aÁì nüV –Üø­,>¸‘Ý`Áß*ÀâƒÙ Üø­,/¸ñ[%X|p#»À‚¿U€Å7!Dô̈ˆÿ—ýû½;¨#˜?·o¨,^…ãn°<Ý`yHèƒpÔ,Ñ3#"P°,þ?üR¢ñÿ`IEND®B`‚pfm-2.0.8/doc/en/0000755000175000017500000000000012110362244011617 5ustar wimwimpfm-2.0.8/doc/en/body.html0000644000175000017500000026203211604067600013454 0ustar wimwim Postgres Forms Help File <!-- begin of toc --> <h1 class="toc"><a href="#intro" target="_self">Introduction</a></h1> <h1 class="toc"><a href="#copyright" target="_self">Copyright</a></h1> <h1 class="toc"><a href="#user-interface" target="_self">1. User interface</a></h1> <h2 class="toc"><a href="#main-window" target="_self">1.1. The main window</a></h2> <h2 class="toc"><a href="#help" target="_self">1.2. On-line help</a></h2> <h2 class="toc"><a href="#context" target="_self">1.3. Context menu</a></h2> <h2 class="toc"><a href="#printing" target="_self">1.4. Printing</a></h2> <h2 class="toc"><a href="#shortcuts" target="_self">1.5. Keyboard operation</a></h2> <h2 class="toc"><a href="#license" target="_self">1.6. Displaying the license</a></h2> <h2 class="toc"><a href="#about" target="_self">1.7. About pfm</a></h2> <h1 class="toc"><a href="#database" target="_self">2. Working with data bases</a></h1> <h2 class="toc"><a href="#opendb" target="_self">2.1. Opening a data base</a></h2> <h2 class="toc"><a href="#check" target="_self">2.2. Checking the pfm_* tables</a></h2> <h2 class="toc"><a href="#closedb" target="_self">2.3. Close data base</a></h2> <h2 class="toc"><a href="#install" target="_self">2.4. Install pfm_* tables</a></h2> <h2 class="toc"><a href="#exampledb" target="_self">2.5. Install example database</a></h2> <h1 class="toc"><a href="#reports-queries" target="_self">3. Running SQL</a></h1> <h2 class="toc"><a href="#run-sql" target="_self">3.1. SQL terminal</a></h2> <h2 class="toc"><a href="#import-sql" target="_self">3.2. Import SQL-script</a></h2> <h1 class="toc"><a href="#options" target="_self">4. Configuring pfm</a></h1> <h1 class="toc"><a href="#forms" target="_self">5. Working with forms</a></h1> <h2 class="toc"><a href="#open-form" target="_self">5.1. Opening a form</a></h2> <h2 class="toc"><a href="#form-window" target="_self">5.2. The form window</a></h2> <h2 class="toc"><a href="#update" target="_self">5.3. Update record</a></h2> <h2 class="toc"><a href="#add" target="_self">5.4. Add record</a></h2> <h2 class="toc"><a href="#delete" target="_self">5.5. Delete record</a></h2> <h2 class="toc"><a href="#status" target="_self">5.6. Status field</a></h2> <h2 class="toc"><a href="#links" target="_self">5.7. Links to other tables</a></h2> <h2 class="toc"><a href="#fillingout" target="_self">5.8. Filling out a form</a></h2> <h2 class="toc"><a href="#views" target="_self">5.9. Views</a></h2> <h2 class="toc"><a href="#search-record" target="_self">5.10. Search record in internal buffer</a></h2> <h2 class="toc"><a href="#limit-clause" target="_self">5.11. Forms with a LIMIT-clause in their definition</a></h2> <h2 class="toc"><a href="#form-help" target="_self">5.12. Help for form</a></h2> <h1 class="toc"><a href="#definition" target="_self">6. The pfm_* tables</a></h1> <h2 class="toc"><a href="#pfm_form" target="_self">6.1. pfm_form</a></h2> <h2 class="toc"><a href="#pfm_attribute" target="_self">6.2. pfm_attribute</a></h2> <h2 class="toc"><a href="#pfm_value_list" target="_self">6.3. pfm_value_list</a></h2> <h2 class="toc"><a href="#pfm_value" target="_self">6.4. pfm_value</a></h2> <h2 class="toc"><a href="#pfm_link" target="_self">6.5. pfm_link</a></h2> <h2 class="toc"><a href="#pfm_report" target="_self">6.6. pfm_report</a></h2> <h2 class="toc"><a href="#pfm_section" target="_self">6.7. pfm_section</a></h2> <h2 class="toc"><a href="#schema" target="_self">6.8. The links between the pfm_* tables</a></h2> <h1 class="toc"><a href="#design" target="_self">7. Designing a form</a></h1> <h2 class="toc"><a href="#modify_form" target="_self">7.1. Modifying or adding a form</a></h2> <h2 class="toc"><a href="#define_attributes" target="_self">7.2. Defining a form's attributes</a></h2> <h2 class="toc"><a href="#define_links" target="_self">7.3. Defining links between forms</a></h2> <h2 class="toc"><a href="#define_value_lists" target="_self">7.4. Defining value lists</a></h2> <h1 class="toc"><a href="#reports" target="_self">8. Reports</a></h1> <h2 class="toc"><a href="#run-report" target="_self">8.1. Running a report</a></h2> <h2 class="toc"><a href="#design-data" target="_self">8.2. Designing the data for the report</a></h2> <h2 class="toc"><a href="#design-layout" target="_self">8.3. Designing the report layout</a></h2> <h1 class="toc"><a href="#PostgreSQL" target="_self">9. Hints for using PostgreSQL</a></h1> <h2 class="toc"><a href="#encoding" target="_self">9.1. Character encoding</a></h2> <h2 class="toc"><a href="#sql-hints" target="_self">9.2. Some general hints about SQL</a></h2> <h1 class="toc"><a href="#sample" target="_self">10. The example databases</a></h1> <h2 class="toc"><a href="#addressbook" target="_self">10.1. The addressbook database</a></h2> <h2 class="toc"><a href="#customerdb" target="_self">10.2. The customer database</a></h2> <!-- end of toc -->

Introduction

Postgres Forms (pfm) is a client application with a graphical user interface for the PostgreSQL data base server. It enables the user:

  • to design forms for adding, modifying or deleting records of data base tables;
  • to design links (one to many relationships) from one form to another, for navigating in the data base;
  • to design and to generate reports based on the data in a table or view;
  • to edit and to execute SQL statements.

Postgres Forms is implemented in Tcl/Tk, but there is no need for the user to program anything in Tcl/Tk. The user has to use SQL for creating tables and views and for designing forms and links.

Postgres Forms makes no attempt to hide the underlying SQL. On the contrary, in most cases, it shows both the SQL statements it sends to the PostgreSQL server and the results it gets back.

Postgres Forms has been designed and tested with version 8.5 of Tcl/Tk and with version 8.1 of the PostgreSQL database server. It has also been tested with version 9.0 of PostgreSQL. For more information on Tcl/Tk and PostgreSQL look at http://www.tcl.tk and http://www.postgresql.org respectively.

The pfm distribution includes a tclkit 8.5.9 for the following architectures:

  • linux-x86
  • windows-32

For these architectures there is no need for the user to install Tcl/Tk separately.

Postgres Forms needs one of the Tcl interface packages pgtcl, pgtclng or pgintcl to communicate with PostgreSQL database server. All three can be obtained from http://pgfoundry.org.

pgintcl-3.2.1 is included in the pfm distribution.

Copyright

Postgres Forms (pfm) is a client application for PostgreSQL.

Copyright © 2004-2011 Willem Herremans

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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

A copy of the GNU General Public License can be displayed with the menu item 'License' under 'Help'.

The home page of pfm can be found at

http://pgfoundry.org/projects/pfm

There you can report bugs, request new features and get support.

1. User interface

1.1. The main window

The following figure shows the main window of Postgres Forms (pfm):

Main window

This is a tabbed window with 3 tabs:

  • Forms: This tab displays the list of forms for normal use.
  • Reports: This tab displays the list of reports that can be generated
  • Design: This tab displays the list of forms for the pfm_* tables. They can be used to design forms, reports and links. See section "The pfm_* tables" for details.

1.2. On-line help

You can invoke the on-line help by selecting "Help file" in the "Help" menu on the main window.

Pressing F1 on the main window has the same effect.

The help file is displayed in the web browser of your choice. See 4. Configuring pfm for how to choose a web browser.

1.3. Context menu

By clicking the right mouse button the "context menu" pops up.

It is a classic "edit menu" with menu items: Copy, Cut and Paste.

You can use it to copy text to/from the clipboard.

This menu is not restricted to the main window, but works on all the windows of pfm.

1.4. Printing

Printing can be invoked from the 'Run SQL' window for printing the text in the output text area (menu Output -> Print) or from any 'text window' (menu Text -> Print).

How printing is handled completely depends on the value of the 'printcmd' option. See help for option 'printcmd': invoke menu 'Tools -> Options' then press 'Help' button of option 'printcmd'.

The 'printcmd' calls an application that accepts plain text as input. It can be either a printer program that directly sends the text to a printer, or a text editor or word processor that accepts the text generated by pfm as input and that offers you the possibility to choose things such as font, font size, margins, page orientation etc. before sending it to a printer from there on.

The command may contain parameters for which you will be prompted to provide a value when the command is called.

1.5. Keyboard operation

It is possible to operate pfm without using the mouse. Not all keyboard procedures are explicitly mentioned in this manual because they follow some general rules which make it very easy to know them.

  1. The menus on the menu bar of any window have one underlined character. Pressing "Alt-<that character>" invokes the menu.
  2. The menu items can also have an underlined character. Pressing that character when the menu is displayed, selects the corresponding menu item.
  3. The menu items can have a so called "accelerator" or "shortcut" displayed in the second column of the menu. Pressing that key causes the menu item to be invoked even when the menu is not displayed.
  4. There can be several widgets like text entries, buttons and listboxes on a window that can receive keyboard input. At any time, there is only one widget that receives the keyboard input. That widget has the so called "input focus". The widget that has input focus is shown with a black frame border.

    Clicking with the mouse on a widget moves the input focus to that widget. The widgets on a window that can receive input focus are arranged in a circular list. Pressing the Tab key moves the input focus forward along the list. Pressing Shift-Tab moves the input focus backward along the list.

  5. Buttons can have an underlined character. Pressing "Alt-<that character>" has the same effect as pressing that button.
  6. Pressing Return on when a button has the input focus, has the same effect as clicking on the button with the left mouse button.
  7. The Escape key usually has the effect of closing the window which has input focus without doing anything else. The only exceptions are:
    • Pressing Escape when adding or updating a record, aborts the operation, but keeps the "Form" window.
    • Pressing Escape on a "form tab"
      • either closes that tab if it is the most right tab;
      • or "unlocks" that tab and deletes all tabs at its right.

1.6. Displaying the license

Selecting "License" in the "Help" menu on the main window causes the "GNU General Public License" to be displayed.

1.7. About pfm

Selecting "About" in the "Help" menu on the main window displays some general information about postgres forms, such as: version, copyright, contact information, Tcl/Tk run-time environment, interface to PostgreSQL, installation directory.

2. Working with data bases

2.1. Opening a data base

After selecting 'Open...', from the Database menu on the main window, you are prompted to enter the parameters for opening a data base:

  • host: Host that provides the database server you want to connect to. Leave empty if the database server resides on the computer from which you are running pfm. If a non-zero-length string is specified, TCP/IP communication is used. You can either specify a name or an IP address. Using a name causes a hostname look-up.
  • port : Port number to connect to at the server host, or socket filename extension for Unix-domain connections. The default is 5432.
  • dbname : The database name.
  • user : User name to connect as.
  • password : Password to be used if the server demands password authentication.

Notes:

  1. When a database is opened, pfm is connected twice with the PostgreSQL database server:
    • via one of the Tcl interfaces Pgtcl or pgin.tcl for all form and report operations;
    • via psql for the "Run SQL" feature.
  2. You are only prompted for a password if the "usePGPASSWORD" option is 'on'. In that case, the password you enter is stored in the environment variable PGPASSWORD during the short time between pressing 'OK' on the Open database window and the actual connecting of psql with the database server. For connecting via the Tcl interface Pgtcl or pgin.tcl, the connection parameter "password" is used instead of the environment variable.
  3. If the "usePGPASSWORD" option is 'off', you are not prompted for a password and the environment variable PGPASSWORD is not used. In that case you need a properly configured password file. See subsection "The password file" in the "libpq C-library" chapter of the PostgreSQL documentation. On UNIX platforms the password file is

    ~/.pgpass;

    on Windows platforms the password file is

    APPDATA\postgresql\pgpass.conf

    where APPDATA is the user's application data directory, e.g.

    C:\Documents and Settings\'username'\Application Data

  4. If the "usePGPASSWORD" option is 'off', pfm reads the password file to find the password, which is then passed to the database server via the "password" connection parameter, when connecting via the Tcl interface Pgtcl or pgin.tcl. pfm supports the "\:" and "\\" sequences to escape ":" and "\". For connecting to the database server via psql, psql reads the password file independently from pfm.
  5. According to the PostgreSQL documentation, the use of the environment variable PGPASSWORD is deprecated for security reasons.

Default values for the parameters host, port, user and dbname are read from the user's options file (see section 4. Configuring pfm).

All parameters can be typed directly on the window that is displayed. The 'dbname' can also be selected from a list 'dblist' defined in the user's options file.

After a database has been successfully opened, its name is stored in the 'dbname' option and added to the 'dblist' option if it is not in the list yet.

2.2. Checking the pfm_* tables

When you open a database, pfm checks for the presence of the pfm_* tables.

If pfm does not find the pfm_* tables, you get a hint that you can install them using the 'Tools -> Install pfm_* tables' or the 'Tools -> Install example database ...' menus.

On a database without pfm_* tables, pfm only allows you to execute SQL statements using the RunSQL feature.

On a database with pfm_* tables, pfm allows you to also design and use forms and reports.

When you open a database that was previously managed by an older version of pfm, you will be asked whether you want to convert the pfm_* tables to the new format.

If you choose "yes", the pfm_* tables are converted to the format compatible with the new version of pfm.

Note:

There is no way back: i.e. there is no automatic conversion from the new to the old format. So, to be sure, it is recommended to first make a backup of your database using PostgreSQL's "pg_dump" before answering "yes" to this question.

If you choose "no", the database is opened but not modified in any way. Since the format of the pfm_tables is not compatible with the new version of pfm, some features like defining and using forms and reports may not work properly, but you will be able to execute SQL statements using the RunSQL feature of pfm.

2.3. Close data base

By selecting the Close menu item from the Database menu on the main window, you close the currently open data base. The list-boxes on the main window become empty.

2.4. Install pfm_* tables

To install the pfm_* tables in the currently opened database, select 'install pfm_* tables' from the Tools menu on the main window.

2.5. Install example database

There are two example databases included in the pfm package:

  • the address book data base; and
  • the customer data base.

Using 'Tools -> Install example database', you can install one of these example databases in the currently opened database. This database should be completely empty. It should not even contain the pfm_* tables.

The easiest way to obtain a completely empty database is to create a new one:

  • Open a database that you can access in pfm.
  • Start the "Run SQL" feature with "SQL" on the main window.
  • Type and run an SQL statement like:

    CREATE DATABASE exampledb;

    This assumes that you are known to PostgreSQL as a user who has the right to create databases.

  • Close the database from which you issued this command and open the database you have created in pfm. Then activate the 'Tools -> Install example database ...' menu.

See section 10. The example databases for more details on the structure of the example databases.

The example databases only contain characters of the LATIN1 character set. So, LATIN1 encoding is sufficient. UNICODE is more than sufficient. See section 9.2. Character encoding for more details on character encoding.

3. Running SQL

3.1. SQL terminal

When you invoke the menu SQL on the main window's menu bar, the following window is opened:

SQL window

It has two text areas, one for the SQL statements, another for the output.

This tool is in fact nothing more than a GUI-frontend for the psql interactive terminal that is included in PostgreSQL.

When you press the [Run] button, all the text present in the SQL statement text area is sent to psql.

Don't forget to end SQL statements with ";". If you forget this, the statement is not executed by psql until you type ";" and press [Run].

Sometimes, the Run SQL feature appears to be dead, i.e. it is not responding to commands anymore. In many cases that is due to mismatched parentheses or quotes. E.g. if you enter

SELECT * FROM pfm_form WHERE (name='pfm_attribute';

psql does not respond until it gets a ')'. If you run psql in an xterm, it displays a prompt ending with '(>', indicating that there is still an unmatched '('. In pfm however, psql does not display this prompt, with the result that it does not seem to respond. If you cannot guess what is wrong, you can close and reopen the database to recover from this frustrating situation.

Apart from SQL statements, you can also enter the psql "\" commands. E.g.

  • \? : returns the psql on-line help on the "\" command;
  • \h : returns the list of SQL statements for which psql provides on-line help;
  • \h <SQL statement> : returns the on-line help for the SQL statement.

These help functions can also be invoked from the Help menu on the "Run SQL" window.

Every time you press [Run] the commands in the SQL area are stored in the command history. You can scroll through the command history using the [Previous] end [Next] buttons.

Note:

Alt-p and Cntrl-Up are alternatives for the [Previous] button.

Alt-n and Cntrl-Down are alternatives for the [Next] button.

Since version 2.0.0, you need not press the Run button if you end an SQL statement with ';<Return>'. Similarly, if the command you are typing begins with a '\', the command is sent to psql when you press '<Return>'.

The output area is not cleared when another run is done, i.e. the output of a new run is appended to the text already present in the output area. You can clear the output area explicitly by invoking the menu Output -> Clear.

3.2. Import SQL-script

You can also import SQL statements from a file by selecting "Import SQL from file" in the SQL menu on the "Run SQL" window.

After having selected the file to be imported, you are asked to select the character encoding. The default is the same as your system's default encoding. Usually, that is OK, but if you know that the file to be imported was made with another character encoding, you can select it here.

You are also asked whether to offer the file to psql via the '\i' command or to import the file into the SQL window. For large files it is recommended to offer the file to psql. pfm sometimes hangs for no obvious reason when large files are run from the SQL window.

Notes:

  • SQL files that are imported in either way are automatically converted to character encoding UTF-8. This is necessary because pfm uses clientencoding UNICODE. See 9.2. Character encoding for more details.
  • The original SQL file is not touched by this conversion. If you choose to 'offer the file to psql', the result of the conversion to UTF-8 is stored in a temporary file, which is then offered to psql.
  • SQL files that are offered to psql by typing a '\i' command in the SQL window are not converted to UTF-8. So, if your system's standard character encoding is not UTF-8, always invoke the '\i' command via the [Import SQL] button.
  • SQL files containing an SQL statement "SET client_encoding" override the clientencoding set by pfm. Such SQL files should not be run via the "Import SQL" feature of pfm. Just run them outside pfm (or remove the "SET client_encoding" statement).

The text in the output area can be printed or saved to a file, by selecting respectively "Print" or "Save" in the "Output" menu on the "Run SQL" window.

4. Configuring pfm

Postgres Forms has a number of options that are stored in a text file. On UNIX platforms that file is

~/.pfm2

On Windows platforms that file is

APPDATA\pfm\pfm2.conf

where APPDATA is the user's application dirctory, e.g.

C:\Documents and Settings\'username'\Application Data

These options can be viewed and modified by selecting the Tools -> Options menu item:

Options window

For each option you can get dedicated on-line help by clicking the Help button at the right.

If you start Postgres Forms for the first time, the options have their default values. On later occasions, the options are read from the options file.

5. Working with forms

5.1. Opening a form

When a form is opened, by pressing the [Open] button or the Return key on the main window, you will see a window such as this:

Open form window

This window shows the SQL SELECT statement that will be executed to load the data into the form.

You can modify the 'WHERE' and 'ORDER BY' clauses.

The 'paste attribute Name' and 'past attribute Value' menus may help you to do so.

After pressing the 'Run' button, the SQL SELECT statement is executed, the form is displayed in a new tab of the current window and the data resulting from the SQL SELECT statement are loaded into the form's internal buffer.

5.2. The form window

The form window looks like this:

Form window

The upper text field shows some information on how the form was opened. This will become more interesting when you have followed one or more links.

The lower text field displays the SQL statements that were sent to the PostgreSQL server. On this example, it shows the SQL statement that was issued to load the data in to the form's internal buffer.

The middle part of the window is the actual form. It shows:

  • An 'n/m' indicator where n = index of current record in the internal buffer, m = index of last record in the internal buffer.

    Note:

    If a LIMIT clause has been specified in the definition of the form, the behaviour is slightly different from the one described above. See 5.11. Forms with a LIMIT-clause in their definition for details.

  • Four buttons with arrow icons for moving through the form's internal buffer. From left to right the functions of these buttons are: go to first record, go to previous record, go to next record, go to last record. The same functions are also bound to the keyboard keys: Home, PageUp, PageDownn, End.
  • A label in blue text with the name of the form.
  • A label with the status of the currently displayed record. Immediately after opening a form, the status of all rercords is 'Not modified'. See 5.6. Status field for other possible status values.
  • The names and values of the form attributes. Until you start an 'update' or 'add' operation the attribute values are read only. By clicking on an attribute's 'Expand' button, you open a text window that is more convenient to view larger, multi-line texts.
  • The buttons 'Update', 'Add' and 'Delete' that start 'Update', 'Add' or 'Delete' operations.

The right side of the form window shows the link buttons which can be used to follow 'one-to-many' or 'many-to-one' relationships to other forms. See 5.7. Links to other tables for more details.

5.3. Update record

After you press the [Update] button, Postgres Forms reloads the current record.

If that is successful, the form's table attributes become modifiable, the buttons [OK] and [Cancel] are displayed and all the other buttons on the form are deactivated. The form's status becomes "Updating".

The calculated fields and the attributes of tables other than the form's main table are read-only and do not become modifiable.

If you press [OK]:

  • A 'START TRANSACTION' is issued.
  • A SELECT ... FOR UPDATE is done on the current record.
  • It is checked that the record has not been modified in the mean time by another user: the values returned by the SELECT FOR UPDATE should match the values in the internal buffer.
  • The new values for the record are stored in the data base by sending an 'UPDATE' and a 'COMMIT' to PostgreSQL. This ends the transaction and releases the new record values to the data base.
  • the record's status field becomes "Updated" and the modified record is reloaded from the database. The result is that the record's read-only data, such as calculated fields and attributes of other than the main table are refreshed, taking into account the modified data.
  • The new values are also stored in the internal buffer. This means that even if the modifications are such that the record would no longer be selected by the original query, it stays in the internal buffer until the form is closed.
  • Similarly, the record keeps its position in the internal buffer, even if that is no longer the position it would get according to the initial "ORDER BY" clause.
  • the SQL UPDATE statement, the SQL COMMIT and their results, are displayed in the text field at the bottom of the form window.

Note:

If a LIMIT clause has been specified in the definition of the form, the behaviour is slightly different from the one described above. See 5.11. Forms with a LIMIT-clause in their definition for details.

If you press [Cancel], the modifications are discarded.

While a record is displayed on your screen, it may be deleted by another user. This can happen :

  • after you have opened the form and before you have pressed [Update];
  • after you have pressed [Update] and before you have pressed [OK].

In the first case, this is noticed by Postgres Forms when you press the [Update] button. In the second case it is noticed when you press the [OK] button. In both cases, the record is marked as deleted and the update operation is cancelled.

Similarly, while a record is displayed on your screen, it may be modified by another user. This can happen:

  • after the you have opened the form and before you have pressed [Update];
  • after you have pressed [Update] and before you have pressed [OK].

In the first case, the 'reload record', which is executed when you press [Update], refreshes the screen and the update proceeds as described above.

In the second case, Postgres Forms notices that the record has been modified by another user when you press [OK]. Then, the update operation is cancelled, and you are notified.

5.4. Add record

After you have pressed the [Add] button, the displayed record's attributes become modifiable in the same way as for "update record". The attributes for which a default value has been defined in pfm_attribute now get their default value on the screen. The buttons [OK] and [Cancel] are displayed and all the other buttons on the form are deactivated. The form's status becomes "Adding".

If you press [OK], the new record is stored, both in the data base and in the form's internal buffer. In the form's internal buffer, it becomes the last record. This means that even if the new record's values are such that the record would not be selected by original query, it stays in the internal buffer until the form is closed.

Similarly, the record gets the last position in the internal buffer, even if the that is not the position it would get according to the initial "ORDER BY" clause.

If you press [Cancel], the new record is discarded, i.e. it is neither stored in the data base, nor in the form's internal buffer.

After pressing [OK]:

  • the SQL INSERT statement, and its result, are displayed in a the text field at the bottom of the form window;
  • the record's status field becomes "Added";
  • the record is reloaded so that the read-only data, such as calculated fields and attributes of other than the main table, are also displayed for the newly added record;
  • the 'last record number' in the internal buffer is incremented with 1, which is shown on the 'current record number'/'last record number' display.
  • if the insert fails, the values entered by you stay on the screen, the status field becomes "Not added" and the current record number becomes 'last record nr + 1'.

Note:

If a LIMIT clause has been specified in the definition of the form, the behaviour is slightly different from the one described above. See 5.11. Forms with a LIMIT-clause in their definition for details.

5.5. Delete record

If you press the [Delete] button, the current record is deleted from the data base. It is not deleted from the internal buffer, but its attributes are all put to an empty string and its status field is put to "Deleted".

Note:

If a LIMIT clause has been specified in the definition of the form, the behaviour is slightly different from the one described above. See 5.11. Forms with a LIMIT-clause in their definition for details.

5.6. Status field

The status field is a property of a record in the form's internal buffer. It is kept in the internal buffer until the form is closed and it is displayed on the form. The possible values are:

  • Not modified : the record was not modified since the form was opened;
  • Updated : the record has been updated since the form was opened;
  • Added : the recorded was added since the form was opened;
  • Deleted : the record has been deleted since the form was opened;
  • After last : the current record pointer is 1 after the last record in the internal buffer.
  • Not added : the last "Add" operation has failed and the current record pointer is 1 after the last record in the internal buffer.
  • Updating : You have pressed the [Update] button. pfm is waiting for input.
  • Adding : You have pressed the [Add] button. pfm is waiting for input.

Whenever there is a one-to-many relationship between two database tables, you can define links between the corresponding forms.

When a form is displayed, all the links originating from that form, are displayed as buttons in the 'links' pane.

So, links are navigation tools that allow you to follow the one-to-many relationships in a database.

Note that you can define a link in the "one-to-many" direction and another one in the "many-to-one" direction of a relationship.

Pressing a link button, brings you to another form in which the records, related to the originally displayed record, are loaded.

This form is opened in a new tab of the current window. By switching tabs you can move back to the form and record from which the link has originated.

Every time you follow a link, another tab is created. So, after having followed one or more links, you get a chain of records in which each record is represented by a tab on the window.

All tabs in the chain, except the last one, are 'locked'. That means that no operations are allowed on them.

You can 'unlock' a 'locked tab', by pressing the 'unlock tab' button or the 'Escape' key. That also deletes all tabs to the right of it.

Links are defined in the pfm_link table. See 6.5. pfm_link for details.

Note:

Altough "many-to-many" relationships do exist in the real world, they are implemented as two "one-to-many" relationships, using an auxilliary table.

E.g. if you have a database containing tables for persons and groups of persons, you can have a many-to-many relationship between persons and groups: a person can be member of more than one group, and a group can contain more than one person.

To implement such a database, you would not only need tables "person" and "group", but also a table "membership". The "many-to-many" relationship between "person" and "group" is then implemented as a "one-to-many" relationship between "person" and "membership" and another "one-to-many" relationship between "group" and "membership".

So, to find all the members of a group, you first follow the one-to-many relationship to the membership table, and from there the many-to-one relationship to the person table.

5.8. Filling out a form

After pressing [Update] or [Add], you can fill out a form, i.e. provide values for the table's attributes.

You can provide values for the attributes in one of the following ways, depending on the attribute's "type of get" (see section 6.2. pfm_attribute):

  • tgDirect: you directly type a value for the attribute;
  • tgExpression: you can type an expression which is first evaluated before it is stored as a new value for the attribute (e.g. 1.0/7.75);
  • tgList: you select a value by means of a list-box containing a predefined list of possible values;
  • tgLink: you select a value by means of a list-box containing a list which is the result from a query on another table.
  • tgReadOnly: the attribute cannot be modified.

Notes:

  • When selecting a value from a list, you can specify a search string at the top of the list box. Every time you press the [Next] button, the next list item matching the search string is selected. The search is not case sensitive.
  • When "type of get" is tgLink or tgList,you normally select a value from the list, but by pressing the [Expand] button instead of opening the listbox, a new window is opened in which you can directly type a value.

5.9. Views

For a "view" it is possible to define a form in Postgres Forms in the same way as for a "table", but it is not possible to update a view.

The buttons [Update], [Add] and [Delete] are absent on a form for a view.

5.10. Search record in internal buffer

The "Search" menu on the "Form" window contains a list of the form's attributes. After having selected an attribute, you get a search bar in the form window in which you can specify a value for that attribute.

You can then search the internal buffer for the next record with the specified value for that attribute.

5.11. Forms with a LIMIT-clause in their definition

The designer of the form can specify a LIMIT-clause to limit the number of records that is loaded into the form's internal buffer. This makes it possible to handle large tables without causing excessive memory usage. It is almost, but not entirely transparent for the user of the form.

As an example, we consider a form for which 'sqllimit = 100' has been specified in pfm_form and for which there are 523 records in the database.

When the form is opened, not all 523, but only the first 100 records are loaded into the internal buffer. Since pfm does not know how many records have not been loaded, it displays the the record number for records as '1/?', '2/?', ... '100/?'.

When you move beyond record 100, the internal buffer is first cleared and then reloaded with the next 100 records.

The same thing happens again when you move beyond records 200, 300 etc...

Finally, when moving beyond record 500, pfm only finds 23 instead of 100 records. Then it assumes that 523 is the last record and displays the record numbers as '501/523', '502/523', ... '523/523'.

When moving backwards through the records in the internal buffer, e.g. when moving from record 501 to record 500, the internal buffer is first cleared and then reloaded with the records 401 through 500.

Also searching the internal buffer with the Search menu, causes pfm to load another set of 100 records when required.

There are a few cases where this automatic clearing and reloading of the internal buffer has some confusing effects.

  • If, after having done some updates on records in the range 101 through 200, you move to a record outside this range and then come back to the records 101 through 200, you will notice that the status of the updated records is no longer "Updated". Every time the internal buffer is cleared to load another set of records, the status of the records is forgotten.
  • If you add records, when records 101 through 200 are loaded in the internal buffer, they will get record numbers 201, 202, ... etc. Then when you move beyond the last added record, the internal buffer is first cleared and then reloaded with records 201 through 300. The added records will most likely not be at the positions 201, 202, ... etc. Instead, they will be at the position determined by the "ORDER BY"-clause with which the form was opened.
  • If you delete e.g 4 records, when records 201 through 300 are loaded in the internal buffer and then move from record 300 to 301, the internal buffer is cleared and then reloaded with records 301 through 400. However, since 4 records have been deleted from the database in the mean time, the records that without the deletion would have been at positions 301, 302, 303 and 304 will now come at positions 297, 298, 299 and 300. So, although you think you have been stepping through all the records by doing "next record", "next record" ... you will have skipped 4 records.

5.12. Help for form

By invoking the "Help" menu on the "Form" window you get help information for filling out the currently displayed form.

This is the text that is stored in the "help" attribute of the table "pfm_form".

For the pfm_* forms, this help text is provided by pfm, but for the other forms that information has to be provided by the designer (probably you) of the form. See also section 6.1. pfm_form

6. The pfm_* tables

Per data base, pfm uses tables with a name starting with "pfm_" to store the definition of forms and reports.

6.1. pfm_form

A form allows the user to administer the data of just one table. This table is henceforth referred to as "the form's main table".

However, a form also has an SQL SELECT statement, which generates the data that are displayed on it.

In the simplest case the SQL SELECT statement is just:

SELECT <attributes of main table> FROM <main table>

In that case, the data which can be administered and the data which are displayed on the form are the same.

In more complex cases, the <main table> can be JOINED with other tables, which makes it possible to display data of other related tables as well. These data cannot be modified by means of the form.

The table "pfm_form" has the following attributes:

  • name : the name of the form (usually equal to the name of the form's table);
  • tablename : the name of the form's main table;
  • pkey : the primary key of the form's main table, which may consist of more than one attribute. In that case pkey is a SPACE separated list of the attributes of the primary key;

    Note:

    If pkey is empty, or if pkey does not uniquely determine a record, all Update, Add and Delete operations will fail.

  • sqlselect : the attribute list of the form's SQL SELECT statement, not including the word 'SELECT';
  • sqlfrom : the FROM clause of the form's SQL SELECT statement, not including the word 'FROM';
  • groupby : an optional 'GROUP BY' clause, not including the words 'GROUP BY';
  • sqlorderby : an optional 'ORDER BY' clause, not including the words 'ORDER BY';
  • sqllimit : an optional 'LIMIT' clause, only specifying the limit value as a positive integer;

    Notes:

    • This enables the designer of the form to avoid excessive memory usage by limiting the number of records loaded in the form's internal buffer. This may be useful for handling large tables.
    • If sqllimit is a positive integer, a

      LIMIT sqllimit OFFSET 0

      is added to the form's SELECT when opening the form.

      This means that only 'sqllimit' records are loaded into the form's internal buffer. When the user moves beyond the last record in the internal buffer, the internal buffer is first cleared and then reloaded with the next 'sqllimit' records by re-executing the form's SELECT but now with another OFFSET in the LIMIT clause.

    • If sqllimit is an empty string, no LIMIT clause is appended to the form's SELECT.
    • Always specify an 'sqlorderby' if you specify an 'sqllimit'. See PostgreSQL documentation of LIMIT-clause in SELECT statement for more details.
  • view : a boolean indicating whether or not the "tablename" is a view;
  • showform : a boolean indicating whether the form is shown in "normal mode" (showform = 'true') or in "design mode" (showform = 'false'). Typically, showform is set 'true' for user defined forms and 'false' for the predefined pfm_* forms.
  • help : a text which is displayed when the user selects the Help menu on the form.

The form's main table is defined by tablename. Only the data of that table can be administered by using the form.

All the data generated by the form's SQL SELECT statement can be displayed on the form. The SQL SELECT statement is defined by:

  • the sqlselect, sqlfrom, groupby, sqlorderby and sqllimit attributes of pfm_form; and
  • the optional WHERE and ORDER BY clauses provided by the user when opening the form.

Note:

The WHERE clause provided by the user when opening the form, becomes a HAVING clause, if there is a GROUP BY clause.

The following rules should be observed when filling out sqlselect and sqlfrom:

  1. The form's main table must appear in 'sqlfrom', and must not be aliased. Similarly, the main table's attributes appearing in 'sqlselect' must not be aliased. The other tables appearing in the 'sqlfrom' may be aliased.
  2. The fields appearing in 'sqlselect' must have a unique, simple name without the need to precede them with a tablename. So, calculated fields must be given a name by aliasing and attributes of tables other than the main table may need to be aliased in order to have a unique, simple name.
  3. The 'sqlfrom' is either just the name of the form's main table, or it is a JOIN clause in which 1 of the tables is the form's main table. Several join clauses can be nested in order to involve more than 2 tables. See examples below.

Example 1: the SQL SELECT for the person form of the addressbook database

tablename:
    person

pkey:
    id

sqlselect:
    id, christian_name, name, street, town, "ZIPcode",
    country, category, description

sqlfrom:
    person

groupby:
    -

Example 2: the SQL SELECT for the memberlist form of the addressbook database

tablename:
    memberlist

pkey:
    group person

sqlselect:
    memberlist."group", memberlist.person, p.christian_name, p.name

sqlfrom:
    memberlist LEFT OUTER JOIN person p ON (p.id = memberlist.person)

groupby:
    -

6.2. pfm_attribute

The table "pfm_attribute" defines all the properties of form attributes.

It has the following attributes:

  • form : the "name" of the form to which the attribute belongs;
  • attribute : the name of the attribute; this must be equal to the name of the corresponding attribute of the form's SQL SELECT statement;
  • typeofattrib : the type of attribute:
    • taQuoted: the value provided by the user is put between single quotes when it is transferred to SQL UPDATE or INSERT statements;
    • taNotQuoted: the value provided by the user is not quoted when it is transferred to SQL UPDATE or INSERT statements.

    Hint:

    In general, all attribute values must be quoted, exept the values or expressions for numeric attributes.

  • typeofget: defines how the user provides a value for the attribute; possible values are:
    • tgDirect: the user types the value directly;
    • tgExpression: the user types an expression which is first evaluated before it is passed to SQL UPDATE or INSERT;

      Note:

      Even with tgDirect it is possible to enter an expression as new value for an attribute, but then the expression is evaluated by PostgreSQL whereas with tgExpression, the expression is first evaluated by Tcl before the SQL statement is sent to PostgreSQL.

    • tgList: the user selects a value by means of a list box containing a list of values defined in table "pfm_value";
    • tgLink: the user selects a value by means of a list box containing a list of values which is the result from a query on another table.
    • tgReadOnly: this attribute cannot be modified by the user.

      Note:

      All calculated attributes and all attributes from tables other than the form's main table should be declared 'read-only'. If this rule is not observed, the Add and Update operations on this form will fail.

  • sqlselect: the SQL SELECT statement which is used to fill the list box with possible values for the attribute (only meaningful if typeofget = tgLink).

    Note :

    The sqlselect may return more than 1 attribute. If so, all the attributes are displayed in the list-box, but only the first one is used for updating the attribute.

  • valuelist : the "name" of the value list defined in table "pfm_value_list" (only meaningful if typeofget = tgList);
  • nr: a number which determines the order in which attributes are displayed on the form;
  • default: a default value for this attribute which is used when adding a record. If the first character is an '=' sign, the following characters should be an SQL SELECT statement which returns just one value.

    Example:

    default: =SELECT nextval('seq_person_id')

    In this example the default value is the next value of the sequenece 'seq_person_id'.

6.3. pfm_value_list

The table "pfm_value_list" contains all the value lists of all the forms.

Its only attribute is

  • name : a name uniquely identifying the value list.

6.4. pfm_value

The table "pfm_value" contains all the values of the lists defined in pfm_value_list.

It has the following attributes:

  • valuelist : the name of the valuelist to which this value belongs
  • value : a character string;
  • description : a description of the value.

A link is a navigation tool which allows you to follow a "one-to-many" or "many-to-one" relationship from one form to another.

Every link is stored as a record in the pfm_link table, which has the following attributes:

  • linkname : the name of the link, which is displayed on a link button on the "fromform";
  • fromform : the name of the form from which the link originates;
  • toform : the name of the form to which the link leads;
  • sqlwhere : the "WHERE"-clause which is used to open the "toform" and in which the value of an attribute of the "fromform" may be represented by $(attrib-x), where 'attrib-x' is the name of the attribute;
  • orderby : an 'order by' clause which determines the order of the records in the 'toform';
  • displayattrib : a space separated list of attributes of the 'fromform', the value of which is displayed on the 'toform' to remind the user from which record the link originated.

Note:

Postgres Forms does not provide any checks to safeguard the referential integrity of the data base in case of updates or deletions. However, PostgreSQL provides these functions as 'foreign key' table constraints (see PostgreSQL documentation).

6.6. pfm_report

The table pfm_report defines all the reports for the current data base.

pfm_report has the following attributes:

  • name: the name of the report. This is the name that appears in the selection list of the "Run Report" function.
  • description: free text describing the purpose of the report in more detail.
  • sqlselect: an SQL SELECT statement that generates the data for the report.

The sqlselect may contain one or more parameters for which a value is requested at "Run report" time. A parameter in the sqlwhere must be formatted as $(parameter_name).

Example:

sqlselect:

    SELECT g.name AS "group", g.description, p.id, p.name,
           p.christian_name, p.street, p."ZIPcode", p.town, p.country
    FROM "group" g
       LEFT JOIN memberlist m ON g.name = m."group"
       LEFT JOIN person p ON m.person = p.id
    WHERE "group" = '$(group)'
    ORDER BY g.name, p.name, p.christian_name

When the report is run, the user is prompted to enter a value for the parameter "group". Then the report data are generated by executing the sqlselect statement in which $(group) is replaced with the value entered by the user.

6.7. pfm_section

The data returned by the report's SQL SELECT statement may be considered as a table with a column for each 'field' specified after the word 'SELECT' and with a row for each record.

By specifying an 'ORDER BY' clause in the report's SQL SELECT statement, it is possible to group rows with the same values for some fields together.

The report generator has an "economy" algorithm which avoids printing the same data repeatedly.

To control this you have to distribute the fields (columns) of the table over n sections such that section 1 contains the fields that are changing least frequently (when moving from one row to the next), section 2 contains the fields that are changing more frequently, and section n contains the fields that are changing at every row.

When the data of the first row of the table are printed, the data of section 1 are printed first. Then, on the following line, indented by one tab stop, the data of section 2 are printed. Then, on the following line, indented by 2 tab stops, data of section 2 are printed, etc.

[section 1] <--- row 1

    [section 2] <--- row 1

        [section 3]  <--- row 1

Then, when the next rows are being printed, data of the lower numbered sections are only printed if they are different from the data of the last printed section of the same number:

[section 1]

    [section 2]

        [section 3]  <--- row 1
        [section 3]  <--- row 2
        [section 3]  <--- row 3

    [section 2]

        [section 3]  <--- row 4
        [section 3]  <--- row 5

[section 1]

    [section 2]

        [section 3]  <--- row 6
        [section 3]  <--- row 7

The report generator also enables you to print a summary at every point where a higher numbered section is about to be followed by a lower numbered section:

[section 1]

    [section 2]

        [section 3]  <--- row 1
        [section 3]  <--- row 2
        [section 3]  <--- row 3

        [summary 3]

    [section 2]

        [section 3]  <--- row 4
        [section 3]  <--- row 5

        [summary 3]

    [summary 2]

[section 1]

    [section 2]

        [section 3]  <--- row 6
        [section 3]  <--- row 7

        [summary 3]

    [summary 2]

[summary 1]

A summary i is printed just before a lower numbered section j (j < i). Its data can be calculated:

  • by applying one of the aggregate funtions: COUNT, SUM, AVG, STDDEV, MIN, MAX;
  • on the fields of the sections j (j >= i), between the last printed lower numbered section k (k < i), till the next (not yet printed) lower numbered section k (k < i).

In particular, summary 1 is printed at the end of the report, is calculated from all the sections of the report and may be calculated from all the fields.

A record in pfm_section defines a section and a summary of a report.

The table pfm_section has the following attributes:

  • report: the name of the report to which the section belongs
  • level: a number 1, 2, 3, 4, ... . The first level must be '1'. The next levels must be numbered consecutively. In the most simple report, there is only a section with level 1.
  • layout: can be "row", "column" or "table".
  • fieldlist: a space separated list of field specifiers, one for each field to be printed in the sections of this level (see below for details).
  • summary: a space separated list of summary field specifiers (see below for details).

The fieldlist is a space separated list of field specifiers:

    field_spec_1 field_spec_2 ... field_spec_N

where each field specifier is formatted as follows:

    {field_i label_i alignment_i max_length_i}

where :

  • field_i is the name of one of the columns returned by the report's SQL SELECT statement;
  • label_i is a string which has to be used as label for printing the i-th field of this section; if it consists of more than 1 word, it must be delimited by double quotes (" .... ");
  • alignment_i is optional; if present, it is either l or r, indicating whether this field should be left or right aligned.
  • max_length_i is optional;if present, it is the maximum number of characters per line for printing the data of this field; lines longer than max_length_i will be wrapped by inserting one or more line breaks before printing.

    Notes :

    • The alignment is optional. If it is left out, left alignment is assumed by default.
    • The alignment only influences the table layout. Column and row layouts are unaffected by the alignment indicator.
    • Multi-line fields, i.e. fields containing more than one line of text are only formatted properly in a table or column layout.
    • For a table layout, pfm automatically calculates the column width that is required to display all data. So, normally you don't have to worry about column widths. However, sometimes, the data of a few records, make the columns excessively wide. That is where you might consider using "max_length_i" in the field specifier. If the data do not exceed that maximum, it won't have any effect.
    • Although 'alignment' and 'max_length' are both optional, you have to specify 'alignment' if you want to specify max_length.

For every section, the layout can be defined as:

  • row: the section's field labels and field values are printed in one row in a format: label_1 : value_1; label_2 : value_2; ... etc.
  • column: the section's field labels are printed in a first column, the section's field values are printed in a second column.
  • table: the section's values are printed in a table with a column per field and a row per record, the section's field labels are used as column headers for the table.

The summary must be formatted as a space separated list of summary specifiers:

    summary_spec_1 summary_sepc_2 .... summary_sepc_N

where each summary_spec is formatted as follows:

    {field_i aggregate_i format_i}

where:

  • field_i is the name of a field defined in the fieldlist of either this section, or another, higher numbered section;
  • aggregate_i is one of the aggregate functions: COUNT, SUM, AVG, STDDEV, MIN, MAX (see below for details); and
  • format_i is an optional 'ANSI C sprintf' formatting string (see below for details). If it is left out, the number is printed with maximum precision.

Aggregate functions:

In general, the aggregate functions, use the same "economy" algorithm that is used for printing section data.

When all the fields of a section, which is not the highest numbered section of the report, have the same values for a number of consecutive rows, this section's data are only printed once for these rows.

Similarly, these rows are only counted once by the aggregate functions applied to a field of this section.

The aggregate functions that can be used in a summary are:

  • COUNT: Counts the number of rows. In this case, the field_i that is specified only determines which section is counted.
  • SUM: Calculates the sum of all the values of the specified field.
  • AVG: Calculates the average of the values of the specified field.
  • STDDEV: Calculates the sample standard deviation for the values of the specified field:

    SQRT (SUM( (value_i - AVG(value))**2 ) / (N - 1) )

    where :

    • value_1, value_2, ... value_N are the values of the considered field;
    • AVG(value) is the average of the considered values;
    • N is the number of values.
  • MIN: Calculates the minimum of the values of the specified field.
  • MAX: Calculated the maximum of the values of the specified field.

'ANSI C sprintf' formatting string:

Here is a short overview of the 'ANSI C sprintf' formatting string. In general its form is:

%'MinWidth'.'Precision''Conversion'

where:

  • 'MinWidth' is an integer defining the minimum width (as number of characters) for the number to be printed. If the number does not need so much space, spaces are inserted in front of the number, unless MinWidth is negative. In that case, spaces are appended at the end. If the number needs more space than MinWidth, more space is used.
  • 'Precision' is an integer defining how many digits to print after the decimal point, or, in the case of g or G conversion, the total number of digits to appear, including those on both sides of the decimal point.
  • 'Conversion' is one of:
    • d : convert integer to signed decimal string. In this case, there is no need to define a 'Precision'.

      Example: %1d

      prints an integer and uses as many characters as required.

    • f : convert floating point number to fixed point notation. In this case, 'Precision' defines the number of digits to print after the decimal point. If there are not enough digits available, trailing zeroes are appended.

      Example: %1.2f

      prints a floating point number wiht 2 digits after the decimal point and uses as many characters as required.

    • e or E : Convert floating-point number to scientific notation in the form x.yyye±zz, where the number of y's is determined by the 'Precision' (default: 6). If the precision is 0 then no decimal point is output. If the E form is used then E is printed instead of e.

      Example: %1.5E

      prints a floating point number in the form x.yyyyy E±zz

    • g or G : If the exponent is less than -4 or greater than or equal to the precision, then convert floating-point number as for %e or %E. Otherwise convert as for %f. Trailing zeroes and a trailing decimal point are omitted. In this case the 'Precision' specifies the total number of digits to appear, including those on both sides of the decimal point

      Example: %1.4G

      prints 2345.0 as 2345
      prints 234567.0 as 2.346E+05
      prints 0.003456 as 0.003456
      prints 0.00003456 as 3.456E-05

6.8. The links between the pfm_* tables

The following drawing shows the links between the pfm_* tables:

Schema of pfm tables

The referential integrity is guaranteed by the following table constraints which are introduced when pfm installs the pfm_* tables:

ALTER TABLE pfm_attribute ADD CONSTRAINT ref_form
FOREIGN KEY (form) REFERENCES pfm_form (name)
ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE pfm_link ADD CONSTRAINT ref_fromform
FOREIGN KEY (fromform) REFERENCES pfm_form (name)
ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE pfm_link ADD CONSTRAINT ref_toform
FOREIGN KEY (toform) REFERENCES pfm_form (name)
ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE pfm_value ADD CONSTRAINT ref_list
FOREIGN KEY (valuelist) REFERENCES pfm_value_list (name)
ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE pfm_attribute ADD CONSTRAINT ref_value_list
FOREIGN KEY (valuelist) REFERENCES pfm_value_list (name)
ON DELETE RESTRICT ON UPDATE CASCADE;

7. Designing a form

Designing a form is in fact nothing more than filling out the data base tables pfm_forms, pfm_attribute, pfm_value_list, pfm_value and pfm_link defined in section 6. The pfm_* tables.

This is done using the forms that are predefined for these tables.

Note:

Postgres Forms does not offer the possibility to define data base tables or to modify their definition. That can be done using SQL statements CREATE TABLE ... or ALTER TABLE .... using the "Run SQL" function of Postgres Forms (see 3.1. Run SQL) or from 'psql', the interactive SQL terminal of PostgreSQL.

7.1. Modifying or adding a form

To modify or add a form:

  • Select the 'Design' tab on the main window. The pfm_form form now becomes visible in the listbox on the main window;
  • Select the pfm_form form and then open it;
  • If you want to modify an existing form, select the form you want to modify either by specifying a"WHERE" clause, or by scrolling through the already defined forms until you reach the one you want to modify;
  • If you want to add a new form, don't specify a "WHERE" clause, just press [Run] and then press the [Add] button;
  • Now it is possible to enter/modify the form's definition, as described in section 6.1. pfm_form

7.2. Defining a form's attributes

First proceed as explained in the previous section to make the form you want to work on visible in the pfm_form form.

Then, follow the 'Attributes' link by pressing the [Attributes] button. This results in the form pfm_attribute being opened with all the attributes of the selected form loaded in the internal record buffer.

Here you can add, delete or modify the form's attribute definitions as appropriate. Note, that it is possible for the form to have less attributes than the table corresponding to the form, but all the attributes defined in the form must be returned by the SQL SELECT statement related to the form.

See section 6.2. pfm_attribute for a description of the attributes of pfm_attribute.

It may be necessary to add a value list for some attributes. See section 7.4. Defining value lists.

When finished, press [Back] to return to the form defining the selected form.

First proceed as explained in the section 7.1. Modifying or adding a form to make the form you want to work on visible in the pfm_form form.

To define links originating from the selected form, press the [Originating links] button. Then add, modify, delete links as appropriate. See section 6.5. pfm_link for a description of the attributes of pfm_link.

When finished, press [Back] to return to the form defining the selected form.

To define links terminating on the selected form, press the [Terminating links] button. Then add, modify, delete links as appropriate.

When finished, press [Back] to return to the form defining the selected form.

Links allow you to jump from one table/form to another while taking into account the relationships between the tables.

7.4. Defining value lists

To modify an existing value list, select the form 'pfm_value_list' in the main window and press the [open] button.

Then, specify a "WHERE" clause to select that value list and press the [Run] button. This results in the selected value list to be displayed. Here you can modify it's name or delete it.

To add, modify or delete the list's values, press the [Values] button. This opens the pfm_value form with all the list's values in Postgres Form's internal buffer. Here, you can modify, add, delete values.

To define a new value list, select the form 'pfm_value_list' in the main window and press the [open] button.

Then, don't specify a "WHERE"-clause, press [Add] and specify the name of the new value list.

To insert values for the list, press the [Values] link-button.

8. Reports

8.1. Running a report

To run a report, select the 'Reports' tab on the main window, select a report on the list-box and press the [Run] button.

The output appears in a new window.

For some reports you may be prompted to specify a value for some parameters.

8.2. Designing the data for the report

The first thing you need to design a report is an SQL SELECT statement which generates the data you need for the report.

You can use the "Run SQL" function of Postgres Forms to design this SQL SELECT statement.

Let us take an example from the addressbook sample database (see 10.1. The addressbook sample database for more information).

Let us assume that we want to list all groups and the persons that are member of these groups. The following SELECT statement generates the "raw" data that we need for that report:

SELECT g.name AS "group", g.description, p.id, p.name, p.christian_name,
       p.street, p."ZIPcode", p.town, p.country
FROM "group" g
     LEFT JOIN memberlist m ON g.name = m."group"
     LEFT JOIN person p ON m.person = p.id
ORDER BY g.name, p.name, p.christian_name

The result is:

  group  |             description             | id |     name     | christian_name |        street        | ZIPcode |   town    | country
---------+-------------------------------------+----+--------------+----------------+----------------------+---------+-----------+---------
 cycling | Cycling companions                  | 10 | Van Horebeke | Norbert        | Hoogstraat 3         | 9620    | Zottegem  | Belgium
 family  | Members of the family               |  8 | Lemmens      | Nancy          | Copernicuslaan 198   | 9000    | Gent      | Belgium
 family  | Members of the family               | 10 | Van Horebeke | Norbert        | Hoogstraat 3         | 9620    | Zottegem  | Belgium
 family  | Members of the family               |  6 | Van Riel     | Hugo           | Kerkstraat 56        | 2520    | Ranst     | Belgium
 pfm     | Persons involved in the pfm project |  1 | Brouwers     | Adriaan        | De Coninckstraat 23  | 8750    | Zwevezele | Belgium
 pfm     | Persons involved in the pfm project |  2 | Van de Perre | Albert         | Schanslaan 45        | 2600    | Berchem   | Belgium
 tennis  | Acquaintances from the tennis club  | 10 | Van Horebeke | Norbert        | Hoogstraat 3         | 9620    | Zottegem  | Belgium
 tennis  | Acquaintances from the tennis club  |  6 | Van Riel     | Hugo           | Kerkstraat 56        | 2520    | Ranst     | Belgium
 tennis  | Acquaintances from the tennis club  |  2 | Van de Perre | Albert         | Schanslaan 45        | 2600    | Berchem   | Belgium
 tennis  | Acquaintances from the tennis club  |  3 | Verdonck     | Nelly          | Azalealaan 33        | 9080    | Lochristi | Belgium
 work    | Acquaintances from work             |  1 | Brouwers     | Adriaan        | De Coninckstraat 23  | 8750    | Zwevezele | Belgium
 work    | Acquaintances from work             |  8 | Lemmens      | Nancy          | Copernicuslaan 198   | 9000    | Gent      | Belgium
 work    | Acquaintances from work             | 12 | Van Geluwe   | Adri           | Jan Breydelstraat 21 | 8511    | Aalbeke   | Belgium
 work    | Acquaintances from work             |  3 | Verdonck     | Nelly          | Azalealaan 33        | 9080    | Lochristi | Belgium
(14 rows)

Postgres Forms has tools to easily layout a report for these data. That is explained in the next section.

8.3. Designing the report layout

Enter the design mode by selecting the 'Design' tab on the main window.

Then select the form pfm_report and open it.

Don't specify a "WHERE" clause, press [Run] and then [Add].

Then fill out the form as follows:

  • name: Groups and persons
  • description : Lists all groups and their members
  • sqlselect:
        SELECT g.name AS "group", g.description, p.id, p.name, p.christian_name,
                       p.street, p."ZIPcode", p.town, p.country
        FROM "group" g
              LEFT JOIN memberlist m ON g.name = m."group"
              LEFT JOIN person p ON m.person = p.id
        ORDER BY g.name, p.name, p.christian_name
        

Then press the [Sections] link button to define the sections and their layout.

A group having more than 1 member is listed more than once in the "raw" output of the report's SELECT statement.

Because of the order specified in the SELECT statement, all records related to a group are grouped together.

By defining a report with 2 sections (levels), we can avoid printing the data for a group more than once.

Section 1 will only print the fields related to group.

Section 2 will only print the fields related to a person.

When the report is printed, the records are printed in the order specified by the SELECT statement.

When the first record is printed, first section 1 data are printed, then section 2 data.

For every next record, the section 1 data are only printed if they differ from the data of the previously printed section 1 data. Then section 2 data are printed.

In this example, section 1 is a so called "group level" section.

In general, all sections, except the highest numbered section, are "group level" sections.

Possible section definitions for our example are shown below:

    level     : 1
    layout    : row
    fieldlist : {group group l} {description description l}
    summary   : {group COUNT}

    level     : 2
    layout    : table
    fieldlist : {id id r} {christian_name "Chr. name" l} {name name l}
                {street street l} {ZIPcode ZIP l} {town town l}
                {country country l}
    summary   : {id COUNT}

See 6.7. pfm_section for more details on section definitions.

When this report is run (Tab 'Reports' on main window, Select "Persons and groups", press [Run]), this is the result:

Groups and persons
------------------

Description: Lists all groups and their members
SQL        : SELECT g.name AS "group", g.description, p.id, p.name, p.christian_name,
                    p.street, p."ZIPcode", p.town, p.country
             FROM "group" g
                LEFT JOIN memberlist m ON g.name = m."group"
                LEFT JOIN person p ON m.person = p.id
             ORDER BY g.name, p.name, p.christian_name
Date       : 13-Dec-2004


group: cycling; description: Cycling companions;

     id | Chr. name | name         | street               | ZIP  | town      | country
    ----+-----------+--------------+----------------------+------+-----------+---------
     10 | Norbert   | Van Horebeke | Hoogstraat 3         | 9620 | Zottegem  | Belgium

    Summary: COUNT(id) = 1

group: family; description: Members of the family;

     id | Chr. name | name         | street               | ZIP  | town      | country
    ----+-----------+--------------+----------------------+------+-----------+---------
      8 | Nancy     | Lemmens      | Copernicuslaan 198   | 9000 | Gent      | Belgium
     10 | Norbert   | Van Horebeke | Hoogstraat 3         | 9620 | Zottegem  | Belgium
      6 | Hugo      | Van Riel     | Kerkstraat 56        | 2520 | Ranst     | Belgium

    Summary: COUNT(id) = 3

group: pfm; description: Persons involved in the pfm project;

     id | Chr. name | name         | street               | ZIP  | town      | country
    ----+-----------+--------------+----------------------+------+-----------+---------
      1 | Adriaan   | Brouwers     | De Coninckstraat 23  | 8750 | Zwevezele | Belgium
      2 | Albert    | Van de Perre | Schanslaan 45        | 2600 | Berchem   | Belgium

    Summary: COUNT(id) = 2

group: tennis; description: Acquaintances from the tennis club;

     id | Chr. name | name         | street               | ZIP  | town      | country
    ----+-----------+--------------+----------------------+------+-----------+---------
     10 | Norbert   | Van Horebeke | Hoogstraat 3         | 9620 | Zottegem  | Belgium
      6 | Hugo      | Van Riel     | Kerkstraat 56        | 2520 | Ranst     | Belgium
      2 | Albert    | Van de Perre | Schanslaan 45        | 2600 | Berchem   | Belgium
      3 | Nelly     | Verdonck     | Azalealaan 33        | 9080 | Lochristi | Belgium

    Summary: COUNT(id) = 4

group: work; description: Acquaintances from work;

     id | Chr. name | name         | street               | ZIP  | town      | country
    ----+-----------+--------------+----------------------+------+-----------+---------
      1 | Adriaan   | Brouwers     | De Coninckstraat 23  | 8750 | Zwevezele | Belgium
      8 | Nancy     | Lemmens      | Copernicuslaan 198   | 9000 | Gent      | Belgium
     12 | Adri      | Van Geluwe   | Jan Breydelstraat 21 | 8511 | Aalbeke   | Belgium
      3 | Nelly     | Verdonck     | Azalealaan 33        | 9080 | Lochristi | Belgium

    Summary: COUNT(id) = 4

Summary: COUNT(group) = 5

9. Hints for using PostgreSQL

This is no replacement for the PostgreSQL documentation. It only gives some hints for using PostgreSQL in combination with Postgres Forms.

9.1. Character encoding

When the Pgtcl package is loaded and initialised, it sets the environment variable PGCLIENTENCODING to UNICODE.

The pgintcl package does not do this.

In order to get the same behaviour of pfm with both Pgtcl and pgintcl, pfm always sets PGCLIENTENCODING to UNICODE.

Notes:

  • What PostgreSQL calls 'UNICODE' actually means 'UTF-8'.
  • The newer versions of PostgreSQL (certainly from 8.1 on) use the more correct encoding name 'UTF-8'. The encoding name 'UNICODE' still exists as an alias for 'UTF-8'. pfm still uses 'UNICODE' to be compatible with older versions of PostgreSQL.

Because pfm uses clientencoding UNICODE, it can work with most database server encodings. PostgreSQL automatically performs the necessary conversions on the communications between client and server (see PostgreSQL documentation Section "Automatic Character Set Conversion Between Server and Client" which is a subsection of Chapter "Localization").

The default character encoding used by the PostgreSQL database server is SQL-ASCII. This character encoding is not suitable for texts containing special characters like ë, é, è, etc.

The encoding LATIN1 (also referred to as iso8859-1) already contains some special characters, but it lacks some characters like the Euro symbol (€) and the French character œ.

The encoding LATIN9 (also referred to as iso8859-15) is almost identical to LATIN1, but it includes the € and œ. It still cannot encode characters from Cyrillic, Chinese or Japanese scripts.

The encoding UNICODE (which is in fact UTF-8 in the context of PostgreSQL) includes all these and much more. It is meant to be able to encode all possible characters of all known written languages.

When you create a database, you can specify an encoding for the database (e.g. CREATE DATABASE example ENCODING='LATIN1' or CREATE DATABASE example ENCODING='UNICODE', see PostgreSQL documentation for more details).

9.2. Some general hints about SQL

Here are some hints about SQL. For full documentation see PostgreSQL documentation.

  • The SQL language is not case sensitive, but it is customary to use upper case for SQL statements.
  • PostgreSQL always converts the names of tables and attributes to lower case, regardless of the case typed by the user, unless these names are enclosed in double quotes. Example:
                CREATE TABLE Customer (
                     Id        serial,
                     Name      text);
        

    is implicitly and silently converted to:

                CREATE TABLE customer (
                     id        serial,
                     name      text);
        

    If you really want Customer, Id and Name, you have to write:

                CREATE TABLE "Customer" (
                     "Id"        serial,
                     "Name"      text);
        
  • SQL keywords are not allowed as names of tables or attributes, unless they are enclosed in double quotes. Example:
                CREATE TABLE order ( ...
        

    is not accepted because 'order' is an SQL keyword, but you can use

                CREATE TABLE "order"
        

    to create a table with the name 'order'.

  • Postgres Forms automatically encloses the names of tables and attributes in double quotes for the SQL statements it generates itself. However, you still have to take care when entering SQL statements, or parts of SQL statements, in the pfm_* tables.

10. The example databases

You can install the example databases as explained in section 2.5. Install example database.

10.1. The addressbook database

The tables of the addressbook database have been created with the following SQL statements:

CREATE TABLE person (
    id serial primary key,
    christian_name text,
    name text,
    street text,
    town text,
    "ZIPcode" text,
    country text,
    category text,
    description text
);

CREATE TABLE "ZIPcodes" (
    town text,
    "ZIPcode" text
);

CREATE TABLE "group" (
    name text primary key,
    description text
);

CREATE TABLE memberlist (
    person integer,
    "group" text
);

The following figure shows the links between the tables of the sample database:

Example database

This database shows a typical example of a many-to-many relationship: a person can belong to more than one group, a group can have more than 1 person as member. Such a relationship is implemented as a combination of 2 one-to-many relationships:

  1. the one-to-many relationship from "person" to "memberlist"; and
  2. the one-to-many relationship from "group" to "memberlist".

Both relationships are implemented as "links" in pfm:

  • on the form "person" there is a link button labelled "Groups" which opens the "memberlist" form in which all the memberlist records for the currently displayed "person" are loaded (one-to-many relationship "person -> memberlist");
  • on the form "memberlist" there is a link button, labelled "Person", which opens the "person" form in which the person, referenced by the currently displayed "memberlist" record, is loaded (many-to-one relationship "memberlist -> person");
  • on the form "group" there is a link button labelled "Members" which opens the "memberlist" form in which all memberlist records for the currently displayed "group" are loaded (one-to-many relationship "group -> memberlist");
  • on the form "memberlist" there is a link button, labelled "Group", which opens the "group" form in which the group, referenced by the currently displayed "memberlist" record, is loaded (many-to-one relationship "memberlist -> group");

The referential integrity of the these relationships is guaranteed by PostgreSQL table constraints, which have been created with the following SQL statements:

ALTER TABLE ONLY memberlist
    ADD CONSTRAINT ref_person
    FOREIGN KEY (person) REFERENCES person(id)
    ON UPDATE CASCADE ON DELETE CASCADE;

ALTER TABLE ONLY memberlist
    ADD CONSTRAINT ref_group
    FOREIGN KEY ("group") REFERENCES "group"(name)
    ON UPDATE CASCADE ON DELETE CASCADE;

A person can belong to more than 1 group, and a group can have more than 1 member, but a person cannot be member of the same group twice.

The following constraint guarantees that a person can only be member of a group once:

ALTER TABLE ONLY memberlist
    ADD CONSTRAINT memberlist_pkey
    PRIMARY KEY (person, "group");

10.2. The customer database

The second example data base which is included in the distribution is somewhat more complicated. It is a database with customers, invoices, products and orders.

The following image gives an overview of the structure of the customerdb.

Structure of customer data base
pfm-2.0.8/doc/en/index.html0000644000175000017500000000144611472150463013631 0ustar wimwim Postgres Forms Help File <p>Your browser does not support frames.</p> <p>Here is a <a href="body.html">"No frames"</a> version of this document.</p> pfm-2.0.8/doc/en/contents.html0000644000175000017500000001467011472150607014362 0ustar wimwim Postgres Forms Help File
Postgres
Forms

Table of contents

Introduction

Copyright

1. User interface

1.1. The main window

1.2. On-line help

1.3. Context menu

1.4. Printing

1.5. Keyboard operation

1.6. Displaying the license

1.7. About pfm

2. Working with data bases

2.1. Opening a data base

2.2. Checking the pfm_* tables

2.3. Close data base

2.4. Install pfm_* tables

2.5. Install example database

3. Running SQL

3.1. SQL terminal

3.2. Import SQL-script

4. Configuring pfm

5. Working with forms

5.1. Opening a form

5.2. The form window

5.3. Update record

5.4. Add record

5.5. Delete record

5.6. Status field

5.7. Links to other tables

5.8. Filling out a form

5.9. Views

5.10. Search record in internal buffer

5.11. Forms with a LIMIT-clause in their definition

5.12. Help for form

6. The pfm_* tables

6.1. pfm_form

6.2. pfm_attribute

6.3. pfm_value_list

6.4. pfm_value

6.5. pfm_link

6.6. pfm_report

6.7. pfm_section

6.8. The links between the pfm_* tables

7. Designing a form

7.1. Modifying or adding a form

7.2. Defining a form's attributes

7.3. Defining links between forms

7.4. Defining value lists

8. Reports

8.1. Running a report

8.2. Designing the data for the report

8.3. Designing the report layout

9. Hints for using PostgreSQL

9.1. Character encoding

9.2. Some general hints about SQL

10. The example databases

10.1. The addressbook database

10.2. The customer database

pfm-2.0.8/doc/schema.png0000644000175000017500000007317010777700777013223 0ustar wimwim‰PNG  IHDRšXÔ¬·sBITÛáOà pHYsÐй‹çŸ IDATxœìÝw\SWð“@3@VåTq ‚UÁ…Z'8Z«â@°(‚QÀ‚UgUª´*ÈrÛºW+¨àF(‚@Ø’÷ÛÆ¼B€$7¹<ß$‡sÏ}²~Þ}I\.àÁƒïß¿ÏÌÌ|ùòe~~Aù§rVU•Ű‘÷ïÜ»4‰;Î6óù_4MMºŽ¾¾ž¹¹¹………™™™ Þ¥q"Aœ—˽téÒ•+WΟ?_ZZŠRPP0ÿr¸‘q?MMmu šž¾Amm5ÞeJœššFqQAu5«ªòSA^î‹ç±Ùl„ŽŽÎÌ™3gÍšE"‘ð.tÄ1•––=z4&&&77W]]c¢ýTûÉ΃͇™9ïÒdBÖËô·o2o]O»y=…UU©¯¯ÿÍ7߬\¹²OŸ>x—ºâŒhªªªBCCúé§ÆÆÆñ¶+Vn˜h?ï¢dÚƒ{·ŽŽºq5‰L&¯^½zÇŽ ï¢@W@œ—Ë=|ø°¯¯oMMÍšõٰ߯EMMï¢äF}}Ýá{£Âƒ”””BCC×®] «Ÿr⌠—.]zíÚµqã'í `:ïŠäRÞ?·y]»rÉÆÆæÔ©Sýúõû"Ð gDpõêU777„H;ŒvûÚïräÞ•Ô‹~›¾m¨¯;sæÌ¬Y³ð.ˆŠŒw »:äììÜ·ÿÀ÷^A–‰…£ó¬›÷^Y éææöã?â]Ä™| ]½zµƒÓÌóÉw{ëÁ^9±¡ëèž½xkî‚e[·n]¶l‡ÃÁ»"Ð1E¼ ]øÃ?,\²rמƒd2üÏ$fŠŠŠa‘GètÆ¡ýaQQQxW: ðÃ?à]èŠ#GŽx{{/òXõãÞC°NBH$Òø‰Sš››÷G…«¨¨|õÕWxW„]réÖ­[“'Ožäàsü¼‚‚Þå—ËݼaùïgO$&&NŸ>ïr@» ÎäOYYÙ°aÃè:_$]{ Y&çë™¶o_¿HOO766Æ» lp‘?ß~ûmeeUTLd™ÔÉ䨃g¸ˆäîîÞÒÒ‚w9@0ˆ39—˜˜¸í‡=p ¬”éõ1 ;xûöíà]  V6åICCàAƒè ½‹©÷aó?.–.œö"ýñ»wï´´´ð®´Kgò$***77w{à^È2¼l «¨¨€cke,Ɇ†==½±ãìcŽŸ—ÎkkkÂvm½š–PZRÄf³sKÅüU‘ôø²eÓÊ‹¿Ÿ***‚4YKgrãÌ™3•••«×ùJmŽ;üŒw! 5X:“#Gjjæ¦\ÿKjsüÒT›UUù6¯žJU–Çñ%gñ\‡ü¼ïß½ƒ“1d |ò!==ýï¿þr_¾Fš3eUU"„$—5’_r–,[ý1;ûÖ-âßfA¾@œÉ‡””Gg1_¬Æ¸ɸ©´¤hñ\‡T;ëWÒxâïƒ=ÅÔÖToôtl¢n9qhB¨´¤è»enƒŒÕ†šÑƒ¼E92Kàøùy9›ÖzŒ²Ðë¯O±úRß{ÝÒ‚üÜVÕææ|pu°2ù‚Ìß(–’Dg7ÙYUU-%%EŒc‚îƒ8“©©©ÃGŽÕÒ¢KbðƒÑ»Ã"Ü~øŽBQZ½|γ¿"„x³rK¹Ø?^ÿˆ°€•k¼ŸfO°wüq§_R¹í¾k¼6ûÿ•Ub;É)ö`Ä‘Cδíøùy9®S¬n]O>÷*§fßS7®&»:ŒæO4„ÐáŸ÷8ìt£XJ’Õæ+ûÔÔT1Ž ºâLTUU=|øÐn²“„ÆÿjÂd}#}#ßm!---£v ï?ÚzÂ`óajjêë¼¶!„|½V¸Í÷àoùýìÉ.”êÿésë{¬¿²SR¢Ž?iËŽÐrfiÄîþn¶“œ úÚO™&…’„°ŸâüæÍ›‚‚ñ ºâLdddp8œ£¬%4þ¨Ñãø<}|Oxk[ìIß¡ººÚV-9ßw¡Œ;^CÙOvæµLvtEÝþãêÿU;fœÔJû8?~,ÞaAw@œÉÌÌL„Ù@s ¯AÓäPYùIxšæ¿Ç[Q””¶455v¡ŒŠOL„–¶¯{ü©¼Œ¿›À5n •$D¿±Èˆ39™™I×a0t¿ÐøÕ¬*þÚB×!m:!TYQÎkÁÓutù»ÉÈTª²±Iˆ3™q&òóó úJnü¿žÜç0ÒÊFrób‚­BèÆµÏ» ¯_IBM´sÄ¥žõÍÏÏÇ» ðÄ™¨ªªRW§Inü»·¯äæÿ³'d›‚‚Â*)žxÀo“_ ÎÝé÷àî­æ¦¦{wn„mÑaôÚäˆK=ÒРUVVá]ø âLT±X¼Í[’ð§Ï–MßÚZlh¨?p$¯¥3C“¤kOl'9­[µp‰Ú†Õ‹'9¸$]}¬o`„K=ÒÐÐdU³ð®|'9É—óôûn ûÈØÁ«rt¾¤LÙ·'0ýÙã›×à`ZYwr’Y™ÊÊ*xWZc±ªÒÿz„wà3XÙ,Iá•Ù¬ÞI€8ëÑ$+Y@Ê`e@g‚€8Ä€ Îq ˆ3A@œâ @g‚€“œä[KKËÕÄ#;wî„Ûq‹×СCÜw ¯ëŒƒ®8“o,VåªU«&L˜0dȼk!”C‡™[\ëµ ïB@'@œÁ‚ V­Z…w„rèÐ!¼Kk(‚€8Ä€ Îq ˆ3 ‰D"‘ڽؿð¿€ ˆ³Òôg‚€8ÄññV3IÿÁžzzzjiiQ©Ô!C†„‡‡s8œVÓ988P©Ô&$$´7‹ÊÊJ___SSS*•J§ÓgÍš•••Å_‰Dª®®vwwWSSc0^^^l6›×Ax%ü“«««3Œ°°0¬6777555:îííÝÒÒ"b=€°¸@æõíoæ6Ï=·”Ûö_ú&BèàÁƒÂGhûYçääXXXܾ}»¹¹9??ñâÅ¡;w¶šdÆ ¹¹¹¹¹¹ |¸šš‰D¢Ñh¡üüüVÝÆÇÿàÞ½{m‡ÂVB]]]y-ãÇGݺu‹¿Ûĉ±fff¡>tª[[[ìÁ€Bµµµ­ZÞ¿ß©zñ@œõP>>>>>>#FŒÈÊÊb³ÙØ–¬¶ÛÎ455ù|úô©íPÅÅÅ¡þýû+***((Éduuu„“É8”’’B¨±±±S•hiiñOÞ¶…7 ˆõâ8ë¡Nž<‰ 766VPP(**Ø­ªªŠÿ.àr†ººº¡’’6›ÝÒÒÂáp°%ÿ¶‘ÔJD×Íz€ü‚8ë( Bˆß_}}=BˆË·Là„÷ïßç`ccÓ¶ÏìÙ³Bqqq]«MÄJD×Íz€ü‚8ë,--BW®\᥆££#B(((¨¾¾þîÝ»>8áõë×óòòþùçŸmÛ¶)((øúú¶í³sçNKKKŸðððÂÂB6›]\\ogg'Jm"V"ºnÖäN» @'tÏfzzº½½=¶•ûÐËÊÊfÏž­ªªª¥¥åááQYYÙêû€=-((pttTVV655=þ|«¿òžÖ×ׇ……5J]]]QQÑØØØÝÝýÎ;;·j±’ö&ïB=¢@°gSÁŵ{„aÆݸqƒ¿…Á`œ?ž¿…û_(´zzùò嶶ꬬ¬ŒmÑ8÷V[µˆ^‰è-ÂëD+›‚€8Ä€ Îq ˆ3AÀD°oß¾ß~û ï*ÀÄ™|ÓÖÖ‰OøcßÞ Mx—B,ß®ö^ëµ ï*@ç@œÉ½16½p£ã~l;Ä€ Îq ˆ3A@œâ @g‚€8ÃÇëׯCCCÇŽK&“I$ÞåÉ‚[: ÎðáææÖÐÐpôèѶ׉Ä·tÀINøÈÌÌÄ» =ðqK,–΢©©±œY†wDC¡Pº_à]ÄAÝÿChh(ÞU±±ñþØß‡ZŽÂ» ˆ3‚ 3gŽ««+Þ…ÊÚµkÎÇAœÉ ˆ3â°´´twwÇ» BÙ´iÞ%€N€]‚€8Ä€ `Û>ZéÂ{ G|ÜÒq†ø÷(ðqK¬lâ @g‚€8"!‘H$©ººÚÝÝ]MMÁ`xyy±Ùl^‡ÂÂBOOO---*•:dÈððp‡#pruuuƒ†***rssSSS£ÓéÞÞÞ---¼I*++}}}MMM©T*NŸ5kVVV–4_2;g ‚‚‚6oÞ\\\looÿÓO?a‘„ÊÍÍuttœ?>“ÉÌÎÎ9räæÍ›wíÚÕjò€€ooïââbGGG??¿sçέY³Æßß¿¤¤ÄÉÉ)"""""ëY^^>f̘˜˜˜°°0‹õÇdffZ[[¿~ýZª/È.y}û›¹ÍsÏ-å ù‡ –\ Ø·%11{úâÅ „Ð!Cvf±X!##£V“_¼x{Š]ÿKMM­U‹¹¹9ötݺu¡èèhÞiii¡yóæ‰ý¥ ¡££³â»í½ç+¾Û¨­­#Íz€p$.!v!üøqýúõxW!)7oÞÒÔÒbn)¤ÏkÉÁÁÁÛ¶m“P Ø¡R•••ššš¡¦¦&*•J¥RB---‘‘‘gΜyûöm]]6 ™Læ­Æ½ð¼¡ÆÉ“'BáááX<åççws@]]ÝÂÂÂ’’1Ô×U ®3çûEüëΛ.Äÿrÿþ})W%.Ä» ì bP__øŽ}OJJêæ€³gÏFÅÅÅusУ@œÉa–£´éx.¤tÈÑÑ!T__÷î݇vsÀ;wZZZúøø„‡‡²Ùìâââøøx;;;qÔ+êVc¾Â» ðÄ™Èÿç]nN6ÞU;{ö옘}}ý#GŽDEEus@mmí={vàÀ***cÇŽMII KÁbQX÷îÍ ¼«ŸjÛQiÒhÕÕUøÖÐv8 ƒÁ8þ¼þÂ'Ø¢¬¬ìãããããÓµ‚¥ ¦š¥©¥‰wà3X:“šššÕU•xWZcUUjÒhxW>ƒ8“ýúõËùøžGIÎÇwýúõû ðÄ™077¯­­ÉÏËÁ»ðY5«ª° ÏÂÂïBÀggrûͼ}“‰w!à³·o2¹\®¹¹9Þ…€Ï Î䀅……²²òÃ{â]øìñƒÛd2ÙÊÊ ïBÀggr@YYyÒ¤I·n¤â]øìæõÔÑ£GÓét¼ ŸAœÉ''§wo² þÁ»€B¬ªÊ¿Ÿ>prr»ðà¸3ù0cÆŒuëÖ]üí´§×Ööú¼yó&99YšU^ss³Àö¤„³l6{îܹR®q& œœœÎü³jŸ‚‚BÛkÖ¬9pàÀ©S§¤_8:ÍhÛ~êøÁ1cÆ uÌì¹K–®X‹w!SVVéÛ߬Uã“Gw_e=Ç."d Ä™ÜpvvîÛ·oTDðdÇém/í¢¦¦>Ø|.…u™Df0¾—jÛŠ êիׂ ð.´»ä‰DúñÇŸ§?½tñW¼ké¹nߺrû« ïZ@kgòdÞ¼ycÆŒ ØÜØØ€w-=—Ë ô4hÐÊ•ò±^ßÓ@œÉ‰UVZè‹w-=ÑOá;_e=ŽŽVT„­4²âLÎŒ=Ú××÷äÑýÜHû–žåï§¢#‚¿ùæ›É“'ã]  âLþìܹsøðá›×/«ª¬À»–ž¢¶¶fýªE}ûöŒŒÄ»Ð.ˆ3ù£¤¤___;Çu<$šÔÕÕ.r›\VZôûï¿«««ã]hÄ™\êß¿ÿåË—óþÉ^4wJCC=Þ廹yéÂi/_ü}áÂ…¡C‡â]âL^7.!!!ëeú¢9S*+?á]1ÕÖÖ,_2ýÑý?> ghÊ>ˆ39æààpáÂ…¬—ÏÜ\¾‚³ÓÅ®œY:–Ýý;7N:µtéR¼Ëƒ8“oÓ§O¿~ýzEy©ËäQ×®\»â¸÷æ´É#?~x“œœ¼xñb¼Ë"8“{ÖÖÖOž<éÓGï›%3¶ú¬®««Å»"ùÖÔÔè»hÎ uÕG988à]ÄôíÛ÷éÓ§Û·o?{:Öv¬Y|ܱ––¼‹’?\.7)ᬽÍàÃö®[·.##®™!_ ΂B¡=}úÔÄØÐÇk…³ýðÄ ql6ïºä‡Ã¹œra¦Óص+еi÷îÝÛ·oŸ²²2ÞuÎ8#KKˇ&&&’I-ëW-²ĈùyÏëWp§îve¿sìð¾Cz}·Ì­†õ)..îÙ³gÖÖÖx׺N=# éÓ§»ººþùçŸØ²-$ÐWSK{šëÜ/-GZm6È¢'ŸrØÒÒòîmVúß²^f$]<ûéSAAÁÙÙyÍš5d2ü/ÇzîךØH$’­­­­­-“ÉLKKKMMMMþ-îÔaì¯ú} 55µ5hšYäI¹¶¦¦¦äÄøW™R›£^ƒ·o²jXUUU…yØ ˜544œœœœõôô¤V ˆ3‚c0K–,Y²d ‡ÃùðáCffffff~~~yy9‹Åjnnª¨(—zQ܆†:iÎW‡¡«¥¡lÔ§ƒÁÐÓÓ377777755x™r ¿ Îz 2™ljjjjj:sæL|+QUU]±|ipp0¾eâ-‚€8Ä€ ÎqdËëׯCCCÇŽK&“ÛÞ! ΀lqsskhh8zô(vt¢ƒ5€lÉÌÌÄ» ¯`é @g‚€8Ä€ Îq ˆ3A@œ ¯jjjÖ¯_oddD¡P:<ƒ¢SåÄ-¤ÿ| øyzzFGGûùùÕÕÕuxE§:Ë)8+È¢þÒ$áÒ¥K¡+VP(ñv–S°t€¼ª¬¬D‰x½Nu–SgÈlúÇVVV¼JUVVúúúšššR©T:>kÖ¬¬¬,^þ …¯·×ùüùózzz E__éÒ¥¹¹¹BêÁZª««ÝÝÝÕÕÕ FXXB¨¨¨ÈÍÍMMMN§{{{ãyËj.!¤§§#„’’’ð.tLEEeÛ¶mxW!s°ßãªU«²³³“““¹\.“É433£Ñh.\hhhÈÈÈ055¥Ñh¯^½âŸ¤Sãóž~üø!¤ªªzóæÍ†††ëׯëèèôêÕ+''§½z°–7¦§§WWW/\¸!töìÙ™3gò·„……‰ó}é ‰ÇYuuõºuë ±{;б3¿nÆY—ç ºâL ,,y-ëÖ­CEGGóZÒÒÒBóæÍ㟤Sãóž.Y²!4{öl^Kll,BÈÃã½z°–‹/bO±kŸ¨©©µj177ùE‹™Äºîîî¡ýû÷755‰·3¿nÆY—ç ºâL ,,˜L&¯ÅÐÐ!Ä[\âr¹555!]]]þI:5>ïiïÞ½Bßÿ=¯¥¸¸!¤§§×^=XKEEö´±±Q` •JýU‹—Ä÷lÊÅΗž°ÓÈ:Î{ŒåKÿþý_ !„˜Lf÷g„ ¢¢¢ÂkÑÑÑA•••µWFKK { ¤¤$°…sÒ'ñ]r±ó¥'ìôr‹¾®®.B¨¤¤„Íf·´´p8,Ñ8N÷gÄ`0Bõõõ¼–òòrÞLÖ#û:gÒßù"pŽ999ü{dŠŠŠZ)úþ—îÌ·íž ™ÞïäÊìÙ³Bqqq’ÜÁÁ!ôæÍ^KRRBÈÑÑQ³“’N­šb“Hmç‹À9~üø‘Á`0 þ=2Ø"1¶í ›¤Sû_º<ß¶{‚dy¿Œ€mgµý~úôÉÒÒ’J¥îÝ»·   ¹¹¹¨¨èܹs¶¶¶íM"úøØžM55µ›7o666ò¾Ï¹¹¹í ÞµiêJœImç‹À9b{dŽ?ÎkÁöÈ´Š³NíéÎ|[í ’åý>2âL ¿”úúú°°°Q£F©««+**»»»ß¹sGÈ$¢>|xïÞ½{÷îíááÁ¿Û¡§Ä™Ôv¾œ#¶G¦¤¤„ׂm1mgÚÿÒù¶Ú$Ëû}dÄ™Œ@ã]…8ueϦÔv¾œ#62¶Ãÿ˜G,û_D™o«=A²¼ßbëÊžM©í|8Gl ¶ÃÿX¼D™o«=A¼t÷@ ‰î|Û#“’’ÂkÁöÈà5_ùÞˆ‚Ô>¼K“¢N­š¶D¢;_¶`{uuuù÷ȴݳ٩a»<ßîï ê`Û™Œ@„ÛvÖÝ¥3mmí={vàÀ***cÇŽMII êæÈí111yò䉓“ÓÂ… ÕÔÔ/^ìââræÌ ÍNø|?~ldd$éYDѹ]\A—ÖSVVöñññññ}ÑÇ8¹‰‰ÉÉ“'ù[222:5H«–.Ï·³ƒtêÝt \ï @g‚꽄ìdµ0@7I5Î ³’+›‚€8Ä€ Îq ˆ3A@œâ @g‚€8Ä€ Îq ˆ3A@œâ @g‚€8Ä€ Îq:VSS³~ýz### …"äv\2;>è! Î@Ç<==£££ýüüêêê:¼‰Dêl$uj|Úq:véÒ%„Њ+(Š<Žzˆ3бÊÊJ„²²²œŽzˆ³[üðვ•™üï ²²Ò×××ÔÔ”J¥ÒéôY³feeeñúóO(|E²½Î999zzz ¥¡¡!)))77WÈ$üýõõõ—.]ÊëßÞKÀ«««ÝÝÝÕÕÕ FXXB¨¨¨ÈÍÍMMMN§{{{·´´t÷2‹Kéé顤¤$¼ ‘Øç¾jÕªìììääd.—Ëd2ÍÌÌh4Ú… 222LMMi4Ú«W¯ø'éÔøü-?~d0 ãæÍ› T*UEE¥W¯^999'iÕÿúõë:::mûó¿^ãÆÓÓÓ«««.\ˆ:{öìÌ™3ù[ºñæ B(88ï*Ä â¬ÇÁ~ö‰‰‰¼–uëÖ!„¢££y-iii¡yóæñOÒ©ñù[–,Y‚:~ü8öTEEeÚ´i!“´êÏårcccÛöç ¼Æ‹/bO333Bjjj­ZÌÍÍE|!„q&£ ÎD‡ýì™L&¯ÅÐÐ!Ä[öár¹555!]]]þI:5>KïÞ½B%%%ØS///„žžžÀIZõçr¹ÅÅÅmûó¿^cEEö´±±Q` •Jñ…ñâL±ƒuQ@Pt:÷ ‹þýû#¾LA1™L±Ì GGG‡×¢¢¢‚*++±?ö¸Uþ—À£¥¥…=PRRØÂ‹9@<°+ ‡âߢ¯««‹*))a³Ù---K4‡#–y1 „Pyy9¯¥¾¾ž7_Qúc[õ‡nA+="ΰ}^111mñ*I¦Ìž=!'–ѰcÇøw :88 „RRRx-ïÞ½C9:: ¡mÿ¤¤$!ý„ÈŒˆº÷ IDATß§•\1¾í {¥}úô©¯¯oÕ(­eHÛþéÓ'KKK*•ºwïÞ‚‚‚æææ¢¢¢sçÎÙÚÚ¶7‰VVV¡””ÞR¶§RWW÷æÍ›¼=›¹¹¹ÇoÕŸ·g³½þí5ŠÒÒ“¡Ž¶ÉÝGFËê,Qâ !´gÏžVÒ*P†|áõõõaaa£FRWWWTT466vww¿sçŽIÚ“žžnooO£Ñø§úøñ£»»{ïÞ½I$ÒСCù÷<´Ÿ¿ïÞ½=<<„÷Øq&œˆq&G?-«³D‰³ÁƒëèèTUUñ7J±Fð/•mÛ¶á]5Îäè‡Ó#¶avïÞ]^^Žw!È9úáô 8suuµ³³‹ˆˆhïø@[rôÃéAq†Ú»wommmHHÞ…È1Rûð. HмüpzVœ1bÑ¢EÌËËûy%dËÞ¥I‘—NÏŠ3„PHH‰D Ä»ä‰\üpz\œzyy8qïB'rñÃéqq†úþûïžîBö8=1Îh4Z@@ÞU gdÿ‡Ó#â¬í†jOOOØz €pr÷Ãéqè Îq ˆ3A@œî>ØlöóçÏ333óòòÊËËY,–––ÿíD¥àìÙ³Òœ#BÈÈȨ¢¢‚F£1Œ/¾øÂÂÂbøðáT*µû#‹'ÎîÞ½ûúõëÌÌ̧OŸ–——WVUU³XƒÍ‡?ytG,ã‹ÈÕÕUš³ÃX³ÍzùŒ¦©IצYXX˜››=zÀ€Ò/È86›}ÿþýÔÔÔ”ÔÔ—/^`$‰¦©E£iZŽóþÝ[iÖSRÊláJuŽ¡úfÒógOkjXŸÊyÇ|˜š™9:88;;ÛÙÙ)++wmdR—!áp8çÏŸONN¾téRee%BˆB¡ µ´ÒÓ7ÐÒÖÑРõê­WW[ÓµÁ;«¬´øÄ‘ý³ç-é?` tæÈ£ª¢VTTPSͪªªø˜ýîÍ«ØeòMMMœœ¦L™âââ"å’dœªªê¦M›‚ƒƒñ.Dª>~üxèС£G–——+**ÚŒŸ4|Ę/-Gš 472î‡ËõHŒ{‘|¶¯õÚ&ýYóä½}“ùöMæ‡wo~?ÙÜÔ¤¦¦¶hÑ"OOÏ¡C‡vv´®,}üø1&&æèÑ£L&S[[gòÔ™SgZ|9B¯aF‹W™'Žìw™1oÒœ³ƒËåf¾xö<ãé­ëi±±G¢¢¢ÌÌÌV­ZµtéRmmm|k¸ÈÍÍõõõýí·ßœ]çN´s˜é¶H‘BÁ».™ ×ÇP¯áDû©¡½‡=øóΟ×c†>|ØÕuú?†˜››‹>Zçv~óÍ7¦¦¦Ö_M>Ÿ|7ý 3<êø”©3pÌ2™B"‘,†ŽX¸deìÉ‹/ÞW8¯£ÛÇÛÛÛØØ888¸®®ïô,[¶¬_¿~ ‰+×lNÍŒŽ‰›3)d™@d2ÙzœïÖ]/ÞUïþùù‹L —¬¬,QG±‡Ã ïׯß/§NùlÝ•™ÍÚø×Q£ÇuµòBQš6}îÙ‹7¯Ýyi7ÙÅßßßÐÐðøñãxפáĉƒ>w.~ÍúïÓ_—m Ó iâ]”|PVVY²lÍ{Y»öÝðÐÒÒr×®]ü·:lHq–••5bĈ͛7Oqšy÷Iöêu~Tj·ÕõL¦fC¢câR®ÿÕ·ÿ åË—Ïž=›ÿž¸€`***ÜÜÜ–-[Ö¯ÿ [Þøl VS×À»(ùC¡(-ÿvÃíGï§Ï^¸}ûö‰'þóÏ?Â'é8ÎNŸ>=|øðâ’²Ó¿]ýùðÙÞz}ÄTmcþåðóÉwwþ–vyÀ€wïÞÅ»" ~ÙÙÙcÆŒIII ŽL¼òHOßïŠäMS+"úÄϱç^ff5êÑ£GB: ‹3.—ëëë»dÉ’±ãìnÜÍ?qЏKíqH$’ÇŠµ×î¼ÔýBÒ¤I¿þú+Þqúûï¿mllÊ?U\L½¿|¥Ü?A\\fÌ»|+]U6iÒ¤”””öºµgÍÍÍsæÌÙ³gÏ7«6ü5VûÅÈȸ_bÚÃ1Ö±ë¯ã]W¯^ÙÚÚª¨j$¤=4ÿr8Þå~£Ä+êêêzáÂ}Ç—Ë]¶lYBBBPèþ;ÃÉd8JÌÔÔ5NÄ¥L›1oÍš5û÷ïÇ»Ð]ïß¿·µµU×мrÏØ¤?Þå“¶¶ÎïIwÆXOX´hÑ;ÑœS~~~gΜÙöÃ÷åž®°çR¤P¢Ź̘·aÆóçÏã]è:&“ikkËEäß.ÝÖaô»"SPPˆ=™`Ò×tÚ´iïÞ½kõWqvúôiló›U›¤RaÏE&“#þeŒõ„¯¿þúéÓ§x—º‚Ëå.]º´¢¢òBÊ=C£¾x—C|4M­³ ·TÕ4fÍžÝÐÐÀÿ§Öq–••µråJû)Ó¶î•b…=—’5öd‚61Á‚êêj¼ËvàÀ””ÿàH#ã~x×ÒShkëì;pêUV–ŸŸûÿÅYccã‚… išÚáÑ'`§ŒÔhÐ4žJÌÍÉÙ°aÞµ€ÎyÿþýÆœf.Xü-Þµô,6_Ù»Ú;::šÿ€§ÿ‹³üñÅóçáÑ'èt†ÔËëÑ,GŒÙ°Ùÿøñã×®]ûÐ ›7o¦P”vGÆâ]HO´eGh¿W¯YÃ;aàsœîÝ»wÖœÅp|.Ö¬ÿ~€ÙàMÞÞ¢œÌdÁíÛ·7ùÂÿ¸ “É[Â^¾xÁ»™ñç8ó÷÷oiáølíY—m‘ŠŠŠ[v„ò6@ÆmÚ´ÉШ¯ÇеxÒsMvpµgëïï-üg'NœX¶rƒ~#)Q[[°u½õp£þúã^âßHgÜ‹Ä?l«§\j¦8N>rl@@, É¾Çÿõ×_k7nURõ2ª’þÚKT7S’³Ñç‡ÂÂBì›ã,::š¬ ðÍw^Ò©`‡Ÿç‰#Ñ«×ù½É­Ë-•Ñ[âbƒ÷Ž‚‚‚¤¤$¼ 8pàMSkú¬¢O‚ï×^`í ÙŽK*‰1 ÇØL8È;»†Œjhh8zôèTçÙ Ý/Ä2ƒ]»r !ôõ¢Ò¹ðSn)W^Bs¢ýT#ã~àÌ'ÙVYYù믿Ι硪ª&úTRþÚ‹—,ÿˆ–,[}ÿþýׯ_“B—.]b2™îËVKmö¬ªJ„\e¨-2™¼Èã»kW¯æååá] hWZZZSSÓœùš ¾ö2Óm‘¢¢â¹sçÈ¡””Ý^½­ÆŽã °…ÉÒ’¢ÅsPí¬^IKàý‰¿ö{P[S½ÑÓ}°‰ºå@Æ¡ýa¡Ò’¢ï–¹ 2VjFèâ^?«ýؼ«Ydn÷b³Ù§Mÿûñ¾CÍè/ŸÿÝ…Yw³ë„ËÜ¥¦¦~Ñ[ˆ…e«v|¿ö%Å…;ü<-h 0 NújHìp‡#dî­ÊnÕ'7烫ƒ•ÉdþÆVsøJvn5¾Àù²ª*C}'Ž1`@jF_é1ëÝ‘®C«AÓie“––Fæp8—/_žh?UÇÍŒÞyäöÃwŠÒêåsžýõ!Ä[dÅ_ù—`#ÂV®ñ~šY<ÁÞñÇ~I ç¶û®ñÚìÿWV‰í$§ØƒGEˆ«¶ŸÂƒVznþ+³Øf¼ý±Ã?a_£VÞ¿}µt³‚¢â™ß®Y !®Y gdܯ¿é ÔÔTéÌtV‡?\¾öù¹Kæ9ºÎšŸþšy÷Iö—CGÿ°yä.ÞLÛ›{{‡Þ{àHü±ÓÂ6ã |¥XLEEùŒ©cÎü³5 ,+›u.á·o2g:[x÷Z”1í&;?yò„üìÙ³ÒÒRûÉ΢LÓY_M˜¬o`¤o`ä»-¤¥¥å`ÔnáýG[Ol>LMM}×6„¯× ·ùü-¿Ÿ=)®Ú¬Æ|5xÈP5u ›ýB çÏ´êP˜ÿÏâyìöéø«_)®ùŠÂÖÞéêÕ«ÍÍÍÒœ)Ñû÷ï™Læ¸ñöíuÀåkßÇÀøêí£ÇŽWTTì­×gWØ„ЯgŽtîµñ±ädhÔ×~Ê4!}:ûJ…Ø·'0ûÃ[ß­»g))QúCHTM5+",@”Ém¾²ãp8ägÏž!„FŒÛå:„àÝL{ðôñ=áý­ml±&} „êêj[µä||/®ÚÆÚLÄôëg†Êýø¡U‡Eóª«Y§ã¯n%®™Šh¤•uccãÛ·Ò¾"Eff&Bhà ‹ö:àòµoii9|`¯“ýðAÆjƽHCúÑBÅ…ùNØžQc:¾Hg_©WÓB“?ß*wôØñ¡wo‰2¹é@s2™LÎÌ̤Ñ4{KæÀ¼‹>b*+? ïOÓÔÂP””¶455н¶öF..Ìojlì°fI0dŽþûÙYóòåKEEÅþ¦ƒÚë€Ë×>$Ðg×>CGܸ—•]ÄÎ.b#„xÛκ@K‹ÞaŸÎ¾R!JK‹Bã­ú÷ÓSìÛ[Áä ò`u„ЧOLQ&WUU304!gffš4—Ð çÕ¬*þ¢¼A²ãØé$…oÝgþqó²”gÝ·¯)EIéåË—Rž/Åû÷ï M„=‹Ë×þ÷s'B;Ãû+((”–us@Q2¡½WŠ¥psSöÛ¥+œŽŽ.B诬’ì"öÇâ–œ¶Y-§DÔDî7` 9/?_riúëÉ}þ#­l$4#I°þÊîèéKd2Yú‰¦H¡èéäçw}MHNee¥¦–°;@ãòµo¨¯Gq¹ÿne¿~µõ&|ì`·V;IÉ mE×Þ+Å®Çûñã¿—W|p¯õ cÛb¦N›J<×µJBZZtrUU•º­ËCw÷öõ‚¼Âüö„lSPPXµÎWB3’qã'9•H"‘¤Ÿh4‹%Í9±X,uua?\¾öíBQA õOÝ}öôÿv2~úÄ45ŒºÿKÞ?óóròórôô zõêݪ1?/Gô™¶÷Jgº-Bý´wgmMõ«¬çOµ¾i™¹…%Bèö­+¼üõöÛ9ÄÂr×>±ÂKŠ ÙlvYiqrbü׳ìD,F]ƒ¦XÍbiH,ξóôñÙ°üáý?õû8/®ÿ¦’ã òsù[jªYcmlÝÿóýÛW¼Æ˜Ÿ÷hiÓ¿^¸¢;ó?qJì/ ß,™ñ­ûÌwù O &ê4Mˆ3Ù¤¥ÓGEEUH }í… Œ%‘ÉgNÆÄÿzÜaêŒÀ¨óñ¿ðþz端2Ÿ#„6oXÞvZþF!»8Újï•®^ç÷©¼ìÂo§¬†öqtš¼ûçØƒÿw¬ÉîˆØþ›Ö~· ¦š…Ê-åjji'¤=8q$úRÂÙÈ=?466|Ñ»ÏX›‰›·‰XL¯Þz¤16¦ÎøfÕFÑ_ƒ(°Cã$tVÄùs'7­[*JOŸ­Ák½¶I¢IÛ²ie ‡}îÌ1¼ ?UUÕM›6Ëëµ[†XXöëovàH|Û?Iôkß---S&Xˆr ×ϱç\fÌ“BIb·'d;ùÑý?êð®¤sf¸-Âv` §¥E_ºbê‘„Ò’¢÷oE:€€)((lðÞÑa·ƒ,°“Rä”\ÞqNQQqý¦ívûvÍ&Ém@¾¸ÌøZÈÁ%˜õÞ;äú.”ŠxТ\ZD®ÍhKÈ×^”Õ[lmýªEíu÷E3$¹¥3‰^N<"ú„>°h¤O _{ÿDAøš¼/š!9]ÙÄÙ‚‹f´%d Í\Ç™-h°h€@í- `Ñ Éuœ¡vÐ`Ñ €ö\@#Æ¢’÷8¸€‹fÑv‹fHÞã µY@ƒE3„kµ€F˜E3D€8kµ€‹ftˆ0‹fˆq†øÐ`Ñ QðЈ´h†ˆg¼4X4@DØ‘Í1â !4ÃmѰá£aÑ )((„ì98mú\¼ '¹<É©-EEÅÓñW`Ñ àîÝÛ¬_Ouý†#Rv5íÞ%ˆÄÐÈdÙ·ë;ì&,Î~‰Ùuüøq ]z»gâr¹...üö¨©kà] ³ÆÆ†•¥¥Tj»—Þ]Àf³Éd²ÇŠµÂ» ‹³ÌÏ‚ƒƒ]]]ûôé#ÖÚz´ªªª˜˜˜aV“¶œB\.7;;;""bãF1_@°‡SUU--)î°[Ç+›^^^ööíÞRtÖÛ·oýõW¼«€€²+ Îq ˆ3A@œ@555ëׯ722¢P(=öÈ*1Ç^ï)|– ‡óôôŒŽŽöóó«««ãÝ‹·§sœáõžÂg z¸K—.!„V¬XA¡Pð®7bŽ3¼ÞSø,AWYY‰RVVÆ»<‰9ÎðzOá³=o é?¡œœ=== …¢¯¯¿téÒÜÜ\þIH$Ò‡¬¬¬xÕÀ«««ÝÝÝÕÕÕ FXXB¨¨¨ÈÍÍMMMN§{{{·´´Hý%ŠDœq&ð=ø®‰òF‹þžÂg z8Þîrrr¬¬¬RSSãââjjjN:•œœWà§öæŸ:„PDD„ô«jõX²d Bèøñã¼–ØØX„‡‡ÿÄÄĶƒ\¼x{𙙉RSSkÕbnn.É—"€ŠŠÊZ¯mÂ2k½¶Ié@ ''§¾}ûN›6 !äïïÏd2÷ìÙcggG¥R'MšZZZÀ?É„ † ¦®®¾mÛ6„Њ+<<<ø[Nž<ÙÞìDœUb™/2âÚµk!ggg^‹««+BèêÕ«üÝÆ×vZ[[[ìÁ€Bµµµ­ZÞ¿/’Å@JqÆÿ®‰øFwç=홟% EI[[§ã~@`W€ Îq ˆ3A@œâ @g‚€8“?ØÝÚcbbÚ6âU²âL^544à]2âL^ìß¿ï*!gòjðàÁ¡¡¡, ïBgòj÷îÝåååáááx€¬ézgµµ5¬ªJI—ÒÓ(«¨tç\eWWW;;»ˆˆˆµk׊ñò@,ÊþùkíÚµ°sF¼ôõõwGÅQ(Jíuè8ÎjªY{‚6œ8qBœu„FöÓ©¾ýͺ<ÂÞ½{G)ÆÂ@÷}õÕW†††Ä»B¹|ù²òÖ !{¶×¡ã8++->qâÄÚµk­¬¬ÄZ[VSSãééyÿîÍîÄÙˆ#-ZtðàÁM›6‰±6Ð}$iÞ¼yÁÁÁxB(†††Â;ˆzqí &Ì W›òòrOOÏîòûï¿v(äì o†††^^^°)qFßÿ=NÇ» ðq&÷h4Z@@ÞU€?ˆ3ùÃår¹\.‹§§gÛFzy³šššõë×Q(”žptOSS“ÉÄ» dšLÄY®áééíççWWWGì¥’ŠŠ ÿ^½z½{÷ïZ€ïµUZ&ãn‘×» _ºt !´bÅ …‚w-’ÒÒÂö÷÷ŠŠªªªÂ»:‹<é/gÈkœUVV"„”••ñ.D"ª*+²?¼ù'7;ãÙ¼kà3_Âe“·ìJúö4''ÇÃÃCOOB¡èëë/]º477WÈ$Búóz~øðÁÊÊŠL&ó7VWW»»»«««3Œ°°0„PQQ‘›››ššN÷öönii‘Ê;BU•á¡þãFõý˜ýNšó€ð3^ÞsÿƒÊÉɱ²²JMM‹‹«©©9uêTrròèÑ£±„j;‰ðþ<{÷îOJJâo ðöö...vttôóó;wîÜš5küýýKJJœœœ""""""¤ð>ð‚,*"¨šk—=Ô‹/H$’ŽŽNSS¯±¬¬ŒB¡P©TlwPaa¡§§§––•J2dHxx8‡ÃioÀ¶[»ZµTVVúúúšššR©T:>kÖ¬¬¬,QF«¨¨X»v­±±1…BÑÔÔœ2eJrr2jE ð3üýý™Læž={ììì¨Tê¤I“BCCKKKÛ;ÀJÄþNNN}ûö6mã„ † ¦®®¾mÛ6„Њ+<<<ø[Nž<)±Šàóå—_Z[[úô ‹̹sçØlöôéÓ Fnn®££ãüùó™LfvvöÈ‘#7oÞ¼k×®®Í®¼¼|̘1111aaa,ë?þÈÌÌ´¶¶~ýúu‡ÓΟ?ÿçŸþî»ïÊÊÊJKK=ŠÚY@‘ÝvvíÚ5„³³3¯ÅÕÕ!tõêÕîô7n\Ûimmm± @ÕÖÖ¶jyÿþ}_FGêëëÂCý‰%Å¢££»ü­•)üË ­ï¾ûîÁƒ'Ožœ={6ÖræÌ„ÐòåËBÆÆÆ/^¼ÀÚûôésàÀÓ§O9rdÇŽ]˜W``àÛ·o£££gÍš…:thTT”““S@@À¹sç„OûÇ „´´´B666/^ìB b$£q†-Tëè|¾ö¸¬¬¬;ýž „}!%%%-]{jnjª¬üÔ(Ú%ÿëêê>}ú$¡J¤iôèÑ^¡'›7ož——WZZZYY™®®î‡>|h``àèèˆjii‰ŒŒÒÔ$,7ýüü¬­­¥VÀ‹ŠŠÊ’%K¢££Ïœ9ãåå‡Zºt)¶ ËÇÇ'22rùòå !EEE!Û΄+..Fõïßýÿº¡(Çl_¸paÇŽ/^¼téÒ¥K—´µµÏž=ëààеJÄB&¶aÇŽñïÈÃÞ”””^ ¶ýûª­Îö—)zúA¡ûo?~ï¾ÜSI‰Šw9 &}hiKöœÿï¾ûý·ÑöÌ™3$iÙ²eØŸ°Æððpccc…¢¢"áCak¼|ì'ìÿû’’6›ÝÒÒÂáp°D%{÷îËd2Ÿ={¶páÂŠŠŠo¿ý¶³¯TŒTTUÈã'N¦RUp,!dii‰ºrå o«a`` ƒÁðóó»uëVSSÓ7¶lÙÒ«W¯ö.ìÕÙþ2BM^T}**,È“è,ÌÍÍmllÒÓÓ;öæÍ[[Û~ýúaª¯¯G|›Û[í¦o [òâRÒj-Û<‡-v™¥¥ell,B¨°°ki»€"åL&ùyú“Ò’^Òbccííí,X@&“±õA“'Ož899-\¸PMMmñâÅ...?6228BgûË,^¨õúBw|)4­¦Zâ7ÐÂÐÖ­[‡Z±b¯[á ª¯¯¿{÷îÇ…³hÑ"„ÐÎ;«««Ÿ?~÷î]þ¿îܹÓÒÒÒÇÇ'<<¼°°ÍfÇÇÇÛÙÙuXáÔ©S¯]»V[[Ëb±°{$N™2ûSÛ)¨®®B†FF —¬Ì-å¶÷ïoBñññ\ >ض‰=Û{Û þÒÕÕÕÓÓ“JýwIíþýûxW ¸\.wÞ¼yƒ)ä'“[ÊUQQÙ¶m[wæR__¯­­ÒÔÔÄNLÆ”••Íž=[UUUKKËÃÃá”á IDATƒ·òÈëÐêissó† ètº††Æ’%Kxw2äŸQXXبQ£ÔÕÕÝÝÝïܹ#p4þ§7nܘ1cNWPP022Z¿~}ee%ö§ôôt{{{Öjòn200X䱪½÷ÜÉÅMQ“¦ÉbÁ]šd‹UÙ»wïýû÷oÙ²%44ôÈ‘#xWþ¥§§wùŠà†ÄHYYYàŽlƒqþüyþîÿ/µzª¨¨¸oß¾}ûöµ×AYYÙÇÇÇÇÇG`B······8Õ°aÃnܸ!ðO’SXð¹_¿¾?¼•òŒpõõuÅEØæƒýû÷3™Ì¾}ûâ]@!sssVUeQa’ÀápÞ¿}E¶°°xÿî5œ(SÞ½Éâp8æææ¼uuõÞ½{ãXà±°°@½{“‰w!ೂüÜÚÚ²¹¹yccCîGIøºû©`? k,,,Èdrú³ÇxÒ]\Ù¾;J¦p8œÛ7/O:•Œrvrºwûzs3œ,2_<+-)rrr»Ð.ggç–––ki‰xÒEUUU‘‘‘?þø#ÞµˆÁ_Oî—••L›6M!4cÆŒ¨¨¨«i‰Ó¦·{Ÿó¿ÿþ[E瓈¤ººº½?]üýŒ¢¢âÔ©S¥YèKKKËáÃOŸ<4oár&+åu›ÍnnnF8pàØ±cò²@¡PŒŒìß?uü ŽŽŽ«««"BÈÎÎÎÌÌìÔ‰ƒ㌮£;gΜÐÐP‰Ûà 6l¤•M«ÆúúºßÏX°`ïª@6­Y½zåÊ•éO†YZµýë£ §OÆH¿*Q456ž<¶¡fì©©™ùD{ùø¿“®£³â»­Ë™¥i)çׯ[§¬¬LÂönDFFz{{߸›ÕßtPÛQ**Ê‹á(q£ijõ10nÕwÌÇkŃÆŽ‹KU@Duuuúúúãí¦þ|ø,ÞµtNTDPx¨?ï©’õöã÷zú8–Ôá¡þû÷íúð჉‰É¿qVWW×»wo»É.Ñ1Ý:t»¹yòxs u•ŒŒ ¼k ö÷÷O¼üpØðÑx×"ªjVÕ¸Q}«*+ø—,[¼ûg¼JꎒâBÛ±fnn³ùåÄ»@ªªª¯¯oRÂÙŒt¸unNŸ<ô1û]xx8Þ…‘lÚ´IO_ׂO’MÇDµÊ2„й3Gåô$‡ðP.—Œ=ý|Íì³ ö÷Æ©°ž®šU¾Ûª“ÓäÉ“ñ®ˆDUU58(èуÛçÏIönâRͪ:r(²m{SSãÏ?Éß.ÎŒg?wbÆ ¼ ç|Ž3UUÕCB?¼sòè~œÊëѶù®©­©ß»ïB@',]ºtò”)þ߯û'7ïZ:&pÑ #w hÕ¬*Ïo¿655å?øÿ®¨åîî>kÖ¬@ß·p>št%œ?“x!.$$dÈ!x×:D"zð'BH‡Áø¢W/sss ‹AƒÍž=»›ëu+ÎxØlö›7o233Ÿ>}Êd2Y,‹ÅÒÔÑÏ~Oüû«0Ī(¢Ñh:::FFFØgcbb"/×_2‚ÅbÕÔÔp8éÌîÈ‘#ïÞ½ëÚrPwÉdUUUI\>^×2vìX„PjjjÛÎ, !¤­­Ýµ8ë°l!„ÌHII !ôäÉ“§6¾(Ý„ý&™L&¯ÅÐÐ!”““Ãk©©©AéêêòO"ÊàuuuZZZ ¥´´”Ëå¾ÿ!d``ÐÒÒÂårÙlöž={,--UUUy«eØZ[â¬Ã²;|>Å0I$ÒôéÓO:Åb±„ÔÓîø¢ttö›äp8¼l¥‚‚‚‚‚™LæßHÄ?‰ˆãc M‘‘‘\.wçΡíÛ·cÚ¸q#Bhùòå999l6›ÍfóB­ qÖaÙ¾ Ÿ}óÍ7:::X£¶¶ö•+W:û>@œ m“úúú­–×þ×ÞÇCµÿÏû>–¢t¥nR–[–”ÜÒæ¶_ZT®´HI˯Õm¡E%mÚ•¤¤Ò*•ÖÛ¦…B)-²e2–a~ßi²Œe†3Æûùè9Ëœó6ÓyÍY?ŸFßÂÃëׯÀÄÄ„Ífëéé—ˆIt: ˆÁÏŸ?óŽ3ⸯ¼¼œ,((àž¡Ñ²yh47ÙlöóçϧL™ZZZ$€q†ªŸÐÆVCð`!$"0ÎB"ã !$"0ÎB"ã !$"0ÎB"ã !$"0ÎB"Bð·ÑVTTÄÇÇ3 ƒQTTL&Sàk6òòò JJJzzz:::í¢«DŠÌÌÌׯ_geeååå1 ))©œœœ6®¡°°ðþýûÞÞÞm¶ÆÎ;3™LMMMƒN: vÜøûêÕ«ØØØÛ·o?}ö,';›{’f·î_>äB®{ß>~xÇ<ØÚØØÈÑÑÑÖÖV\\œÄÚ0HNN޽|9á΋Å=ÉÚÖáeâ“6®§¨¨@\\BFF¶ÍÖhô»Ùí›W¸ÇÐh4[[Û‘#G:::êêêò¿ ¾â,+++((èøñãŸ?¦P(ØöÐé¥×ÛÀȤ¿’]N^AN^AZZ†ÿ*…›Í.,Ì/)f¤¦$¥¼yýöÍ«„[תªª¨TêèÑ£.\hmmMv™¨­}ÿþýÀBB>~ø}M 7îg¦×Û°«†–2]EJJš”ÂÌMºÙ ¹qËÞ6^/“YZXÿ9#=5%éeâÓ—‰ß$¿===ww÷™3g***¶xá-Œ³‡nÚ´éâÅ‹0Æyê ›¡Žcdåä[\‡Hb±XîÅ_»|>"üPyyY§N–/_>þ|*OYо¼¼¼Õ«W8p ººÚyâ4 KkǑγgu1Š /]ˆ|tÿι3a4ÍÃÃcýúõJJJ-XT³ã,==}ùòå‘‘‘jêÿžþÏßÓþQQUoÁŠ;”ÊÊŠK1‘‡÷ï|‘øXßÀ ÀßĈd…ZK^^Þúõë÷íÛ'-#;iê,7w¯Î]4È.ª6á‰3ŽÏŸ>ÜxúÄa6»zÁ‚Ë—/oîžZ3⬺ºzÙ²e[·n•““÷˜ïëöÏ"²ö“Û¯›×/mZ’dooêÔ)²+BvþüùéÓ§ÿ(-ý{Ú? ¯¢ÓUÉ®¨~Bg„œìoÛ6¯Ž÷ãÇOœ8ÑÔÌêòÍç]4º ¨N@¡P6ìY¶jshh舑#«ªªÈ®µÄ£G  .!sõ¿¾Æ¦d—#jX >{é“YneeõåËÞ3óгóçÏϘ1ÃÖþϰÈkò -¿ñà1ß7hÿ‰ׯ»¹¹áQg»“šš:räH%e•s±÷{èàFPTWãs±÷YUÕ–––¼­Á8{øðáäÉ“MÍ,÷>#&&Ö E¢Nc&-_íºråJ²kAÍ““cooO¾ü@UMÀÏë nšÚg.$0ŠKìíí‰G'ëUœåææŽ;¶‹†Öá° ’’R­V$ªáîé3iê¬M›6={–ìZP“TWWO™2¥  ðtô-ÍnÝÉ.GôéöÒ?÷áãÇÙ³g74Oýq6kÖ¬¢"FHè9<Æl3¶ì55³œ3gNff&Ùµ ÆÆÅÅùù÷èÙ‹ìZ: ƒ¾¿/_µ922òèÑ£õÎPOœ8p &&æÿÖmýM·OëV‡¸Ðh´{ŽWTTº¸º’] jÄ›7o–-[æ4fÒ¸ñ.d×ұ̘½p°Í0OŸ>ÕZ;Î –/_nneó÷t6)ý¤¥­³Êoû͸¸¨¨(²kA¼Ì›?_™®º}÷Q² ép(ʶ £l6øøøÔZ;Î6nÜÈ`06ìi“ÚPmã'Í04êç»lYEEÙµ úÅÆÆÞŒ‹óö]'!!Iv-‘z§.^KVGFF>|ø°Ö¤_âìË—/;wîœ8uf’…J¥®\»åý»wû÷ï'»T6›íã㣫§?aòL²ké¸fº{u骹téÒZ㉳]»v±Ùlï¥kÛ¦¦?JÖ¬X`ñ»VÏ®âÚêÂÕv«¶:…¬’,ÿ°³4$pÇŽêêjR @<\»v-99y‘ÏZMð-9£&’’’žçµ"!!áÉ“_š½ügeee‡1jB›ÝA³Ê×óèÁ ù¾)¥9xéOÓgÍûž~åÊ•ÆgEmkïÞ½»h8ü9–ìB:º1ý­  ¸wï/-‚üŒ³Ó§OÿþÝuæÜ6+èúÕ˜8Õ†mOÿjÈ0§®Ýj}Uˆt™™™/^œì2»Y»fxRwEü¯WVVnÜ×ððpî»j‰³:ºýXñ³ŽfaÞ¦[—˜˜˜ó„iW¯^åq4j{QQQUUU“¦ÎjÖ»ð(¤Åx§Þ„É3ÊË˹[Ùª‰³²²²øøx{‡Q­QMNö·¿ÇûMSÒÖBïêåhÎ$îyˆAâÅ’âEž®}ºË™è©îÛ9ÙßÜg8÷Ö–5êEÿwÍâFŸÖ~ûæ•¶:ÅXO¥’ëúà÷ï¹=»Šÿ¦)™ŸŸÙY™«|= SúMSrÈú!{¶ñ8WU÷c­5†QT¸qÝR뺿iJõ¢Ï™66-%¹YŸU-ö#+++oܸÁÏB`ÅÆÆöîÓ·¹MËâQH]9lþ“]ßФsË—/sÆÔÄÙíÛ·KKKmíù\A½öù¼ó0M\\Âcæ_ÏŸ>ÎCüaÜÛö€5sæ.~’”5ØÎaÓzß Ñÿ·t®×’ÕO“³m†8†ìÝ~pßvÞkìݧo¿þ…ùq×&÷Åè‹5Ôa®úõK†Ë§±“ßæÝ}œÞ×ÈôßµKvnhÙXPð}ôðáÇö¯XœÎˆˆ¾•š’4æO‹÷io[¶@021SQQãþª¹JKKoß¾m3¤ÙÛ…´ …bm;üêÕ«œý›Ÿq&))eniÓkýc°}WM­®šZKWn¬ªªÚ»ËŸ÷ü,÷10–••›ïµ–z¹9OšÆ=æÌ©ÐFW:uš;œ‰ø9ç¹3á0qÊLÐÐÔ¾vçÕóA4­s â>»“á[öîØ².ý}êÒþ+!!ÙGßhíÆ]%ÅŒíkZ¶@ R©ƒìnݾÝâ% Ázúô)“Éüúž¶žñ(¤¹¸[TX°jÙ<‹~Ú=»ŠôTœú×иk¡®–A6öyyyoßÖì7ÔÄYRRÒo½ú´RËœóqÄ‹'ÿÝã=¿ÅÿR•hÒ³´ôG­1Ü=Z6däè ŠJñq—¿Ï€ŒïŸ?}Ø¥«æ`[¨ªª:°g«£Ýï½µeµÕ)ú: •ÙHkJ ¹v9ìœ8c˜€wã[¶@‚ñ‡ôôÒÒR~‚åõë×ÐGߨ¡ð(¤eæÍ™tìpðTW÷çorŸ¿Éñö]~x~t½õ ))‰¬‰³×II½ô ø©‰ÎsìÄ‹ÂÂ|Þó+(ÖôI%.!QŠòFW*%%=n¼ «²2úL8œ:MšN´ºqφµ>†Fýâî%§c¥c@‹ïóÊÉÉ€Af=uºÐztëÞ‰Ú§»?-¦ÛÛ ººšó˃ȕ””DWQåq…´Ìƒû·`°Í0E% IS3Ë¡çšøÞ=tÅ%$~‰³ŠŠŠéé½ôôù©‰‡bF÷ %%z+­¨îoú\T8…B™0y1‰¹jÝ6 Mm11±œìo¼EÄ(g—ž8¡¢¢O“³Ó¿±>dU}Ì®&~L>fóu¬n/}À8©©©¼Ÿ–Á£–ékd £Ìr}îLؒ⦿—&.Þ½Ço)))Ä  Øl¶²Jkõ:óôñ}î¦f–­´¢Zz阚Y&¿N<}âpú»s+-mbR“ œÖ_o\»À{QÚÝ{À‡iÄàƒ{¿|ÃGŒƒÿí ÑP~~#;³¨mä()óêHBZæÀѳ“þž¥¤D¿~%Æk®‹E?í;·®5ýíÊÊ*……5»T`0 /ßZM›Ý½s#óëçÌ/Ÿ¶l\)&&öÏüÚOZµbmõòù0iŠg¼µ­ìÚîWVÆ|üèîó'µŸe­eŒóTعuý’â7É/?ºË=u±ïz}C“ k}BölËÎÊd±X¹9YÏŸž8Ö–Ÿâåä( ñí ÒÉË+ð˜BZFM½³ÿöÄ”¼Ë7ŸvžRTXàëÝ`uÉÉ+pnϤÀ?Ô;w•ãùUñÃÝÓg™÷l ½²2枃§Ûlï FŽž ¨¤Ìd–Ê+(9Ž3~s`ÈðãÂC÷›õíz*ìົx/Çc¾ïÌ9 ïÞ¹af¤q xë"ŸµÜS•”£/?X²Ü/&ú”­…žž–ôèáæ7¯_Z²ÌŸâ©Tªz§®øä¦½GdI÷IDATPQíª¢¦Îc< ᓾ¡‰ÿöÈɪiÁ”¸S÷^5õ.r j5ó“ÉÌÉÊ”øß¯ÀuêÜõXD=Ö½TѲ1}¨{òøÑÝ k}*+*F›¬¢ª^Ÿ÷èÁãG÷Fœkùé3׉ÃgÏ]ljfY]Uºþ°JL204yñüñø«6C)”ú`UV¼Ïuî !Ä?< i™æ-=v(Øâw-£^ôÐÃÁ3f/ؽÿ$1É{ˆå »yî“»w¢6åOlä!ÁÀ£x¬×rå »zßÕÇÀødT\Ó×ÒŽãŒGZヾu@­g­+˜Y!níxï ¡Ž ]…I‘gñ B„¤H¼²‰g!q†ÀÛ·o7oÞlnnN¥Rºc 9Œ³vŒB¡P(”Z=r#É*©ýrvv.++;tèç‘ ÔîीvÏÏÏoÚ´iRRØv3_8mf¡ö ÷ÎÚ½¯_¿îÞ½›ì*"ÆY»×§OŸÍ›7c;B5é`óþÝ›ßZÚ‚%jHÿVD{,|ò÷÷5jÔ¶mÛÖ­[ÇÿÒ?ò¿çž;FvMÒ[ߨ²¢üÐþ@² iœŒ¬Üä¿›ÔZãqv'þêòų¾|Á80++«=GbøoäÏÉÉÉÖÖvûöíóæÍSSSHm¨e–/r½r¥ž§Ð?deeK”¸¹/jtÎÆã,-5ùË—/iiix²Y€¢¢¢¼¼¼˜¥¥i³tëÖ­ýû÷߸qc``;ø±a?ž?¾¿#ž f166Nz•Ø”9›zeSCCCZZš’Ð/”••¸´~ýúM:uïÞ½ÞÞÞ\,j†[Š`5ýÆ#¼ "6nÜH¡PðôêÈ0ÎDD·nݼ¼¼Ž=Jv!‘ãLt,_¾œNo£ÞƒDåêDíÆ™èPPPX³†¯þ¨;2vÈ® 5ƒ°Ç™`!k-­½üü²X¬ÐÐPŸÚ ´×ÝÞ<==q#D>³Ù8"òÈʈäää>}ú¼{÷nìØ±¤€P{Ñ¡ãL˜÷bX,Öù¨ð¢¢Â«W¯’] BíC‡Ž3áDÙ®íÿ~üðŽìZjOZ÷ÜÙ«W¯(ŠŠŠJEEgdnn®¸¸¸¤¤d^^dffzzz*))IJJêëëoÛ¶­ººº¡Ö=ÛUkLaaáÒ¥Kuuu%%%étúرc“““›²´‚‚‚yóæikk‹‹‹+**:ôâÅ‹Àu _\êb±XQ¡C¬úxÏŸŽY†8ˆÿxÅÅÅ®®®²²²ªªª^^^,‹3ïˆûírrrªªªðíÛ7gggYYY:¾xñ⪪*Î[šµ Ö³¾}ûZXXäççÑ@ˆˆˆ`±X£FRUUÍÈÈppp˜4iR^^^zzº©©é’%K6lØÐ²Õ}ÿþ}àÀû÷ï`0·nÝJJJ²°°xûöm£ï4iRpp°»»{nnnNNκuë:\¤­z© ƒ 5ÊÏÏoÉ’%YYYvvv;wî$" š¸­Y³fñâÅYYY¾¾¾sçÎ]½zuvv¶££ãöíÛ·oßNÌÉÏvD®V?ØtwwðàAhhè¸q5}͇‡‡ÀÌ™3@[[ûÕ«WÄx ={ö„……~üÈ™ZRRjjj.ÍÂÂ(ʨQ£Ž?Î`0x¬”¡¡¡ðàù'C£~Mü¦ØÂïµ:¾hÑ"ÁþWaÿï`aa!1X^^’’’Ä`7¢‚‚î·×ÃY`£ÛQëÕ«—ó×zÓ)#‡í<ÁµGÏ^Äœ­¾w&--íââîååuâÄ ˜>}:•JŸÀÀÀ™3gFGGkjjFãq¬¬,èÙ³'üšYÄI:ÞΞ=»jÕªsçÎÅÄÄÄÄÄ(++Ÿ:ujذa-«¤‰(Ê¥Oï%ÄíØ²î¿‡ ¼gVSSKLlR»BHNNNRR’ì*øbhhäèô—×’Õ<æ1Ĥõ PTT$^HHH'•š¸‡œ·×ÃY ?Û¹Úâʦ»»{PPPhh¨——Wxx8…B™1c1‰ØIÙ¶mñÉ6Úªš„„DEEEEEñÔ:ŠQSSËÌÌÌÎÎVQQin‘;w ILLܲeˉ'fÏž‘‘ÑÜå´€Õ !Vƒ†4jâââÄ¡"•J•••ëÒU“ìBjkîFÔ(~¶#rµÅS–––‰‰‰‡NII±±±ÑÑÑ!&1™Là:Ý~áÂÞ‹"~1ÒÒÒˆÁøøxî©Äé9b°ÅLLLBBB 33“#..Ü×}ZƒÕ !‘1wNDÝ`>¨UW„DLs7¢F d;"E=ääîîóçÏ777Îxðóóc2™wïÞ}øð!ïåL:Ö¯__\\üòåË»wïrO]¿~½‰‰‰Ï¶mÛ233Y,VVVÖéÓ§mmm­pøðáׯ_ÿñãƒÁ z:t(1ÉÄÄ®^½ÊnýÛn1ÔPs5w#j?ÛÉØ­|)€Àd2‰ ¹•››;nÜ8%%¥iÓ¦q¹Ø¹+++.\H§Óååå]\\8ý}p¯(  ÿþrrr4M[[ÛÕÕ5!!¡Þ¥qÆÅÅ=šN§‹‰‰iii-X°€sæ511ÑÎÎNAA¡ÖÛùA eÊ”‚‚‚Ù³g vHäaœ!^Ú¾S²zÒB"ã ñÒfúIOZ¨]Ã8C¼(++?xðÀÏÏïÔ©SzzzÒÒÒæææ—.]òóó슖.]¬¥¥E§Óƒƒƒ,XpòäIbRHHˆÝäÉ“©T*Þw†xÀ‡œÐOõîþ4«s¬f-_È{ÒBíî!„DÆBHDàÁ&'¶ð,>jgH`0³¹ð`!$"0ÎB"ã !$"0ÎB"ã !$"0ÎB"ã !$"0ÎB"ã !$"0ÎB"ã !$"0ÎB"ã !$"0ÎB"‚ bbbJJtv5¶î"tää奥¥É®tÑÐ’””$» T›´´L§N]‰×T/,Ì///#µ*T¼ÜlDLHæ’]ª­ à{iIñš ŠŠŠPR ³(T‹Åb2K‰o‘NAA¡˜QDv¨¶âb†¢¢ñš ÄX2‹Bu?0Ä·ƒH§¤¨ˆ?ùB¨˜QÄÙF¨ ¤¤$&&–“õÔªPm9Ùß@MMìB€ººzvV&ÙU Úr²3;uêD¼¦FÓÓëš’DjU¨¶Ô·I ¯¯Ov!@__ÿ]ꛪª*² A?•32¿~æl#57j¤aœ ™Ô·Iºººd‚ ËËË>}|Ov!è§Ô”$6›mhhH ÖÄYß¾}?¤§áÅM¡òúå3½Þ½ÅÅÅÉ.ôíÛ^½|Jv!è§W/ž€±±11Xgvvv,ëöÍ+¤Õ…~UQQþðþ­!vvd‚jvîÜùVn#BäÖÍ+úúú;w&kâlàÀ***7¯Ç’WúÅ£w~ü(ùóÏ?É.Õ P(ŽŽŽ·o^©®®&»PVÆ|p7ÞÑÑ‘3¦&ÎÄÄĆ vãÚüª„Äõ+1²²²ƒ&»ô“££c^^γ'È.$ܾÎd–rÿäÿ|fsêÔ©¹9Yq×.Qú“YzîLØ_ý…OÕ•‘#GÒéôðÐýd‚ÂC÷kiiY[[sÆüŒ3GGÇî=z;¼‡ŒÂÐ/Ο=Á(*œ;w.Ù… _HKKOŸ>ýRLd~~ÙµttŸ2Òoß¼âîî.&&Æù3ΨTê?îî ·¯§¿K!£¾ÝúÄáÂÂüñãÇoÚ´IGG‡ì¢P+JHHX²dÉÿý§¥­3ušû„)3étU²‹j9ÙßN†…œ<ò-ó‹Í–-[ú÷ïßÜ…4;ÎjÖ“³{÷îàààüü|íî=‡9ޱµwh>˜†ýtpIKMŽ¿{óú¥÷n‰‹‹?ÞÛÛÛÔÔ”ìºP¹xñbPPÐõë×%$$-ÙÙÙÿiïàÔUC‹ìº„Hú»”ø¸Øø—Þ¿UUU5räȹsç:88´li-Œ3BUUUTTÔÅ‹Ož<Éb±ddd{ëõÒ3021URV‘—W“W’’îûnÕlvQa~1£¨¨° åmRjJRò«çyy9 ¥_¿~Ë–-“——'»LD‚ïß¿oÛ¶í|LLrRÈ+(›˜õêmØMK[Q‰./¯ ¯ Äf‹~£öT µ¨¨ ¤˜QX˜Ÿñ1=õmÒ«OJJŠÀÄÄÄÉÉÉÛÛ[II‰ŸUðg,+..îæÍ›‰‰‰¯“’2¿~åLÒÔêþåÓGþW!äzèèr®gIHHôë×ÏÌ̬_¿~ÎÎΘbˆÀ`0Ž9’œœœ””ôôéÓ²²š~ ZZ?º›ÜÚÚ€¹•õÃ{5¦ŒŒLÿþý úôé3cÆ 999¬âÿYT|ÝSÿêCIEND®B`‚pfm-2.0.8/doc/print_result.png0000644000175000017500000001663710661565715014513 0ustar wimwim‰PNG  IHDRßø"¼{5 pHYsœÄuö„tIME×'"% >IDATxÚíy´U•‡wÝûæ9dz™€„0˜ÐÐ$b[BŒ€sºÕ%ˆJC£¶Ð.Ó-B3GZm±‘2@$A„ÄV A—JÕ‚d&#É{É»·úûr_½O·ªî÷­E¸¯êÔ©SçœúÕ®]U{‹@úÐDä…µ[途ðÕ+®]üƒk*,~è)z = ªs©T¢/¢âÓï;Üï&·=ð¢:—Qg€Ñõñ'«êñÕ&Þ¯Îå¡¥/ýî)4èºç®Ë M‡LŸ©i`e׎í*ÅvìxS×u£ÛÛÎÛ·nÞQnß;0à^]¹4PÞµñÀ©Ó›ƒ|säÔ ï;áÈ®ŽVMäkÿ¹,%­úÆççHšÚ0Ì~­è¤^>ýýï‘•+VXËTWéƒ×Óvž1}ÊqÇŸ¨ë¢4MÓ ZA4­ iZAÓiMëïï¿íG?)—K&½‡üñþÿ¦³­eÞg¾ðüóω®¿÷Ã_ˆc/W]ôÁ¡kYß¾kÏ^Z»ê·Ï ”Ê.[=pç·ÜÛS©ö«ßYìR¦»£õ=ÇM;ôÀÞöÖ¦Ý{ö¾üÚ†‡ŸxnÛÎÝŠmv¯Ü×áGR¤Ä³Q‘ܪ¯¸ÿçÃN«3άú!D×uq°OÇöt}ÄdMÓ …Âþ+?µ‚aÑž¾¾…mÍ¥RI+ Î9§³­EDžnÍ„Cÿ¶¡©õÅ?>5uÚ11íëØN‘žîîK.úܧ¿wßî­>õ…BÑZò²›î~xÑ÷ED¥=ÞõÝ÷œ{¡½4w¶}îC§–uýêß{ü±_>uÊ\1ÿü¹ïºé'+ö (yí\*÷K„UA*$º¬ß¿|ùgU‘ãû—/¯,¯,‘û—/×ËzE¤Kž¶³VÔ†!ƒÿi…Ö ÿŠ`;ç€ë.ýˆˆ,|àñ“}Û¨žÎ»û~ýô <ó¢q­ˆ<ùèj¹äêÝtù'Edñ/{Ê;¦uw´mÚºãGþpð„Ñ3§MnkmÚ°eûÒ‡žzuÝæÀí9húq"r׊GÎ8ý½ïœyÔõߺù¨ãO»þ‹‘e«ž>ùØ·uu´^öÍ»®»ô#rÉy眹ˆ\sÉy"r÷/ž8õÓFt·oÙ¶kéÃOþyíFSãÿeÁϬ»;í¸im-_¿ú†_­zh̤#6÷7ÿø®û.½pÞÛ§ŽXñøŸÛ»z*•T·­þi[¹Jg*V÷l ÙÎ?¿ïÞ3gŸ]åêïÊòJ]t]o¿³¥ªÌ…a2-š¦‰a‘®ëåR©Œíœ Æ´îûú ·¬ýó³—~þ3³O>qËú×׬Ý!"_ºþ§7|ùãU«VDäòOŠˆöÖ†/ý‘­rÝUóÿqÎI¿züw|ñkF¶_Õ¿Ÿýî#¯øÎϺ¸1:½¥qprnÛôúæõ4voøçùË_þãÓý}oÉ¥‘W×<þî9Ÿ®¬mÙ÷æåW}{lWñÊù—õ®éóozÆÚøjá*‡Kž *È*šÁÁ!2gË–,ªüž3÷œeKU k¢9½³Q6(øð‚RÕæÊóÁ‚á¡ ^*•µB™È—\táîùE{[ë٧Α;.*66 ”JÃ_h3ý©‹f;%+wj%ËËpSgœº¯·Smÿ‰V(h-m]]ݦ½”Ü^³Ö†¾þ½-ÍMÆsÚÝòUOvÐØ‹¿ðÙk¾ýÃþryÊãg¿gÖŽoÝzÛ[:ºµbqóÖ£Ftô®þº«xú‰3ŒÛ:UîÔ™ª‚ÌŠ³Vud}àƒçU.]|·®ëKß]Y2gî¹Kß-ƒî ó¸ÛØÎ;wìܼyÓ ¥\Øo@¾K7èƒ. }}}ý{÷6è¥r¹ÀHä€_ÿæÑ+¿ò¹ ãz·ï|ëû·Ý±hñ²ÆM.Цëy˜?E¤ç€‘"#=ÄÑã&99ïˬÎ{ûûDä±Õ+7®_K$ϘÞIã¦mVçB/JjLEŠ+Ñ9Õ¹X,Ò/µ¥"Å&uÆv¨¹:Ddß>1¨sÛ ÖêlâBU°1Ÿ`Lï$Å…µjLÊwW«¾bæd¥ýcz'¥³[[e”âª:ƒ¹ž+}‘ª Ù’˜¤ÚE¦úÇôNŠä=™t5Ä:XׯMÿÀÅ=ó­u¦çÅ3ã!+–QŠCùMýGwçæl¬ôLåGJ:ŠÁʬtÎü4_€R\¨:;<]ÏVÙ¥£«WKë3˜¡m[•õ·u¿.-QÙ<Ö3ÍzysÚ»i¡©¤ûŸNÝì¨Ý[â÷–Keθ”÷\èkG¾&gJ˯ùlÛlÛ»+N°Ù×Ì÷œZêå“?j[^e°ŒR¬j;{Sñhºl†1®Õk°½>Þܶ+CB€#u_h{ÔN%m›¡nÔx¶ÄoW85Û¥ý.;UìÏÉi\¨"1é,¿·¼*]ê´Ð¥£bšZÖòî=“ðQÆ(Ūï;{îÒÚt¿­´õ¦™z<ØÔ Ü_.#m[Àé\NuÏ=:¾çe&ò›â`-ñ5ŠN$;Rïä–íT4í¶Áî&Ž¯Ž 3ó­Mõ¥ 5<êpê\4«³Ê·‚.]¬¨˜Õ‰â9)qN9]Ýç¢ûlsézÁRÛÅž p×·q”Ì`Er699‹­ïÙQág~b6Y„G£«ÚÎ.÷û¾ŒÙ.Ž„‰£‘É?g÷ôÕ$/Í‘´Çs.ùÝ‘­!£“Ì©áëþ)o˜D~Ôalç°ï;' ³ÆAu¿…I­]é´ •ùêyøINÍڶĽ¯"<ùד|…Ü‹SƒMn™0OŒâ¬ý÷Q‡,£ûð;[ý>Ö×üDNKÂÜÆÚ¾Wè¾_õÍ#Ô5Ïš=›àNßÅï÷¨#é•ö8íÔôê‚SGUPN;²nnõϪKO:+Z‘燜ù 4;±£ŽÊvÌÉ}çÝKDäŠ/~:Ž'Hé¹NgK¿Ó·‰`×Ð9Ì”HÃ`eÅCè9ó땃Ó;iþ·‰Èš^ÊÉMŒºÄœVêËÓÖθ·…¼övVf~ËæÈÕ9=ýž¡€´e¨Ÿ,ˆÛ³QýV°P$:Ü)lO £Õ¤ùéÃKAZÔÙ Åa£ Až®u­&Z‰ÌÁ«cPCÛ9š(H'q©Ïh590]©|©³5 ¶slÖ¨Kà—óÜ=¼KL‘}¬'|ÂÑj²ØoãÝØ…gkÝãyIÐ0UµŽn¶3Y_ã³FìÓÀ¶­8|&§v*sÑjÒßo1ÝÁX[«Òáêa¤<'@$q‚À/A¾äw«Áó#‚±œTÂ&(~Më²¼æÑj2×oQM•ÖúŠDæƒI1¦OTq‚ €ílUglç ¤0r“íýu.£Õ¤ªß’™0á=‘ˆzœ ð¯ÎVÛ™¬¯¹¾~Ô[´ššô[’¾²0‘@’ì7ð­Îd}MÌø²]â9w“Ç”D«É\¿©(iMf]Ê£#‹íì; „™ßÆÙ,®Ð`éˆ<Õ*CÑj²Õo5œQqHJmœ ¼«³% ÒSÏüND>~ö©tk=C´š,ÎŒNžfÎOï]%"Kþ ! ~g ZMÌÝÇÉÌèäÍv.ðΗH¡:Çý­`úŸø«|G'ÄÝ3Á>ÓÊî Œc¾;PåâÈ=¤òÙav;9ÌQ¥˜/¹c”È YëÇžŠvm/x¦…5y1¾„m&ó<kFß—Û~jäßv¶|ÉÍía´)„ÊË8Ú JþÞêõ«tõ!¦þT 9B Q„SÄ÷bN‘_Üo|‚Õ¬~wU,!‰:œK÷Ø6ñŠ{ìG[™¶¾âòÄq r` \ºÑvs•ÎöDH¾*gK§¹t¯éÏHn§"‘f—x(*aY|]Ù‚Õøšïb79MÁ˜Â ¹o«nž$<^IŽ]´ã˜€kÂÓÜ4œQ¬ú=¢ø2TcÛÙ6ޏg1Ï@ìê.$ÅÄ}€¦Â±º5ÂÜ-&<^µ»ðãß­º©=~»ÂØŒÝèrN«â8ânF€É`»\eÐ#÷á FAzáÅ—DdÎßÍJ¹µŸ•×# 'd½¤'³m´fHø×0ju,ù›–=27”¾³™Ö.{è ¹cáÒ¡(H¹™ækyøÂ)ѯ˜î]2:ÜPŸqµÓƒyRÈÍac§YJh:€4‚: Î O!9lßQ«Æ0‡w›¬1ŒËm˘váÙŒhC| Î1ivúøP=îŒXß(†URiê 0 õ¸3(äKøÈ6 ¨3@Fº§ú[ ¸‰G”u†zTϸ-ñš4 $¼³ Q±LÕ3Žô䮿NÍ Í`;C] ´_5¼•ÔëgÄ€:D/ÙV{V±p$Ù!Pm@¡NIÉ›¼€¨3@SD2OPg@Pg@Pg@u@u@u@u„1J$ù¨Hji€¢àO¶j~3åi¥Œ‚^mg€ü³¨3ä“HäÏo%¦+‡ñOäPg¨±Ùh•'Ûå.¦mÉÊÂÊŸ*iÊèÙ §ªL•˜Z‚àê Ùf[Ù².·•<«˜Ú*`à„ÙžÍP×ú=¨3$$ÍVÍk’*Û ‰À] cwê ÙsŒØŠ]LZ¦þJ†zKœ8¨3dUŽ¬ÑøŒM_uân†ZÁûÎJæl=È.…]Š%orØ£í&Nõ`D¶3Ô@Ñ*"ëË a-cÔw÷·8¢rwØîQñ`Uê4vZ€:C>[ÍU—$÷Íý.÷ûÛ½ÂhÛ€:C6ìn@!½¦7ØÂSAÔPg€ì‚ß9“ð"-ê )å–[n¡rÃ\`Z‚g ÆœuÖYØÎyãüóϧ2ͺuël—c;¤‘t©s»ÈMÓ8y²ªÎ!e‹×²"µ(5@þmg¾ôÍ º®Ó ±ÒàiÀ:En4-4•4¥é´Öã”ëÓo–OÏ–ø5ÕU²—ŠWœ_Ϧâ3Mªçhš¦ëzÅŒ5I§i¡©då‡ñOS=UÓØXm¥Xu­í~ Fu¯t™N94­i:JÚ†cWÏò,›§S%¢½T˜ÚÖìwG~Ȩ˜&-vZhTdk=.¿F´írˆÝ³á®8ê±Ì}Ø  ª¾¢ª«{TÂ4Õ6·t|^«DZUXÅ/a\‹ì¤×vމ´=ô ßEwН¥¡—xèPGêl먭4GÒÏ ¡~wTóǤ&Ï' @Š<.F¢Ô:WqÍ[â®°¦´rÉ›öFoÞa€¼ÙÎSpº”1ùˆ] ŠrÂÍ0Ò¬Ò§/.Uùí²#ëæ~=é¶ne§9lEÜ顟ñ·KÔ QuVLgéžT11¨SVËÑå£ç«o¶»ólÓ^\ŽÑ³yžOJ}]rœÄѴܶ˜ñá¡ËæNÅÐe€¨si3Ì9 H[Ö/?X¯ØÎ:œb@ÎmgH3¶»ÛjÉ\`Mu¨3Ô¼ÌùÃôÁêœUn½õV: 7X³Ðáw¨1d}­‹ë-d ²¾d‰|ªó˜ÞI¤%€L“CÏFmÃæÕÑ Î:]¦òDŠ<ÕL¯F§„íBÓVŠåê·nîÔ6ã&ióœèºŽÕ €í£@ûJxj-ïY%£«SaÏÆäÓv…ÇF»Õ)å«Õ²¶ao»¹m=(2Ô»íL¾#,oRdÛ¤ÚáS±dÌvÎÊ"„ySç)_67šÌ*9â ÛoÔ…ô3ØÜWV€<¨³JX÷­Ü7QO«ªžR6mðR@nÀï €:€|ÉmÈú €í ©ƒ¬¯ØÎ:ˆ»€í ØÎ Y_ò}7Œí PcÈúZ×[Èd}È™Tç©I/é'{ž ‚ƹ@ÖWÔR§Ët@žHγwRWcy—¼®¶­rúmÜÖZScj’–¬¯ØÎ8%’Iê*^y]Õ[+ûc‡zæÅñ³%椮.ÒìRƒbk}ÅŒȘíL¾#,ŸÌ!2f;× ¤…€<«³ß¤®VAôUb ©"ÕoÔùõ娸Q\ÇÕ‘4ia cêwRWõòN‰\úú][Qæ¥:€Ü€ßu5ø’;ÛõÛRY_°!uwÛ°A ²¾äûnÛ Æõµ.®·-Èú %j¦ÎÁ²‡d7V‘S[jãÙ Häó š¢mTV‚u"¿2™Rqÿ¬þ&]7@¶ðölËÖjª!ä¶¶iUÝ‹9¥mUÜià½(Þ:ØWHÕÖu¼Ýy³ýfkußÖ=s«§Ä=ѪÊï`‡UÍaöØÎÞwâ~åÕox{'URøFÔÔ· /ák¨ØÅU˜Öõb;§OÝ$k’ïZøÝ—ÉïÌÌ@S!ÍF?€»‰ÆQ¤‡o@ïûÎ~3·:m›9S=á}9ËÆ'¼³€í™KÁšÅU¥˜Ó³D_6©µ¼bc"¿ª©ìËô>†Q…««f€¼©³JnS«}ç”ÕºÄ×¶ê X]6 ÖfQx éRg˜”²*¸ˆ/º P¿¶s2oGDnŸf¢N¨Oˆ‚„È@~mg¨d}Àv†ÔAÖWlgHÄÝÀvlgPƒ¬¯ù¾Æv¨1d}­‹ë-d ²¾d‰ª³KØ Ò­êŒ'JbÑ÷  9ô;×íwØÖL¯€í\cÃÙ”ÝÕi•iÃðiU3dS[í_[CÛ´ÐTÒeCÀv6[Ê.VÕIZU£m2¨«Ú–qÚ°-ÃuZlrq¸`-ã¤Âh1êœ(ùóiTÍ^[ÉÔ9äÕ›ar>`ùd¾F©—·îhlgHÝYuœ¿¡4.W¹2•7ý‰:$zV+®B»3:”ÆåFÕVhÛjó=Pg¨%îw©q>†’qD!W&30”uOPg@Pgˆž f§\ëÀP¶3ÔŒo¸–N`(ÛÒŦ ¯ÏûÄÇè†êBgÏ»˜ 5aö¼‹ÕùSó>ú©y¥j~g€4Ò "_½‚'^ü?¦"„¬„ IEND®B`‚pfm-2.0.8/doc/form_window.png0000644000175000017500000010305711005072201014254 0ustar wimwim‰PNG  IHDRbØa? pHYsÄÄ•+tIMEØ )$1~Àâ IDATxÚìÝyX×Úð3ÙþˆˆŠ@݈€J ¢X•RiäVûa ¸U¼­Uª‚ëuC+­J[¼×ëU‘ZDQj[«¸ "(KD6„ìùþ¦„€ˆ,ïïñi'gÎ9sæÌd^ÎÌdC7`È0ôU¥…¹ª±òû~‡>Ðgýï‹ ªÁ#b$ ‹Êk¡ôYýÍð`‰GJŒˆ‘ž¾€Þð–”^ŽÕ1ç€)¡ï¼leAƳ¤m¥…¹"I®PÀVð6”¥þ85d½Ž™=´yjÈú‡%ÏßmY†%³WÂVð6T^sž¾B÷ü/…•W㦆¬Ï~\ù®Ê"„jž·ËšÑ}êìæë®Z•öj/ýÓ!ôat|7{ðhRç`‡$¹BAÄ&¢lÂbgµEæü˜¯cY"gë”Öeÿ “J¥Ruy˜ŽR*•-Ê‚ÞÁTŸŠ*ÈhàäM¢ë7ßÖ8º›´M{cº¬©ocAݪŸ»¦MÅ·}v++#u©¶âì†~36Â׳×&ñ°"Wü_Ú,Kdh³lÂbçÀ؇¡“¡CÛ,«1L"%ÂH$<’°WQa¯RÐëY†A˜ì¡~ýÊ!´+ùÉì±ìþ&ŒZôÔÝçgî=W‹zpíq`(šfþs_øúƒ-õVy›Jjžu %t*)xÓ±1“?ùöûƒŽ`}> SH›•Je€«e¨·mþãgSæ.ùÇg‹ÝŒÿöà ísqýšóç­?ìæ3ŸŸ´qÖh+ž·mFîãñþË©Ó!^6~öÍJ…B©T®õôžµþúèŸÆO›ý¯ˆÅUEZÒñʽ==xQ[wÝœ1ìÿßÿÊ­ln@HÊÅäÏÆ÷ÿx°ÿÖi™E´ÙgŒã:ÿAb©léê?l\*(¾…§óÏEiÙŽºl-= {%Ú×½ÍE¨n‘ÖÛ«áÁyæ7µÝÞb‡ÔÔ ›p:S;"T¼>Ú:¿j¢–² …"àû;’|ÿ@Ór‰âoRÅJ<|I‹–õtÑûUf_•Yн\„ ïØ”—Ú"ÑH?£‘~øô÷qGëßËZâ÷þ+ª©¦ò~“BȶŸeýÝ…XТøåÕ£‰jÛp<½¢¨¸„æ0ñÊ3 BhÜÈAõwQ*ä»Y!„¶ÿk[ƒH)ï?öàÅ¿ýý¨}.nÜÑú'YúïM69Ýo„ Bè@Ì)I/1[ˆúÄÓ±ñáe¤DLI*“•?¢÷ùXn÷õþóMÒä¢MéD×U—?iÝu~K„PÜþ}ršÉé¬F„ÿûŽ9—”H©e^çì€Ö}:‰_Û¼ø‹wî냑ÈÞë/Œï‡j.½§i;ê²]üGYjêön\MëÞæ"T·ˆÚi.½§½Û‰RKo€n|ÒU©ã¿Ö#HÕYjóëRV5ƒ–Y-F½š¯M’TâaËx‰ˆá%œtíÑ*Ÿ¿ÐsœH¢2_2mBý¬,šŸe2ú Z¨fcعâuõ/ ‡ŽURôð5µõ†ïÆFø>£ $5Oèý4>=ÿyÒFK¿ ª)#m ?ó²l1ŠI£àwJkŸIkŸZ%ZHÑ7oгU-hiDÓ2÷¢¦Ö`¨+™nH5³µ45@%'Äý5Ö´²•ÝgÚŽ<|­<Ä“}üÇ= …²°²!îjÉ­…²Æ*MéÚ»ÎÜ€Šª©«ÓsvmÀôBVæâŠl¦í(-³ð:ÿ¹ôÿ0 í‰9ð¤¢FÏa…aL±Df RŠÔ\yM—íÂ2œ ©t¯¡‰ZÖeHÓ¾Õ-¢i'ÑÞíª;$yzÞIWy;¯MÊÿŠ/ªeÕÖC$j/{!|”j©s+\¦íÍÔ²\¤ë-<èï£HŒ„GN%RB˜ìÑúYY4Ò˜dSK3üð§4ËŸ“ X­wÙWû…F1î÷×G*ü×G¥R&n±?|°ýÖ‹ ›‰¢§tÛ¿vÓu3Ìô©+·üó÷4 Éï\>…a˜R.•‹ª$ýLèD -Ì T«Õ>÷¯‹ ŸŸa/$lº÷ŒàšÚzÕs&²†§îTž8ö?ZÝØQ#¾à-ˆðäŸñf1HSºö®«H- i榦b2ƒÅ¶A=Q£ŠäM/´ÌÂë\þõ¦ïwDn[·Šôý/é%ÍTÖ`ŒÊP*•4‹Áj¿§ûζµ]´ô€î•h_w]AlM+¢½ÛÛìнG“¯ÂÕź©Íà}ïoáJ匽âo£C…–ʵ”%–‹/ÿx!|”–åj¼6‰GDqU’¤ñx‰Toá=ôRÁ?—†Ñ‘“çm‡úßω$S)—¶8 µüˆ‘ÿ~„Vù¨Ts¹ÚxL~¢ ?yû÷= !„êù%FƒÝ¿ZùW- Ù©;•­[¨z0Õ2÷o­%Q”Jå©»•¡ðÐÏú¹ùYþxòœ°Ÿ~8h4ÂÉ¥QæñŸ9<» C‰D¢†Œ_ä¢MéÚ»î×ì¡ó>¡’d¶¡SI)$*Cû,¼ÎÛ¾Ü÷‹R¡ÜþÅ_÷ˆBW*•©kƦ®«};ê²]´ô€î•h_w]A¢h‰sšº](–#„l¬ÙpKDÏ=àÈ üŸ–{|ð¯o¥Q_öâ?ÝZüÓ½,Bhê®;øÇ©»îhY.Qœ¢¶­ÿ½pýÒ­<|Xùú¿]‹$–VZÀÊìêõ[ÿ9¯?«š&ñžÃgOœ>Ïè?#Q;w)TÖ M³¶+ gxhß¶êzÁñ륪GäÓwùò³?•´Û£¢®ùØŸOÇ;™óµÏU7Fá7<Í™5qøù)$r”SÖ˜ð[Vsežñèy—¼X<ÞjØÿV*±ì'ÏwíÜa$YMé¥vjÓµwݯ•É^ùNhZÝ$ýñÔÕCG¦Y:bdm³ˆvf=müj⮥3"þámô˵Ӆ¯fU'oa}´îM¶‚–è¬Ý¦S¡is¹^6 ?£;é_7áËÛ£G““wÜÒé$­ºdç–U[[‹@þê™®fäýmedR¥BÞvC0D¦ÐÜËÓ¥}ã‰9ÞiË¿(VH$*ƒÆD3@6îGÒ7GÕ\ÚŽ2éGµÞÞº—ÞßTH$ºÆ#,ºFÔ£56ý)y^ ‰Éz&ô~Ã…Eè8·u{âFÑãtqU‘BÒD"QÈÆÖ4Ö`Š‘ÅÜ^ü4£ùY¦BP§DˆÌ4aôs¢YR,4¥ã•kê:… FXð‡¤¶T)“èz4³4Ö ’‘ÙÐBË,¢ÁË!¢§™Â¢?B¡7h,ÓÑ[{ßê¾]´ô€î•´±î:/Bmµø´¦n<üU\ž¾Í?\ßâD˜ù‹óôO+žëQåÂÌ_|—V🿫²!YíÓ¿žéÚ"(b$’ŽOjU*.ôdTSš•“RÖŒ D"“ XÓßþºs¤ýÛÑöPCC $*• D"¹ÍBR^FÓcG³rB²f¤DˆL1v TJ›u™Ûº=U1ȃf9I›•H”Fc’ôL• 9Õʉbh¡ R†”F¥“ ­´¤•«í:ŒaÌtšHÔ ™)•ˆD&é™’ôMµÏ"êD$ ½ÿHª‘•R"DH)o¬ÒÞ·ºo-= {%m¬»Î‹ÐT­–ng K·¬”ŠRJ+r(ì÷àûÛCG“mÁòW×_ï-ïªì_£É?n߇Mاü9 Mô#[:B‡Þ*Qö9šÓ3K]85¬²²üÝ–U4ð5Ž&A¯çy¹þÊ>Øú€®1`Jh»^\Qþô—ýÛhòê;°¼=ýlìuÌYYVÒ-Ê kÿM"a-lBoOeAm+‹ÂÆ+4ÛhA¯6—‚º›t¨¶ª O2³´~Ðgñyy oÙ—j/PPPÝ JHHXÊûL5…ha€0 @˜ L&“„ÉiÇŽ»wï†~ЄËåvÙâ¼½½srr ÛÝ1Lúùù9©x'G+¢ ®®®aaaUUUݳ[/_¾ùä¿ÿý¯\®Ó[8Š‹‹;¥ ³gÏ>|8‡Ã ¼}ûv7ì(??¿sçÎ/\¸ðá‡Â×ÐSG“Û¶m{ðÚ°am? VÇØÐÞáÝÇÏ;WSS³cǎέ¼S|âĉˆˆˆ¹s禥¥Ý¼y3""âÞ½{uuuº”ÍÍÍ …oÞ†æææÅ‹O:õæÍ›7nÜX¹r%•J}·Ý½?LR©Túk†!„ çÍ›Çáp¼½½O:…gãr¹ûöíó÷÷÷÷÷Ç??~ÜÇLJÃálß¾½²²288ØÅÅeáÂ… Ý-&‘Èd²Í”)Sžzôh×ÐÐЮæ‰D¢èèèÈÈÈ€€SSS===ww÷˜˜‹¥i鄨¨¨êêêÐÐPooï³g϶™_‹§OŸ666.X°ÀÀÀ@OOËåŽ5JK_íÙ³ÇËË‹Ãá|ôÑGþù§ÚnQÛŸø°õ£>rqq —Édµkªm'ËåîÝ»wöìÙ¾¾¾«W¯‹Åðe¼û0Ù‚L&ãñx^^^111[·nÍÈÈÀgœ>}úüùóøÇÔÔÔ“'O&%%>}:<<|Ë–-ééé‰$>>¾ÃÇÐß~ûÃá „ Çspp¸yóæ™3gSRRärùçŸ>`À€7n¤§§Ï;WÇËd²eË–Íœ9óöíÛ!!!Ä*è(77W L™2¥]ÝE„I‹›––Ðf~-ìììLMM¿üòË´´´šš"]m_!„† –”””™™¹xñâ•+W677·èµý‰»|ùòñãǯ\¹’———˜˜Ø)û¥¦vª***Š¿pá‚P(Ü¿?|™ï>LFFFŽ;vìØ±AAA¡¬¬,¡PÈãñH$Òˆ#üýý‰ òé§ŸR(|ĉ 122²µµuww÷ðð°··×ÓÓóññÉÏÏoo‹¿úê+''§ &Ðh4|L“]VVN¥R---çÏŸéÒ¥¼xñbõêÕ ƒB¡àc)]œ••%‹,X€aؘ1c¸\n»šWWW§¯¯O§Ó‰çr¹\.÷ÚµkZ–®V{ó«b2™Ç×ÓÓÛ´iÓ¸qゃƒŸ={¦©¯B>>>ÆÆÆ†áƒ`âñ÷D·¨íOÜâÅ‹MLLÌÍͧL™Ò JìTcÇŽ]¿~=ž¨©ª‚‚‚Èd2‰D NNN†/3àm ´+÷ªU«ðq…BAÕÔÔ°ÙléU¬µ¶¶&îë133S-haaOÐétüô#BˆF£I$’ö¶x×®]Ó§OÏÎÎ^¼xñíÛ·ÇÏçó%É´iÓð R©ÔÙÙ¹ªªÊÚÚšL&«–Õ¥Á555ÖÖÖD€·±iß›ÅLLLD"¡Ñh¡èèh™L6{öl±X, 5-]--­ÕÅ Aƒþõ¯!„***¾ùæ›5kÖ;vLm_!„Nœ8_WWG&“«ªªˆë£D·¨íOœ©©)›Û{ŽZu§B]¹råÈ‘#øÙµíTEìHæææÕÕÕðe¼û0illÌf³‰æææ|>_¡Pà‡òŠŠ âÈõVa6räÈ%K–ìØ±ÃÓÓÓÂÂÂÔÔôâÅ‹DlCeffVVVÊårÕ#». 677W=KY[[«ºÊm>|¸žž^jjª¯¯/5BxtYºê*tV÷Z[[Ï›7oݺuøß+­ûª°°0:::!!aРA¡©S§*•Ê•XZZ¶îÏN¡ºSWµng åååNNNøD×ìx€>è®Mr8&“§P(rss“’’ˆ?ÿ»@```UUUZZ‡Ã166މ‰‰D¡’’’œœ‹µgϱX,“É233ul0‡ÃQ*•¿ÿþ;~üMKKkW« ƪU«6lØpîܹ—/_ŠD¢ìììÆÆF ÃtYº‘‘QYYÙ›woYYÙÁƒKJJd2YiiiBBˆ#ð:[÷•@ `0ýû÷G]¿~½´´´u…jûóíQÛÎy:ÔÐÐÐØØ‹ÿQÝ+LR(”ØØØ+W®¸ºº.]ºtÍš5nnn]Öt&“9þüŸ~ú‰D"ÅÅÅyzzººº®Zµª®®ŽD"ýøã………ï¿ÿ~BB‚Ž ¦P(ßÿýÞ½{çÎáééÙÞ†mÙ²åØ±c^^^cÆŒ‰ŒŒ\²dÉ„ tYú¢E‹6mÚäîîþóÏ?¿I÷<~üxÁ‚#GŽœ5k–~Vm_¹¸¸L™2ÅÏÏ/88øÂ… jvuýù÷Kuíl‘ÇÇÇ'00p„ 666K—.…/3àmÀÆûÇõ›µU¯F0f–6Äít[\.÷ðáÃC† ®t¢„„„¥¼Ïðéçå%¼e_êzm¿Ô£Gº²õjÛÐõÍèX#»¸ÐWÐ)t “Ýá°Õ#ݤ‘ÐWÐ¥a€nåúõëÐ €.o L&“„IÂ$Ð=©ùAÈÛ~ÐSÃdAAÁœ9sßm³V¯^½hÑ"âã;oÏÛ¶yóæ9sæÀî=`4éèèøçŸ¾ÛfùûûÇÅÅá‘ÒÑѱ×?fvΜ9 ) »!ué)ûBŒT”°G@·“€0 @˜ Lv".—[XXر²ÞÞÞ999ªúD˜üí·ß>ûì³I“&ùûûïܹ³¡¡úN‹/¾øÂÚÚZí¬°°°ââbè"è=a211ñÛo¿ IIIùñÇ_¾|¹råJ©TÚá¥Êåò^Ö-Ö( ÀÌÌLmÎÜÜ\¡P{ô’0)‘H~øá‡ððpOOO*•Êf³£¢¢/^¼ˆš0aBmm-ž3&&æ§Ÿ~§_¼xñõ×_øá‡3fÌ8räž8cÆŒC‡-\¸páÂ…ñññëÖ­#–òí·ßîÛ·OÇFs¹ÜãÇûøøp8œíÛ·WVV»¸¸,\¸P àyø|~hh¨››—Ë=xð îBééé>>>nnn«W¯‹ÅÚ+Ü·oŸ¿¿¿¿¿¿j ‰“®»víòððpwwŸ}:<<|Ë–-ééé‰$>>oÇspp¸yóæ™3gSRRt)ˆ»|ùòÉ“'¯^½ZVV¶ÿ~íœ>}úüùó­™•••œœœœœ|÷îÝ£Gâa°X¬ØØØ´´´€€Øÿ Ç‡É—/_êééÑh4ÕDSSÓ—/_j‰¬•••‹/¦R©,ë“O>!Âä¬Y³( †aæææ...iii¡[·n;;;ëÞî###[[[www{{{===Ÿüü|„PvvvYYYxx8•Jµ´´œ?þ¥K—t)¨šÇÐÐ0,,,99Y{…Ÿ~ú)¾F­É`0D"Q^^žD"a³ÙVVV°Ã@ÏBi3‡±±±P(”H$ª‘²®®ÎÈÈHS‘/^H$’üãøG©Têàà€O›˜˜Ù|}}Ïœ93}úô_ýµ]CI„……>A§ÓY,>M£Ñ$ BˆÏçK$’iÓ¦ b°ö‚86›MLTWWk¯PÓ5H„³³óòåËwîÜYZZêååinnûôª0éä䤧§wíÚµ>ø€ׯ_ BQ©T™L†§7551™Lú!äààpïÞ=<´Ü¸q/2lØ0CCÃÿûßøý/Ïž=S=¥I Ñh'Nܸq£³³31€ëÇØØ8&&F$!„JJJÚõ+ÆC‡566655ýðþ¾¾®°¨¨(;;[¡Pèëë3™L¥R‰222*++ƒ=zI˜D}üñÇ+V¬øé§Ÿ¦NØÔÔôÝwßÑét„Њ+Ž;²cÇ77·W•’H»wï~òäÉŒ3|||6nܨéB¦¯¯oqqq{ϸ¶½V$R\\\QQ‘§§§««ëªU«êêêt/>yòäÀÀ@///6›½téÒW("""ÜÝÝßÿýæææ%K– „-Z´iÓ&ww÷Ÿþö?èæ°ñãÆþqýfmÕ«ñ™¥ö7„üüóÏñññ‡666~óÅ?þ<((èܹsúúú­çž;wnçÎ}ä !!x—¼s,½Wª¼„·ìËv?¬.00ÐÉÉéðáÃÒš_~ùeâĉjc$ðÎQÚ[ðíÛ·¿ù‚E"ÑŒ3,,,vìØ›@/ “…Á`¿Ðº'xCah?5']===»IãºOKºŒ££#ì”ð?ýÐ&ûÎ0º›9sæ@çÀ;”°”÷™j œt4‚0 @˜ Lˆ]oܸa€6}áÞõ„„„õë×÷Ž5MHHh'] L&“ÐËdee ‚vÉÌÌlnn~ÃåvJ%­y{{çäätqr¹ÜÂÂB“ÐÝݽ{·±±‘øXYY™ŸŸßõÍ(,,‰Do{))))³gÏvqqáp8Ÿ}öÙíÛ·ßÆRŠ‹‹ßÞZÀ®Ðç¹\þV‘°k×® 6Lœ8!”žž~áÂ…1cÆtú‚rss…B!„Ièå233­¬¬êêêd2™¾¾þàÁƒ1 SÍÐÜÜüäÉ¡PH¡Pú÷ïoaa’H$%%%†™˜˜ 4HµHSSSQQÑÀ‰Ä’’©TZPP€a˜ ‹Å*++«®®–Ëå4ÍÎÎŽÈÜÐÐPXX(•JMMMíííI$]O@ŠD¢Ý»wGFFNŸ>Oñññ™:uj‹í† òòòLMM—-[6sæL„Ю]»N:%“ÉLLL¢££]\\ø|~TTÔ;w FpppXX˜j%QQQÕÕÕ¡¡¡T*õ‹/¾سgÏÙ³g¬­­#""ˆ7M¥§§/[¶¬ººúƒ>ؼy3N‡0 =LssóСCBùùùÕÕÕx Ä)•Ê‚‚ ‹÷Þ{O(æçç3™L}}ýG;88`ÖÔÔ¤Z[CCCqq±ƒƒƒjº½½}}}ý!Côõõñ==½#FÉäêêꢢ"‡C&“BuuuC‡Å0¬   ¢¢ÂÆÆF÷ž@ hU£¾L&ãñxÇŽËÍÍ]°`ÁÀI$Rrrrrr²™™ŸÏÇ0L¡Pðx¼ñãÇ÷ÝwuuuŸ~úé Aƒ|||TÃä•+W80|øp_"‘L›6 Ï&•Jµ/úĉñññuuud2¹ªªŠ¸lÉf³‰‰êêj“ÐQ©T±XL û$ •JÕé`M¡H$¥R‰GJ±XL£Ñ¨Tªj¢ªÁƒ—””TVVöë×O{ÍB¡ðÙ³gÆ c0¡û÷ï+•J¢yzzzíj'nذaúúú¿þú+qm!¤ÚNsss>Ÿ¯P(ðHYQQÁb±BAAAAAAõõõk×®õõõ555½xñbëTûwFaaatttBB~vêԩĺ<þ±|>_–Žà¤+t33³ÊÊJ‘H¤T*«««ÍÌÌt)h``@&“ù|¾R© …555fffT*µ¬¬L¡PàªÆã÷Þ{¯ªªŠÏç·®L&‹Åb|UøÀñåË—D:B¨²²R.—ËåòŠŠ Û‰c0ÿüç?7mÚ”””ÔÐÐÐÐÐ’’²qãF"‡Ãa2™qqq …"777))iÚ´iEEEÙÙÙ …B__ŸÉd*•J‡cllƒÿ|¥¤¤¤õo.ŒŒÊÊÊði@À`0ú÷ïº~ýzii)‘íСCMMM?üðƒ¯¯/Œ& ;êß¿¿B¡ÈËË“J¥ ÃÆÆÆÄÄD—‚† 2¤´´´¼¼œB¡ØÙÙáCRGGÇÒÒÒŒŒ ‰dbb¢zy”øï2‰Sޏ~ýú•––>yòÄÎÎÎÂÂÂÌÌ,''‡J¥Òétü†"¨çææJ¥R<öènîܹ¦¦¦qqqëÖ­Ã0lÔ¨Qª7©R(”ØØØÈÈȘ˜˜¬Y³ÆÍÍíþýûëÖ­+//'‘H£G^²d ‰DŠ‹‹Û²e‹§§§B¡°··o± E‹mÚ´iݺu«W¯ œ2eŠŸŸŸ•••ƒƒ‘mòäÉUUU“&MZºt©î+‚7öë7k«^…b3K›¾ðH{xÛá !=n]BKyŸáŸ——ð–} £I辈'×¼æ÷NNNª=zÔé‹€0 ÝDGíÞF\lná L&€N×&àmÁ5…0  ¥‚‚‚9sæÀšöôx'] L&“@'‚[xôfqqqºgV}v¼¶0yõêUèY=ÝĉB;wîÔ1ÿæÍ›uM.^¼ú@ÏUYYI¼ƒ³Íw˜à¯:i×& Lèsˆ7”õ°0¹mßÄ侉Éý›7ݳgíísîÞv¬lCƒÜÞ>ÇÒòÁĉjÆþNÞo¿5¾ ]#uŒ”m„Éâbñøñ F¦³óÃ+WÞîA\$RDEUff¾W_ïâá¡ßû6Œ‘¹¤døvjç®]Ëvvf´YÉáÃ5;w>‡½Þ|©K¤l#L—p8̺:—5k¬fÎ,nh¿½¦×ÖÊI$4p ­on¹9sLml¨mf++“‹aG€7Œ‘:FJ’ö#rzº`íZ6“IZ¸ÐÜÐ|éRBÈÆ&;(艫k¾£cîáÃ5xæòr©Ÿßc#£û¶¶ÙÇ×≹_|QæáñˆÍÎŽ‰©Ò²¬‰ ÆŽÍ‹•öö9ĉͼ<Ѹq ²sñEãìís¾ý¶ÊÅ%ÏÆ&{îÜ'GÖr8ylvöŒ}}‹ÌÍܾ­þœ­¦œ­¯¥ÎøøÚ~ý²--üôSµ–uoÝNMëþí·Uöö94Z¦êI×GDŒŒî[Y=ˆŒ¬Dݽ+´·Ï‰Ž~~ìX-ÞKB¡vzÐÝu´ÑöxGDL&ÉÚúÕgð`ú£G"|šN'ed8——K‡8q¢¡½=í“OŠ=<ôÏœôø±˜Ë-3FßÁŽjnVܼ锗'rsË_¼˜Å`¨ÌW¯:–•IrJJ†‰óæ=ñ÷7ùóO§K—^Ξý¤¤d˜™Ù«Ÿ9Sõª£©)ùñcqzº@_ŸTT4ÌÊêÁ/¿ zð ùçŸëÇŒQÚVmÎÖ×”ÞOž {øP4~|Áĉ†C†Ð5­{‹vjêç•+-W®´äpòT##+== ÒÓš›ÙÙÍ!ww½’’á[¶ðËÊ$±±v°»@Ð&…B“ùWTc2±¦&ÅëfŠêߟêá¡éRƒ——Af¦ðÊ•!T*æìÌøðC£ää—+VX"„æÎ5E½÷ƒNÇÊʤDühÓ³g’šÿøÃ‘DB}d

‹Ï±ž?—yy‘ûõ£šš’óó‡vâ*™›SD"ECƒ”UU2ë¯Öb˜¶²JñR‰Ô6þèÑZMuVUɈ‰±cõµ¯{ëv’HH®ÃPýûSµG%$Ô}þùÓ¹sMñª0¬k÷VžÂƒŸîÛ¾ý¹H¤8r¤¶¡Aîãc„Ï:xð…H¤¸wOxë–ÀÇÇhØ0†­-mëV¾D¢”J•©©Z.ÅéÈÎŽ6|8s÷î*¥%'¿|üXÃSž——ð–} Ït4‚0 hÔ‘«}ee# ãÀh€0 “„IÂ$a€0 @˜ L&“„I@˜ L&“„IÂ$a€0 @˜ LÂ$a€0 t2 t€^/!!Â$ Þúõë!Lê´9ÜTJáÚ$£I}ÞíÛ·['Ž3FKMè+ZGDí1Â$€¾)ÛŒ‘&ôÑH©KŒ„0  ¯)!L¤ñN×ÊÊJè½C'?…gâĉЭzx  Æ¢E‹Ú|pm€0 @˜ L]AÍ-<ŽŽŽÐ/Ðw´¸Ã¢@aòo¡.ØT]¿Äv5ºLBBœ9sàè„4ü¶’Ò‰Gpü–í-¨v uØݹkÝ W@kpmxã0éää¤åcÇ*é:eM{âŠq¹ÂÂBEÇÊz{ rrªh±cÇŽÝ»w¿Þy¼srrB………¾¾¾nnnG%ÛUÕ»Òfk¹\naaaMvqŒÜ·oߨQ£¨Tjhhh‹Yååå¶¶¶Úó „JJJôõõ?üðÃw)Ðâ‹/èÖÖ˜ÚYaa¢âbˆ ï†ŸŸŸ««k}}=þñÂ… Ÿ|ò‰–üaaaÅÅÅ]µÏ|amm:vìØ„ îÝ»L$vÞ>§iûvqe¥"8¸ÙÅE°pa³@ ÄóðùÊÐP‘››€Ë<(ѽ B(=]îã#ps¬^-‹Û¨pß>‰¿¿ÐßÿoSâ¤ë®]b»»`òdáýûò¨(qu5 mööœ=+ƒMÙõ.\_]]Ý"½°°pÞ¼yÇÛÛûÔ©S¡¨¨¨êêêÐÐPooï³gÏ¶Ú ¹Ç÷ññáp8Û·o¯¬¬ vqqY¸p¡@ ÐT'z§OŸ>zôh×ÐР²Ïxçää|ýõ××®]Û½{···wII q“Ï燆†º¹¹q¹Üƒj¯ê]!ZËår88yòäððp™¬å®~ÿþý &üù矡]»vyxx¸»»Ož<ùþýûÝ+Lv·8ÑÔÔtçÎI“&iÉ#Ö¬Y³oß¾NY"DÊž+5U~ò¤^R’ÞéÓ²ðpÑ–-Œôt}‰ÅÇËB âñš°›7õÏœa&&ÊRRäºÄ]¾,;yRïêU½²2åþýRí(NŸÖ;^¯u#³²äÉɲäd½»wõe²Ù¤¨(:‹…bc™iiúð¢‚w`ðàÁS¦L‰UM”Éd<ÏËË+###&&fëÖ­QQQ,+666--- @ÝN˜zòäɤ¤¤Ó§O‡‡‡oÙ²%==]"‘ÄÇÇkªS&“-[¶læÌ™·oß 9þ|‹:wìØÁår¿üòË´´4{{{õÒ%©.Uóbaa´äd™ö ?ý”J¡ LÝV‰P^ž\"Al6fe…Á†ë–-[vêÔ)ÕWðfee …BG"‘FŒáïï¯KÔ 122²µµuww÷ðð°··×ÓÓóññÉÏÏ×TgVV–X,^°`†acÆŒár¹º48;;»¬¬,<<œJ¥ZZZΟ?ÿÒ¥K«ªËÌŸ?ŸL&“ÉäqãÆá‚ûí·ßÖ¬Y‹_8c0"‘(//O"‘°Ùl++«.h[ûþ>}ôèQÆF+Õ&⌫&>LLLÌÊÊ‚ ,,^…:c±^MÓhH"Q"„ø|¥D¢œ6­O—J•ÎÎd] âØlÒë ¬ºZ¡½BM× BÎΤåËi;wJJKE^^”ÈHº¹9DÊwÏÖÖvúôéû÷ïÿý÷ñ”šš6›M"½ÚîÖÖÖºÜ_jaañz_¢³X¬×ûM"‘hª³¦¦ÆÚÚ{ýW•. æóù‰„¸Ò$•J;VU—100x“(¼Cpÿûßÿ|}}‡úú;â¼|ùò;w–––zyyEFFš››w¯0Ù}"¥\.OII‰ŽŽÖ’ç?þxöì~"B H${{û’’ˆ‘ u55Å.^ÔÃ:•ž?W8;“ðèÈb‘ޤ  jPµ¾^¹v­86VúÍ74 e7æëëÛ¿ü£¹¹9ŸÏW(xT«¨¨ÀÃö[Kmæææ555DžÚÚZ]N3ZXX˜šš^¼xQµ=wïÞí@UïÜž={6nÜøã?.^¼øõw$(((¨¾¾~íÚµ±±±ß|óÍÛnCG~ÒÅ1C&“‰D"¹\.—ËE"~u÷ÆC† !þ"S›gÁ‚?ÎÊÊÊÊÊúâ‹/¸\î­[· F‚Ö8²±1#‰”¡’e»~Åxè´±QÙÔ¤üቯ/¥Ã)²³ Ò×ǘL¤T*BFFXYü äëׯ߬Y³þóŸÿ¼Þa8L&3..N¡Päææ&%%áC7##£²²²Žî„jêäp8J¥ò÷ßG•——§¥¥éX•±±qLLŒH$B•””ääät¬ªN$•Jůµ¾IGË_GŽùå—_þýï#„ŠŠŠ²³³ …¾¾>“ÉÄ¿#Ý%L¶8èwe䈊Šb2™±±±qqqL&sݺu¨ÕWµy˜L&û5ÖÙ²¦2»9 ÅÅ1‹ŠžžBWWÁªUÍuuíøúMžL löò²ÙØÒ¥ÔW((#"Dîî‚÷ß47+—,¡!„-¢mÚ$vwüü³¶Ô;Jœ ¤P(±±±W®\quu]ºtéš5kÜÜÜB‹-Ú´i“»»ûÏ?ÿÜÞúÕÖI¡P¾ÿþû½{÷Î;7""ÂÓÓS·ý™WTTäéééêêºjÕªºººŽUÕ‰"""F¾†¢u>ÙcqäÈ‘øøø#GŽ‚ˆˆww÷÷ß¿¹¹yÉ’%]Ðrlü¸±\¿Y[õê/ 3ËW'¬»Ã3]µ<ˆÜÉÉéܹs>‘«åÑçðLW@ŸÒúXÔ#a¯é „ÐRÞgøÇçå%¼e_öÔ[Ìa|  P´ÕGãžþ×D_[kí“;Øá‚ÝDÇÚßÓ×@ûÂ$Œ /ƒ(@€÷M@ßµ~ýzèí]¡í8‘=K\\œî™wîÜ©=C߉Z~XS€^¥ÍàÚÂ$ô6mþêÿÁ7t”.àÚ$aúªÛ·oC'@˜ 1FB¤„0 @Û8"%„IHK\„HÙp§+ôNcÆŒN€Ñ$£I:ƒ'²B˜ <:Â$à)<®M&“„IÂ$a€0 ô0ð»É®ö]ç @/vë§/¡ºL›¿&{¤àá0ˆ½ÓÑEO¼sçÎÝ»wsss;·þÂÂB‘HÔ)Uùùù9©àñx°ùúšîx \.'“É}¡÷û[[¯ûfÍ©ÓgaG}‡B¡xô葵µµ““†a†uî"\.ï¬Ú¶mÛæçç‡O÷‘Cèº0ÉårgÍš•žžÞØØ8bĈ͛7Óét„ŸÏŠŠºsçƒÁ Ã3Ïž=;55U.—_¸pa×®]§N’Éd&&&ÑÑÑ...………6lÈËË355]¶lÙÌ™3ñRóçÏOKK«««1bÄ®]»(”sûî ?„Ðo©i2™ öEÐGˆD"¹\Îf³ñèhllLÌ’H$%%%$ÉÊÊÊÚÚZµ Ú¹D"†a&&&ƒ *))‘J¥†ÙØØ°X¬7l0•JÅ\„§OŸÎœ9óðáÃÆ «ªªò÷÷‰‰3fŒ¦#Þž={Ξ=ÛÐÐ`mmáéé©åØ•››»fÍ>ŸïêêÊb±LMMaŸy·ÞúI×¢¢¢øøø .…Âýû÷ãKòx<‡›7ož9s&111%%Ï\PPpúôéóçÏgee%'''''ß½{÷èÑ£l6[&“ñxveee‰Åâ `†Oa‡yçÞúùIâ¯9ssóêêj„ŸÏ—H$Ó¦MÃÓ¥R©³³3>mff†O8;;/_¾|çÎ¥¥¥^^^‘‘‘555l6›Dz×­­­srrðiƒW+C¡H$بts càÀ!±X\RRR\\üÞ{ïI$¥R™M õôôˆ"jçJ$’§C߆U«VM™2¥ÅÑ!¶yóf¦åˆ‡:qâD|||]]™L®ªª …šŽ]555ÖÖÖÄÅZØ[z˜,//wrrÂ'ðÈÂÂÂÔÔôâŋگÛÕ×ׯ]»666ÖÇLJÏç+ |ëÖ­'Nܰaƒ–#ž@ `0ýû÷G]¿~½´´T˲8ŽR©üý÷ßñ@›––;LïMúøø¾xñbÒ¤IK—.E‘H¤¸¸¸-[¶xzz* {{{Õ3û8@°nݺòòr‰4zôè%K–P(”ØØØÈÈȘ˜˜¬Y³ÆÍÍ­§÷þ¶í;wíÞ‹Oÿ÷ÈÑð•+6E­‡ônd2¹¹¹9??_*•’H$ccã „0 srrzúôiff&>дµµ%J©‹a˜££ciiiFF‰D211Á/Oöëׯ´´ôÉ“'vvvø}=o""""""ŸvvvNLLLMM½ví~{ÄÚµkg̘‘””4}útµG<—)S¦øùùYYYÙÙÙ988h;"S(ßÿýºuëékºã-yòäû÷ïGEEUWW‡††z{{㯛!rîÙ³ÇËË‹Ãá|ôÑGþù'Qí'Ož¬º¸Ö4åÔTíñãÇ}||8ÎöíÛ+++ƒƒƒ]\\.\H¼K–Ï燆†º¹¹q¹Üƒª]èñø„‰'Ís,þ¯w„eÝà1ÎËf€Ã¬9Aõ/_郇nÙ¶ý}î„÷='"•“®Q›¶:½7ÒªŸûXîo©¯Þãª6q°ãÐ;£'~à3‚ã¾ðÿ>'Þ1 ЄJ¥Ò_£P(¡§OŸŽ=:77!TUUåáá¿ö„ËåîÝ»wöìÙ¾¾¾«W¯&^ Ý®CSnnîôéÓGÍãñ ÿ{s˜”ËåŸþù€nܸ‘žž>wî\bÖåË—?~åÊ•¼¼¼ÄÄD"½  àôéÓª/xËÊÊJNNNNN¾{÷îÑ£GÙlvTT‹ÅŠMKKk1Ž6lXRRRffæâÅ‹W®\ÙÜÜŒ§Ÿ8q"%%¥²²Ruq­©Í©©ÚÔÔÔ“'O&%%>}:<<|Ë–-ééé‰$>>!¤P(x<žƒƒÃÍ›7Ïœ9“˜˜˜’’¢&LžHø$`Æ'ÏHMM«ªz’J¥Aó=+)\¹bÙÉ“§þ>’Îûãêo·Ó¯©&Žâ¸Ü¼ñ;¿¢tUøŠŸ-67kJD¥þšœuïÖ³²ò„Ÿïíegg÷ÕW_}õÕWÍÍÍk×®ýøã‰×žÅÇÇ_¸pA(îß¿¿½‡&™L¶lÙ²™3gÞ¾};$$DíÛ.Aï “ù?|'yOx ß´‹ŒŒûÚðÄÙ³g0 00°ªª*<<œÈD&“I$Rppprrr{MYYYb±xÁ‚†3†ËåBÿ¿soñÚdUU•µµµÚSù¦¦¦ø“ÉT=«`ffÖ"§³³óòåËwîÜYZZêååinn®i‰'Nœˆ¯««#“ÉUUUÄõKƒWkK¡H$Úî”Q›SSµøN'^•N£Ñð‚|>_"‘L›6 O—J¥ÎÎÎ-wìø‰IÞÞFFF¡O>8~üIJ%¡UU/ìlm‰@h?ÀNµ‹¥fõÿýŸÿÆúOMM-…B©äó›M‰!C£WëH¥P;pß}ͪU«¦L™Òâ Û¼y3FSù†¾:˜››WWW·÷ÐTSScmmM|ýmll ÿ{s˜´´´¬¬¬ÄïÇy“z‚‚‚‚‚‚êëë×®]ûÍ7ß´Ká £££ „š:uªR©|óµèpµ¦¦¦/^TÛZ„P³HtúL¢\.ì8!$K^¾|™“kiiñâE5‘íEuuÿþýµ,(/??rã–Ô_“‡ „8nc•J¥ÚDØÝèccc6›Ý"Q(nÛ¶mÖ¬Yß}÷>XÄÓËËËœœð øàÕË“ºG©uëÖ•——“H¤Ñ£G/Y²!´hÑ¢M›6­[·nõêÕ³gÏ&¢ò”)Süüü¬¬¬ììì:+Øw¬Z‰·eËOOO…Baoo¯z!t<þÄ?æÏ³U9©Â[¼è«¯×nÚ¸áØÑÃËW„ïܽ‡É`LþÀ[û‚ÜÝݦOŸ6æ}Oë~ý èìì¤)Ðø´³³sbbbjjêµk×ðûkÖ®];cÆŒ¤¤¤éÓ§#„|||_¼x1iÒ¤¥K—¶÷B¡P¾ÿþûuëÖtèP~~¾B¡ppp˜1cFpppw~C„Éw å"<õ€nÄÇ×õÀÛ8Ûu»æ[beeµbÅ â!ïm:qâDttô7ß|3aÂ:þðáÃ#GŽL›6x.zýÊa“}<^€n¼ôCÿÂðñAýùçŸr¹üoƒ”©TJ¼Î'‰¢££###‰twwwwww|šËåΞ=;55U.—_¸p¡°°pÆ yyy¦¦¦øËÅBC‡ýã?ð˜ºuëV}}ý•+WâegÍš•žžÞØØ8bĈ͛7ÓéôÎZG¸Ó@'»qãÆÕ«W[$æææ âu+­¯–Éd<ÏËË+###&&fëÖ­Ú—¨öMŸ&tG7nܽ{w‹Äºº:}}}bœÂår¹\îµk¯Þ3O¼r8++K(òx<‰4bÄÿ6ßP­öMŸ& 7Û±cGë`Ós™˜˜âU¾ÑÑщ‰‰t:],ã)Ä+‡kjjØl6‰ô*BY[[/ïÔDí›>!Löxc<ÆšXšX˜YX{Oþ0ëþèº???'<ú¤Ã†®§§—ššJDM‹¥önsss>Ÿ¯P(ðx¤R©Ä»ÌšššT‹”——ª7A˜ìñ~ˆý¾®º²¸ð!Çeäg!‹[Ì•Éd]Ù˜.^=¶mÛ¼Ö¹½z¹\.‹ >AÜÈ“’’Òú4)ƒÁXµjÕ† Î;÷òåK‘H”ÝØØØúÝõ‡ÉdÆÅÅ)ŠÜÜܤ¤¤iÓ¦!„œÓÓÓñÀyåÊÕ"­ßôÙ3Â$—Ë=pàÀÿ·wïq1¦ÿÿÀ¯9TÓù4u˜”ŠR:¨Øv“ÍaQ¢v"ÖGY–]‡Å:[-_ö·>>l²¬°ëH–D-¢"ŠJÒRM35óûãæþ´Í¡I¥Óëùðð¸»çºïûšëš¹ßsÝsÍý :thdd$}Þ¼yóÀ]\\†~õêUºð¯¿þêïïïââ²nݺçÏŸ‡……9;;O:µ²²’*SXX8kÖ,777ŸŸ~ú©¼È˜ &›ÍÖÓÓ ›’“ó6_«­}¯Õk×õ÷ù¨¿¯!äþƒûÔÄÜÚ±·ëƒ1o?ˆš¾xQD-ÿ{É×ß­^K9p0&($”Zéìê1iÊÔ2¿—ó»÷!ùA!¡æ–Ýmí{ý°q‹ÌÃ-_ñÏÖ¡›•mo—~))·p"€.NEEEí6›Myúô©»»{zz:!¤¨¨ÈËË+99™:‰mÙ²%888 `ñâÅôµDyg<™§Çôôô#F¸»»‡‡‡———·óÆÙ±cGŸ>}bbb~ûí·>}úlÙòö¬"s !dâĉ«W¯>xðàÀ=<ÞÿŽZ&‹_‰„¢ääBHNNî›ÊJ'Ç^b±8(xâСCîÿ¥¤´ôÓŸõèa7zÔˆú‡»y3å÷ßãRn\ãr ó ¤?倕•Õ¢E‹-ZtôèѨ¨¨Ï>ûÌÃãí[’šoÉ`0æÍ›·sçÎ (8ãIŸkkkçÎ;eÊ”)S¦Ü¼ysÆŒ“&MjÏM1þ|ê' (¸Õ€¿¿¿ÌóvƒÇ;88PSŸ³³ó™3gdî¶ÿþ“'·ÊíZý¢khh(‹Åb±XÞÞÞt3éêê2ŒÑ£GëëëÓ?Z¢^I–––ýúõóòòâñxþþþÔ†wïÞÍËË‹ŒŒTQQ166 ={ölG¿ÍÐÖ32íÆ‹=|ô»•ÿ» ÈìY3TTT ÆÍ”[•••_-ø“Étsí;>xÜoG—·7ÏZ[KëÎÝ{II 2ÈÌÌ4+ëáÕ¤kÞý½˜LæíÛ©9¹OW,_ªªªjfj:sú´¸ã'Ž£®^U]uçîÝša7sss33œ¡‹[±b…ç;ÑÑÑÔÊàà`kkë   ¢¢¢ÈÈÈú£%éù–òÎxÒ§Ç´´´ššš)S¦0 ŸöÜ2rt²@«&µ´´èA4=ÁéСC111eee,«¨¨¨ªªŠZoddD-¨©©ÑßÁªªªR …Bê 5!D$ñùüŽÞ{vG‡Œª­­=ûç¹ÑcÇß¼qÕÌÔ”ÂåRŠŠ^v37§g|YYZ¤¦ý­`‡¾¾Þ‰W“?~âëã­««{5éZrrН¯7!$¿à¹PXãîåK• …½œ¨eúp½—.Y¼lùÊìÇO>:x˦ŒŒp—vèÒ,X@ÿÔ>¡B‚‚‚fÏž½jÕ*UUUz¥Ìù–òÎxÒ§Ç’’sssú*Ž……E{n™.’w³ îÂóðáÃM›6ÅÆÆvïÞòÉ'ŸH$e6422Ò××?sæLç»Èf³?Îá,JLL [ÿ!cc£ü‚±XLEÊgyùÆÆÆ„UQíÛ_¯ËË5µ4߆IïÓgþÌÍ}ºð«ùzºº±¿¹‘|sæÌi„ccCÃ[É×7àŒé_̘þEii鬈y?lÚ¼aÝZœ(¡+ÓÕÕ•ÎÙ^UUµvíÚqãÆíرƒ,Rëóóó©á=ß²Ig½ !ýôõõÖ|¿¾Z „;ë~ý?{]<×ðÞKýú¹ÝJ¾&sóG™éôò•„ õ273;øß_®_?·ë×®à@‘y´!C† 2„ZÖÐÐ8þ<ýô|KeÎxôé‘âää×>[£k¦äD†€¦‘9™³+Ìgéˆ)9åQ>- Â$@Ót‘žÍ -í\“Ru"L´ŒVo m÷tÀh²ÑÒÅoö&Aÿ€ÀNs‰ ÓèšÓ8a>({{ûZóŽ8³m?p‹D¢U«V]¹r¥¨¨ÈÚÚzÞ¼yf° |øð!!DKK«ÿþß~û­¡¡!Â$4ôç™?ÐÐ)ù’8266vùòå±ÚÊO×l B¡P]]=::ÚÌÌìÂ… _}õU=½cÌúõëGŒ‘——7þü 6¬_¿^qùºº:™©›&;¹Ï§NF#@'£¥ËÅ· ]Ц¦fTTµ´oß¾ôôt*LþùçŸ"‘(00Pz+&“Éb±¬­­‡N%Q),,\¹råÍ›79NXXØìÙ³ !>>>ÁÁÁ/^¬««;uêÔ?üpôèÑÚÚZ==½M›69;;?|øð›o¾yð྾þܹs©,>>>¡¡¡—.]*++ëÝ»÷?ü@¥E˜€¶T\\œ——GßxáÚµk•••2Ã$¥ªªêÊ•+ÖÖÖ  geeýþûï,+--íôéÓ§OŸ600(,,d0µµµáááAAALOOŸ2eŠ««+‘“ä¸9ðƒh‘HôÕW_5ŠNnøí·ßnܸQfáï¾ûÎÇÇÇÃÃC(.Z´HA"áÉ“'³ÙlƒÁápÁƒ„B¡©©©‰‰IZZZUUUxx8“ÉìÝ»÷È‘#éÛ ÊLrŒÑd×ekßëäñ£½zöDS@g•––Ö£GMMÍ.øÜׯ_Ïb±.l×jkkçÏŸ¯¡¡±bÅ eÊGFFúûûkjjª««BîÝ»'/‘°µÀçó¿üòË 6äææ8pÅŠ%%%¦¦¦t"^sss:a”Ì$Ç“Б¼yóæéÓ§••• C]]ÝÊÊJ[[»÷ÿðáCKKK‡Óü]Ñ33)~~~»wïFÒêêê"##…Batt´’m´µµéäÕDéDÂ'Nœ8qâ«W¯¢¢¢víÚåïï_XXH'â-((¨¿Ï–Åló&Æëì½?Á¡ #‹Å™™™úúú®®®®®®-žh½²²²Ï-k×®½óÎÎ;уõOà .|õêÕ¦M›ÄbqMM Ýìþù§Ìä*Ò”I$üèÑ£»wïŠÅbj *‘H\\\ÔÕÕ÷îÝ+‹ÓÓÓOž}êîîžžžN)**òòòJNNVpvÚ¼yóÀ]\\†~õêUúT4tèÐÈÈHúƒozzúˆ#¨Ófyyy{îÊÂÂÂÓ§O'''»»»÷éÓ§OŸ> ºvíZBB‚RAˆÉÜ»wï£G|}}]]],XPVV&ý¹géÒ¥ýúõëß¿uuuDD›ÍÞµkW||¼««ëœ9s–,YâææÖJO³/ºÖÖÖΙ3gÆŒaaa©©©ŸþyXXý(=…IÁ„%y=zÃ`0æÍ›·sçÎ tÄ“…¯÷¿£–‰ÅâEE"¡(99…’““û¦²Òɱ—H$ :uò¤?OŸLMû;pä;[[//BHzúƒ+ êÏr¾|%qføœÿü²—*ОQÁ&;;›Ëåjjjª¨¨Pë%IVV–®®n=jkkóððPpvrttœ6mšŽŽÎñãÇçÏŸŸ˜˜H}-'='³¶¶vîܹS¦L™2eÊÍ›7g̘1iÒ¤vەݺu“—,åÛo¿•¹^æSf"áú÷‘wvv>yòdƒTëÉÛª~Ïv:šLMM‹ÅThìÛ·ïÀë?JOaR0aIž‰'RŸCèŸÝtD<žµ¶–Ö»÷’’þ2d™™iVÖëI×¼û{1™Ì›)·*++¿Zð/&“éæÚw|ð¸ßŽþNm8{Ö ú:Õ©SggÍþòpìAÄHèX,V¯^½X,VnnnjjꃨQWeeeMM u VEEÅÄĤ¤¤¤þxBúÑÊÊJ¡PheeÅd2 FË~ÁI[±b…ç;ÑÑÑÔÊàà`kkë   ¢¢¢ÈÈHÅg']]]ƒ1zôh}}}ú×¥Òs2ÓÒÒjjj¦L™Â`0<<<|||ÚsW:ÈÑÉ^±­8š,--566¦ÿ455­ÿ(=…IÁ„%yè¯j [ä¢J› (}½¯&=~üÄ×Ç[WW÷jÒµää__oBHQÑËnææt³XYZ¤¦ýýîéÿãÞN?íþ¿1ŸvîÓç_è(8Ž !¤¦¦&''çñãÇ={ö …‰äîÝ»ôðQCCƒÞDæ£B¡PMM­µk»`Á‚?þ˜Z¦¯B‚‚‚fÏž½jÕ*UUUÅg§C‡ÅÄÄ”••±X¬¢¢¢ªªª{£çd–””˜››Ó‚-,,Ús?v‘¼›­& ŠŠŠè? ­¬¬¤‹Êœ°¤¢¢"‰¨2oÞ¼©ý$??ŸúÀ’ŸŸßz³›>D˜ôñ>}æÏÜܧ ¿š¯§«ûÛ‘É7gΜF166Ê/( ›åY^~ýÏõýòóÿ-XøïÍ[¶-ˆüοб¨©©=yò„¢ªªÊf³ûôé#³¤ÌG+**¨ðÙâ“€êÓÕÕmð)ŸRUUµvíÚqãÆíرƒ,Ê;;=|øpÓ¦M±±±Ý»w'„|òÉ'‰DÞ± ë¡KKK¥ X+^tíÛ·/ƒÁ8xð !$55511Qf1y–ø|þ_ýEÎøøøú›üüóÏååå»víjôN»í}úœ:uŠZþòË/{¾»YLýÉHD΄%ggç3gÎÈÜmÿþý'Oî$·”™N/_I¸Pÿ!'Ç^Ï5œ ”u_æŸÝÌÍï¤ÞÄ :ÀI‡Í¶µµ•ùªªªtqqqQ𨚚štê..—ÛR_ÇÈœQ8dÈ!C†PËçÏŸWpvb0õ‡›4ys2œœâââÚgßuÍ”œ­&ÓÒÒx<žžžÞõë×Ûù-—à ~b(þMEû's2g§ŸÏ’••Õ¶y»:g˜ÌÌÌŒˆˆ‰Dzzz«W¯¦.­@WÖ¡‚3úa²eŒ?¾Å?}4¸` ÐNàìÔ)!‘@&A-].adð¤ïVÐùtÐØ«¹ IDATù]s' LÀÕAçCvÙiœÐ(|7 €0 €0 €0 €0 €0 €0 €0 ÐYàö î(„0 r-_¾€0 ²5z×èØØX„R%á»IŒ&º˜äädé•hŒ&@FDDŒD˜Ùq1adGJÄH„Ih|L “-3]:Ü…aä­>P˜´··Gt 6l@#´,&>Œ4m49~üøFo ÐuG“€0 €0 €0 Ђð»É® ?øFa*Â$N‚ •rÕþ`Õ?~<Þ&ÐÅá¢+Â$Â$Â$´këׯ߸qcgªyû|Fƒ ºwï^o“𡹸¸Üºu‹þó矞:uj îöìÙ?n“§6iÒ$www¡PØ›=00ÐÁÁÏ绹¹ïß¿¿®®®#62Â$@³¤§§WUU}øãæççÿý÷ßúúúñññív8›žž?kÖ¬,X° Ã52Â$ti>>>[¶l X¼xqMM }R1b„»»{xxxyy9]~óæÍtqq>|øÕ«W !+W®,..ž5kÖ Aƒâââ!………³fÍrssóññùé§ŸZ¯òqqqÞÞÞ£G>vìXýp"³æòÖ·î’Éd±XºººƒÞ½{÷¹sçîÞ½ÛhI?Ú† €0 ]Ý£GbbbN:UUUµsçNBHmmíܹsÇŽ›œœ|xbbbqq±‚š+xFŒM=RRR7‘ÌGÛ°‘&¡«›8q"‹Åb2™aaa§OŸ&„¤¥¥ÕÔÔL™2…Á`xxxøøøÐ…ýýýuuu ÆèÑ£õõõ¥b÷îݼ¼¼ÈÈHccãÐÐгg϶FµoݺUXX8xð`gggGE>y5WðŒ>$ƒòòrÅM¤L~°Fè|põé‰É‹ÅôŸ‰„ÅbÑr¹\jÁÐГ•””˜››3 j½……]øÐ¡C111eee,«¨¨HúÛ²ÂÂB¡Pøé§ŸRŠD">ŸßOêØ±c¾¾¾ÚÚÚ„€€€cÇŽM:U^Í<£©´´TWWWq)Ó€¬‘&¡Kàr¹ôŸyyy†††ôŸùùùÔ2 KJJêŸÜMMM !>Ü´iSlll÷îÝ !Ÿ|ò‰D"!„Ðá‡bdd¤¯¯æÌ™ú+[œ@ 8sæL]]5. …åååòj.oý‡”››ûðáÃ~ýú …BM$¯?|#tÎaš¤ >|Ïž=Ož<©­­½qãÆÉ“'èGþùçòòòŠŠŠ]»vQë]\\$ÉåË—©ØyéÒ%ªdee%‡ÃéÖ­!$)))77—Z¯££“——G-»¸¸èêênß¾] BrrrZã·€.\`2™§OŸ>~üøñãÇÏœ9Ó¯_¿¸¸8y5—·¾µ‰Å⺺ºòòò„„„™3g~òÉ'NNNŠ›HÞ£¾‘&¡«˜3g΀ÂÂÂúôé³lÙ²… 4ˆ~Ôßß?((è£>²°°˜3g!„Ífÿøã[¶l Yºt©¯¯/UÒÙÙùã? ;uꔵ~úôéß}÷]¿~ý>Ìd2÷îÝûèÑ#___WW× ”••µø3:vìØØ±cÍÍ͹…Ÿ3l˜ÎéÓ¯çÍ3¦7ÏX_ŸE±µUË Ó{ï<ÊM“ÅŵSG‡EýilÌ..®%„ÄÄ”mÜø¢¨HÄb1Š‹kkj$eeuFFl&“B Ùl6CñžÙôBIIm3[¤ @DqsË þ¬¬¬ëÝ›C?je…¯0º™·h•0ihÈÄååuT¤,*ªårÙÏž §M˽|ÙÞÝ]ƒbl|G"!\.ûõk1µÕ›7âÚÚF.ºÕÒ žžš„6›!BˆDB**êšTO33}}VFF/™2xÍt!­xžÚZ‰@ ¦þ‰D++U''õ‹$rúôëìì??­ÊJ±ª*£gO!äĉ×/_ÖBz÷æhk3/^¬ „:TÚ袣_ âÔÔª¤¤7:„[[µääJjŸB¡¤I-âèȱ´T]³¦P(”ˆD’‹+ð5$´üh2*ª *ª€Z;VïÈ‘î116Ó¦ånÚôÂÌL%&ƆËes¹ìÙ³úö}`f¦Âçs,,T!,ãÐ!›ðð§ÓÎNMUµ‘\Ïž›ôº:ɶm–ÔW†ß|c:a“ƒK½½µ ›<ð‹³ˆxjdt‡Ò·¯úÏ?[£¿ %Ãä… =dÔ®_oøC“ï¿7ÿþ{ó+½¼4ÿþ»'µ|ð OÁ¨ª›6YÔ_éé©ùøñÛ™«Û¶ýï!  oƒ=H¯´°P9qÂVÞ±…{º L L L L¶—’’ò~Û–——óxŸQÏ¡C‡!C‡ݸqcƒÍe–$„üðÃÖÖÖªªªÎÎÎÆ cü“––V;l,œœœèèh™FEEñùüFw²oß¾ 64Z¬¶¶–Á`B¼¼¼>ð3mÛ£w”>€®@ÑBÁˆ#–/_žðúõëcÇŽÑíß¿?((ˆZVUUtã7é’Û¶mÛµk×ñãÇ{ö왘˜X[[{òäɺº:BˆžžÞ¹sç<<<ð69ãÇW¦X^^^^^^yè#èð£Éììì²²²¹sçjhh˜™™EDDЩ¨¨pÞa2íDºä† Ö¬Yãâ⢦¦6tèP'''º J9Žššì;’8pÀÅÅÅÔÔtÔ¨Q†††ÉÉÉ„üüüÀÀ@KKË_ýUAIBHLLŒ™™™±±ñž={¨5Ò›Sx<ÞÖ­[-,,BBBä=Ç­[·òx|ØÃÃc̘1^^^ÇŽËÎÎöñññððW’:=yòäþýû ðóóëÑ£‡ôævvvÔáŽ;– ¯¯Ÿ-¯JóçÏŸ?¾‹‹Ký•+V¬ðõõý믿ª««ïÞ½Kéׯ_NNÎêÕ«óòòvíÚ¥øi²X,*B,]º”Ê*‹CBB.\8{öì+VÔÔüãÞ{ ê9a„‘#G^½zõìÙ³ÁÁÁ999òŽ¥¦¦vûöíüü|'''???'}tô‘’M§|=¥® ãª««¯_¿þàÁ77·™3gRŸ) G“ššš ÅÅÅþþþ\.wÍš5ôC3gÎä¾C JäiPòÕ«W„]]Ý÷®q÷îݵ´´Œíííy<Þ‹/îß¿ŸššºvíZ>Ÿ?lذӧOË,Ií!""‚ÃḺºúúúž={VÞæ”yóæéëëBlmm›TO555êÚ†††§§gSŸ&ƒÁ˜>}:!däÈ‘fff„»wïÏš5‹Á`DFF6(_¿žÏž=»sçÎÂ… ™LæðáÃmllâããk„ „nݺyyyQ#飣”iº&ÕSúè :Ž€öìÙ“Ú g.€v& !}úô9räHiiéï¿ÿ¾nÝ:úM»nݺ´wèË25(©££Cyýúõ{טÅbQÿSjkk !nnn|>ŸÏç_¹r…ºP&]’Úƒ±±1½PRR"osŠ••ÕûÕsíÚµl6ÛÝÝÇãÅÄÄ4¿«^¾|Éår© ×úúúlö?®Ô¯gqq1‡Ã¡ššzšÅÅÅ ö¬§§G/(.‰>RÜtMª§ôÑtœ††ݶt+@_t­?²2dˆ§§gFFÆàÁƒ !ÊlÛ ¤•••‰‰IRR’‚¯‘šD"‘˜™™éëë7Ô8p@º$µPTTD/xzzÊܼþsoøÉ‚ɤ&)Ö­[7ª±±±3fÌ ¡vÅ`0èš4‰‘‘Qqq±X,f2™eee Εõëihh(ÊËË©nQQ—Ë%„°Ùl±XL5EEE]¾°ðmÆì/^ 8°Å_d¸4]“ê)}tyít4ùúõëo¿ý677W(ÆÇÇ''';;;S‰D"Á;ôù¨¶¶–^)‰ä•\°`Á×_––& /]ºtïÞ½f>GGGKKË5kÖ…B‘HtñâEßQB¢££AjjjRRR@@@S7·µµMOOotÔuêÔ)ê\¯¦¦¦ªªJŸ"¹\îýû÷œÄŸ>}jggwêÔ);ìÝ»·¡¡!õ•áŽ;”´²²rrrÚ¸q£D"9}útvv6õ#B[[[jŽÌ‰'„B!]þ§Ÿ~·nݺqㆿ¿¿‚=WWWÏš5ë=~"ÒYû¨AÓ5©žÒG—×q-ÞÐ2aRUUõÞ½{ýúõÓÐИ6mÚêÕ«é©7“'OVgË–-Ôʨ¨(z%õÌ’‹-š6mÚˆ#455çÏŸO]vk¦¸¸¸7nq¹ÜU«V).ܳgOÿmÛ¶Qß5isggç°°0kkk---êê±££#Ç»ÿ~XXÇ;xð !äÚµk}úôÑÖÖ^²dÉþýûéÍÇ'‹MLL,,,è+rB¡0;;»þOFW1™‡Ú¼y3—Ë­««SSSS𳙘˜˜sçÎiiiÍ›7/&&†”|óÍ7Û·owww744¤ óù|›€€€;vtïÞ]AªªªvïÞýðáCô‘¼¦S¾ž2.³ãZ£;@IŒÞžW’®—½``l‘•••0sæL´N»%455ËËË555›¹+ ‹¸¸¸~ýú)SøüùóãÆËÉÉ¡¦¢tqMjºÖ€îPìùóç{÷îݰaCVV–â±±±Ë—/_µjÕœðÏ©5/òsÂç.Ä=];’k×®QsAùåOOÏæÇȦº|ù2=]Úºà`£ :»wï~öÙg‰ÄÖÖö—_~ùðX½z5z¡ý@w LÂ?„‡‡‡‡‡·ì>ñ#<4(€‹®m&ëßDTÍÌs„4IЙG“ÍÌs¤äæJj_ßM63Ï‘’›´Ìh²IY–6mÚdee¥¥¥åîîþìÙ3jåÅ‹»wï®­­]?tR¡fæ9’¹ùÚµk­¬¬444ø|>}óåsÉ,)½OåóF5‰ò-ß¶õèê£IéTA2‹ýç?ÿÙ¹sçùóçnܸAßqôæÍ›éé鉉‰£G®Ÿ¨AR¡fæ9’¹¹¹¹ùåË—y<Þ‰'BBB^¼xAÝ?ZùœDÒ%¥÷I”Î¥øñïÝòm^O€®;š$²²,É´oß¾… R¹ ===mll¨õ3fÌPWWÿä“OX,Vý+¥Ê$?jfž£©S§ÚØØ0ŒQ£Fijj>~ü˜Z¯|N"é’2÷©|Þ¨&Q²åÛ¼ž]:L*™eéùóçÖÖÖÒëé{‡ª©©ÕOj¡Lò£fæ9Љ‰qss³´´äñxÅÅÅtcåsI—”¹OåóF5‰òù­Ú¶žXã]•̲dff–››«üܶ›Öœ½E®ÓJCò#€Î&»Bª ™a²Í!I@{€‹®=Lòx¼”””÷Û¶¼¼œÇãËœzŠä\О;88TWW¿ßæ ^Þ§=\ÒÀ[[IÇWægfÐòa2,,ŒQϲeË222¨e''§ãÇS%?~<`À‡Ãçóããã !+W®d0TϽ~ýZMM-00°6ŽŽNNNNtt´ÌG›™Û«¡C‡nܸ±ÁJ>Ÿ_¿‘:$³å‡ Æø'---y’îŽG1Œ7oÞPœœœ8 oŸÒU’×ï οTùéÓ§Ë«Uúiii cùòåt‹Õ¿yog"Ý ïñÜׯ_?uêTuuõ÷«CçÎ=÷!ßÚmnÑ¢Eô+>ôhrîܹÕïÐ_ã½y󦬬lÊ”)ãǧ~½æââRVV¶dÉ’±cÇ–——Blllâââ!'OžìÖ­[Gl ñãÇ+3—'//¾gì{Ø¿?ÝÈÁÁÁ2[þäɓԲššÚåË—«««KJJ|¾‘îi ö)³JÒý.Orr2u#ÅUº}ûvuu5‹ÅJJJª®®¦nCÏf³ÿûßÿ*¾}D' ³AšôÜ…Bá¾}ûBCC[ûåÝ)}˜·öãçç÷äÉ“û÷ï#ªµA˜d±XœwØl6½RKK+""¢¦¦æñãÇyyyýõWTT”ººúÔ©Sµµµ©[u4ˆM=ztôèÑò¡|Ž'y% !111fffÆÆÆ{öì¡ÖÈK%ÆK¦fæöjº‘™L¦Ì–§ËBTUU9Žššš¼7¶ÌîPp\é}Ê«Rý~WðŒ¨½Qw‘UP%ªXýBˆššš‹‹ËüÑh»ÉL7¦|¿K÷&!äÁƒÞÞÞZZZöööt»)ŸMÉ hòDùçN}ÑÖÖ®{dåó¯É|y_»vÍÁÁAWWwñâÅ-r~é:oí6)2Œ"uAÛ„IyjjjöïßÏår{öì™™™©®®nnnN=dkk›™™Ijœœsrrzõê¥`oTާ .Ì›7oñâŇ&„Œ3ÆÖÖ¶¤¤äüùó_~ùå£G䕤^ROž<9{öìüùó>|(os •Æ+//oÍš5òª4þ|éjS¹½^¿~ýäÉ“áÇ“w¹½¾úê«ÐÐМœœœœúFämE^w´ˆúýÞzUš9sæÿýßÿ)³g*ÝØ¥K—"##©[$*ßïÒ½I™0aÂСCËËË·nÝ\ZZJ­§²•]ºtiÉ’% IUjRƒ(ÿÜïܹ#}ÙP™£Ë|y×ÕÕ…„„,X°àÕ«Wl6›NÐL]ç­Ýæ/EGGÇÔÔTDµ6“»wïæ¾óäÉj¥ºº:‡ÃY»víÕ«Wuttªªªê;¢®®NöÙgŸÍ™3‡¾%©<Êçx’.Ií!""‚ÃḺºúúúž={VqŠ(eÒxÉ{'4'·—‚¨@72}#r™-¯ ÝÑÌ*5è÷Ö«Ò°aÃ{öìÎ; .d2™Ã‡·±±¡¿FU>ÿš2Ð4ˆòϽ¬¬Lº”Ï¿&tËÊÊf̘Á`0¾úê+%7oÞÌý'~»Î[»Í_Šºººeeeˆjm&CCCÓÞ¡/ïP_bñù|jІ†Fý¹vÕÕÕôì’€€€G)¸âJ_Í#Êåx’.IíÁØØ˜^())Qœ"J™4^253·—<ëÖ­£™ÎŠ,³å•!³;¤S²(NÒ"³J ú]ÔQ¼Bd¿.™Ì©S§îÝ»·Ñý7H7Ö¤~—îÍââb‡CÇccc:…™òùהɀ¦ A”î:::ÒŸ6”Ï¿Ö@II‰‘‘uÝÐÐþ†EÚŒ3ÒþIÁ…è®óÖnó—bEEE“>¼‚’¿ –––Ì/º vìØÑ·oßÅ‹ÛÛÛWWW?þÜÌÌŒ’={ölêËd êÅqïÞ=å«%/Ç•W«AIjÊ%B-xzz*N%!˜L¦2ó&š“ÛKéF–×ò’ÙÔ[‹~ŽuuuН ɬRƒ~§ö¯@uu5u™UR¼í´iÓ|||èüÞò4H7Ö¤~—îMCCC@P^^NnŠŠŠ¸\nSÛ_™ hŠDÉçÞ³gÏ;w¾ÇÑeâr¹¯_¿¦–ß¼y£à£ÀîÝ»ÜLjذaÔ í®üÖnó—bFF†££#¢ZŒ&àóùƒÞ¾}»¥¥¥§§çºuëÁþýûËË˽ÊÚ¨¦æxŠŽŽ©©©IIIMÝÜÖÖ6==½ÑOßÍÉíE©­­¼#‰¨•"‘ˆ^Ùüž2»ÃÔÔÔÒÒrß¾}B¡0!!!''§oß¾ v¢ Jt¿ËÜðôéÓÿùÏ***òóó/^¼èìì,¯J„¡PH}¹B/Ô?qôéÓçúõ늟lƒtcMêwéÞ´²²rrrÚ¸q£D"9}útvvv£‰\­’ò}ÔÔçÞ¿ÿ‚‚:Š(8º2ù×z÷î­­­}ñâEBˆâ˜~£uç~kËlÏ6)^½zµù'^há0I‰ŒŒÜµkWEEÅRRRtuu׬YsäÈ]]ÝæW...îÆFFF\.wÕªU~²¶±±ñ÷÷ß¶mu¹¿I›;;;‡……Y[[kiiQŸ©y<Þýû÷ÃÂÂx<ÞÁƒ !×®]ëÓ§¶¶ö’%Käö‹Å&&&ŠgºR“)Ô—„É“'Ó+·lÙÒü¦“îƒqøðá_~ùESSsòäÉ»wïæñx ö ¸Jt¿Kohnn¾{÷n###>Ÿ?`À€I“&É«!ÄÕÕU]]½®®ÎÇÇG]]½A¤ W:ÿÿñãŸÆBR Sßþž©¦®fðáÁÓÿœŽ^€¶ “a§Â\Œ]ʾ,‹òŒwbÜëš×²rôÑ—x,{|l¹ðÙ ê$uþ˜`À1ØýÉnô"´A˜Ì«Èû«à¯(Ï(u¶úT§©:ª:gŸœ%„Øíµ»þüíýA¸;¹¥„ü7ù¿êl×±Ümùëƒz)cþ·õÖVçÿ8[ì²ù#äÆó¦Ñ¦u’··´ˆ¼ùåÅ/•9º¶ª6utJø¹ð aůŸþÊb°Ð‹ÐJÍtÍ,ËTg«›k½Ëõ£g›Y&7ùјãc¼Ì¼Ž>–ý*ÛçW3;½·÷Ë>öðXÂø}Ž~ö«l[=[-U­„g C¬†HˆäH֑رʽ4³»nwBÈâË‹ãŸÆß™zG•¥Š.€¶MV‰ªÔÙõrý°Õ+E•2KÞ/¹Ÿú"u퀵*L¾˜Í°Ó륌q§ÏѧB!dbω‡"„üUð‹ÁêoÞ_É£¿½M†pêñ©¢ª¢»/ï¢ÿ ÍF“*ÕµõrýÔVkªhÊ,Yð¦€âö_7êÏJQeonoúQ+¤Œ™Øs¢Ï¯>ÑGÎ8Âa†’G×Ry›iè|Ðùýéû¿8ûEÚ”45–zÚ`4é ïP][ý¼ò9õgö«l{}{B›ÉKÄ„ ‘T+!fšfúýŒ/2¨ÏŸ-ñ\Bï§A äð­u¬Ïåœ;úð脞äÝ^ß¾ÁÑ ¨es-ó…î UYª+¯­D@Û„I m /3¯u7Ö jûÓ÷— Ël!¶z¶ÉÏ“ !'Ö !Ž\GKmË5××ë„"±èâÓ‹Ù¯¥¶ íºøòbmUmg#gye,µ-=Í<ëÝŸ÷¿1l&{Ï'{6§l¾ýâ6zÚ LBþûéS S´·kO=35&0FWM—òMÿo¶ßÞî~À=þi¼¡úÛ:q£ãn<¿a´Óˆû#wÕ_¤¶ á‡d”f„ðC;ðé”Âݺk®¯92òutš‡™Ç,çYŸŸý\$¡# 54rOW;=»¤‰Iu’º A?¥ý4Ìfƒ0<Í<ÏxLØ6x=ô<ñÙ é=äÌÌ‘^ÉUçj°5Æ;ŒWæèõ×ô3í'Yø¿dôÑÚ LRX VL`LJaмé6MõóÝŸ{q{Ñß5tà0IQc©ùtói‘CvßÓ]X'<2êZ>ŒØØØÖ “-ˆ¾` ð\½zuРAï·-2„ L4M›¯¯/Â$€\YYYŠ ÄÆÆ._¾\z=.º`4 ]žƒƒŒßëgff*Ø£Iè*¤#¢â‰0 ]7R6#& ‹FJeb$Â$tõ1%Â$À{’;ÓõùóçhèZøÖç~~~hVèdÞ:à=Ãä{‡\€öF[[{ÕªUï½yÃ0™••5~üx4+tAööö¥Eyõ×` ÀÛ)=îD˜#&þ#¥¿vD˜ÄHÙ1a#ÿ#ííí&dÇHLáP6F"Lb$¦ð4=F"Lb$¦ð(#1…#•‘“€‰)<€ÙôIa ðö¼’t½AÞ€.îE~NøÜ…MÈ…0 ›Z¢-` ðöD+Èôÿï´”3اIEND®B`‚pfm-2.0.8/doc/customerdb.png0000644000175000017500000001671010661565715014120 0ustar wimwim‰PNG  IHDRlÞ$#+tPLTE     $ $"-%6*6$!$%-,60$$$-+-$*6$/?$0?65$?=$616?=?$5@-5@-:@-=I6@R6J[?K[?Qd@@$@B-IK-RP6[Z6[]?dc?@@@IKIRPR[][@Um@Zm@\vI_vI`vI`mj@vp@vuIzIdddmjmvrvzRk€Ro‰Rp‰[u’[z’[›d€›d‚¤dФmŒ­m¶m¶v•¶v•¿vš¿vŸÀ À¤ÉªÒ€‚R‰ŒR’‘[›š[›Ÿd¤£d­¬m¶³m¶µv¿¾vÀÀvÀÅÉËÒЀ€€‰‹‰’’›››¤¢¤­¬­¶´¶¿¾¿€¬Ò€°Û‰µÛ‰ºä‰¿í’Àí’Åö’Êö›ÎÿÒÒ€ÛÜ€Û߉äã‰íê‰íï’öó’ÿþ›°W52 pHYs  šœtIMEÕ &]­YçIDATxÚíýsÓV¾Æ{Ž5Y¥c3X‰`†Ä.tyér7À´î .ìÒé.a[ t¦ü·GËÒþù«sdù݉ìX²Žôy<[ov¢OžçÉúžOB„rÒ'ü °!`Cذ!lذ¡‚JGÀVvÐTP”Gp"pÀf1iæGúã˰يZ¡8&n6nÀf'kE-æM[™l-(¶f˜°ak™d)°•ƒµÀ `ÃØ€ •ÍØ¦)°ák¹Y°ák¹Ñl°!Ëm mÀ†±²ÞÙ&h6`6d}ŒÆk÷[`C9›ôò·6`«¨³É&°¡eÂö´Û8Û}z‘½õŒ™^j¬u¿ Œ 3Zú¸ë^~ÿm«¾¯7Ý¿T?sI?‹<¼ %°£Çj_~kúªx]z=i­©÷n„ŽhÆkìÁçΡ¶:ù8øNúûï¥P~µëœ¥|¬ `ÃÙŽUW&pŠV2YsÌm2æE´Æ¯ŽšÉ@¸*Pb-êÚźN´Ò{yÅ, FíD9õþQo&“ƒsò«wÉ‹x %Î&¾§'-ÑèD)Ûj(`#Fç†Mß:ò/}ØÎ˜¥çú«´ lÛö<Øp¶ãcTéœw* x—PóÖqú1*ÞÁâj?i›6ÕÛ4¸üUßëò[e`ÛQcìŽnwoƒo 5rM©µˆ«µú{Ñ žˆ;QA›Ÿq¶} àwÑuØp¶êĨÚïþ®uUoó´%?û»ÍwÁ7þZ½û6¯¹gžêI·^¿¤O}4…lFÑl C×þgk­î?ô©Ñ£MÎ6Ç}y‹­,l+¿l%ŽÑ• g«–³-OlÀ†³!b”6[åœMálˆEÄ(°¡Ó9›6DŒ¢B¨á£(íì oÙΦ€ ÍHͥƳ¡Y°£¨LΦ€ £ˆ6œ­r€ Ø€ £hEò|1nm©ÍufIί¡­q¶ Y]ê£|N,¤þæR[u`S‹åô±Ýö– ;°UËÙRhóF?²°¡EaKµ»÷’gm`CùÄhzØh³UÙÙTªsÓÕmýß]gë'œ -|°eºÝiØž‰áKgC‹ì9`Û¨E‰³¡ŒÍKˆ#FQ1:lÄè’¨5’bÊ¥X"=l›’]lÅn³%ÓUý>éØ#%;^ÒAÀl‹ìT?DCk»[Û|ÞíWÄ(°-t°E–°+`ÃÙæî d÷þÀFŒææ¬ÀV¡ÅÙ€Ízg6`ËÍÙÔ|=`#Fq6`[fŒf$`¶©5<²xÌYت£Ü¤ ly·™€ Ø€ £Ë.êl8ÎlÀlÄ(°álÀlÀlÄhîQ`ÃÙp6`6`#F g6`6`#F Øp6`6`#F Øp6`6`6bálK”›½°YF›lVæ€ Øp6`¶5Ù€ÍÍØ€Í.Øl²¶õØ€mUÆl–ÁfON°Ù›5Þæ›ý°YBÛc6 aS–°Ù›Ö6ÍØ€mH+` EÑq[wB`;N·E<&âɰían ذ ©æ§Q3L'æ6§­Û„쀭°¸­»³?rEaó"`¶ÍðÁ;žß‰çφ­Ó8ßé­O¢uîöК¯y3ÙÙN-zê5ãMûLÞ%éa» šë÷«êl¢†®ˆÑÚSœM|MÜd¾™l4zÛÄkŠaxËMvŠøßÈ>{ï’fïêG"ój9Ù“>lea‹Ý¾Ãr[OFÚl×ûˆ˜ùfò©švDŸÉ¶OÃÐcûtiž[‚–ŒÑóÅù g“þH -^¸-öw(âMýÞ\[oŸ‡FÕ†Íé‚NÀV›[Rm¶íþ*~¶)ûD8[’…7÷&b4^`Úla‹rœ±½?álSö‰p¶9£ƒðà¶ÉξwEì}Å¦Û E§×A¨˜Ÿ/¦ïUûÔGÂA§98õŽžúðï›'çÝN/<ÿiS_Ð ›®q¬m¿q³¿©¯Oˆôö±3¾OTig;Æóø°°!`[¬-çûQÓ˧¥l8°6`6`6lÀlÀlØ€ Ø€ ذ°°!`6`6lÀlÀlÀ†€ Ø€ ذ°°6`+#l¢8¶2æ° ŠòrØV[´IœÌy¶üaE"Iól¥‚­Ð£q)lå­ðƒ#ehnÀ–/lÅd0ÊR`+lvŒž*€­°)`CùÀf‡±e¤À–#lvøZvÖlùÁ&k$€Ívذ¡|`³ÈØ2¢ ØòƒMÂÙr¢ Ø€ ؈Q`C‹Â¶¨±u¯œ¼Î¹[ÀlK0¶ßï¦XçŽÖlEw¶å(°6O6ÍÔ{Ôuº‡ú™ÛЦN7ö»­úåC¥çµôªûÝÆÙî#ýäRýÌ¥}`#Fç•-òq𽼨àR=ÚÑ»® öån >w“uöå—Ap°¦Ÿ|»Î6œmNØŒk WJøš)ñ]|XWã{  S¶®üµ×¥p¢É{yg¶yºq6QO&A+JÐVÄ™c^‰³ ñkýD6[­¦YlÄèü1jÆä‰ƒï¾Ô ΘTÎ%K3½¯£õ©6œm¡mõMN­]½| ÓR¼bT\Mtůš´wq¾—¿:Ý(%°U¶!g3Oƒ]ÙÕ8<w‚àóÚÛÅ'"ò»;Wt«.ð;yˆ³£sžúš¶ºž6ELÛ{ù³èI·^¿¬Yë-xÒ]k]UæÔÇZ«ûbg›?вßbª£[õ`[•€­‚½ÑUÝbl8Ά€ ؈Q`ÃÙVÓÃ\¶†lÄh.°íì ؈ќœ تêlj°£Ä(ΆˆQ`+•³)b£ÀFŒÎVìD%F-_gó|±Dk¶ÂŨë#9F7åÚ(Î6!ç(œý˜OÃ[Ê9#×¶*À6×»|l·Óåܼ1lUˆQ'•mÞHžy"؈Qœ-Ñ{ɳ¶ÀÙ€mÁƒ=¯¥Z8Fe:ÂÚú¿»ÎÖË´°ÑAÀÙt6Û3ñ"|)q6”lµhââl(Ó¯Yâ8ÊÁÙÌ$m›-RrQÁ=QŽ#…E¶ìaÛÔë:©cTé+÷i6–¶bô¹î Nê[¦ÎÖÆÚîÊÍçmÑ~•Ag¶ÅÎFŒ°•ÏÙæ‹QlÀ†³1 l…v6Uä6bg¶¥ÆhF—tæ¼Eتàl*£Îl”9¶ÕÅ(°ÛÒM°£}ØËÏÙâCææ£¢הЫï‰)ÎÖó[]{Bè¦ÙŽYI³¤ÏAßödÝ•vÂá}-ýUk;!FÕÂÎ0Ê:œá+¹Æ¨pã¨ùÏÐ4ÆzüibØ6’ë6Z'FóëdNòŽÙßW ŒMÓ#rŠÑÅ`{ÇQ)bt%§ÃzhÉ(;=_\œâl^µžE®¦©“ñ‚þ&²ÙÛ¢·¯ó%ñµIk;]Œ¾Þjx×^„oZ†¯7×îÅgæþdϬÑ^ÿâß¡©ÕöÓ5ùÅoÏÚëænå××Úík¯ãÏ.&œ†ßÂttóo;¦…Íï‚NQoÀ]´ç¯û›ˆídþ.j~Fþ_Úíšµ_‰?Et¹áp™ ÷迺°Gìl¯Ä½0üƒó½T¾ ÿ)6~ùÍ„Êk½à®øÅ$Í?“ŠZý·w’S*Å…-&#ÎÀ›{3ctÈÙÜû7Ù1ê$­:³¯rÛI°Í£[â·áÐ4W~8Äè–ˆ^¼ÿÛk½é–Ž)h´¥÷ð›¸1hý Þ~øÔÝ gyWîl¡¸¯0œ£CÎö@Æ]MÓA¨ ­q÷àöȾJblc´*Fe;œ€í—Mñ§ýßc¼†Ø^ÅÀ&E»íyƒý·Ÿï,ïÊ`|Ÿ©Óœú˜XÁL{KDXë-ÛöãSÉ&†?ì=é„%2¶`Sé.ŹÖù3îý²ŸÉÚO BµY°ÕÚÓºÓ.*Œ ã:Y×g+ú…x»¾s(–£‘Áé™Xà 5jÎHŒþÇh›áš^~ñ§1ØÔ¸³Õk ²¯Ï6U5ØLŒ¾Ö„{ºÝµ)>„?Æ8ÕŽŽÜ¨ à6þ+¶z„Ú‡ g{-þ†?ËÿLÄh„Ð1*«Ûò‰xçùÞ `›«7¾Úl´ÿ¨¹{½!¯½¢ý1üqÃm\‹àz¹îêŠl¯§>"¨Ì ‘¸hÛ«­õöµ_Â~·á·WÃÎ6÷w V*}¦L?²ƒ-ck‰¢ÇhvâûqгåÓdË ¶aÚŽ‡M-~mô´¿ÈnÀ–lÞ*`S«u65þ¨.WU¶SÆèÓn«qùPó%½‡¤ ‚]·û¸n`Ûï¶ê—öU¼ì¼^–£³­,FOrÖl?F™m_îÁeGÓ&å÷+‚‡ò‘z,5ÃfÙ®sÐ[¶&²…-í’}Œ¦’¶yÕÕã¿•_j \3Ç×VÓ»½ g¼—Wt¦¸ÙÇhZ“.Ä—'U5a;]Œ:uÓÝ?«1¨Ç±uÓAPúŠt³Õò†–©^–­³Yt™WÙÅZQγý­{¦~é0B«)zDíÊ nŠæÓ xòY½ÞÕѦÍ`9­¶ca³ç2/°-þ‡šßQ:&FGk¥;©»Â¯}|bíO£ª ›]¬ïl¶ü8¶±f)l*kØ dmª4ÆVÕ=¶Ô< c#FO×fÓ?(xÁµ5Úl3`+úd!jóÂÖì•¥nŽ—¥^NŒª"ÁŠ‚à¦Êakó;Û¬²Ô%t¶ô¸)4ØšÛÕ- uaÉB5Ûô£aXؼЙV–zi½QU,ØzÀ9crÍcPèTŠeß+¬÷(G¿[h³æ…mFYê²:Z­³M-K l(g å^v1šS˜›%Î6­,5Ά2‚m²,5°¡lbtJYꌮ*`«.l3ËR§w6wâ,¤ä±r¦ ØJçl3w“þ¬d<2Jÿ1ö2Ͱ)Ó6™ó>yØ,†M-p˜—úsÎ6ÍÙ†µù?'ï|óFš ˆQ`;ÞÙnÜ;yçiÖÁÙˆÑÉ–;1 lkG׺vfÇh<&J4}qMn}ÐÏt)÷†Ü2c§4LyÐxd,=2J#åç^€­„°É^mug–³Å é±O~ºùv½M>nõFFùÂí×7î¥òZü˜ŒŒ‚³£°iÞ’<›ûDSî_âç0¼÷¢WÒý߃’îý±T¶tã>gÃÙ†o,þŠ–ám¬ÆSïbkx”pãZôïhtd3r[RPÛŒŒÒ6 p6Ë`[¾†¾õ71T‰#ÅTgÀöB¼ùYÛ™åh02J@”Z›BÅ™íBŒ”~՜ۑûGÓ-˜ŒÑÞX*C#££Õ„-EŒÆ<ˆÙ1_o½'¯™ÎçÐÈ(fØÎd,•¡‘Qp¶Ê:Û °©‰ÛƒÍixxÞØ'zÖoµ—fÑèÈ(Ãc©$#£Î6 ¶>‹JäK;°U 65½ðÁò?€¶jǨšYe#Ú­:Î6Á 1Šò3œ åf,À†¬w65Wت[ÑFF¶òÆháFF¶ò:[á 7°[µ5³Èiqª…ÓA(Í(rг¡åÃ6£È)°¡åÃ6£È©}ƒn[ña›QägCY8ÛÔ"§ÅB©œmZ‘SbeâlÓŠœ£(#Ø&‹œÊ&F§9M£ O±Àex`+¾f9Mïl2£¿`«’ç-ÿÛlÛm/õÛ«9 >›Í°©¥Ã¦tÆ‚»Ïp6œm¹1Úž›hŸöÌ °•6Ç‘§q¶9`ÃÙ*£R.’3œ­-ÚÏ·ÜÍ×½§›ú%¯uÉ…&)Ý­:FßDËÂí~ñÉ­¦ƒ76® TÖÙdR)kª³ ]Bòâ~Zûg芸Úät͘çâyøÒ}->IŒÛ17·$¼ ·Ùt¹Éð)!)\3Ç”Åz£Ëdmèer ¶~ñIb”=þN*Ûoy •ûKêüé9fF{ ¶~ÀÉ/ ›”dXzrÊ‚ ØÂ ØŽÆaó’É6bÔNØ–çlý[êú÷×_wr8FÛƒ5Õ&7uÞÖbØÖÃðãPñI÷?ÇÄ(„*À¶@ÝIQtbz^‰»½j“ÏÅ‹ðe샛âCøÌ¬Ð/>é6þ+¶ˆÑ*;Û¼u'EûåÅõWa¿¶äPµÉðnmóy[´£¥¯7j×ÿ­ðqP|ò庫—£8[Úº“¢qgتÛ¬º“Â˶tç†Àf/lÓƒìØº“ýìÌ,ÅS°YÛjëNÀVuØŠZ ØJ£y—L«á&°ál™v`³¶”´¢îäzl–ÖÊS2«;©l±›°Ùsë許›°YS‚Á6ûa+„·©y Ø,…MYhlÀf'lV4ÛÆ Ø,…-EÇmÝ ­$°ÝÜÖ§|b`³¶U››šÏÖ€ÍjØ ›¥ëîô l6æoø”²` ¹Î¬ lvÃf€ëK ?²Sra$'‘Ë9ý°Y#`6`6`CÀlÀlØ€ Ø€ Ø€ °°!`6`6`6lÀlÀ†€ Ø€ Ø€ ذ°6`6`6`CÀlÀlØ€ Ø€ Ø€ °°!`6`6lÀlÀlÀ†€ Ø€ ذ°°6`6`CÀlÀlÀlØ€ Ø€ °°°!`[%l¢8¶2æ° ŠòìØ>9–4sˆ‹#ýqìå Ø>™Z¡8&ÎRÜ€mlEE-V[ÇßI ›ç{ÀV\[+º¹ÝŽ>—ðR;›?ëÇØ«lÅF­Èæ&½¹bÔ›õS¯*°ÙÀš*&mb>Ø|` Z=l͉mè;M¿Ïž?²¼ãn'Oã~ôu¿q3NJ·Ãæi²¼^×›ç;úÕð~Ê › Ʀ½mõ´iz\{Öž˜t$C×ÍxA-fÕaxËIŒÎlo)tcÍ­Ž³Ùák…°6áÆPi^ÜÖ“?ŠÓÓûñªÆcº¾à MœŒ_úɤ“üL¯l–øZ!hëùO-Ê<Ïç'-v«ó1=úãÖüdŽÁÉs¶š_­6›5ÆVØüñ.èl1ZÛñ‚!Ø¢9¢Ùï T6‹Œmõ´õˆÃïæÞ¬½=p¶xU¬Û5ã‰æec4FeUbÔ"c+ lq«LNq6 ԭĽDÒ¸UK:ñ¹ém1ˆÜ¨5÷ ‚Óm†¢lÐ6tJ6a«ÓˆO} æ$1ÚižOĶÓ?õQÛNNxl8;aoËhO÷͹·§>¬ŠÑ à×¶Ç|.¬ºrp¶wQ_­¢°ùÀv lÙ[k»ߖ¶ñP¶<šl3a“^­­é7}¯5q½ ØrÍ› [½Ü9 lËŽQOz»î¹ýÞÓ RÁÓn«~ùÐ$¥Û}\`;ˆ–o½ØËö»³ÝG„²éÎ6‡¤ü&>—æé÷+‚}¹Íq"ÚÊGê±4 ‹z2Ù—_Fô­E¯š8°ÍwÄeÄÒ¸ªŸºfÎùk4G#åG6ÔÌnE+™tõbs»Të4ßh¶ öFã–—8›WoŽÒsœºé gk&'iªál8Ûb° úV%Ï$ø™egD0 Û`¶EÛlÑäqe@OWüŸžÅè9½Lư}ïâQŒ*ç:MŒÆo lU‹QÓA? `û›¸£» ºƒ 㛵Ή·Á·f…'Ã;œný½èžæÛºÀV¹Bó¯Ýµ³O¢gMÑ£íI·^¿¤»§j×¹ðç¦h> ‚§-ùÙߣÞéÅk­«€çžyBŒÛÎÖœÕYL=›%FSñV°"£Us6OœºS‰³[ñlU‹QlÀ†³°°…¶Ý‚l8°[¶°)`¶ªÄh£lÄh.°íì¿:°-6µ:g+~u `+MŒâlÄ(°[¾°eg—1JŒâlÀVºu€ ØV1JŒâlÀV2gó|QxÚ€mQØ\§'×é?HΡÑ-v¶â ØQç(ì=ŽúÏúù4¼¥6œí”Gùc».ä°Û4gK¡ÍÉ3O¤„M1ºÐQ¾q/yÖ8°å£ÀlYÇhC´õw­ŸÒ¦€]ì(Øž‰áKg¶lcÔÀ¶¡‡j’8°eÛ7lÓf¶œbt.Ø"%Üå8RX$`Ë>F7å\1ªô—-Ó=Š0N<*TŒš‚È&F%GMN}k»[Û|ÞíWtp6btñF4Îl ÆhÖ°)`#F‡Mál81Š*£ÀV¦UÄ("Fã¿`«l]Ò!FéNq•lÀ†³QSG°41Zìb tp6œ ²´7šm:ΖcýÒÞƒ€ˆQTŒUÀ†Jîlж6Ü€ÞhnlÄ(Άæ…M­6K°£Ä("FQ•`SÀ†,Ql81Š– ›6Do؈Q`Cå…ÍåZ ›e´alvælê Îlh^g£É†p6Œ­°ÙDÛ:‡ÏvØ”5ŒÍvØì±6ŒÍ~ج¡ c+l–І±•6…±¡œ`K¬Mal(sØBQô$]Ç×J[ÑÛmØZ©`+²¹akeƒ-ÂM²Õ¶Îц2’*h.®VRØbàœ1¹Ž{¬ô ÎiïaÚž9^å† !`CÀ†°!`CÀ†°!`Cذ!`C(;ý?£º·bÆÔËIEND®B`‚pfm-2.0.8/doc/main_window.png0000644000175000017500000003015410776405670014262 0ustar wimwim‰PNG  IHDR‹`Å pHYsÄÄ•+tIMEØ 7$ú¿Ÿ IDATxÚíÝ{\LéãðgÎÌt›jšîT$TTB¨•²å¾Š­BÖ‰ìÚ¹†r[ֲ붛]¬[K¬K.k]vmˆÊ¥"¥›î÷¦™ùýq8FMcÜÖ¤Ïûµ¯ï÷Ì9Ïsæ\æ|<çÌô<,ò²ömÀ’•–,ý’Õ(›&lúÇ>”ßfõ—Î)Ot6¥çãÀ‡ÒÉD—Î):¤XL<ÝTˆ£ðÑÞ=Û®`Éöƒ‚>xÝÜÔ„Ç'Ve¥%s˜Y"±gࣔ}þ§Á“Ã,üç΃'‡ÝÍÌÿ°uŸÝåÑ ¨Ä¹8‹¥ÜKÖž_)^¾¬¢:÷RÄàÉa‰r?T]BHQÆÇ'Vqd6 Ê¯üÊVUåË…lã>>,ŠÂ‡@ɉDŠÞ$Ñi ‰™XøPu !™kÑWäÕ½r¥E¹zöÃÙ*j8ýr¸XêLq5Ñ×â²ùlã-eؤS!ÝIó#é¶ʳ΃XáÇ8tˆÄ&>TÝ Õh-£; <˜EQE±XE=Ÿ|ùÿ§††JÄ,<À’+èS;jjhêý{D"iûùrey6qd±œ‘¿ô?Û åYg‹oC)|Òi ‹™X`êFZˬ2ú§ë2%›ÎiZ÷EBI$é÷c)ˆ‰DÒ¨.4"àq !©)w5­Ü(U^åÃë¼½•dÛäo̶©ïã”ê8·¬6}E‹Ä/.íWÖe ¼²nd µïö»„CA]_Y·Ù„"¢(:…(Ö³À"¬gsÈóE, õçÜ^„oOeø9›è¨W £âòÆçK/%„ܹ|’2pÕ忺B¾?›5ÆÉX_Såqqí¯—slM5Úèi«s²žÖüpîÑÝ'•o°%ÞFAîf )­Æ>,ÛqñqUèÙ¢^F^FzšÜœ’Úßcó}å,=;Dzåü#¿>ÆzšÜ¡ëãY„x÷2òìah¨­RR%<~³à÷Ø<úÐÍLk²«©…:a‘¤ìʨ¸¼¸Œr9óižCݧþo´©¡N£CÇb‘ÑŽm†ÛèkrŸV OÝ.<›KÖä,’æÜYg¡‡E‘mK(@êµJ,3—¶t]ï­IÊ™a+z9edÖõÞštd†­t65Z[Óº„ŠY&°¤šQ/òˆ<Ï,¦ Õ¸b+DÀ¼ÜàõGû*ñVütw³¡fUôÒAënк¹xtsñ(8½–~iR{wÂò}!Ë6t0P_îÝIM\é3{SHØºŽ†߸ ê‹¿Á–¨r©ñË÷õèýýÛ†ÚéOíÁ k$Éç= ƒÜÌR<4fÆ„Iü—~”+)­MMÊØ°]CÆåø¿Q½¦¹™%$?pñšz8êÈdWSó‰X,‘HxXtiË Ûð³Ëð±›7¬Ú¾®® ]Î|zånýœ¦-ÛÖôÐîcü?“ ±‰ÎŸO>{úÔ$¯ŽµôÇWÎ"f›‡ô±\<¢NØú;þ/¸êal+ÿˆŠ&³$~~óÕ´¼ôL9uÅbñç?Þyé³÷ãæÞ—©Þì×p,"EÏ‚‰z)¨X,´œ¶ìÌM¼Ô`Ôcû¹tBˆ¯‹eå½óÊhwóÐîæAOÿ±·äA|bµ!ýrãêe•E¹·+ !fm Kã"ÅuUªŸ íÍü'sö_}’þ0S¥Ó§s!}»Y”Æ–ˆE^F„5«W•×JD&ŽÛN¿ôo ü¥´-{K3nñº Ôîæéa§CÙºù;!¥q,±šâÝϲâî9"!ê*”°¡!ûá}U“nDíæm9Yyÿ¢¨¶¼¹ùÌ¡{š“ÑôÐyt7$„DlÙ$RÑ9r«‚2âËŠ¤3"‘³ˆ^§ßçŸ-pÏ+®8ëÊÛ¼}X»¡¢U7£Äb‰‚ÿ5m7I/’Y^‘ºÒä,jÔÖkþ9%E£Š0*ÜåÑró 5,?¥¸êeêf„6F5oª™Ø±µ ¤‹©µëIO””–iuu”p4è—EÅ¥Z]z³Ô´éƒ/®«ª/ÊPmÓì_qçŸø?C%Òsº™iMr5íhÐC]…CÿÛÁ×Ö??2Ôvd¶ÃÓ+×0“®h¨­"g)­°¨X³kO¶ªW×ÌP I9ñ¢…edX›}[ݬۮË9“ûïÿé;±X’–[q)3öNZCEAsóå:=M.!¤¨¤Dúg9‹G12Ы{’¨nÖCÎ"z³ƒÿÇb‘ï6oÍxR¤Ñ©/GÏ1°`«i·æÏªâßú?kˈ^\ÚÒue®‡™)¿ntHéZÇ¿²¾ñ¦œ÷UøI9y¹íÄ¢èÐ’ êùUjP¡¢Î˜êjÒW¸¾¦¡"ŸÒÔozîŸdŽ ‡ßæÅK®*ûÅK‰¤¡®Ñ°&¶0zó²öQ‚ªÙ‹ó½xd']÷ë•Ûþýû"‹ˆnœ‹b±X‘PT[^P^ßFG•ÙB]MéÕÊ_úâqGÃo#!¬ÂòzcU·‘㋊K¥›Ü å…Q7rîûÍT¥Ä±‡Ý¬izXŒH¸«b`ÑÜ|ù‡®¸Jh ¥¢'Ô±ÕôM !ù…Eba­¨²PÎ"z_Î[þãÚ¥«Cýxøjf W¿#‹«ÖÊ?¨ÌéÓ³d¶!þå'Öb™O»e>5oò´[F]æ}é7¢_F‡ôó¾Í>‡¢Ãˆbž@QÒ¹DG‘~RŽçP³ƒ'k«m õiní!¿ý~ŒRQ—ˆ„®ŠÆ/Yì—o­ÙÒ_V4}/~úV‘¾[|ù”BHi^¦vÇ^s,}±qCÔܦ[È¿ô¥­¥8‰$*.—4©ƒ‡Qo¯£§ÿ¼c›¶ —yu¶q§n›XÀ"„ÔÖÖ–'Õ–77_þ¡û3±2q¬7—jðu2#„D8KqÕä/¢×y=áΜM‡%bÉšY£‡õê@8ªøˆŠÄbú?9Òéÿž?±–]÷ôl‡Fÿ)^—2øÛôËÁßÞó¾LuŽÌmÝs&öݘzþ¿/ž;13ïf=Õ2ÀÓ(B¹»möX“6FE•ußíúãà‘“j&¶,Šûnß…«oÑÜ¢UÇÓ§÷ÕÚ¹iÕÓÒªý1YÒIw$.OT˜ê7 Ç‰õNOJjöýûÈÅJ÷Å1r—6u#¯üQÒ¨OmOê_/"IÙ‘ݪɽÇï=öÌÂ@#›ÿõ–HX‰ùß®[ÏbQ EYgî´“9_þ¡Û}9»¡0}˜»Ëx_ÁÓJáOQ—vîý]ÅÐ’Å–·ˆÙÎ[*æn9ömðÈ…Ü´_>’ÆmåQ¦-3pmìë~÷÷žêÊ\[3Ï¡Ä /íŒÿ±è•ÛÁÓ#,ŠjT·u:~úü¹¤¢ºÂ‡âú*Š«¦fb§¢×žÒÔ“ˆÜÂÿ-:³†9ίõRñ ˆ{X4þâÙê‡×ÄõU”ªfäÑîÕé—™õ¹'ÚwâûúüTqC[Cçc¶Õéÿ(²TæöœÉdÿöK]Aº¸¾’¢8l~[ýŽÂÂô+bó‹%Ô<¾)®*‘ÂV×Qoß“E±®¤ʜϬ\æ¡6‰-ýñ·UõÅYa¥ª¡bÔEEß‚¥!³HzƒïTèöŸ±¹:ý"kX8ª[ºá׊…™èÙwjÏÏø‡ª+P/å‹¢ük;‰DLðŠnàLUŒ¬$ 5D,&›ÒÔg©ëЖßÇ_R[Îç×zù`ÜUKË€ÔUK$bBQÚ£ˆ°–^KEC½c_#+ÒPC$„°9|_‰°F‘¥M·‡ÅÕP³pR1ìL„5"&KEÒHÄ"®‘GË@\WE$ DÂbqU)-#9ó™•Ël]qyÞ‹¾ ^÷Ÿk`¸.=Wza#(³öƒ‚^«'¹'9>xÝ—ÚP—®ÜÀYøˆµ15W°dnv¦RÔ­.~ц"Õèžàc–›ZÜâêBX.}«u1(âd!$îÄÎâ‚lz–®¡)Ž |(LåçdN›9GÆ/6SSSq˜à¿·¯‘PVVVÖÖÖ~~~{öì‰äý)ÿôéÓ>|¨´Ç.!!ÁÏÏÏÖÖ¶{÷î¾¾¾×¯_g>~Ó¦MÍNNN®®®VÎ]SSôË/¿PuóæM.÷Y/±ܸqãêÕ«ûõëWXX¸zõê/¾ø"22REEåÍÞKUUõÎgĹºº~ûí·ŽŽŽ„æàÝÜåQÅf³ù|¾»»ûŽ;þüóÏÄÄDBÈwß}çêêÚ½{÷Ï>ûìßÿ%„,[¶ìéÓ§AAAnnnüñ‡Ì2´«W¯2ÄÁÁ!44´®®Žž)³ð·ß~ëääÔ«W¯Þ¾}›’——äàààìì¼mÛ6ÅwäÑ£G'NÔÔÔÔÐÐpvvîÑ£ÝÞÙ°aÃ’%KÜÝÝUTTLLL¾ûî»ÒÒÒ£G¾ÍQV}ŽÂápèiŠ¢ÒÒÒÆŽÛ½{w77·¨¨(º°Ì™Œ¦@È¿7nÜèçç7lØ0é+ý'”´:tîÜ9..Žbccsâĉ›7o~ýõ×555Ë–-Ó××ß¾}ûÅ‹?ÿüs™eèõœ;wîСC—.]ÊÎÎÞ²e =³iá[·n:uêÔ©Sqqq{÷î566‹ÅÓ¦MëÔ©Óµk׎=zìØ±³gÏ*¸ñíÚµsæÌ¹xñbQQ3?))©ªªjàÀÌ•ÄÄļó3ÚÐÐ0mÚ4WWׄ„„Í›7‡‡‡'$$ÈœÉTizpa€2PäbLOO?pà@tttuu5s¥¿Ç„"„èêê–——B† ÂçóY,ÖçŸ.dþíqse&Ož¬­­­¥¥5}úôS§N5WXMM­¶¶öÞ½{õõõÆÆÆFFF‰‰‰ÙÙÙ!!!\.×ÐÐpܸqgΜQpËÕÕÕ÷ï߯¡¡±|ùò¾}ûŽ?þñãÇ„’’G7v¤w³¤¤äŸÔ[·nUWWO›6¢(;;»#Fœm®°½½ý—_~¹nݺ¬¬,WW×¥K—æååÕ××>œ®" ­­­ßx ‹Õ«WBž½3yyyô>4WØßßßßß¿´´tÁ‚Û·o6l˜@ 8}ú´ô½¶mÛŽ;vñâÅôÝ%Çû믿˜c]__þüù©S§¾ó3­§§———'‹é‹-Â5ÿ%>ŸÏ´*è !ÄÀÀà•cNNŽ••=ÑèSýÎîòÄb±H$*//¿téR``ààÁƒmmm«ªªÔÔÔLLL!111YYÏFjÔÖÖÎÎ~ÖUse!;w¨¨¬¬Ü±cǰaÚ+œžžž˜˜(‹y<žºººD"éÞ½;ŸÏß¼ysmm-!$33Sñ_^dggoÛ¶-33³¡¡!+++22ÒÎÎŽ¾ Y¾|ù… êëësrr¾ùæçííÍüãP÷\CÃ[ ؽ{wuuõˆˆ±Xœœœ|âĉáǘ)}'ßè à‚e ÈŸsçÎòòòŠŠ ºyñ^ÚPóæÍ›7odz°°7nܸqã!öööƒ òðð022j×®]§NèÂS¦LY¾|ùâÅ‹CCC}}}e–!„ 8Ð××·  ÀÝÝ=88¸¹VUU-^¼8''‡¢¨Þ½{Ϙ1ƒ¢¨ˆˆˆ•+Wöë×O,›››‡„„(¸#ššš<˜8qbaa!ÇëÛ·/Óñ÷÷×ÒÒúþûï¿üòˆ†''§½{÷ª©©ÑK.\¸páBzÚËËkÍš5o|R9ÎöíÛ—.]ºuëVùóç;88BdÎd‚¾ÑAÀµÊ@‘‹qÈ!¾¾¾………Ì•®–K_Çb®I÷Œ>6»wˆ8qâÄûxÐz8;;ïÚµ«sçÎò‹I÷±Ùl/À2Ñ72Ý¿¿Å¯æv§Ñ¾\»vmË–-où¸GæÛµÄã­Ù Eê#»œÜ‹õZ?m%GZ§ò1æà¸ÀûöÆ¿yFï+ ¼P€„@B ­›Œ_DFF⸀2&TjjêèÑ£---?ø–­X±bôèÑÌKeØ$9BCC§L™¢´[ÛhóZpÊÒÒRþ4oôèÑ‘‘‘tHYZZJw¬„FŒA§€n­ôæ´ ”rÆ“tH)>>„ggg??¿óçÏ‹D¢èèhggçàààÝ»wççç3fâĉsçÎMLLìѣǖ-[x<!äÛo¿ŠŠjhhÐÑÑÙ°aƒ½½½¢áJQl6»M›6®®®/^¤g®_¿þöíÛªªª>>>„‘#G><>>¾ªªÊÚÚ:44TEE…òðáÃõë×§¥¥éèèLš4é³Ï>£ 1âòåË"‘¨{÷îEEEóçÏçr¹“'O:tè¶mÛ¢££ø|þÒ¥K»víúZ[«­­íììlff6a„{÷îuéÒEæ7}__ß+VX[[§¤¤¬^½º  ÀÎÎNOOÏçÑ›íååuåÊ•²²².]º,^¼˜ÃA÷„Ðjªzðn__ß}ûö%''Oœ8±C‡={ö¤GŽa³ÙtÉóçÏ:t¨¬¬ÌÇÇçÖ­[kÖ¬144œ2eʦL™Â ó­««›——÷#ß^¾|™nÖ‰ÅâÐÐPGGÇ•+W–••Íš5«]»vŸ~ú)!$33“îÏ7,,l×®] óæÍóôôüñÇïß¿ÿõ×_›™™ÑcR=xð ""‚Íf³X¬˜˜˜U«VÑ#ú%''_¸païÞ½:::………o|ÐÛµkסC‡;wîXYY5Ý`ƒæÞ¥¡¡aáÂ…ãÆóññIJJ ¡ÿa =~üxûöí„ààà?ÿü“\€Vq—Ç …ìïïOšÑ›yæÂáp˜¬¡=733ëÕ«—“““¹¹¹††Æ!Cèç¾o3Ì÷Š+\\\¼½½UTTèÁmRRRrss¹\®¾¾¾··÷¥K—èÂ^^^EQåããsþüy:njjjÆOQT—.]ü×_Ñ…G%½ UUÕÚÚÚ´´4¡Ph`````ðÆÇ]GG§¢¢BæËy—ääd‰DB§’­­­“““ô:™}¤‡YŇZQŠ ™¾w3x·®®®tEæSUUef«¨¨Ô×ד·æ;,,lРA)))sçνy󦣣caaa}}ý„ èB¡˜Ù*@P\\L)))144dvÁÈȈé(¾¹'>:uúßÿþ·uëÖììlGGÇÙ³g ‚7;¥ÚÚÚ27Xλ”””H!`hh(½N g'•à …øpC+J(é¡I3#z¿Ù¼Í0ß,«K—.'NܲeKŸ>}tuuuttöîÝÛ´ù“››Û±cGBH^^V   €Ù…üüüFÙ*“—————WYYÙêÕ«÷ìÙ3kÖ¬7Øåìì쌌Œnݺ …B™ÜÜ»éï è‘™Zû]^#òïVÜ;æÛÓÓ³¨¨èÊ•+666ZZZ¿üòK]]ý\†ù ÑÁƒ+++«ªªöìÙãîîN±±±QSSÛ¿¿X,¾ÿþ¹sç ÐtåZZZ¹¹¹ôtffæ½{÷Äb±†††ššÚkm-= |EEÅÕ«WCCCû÷ïomm-sƒå¼‹ ‹Å:rä!$)))66Ÿ`@JVµfFô~]ïd˜o555//¯}ûö9;;¯_¿~Ó¦M#GŽ”H$fffS§N¥Ëôïß?00°¨¨¨_¿~_|ñ½ k×®]¿~ýîÝ»µµµgΜ٭[7™M¼7®]»688¸cÇŽk×®¥Ÿè÷èу^‚ÂÃÃÃÃÃéßCyyyy{{B(ŠjºÁb±¸¹wáp8ááá«V­úùçŸíììÜÜÜ^ëGU-޲Šö–½Œ9rãÆï{k?¾nݺÿ¬oƒÅ‹÷èÑCúë¼Wn:`%§¯óìqª¢£¢+áˆÞ...2ç_¾|¹e ™;"/’““MMMù|~BBBll,ýc(€Ö{—§„#z·¸$z‡;òàÁƒ …B>Ÿ?oÞB]»vµ²²ÊËË{·«ÍÌÌ5j½ò³gÏ*¸ ©ëׯ#¡>~®®®VVVÇo4ßÞÞÞÞÞžîéX¦ øùù%$$¼ÖÛíØ±ƒîÁyáÂ…t§£Š,O †º²þ8p@~û÷ï'''—••½ÖjéF™··wÓ¿U–³¨9"‘ˆé*Zgëéúõë}úôAªõÞåѹ8;;wíÚµwïÞþþþOŸ>:thrr2!$((ÈÊʪé9²²²¦OŸîàà`ggçççGwÕ0vìØ+W®B.\heeõðáC¦|ÓE2×ÀlØ/¿üÒ¿ºËV+++++«¸¹¹õîÝ{×®]7nÜ2dH·nÝæÍ›G·#s/p–?Ž›»W¶¤Ð†ú˜ÅÄÄüòË/–––ååå±±±ÕÕÕ^^^»wï.**rww755¥`TUUM˜0!??ßÑÑÑÀÀ :::00022rذa¹¹¹¹¹¹ÎÎÎ;väóùL•F‹8Îøñã›®y£M›6¹»»KEóÃ?ØÚÚþý÷ßkÖ¬áóù={öÌËËûã?\\\<<ß××wܸq„©S§Ò£ìܹsÇÕÕU:¡x<Þo¿ý¶víÚëׯ߾}ÛÖÖöË/¿”9ÆDsÞ~ Me2÷Z$T öÏ?ÿÈœ÷î]zÂØØøàÁƒM 8880,7Õ¡CztõFvíÚÕ\•F‹š[³a é>¦ÿýwfzÞ¼yóæÍ£§;wî,s/ 5Àw"€„x‡wyoüí ÀûM¨á3ÂѼ€·ôöãÇ"†@y!¡ €„$@ËI(6zÀ÷@¡¿zéܾMÈO}ÖÝÙ%å•å•5¿þq²oÍ×§ÿ½édoɦ¨é+v´ok0sìgfFå•Õû£/Ÿ»z›râÇ… 7—”WB}ÕÖ ÷¿D×={åVwksžšZjÖ“7ÿ¨ÿIDATöŸª6à|Àë%‡M-ž6êðŸWO\Šëbaºò+ÿ“—☥æ&_¯Ù)‹9ljéôÑgcn†~·§s»6«fË)(ºû [Κ۵џ³~·DBMõû™Ëîcq>àõîò¬;˜R,Ö‰Kq„{³ã’_ãøøÅ "±DB¬:˜¨«r?{E"‘¤f=¹x#©/ùkŽþ'^,–H$’ǹ:tÅÉ€×N(-^Qi%󲨤BziYå³£ZšOK+˜NÑ ŠËt´yò×\Z^ÅL¼²0 ¡dåHE•žŽ&óRO %³XIE¥¾Ž‹Å¢_´éj‰8ìgïÂSW“®b¨§ó|‚ϤÀk$TJF¶DB<ú÷"„X[˜8t•Ý?ôýŒœºú†Qƒ?a±XÚµqëcûOÜ]BÈÃì|{+sBˆ®¶£]gé*>xêjŸ¿hÑ¢ÈÈHù+üꫯ¸\.Ó™§4uuõêêê[·nÕÕÕ™ššš˜˜à´hŠ>‡266¦'ÔÔÔ¤§ëêêè;²ºº:›gC'Ô××3waò+ÒLMM™‰üü|ù+400hn#ííí—-[6wîÜôôô¡C‡nݺÕÐÐçàãO¨Wæ—¾¾þ½{÷d6m^)''‡ ììl##£·YáŒ3f̘QTT4iÒ¤ðððM›6½Ù&@KºË“ï“O>ÑÕÕ]ºtiMM !$55õµ~´~ýú²²²òòòÕ«Wûùù½ñ ïÞ½{ãÆ ±X¬¥¥ÅãñègAFFÎ4@ëM(Š¢NŸ>œœÜ¦Mmmmÿ§OŸ*^}äÈ‘ŽŽŽ¦¦¦fffK–,yãVTTLž!„Èì§üÚµkµµµ_ý5‹Åêß¿ÿàÁƒéÊì ½ißç8‹+Eû‡úþûï¯\¹Ïf³›+>jÔ(MMMG‰o×®‡óÒ[Èì§744ĉh½m¨þù',,,**JOOON1>ŸoddDÇ!¤mÛ¶?‰DÒedöSndd$ýÝ3Ý\Wèú>ÇYh½ õäÉ??¿µk×ÚØØÔ>§Èˆ›ŽŽŽFFF , Ë_½z•"³Ÿr'''±X|êÔ)BHffæÉ“'é5Èì ½ißç8‹­7¡8ŸŸ¬.Å××÷Õ«¦¨èèèäädCCÃ;vBdöSÎår9²hÑ¢¾}ûNžXH½2žPðaBJ‘xBB€RÜî!¡ åÁwyðÞ½ãß”çææâ˜À»ò.Sþé§ŸVTTà˜ÀÛ›2eÊ+P.žC€òBB H($ ¡PH(øXàïò>KKËÖ¹ã¯ûûr$@˸V?‘‘‘£GÆ]|$P€„@B H(héNž<9räH;;»¾}û†……•••!¡@)U•äBˆÕبÆl±}Û²6ÞHUÿ¨«¯–ÅCýÝý’”¾^®ª&ª¥úrI¬»‡W„jÌ£CxïœÍªX·mÔtè-‘ÒÆ[ÕWþ;› š‰ãÿaÙzn׫Rõþžˆõ«ýJ½¯Ê3ûW¹ù^ß/ÔË=TͽC«ôÌ®ËM÷* î;.¼wîòÂS{¯j½1O’˜šîȃßí½û½=ÕYÕ½ƒŸ¥Ždàøï>”çÞ릟ªòR/{(O½×§ò0”ª®Yý¬»vZ7Ð “¾åœÐÞÕeO.äç &)yÿ5Pì{)ë×ȃiOUŸB[·ËŸóâ%Žz/7Lªj Ú`^äÖÏG +whï–,Í*•Ìb³¶$/ÚÛõT§7T?‰"V3pVåáý½”ýEþÊâ´—»²äNOV'€ðÞu?¨çv‹ye‰u«~èöW¼[‘Õ”ŸU9~·ÜöiUð};lz z•¨Æ€`ï¤dû£»Ó^Œ¾¸çX½î.è©ÎÕl @§‹Õö!²ÚÅÖj €ë¦YüBnécü¡Rþ@D]õL–¨öž4"©sZå½0S›¡ª¼Sêµ/|þ‰Ëfò§UÉ€Þ;wÓ?­ª·áìÖä'«Ü4§Uî°“FR5í—Uß•¯ù«‚T}_¶~³ŸJU§O]nìU-_¬òói\¬JH‚½s7äÁkûå½… ¦òæ¾½[süþ'ë™ý›_%pßlX²aËR £êð~YÊþErw³$7÷:·^/Oå@2Þ»§ËÞiÔòf€êý%½À\·:¼­} Èà}Õ?Œ€”÷§«Tè E¥Sª=ûÛþë®3¬¤Î÷Nè•TM»ø£E@¯ÕÉ®ð@~@Uë›QÔ Ÿ¡¤QfþH0]¸]©>|¯Ò–ª—R5¯à$ºÙŒ@)P͹¤`$"骃1­+©I£Âeޤê\H!ª¨"€ T@ª÷–­?P˜Ù¸y硪W&ñ’èvœ÷ ¨^Nb&Ї*p…`§ý\+ÅWlà*¯ØÀÄ\é@2!C“A¦1œ«€i×ÌZi%?äê`êu 3€©´Ø½z˜n¶ýJlû-0Ë+Siñ, %µÐÔ’­#˜QT½0ÉðÖέLÕî¿ÞþB®F†'C~༙8ª—¯ƒ1\ ù€ž=¨¥¤ÆdW.:pþµ&•]Óœíµ©<>ó»ýe­è->{\‘Õ/zIóCM*NÓ-ÿÛ*.lÉJJD†1Ù€ NG*µ/'äH7gýøäô¸RùG÷©RûÜx,Û‹_WåD¯¬|l|®-œ¥T ªß¯/üÙ8ª!¿pA‹å[«u.«Ý}•Ý'½¸)á¿åq«u$:[úÛt^ÐIº#ŸÌ+K¨è¶ÔŠ~{¾ŸžÊšIU³XÎõ¡_/Y3 Z‹•4 Àª4Íû È¥Ô|&õë‡ëæ*ÁZ÷ÌÄ_lþPìÜnL³´ãH †IŽ0“Û²}Úl…›Fu±Ñ8Õ#Íàïu3!T§;æÕh©髃êŠf²bîg!TWTýckÊo¬šýMJ"þ ¾$©.Ϊ*VùñY4¨nû¨®„SuEŸV¥ú€Ùë9ö^w¾[5WÕ ]ÕÄçy|r²½:”†Rµµ%;ߟŸTÒ,ß>5޳pT'K#?>›;‹RÙ:ÖñzQ*ë»é¾ðïbqý$ W@5^¹<˜|.¨NAœ|>0Û:PŽð) À§¦P¨"€ ª ó$^0p'½Ï=êk ÒY¨&'iyù] :•gRÚâBNÞr šœ¤U‡TEPEPEHUUU€TEPEPEHUU@R@@@€TEPE UT@HUU@R@@5ëª+Ó ?@5N‘’åP PÍà{ÎÕ˜¨:¹HÕ,¨æ=U/@ÕÕ¼£êf¹ÃYùªùçTsªª š]詪“:@5‡±ê\ˆª“ËPÕÌÆjoQ…œÔª?r—«ÎŨf¹Pý‘»\í«©“:@5w±êŒFÕÉ_¨‚jFcµ¿¤BNêÕè›f"d ¢BNêÕ8g2Š 9©Tcmò@E…œÔª?rë .ä¤Pý7$mÖ{uÁrR¨Nô·R7Fìl!'u€jîG.R$¨ÒEŠUºªt‘"A•.R$¨‚*¨ÒEŠUºH‘  ª J)Té"E‚*¨‚*]¤HP¥‹ ª ªt‘"A•.R$¨R$¨ÒEŠUºªt‘"A•.R$¨‚*¨ÒEŠUºH‘  ª J)Té"E‚*¨‚*]¤HP¥‹ ª ªt‘"A•.R$¨R$¨ÒEŠUºªt‘"A•.R$¨ÒEP¥‹ ªt‘"ATA•.R$¨ÒEŠUPUºH‘ J)T)Té"E‚*]¤HP¥HP¥‹ ªtTé"E‚*]¤HP¥‹ J)Té"E‚*¨‚*]¤HP¥‹ ª  ªt‘"A•.R$¨R$¨ÒEŠUºH‘ J‘ J)Té"¨ÒEŠUºH‘ JA•.R$¨ÒEŠUPUºH‘ J)TATé"E‚*]¤HP¥HP¥‹ ªt‘"A•"A•.R$¨ÒEP¥‹ ªt‘"A•.‚*]¤HP¥‹ ª  ªt‘"A•.R$¨‚*¨ÒEŠUºH‘ J‘ J)Té"E‚*E‚*]¤HP¥‹ J)Té"E‚*]UºH‘ J)TATé"E‚*]¤HPUP¥‹ ªt‘"A•"A•.R$¨ÒEŠUŠUºH‘ JA•.R$¨ÒEŠUºªsÙEIÙUP½SG¥ë+® :'¨¶1MÛp½ŠH+¨ÎªšÓô®  ªf}šAmà ª šòHí˜ÀØ`ÕÜ£šP£+¨æÕŒj‚TçU•™ªsjvBuœ€j¾QÍ©ãbTsj¶HUŽ€êÜ¢ª²5@u^QM8T¥aõ˜&UPÍ5ª _Lª:#1Ó«û7úõYR± ª¤jôÈ‹’ª‡Nœ$Ž« Jª&+ñðŽ« škTÝqì½\kSÙw,©îéͪX·íá+¥º¿!ÖÝw&<ÿÚpoY¤->°‡Þ?#¥·Ê€ÚUPÙÅqˆ¸bU_Ü‘{å_öoÈïÞ’å…§ö^Õz3t¥X¥gv]–o^3‹CW|`Kò¢ëNBçU :Ç©Ú>dW}¥•Ju?žçYéJÙÏ=Ïz6,\W}e‰u«~èÆOUªs‰ªéêSUº¤IÉöGˆãC‰k˜îlègþTuŒ:/îy'V¯'¸#ªÀÐTu NÇ*«úï\ÐXé˜ Už¸7ƒ ;4†À ýL–; PMFºÜÖÍI”J4°Ò;êë[O›þiU½øÁ«Fpõ © ªI €Ûùƒ»!^Û/ï-ô‰¬¿¥XמÛu¹éZj¨”7÷íýÛšéÉ‹8«@+mÒž.{gDË›CWJuoI¬»‡*Øð—áð`I¯Ô×°¼ÍâÜ@€áÏݯíÿ¸ÊíN&Ñ·²8WõmØûov׬'UA5¾dë‘UPgÈÔ@už€TUTAU€TUTAU€TÕT ÀT~+…ª š´8ÒJ~ªÉ ÀTPuIUPM^¦*ª¨"¨"  ª±`àB’'Rûk­Xyr®!k~¨IåÑ©á­öu½Xl5wk"××ÿñ–œo/Êõíïþʿ֤²‹€ê”À¼ìé¡Z©üÙ8’5 ÙzåcãsmáLóVô¾ÿÚú윜~Ùö–4k ǧ‹«M½²RûÒØ’#T§$ƒé=TåØÃkG¿ÉïÛ†<Ö¼Ég]q±à®Ùî£ì˜•ß¼”•UT§'½´j¦áóQ«µV1¨-VLªšï¯Ë§6€«f»¦áS̆µ"ªS€0­ÚU}¾<(vV5‹¿V¤²u¤EÖò>ý•5A@5ªIŒ6vMƒêb£qªG€jëüÓ“šX{+P]T/E°£.úHÕ^ðñmÞj{DzÞ'£SÕh¨ªè®Ú=­ÚõO«Ž†¢j"×?­:öO«VF¦*ªQS5Ö€Êâ§ÓöŪ-ÙùÖøü¤Ò¥±UÛýÚøû‘¦ô\_¬:.®4ǧ*ªÉ¤ªž-ÚÃîÛzç@ëxmA*ë»Aªn{ Šë溕¾°Ø¹02UP†ª{ñ4æCïVñ ¨¦Må >Ð>Òó ¨¦I†=®2…TE@õ’0ôyUTSx€‡A5ÃÀ§@5 b~á¤ØÎ~`Õø æú9b3¨¦Z¹#&UG\¿Jú‹‰€@u°Ë#à*¦ø‘wÎø=UÀÞ“Ê{V¤:¾‚ŒÛPElw¹êªÙ¡ªÆ)€´SÕ±¥îÎR")¨"^˜Ý³ÞÍRŒ‚(TÇ €Rכּ³£ .ªc@©_d¦ Õ ÜòLÀSy‡€êhHÁÔéFAî! :Zf?Ãz4Õ¹€™O°êDSPwHëN¤w ¨"Ùx¶T€ŒÌ°ª@6fXGTó#g[ÅâÖ™ùàj{VõÞ¹Ôƒ9Âs±Ç€(Ÿ U`,#ç‹•O£šª?«zÏ\êaTCs±GÿB+ª—€3/õŽª?«zÏ\êaTƒ¹Ø£ @÷ž€Kª‚ê¥À§°ÑNÕž¹Ô×úfR MÅAb}0T€ñ7VÛ¬¯Óÿu&¨–îä•WíÎÅ>Núî´" zyèL°^Dµ.õÐ\ì£QMr€*Ѐf µ_¦âc[‰'ærý%?æw ‘O«>›3©„çR_•óV뛄N«vÇŸV)×éýu.“_WU t±ê¸{±ÊŸkµ=—zMgè‘fNÕ‰pM× ' ÊUT Ê`€*ƒ‘Ôø?¿hXæYIEND®B`‚pfm-2.0.8/examples/0000755000175000017500000000000012110362244012266 5ustar wimwimpfm-2.0.8/examples/install_addressbook.sql0000644000175000017500000024715511472160234017060 0ustar wimwim-- install_addressbook.sql -- Version 1.5.0 -- The character encoding of this file is iso8859-1 (latin1). -- When imported via the 'Tools -> install pfm_* tables' menu, pfm -- converts it to UTF-8 before offering it to psql, which is always -- running with client_encoding 'UNICODE(=UTF-8)' when invoked by pfm. CREATE TABLE "ZIPcodes" ( town text NOT NULL, "ZIPcode" text NOT NULL ); CREATE TABLE "group" ( name text NOT NULL, description text ); CREATE TABLE memberlist ( person integer NOT NULL, "group" text NOT NULL ); CREATE TABLE person ( id serial NOT NULL, christian_name text, name text, street text, town text, "ZIPcode" text, country text, category text, description text ); SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('person', 'id'), 12, true); CREATE TABLE pfm_attribute ( attribute text NOT NULL, typeofattrib text, typeofget text, sqlselect text, nr integer, form text NOT NULL, valuelist text, "default" text ); CREATE TABLE pfm_form ( name text NOT NULL, tablename text, showform boolean DEFAULT true, "view" boolean DEFAULT false, sqlselect text, sqlfrom text, groupby text, help text, pkey text, sqlorderby text, sqllimit text ); CREATE TABLE pfm_link ( linkname text NOT NULL, sqlwhere text, orderby text, displayattrib text, fromform text NOT NULL, toform text ); CREATE TABLE pfm_report ( name text NOT NULL, description text, sqlselect text ); CREATE TABLE pfm_section ( report text NOT NULL, "level" integer NOT NULL, fieldlist text, layout text, summary text, CONSTRAINT level_min_1 CHECK (("level" >= 1)) ); CREATE TABLE pfm_value ( value text NOT NULL, description text, valuelist text NOT NULL ); CREATE TABLE pfm_value_list ( name text NOT NULL ); CREATE TABLE pfm_version ( seqnr serial NOT NULL, version text, date text, "comment" text ); INSERT INTO pfm_version (version, "date", comment) VALUES ('1.5.0', CURRENT_DATE, 'install_addressbook.sql'); COPY "ZIPcodes" (town, "ZIPcode") FROM stdin; Aaigem 9420 Aalbeke 8511 Aalst 9300 Aalst (Limb.) 3800 Aalter 9880 Aarschot 3200 Aarsele 8700 Aartrijke 8211 Aartselaar 2630 Abée 4557 Abolens 4280 Achel 3930 Achêne 5590 Achet 5362 Acosse 4219 Acoz 6280 Adegem 9991 Adinkerke 8660 Affligem 1790 Afsnee 9051 Agimont 5544 Aineffe 4317 Aische-en-Refail 5310 Aiseau 6250 Aiseau-Presles 6250 Aisemont 5070 Alken 3570 Alle 5550 Alleur 4432 Alsemberg 1652 Alveringem 8690 Amay 4540 Amberloup 6680 Amblève 4770 Ambly 6953 Ambresin 4219 Amel 4770 Amonines 6997 Amougies 7750 Ampsin 4540 Andenne 5300 Anderlecht 1070 Anderlues 6150 Andrimont 4821 Angleur 4031 Angre 7387 Angreau 7387 Anhée 5537 Anlier 6721 Anloy 6890 Annevoie-Rouillon 5537 Ans 4430 Anseremme 5500 Anseroeul 7750 Anthée 5520 Antheit 4520 Anthisnes 4160 Antoing 7640 Antwerpen 2018 Antwerpen 2020 Antwerpen 2030 Antwerpen 2040 Antwerpen 2050 Antwerpen 2060 Anvaing 7910 Anzegem 8570 Appels 9200 Appelterre-Eichem 9400 Arbre (Ht.) 7811 Arbre (Nam.) 5170 Arbrefontaine 4990 Arc-Ainières 7910 Archennes 1390 Arc-Wattripont 7910 Ardooie 8850 Arendonk 2370 Argenteau 4601 Arlon 6700 Arquennes 7181 Arsimont 5060 Arville 6870 As 3665 Aspelare 9404 Asper 9890 Asquillies 7040 Ass. Commiss. Communau. française 1007 Ass. Réun. Com. Communau. Commune 1005 Asse 1730 Assebroek 8310 Assenede 9960 Assenois 6860 Assent 3460 Assesse 5330 Astene 9800 Ath 7800 Athis 7387 Athus 6791 Attenhoven 3404 Attenrode 3384 Attert 6717 Attre 7941 Aubange 6790 Aubechies 7972 Aubel 4880 Aublain 5660 Auby-sur-Semois 6880 Auderghem 1160 Audregnies 7382 Aulnois 7040 Autelbas 6706 Autre-Eglise 1367 Autreppe 7387 Auvelais 5060 Ave-et-Auffe 5580 Avekapelle 8630 Avelgem 8580 Avennes 4260 Averbode 3271 Avernas-le-Bauduin 4280 Avin 4280 Awans 4340 Awenne 6870 Awirs 4400 Aye 6900 Ayeneux 4630 Aywaille 4920 B.S.D. (Belg. Strijdkr. Duitsland) 4090 Baaigem 9890 Baal 3128 Baardegem 9310 Baarle-Hertog 2387 Baasrode 9200 Bachte-Maria-Leerne 9800 Baelen (Lg.) 4837 Bagimont 5550 Baileux 6464 Bailièvre 6460 Baillamont 5555 Bailleul 7730 Baillonville 5377 Baisieux 7380 Baisy-Thy 1470 Balâtre 5190 Balegem 9860 Balen 2490 Bambrugge 9420 Bande 6951 Barbençon 6500 Barchon 4671 Baronville 5570 Barry 7534 Barvaux-Condroz 5370 Barvaux-sur-Ourthe 6940 Basècles 7971 Bas-Oha 4520 Basse-Bodeux 4983 Bassenge 4690 Bassevelde 9968 Bassilly 7830 Bastogne 6600 Bas-Warneton 7784 Batsheers 3870 Battice 4651 Battignies 7130 Baudour 7331 Bauffe 7870 Baugnies 7604 Baulers 1401 Bavegem 9520 Bavikhove 8531 Bazel 9150 Beaufays 4052 Beaumont 6500 Beauraing 5570 Beausaint 6980 Beauvechain 1320 Beauvoorde 8630 Beauwelz 6594 Beclers 7532 Beek 3960 Beerlegem 9630 Beernem 8730 Beerse 2340 Beersel 1650 Beerst 8600 Beert 1673 Beervelde 9080 Beerzel 2580 Beez 5000 Beffe 6987 Begijnendijk 3130 Beho 6672 Beigem 1852 Bekegem 8480 Bekkerzeel 1730 Bekkevoort 3460 Belgische Senaat 1009 Belgrade 5001 Bellaire 4610 Bellecourt 7170 Bellefontaine (Lux.) 6730 Bellefontaine (Nam.) 5555 Bellegem 8510 Bellem 9881 Bellevaux 6834 Bellevaux-Ligneuville 4960 Bellingen 1674 Beloeil 7970 Belsele (Sint-Niklaas) 9111 Ben-Ahin 4500 Bende 6941 Berbroek 3540 Berchem (Antwerpen) 2600 Berchem (O.-Vl.) 9690 Berchem-Sainte-Agathe 1082 Berendrecht 2040 Berg (Bt.) 1910 Berg (Limb.) 3700 Bergilers 4360 Beringen 3580 Berlaar 2590 Berlare 9290 Berlingen 3830 Berloz 4257 Berneau 4607 Bernissart 7320 Bersillies-l'Abbaye 6560 Bertem 3060 Bertogne 6687 Bertrée 4280 Bertrix 6880 Berzée 5651 Beselare 8980 Betekom 3130 Bettincourt 4300 Beuzet 5030 Bevel 2560 Bever 1547 Bevercé 4960 Bevere 9700 Beveren (Leie) 8791 Beveren (Roeselare) 8800 Beveren-aan-den-Ijzer 8691 Beveren-Waas 9120 Beverlo 3581 Beverst 3740 Beyne-Heusay 4610 Bienne-lez-Happart 6543 Bierbeek 3360 Biercée 6533 Bierges 1301 Bierghes 1430 Bierset 4460 Bierwart 5380 Biesme 5640 Biesmerée 5640 Biesme-sous-Thuin 6531 Bievene 1547 Bievre 5555 Biez 1390 Bihain 6690 Bikschote 8920 Bilstain 4831 Bilzen 3740 Binche 7130 Binderveld 3850 Binkom 3211 Bioul 5537 Bissegem 8501 Bizet 7783 Blaasveld 2830 Blaimont 5542 Blandain 7522 Blanden 3052 Blankenberge 8370 Blaregnies 7040 Blaton 7321 Blaugies 7370 Blégny 4670 Bléharies 7620 Blehen 4280 Bleid 6760 Bleret 4300 Blicquy 7903 Bocholt 3950 Boechout 2530 Boekhout 3890 Boekhoute 9961 Boëlhe 4250 Boezinge 8904 Bogaarden 1670 Bohan 5550 Boignée 5140 Boirs 4690 Bois-de-Lessines 7866 Bois-de-Villers 5170 Bois-d'Haine 7170 Bois-et-Borsu 4560 Bolinne 5310 Bolland 4653 Bomal (Bt.) 1367 Bomal-sur-Ourthe 6941 Bombaye 4607 Bommershoven (Haren) 3840 Boncelles 4100 Boneffe 5310 Bonheiden 2820 Boninne 5021 Bonlez 1325 Bonnert 6700 Bonneville 5300 Bon-Secours 7603 Bonsin 5377 Booischot 2221 Booitshoeke 8630 Boom 2850 Boorsem 3631 Boortmeerbeek 3190 Borchtlombeek 1761 Borgerhout (Antwerpen) 2140 Borgloon 3840 Borlez 4317 Borlo 3891 Borlon 6941 Bornem 2880 Bornival 1404 Borsbeek (Antw.) 2150 Borsbeke 9552 Bossière 5032 Bossuit 8583 Bossut-Gottechain 1390 Bost 3300 Bothey 5032 Bottelare 9820 Bouffioulx 6200 Bouge 5004 Bougnies 7040 Bouillon 6830 Bourlers 6464 Bourseigne-Neuve 5575 Bourseigne-Vieille 5575 Boussoit 7110 Boussu 7300 Boussu-en-Fagne 5660 Boussu-lez-Walcourt 6440 Bousval 1470 Boutersem 3370 Bouvignes-sur-Meuse 5500 Bouvignies 7803 Bouwel 2288 Bovekerke 8680 Bovelingen 3870 Bovenistier 4300 Bovesse 5081 Bovigny 6671 Bra 4990 Braffe 7604 Braibant 5590 Braine-l'Alleud 1420 Braine-le-Château 1440 Braine-le-Comte 7090 Braives 4260 Brakel 9660 Branchon 5310 Bras 6800 Brasmenil 7604 Brasschaat 2930 Bray 7130 Brecht 2960 Bredene 8450 Bree 3960 Breendonk 2870 Bressoux 4020 Brielen 8900 Broechem 2520 Broekom 3840 Brucargo 1931 Brugelette 7940 Brugge 8000 Brûly 5660 Brûly-de-Pesche 5660 Brunehaut 7620 Brussegem 1785 Brussel 1000 Brussel (Anderlecht) 1070 Brussel (Elsene) 1050 Brussel (Etterbeek) 1040 Brussel (Evere) 1140 Brussel (Ganshoren) 1083 Brussel (Haren) 1130 Brussel (Jette) 1090 Brussel (Koekelberg) 1081 Brussel (Laken) 1020 Brussel (Neder-Over-Heembeek) 1120 Brussel (Oudergem) 1160 Brussel (Schaarbeek) 1030 Brussel (Sint-Agatha-Berchem) 1082 Brussel (Sint-Gillis) 1060 Brussel (Sint-Jans-Molenbeek) 1080 Brussel (Sint-Joost-ten-Node) 1210 Brussel (Sint-Lambrechts-Woluwe) 1200 Brussel (Sint-Pieters-Woluwe) 1150 Brussel (Ukkel) 1180 Brussel (Vorst) 1190 Brussel (Watermaal-Bosvoorde) 1170 Brussel X-Luchthaven Remailing 1934 Brusselse Hoofdstedelijke Raad 1005 Brustem 3800 Bruxelles 1000 Bruxelles (Anderlecht) 1070 Bruxelles (Auderghem) 1160 Bruxelles (Berchem-Sainte-Agathe) 1082 Bruxelles (Etterbeek) 1040 Bruxelles (Evere) 1140 Bruxelles (Forest) 1190 Bruxelles (Ganshoren) 1083 Bruxelles (Haeren) 1130 Bruxelles (Ixelles) 1050 Bruxelles (Jette) 1090 Bruxelles (Koekelberg) 1081 Bruxelles (Laeken) 1020 Bruxelles (Molenbeek-Saint-Jean) 1080 Bruxelles (Neder-Over-Heembeek) 1120 Bruxelles (Saint-Gilles) 1060 Bruxelles (Saint-Josse-ten-Noode) 1210 Bruxelles (Schaerbeek) 1030 Bruxelles (Uccle) 1180 Bruxelles (Watermael-Boitsfort) 1170 Bruxelles (Woluwe-Saint-Lambert) 1200 Bruxelles (Woluwe-Saint-Pierre) 1150 Bruxelles X-Aeroport Remailing 1934 Bruyelle 7641 Brye 6222 Budingen 3440 Buggenhout 9255 Buissenal 7911 Buissonville 5580 Buizingen 1501 Buken 1910 Bullange 4760 Büllingen 4760 Bulskamp 8630 Bunsbeek 3380 Burcht 2070 Burdinne 4210 Bure 6927 Burg-Reuland 4790 Burst 9420 Bury 7602 Bütgenbach 4750 Butgenbach 4750 Buvingen 3891 Buvrinnes 7133 Buzenol 6743 Buzet 6230 Callenelle 7604 Calonne 7642 Cambron-Casteau 7940 Cambron-Saint-Vincent 7870 Cargovil 1804 Carlsbourg 6850 Carnières 7141 Casteau (Soignies) 7061 Castillon 5650 Celles (Ht.) 7760 Celles (Lg.) 4317 Celles (Nam.) 5561 Cérexhe-Heuseux 4632 Cerfontaine 5630 Céroux-Mousty 1341 Chaineux 4650 Chairière 5550 Chambre des Représentants 1008 Champion 5020 Champlon 6971 Chanly 6921 Chantemelle 6742 Chapelle-à-Oie 7903 Chapelle-à-Wattines 7903 Chapelle-lez-Herlaimont 7160 Chapon-Seraing 4537 Charleroi 6000 Charneux 4654 Chassepierre 6824 Chastre 1450 Chastrès 5650 Chastre-Villeroux-Blanmont 1450 Châtelet 6200 Châtelineau 6200 Châtillon 6747 Chaudfontaine 4050 Chaumont-Gistoux 1325 Chaussée-Notre-Dame-Louvignies 7063 Chênée 4032 Cherain 6673 Cheratte 4602 Chercq 7521 Chevetogne 5590 Chevron 4987 Chièvres 7950 Chimay 6460 Chiny 6810 Chokier 4400 Christelijke Sociale Organisaties 1031 Ciergnon 5560 Ciney 5590 Ciplet 4260 Ciply 7024 Cité Administrative de l'Etat 1010 Clabecq 1480 Clavier 4560 Clermont (Lg.) 4890 Clermont (Nam.) 5650 Clermont-sous-Huy 4480 Cognelée 5022 Colfontaine 7340 Comblain-au-Pont 4170 Comblain-Fairon 4180 Comblain-la-Tour 4180 Comines 7780 Comines-Warneton 7780 Conneux 5590 Conseil Region Bruxelles-Capitale 1005 Corbais 1435 Corbion 6838 Cordes 7910 Corenne 5620 Cornesse 4860 Cornimont 5555 Corroy-le-Château 5032 Corroy-le-Grand 1325 Corswarem 4257 Cortil-Noirmont 1450 Cortil-Wodon 5380 Couillet 6010 Courcelles 6180 Courrière 5336 Cour-sur-Heure 6120 Court-Saint-Etienne 1490 Couthuin 4218 Coutisse 5300 Couture-Saint-Germain 1380 Couvin 5660 Cras-Avernas 4280 Crehen 4280 Crisnée 4367 Croix-lez-Rouveroy 7120 Crombach 4784 Crupet 5332 Cuesmes 7033 Cugnon 6880 Cul-des-Sarts 5660 Custinne 5562 D.I.V. 1045 Dadizele 8890 Dailly 5660 Daknam 9160 Dalhem 4607 Damme 8340 Dampicourt 6767 Dampremy 6020 Darion 4253 Daussois 5630 Daussoulx 5020 Dave 5100 Daverdisse 6929 De Haan 8420 De Klinge 9170 De Moeren 8630 De Panne 8660 De Pinte 9840 Deerlijk 8540 Deftinge 9570 Deinze 9800 Denderbelle 9280 Denderhoutem 9450 Denderleeuw 9470 Dendermonde 9200 Denderwindeke 9400 Denée 5537 Dentergem 8720 Dergneau 7912 Dessel 2480 Desselgem 8792 Destelbergen 9070 Desteldonk 9042 Deurle 9831 Deurne (Antwerpen) 2100 Deurne (Bt.) 3290 Deux-Acren 7864 Dhuy 5310 Diegem 1831 Diepenbeek 3590 Diest 3290 Diets-Heur 3700 Dikkebus 8900 Dikkele 9630 Dikkelvenne 9890 Diksmuide 8600 Dilbeek 1700 Dilsen-Stokkem 3650 Dinant 5500 Dion 5570 Dion-Valmont 1325 Dison 4820 Dochamps 6960 Doel 9130 Dohan 6836 Doische 5680 Dolembreux 4140 Donceel 4357 Dongelberg 1370 Donk 3540 Donstiennes 6536 Dorinne 5530 Dormaal 3440 Dottenijs 7711 Dottignies 7711 Dour 7370 Dourbes 5670 Dranouter 8951 Dréhance 5500 Driekapellen 8600 Drieslinter 3350 Drogenbos 1620 Drongen 9031 Dudzele 8380 Duffel 2570 Duisburg 3080 Duras 3803 Durbuy 6940 Durnal 5530 Dworp 1653 E.U.-Commissie 1049 E.U.-Raad 1048 Eben-Emael 4690 Ebly 6860 Ecaussinnes 7190 Ecaussinnes-d'Enghien 7190 Ecaussinnes-Lalaing 7191 Edegem 2650 Edelare 9700 Edingen 7850 Eeklo 9900 Eernegem 8480 Egem 8740 Eggewaartskapelle 8630 Eghezée 5310 Ehein 4120 Eigenbilzen 3740 Eindhout 2430 Eine 9700 Eisden 3630 Eke 9810 Ekeren (Antwerpen) 2180 Eksaarde 9160 Eksel 3941 Elen 3650 Elene 9620 Elewijt 1982 Eliksem 3400 Elingen 1671 Ellemelle 4590 Ellezelles 7890 Ellignies-lez-Frasnes 7910 Ellignies-Sainte-Anne 7972 Ellikom 3670 Elouges 7370 Elsegem 9790 Elsenborn 4750 Elsene 1050 Elst 9660 Elverdinge 8906 Elversele 9140 Emblem 2520 Embourg 4053 Emelgem 8870 Emines 5080 Emptinne 5363 Ename 9700 Engelmanshoven 3800 Enghien 7850 Engis 4480 Enines 1350 Ensival 4800 Epinois 7134 Eppegem 1980 Eprave 5580 Erbaut 7050 Erbisoeul 7050 Ere 7500 Erembodegem (Aalst) 9320 Erezée 6997 Ermeton-sur-Biert 5644 Ernage 5030 Erneuville 6972 Ernonheid 4920 Erondegem 9420 Erpe 9420 Erpe-Mere 9420 Erpent 5101 Erpion 6441 Erps-Kwerps 3071 Erquelinnes 6560 Erquennes 7387 Ertvelde 9940 Erwetegem 9620 Escanaffles 7760 Esen 8600 Esneux 4130 Espierres 8587 Espierres-Helchin 8587 Esplechin 7502 Esquelmes 7743 Essen 2910 Essene 1790 Estaimbourg 7730 Estaimpuis 7730 Estinnes 7120 Estinnes-au-Mont 7120 Estinnes-au-Val 7120 Etalle 6740 Ethe 6760 Etikhove 9680 Ettelgem 8460 Etterbeek 1040 Eugies (Frameries) 7080 Eupen 4700 Europees Parlement 1047 Evegnée 4631 Evelette 5350 Everbeek 9660 Everberg 3078 Evere 1140 Evergem 9940 Evregnies 7730 Evrehailles 5530 Eynatten 4731 Ezemaal 3400 F.B.A. (Forces Belges en Allemagne) 4090 Fagnolle 5600 Faimes 4317 Falaën 5522 Falisolle 5060 Fallais 4260 Falmagne 5500 Falmignoul 5500 Familleureux 7181 Farciennes 6240 Faulx-les-Tombes 5340 Fauroeulx 7120 Fauvillers 6637 Faymonville 4950 Fays-les-Veneurs 6856 Fayt-le-Franc 7387 Fayt-lez-Manage 7170 Felenne 5570 Feluy 7181 Feneur 4607 Fernelmont 5380 Ferrières 4190 Feschaux 5570 Fexhe-le-Haut-Clocher 4347 Fexhe-Slins 4458 Filot 4181 Finnevaux 5560 Fize-Fontaine 4530 Fize-le-Marsal 4367 Flamierge 6686 Flavion 5620 Flawinne 5020 Flémalle 4400 Flémalle-Grande 4400 Flémalle-Haute 4400 Flénu 7012 Fléron 4620 Fleurus 6220 Flobecq 7880 Flône 4540 Florée 5334 Floreffe 5150 Florennes 5620 Florenville 6820 Floriffoux 5150 Flostoy 5370 Focant 5572 Folx-les-Caves 1350 Fontaine-l'Evêque 6140 Fontaine-Valmont 6567 Fontenelle 5650 Fontenoille 6820 Fontenoy 7643 Fooz 4340 Forchies-la-Marche 6141 Forest 1190 Forest (Ht.) 7910 Forêt 4870 Forge-Philippe 6596 Forges 6464 Forrières 6953 Forville 5380 Fosse (Lg.) 4980 Fosses-la-Ville 5070 Fouleng 7830 Fourbechies 6440 Fouron-le-Comte 3798 Fourons 3790 Fouron-Saint-Martin 3790 Fouron-Saint-Pierre 3792 Foy-Notre-Dame 5504 Fraipont 4870 Fraire 5650 Fraiture 4557 Frameries 7080 Framont 6853 Franchimont 5600 Francorchamps 4970 Franc-Waret 5380 Franière 5150 Frasnes (Nam.) 5660 Frasnes-lez-Anvaing 7910 Frasnes-lez-Buissenal 7911 Frasnes-lez-Gosselies 6210 Freloux 4347 Freux 6800 Froidchapelle 6440 Froidfontaine 5576 Froidmont 7504 Fronville 6990 Froyennes 7503 Fumal 4260 Furfooz 5500 Furnaux 5641 Gaasbeek 1750 Gages 7943 Gallaix 7906 Galmaarden 1570 Ganshoren 1083 Gaurain-Ramecroix (Tournai) 7530 Gavere 9890 Gedinne 5575 Geel 2440 Geer 4250 Geest-Gérompont-Petit-Rosière 1367 Geetbets 3450 Gelbressée 5024 Gelinden 3800 Gellik 3620 Gelrode 3200 Geluveld 8980 Geluwe 8940 Gembes 6929 Gembloux 5030 Gemmenich 4851 Genappe 1470 Genk 3600 Genly 7040 Genoelselderen 3770 Gent 9000 Gentbrugge 9050 Gentinnes 1450 Genval 1332 Geraardsbergen 9500 Gerdingen 3960 Gerin 5524 Gérompont 1367 Gérouville 6769 Gerpinnes 6280 Gestel 2590 Gesves 5340 Ghislenghien 7822 Ghlin 7011 Ghoy 7863 Gibecq 7823 Gierle 2275 Gijverinkhove 8691 Gijzegem 9308 Gijzelbrechtegem 8570 Gijzenzele 9860 Gilly (Charleroi) 6060 Gimnée 5680 Gingelom 3890 Gistel 8470 Gits 8830 Givry 7041 Glabais 1473 Glabbeek-Zuurbemde 3380 Glain 4000 Gleixhe 4400 Glimes 1315 Glons 4690 Gochenée 5680 Godarville 7160 Godinne 5530 Godveerdegem 9620 Goé 4834 Goeferdinge 9500 Goegnies-Chaussée 7040 Goesnes 5353 Goetsenhoven 3300 Gomzé-Andoumont 4140 Gondregnies 7830 Gonrieux 5660 Gontrode 9090 Gooik 1755 Gorsem 3803 Gors-Opleeuw 3840 Gosselies 6041 Gotem 3840 Gottem 9800 Gottignies 7070 Gougnies 6280 Gourdinne 5651 Goutroux 6030 Gouvy 6670 Gouy-lez-Piéton 6181 Gozée 6534 Grâce-Berleur 4460 Grâce-Hollogne 4460 Graide 5555 Grammene 9800 Grand-Axhe 4300 Grandglise 7973 Grand-Hallet 4280 Grand-Halleux 6698 Grandhan 6940 Grand-Leez 5031 Grand-Manil 5030 Grandmenil 6960 Grandmetz 7900 Grand-Rechain 4650 Grand-Reng 6560 Grandrieu 6470 Grand-Rosière-Hottomont 1367 Grandville 4360 Grandvoir 6840 Grapfontaine 6840 Graty 7830 Graux 5640 Grazen 3450 Grembergen 9200 Grez-Doiceau 1390 Grimbergen 1850 Grimminge 9506 Grivegnée 4030 Grobbendonk 2280 Groot-Bijgaarden 1702 Groot-Gelmen 3800 Groot-Loon 3840 Grosage 7950 Gros-Fays 5555 Grote-Brogel 3990 Grotenberge 9620 Grote-Spouwen 3740 Gruitrode 3670 Grune 6952 Grupont 6927 Guignies 7620 Guigoven 3723 Guirsch 6704 Gullegem 8560 Gutschoven 3870 Haacht 3150 Haaltert 9450 Haasdonk 9120 Haasrode 3053 Habay 6720 Habay-la-Neuve 6720 Habay-la-Vieille 6723 Habergy 6782 Haccourt 4684 Hachy 6720 Hacquegnies 7911 Haren (Bruxelles) 1130 Haillot 5351 Haine-Saint-Paul 7100 Haine-Saint-Pierre 7100 Hainin 7350 Hakendover 3300 Halanzy 6792 Halen 3545 Hallaar 2220 Halle 1500 Halle (Kempen) 2980 Halle-Booienhoven 3440 Halleux 6986 Halma 6922 Halmaal 3800 Haltinne 5340 Ham 3945 Hamipré 6840 Hamme (Bt.) 1785 Hamme (O.-Vl.) 9220 Hamme-Mille 1320 Hamoir 4180 Hamois 5360 Hamont 3930 Hamont-Achel 3930 Hampteau 6990 Ham-sur-Heure 6120 Ham-sur-Heure-Nalinnes 6120 Ham-sur-Sambre 5190 Handzame 8610 Haneffe 4357 Hannêche 4210 Hannut 4280 Hanret 5310 Hansbeke 9850 Han-sur-Lesse 5580 Hantes-Wihéries 6560 Hanzinelle 5621 Hanzinne 5621 Harchies 7321 Harelbeke 8530 Haren (Borgloon) 3840 Haren (Brussel) 1130 Haren (Tongeren) 3700 Hargimont 6900 Harmignies 7022 Harnoncourt 6767 Harre 6960 Harsin 6950 Harveng 7022 Harzé 4920 Hasselt 3500 Hastière 5540 Hastière-Lavaux 5540 Hastière-par-Delà 5541 Hatrival 6870 Haulchin 7120 Hauset 4730 Haut-Fays 6929 Haut-Ittre 1461 Haut-le-Wastia 5537 Hautrage 7334 Havay 7041 Havelange 5370 Haversin 5590 Havinnes 7531 Havré 7021 Hechtel 3940 Hechtel-Eksel 3940 Heer 5543 Heers 3870 Hees 3740 Heestert 8551 Heffen 2801 Heikruis 1670 Heindonk 2830 Heinsch 6700 Heist-aan-Zee 8301 Heist-op-den-Berg 2220 Hekelgem 1790 Heks 3870 Helchin 8587 Helchteren 3530 Heldergem 9450 Hélécine 1357 Helen-Bos 3440 Helkijn 8587 Hellebecq 7830 Hemelveerdegem 9571 Hemiksem 2620 Hemptinne (Fernelmont) 5380 Hemptinne-lez-Florennes 5620 Hendrieken 3840 Henis 3700 Hennuyères 7090 Henri-Chapelle 4841 Henripont 7090 Hensies 7350 Heppen 3971 Heppenbach 4771 Heppignies 6220 Herbeumont 6887 Herchies 7050 Herderen 3770 Herdersem 9310 Herent 3020 Herentals 2200 Herenthout 2270 Herfelingen 1540 Hergenrath 4728 Hérinnes-lez-Pecq 7742 Herk-de-Stad 3540 Hermalle-sous-Argenteau 4681 Hermalle-sous-Huy 4480 Hermée 4680 Hermeton-sur-Meuse 5540 Herne 1540 Héron 4217 Herquegies 7911 Herseaux 7712 Herselt 2230 Herstal 4040 Herstappe 3717 Hertain 7522 Herten 3831 Hertsberge 8020 Herve 4650 Herzele 9550 Heule 8501 Heure (Nam.) 5377 Heure-le-Romain 4682 Heurne 9700 Heusden (Limb.) 3550 Heusden (O.-Vl.) 9070 Heusden-Zolder 3550 Heusy 4802 Heuvelland 8950 Hever 3191 Heverlee 3001 Hévillers 1435 Heyd 6941 Hillegem 9550 Hingene 2880 Hingeon 5380 Hives 6984 Hoboken (Antwerpen) 2660 Hodeige 4351 Hodister 6987 Hody 4162 Hoegaarden 3320 Hoeilaart 1560 Hoeke 8340 Hoelbeek 3746 Hoeleden 3471 Hoepertingen 3840 Hoeselt 3730 Hoevenen 2940 Hofstade (Bt.) 1981 Hofstade (O.-Vl.) 9308 Hogne 5377 Hognoul 4342 Hollain 7620 Hollange 6637 Hollebeke 8902 Hollogne-aux-Pierres 4460 Hollogne-sur-Geer 4250 Holsbeek 3220 Hombeek 2811 Hombourg 4852 Hompré 6640 Hondelange 6780 Honnay 5570 Honnelles 7387 Hooglede 8830 Hoogstade 8690 Hoogstraten 2320 Horebeke 9667 Horion-Hozémont 4460 Hornu 7301 Horpmaal 3870 Horrues 7060 Hotton 6990 Houdemont 6724 Houdeng-Aimeries 7110 Houdeng-Goegnies (La Louvière) 7110 Houdremont 5575 Houffalize 6660 Hour 5563 Housse 4671 Houtaing 7812 Houtain-le-Val 1476 Houtain-Saint-Siméon 4682 Houtave 8377 Houtem (W.-Vl.) 8630 Houthalen 3530 Houthalen-Helchteren 3530 Houthem (Comines) 7781 Houthulst 8650 Houtvenne 2235 Houwaart 3390 Houx 5530 Houyet 5560 Hove 2540 Hoves (Ht.) 7830 Howardries 7624 Huccorgne 4520 Huise 9750 Huissignies 7950 Huizingen 1654 Huldenberg 3040 Hulshout 2235 Hulsonniaux 5560 Hulste 8531 Humain 6900 Humbeek 1851 Hundelgem 9630 Huppaye 1367 Huy 4500 Hyon 7022 Ichtegem 8480 Iddergem 9472 Idegem 9506 Ieper 8900 Impe 9340 Incourt 1315 Ingelmunster 8770 Ingooigem 8570 International Press Center 1041 Irchonwelz 7801 Isières 7822 Isnes 5032 Itegem 2222 Itterbeek 1701 Ittre 1460 Ivoz-Ramet 4400 Ixelles 1050 Izegem 8870 Izel 6810 Izenberge 8691 Izier 6941 Jabbeke 8490 Jalhay 4845 Jallet 5354 Jamagne 5600 Jambes (Namur) 5100 Jamiolle 5600 Jamioulx 6120 Jamoigne 6810 Jandrain-Jandrenouille 1350 Jauche 1350 Jauchelette 1370 Javingue 5570 Jehay 4540 Jehonville 6880 Jemappes 7012 Jemelle 5580 Jemeppe-sur-Meuse 4101 Jemeppe-sur-Sambre 5190 Jeneffe (Lg.) 4357 Jeneffe (Nam.) 5370 Jesseren (Kolmont) 3840 Jette 1090 Jeuk 3890 Jodoigne 1370 Jodoigne-Souveraine 1370 Jollain-Merlin 7620 Joncret 6280 Julémont 4650 Jumet (Charleroi) 6040 Jupille-sur-Meuse 4020 Juprelle 4450 Jurbise 7050 Juseret 6642 Kaaskerke 8600 Kachtem 8870 Kaggevinne 3293 Kain 7540 Kalken 9270 Kallo (Beveren-Waas) 9120 Kallo (Kieldrecht) 9130 Kalmthout 2920 Kamer van Volksvertegenwoordigers 1008 Kampenhout 1910 Kanegem 8700 Kanne 3770 Kapellen (Antw.) 2950 Kapellen (Bt.) 3381 Kapelle-op-den-Bos 1880 Kaprijke 9970 Kaster 8572 Kasterlee 2460 Kaulille 3950 Keerbergen 3140 Keiem 8600 Kelmis 4720 Kemexhe 4367 Kemmel 8956 Kemzeke 9190 Kerkhove 8581 Kerkom 3370 Kerkom-bij-Sint-Truiden 3800 Kerksken 9451 Kermt (Hasselt) 3510 Kerniel 3840 Kersbeek-Miskom 3472 Kessel 2560 Kessel-Lo (Leuven) 3010 Kessenich 3640 Kester 1755 Kettenis 4701 Keumiée 5060 Kieldrecht (Beveren) 9130 Kinrooi 3640 Kleine-Brogel 3990 Kleine-Spouwen 3740 Klein-Gelmen 3870 Klemskerke 8420 Klerken 8650 Kluisbergen 9690 Kluizen 9940 Knesselare 9910 Knokke 8300 Knokke-Heist 8300 Kobbegem 1730 Koekelare 8680 Koekelberg 1081 Koersel 3582 Koksijde 8670 Kolmont (Borgloon) 3840 Kolmont (Tongeren) 3700 Komen 7780 Komen-Waasten 7780 Koningshooikt 2500 Koninksem 3700 Kontich 2550 Kooigem 8510 Koolkerke 8000 Koolskamp 8851 Korbeek-Dijle 3060 Korbeek-Lo 3360 Kortemark 8610 Kortenaken 3470 Kortenberg 3070 Kortessem 3720 Kortijs 3890 Kortrijk 8500 Kortrijk-Dutsel 3220 Kozen 3850 Kraainem 1950 Krombeke 8972 Kruibeke 9150 Kruishoutem 9770 Kumtich 3300 Kuringen 3511 Kuttekoven 3840 Kuurne 8520 Kwaadmechelen 3945 Kwaremont 9690 La Bouverie 7080 La Bruyère 5080 La Calamine 4720 La Glanerie 7611 La Gleize 4987 La Hestre 7170 La Hulpe 1310 La Louvière 7100 La Reid 4910 La Roche-en-Ardenne 6980 Laakdal 2430 Laar 3400 Laarne 9270 Labuissière 6567 Lacuisine 6821 Ladeuze 7950 Laeken (Bruxelles) 1020 Laforêt 5550 Lahamaide 7890 Laken (Brussel) 1020 Lamain 7522 Lambermont 4800 Lambusart 6220 Lamine 4350 Lamontzée 4210 Lamorteau 6767 Lampernisse 8600 Lanaken 3620 Lanaye 4600 Landegem 9850 Landelies 6111 Landen 3400 Landenne 5300 Landskouter 9860 Laneffe 5651 Langdorp 3201 Langemark 8920 Langemark-Poelkapelle 8920 Lanklaar 3650 Lanquesaint 7800 Lantin 4450 Lantremange 4300 Laplaigne 7622 Lapscheure 8340 Lasne 1380 Lasne-Chapelle-Saint-Lambert 1380 Lathuy 1370 Latinne 4261 Latour 6761 Lauw 3700 Lauwe 8930 Lavacherie 6681 Lavaux-Sainte-Anne 5580 Lavoir 4217 Le Mesnil 5670 Le Roeulx 7070 Le Roux 5070 Lebbeke 9280 l'Ecluse 1320 Lede 9340 Ledeberg (Gent) 9050 Ledegem 8880 Leefdaal 3061 Leerbeek 1755 Leernes 6142 Leers-et-Fosteau 6530 Leers-Nord 7730 Leest 2811 Leeuwergem 9620 Leffinge 8432 Léglise 6860 Leignon 5590 Leisele 8691 Leke 8600 Lembeek 1502 Lembeke 9971 Lemberge 9820 Lendelede 8860 Lennik 1750 Lens 7870 Lens-Saint-Remy 4280 Lens-Saint-Servais 4250 Lens-sur-Geer 4360 Leopoldsburg 3970 Les Avins 4560 Les Bons Villers 6210 Les Bulles 6811 Les Hayons 6830 Les Waleffes 4317 l'Escaillère 6464 Lesdain 7621 Lessines 7860 Lessive 5580 Lesterny 6953 Lesve 5170 Lettelingen 7850 Letterhoutem 9521 Leugnies 6500 Leupegem 9700 Leut 3630 Leuven 3000 Leuze (Nam.) 5310 Leuze-en-Hainaut 7900 Leval-Chaudeville 6500 Leval-Trahegnies 7134 Liberchies 6238 Libin 6890 Libramont-Chevigny 6800 Lichtaart 2460 Lichtervelde 8810 Liedekerke 1770 Lieferinge 9400 Liège 4000 Liège 4020 Liège 4030 Lier 2500 Lierde 9570 Lierneux 4990 Liernu 5310 Liers 4042 Liezele 2870 Ligne 7812 Ligney 4254 Ligny 5140 Lille 2275 Lillo 2040 Lillois-Witterzée 1428 Limal 1300 Limbourg 4830 Limelette 1342 Limerlé 6670 Limont 4357 Lincent 4287 Linden 3210 Linkebeek 1630 Linkhout 3560 Linsmeau 1357 Lint 2547 Linter 3350 Lippelo 2890 Lisogne 5501 Lissewege 8380 Lives-sur-Meuse 5101 Lixhe 4600 Lo 8647 Lobbes 6540 Lochristi 9080 Lodelinsart 6042 Loenhout 2990 Loker 8958 Lokeren 9160 Loksbergen 3545 Lombardsijde 8434 Lombise 7870 Lommel 3920 Lommersweiler 4783 Lompret 6463 Lomprez 6924 Loncin 4431 Londerzeel 1840 Longchamps (Lux.) 6688 Longchamps (Nam.) 5310 Longlier 6840 Longueville 1325 Longvilly 6600 Lontzen 4710 Lonzée 5030 Loonbeek 3040 Loppem 8210 Lorcé 4987 Lo-Reninge 8647 Lot 1651 Lotenhulle 9880 Louette-Saint-Denis 5575 Louette-Saint-Pierre 5575 Loupoigne 1471 Louvain-la-Neuve 1348 Louveigné 4141 Lovendegem 9920 Lovenjoel 3360 Loverval 6280 Loyers 5101 Lubbeek 3210 Luingne 7700 Lummen 3560 Lustin 5170 Luttre 6238 Maarkedal 9680 Maarke-Kerkem 9680 Maaseik 3680 Maasmechelen 3630 Mabompré 6663 Machelen (Bt.) 1830 Machelen (O.-Vl.) 9870 Macon 6591 Macquenoise 6593 Maffe 5374 Maffle 7810 Magnée 4623 Maillen 5330 Mainvault 7812 Maisières 7020 Maissin 6852 Maizeret 5300 Mal 3700 Maldegem 9990 Malderen 1840 Malempré 6960 Malèves-Sainte-Marie-Wastines 1360 Malle 2390 Malmedy 4960 Malonne 5020 Malvoisin 5575 Manage 7170 Manderfeld 4760 Manhay 6960 Mannekensvere 8433 Maransart 1380 Marbais (Bt.) 1495 Marbaix (Ht.) 6120 Marbehan 6724 Marche-en-Famenne 6900 Marche-les-Dames 5024 Marche-lez-Ecaussinnes 7190 Marchienne-au-Pont 6030 Marchin 4570 Marchipont 7387 Marchovelette 5380 Marcinelle 6001 Marcourt 6987 Marcq 7850 Marenne 6990 Mariakerke (Gent) 9030 Mariekerke (Bornem) 2880 Mariembourg 5660 Marilles 1350 Mark 7850 Marke (Kortrijk) 8510 Markegem 8720 Marneffe 4210 Marquain 7522 Martelange 6630 Martenslinde 3742 Martouzin-Neuville 5573 Masbourg 6953 Masnuy-Saint-Jean (Jurbise) 7050 Masnuy-Saint-Pierre 7050 Massemen 9230 Massenhoven 2240 Matagne-la-Grande 5680 Matagne-la-Petite 5680 Mater 9700 Maubray 7640 Maulde 7534 Maurage 7110 Mazée 5670 Mazenzele 1745 Mazy 5032 Méan 5372 Mechelen 2800 Mechelen-aan-de-Maas 3630 Mechelen-Bovelingen 3870 Meeffe 4219 Meensel-Kiezegem 3391 Meer 2321 Meerbeek 3078 Meerbeke 9402 Meerdonk 9170 Meerhout 2450 Meerle 2328 Meeswijk 3630 Meetkerke 8377 Meeuwen 3670 Meeuwen-Gruitrode 3670 Mehaigne 5310 Meigem 9800 Meilegem 9630 Meise 1860 Meix-Devant-Virton 6769 Meix-le-Tige 6747 Melden 9700 Meldert (Bt.) 3320 Meldert (Limb.) 3560 Meldert (O.-Vl.) 9310 Melen 4633 Mélin 1370 Melkwezer 3350 Melle 9090 Mellery 1495 Melles 7540 Mellet 6211 Mellier 6860 Melsbroek 1820 Melsele 9120 Melsen 9820 Membach 4837 Membre 5550 Membruggen 3770 Mendonk 9042 Menen 8930 Merbes-le-Château 6567 Merbes-Sainte-Marie 6567 Merchtem 1785 Merdorp 4280 Mere 9420 Merelbeke 9820 Merendree 9850 Merkem 8650 Merksem (Antwerpen) 2170 Merksplas 2330 Merlemont 5600 Mesen 8957 Meslin-l'Evêque 7822 Mesnil-Eglise 5560 Mesnil-Saint-Blaise 5560 Mespelare 9200 Messancy 6780 Messelbroek 3272 Messines 8957 Mesvin 7022 Mettekoven 3870 Mettet 5640 Meulebeke 8760 Meux 5081 Mévergnies-lez-Lens 7942 Meyerode 4770 Michelbeke 9660 Micheroux 4630 Middelburg 9992 Middelkerke 8430 Miécret 5376 Mielen-Boven-Aalst 3891 Mignault 7070 Millen 3770 Milmort 4041 Minderhout 2322 Mirwart 6870 Modave 4577 Moelingen 3790 Moen 8552 Moerbeke 9500 Moerbeke-Waas 9180 Moere 8470 Moerkerke 8340 Moerzeke 9220 Moeskroen 7700 Moha 4520 Mohiville 5361 Moignelée 5060 Moircy 6800 Mol 2400 Molenbaix 7760 Molenbeek-Saint-Jean 1080 Molenbeek-Wersbeek 3461 Molenbeersel 3640 Molenstede 3294 Mollem 1730 Momalle 4350 Momignies 6590 Monceau-en-Ardenne 5555 Monceau-Imbrechies 6592 Monceau-sur-Sambre 6031 Mons 7000 Mons-lez-Liège 4400 Monstreux 1400 Mont (Lux.) 6661 Mont (Nam.) 5530 Montbliart 6470 Mont-de-l'Enclus 7750 Montegnée 4420 Montenaken 3890 Mont-Gauthier 5580 Montignies-lez-Lens 7870 Montignies-Saint-Christophe 6560 Montignies-sur-Roc 7387 Montignies-sur-Sambre 6061 Montigny-le-Tilleul 6110 Montleban 6674 Montroeul-au-Bois 7911 Montroeul-sur-Haine 7350 Mont-Saint-André 1367 Mont-Saint-Aubert 7542 Mont-Sainte-Aldegonde 7141 Mont-Sainte-Geneviève 6540 Mont-Saint-Guibert 1435 Mont-sur-Marchienne 6032 Montzen 4850 Moorsel 9310 Moorsele 8560 Moorslede 8890 Moortsele 9860 Mopertingen 3740 Moregem 9790 Moresnet 4850 Morhet 6640 Morialmé 5621 Morkhoven 2200 Morlanwelz 7140 Morlanwelz-Mariemont 7140 Mormont 6997 Mornimont 5190 Mortier 4670 Mortroux 4607 Mortsel 2640 Morville 5620 Mouland 3790 Moulbaix 7812 Mourcourt 7543 Mouscron 7700 Moustier (Ht.) 7911 Moustier-sur-Sambre 5190 Mouzaive 5550 Moxhe 4280 Mozet 5340 Muizen (Limb.) 3891 Muizen (Mechelen) 2812 Mullem 9700 Munkzwalm 9630 Muno 6820 Munsterbilzen 3740 Munte 9820 Musson 6750 Mussy-la-Ville 6750 My 4190 Naast 7062 Nadrin 6660 Nafraiture 5550 Nalinnes 6120 Namêche 5300 Namur 5000 Nandrin 4550 Naninne 5100 Naomé 5555 Nassogne 6950 Natoye 5360 NAVO - NATO 1110 Nazareth 9810 Néchin 7730 Nederboelare 9500 Nederbrakel 9660 Nederename 9700 Nederhasselt 9400 Nederokkerzeel 1910 Neder-Over-Heembeek (Bru.) 1120 Nederzwalm-Hermelgem 9636 Neerglabbeek 3670 Neerharen 3620 Neerhespen 3350 Neerheylissem 1357 Neerijse 3040 Neerlanden 3404 Neerlinter 3350 Neeroeteren 3680 Neerpelt 3910 Neerrepen 3700 Neervelp 3370 Neerwaasten 7784 Neerwinden 3400 Neigem 9403 Nerem 3700 Nessonvaux 4870 Nethen 1390 Nettinne 5377 Neufchâteau 6840 Neufchâteau (Lg.) 4608 Neufmaison 7332 Neufvilles 7063 Neu-Moresnet 4721 Neupré 4120 Neuville (Philippeville) 5600 Neuville-en-Condroz 4121 Nevele 9850 Niel 2845 Niel-bij-As 3668 Niel-bij-Sint-Truiden 3890 Nieuwenhove 9506 Nieuwenrode 1880 Nieuwerkerken (Aalst) 9320 Nieuwerkerken (Limb.) 3850 Nieuwkapelle 8600 Nieuwkerke 8950 Nieuwkerken-Waas 9100 Nieuwmunster 8377 Nieuwpoort 8620 Nieuwrode 3221 Nijlen 2560 Nil-Saint-Vincent-Saint-Martin 1457 Nimy 7020 Ninove 9400 Nismes 5670 Nivelles 1400 Niverlée 5680 Nives 6640 Nobressart 6717 Nodebais 1320 Noduwez 1350 Noirchain 7080 Noirefontaine 6831 Noiseux 5377 Nokere 9771 Nollevaux 6851 Noorderwijk 2200 Noordschote 8647 Nossegem 1930 Nothomb 6717 Nouvelles 7022 Noville (Lg.) 4347 Noville (Lux.) 6600 Noville-les-Bois 5380 Noville-sur-Méhaigne 5310 Nukerke 9681 Obaix 6230 Obigies 7743 Obourg 7034 Ochamps 6890 Ocquier 4560 Odeigne 6960 Odeur 4367 Oedelem 8730 Oekene 8800 Oelegem 2520 Oeren 8690 Oeselgem 8720 Oetingen 1755 Oeudeghien 7911 Oevel 2260 Offagne 6850 Ogy 7862 Ohain 1380 Ohey 5350 Oignies-en-Thiérache 5670 Oisquercq 1480 Oizy 5555 Okegem 9400 Olen 2250 Oleye 4300 Ollignies 7866 Olloy-sur-Viroin 5670 Olmen 2491 Olne 4877 Olsene 9870 Omal 4252 Ombret 4540 Omezée 5600 On 6900 Onhaye 5520 Onkerzele 9500 Onnezies 7387 Onoz 5190 Onze-Lieve-Vrouw-Lombeek 1760 Onze-Lieve-Vrouw-Waver 2861 Ooigem 8710 Ooike (Wortegem-Petegem) 9790 Oombergen (Zottegem) 9620 Oorbeek 3300 Oordegem 9340 Oostakker 9041 Oostduinkerke 8670 Oosteeklo 9968 Oostende 8400 Oosterzele 9860 Oostham 3945 Oostkamp 8020 Oostkerke (Damme) 8340 Oostkerke (Diksmuide) 8600 Oostmalle 2390 Oostnieuwkerke 8840 Oostrozebeke 8780 Oostvleteren 8640 Oostwinkel 9931 Opbrakel 9660 Opdorp 9255 Opglabbeek 3660 Opgrimbie 3630 Ophain-Bois-Seigneur-Isaac 1421 Ophasselt 9500 Opheers 3870 Opheylissem 1357 Ophoven 3640 Opitter 3960 Oplinter 3300 Opoeteren 3680 Opont 6852 Opprebais 1315 Oppuurs 2890 Opvelp 3360 Opwijk 1745 Orbais 1360 Orchimont 5550 Orcq 7501 Ordingen 3800 Oret 5640 Oreye 4360 Organisations Sociales Chrétiennes 1031 Orgeo 6880 Ormeignies 7802 Orp-Jauche 1350 Orp-le-Grand 1350 Orroir 7750 Orsmaal-Gussenhoven 3350 Ortho 6983 Ostiches 7804 OTAN - NATO 1110 Otegem 8553 Oteppe 4210 Othée 4340 Otrange 4360 Ottenburg 3040 Ottergem 9420 Ottignies 1340 Ottignies-Louvain-la-Neuve 1340 Oudegem 9200 Oudekapelle 8600 Oudenaarde 9700 Oudenaken 1600 Oudenburg 8460 Oudergem 1160 Oud-Heverlee 3050 Oud-Turnhout 2360 Ouffet 4590 Ougrée 4102 Oupeye 4680 Outer 9406 Outgaarden 3321 Outrelouxhe 4577 Outrijve 8582 Ouwegem 9750 Overboelare 9500 Overhespen 3350 Overijse 3090 Overmere 9290 Overpelt 3900 Overrepen (Kolmont) 3700 Overwinden 3400 Paal 3583 Paifve 4452 Pailhe 4560 Paliseul 6850 Pamel 1760 Papignies 7861 Parike 9661 Parlement de la Communauté française 1012 Parlement Européen 1047 Passendale 8980 Patignies 5575 Paturages 7340 Paulatem 9630 Pecq 7740 Peer 3990 Peissant 7120 Pellaines 4287 Pellenberg 3212 Pepingen 1670 Pepinster 4860 Perk 1820 Péronnes-lez-Antoing 7640 Péronnes-lez-Binche 7134 Péruwelz 7600 Pervijze 8600 Perwez 1360 Perwez-Haillot 5352 Pesche 5660 Pessoux 5590 Petegem-aan-de-Leie 9800 Petegem-aan-de-Schelde 9790 Petigny 5660 Petite-Chapelle 5660 Petit-Enghien 7850 Petit-Fays 5555 Petit-Hallet 4280 Petit-Rechain 4800 Petit-Roeulx-lez-Braine 7090 Petit-Roeulx-lez-Nivelles 7181 Petit-Thier 6692 Peutie 1800 Philippeville 5600 Piéton 7160 Piétrain 1370 Piètrebais 1315 Pipaix 7904 Piringen (Haren) 3700 Pironchamps 6240 Pittem 8740 Plainevaux 4122 Plancenoit 1380 Ploegsteert 7782 Plombières 4850 Poederlee 2275 Poeke 9880 Poelkapelle 8920 Poesele 9850 Pollare 9401 Polleur 4910 Pollinkhove 8647 Pommeroeul 7322 Pondrôme 5574 Pont-à-Celles 6230 Pont-de-Loup 6250 Pontillas 5380 Poperinge 8970 Poppel 2382 Popuelles 7760 Porcheresse (Lux.) 6929 Porcheresse (Nam.) 5370 Postcheque 1100 Pottes 7760 Poucet 4280 Poulseur 4171 Poupehan 6830 Pousset 4350 Presgaux 5660 Presles 6250 Profondeville 5170 Promo-Control 1414 Proven 8972 Pry 5650 Pulderbos 2242 Pulle 2243 Purnode 5530 Pussemange 5550 Putte 2580 Puurs 2870 Quaregnon 7390 Quartes 7540 Quenast 1430 Queue-du-Bois 4610 Quevaucamps 7972 Quévy 7040 Quévy-le-Grand 7040 Quévy-le-Petit 7040 Quiévrain 7380 R.T.L. - T.V.I. 1201 Raad Vlaamse Gemeenschapscommissie 1006 Rachecourt 6792 Racour 4287 Raeren 4730 Ragnies 6532 Rahier 4987 Ramegnies 7971 Ramegnies-Chin 7520 Ramelot 4557 Ramillies 1367 Ramsdonk 1880 Ramsel 2230 Ramskapelle (Knokke-Heist) 8301 Ramskapelle (Nieuwpoort) 8620 Rance 6470 Ransart 6043 Ransberg 3470 Ranst 2520 Ravels 2380 Rebaix 7804 Rebecq 1430 Rebecq-Rognon 1430 Recht 4780 Recogne 6800 Redu 6890 Reet 2840 Rekem 3621 Rekkem 8930 Relegem 1731 Remagne 6800 Remersdaal 3791 Remicourt 4350 Renaix 9600 Rendeux 6987 Reninge 8647 Reningelst 8970 Renlies 6500 Reppel 3950 Ressaix 7134 Ressegem 9551 Resteigne 6927 Retie 2470 Retinne 4621 Reuland 4790 Rèves 6210 Rhisnes 5080 Rhode-Saint-Genese 1640 Richelle 4600 Riemst 3770 Rienne 5575 Rièzes 6464 Rijkel 3840 Rijkevorsel 2310 Rijkhoven 3740 Rijksadministratief Centrum 1010 Rijmenam 2820 Riksingen 3700 Rillaar 3202 Rivière 5170 Rixensart 1330 Robechies 6460 Robelmont 6769 Robertville 4950 Roborst 9630 Rochefort 5580 Rochehaut 6830 Rocherath 4761 Roclenge-sur-Geer 4690 Rocourt 4000 Roesbrugge-Haringe 8972 Roeselare 8800 Rognée 5651 Roisin 7387 Roksem 8460 Rollegem 8510 Rollegem-Kapelle 8880 Roloux 4347 Roly 5600 Romedenne 5600 Romerée 5680 Romershoven 3730 Romsée 4624 Rongy 7623 Ronquières 7090 Ronse 9600 Ronsele 9932 Roosbeek 3370 Roosdaal 1760 Rosée 5620 Roselies 6250 Rosières 1331 Rosmeer 3740 Rosoux-Crenwick 4257 Rossignol 6730 Rotem 3650 Rotheux-Rimière 4120 Rotselaar 3110 Roucourt 7601 Rouveroy (Ht.) 7120 Rouvreux 4140 Rouvroy 6767 Roux 6044 Roux-Miroir 1315 Roy 6900 Rozebeke 9630 RTBF 1044 Ruddervoorde 8020 Ruette 6760 Ruien 9690 Ruisbroek (Antw.) 2870 Ruisbroek (Bt.) 1601 Ruiselede 8755 Rukkelingen-Loon 3870 Rulles 6724 Rumbeke 8800 Rumes 7610 Rumillies 7540 Rummen 3454 Rumsdorp 3400 Rumst 2840 Runkelen 3803 Rupelmonde 9150 Russeignies 7750 Rutten 3700 's Gravenvoeren 3798 's Gravenwezel 2970 's Herenelderen 3700 S.H.A.P.E. België 7010 S.H.A.P.E. Belgique 7010 Saint-Amand 6221 Saint-André 4606 Saint-Aubin 5620 Saint-Denis (Ht.) 7034 Saint-Denis-Bovesse 5081 Sainte-Cécile 6820 Sainte-Marie-Chevigny 6800 Sainte-Marie-sur-Semois 6740 Sainte-Ode 6680 Saintes 1480 Saint-Georges-sur-Meuse 4470 Saint-Gérard 5640 Saint-Germain 5310 Saint-Géry 1450 Saint-Ghislain 7330 Saint-Gilles 1060 Saint-Hubert 6870 Saint-Jean-Geest 1370 Saint-Josse-ten-Noode 1210 Saint-Léger (Ht.) 7730 Saint-Léger (Lux.) 6747 Saint-Marc 5003 Saint-Mard 6762 Saint-Martin 5190 Saint-Maur 7500 Saint-Médard 6887 Saint-Nicolas (Lg.) 4420 Saint-Pierre 6800 Saint-Remy (Ht.) 6460 Saint-Remy (Lg.) 4672 Saint-Remy-Geest 1370 Saint-Sauveur 7912 Saint-Servais 5002 Saint-Séverin 4550 Saint-Symphorien 7030 Saint-Vaast 7100 Saint-Vincent 6730 Saint-Vith 4780 Saive 4671 Salles 6460 Samart 5600 Sambreville 5060 Samrée 6982 Sankt Vith 4780 Sars-la-Bruyère 7080 Sars-la-Buissière 6542 Sart-Bernard 5330 Sart-Custinne 5575 Sart-Dames-Avelines 1495 Sart-en-Fagne 5600 Sart-Eustache 5070 Sart-lez-Spa 4845 Sart-Saint-Laurent 5070 Sautin 6470 Sautour 5600 Sauvenière 5030 Schaarbeek 1030 Schaerbeek 1030 Schaffen 3290 Schalkhoven 3732 Schaltin 5364 Schelderode 9820 Scheldewindeke 9860 Schelle 2627 Schellebelle 9260 Schendelbeke 9506 Schepdaal 1703 Scherpenheuvel 3270 Scherpenheuvel-Zichem 3270 Schilde 2970 Schoenberg 4782 Schönberg 4782 Schoonaarde 9200 Schore 8433 Schorisse 9688 Schoten 2900 Schriek 2223 Schuiferskapelle 8700 Schulen 3540 Sclayn 5300 Scy 5361 Seilles 5300 Sélange 6781 Seloignes 6596 Semmerzake 9890 Senat de Belgique 1009 Seneffe 7180 Sensenruth 6832 Seny 4557 Senzeille 5630 Septon 6940 Seraing 4100 Seraing-le-Château 4537 Serinchamps 5590 Serskamp 9260 Serville 5521 Sibret 6640 Signeulx 6750 Sijsele 8340 Silenrieux 5630 Silly 7830 Sinaai-Waas 9112 Sinsin 5377 Sint-Agatha-Berchem 1082 Sint-Agatha-Rode 3040 Sint-Amands 2890 Sint-Amandsberg (Gent) 9040 Sint-Andries 8200 Sint-Antelinks 9550 Sint-Baafs-Vijve 8710 Sint-Blasius-Boekel 9630 Sint-Denijs 8554 Sint-Denijs-Boekel 9630 Sint-Denijs-Westrem 9051 Sint-Eloois-Vijve 8793 Sint-Eloois-Winkel 8880 Sint-Genesius-Rode 1640 Sint-Gillis 1060 Sint-Gillis-bij-Dendermonde 9200 Sint-Gillis-Waas 9170 Sint-Goriks-Oudenhove 9620 Sint-Huibrechts-Hern 3730 Sint-Huibrechts-Lille 3910 Sint-Jacobs-Kapelle 8600 Sint-Jan 8900 Sint-Jan-in-Eremo 9982 Sint-Jans-Molenbeek 1080 Sint-Job-in-'t-Goor 2960 Sint-Joost-ten-Node 1210 Sint-Joris (Beernem) 8730 Sint-Joris (Nieuwpoort) 8620 Sint-Joris-Weert 3051 Sint-Joris-Winge 3390 Sint-Katelijne-Waver 2860 Sint-Katherina-Lombeek 1742 Sint-Kornelis-Horebeke 9667 Sint-Kruis (Brugge) 8310 Sint-Kruis-Winkel 9042 Sint-Kwintens-Lennik 1750 Sint-Lambrechts-Herk 3500 Sint-Lambrechts-Woluwe 1200 Sint-Laureins 9980 Sint-Laureins-Berchem 1600 Sint-Lenaarts 2960 Sint-Lievens-Esse 9550 Sint-Lievens-Houtem 9520 Sint-Margriete 9981 Sint-Margriete-Houtem (Tienen) 3300 Sint-Maria-Horebeke 9667 Sint-Maria-Latem 9630 Sint-Maria-Lierde 9570 Sint-Maria-Oudenhove (Zottegem) 9620 Sint-Martens-Bodegem 1700 Sint-Martens-Latem 9830 Sint-Martens-Leerne 9800 Sint-Martens-Lennik 1750 Sint-Martens-Lierde 9572 Sint-Martens-Voeren 3790 Sint-Michiels 8200 Sint-Niklaas 9100 Sint-Pauwels 9170 Sint-Pieters-Kapelle (Bt.) 1541 Sint-Pieters-Kapelle (W.-Vl.) 8433 Sint-Pieters-Leeuw 1600 Sint-Pieters-Rode 3220 Sint-Pieters-Voeren 3792 Sint-Pieters-Woluwe 1150 Sint-Rijkers 8690 Sint-Stevens-Woluwe 1932 Sint-Truiden 3800 Sint-Ulriks-Kapelle 1700 Sippenaeken 4851 Sirault 7332 Sivry 6470 Sivry-Rance 6470 Sleidinge 9940 Slijpe 8433 Slins 4450 Sluizen 3700 Smeerebbe-Vloerzegem 9506 Smetlede 9340 Smuid 6890 Snaaskerke 8470 Snellegem 8490 SOC 1105 Soheit-Tinlot 4557 Sohier 6920 Soignies 7060 Soiron 4861 Solre-Saint-Géry 6500 Solre-sur-Sambre 6560 Sombreffe 5140 Somme-Leuze 5377 Sommethonne 6769 Sommière 5523 Somzée 5651 Sorée 5340 Sorinne-la-Longue 5333 Sorinnes 5503 Sosoye 5537 Sougné-Remouchamps 4920 Soulme 5680 Soumagne 4630 Soumoy 5630 Sourbrodt 4950 Souvret 6182 Sovet 5590 Soy 6997 Soye (Nam.) 5150 Spa 4900 Spalbeek 3510 Spermalie 8433 Spiennes 7032 Spiere 8587 Spiere-Helkijn 8587 Spontin 5530 Spouwen 3740 Sprimont 4140 Spy 5190 Stabroek 2940 Staden 8840 Stalhille 8490 Stambruges 7973 Stave 5646 Stavele 8691 Stavelot 4970 Steendorp 9140 Steenhuffel 1840 Steenhuize-Wijnhuize 9550 Steenkerke (W.-Vl.) 8630 Steenkerque (Ht.) 7090 Steenokkerzeel 1820 Stekene 9190 Stembert 4801 Stene 8400 Sterrebeek 1933 Stevoort 3512 Stokkem 3650 Stokrooie 3511 Stoumont 4987 Straimont 6887 Strée (Ht.) 6511 Strée-lez-Huy 4577 Strépy-Bracquegnies 7110 Strijpen 9620 Strijtem 1760 Strombeek-Bever 1853 Stuivekenskerke 8600 Suarlée 5020 Sugny 5550 Surice 5600 Suxy 6812 Tailles 6661 Taintignies 7618 Tamines 5060 Tarcienne 5651 Tavier 4163 Taviers (Nam.) 5310 Tavigny 6662 Tellin 6927 Templeuve 7520 Temploux 5020 Temse 9140 Tenneville 6970 Teralfene 1790 Terhagen 2840 Termes 6813 Ternat 1740 Tertre 7333 Tervuren 3080 Terwagne 4560 Tessenderlo 3980 Testelt 3272 Teuven 3793 Theux 4910 Thiaumont 6717 Thieu 7070 Thieulain 7901 Thieusies 7061 Thiméon 6230 Thimister 4890 Thimister-Clermont 4890 Thimougies 7533 Thines 1402 Thirimont 6500 Thisnes 4280 Thommen 4791 Thon 5300 Thorembais-les-Béguines 1360 Thorembais-Saint-Trond 1360 Thoricourt 7830 Thuillies 6536 Thuin 6530 Thulin 7350 Thumaide 7971 Thy-le-Bauduin 5621 Thy-le-Château 5651 Thynes 5502 Thys 4367 Tiegem 8573 Tielen 2460 Tielrode 9140 Tielt 8700 Tielt (Bt.) 3390 Tielt-Winge 3390 Tienen 3300 Tignée 4630 Tihange 4500 Tildonk 3150 Tilff 4130 Tillet 6680 Tilleur 4420 Tillier 5380 Tilly 1495 Tinlot 4557 Tintange 6637 Tintigny 6730 Tisselt 2830 Toernich 6700 Tohogne 6941 Tollembeek 1570 Tongeren 3700 Tongerlo (Antw.) 2260 Tongerlo (Limb.) 3960 Tongre-Notre-Dame 7951 Tongre-Saint-Martin 7950 Tongrinne 5140 Tontelange 6717 Torgny 6767 Torhout 8820 Tourinne (Lg.) 4263 Tourinnes-la-Grosse 1320 Tourinnes-Saint-Lambert 1457 Tournai 7500 Tournay 6840 Tourpes 7904 Transinne 6890 Trazegnies 6183 Treignes 5670 Trembleur 4670 Tremelo 3120 Trivières 7100 Trognée 4280 Trois-Ponts 4980 Trooz 4870 Tubize 1480 Turnhout 2300 U.E.-Commission 1049 U.E.-Conseil 1048 Uccle 1180 Ucimont 6833 Uikhoven 3631 Uitbergen 9290 Uitkerke 8370 Ukkel 1180 Ulbeek 3832 Upigny 5310 Ursel 9910 Vaalbeek 3054 Val-Meer 3770 Vance 6741 Varendonk 2431 Varsenare 8490 Vaucelles 5680 Vaulx (Tournai) 7536 Vaulx-lez-Chimay 6462 Vaux-Chavanne 6960 Vaux-et-Borset 4530 Vaux-lez-Rosières 6640 Vaux-sous-Chèvremont 4051 Vaux-sur-Sure 6640 Vechmaal 3870 Vedrin 5020 Veerle 2431 Velaines 7760 Velaine-sur-Sambre 5060 Veldegem 8210 Veldwezelt 3620 Vellereille-les-Brayeux 7120 Vellereille-le-Sec 7120 Velm 3806 Velroux 4460 Veltem-Beisem 3020 Velzeke-Ruddershove 9620 Vencimont 5575 Ver.Verg.Gemeensch.Gemeensch.Comm. 1005 Vergnies 6440 Verlaine 4537 Verlée 5370 Verrebroek 9130 Vertrijk 3370 Verviers 4800 Vesqueville 6870 Veulen 3870 Veurne 8630 Vezin 5300 Vezon 7538 Viane 9500 Vichte 8570 Vielsalm 6690 Viemme 4317 Viersel 2240 Vierset-Barse 4577 Vierves-sur-Viroin 5670 Viesville 6230 Vieux-Genappe 1472 Vieuxville 4190 Vieux-Waleffe 4530 Villance 6890 Ville-en-Hesbaye 4260 Ville-Pommeroeul 7322 Villerot 7334 Villers-aux-Tours 4161 Villers-Deux-Eglises 5630 Villers-Devant-Orval 6823 Villers-en-Fagne 5600 Villers-la-Bonne-Eau 6600 Villers-la-Loue 6769 Villers-la-Tour 6460 Villers-la-Ville 1495 Villers-le-Bouillet 4530 Villers-le-Gambon 5600 Villers-le-Peuplier 4280 Villers-le-Temple 4550 Villers-l'Evêque 4340 Villers-lez-Heest 5080 Villers-Notre-Dame 7812 Villers-Perwin 6210 Villers-Poterie 6280 Villers-Saint-Amand 7812 Villers-Sainte-Gertrude 6941 Villers-Saint-Ghislain 7031 Villers-Saint-Siméon 4453 Villers-sur-Lesse 5580 Villers-sur-Semois 6740 Ville-sur-Haine (Le Roeulx) 7070 Vilvoorde 1800 Vinalmont 4520 Vinderhoute 9921 Vinkem 8630 Vinkt 9800 Virelles 6461 Virginal-Samme 1460 Viroinval 5670 Virton 6760 Visé 4600 Vissenaken 3300 Vitrival 5070 Vivegnis 4683 Vivy 6833 Vlaamse Raad - Vlaams Parlement 1011 Vladslo 8600 Vlamertinge 8908 Vlekkem 9420 Vleteren 8640 Vlezenbeek 1602 Vliermaal 3724 Vliermaalroot 3721 Vlierzele 9520 Vlijtingen 3770 Vlimmeren 2340 Vlissegem 8421 Vloesberg 7880 Vodecée 5600 Vodelée 5680 Voeren 3790 Vogenée 5650 Volkegem 9700 Vollezele 1570 Vonêche 5570 Voorde 9400 Voormezele 8902 Voort 3840 Voroux-Goreux 4347 Voroux-lez-Liers 4451 Vorselaar 2290 Vorsen 3890 Vorst 1190 Vorst (Kempen) 2430 Vosselaar 2350 Vosselare 9850 Vossem 3080 Vottem 4041 Vrasene 9120 Vremde 2531 Vreren 3700 Vresse-sur-Semois 5550 Vroenhoven 3770 VRT 1043 VTM 1818 Vucht 3630 Vurste 9890 Vyle-et-Tharoul 4570 Waanrode 3473 Waarbeke 9506 Waardamme 8020 Waarloos 2550 Waarmaarde 8581 Waarschoot 9950 Waasmont 3401 Waasmunster 9250 Waasten 7784 Wachtebeke 9185 Wadelincourt 7971 Wagnelée 6223 Waha 6900 Waillet 5377 Waimes 4950 Wakken 8720 Walcourt 5650 Walem 2800 Walhain 1457 Walhain-Saint-Paul 1457 Walhorn 4711 Walsbets 3401 Walshoutem 3401 Waltwilder 3740 Wambeek 1741 Wancennes 5570 Wandre 4020 Wanfercée-Baulet 6224 Wange 3400 Wangenies 6220 Wanlin 5564 Wanne 4980 Wannebecq 7861 Wannegem-Lede 9772 Wansin 4280 Wanze 4520 Wanzele 9340 Warchin 7548 Warcoing 7740 Wardin 6600 Waregem 8790 Waremme 4300 Waret-la-Chaussée 5310 Waret-l'Evêque 4217 Warisoulx 5080 Warnant 5537 Warnant-Dreye 4530 Warneton 7784 Warquignies 7340 Warsage 4608 Warzée 4590 Wasmes 7340 Wasmes-Audemez-Briffoeil 7604 Wasmuel 7390 Wasseiges 4219 Waterland-Oudeman 9988 Waterloo 1410 Watermaal-Bosvoorde 1170 Watermael-Boitsfort 1170 Watervliet 9988 Watou 8978 Wattripont 7910 Waudrez 7131 Waulsort 5540 Wauthier-Braine 1440 Wavre 1300 Wavreille 5580 Wayaux 6210 Ways 1474 Webbekom 3290 Wechelderzande 2275 Weelde 2381 Weerde 1982 Weert 2880 Wegnez 4860 Weillen 5523 Weismes 4950 Welden 9700 Welkenraedt 4840 Welle 9473 Wellen 3830 Wellin 6920 Wemmel 1780 Wenduine 8420 Wépion 5100 Werbomont 4190 Werchter 3118 Wéris 6940 Werken 8610 Werm 3730 Wervik 8940 Wespelaar 3150 Westende 8434 Westerlo 2260 Westkapelle 8300 Westkerke 8460 Westmalle 2390 Westmeerbeek 2235 Westouter 8954 Westrem 9230 Westrozebeke 8840 Westvleteren 8640 Wetteren 9230 Wevelgem 8560 Wezemaal 3111 Wezembeek-Oppem 1970 Wezeren 3401 Wez-Velvain 7620 Wibrin 6666 Wichelen 9260 Widooie (Haren) 3700 Wiekevorst 2222 Wielsbeke 8710 Wierde 5100 Wiers 7608 Wiesme 5571 Wieze 9280 Wihéries 7370 Wihogne 4452 Wijchmaal 3990 Wijer 3850 Wijgmaal (Brabant) 3018 Wijnegem 2110 Wijshagen 3670 Wijtschate 8953 Wilderen 3803 Willaupuis 7904 Willebringen 3370 Willebroek 2830 Willemeau 7506 Willerzie 5575 Wilrijk (Antwerpen) 2610 Wilsele 3012 Wilskerke 8431 Wimmertingen 3501 Winenne 5570 Wingene 8750 Winksele 3020 Wintershoven 3722 Witry 6860 Wodecq 7890 Woesten 8640 Wolkrange 6780 Woluwe-Saint-Lambert 1200 Woluwe-Saint-Pierre 1150 Wolvertem 1861 Wommelgem 2160 Wommersom 3350 Wonck 4690 Wondelgem 9032 Wontergem 9800 Wortegem 9790 Wortegem-Petegem 9790 Wortel 2323 Woubrechtegem 9550 Woumen 8600 Wulpen 8670 Wulvergem 8952 Wulveringem 8630 Wuustwezel 2990 Xhendelesse 4652 Xhendremael 4432 Xhoris 4190 Yernée-Fraineux 4550 Yves-Gomezée 5650 Yvoir 5530 Zaffelare 9080 Zandbergen 9506 Zande 8680 Zandhoven 2240 Zandvliet 2040 Zandvoorde (Oostende) 8400 Zandvoorde (Zonnebeke) 8980 Zarlardinge 9500 Zarren 8610 Zaventem 1930 Zedelgem 8210 Zeebrugge (Brugge) 8380 Zegelsem 9660 Zele 9240 Zelem 3545 Zellik 1731 Zelzate 9060 Zemst 1980 Zepperen 3800 Zerkegem 8490 Zétrud-Lumay 1370 Zevekote 8470 Zeveneken 9080 Zeveren 9800 Zevergem 9840 Zichem 3271 Zichen-Zussen-Bolder 3770 Zillebeke 8902 Zingem 9750 Zoerle-Parwijs 2260 Zoersel 2980 Zolder 3550 Zomergem 9930 Zonhoven 3520 Zonnebeke 8980 Zonnegem 9520 Zottegem 9620 Zoutenaaie 8630 Zoutleeuw 3440 Zuidschote 8904 Zuienkerke 8377 Zulte 9870 Zulzeke 9690 Zutendaal 3690 Zwalm 9630 Zwevegem 8550 Zwevezele 8750 Zwijnaarde 9052 Zwijndrecht 2070 Antwerpen 2000 \. COPY "group" (name, description) FROM stdin; tennis Acquaintances from the tennis club pfm Persons involved in the pfm project work Acquaintances from work cycling Cycling companions family Members of the family \. COPY memberlist (person, "group") FROM stdin; 1 pfm 1 work 3 work 3 tennis 2 tennis 2 pfm 10 cycling 10 tennis 6 family 8 family 10 family 8 work 6 tennis 12 work \. COPY person (id, christian_name, name, street, town, "ZIPcode", country, category, description) FROM stdin; 1 Adriaan Brouwers De Coninckstraat 23 Zwevezele 8750 Belgium acquaintance \N 3 Nelly Verdonck Azalealaan 33 Lochristi 9080 Belgium acquaintance \N 4 PC winkeltje Wapenstilstandlaan 67 Wommelgem 2160 Belgium company PC hardware 2 Albert Van de Perre Schanslaan 45 Berchem 2600 Belgium acquaintance Colleague from work 5 TEVE-electro Wilgenlaan 98 Berchem 2600 Belgium company TV and HIFi shop 6 Hugo Van Riel Kerkstraat 56 Ranst 2520 Belgium service Medical Doctor 7 Marcel Willekens Schriek 34 Ranst 2520 Belgium service Dentist 9 Erik Van Meensel Kesselheide 34 Nijlen 2560 Belgium acquaintance 10 Norbert Van Horebeke Hoogstraat 3 Zottegem 9620 Belgium acquaintance 8 Nancy Lemmens Copernicuslaan 198 Gent 9000 Belgium acquaintance 12 Adri Van Geluwe Jan Breydelstraat 21 Aalbeke 8511 Belgium acquaintance \. COPY pfm_attribute (attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default") FROM stdin; linkname taQuoted tgDirect 1 pfm_link none \N value taQuoted tgDirect \N 2 pfm_value none \N sqlwhere taQuoted tgDirect 4 pfm_link none \N orderby taQuoted tgDirect 5 pfm_link none \N displayattrib taQuoted tgDirect 6 pfm_link none \N description taQuoted tgDirect 3 pfm_value none \N name taQuoted tgDirect 1 pfm_report none \N description taQuoted tgDirect 2 pfm_report none \N report taQuoted tgLink select name, description from pfm_report order by name 1 pfm_section none \N nr taNotQuoted tgDirect 7 pfm_attribute none \N name taQuoted tgDirect 1 pfm_form none \N attribute taQuoted tgDirect 2 pfm_attribute none \N sqlselect taQuoted tgDirect 5 pfm_attribute none \N form taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 1 pfm_attribute none \N fromform taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 2 pfm_link none \N toform taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 3 pfm_link none \N valuelist taQuoted tgLink SELECT name FROM pfm_value_list ORDER BY name 1 pfm_value none \N name taQuoted tgDirect 1 pfm_value_list none \N christian_name taQuoted tgDirect 2 person none \N name taQuoted tgDirect 3 person none \N street taQuoted tgDirect 4 person none \N town taQuoted tgDirect 5 person none \N category taQuoted tgList 8 person categories \N name taQuoted tgDirect 1 group none \N description taQuoted tgDirect 2 group none \N description taQuoted tgDirect 9 person none \N person taNotQuoted tgLink SELECT id, name, christian_name FROM person ORDER BY name, christian_name 1 memberlist none \N christian_name taQuoted tgReadOnly 2 memberlist none \N name taQuoted tgReadOnly 3 memberlist none \N group taQuoted tgLink SELECT name, description FROM "group" ORDER BY name 4 memberlist none \N ZIPcode taQuoted tgDirect 1 ZIPcodes none \N town taQuoted tgDirect 2 ZIPcodes none \N ZIPcode taQuoted tgLink SELECT "ZIPcode", town FROM "ZIPcodes" ORDER BY town 6 person none \N sqlselect taQuoted tgDirect 3 pfm_report none \N tablename taQuoted tgDirect 2 pfm_form none \N sqlselect taQuoted tgDirect \N 4 pfm_form none \N sqlfrom taQuoted tgDirect \N 5 pfm_form none \N groupby taQuoted tgDirect 6 pfm_form none \N pkey taQuoted tgDirect 3 pfm_form none default taQuoted tgDirect 8 pfm_attribute none typeofattrib taQuoted tgList 3 pfm_attribute typeofattribute taQuoted typeofget taQuoted tgList \N 4 pfm_attribute typeofget tgDirect valuelist taQuoted tgLink SELECT name FROM pfm_value_list ORDER BY name 6 pfm_attribute none none id taNotQuoted tgDirect 1 person none =SELECT nextval('person_id_seq') country taQuoted tgDirect 7 person none Belgium help taQuoted tgDirect 11 pfm_form none \N showform taQuoted tgList 9 pfm_form boolean t view taQuoted tgList 10 pfm_form boolean f sqlorderby taQuoted tgDirect 7 pfm_form none sqllimit taQuoted tgDirect 8 pfm_form none fieldlist taQuoted tgDirect 5 pfm_section none \N level taNotQuoted tgDirect 3 pfm_section none 1 layout taQuoted tgList 4 pfm_section layout table summary taQuoted tgDirect 6 pfm_section none sqlselect taQuoted tgReadOnly 2 pfm_section none \. COPY pfm_form (name, tablename, showform, "view", sqlselect, sqlfrom, groupby, help, pkey, sqlorderby, sqllimit) FROM stdin; group group t f name, description "group" name \N \N memberlist memberlist t f memberlist."group", memberlist.person,\np.christian_name, p.name memberlist LEFT OUTER JOIN person p ON (p.id = memberlist.person) person group \N \N ZIPcodes ZIPcodes t f "ZIPcode", town "ZIPcodes" ZIPcode town \N \N pfm_value pfm_value f f value, description, valuelist pfm_value \N The table "pfm_value" contains all the values of the lists defined in\npfm_value_list.\n\nIt has the following attributes:\n\n - valuelist : the name of the valuelist to which this value belongs\n\n - value : a character string;\n\n - description : a description of the value.\n valuelist value valuelist, value \N pfm_value_list pfm_value_list f f name pfm_value_list \N The table "pfm_value_list" contains all the value lists of all the forms.\n\nIts only attribute is\n\n - name : a name uniquely identifying the value list.\n name name \N pfm_form pfm_form f f name, tablename, sqlselect, sqlfrom, groupby, showform, "view", help, pkey, sqlorderby, sqllimit pfm_form \N A form allows the user to administer the data of just one table. This\ntable is henceforth referred to as "the form's main table".\n\nHowever, a form also has an SQL SELECT statement, which generates the\ndata that are displayed on it.\n\nIn the simplest case the SQL SELECT statement is just:\n\n SELECT FROM

\n\nIn that case, the data which can be administered and the data which\nare displayed on the form are the same.\n\nIn more complex cases, the
can be JOINED with other\ntables, which makes it possible to display data of other related\ntables as well. These data cannot be modified by means of the form.\n\nThe table "pfm_form" has the following attributes:\n\n - name : the name of the form (usually equal to the name of\n the form's table);\n\n - tablename : the name of the form's main table;\n\n - pkey : the primary key of the form's main table, which may\n consist of more than one attribute. In that case pkey is a SPACE\n separated list of the attributes of the primary key;\n\n Note: If pkey is empty, the form is read-only, since pfm is\n unable to uniquely identify a record. You can use the\n 'oid' as primary key, but according to the PostgreSQL\n documentation that is not recommended, unless you set a\n UNIQUE constraint on the 'oid'.\n\n - sqlselect : the attribute list of the form's SQL SELECT\n statement, not including the word 'SELECT';\n\n - sqlfrom : the FROM clause of the form's SQL SELECT statement,\n not including the word 'FROM';\n\n - groupby : an optional 'GROUP BY' clause, not including the words\n 'GROUP BY';\n\n - sqlorderby : an optional 'ORDER BY' clause, not including the\n words 'ORDER BY';\n\n - sqllimit : an optional 'LIMIT' clause, only specifying the limit\n value as a positive integer;\n\n Notes:\n\n - This enables the designer of the form to avoid excessive\n memory usage by limiting the number of records loaded in\n the form's internal buffer. This may be useful for\n handling large tables.\n\n - If sqllimit is a positive integer, a\n\n LIMIT sqllimit OFFSET 0\n\n is added to the form's SELECT when opening the form.\n\n This means that only 'sqllimit' records are loaded into\n the form's internal buffer. When the user moves beyond the\n last record in the internal buffer, the internal buffer is\n first cleared and then reloaded with the next 'sqllimit'\n records by re-executing the form's SELECT but now with\n another OFFSET in the LIMIT clause.\n\n - If sqllimit is an empty string, no LIMIT clause is\n appended to the form's SELECT.\n\n - Always specify an 'sqlorderby' if you specify an\n 'sqllimit'. See PostgreSQL documentation of LIMIT-clause\n in SELECT statement for more details.\n\n - showform : a boolean indicating whether the form is shown\n in "normal mode" (showform = 'true') or in "design mode"\n (showform = 'false'). Typically, showform is set 'true' for user\n defined forms and 'false' for the predefined pfm_* forms.\n\n - view : a boolean indicating whether or not the\n "tablename" is a view;\n\n - help : a text which is displayed when the user presses\n the [Help] key on the form.\n\nThe form's main table is defined by tablename. Only the data of\nthat table can be administered by using the form.\n\nAll the data generated by the form's SQL SELECT statement can be\ndisplayed on the form. The SQL SELECT statement is defined by:\n\n - the sqlselect, sqlfrom, groupby, sqlorderby and sqllimit\n attributes of pfm_form; and\n\n - the optional WHERE and ORDER BY clauses provided by the user\n when opening the form.\n\nNote: The WHERE clause provided by the user when opening the form, becomes\n a HAVING clause, if there is a GROUP BY clause.\n\nThe following rules should be observed when filling out sqlselect and\nsqlfrom:\n\n 1. The form's main table must appear in 'sqlfrom', and must not be\n aliased. Similarly, the main table's attributes appearing in\n 'sqlselect' must not be aliased. The other tables appearing in\n the 'sqlfrom' may be aliased.\n\n 2. The fields appearing in 'sqlselect' must have a unique, simple\n name without the need to precede them with a tablename. So,\n calculated fields must be given a name by aliasing and\n attributes of tables other than the main table may need to be\n aliased in order to have a unique, simple name.\n\n 3. The 'sqlfrom' is either just the name of the form's main table,\n or it is a JOIN clause in which the 'LEFT' table is the form's\n main table. Several join clauses can be nested in order to\n involve more than 2 tables. See examples below.\n\n\nExample 1: the SQL SELECT for the person form of the addressbook database\n\n\ntablename:\n person\n\npkey:\n id\n\nsqlselect:\n id, christian_name, name, street, town, "ZIPcode",\n country, category, description\n\nsqlfrom:\n person\n\ngroupby:\n -\n\n\nExample 2: the SQL SELECT for the memberlist form of the addressbook database\n\n\ntablename:\n memberlist\n\npkey:\n group person\n\nsqlselect:\n memberlist."group", memberlist.person, p.christian_name, p.name\n\nsqlfrom:\n memberlist LEFT OUTER JOIN person p ON (p.id = memberlist.person)\n\ngroupby:\n - name showform DESC, name \N pfm_attribute pfm_attribute f f attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default" pfm_attribute \N The table "pfm_attribute" defines all the properties of form attributes.\n\nIt has the following attributes:\n\n - form : the "name" of the form to which the attribute\n belongs;\n\n - attribute : the name of the attribute; this must be equal\n to the name of the corresponding attribute of the form's SQL\n SELECT statement;\n\n - typeofattrib : the type of attribute:\n\n o taQuoted: the value provided by the user is put\n between single quotes when it is transferred to SQL\n UPDATE or INSERT statements;\n \n o taNotQuoted: the value provided by the user is not\n quoted when it is transferred to SQL UPDATE or INSERT\n statements.\n\n Hint: In general, all attribute values must be quoted, exept\n the values or expressions for numeric attributes.\n\n - typeofget: defines how the user provides a value for the\n attribute; possible values are:\n\n o tgDirect: the user types the value directly;\n\n o tgExpression: the user types an expression which is first\n evaluated before it is passed to SQL UPDATE or INSERT;\n\n Note: Even with tgDirect it is possible to enter an\n expression as new value for an attribute, but then\n the expression is evaluated by postgresql whereas\n with tgExpression, the expression is first evaluated\n by Tcl before the SQL statement is sent to\n postgresql.\n\n o tgList: the user selects a value by means of a list box\n containing a list of values defined in table "pfm_value";\n\n o tgLink: the user selects a value by means of a list box\n containing a list of values which is the result from a\n query on another table.\n\n o tgReadOnly: this attribute cannot be modified by\n the user.\n\n Note: All calculated attributes and all attributes from\n tables other than the form's main table should be\n declared 'read-only'. If this rule is not observed,\n the Add and Update operations on this form will fail.\n\n - sqlselect: the SQL SELECT statement which is used to fill the\n list box with possible values for the attribute (only meaningful\n if typeofget = tgLink).\n\n Note :\n\n o The sqlselect may return more than 1 attribute. If so, all\n the attributes are displayed in the list-box, but only the\n first one is used for updating the attribute.\n\n - valuelist : the "name" of the value list defined in table\n "pfm_value_list" (only meaningful if typeofget = tgList);\n\n - nr: a number which determines the order in which attributes are\n displayed on the form;\n\n - default: a default value for this attribute which is used when\n adding a record. If the first character is an '=' sign, the\n following characters should be an SQL SELECT statement which\n returns just one value.\n\n Example:\n\n default: =SELECT nextval('seq_person_id')\n\n In this example the default value is the next value of the\n sequenece 'seq_person_id'.\n form attribute form, nr \N pfm_link pfm_link f f linkname, sqlwhere, orderby, displayattrib, fromform, toform pfm_link \N A link is a navigation tool which allows you to follow a "one-to-many"\nor "many-to-one" relationship from one form to another.\n\nEvery link is stored as a record in the pfm_link table, which has the\nfollowing attributes:\n\n - linkname : the name of the link, which is displayed on\n a link button on the "fromform";\n\n - fromform : the name of the form from which the link\n originates;\n\n - toform : the name of the form to which the link leads;\n\n - sqlwhere : the "WHERE"-clause which is used to open the\n "toform" and in which the value of an attribute of the\n "fromform" may be represented by $(attrib-x), where\n 'attrib-x' is the name of the attribute;\n\n - orderby : an 'order by' clause which determines the order of the\n records in the 'toform';\n\n - displayattrib : a space separated list of\n attributes of the 'fromform', the value of which is displayed on\n the 'toform' to remind the user from which record the link\n originated.\n\nNote: Postgres Forms does not provide any checks to safeguard\n the referential integrity of the data base in case of updates or\n deletions. However, postgreSQL provides these functions as\n 'foreign key' table constraints (see postgreSQL documentation). fromform linkname fromform, linkname \N pfm_report pfm_report f f name, description, sqlselect pfm_report \N The table pfm_report defines all the reports for the current data\nbase.\n\npfm_report has the following attributes:\n\n - name: the name of the report. This is the name that\n appears in the selection list of the "Run Report" function.\n\n - description: free text describing the purpose of the\n report in more detail.\n\n - sqlselect: an SQL SELECT statement that generates the\n data for the report.\n\nThe sqlselect may contain one or more parameters for which a\nvalue is requested at "Run report" time. A parameter in the sqlwhere\nmust be formatted as $(parameter_name).\n\nExample:\n\nsqlselect: \n\n SELECT g.name AS "group", g.description, p.id, p.name,\n p.christian_name, p.street, p."ZIPcode", p.town, p.country\n FROM "group" g\n LEFT JOIN memberlist m ON g.name = m."group"\n LEFT JOIN person p ON m.person = p.id\n WHERE "group" = '$(group)'\n ORDER BY g.name, p.name, p.christian_name\n\nWhen the report is run, the user is prompted to enter a value for the\nparameter "group". Then the report data are generated by executing the\nsqlselect statement in which $(group) is replaced with the value\nentered by the user.\n name name \N pfm_section pfm_section f f pfm_section.report, r.sqlselect, pfm_section."level", pfm_section.fieldlist, pfm_section.layout, pfm_section.summary pfm_section LEFT OUTER JOIN pfm_report r ON (pfm_section.report = r.name) \N The data returned by the report's SQL SELECT statement may be\nconsidered as a table with a column for each 'field' specified after\nthe word 'SELECT' and with a row for each record.\n\nBy specifying an 'ORDER BY' clause in the report's SQL SELECT\nstatement, it is possible to group rows with the same values for some\nfields together.\n\nThe report generator has an "economy" algorithm which avoids printing\nthe same data repeatedly.\n\nTo control this you have to distribute the fields (columns) of the\ntable over n sections such that section 1 contains the fields that are\nchanging least frequently (when moving from one row to the next),\nsection 2 contains the fields that are changing more frequently, and\nsection n contains the fields that are changing at every row.\n\nWhen the data of the first row of the table are printed, the data of\nsection 1 are printed first. Then, on the following line, indented by\none tab stop, the data of section 2 are printed. Then, on the\nfollowing line, indented by 2 tab stops, data of section 2 are\nprinted, etc.\n\n[section 1] <--- row 1\n\n [section 2] <--- row 1\n\n [section 3] <--- row 1\n\nThen, when the next rows are being printed, data of the lower numbered\nsections are only printed if they are different from the data of the\nlast printed section of the same number:\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 1\n [section 3] <--- row 2\n [section 3] <--- row 3\n\n [section 2]\n\n [section 3] <--- row 4\n [section 3] <--- row 5\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 6\n [section 3] <--- row 7\n\nThe report generator also enables you to print a summary at every\npoint where a higher numbered section is about to be followed by a\nlower numbered section:\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 1\n [section 3] <--- row 2\n [section 3] <--- row 3\n\n [summary 3]\n\n [section 2]\n\n [section 3] <--- row 4\n [section 3] <--- row 5\n\n [summary 3]\n\n [summary 2]\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 6\n [section 3] <--- row 7\n\n [summary 3]\n\n [summary 2]\n\n[summary 1]\n\nA summary i is printed just before a lower numbered section j (j < i).\nIts data can be calculated:\n\n - by applying one of the aggregate funtions: COUNT, SUM, AVG,\n STDDEV, MIN, MAX;\n\n - on the fields of the sections j (j >= i), between the last\n printed lower numbered section k (k < i), till the next (not\n yet printed) lower numbered section k (k < i).\n\nIn particular, summary 1 is printed at the end of the report, is\ncalculated from all the sections of the report and may be calculated\nfrom all the fields.\n\nA record in pfm_section defines a section and a summary of a report.\n\nThe table pfm_section has the following attributes:\n\n - report: the name of the report to which the section belongs\n\n - level: a number 1, 2, 3, 4, ... . The first level must be\n '1'. The next levels must be numbered consecutively. In the most\n simple report, there is only a section with level 1.\n\n - layout: can be "row", "column" or "table".\n\n - fieldlist: a space separated list of field specifiers,\n one for each field to be printed in the sections of this level\n (see below for details).\n\n - summary: a space separated list of summary field\n specifiers (see below for details).\n\nThe fieldlist is a SPACE separated list of field specifiers\n\n field_spec_1 field_spec_2 ... field_spec_N\n\nwhere each field specifier is formatted as follows:\n\n {field_i label_i alignment_i max_length_i}\n\nwhere :\n\n - field_i is the name of one of the columns returned by the\n report's SQL SELECT statement;\n\n - label_i is a string which has to be used as label for printing\n the i-th field of this section; if it consists of more than 1\n word, it must be delimited by double quotes (" .... ");\n\n - alignment_i is optional; if present, it is either l or r,\n indicating whether this field should be left or right aligned.\n\n - max_length_i is optional: if present, it is the maximum number\n of characters per line for printing the data of this field;\n lines longer than max_length_i will be wrapped by inserting\n one or more line breaks before printing.\n\n Notes :\n\n o The alignment is optional. If it is left out, left\n alignment is assumed by default.\n\n o The alignment only influences the table layout. Column and\n row layouts are unaffected by the alignment indicator.\n\n o Multi-line fields, i.e. fields containing more than one\n line of text are only formatted properly in a column or\n table layout.\n\n o For a table layout, pfm automatically calculates the column\n width that is required to display all data. So, normally\n you don't have to worry about column widths. However,\n sometimes, the data of a few records, make the columns\n excessively wide. That is where you might consider using\n "max_length_i" in the field specifier. If the data do not\n exceed that maximum, it won't have any effect.\n\n o Although 'alignment' and 'max_length' are both optional,\n you have to specify 'alignment' if you want to specify\n max_length.\n\nFor every section, the layout can be defined as:\n\n - row: the section's field labels and field values are\n printed in one row in a format: label_1 : value_1; label_2 :\n value_2; ... etc.\n\n - column: the section's field labels are printed in a first\n column, the section's field values are printed in a second column.\n\n - table: the section's values are printed in a table with a\n column per field and a row per record, the section's field\n labels are used as column headers for the table.\n\nThe summary must be formatted as a space separated list of summary\nspecifiers:\n\n summary_spec_1 summary_sepc_2 .... summary_sepc_N\n\nwhere each summary_spec is formatted as follows:\n\n {field_i aggregate_i format_i}\n\nwhere:\n\n - field_i is the name of a field defined in the fieldlist of\n either this section, or another, higher numbered section;\n\n - aggregate_i is one of the aggregate functions: COUNT, SUM, AVG,\n STDDEV, MIN, MAX (see below for details); and\n\n - format_i is an optional 'ANSI C sprintf' formatting string (see\n below for details). If it is left out, the number is printed\n with maximum precision.\n\n\nAggregate functions:\n\nIn general, the aggregate functions, use the same "economy" algorithm\nthat is used for printing section data.\n\nWhen all the fields of a section, which is not the highest numbered\nsection of the report, have the same values for a number of\nconsecutive rows, this section's data are only printed once for these\nrows.\n\nSimilarly, these rows are only counted once by the aggregate functions\napplied to a field of this section.\n\nThe aggregate functions that can be used in a summary are:\n\n - COUNT: Counts the number of rows. In this case, the field_i that\n is specified only determines which section is counted.\n\n - SUM: Calculates the sum of all the values of the specified\n field.\n\n - AVG: Calculates the average of the values of the specified\n field.\n\n - STDDEV: Calculates the sample standard deviation for the values of the\n specified field:\n\n SQRT (SUM( (value_i - AVG(value))**2 ) / (N - 1))\n\n\t where :\n\n - value_1, value_2, ... value_N are the values of the\n considered field;\n\n - AVG(value) is the average of the considered values;\n\n - N is the number of values.\n\n - MIN: Calculates the minimum of the values of the specified\n field.\n\n - MAX: Calculated the maximum of the values of the specified\n field.\n\n\n'ANSI C sprintf' formatting string:\n\nHere is a short overview of the 'ANSI C sprintf' formatting string. In\ngeneral its form is:\n\n %'MinWidth'.'Precision''Conversion'\n\nwhere:\n\n - 'MinWidth' is an integer defining the minimum width (as number\n of characters) for the number to be printed. If the number does\n not need so much space, spaces are inserted in front of the\n number, unless MinWidth is negative. In that case, spaces are\n appended at the end. If the number needs more space than\n MinWidth, more space is used.\n\n - 'Precision' is an integer defining how many digits to print\n after the decimal point, or, in the case of g or G conversion,\n the total number of digits to appear, including those on both\n sides of the decimal point\n\n - 'Conversion' is one of:\n\n o d : convert integer to signed decimal string. In this case,\n there is no need to define a 'Precision'.\n\n Example: %1d\n\n prints an integer and uses as many characters\n as required.\n\n o f : convert floating point number to fixed point\n notation. In this case, 'Precision' defines the number\n of digits to print after the decimal point. If there\n are not enough digits available, trailing zeroes are\n appended.\n\n Example: %1.2f\n\n prints a floating point number wiht 2 digits\n after the decimal point and uses as many\n characters as required.\n\n o e or E : Convert floating-point number to scientific\n notation in the form x.yyye±zz, where the number of\n y's is determined by the 'Precision' (default: 6). If\n the precision is 0 then no decimal point is output. If\n the E form is used then E is printed instead of e.\n\n Example: %1.5E\n\n prints a floating point number in the form\n x.yyyyy E±zz \n\n o g or G : If the exponent is less than -4 or greater than\n or equal to the precision, then convert floating-point\n number as for %e or %E. Otherwise convert as for\n %f. Trailing zeroes and a trailing decimal point are\n omitted. In this case the 'Precision' specifies the\n total number of digits to appear, including those on\n both sides of the decimal point\n\n Example: %1.4G\n\n prints 2345.0 as 2345\n prints 234567.0 as 2.346E+05\n prints 0.003456 as 0.003456\n prints 0.00003456 as 3.456E-05 report level report, "level" \N person person t f id, christian_name, name, street, town, "ZIPcode", country, category, description person When adding another person, a new id will be created automatically.\n\nWhen entering the ZIPcode, you can\n\n - either select it from the list, by clicking on the ZIP-code\n button;\n\n - or type directly by clicking on the 'Expand' button, at the right\n of the ZIPcode.\n\nUp to now, only the ZIP codes for Belgium have been inserted. id \N \N \. COPY pfm_link (linkname, sqlwhere, orderby, displayattrib, fromform, toform) FROM stdin; Report name='$(report)' level pfm_section pfm_report Sections report='$(name)' level name pfm_report pfm_section Attributes form='$(name)' nr name pfm_form pfm_attribute incoming links toform='$(name)' fromform name pfm_form pfm_link outgoing links fromform='$(name)' toform name pfm_form pfm_link Where used? valuelist='$(name)' name pfm_value_list pfm_attribute Values valuelist='$(name)' value name pfm_value_list pfm_value Value list name='$(valuelist)' attribute pfm_attribute pfm_value_list from Form name='$(fromform)' linkname pfm_link pfm_form to Form name='$(toform)' linkname pfm_link pfm_form Valuelist name='$(valuelist)' value pfm_value pfm_value_list Person id = $(person) "group" memberlist person Groups person = $(id) "group" christian_name name person memberlist Members "group" = '$(name)' name, christian_name name group memberlist Group name = '$(group)' christian_name name group memberlist group Form name='$(form)' attribute pfm_attribute pfm_form \. COPY pfm_report (name, description, sqlselect) FROM stdin; DefinedForms Report showing the form definitions of this database SELECT f.name, f.tablename, f.sqlselect AS "SELECT", f.sqlfrom AS "FROM",\n f.groupby AS "GROUP BY", f.sqlorderby AS "ORDER BY",\n f.sqllimit AS "LIMIT", f.pkey AS "PRIM. KEY", f.showform, f."view",\n a.attribute, a.typeofattrib, a.typeofget, a.sqlselect, a.nr,\n a.valuelist, a."default"\nFROM pfm_form f LEFT OUTER JOIN pfm_attribute a ON (f.name = a.form)\nORDER BY f.showform DESC, f.name, a.nr DefinedLinks Report showing the link definitions of this database SELECT linkname, sqlwhere, orderby, displayattrib, fromform , toform\nFROM pfm_link\nORDER BY fromform, linkname FormHelp Report showing the on-line help for forms defined in this database SELECT name, help\nFROM pfm_form\nWHERE help <> ''\nORDER BY showform DESC, name DefinedReports Report showing the report definitions of this database SELECT r.name, r.description, r.sqlselect,\n s."level", s.fieldlist, s.layout, s.summary\nFROM pfm_report r LEFT OUTER JOIN pfm_section s ON (r.name = s.report)\nORDER BY r.name Persons and groups Lists all persons and the groups to which they belong SELECT p.id, p.christian_name, p.name, p.street, p.town, p."ZIPcode",\n p.country, p.category, p.description, g.name AS "group"\nFROM person p\n LEFT JOIN memberlist m ON p.id = m.person\n LEFT JOIN "group" g ON m."group" = g.name\nORDER BY p.name, p.christian_name, g.name Groups and persons Lists all groups and their members SELECT g.name AS "group", g.description, p.id, p.name, p.christian_name,\n p.street, p."ZIPcode", p.town, p.country\nFROM "group" g\n LEFT JOIN memberlist m ON g.name = m."group"\n LEFT JOIN person p ON m.person = p.id\nORDER BY g.name, p.name, p.christian_name Group List all the members of a certain group SELECT g.name AS "group", g.description, p.id, p.name,\n p.christian_name, p.street, p."ZIPcode", p.town, p.country\nFROM "group" g\n LEFT JOIN memberlist m ON g.name = m."group"\n LEFT JOIN person p ON m.person = p.id\nWHERE "group" = '$(group)'\nORDER BY g.name, p.name, p.christian_name \. COPY pfm_section (report, "level", fieldlist, layout, summary) FROM stdin; Groups and persons 1 {group group l} {description description l} row {group COUNT} Groups and persons 2 {id id r} {christian_name "Chr. name" l} {name name l} {street street l} {ZIPcode ZIP l} {town town l} {country country l} table {id COUNT} Group 1 {id id r} {christian_name "chr. name" l} {name name l} {street street l} {ZIPcode ZIP l} {town town l} {country country l} table {id COUNT} Persons and groups 2 {group groups l} table {group COUNT} Persons and groups 1 {id id r} {name name l} {christian_name "Chr. name" l} {street street l} {ZIPcode ZIP l} {town town l} {country country l} table {id COUNT} DefinedForms 1 {name Form l} {tablename Table l} {SELECT SELECT l} {FROM FROM l} {"GROUP BY" "GROUP BY" l} {"ORDER BY" "ORDER BY" l} {LIMIT LIMIT l} {"PRIM. KEY" "PRIM. KEY" l} {showform showform l} {view view l} column DefinedLinks 2 {linkname linkname l} {toform toform l} {sqlwhere sqlwhere l 30} {orderby orderby l 30} {displayattrib displayattrib l} table {linkname COUNT} DefinedLinks 1 {fromform "Links from form" l} row {fromform COUNT} DefinedReports 1 {name Report} {description Description} {sqlselect SQL} column DefinedReports 2 {level Section r} {fieldlist fieldlist l 40} {layout layout l} {summary summary l 30} table FormHelp 1 {name Form} row FormHelp 2 {help "On line help" l 80} table DefinedForms 2 {nr nr r} {attribute attribute l} {typeofattrib typeofattrib l} {typeofget typeofget l} {sqlselect sqlselect l 30} {valuelist valuelist l} {default default l 30} table \. COPY pfm_value (value, description, valuelist) FROM stdin; taQuoted Value must be enclosed in ' ' for SQL. typeofattribute taNotQuoted Value must not be enclosed in ' ' for SQL. typeofattribute tgDirect Value directly typed by user. typeofget tgExpression Value may be given as an expression. typeofget tgList Value comes from a valuelist. typeofget tgLink Value comes from 'sqlselect'. typeofget t TRUE boolean f FALSE boolean column A column for the labels, a second column for the corresponding values layout table A table with the labels as table header layout row Labels and values on 1 row layout tgReadOnly User cannot change the value of this attribute typeofget acquaintance A personal acquaintance categories company A company categories service A service, or a person providing a service categories \. COPY pfm_value_list (name) FROM stdin; typeofattribute typeofget boolean layout none categories \. ALTER TABLE ONLY "ZIPcodes" ADD CONSTRAINT "ZIPcodes_pkey" PRIMARY KEY ("ZIPcode", town); ALTER TABLE ONLY "group" ADD CONSTRAINT group_pkey PRIMARY KEY (name); ALTER TABLE ONLY memberlist ADD CONSTRAINT memberlist_pkey PRIMARY KEY (person, "group"); ALTER TABLE ONLY person ADD CONSTRAINT person_pkey PRIMARY KEY (id); ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT pfm_attribute_pkey PRIMARY KEY (form, attribute); ALTER TABLE ONLY pfm_form ADD CONSTRAINT pfm_form_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_link ADD CONSTRAINT pfm_link_pkey PRIMARY KEY (fromform, linkname); ALTER TABLE ONLY pfm_report ADD CONSTRAINT pfm_report_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_section ADD CONSTRAINT pfm_section_pkey PRIMARY KEY (report, "level"); ALTER TABLE ONLY pfm_value_list ADD CONSTRAINT pfm_value_list_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_value ADD CONSTRAINT pfm_value_pkey PRIMARY KEY (valuelist, value); ALTER TABLE ONLY pfm_version ADD CONSTRAINT pfm_version_pkey PRIMARY KEY (seqnr); ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT ref_form FOREIGN KEY (form) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_link ADD CONSTRAINT ref_fromform FOREIGN KEY (fromform) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY memberlist ADD CONSTRAINT ref_group FOREIGN KEY ("group") REFERENCES "group"(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_value ADD CONSTRAINT ref_list FOREIGN KEY (valuelist) REFERENCES pfm_value_list(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY memberlist ADD CONSTRAINT ref_person FOREIGN KEY (person) REFERENCES person(id) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_section ADD CONSTRAINT ref_sections FOREIGN KEY (report) REFERENCES pfm_report(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_link ADD CONSTRAINT ref_toform FOREIGN KEY (toform) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT ref_value_list FOREIGN KEY (valuelist) REFERENCES pfm_value_list(name) ON UPDATE CASCADE ON DELETE RESTRICT; pfm-2.0.8/examples/install_customerdb.sql0000644000175000017500000011633611472160340016721 0ustar wimwim-- install_customerdb.sql -- Version 1.5.0 -- The character encoding of this file is iso8859-1 (latin1). -- When imported via the 'Tools -> install pfm_* tables' menu, pfm -- converts it to UTF-8 before offering it to psql, which is always -- running with client_encoding 'UNICODE(=UTF-8)' when invoked by pfm. CREATE TABLE customer ( id serial NOT NULL, name text, street text, post_code text, town text, country text, telephone text, e_mail text ); SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('customer', 'id'), 17, true); CREATE TABLE invoice ( id serial NOT NULL, customer integer, date date ); SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('invoice', 'id'), 21, true); CREATE TABLE "order" ( invoice integer NOT NULL, product integer NOT NULL, how_many integer ); CREATE TABLE pfm_attribute ( attribute text NOT NULL, typeofattrib text, typeofget text, sqlselect text, nr integer, form text NOT NULL, valuelist text, "default" text ); CREATE TABLE pfm_form ( name text NOT NULL, tablename text, showform boolean DEFAULT true, "view" boolean DEFAULT false, sqlselect text, sqlfrom text, groupby text, help text, pkey text, sqlorderby text, sqllimit text ); CREATE TABLE pfm_link ( linkname text NOT NULL, sqlwhere text, orderby text, displayattrib text, fromform text NOT NULL, toform text ); CREATE TABLE pfm_report ( name text NOT NULL, description text, sqlselect text ); CREATE TABLE pfm_section ( report text NOT NULL, "level" integer NOT NULL, fieldlist text, layout text, summary text, CONSTRAINT level_min_1 CHECK (("level" >= 1)) ); CREATE TABLE pfm_value ( value text NOT NULL, description text, valuelist text NOT NULL ); CREATE TABLE pfm_value_list ( name text NOT NULL ); CREATE TABLE pfm_version ( seqnr serial NOT NULL, version text, date text, "comment" text ); INSERT INTO pfm_version (version, "date", "comment") VALUES ('1.5.0', CURRENT_DATE, 'install_customerdb.sql'); CREATE TABLE product ( id serial NOT NULL, name text, description text, nr_in_stock integer, price numeric(8,2) ); SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence('product', 'id'), 7, true); COPY customer (id, name, street, post_code, town, country, telephone, e_mail) FROM stdin; 6 Nancy Verdonck Azalealaan 23 9520 Lokeren België 09 223 45 67 n.vcb@pi.be 8 Nestor Stassyns Calle Salinas 343 2000 Buenos Aires Argentina nestor.stassyns@argentel.ag 16 Cecile Verplancke Ternesselei 45 3500 Hasselt België 011 23 443 cepla@tiscali.be 9 Staf De Wilde Lange Lepelstraat 55 2345 Zwevezele België 03 233 62 60 3 Frans Van Langenacker Transvaalstraat 36 9000 Gent België 03 326 25 60 fvla@vt4.be 10 Dirk Versmissen Draaiboom 4 4576 Stekene België 03 465 32 67 dirkv@vt4.be 5 Jacky Verelst Kesselsebaan 45 7400 Sint-Niklaas België 03 234 56 32 jacky.verelst@skynet.be \. COPY invoice (id, customer, date) FROM stdin; 11 8 2003-12-17 12 8 2003-12-18 15 8 2004-01-13 16 10 2004-01-14 1 6 2003-11-07 6 3 2003-12-10 17 16 2004-12-03 18 16 2004-12-04 19 9 2004-12-04 20 5 2004-12-04 21 10 2004-12-06 \. COPY "order" (invoice, product, how_many) FROM stdin; 11 5 12 11 1 12 1 5 3 12 2 1 11 6 5 15 7 3 15 6 2 15 3 4 11 3 3 1 7 2 16 5 2 1 1 1 1 6 4 6 1 2 6 6 2 16 7 3 17 3 1 17 7 2 18 6 2 19 5 1 19 6 2 19 7 2 19 1 1 20 6 2 20 1 3 21 6 3 21 7 4 \. COPY pfm_attribute (attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default") FROM stdin; linkname taQuoted tgDirect 1 pfm_link none \N value taQuoted tgDirect \N 2 pfm_value none \N sqlwhere taQuoted tgDirect 4 pfm_link none \N orderby taQuoted tgDirect 5 pfm_link none \N displayattrib taQuoted tgDirect 6 pfm_link none \N description taQuoted tgDirect 3 pfm_value none \N name taQuoted tgDirect 1 pfm_report none \N description taQuoted tgDirect 2 pfm_report none \N report taQuoted tgLink select name, description from pfm_report order by name 1 pfm_section none \N nr taNotQuoted tgDirect 7 pfm_attribute none \N name taQuoted tgDirect 1 pfm_form none \N tablename taQuoted tgDirect 2 pfm_form none \N attribute taQuoted tgDirect 2 pfm_attribute none \N sqlselect taQuoted tgDirect 5 pfm_attribute none \N form taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 1 pfm_attribute none \N fromform taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 2 pfm_link none \N toform taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 3 pfm_link none \N valuelist taQuoted tgLink SELECT name FROM pfm_value_list ORDER BY name 1 pfm_value none \N name taQuoted tgDirect 1 pfm_value_list none \N sqlselect taQuoted tgDirect 3 pfm_report none \N sqlselect taQuoted tgDirect \N 4 pfm_form none \N sqlfrom taQuoted tgDirect \N 5 pfm_form none \N groupby taQuoted tgDirect 6 pfm_form none \N pkey taQuoted tgDirect 3 pfm_form none default taQuoted tgDirect 8 pfm_attribute none typeofattrib taQuoted tgList 3 pfm_attribute typeofattribute taQuoted typeofget taQuoted tgList \N 4 pfm_attribute typeofget tgDirect valuelist taQuoted tgLink SELECT name FROM pfm_value_list ORDER BY name 6 pfm_attribute none none id taNotQuoted tgDirect 1 customer none =SELECT nextval('customer_id_seq') street taQuoted tgDirect 3 customer none post_code taQuoted tgDirect 4 customer none town taQuoted tgDirect 5 customer none country taQuoted tgDirect 6 customer none telephone taQuoted tgDirect 7 customer none e_mail taQuoted tgDirect 8 customer none id taNotQuoted tgDirect 1 invoice none =SELECT nextval('invoice_id_seq') customer taNotQuoted tgLink SELECT id, name FROM customer ORDER BY name 2 invoice none date taQuoted tgDirect 3 invoice none =SELECT CURRENT_DATE name taQuoted tgReadOnly 4 invoice none id taNotQuoted tgDirect 1 product none =SELECT nextval('product_id_seq') name taQuoted tgDirect 2 product none description taQuoted tgDirect 3 product none nr_in_stock taNotQuoted tgDirect 4 product none price taNotQuoted tgExpression 5 product none how_many taNotQuoted tgDirect 7 order none amount taNotQuoted tgReadOnly 5 invoice none c_id taNotQuoted tgReadOnly 4 order none c_name taQuoted tgReadOnly 5 order none p_name taQuoted tgReadOnly 6 order none amount taNotQuoted tgReadOnly 8 order none product taNotQuoted tgLink SELECT id, name FROM product ORDER BY name 2 order none date taQuoted tgReadOnly 3 order none invoice taNotQuoted tgLink SELECT invoice.id, invoice."date",c.name\nFROM invoice LEFT OUTER JOIN customer c ON (invoice.customer = c.id)\nORDER BY invoice."date" DESC, c.name 1 order none nr_of_invoices taNotQuoted tgReadOnly 9 customer none name taQuoted tgDirect 2 customer none help taQuoted tgDirect 11 pfm_form none \N showform taQuoted tgList 9 pfm_form boolean t view taQuoted tgList 10 pfm_form boolean f sqlorderby taQuoted tgDirect 7 pfm_form none sqllimit taQuoted tgDirect 8 pfm_form none fieldlist taQuoted tgDirect 5 pfm_section none \N level taNotQuoted tgDirect 3 pfm_section none 1 layout taQuoted tgList 4 pfm_section layout table summary taQuoted tgDirect 6 pfm_section none sqlselect taQuoted tgReadOnly 2 pfm_section none \. COPY pfm_form (name, tablename, showform, "view", sqlselect, sqlfrom, groupby, help, pkey, sqlorderby, sqllimit) FROM stdin; product product t f id, name, description, nr_in_stock, price product id \N \N order order t f "order".invoice, "order".product, "order".how_many,\ni.date, i.customer AS c_id, c.name AS c_name, p.name AS p_name,\n(p.price * "order".how_many) AS amount "order"\n LEFT OUTER JOIN invoice i ON ("order".invoice = i.id)\n LEFT OUTER JOIN customer c ON (i.customer = c.id)\n LEFT OUTER JOIN product p ON ("order".product = p.id) invoice product \N \N invoice invoice t f invoice.id, invoice.customer, invoice.date, c.name,\nSUM(p.price * o.how_many)::numeric(9,2) AS amount invoice\n LEFT OUTER JOIN customer c ON (invoice.customer = c.id)\n LEFT OUTER JOIN "order" o ON (invoice.id = o.invoice)\n LEFT OUTER JOIN product p ON (o.product = p.id) invoice.id, invoice.customer, invoice.date, c.name id \N \N customer customer t f customer.id, customer.name, customer.street, customer.post_code, customer.town, customer.country, customer.telephone, customer.e_mail, COUNT(i.id) AS nr_of_invoices customer LEFT OUTER JOIN invoice i ON (customer.id = i.customer) customer.id, customer.name, customer.street, customer.post_code, customer.town, customer.country, customer.telephone, customer.e_mail id \N \N pfm_value pfm_value f f value, description, valuelist pfm_value \N The table "pfm_value" contains all the values of the lists defined in\npfm_value_list.\n\nIt has the following attributes:\n\n - valuelist : the name of the valuelist to which this value belongs\n\n - value : a character string;\n\n - description : a description of the value.\n valuelist value valuelist, value \N pfm_attribute pfm_attribute f f attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default" pfm_attribute \N The table "pfm_attribute" defines all the properties of form attributes.\n\nIt has the following attributes:\n\n - form : the "name" of the form to which the attribute\n belongs;\n\n - attribute : the name of the attribute; this must be equal\n to the name of the corresponding attribute of the form's SQL\n SELECT statement;\n\n - typeofattrib : the type of attribute:\n\n o taQuoted: the value provided by the user is put\n between single quotes when it is transferred to SQL\n UPDATE or INSERT statements;\n \n o taNotQuoted: the value provided by the user is not\n quoted when it is transferred to SQL UPDATE or INSERT\n statements.\n\n Hint: In general, all attribute values must be quoted, exept\n the values or expressions for numeric attributes.\n\n - typeofget: defines how the user provides a value for the\n attribute; possible values are:\n\n o tgDirect: the user types the value directly;\n\n o tgExpression: the user types an expression which is first\n evaluated before it is passed to SQL UPDATE or INSERT;\n\n Note: Even with tgDirect it is possible to enter an\n expression as new value for an attribute, but then\n the expression is evaluated by postgresql whereas\n with tgExpression, the expression is first evaluated\n by Tcl before the SQL statement is sent to\n postgresql.\n\n o tgList: the user selects a value by means of a list box\n containing a list of values defined in table "pfm_value";\n\n o tgLink: the user selects a value by means of a list box\n containing a list of values which is the result from a\n query on another table.\n\n o tgReadOnly: this attribute cannot be modified by\n the user.\n\n Note: All calculated attributes and all attributes from\n tables other than the form's main table should be\n declared 'read-only'. If this rule is not observed,\n the Add and Update operations on this form will fail.\n\n - sqlselect: the SQL SELECT statement which is used to fill the\n list box with possible values for the attribute (only meaningful\n if typeofget = tgLink).\n\n Note :\n\n o The sqlselect may return more than 1 attribute. If so, all\n the attributes are displayed in the list-box, but only the\n first one is used for updating the attribute.\n\n - valuelist : the "name" of the value list defined in table\n "pfm_value_list" (only meaningful if typeofget = tgList);\n\n - nr: a number which determines the order in which attributes are\n displayed on the form;\n\n - default: a default value for this attribute which is used when\n adding a record. If the first character is an '=' sign, the\n following characters should be an SQL SELECT statement which\n returns just one value.\n\n Example:\n\n default: =SELECT nextval('seq_person_id')\n\n In this example the default value is the next value of the\n sequenece 'seq_person_id'.\n form attribute form, nr \N pfm_link pfm_link f f linkname, sqlwhere, orderby, displayattrib, fromform, toform pfm_link \N A link is a navigation tool which allows you to follow a "one-to-many"\nor "many-to-one" relationship from one form to another.\n\nEvery link is stored as a record in the pfm_link table, which has the\nfollowing attributes:\n\n - linkname : the name of the link, which is displayed on\n a link button on the "fromform";\n\n - fromform : the name of the form from which the link\n originates;\n\n - toform : the name of the form to which the link leads;\n\n - sqlwhere : the "WHERE"-clause which is used to open the\n "toform" and in which the value of an attribute of the\n "fromform" may be represented by $(attrib-x), where\n 'attrib-x' is the name of the attribute;\n\n - orderby : an 'order by' clause which determines the order of the\n records in the 'toform';\n\n - displayattrib : a space separated list of\n attributes of the 'fromform', the value of which is displayed on\n the 'toform' to remind the user from which record the link\n originated.\n\nNote: Postgres Forms does not provide any checks to safeguard\n the referential integrity of the data base in case of updates or\n deletions. However, postgreSQL provides these functions as\n 'foreign key' table constraints (see postgreSQL documentation). fromform linkname fromform, linkname \N pfm_report pfm_report f f name, description, sqlselect pfm_report \N The table pfm_report defines all the reports for the current data\nbase.\n\npfm_report has the following attributes:\n\n - name: the name of the report. This is the name that\n appears in the selection list of the "Run Report" function.\n\n - description: free text describing the purpose of the\n report in more detail.\n\n - sqlselect: an SQL SELECT statement that generates the\n data for the report.\n\nThe sqlselect may contain one or more parameters for which a\nvalue is requested at "Run report" time. A parameter in the sqlwhere\nmust be formatted as $(parameter_name).\n\nExample:\n\nsqlselect: \n\n SELECT g.name AS "group", g.description, p.id, p.name,\n p.christian_name, p.street, p."ZIPcode", p.town, p.country\n FROM "group" g\n LEFT JOIN memberlist m ON g.name = m."group"\n LEFT JOIN person p ON m.person = p.id\n WHERE "group" = '$(group)'\n ORDER BY g.name, p.name, p.christian_name\n\nWhen the report is run, the user is prompted to enter a value for the\nparameter "group". Then the report data are generated by executing the\nsqlselect statement in which $(group) is replaced with the value\nentered by the user. name name \N pfm_value_list pfm_value_list f f name pfm_value_list \N The table "pfm_value_list" contains all the value lists of all the forms.\n\nIts only attribute is\n\n - name : a name uniquely identifying the value list.\n name name \N pfm_form pfm_form f f name, tablename, sqlselect, sqlfrom, groupby, showform, "view", help, pkey, sqlorderby, sqllimit pfm_form \N A form allows the user to administer the data of just one table. This\ntable is henceforth referred to as "the form's main table".\n\nHowever, a form also has an SQL SELECT statement, which generates the\ndata that are displayed on it.\n\nIn the simplest case the SQL SELECT statement is just:\n\n SELECT FROM
\n\nIn that case, the data which can be administered and the data which\nare displayed on the form are the same.\n\nIn more complex cases, the
can be JOINED with other\ntables, which makes it possible to display data of other related\ntables as well. These data cannot be modified by means of the form.\n\nThe table "pfm_form" has the following attributes:\n\n - name : the name of the form (usually equal to the name of\n the form's table);\n\n - tablename : the name of the form's main table;\n\n - pkey : the primary key of the form's main table, which may\n consist of more than one attribute. In that case pkey is a SPACE\n separated list of the attributes of the primary key;\n\n Note: If pkey is empty, the form is read-only, since pfm is\n unable to uniquely identify a record. You can use the\n 'oid' as primary key, but according to the PostgreSQL\n documentation that is not recommended, unless you set a\n UNIQUE constraint on the 'oid'.\n\n - sqlselect : the attribute list of the form's SQL SELECT\n statement, not including the word 'SELECT';\n\n - sqlfrom : the FROM clause of the form's SQL SELECT statement,\n not including the word 'FROM';\n\n - groupby : an optional 'GROUP BY' clause, not including the words\n 'GROUP BY';\n\n - sqlorderby : an optional 'ORDER BY' clause, not including the\n words 'ORDER BY';\n\n - sqllimit : an optional 'LIMIT' clause, only specifying the limit\n value as a positive integer;\n\n Notes:\n\n - This enables the designer of the form to avoid excessive\n memory usage by limiting the number of records loaded in\n the form's internal buffer. This may be useful for\n handling large tables.\n\n - If sqllimit is a positive integer, a\n\n LIMIT sqllimit OFFSET 0\n\n is added to the form's SELECT when opening the form.\n\n This means that only 'sqllimit' records are loaded into\n the form's internal buffer. When the user moves beyond the\n last record in the internal buffer, the internal buffer is\n first cleared and then reloaded with the next 'sqllimit'\n records by re-executing the form's SELECT but now with\n another OFFSET in the LIMIT clause.\n\n - If sqllimit is an empty string, no LIMIT clause is\n appended to the form's SELECT.\n\n - Always specify an 'sqlorderby' if you specify an\n 'sqllimit'. See PostgreSQL documentation of LIMIT-clause\n in SELECT statement for more details.\n\n - showform : a boolean indicating whether the form is shown\n in "normal mode" (showform = 'true') or in "design mode"\n (showform = 'false'). Typically, showform is set 'true' for user\n defined forms and 'false' for the predefined pfm_* forms.\n\n - view : a boolean indicating whether or not the\n "tablename" is a view;\n\n - help : a text which is displayed when the user presses\n the [Help] key on the form.\n\nThe form's main table is defined by tablename. Only the data of\nthat table can be administered by using the form.\n\nAll the data generated by the form's SQL SELECT statement can be\ndisplayed on the form. The SQL SELECT statement is defined by:\n\n - the sqlselect, sqlfrom, groupby, sqlorderby and sqllimit\n attributes of pfm_form; and\n\n - the optional WHERE and ORDER BY clauses provided by the user\n when opening the form.\n\nNote: The WHERE clause provided by the user when opening the form, becomes\n a HAVING clause, if there is a GROUP BY clause.\n\nThe following rules should be observed when filling out sqlselect and\nsqlfrom:\n\n 1. The form's main table must appear in 'sqlfrom', and must not be\n aliased. Similarly, the main table's attributes appearing in\n 'sqlselect' must not be aliased. The other tables appearing in\n the 'sqlfrom' may be aliased.\n\n 2. The fields appearing in 'sqlselect' must have a unique, simple\n name without the need to precede them with a tablename. So,\n calculated fields must be given a name by aliasing and\n attributes of tables other than the main table may need to be\n aliased in order to have a unique, simple name.\n\n 3. The 'sqlfrom' is either just the name of the form's main table,\n or it is a JOIN clause in which the 'LEFT' table is the form's\n main table. Several join clauses can be nested in order to\n involve more than 2 tables. See examples below.\n\n\nExample 1: the SQL SELECT for the person form of the addressbook database\n\n\ntablename:\n person\n\npkey:\n id\n\nsqlselect:\n id, christian_name, name, street, town, "ZIPcode",\n country, category, description\n\nsqlfrom:\n person\n\ngroupby:\n -\n\n\nExample 2: the SQL SELECT for the memberlist form of the addressbook database\n\n\ntablename:\n memberlist\n\npkey:\n group person\n\nsqlselect:\n memberlist."group", memberlist.person, p.christian_name, p.name\n\nsqlfrom:\n memberlist LEFT OUTER JOIN person p ON (p.id = memberlist.person)\n\ngroupby:\n - name showform DESC, name \N pfm_section pfm_section f f pfm_section.report, r.sqlselect, pfm_section."level", pfm_section.fieldlist, pfm_section.layout, pfm_section.summary pfm_section LEFT OUTER JOIN pfm_report r ON (pfm_section.report = r.name) \N The data returned by the report's SQL SELECT statement may be\nconsidered as a table with a column for each 'field' specified after\nthe word 'SELECT' and with a row for each record.\n\nBy specifying an 'ORDER BY' clause in the report's SQL SELECT\nstatement, it is possible to group rows with the same values for some\nfields together.\n\nThe report generator has an "economy" algorithm which avoids printing\nthe same data repeatedly.\n\nTo control this you have to distribute the fields (columns) of the\ntable over n sections such that section 1 contains the fields that are\nchanging least frequently (when moving from one row to the next),\nsection 2 contains the fields that are changing more frequently, and\nsection n contains the fields that are changing at every row.\n\nWhen the data of the first row of the table are printed, the data of\nsection 1 are printed first. Then, on the following line, indented by\none tab stop, the data of section 2 are printed. Then, on the\nfollowing line, indented by 2 tab stops, data of section 2 are\nprinted, etc.\n\n[section 1] <--- row 1\n\n [section 2] <--- row 1\n\n [section 3] <--- row 1\n\nThen, when the next rows are being printed, data of the lower numbered\nsections are only printed if they are different from the data of the\nlast printed section of the same number:\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 1\n [section 3] <--- row 2\n [section 3] <--- row 3\n\n [section 2]\n\n [section 3] <--- row 4\n [section 3] <--- row 5\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 6\n [section 3] <--- row 7\n\nThe report generator also enables you to print a summary at every\npoint where a higher numbered section is about to be followed by a\nlower numbered section:\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 1\n [section 3] <--- row 2\n [section 3] <--- row 3\n\n [summary 3]\n\n [section 2]\n\n [section 3] <--- row 4\n [section 3] <--- row 5\n\n [summary 3]\n\n [summary 2]\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 6\n [section 3] <--- row 7\n\n [summary 3]\n\n [summary 2]\n\n[summary 1]\n\nA summary i is printed just before a lower numbered section j (j < i).\nIts data can be calculated:\n\n - by applying one of the aggregate funtions: COUNT, SUM, AVG,\n STDDEV, MIN, MAX;\n\n - on the fields of the sections j (j >= i), between the last\n printed lower numbered section k (k < i), till the next (not\n yet printed) lower numbered section k (k < i).\n\nIn particular, summary 1 is printed at the end of the report, is\ncalculated from all the sections of the report and may be calculated\nfrom all the fields.\n\nA record in pfm_section defines a section and a summary of a report.\n\nThe table pfm_section has the following attributes:\n\n - report: the name of the report to which the section belongs\n\n - level: a number 1, 2, 3, 4, ... . The first level must be\n '1'. The next levels must be numbered consecutively. In the most\n simple report, there is only a section with level 1.\n\n - layout: can be "row", "column" or "table".\n\n - fieldlist: a space separated list of field specifiers,\n one for each field to be printed in the sections of this level\n (see below for details).\n\n - summary: a space separated list of summary field\n specifiers (see below for details).\n\nThe fieldlist is a SPACE separated list of field specifiers\n\n field_spec_1 field_spec_2 ... field_spec_N\n\nwhere each field specifier is formatted as follows:\n\n {field_i label_i alignment_i max_length_i}\n\nwhere :\n\n - field_i is the name of one of the columns returned by the\n report's SQL SELECT statement;\n\n - label_i is a string which has to be used as label for printing\n the i-th field of this section; if it consists of more than 1\n word, it must be delimited by double quotes (" .... ");\n\n - alignment_i is optional; if present, it is either l or r,\n indicating whether this field should be left or right aligned.\n\n - max_length_i is optional: if present, it is the maximum number\n of characters per line for printing the data of this field;\n lines longer than max_length_i will be wrapped by inserting\n one or more line breaks before printing.\n\n Notes :\n\n o The alignment is optional. If it is left out, left\n alignment is assumed by default.\n\n o The alignment only influences the table layout. Column and\n row layouts are unaffected by the alignment indicator.\n\n o Multi-line fields, i.e. fields containing more than one\n line of text are only formatted properly in a column or\n table layout.\n\n o For a table layout, pfm automatically calculates the column\n width that is required to display all data. So, normally\n you don't have to worry about column widths. However,\n sometimes, the data of a few records, make the columns\n excessively wide. That is where you might consider using\n "max_length_i" in the field specifier. If the data do not\n exceed that maximum, it won't have any effect.\n\n o Although 'alignment' and 'max_length' are both optional,\n you have to specify 'alignment' if you want to specify\n max_length.\n\nFor every section, the layout can be defined as:\n\n - row: the section's field labels and field values are\n printed in one row in a format: label_1 : value_1; label_2 :\n value_2; ... etc.\n\n - column: the section's field labels are printed in a first\n column, the section's field values are printed in a second column.\n\n - table: the section's values are printed in a table with a\n column per field and a row per record, the section's field\n labels are used as column headers for the table.\n\nThe summary must be formatted as a space separated list of summary\nspecifiers:\n\n summary_spec_1 summary_sepc_2 .... summary_sepc_N\n\nwhere each summary_spec is formatted as follows:\n\n {field_i aggregate_i format_i}\n\nwhere:\n\n - field_i is the name of a field defined in the fieldlist of\n either this section, or another, higher numbered section;\n\n - aggregate_i is one of the aggregate functions: COUNT, SUM, AVG,\n STDDEV, MIN, MAX (see below for details); and\n\n - format_i is an optional 'ANSI C sprintf' formatting string (see\n below for details). If it is left out, the number is printed\n with maximum precision.\n\n\nAggregate functions:\n\nIn general, the aggregate functions, use the same "economy" algorithm\nthat is used for printing section data.\n\nWhen all the fields of a section, which is not the highest numbered\nsection of the report, have the same values for a number of\nconsecutive rows, this section's data are only printed once for these\nrows.\n\nSimilarly, these rows are only counted once by the aggregate functions\napplied to a field of this section.\n\nThe aggregate functions that can be used in a summary are:\n\n - COUNT: Counts the number of rows. In this case, the field_i that\n is specified only determines which section is counted.\n\n - SUM: Calculates the sum of all the values of the specified\n field.\n\n - AVG: Calculates the average of the values of the specified\n field.\n\n - STDDEV: Calculates the sample standard deviation for the values of the\n specified field:\n\n SQRT (SUM( (value_i - AVG(value))**2 ) / (N - 1))\n\n\t where :\n\n - value_1, value_2, ... value_N are the values of the\n considered field;\n\n - AVG(value) is the average of the considered values;\n\n - N is the number of values.\n\n - MIN: Calculates the minimum of the values of the specified\n field.\n\n - MAX: Calculated the maximum of the values of the specified\n field.\n\n\n'ANSI C sprintf' formatting string:\n\nHere is a short overview of the 'ANSI C sprintf' formatting string. In\ngeneral its form is:\n\n %'MinWidth'.'Precision''Conversion'\n\nwhere:\n\n - 'MinWidth' is an integer defining the minimum width (as number\n of characters) for the number to be printed. If the number does\n not need so much space, spaces are inserted in front of the\n number, unless MinWidth is negative. In that case, spaces are\n appended at the end. If the number needs more space than\n MinWidth, more space is used.\n\n - 'Precision' is an integer defining how many digits to print\n after the decimal point, or, in the case of g or G conversion,\n the total number of digits to appear, including those on both\n sides of the decimal point\n\n - 'Conversion' is one of:\n\n o d : convert integer to signed decimal string. In this case,\n there is no need to define a 'Precision'.\n\n Example: %1d\n\n prints an integer and uses as many characters\n as required.\n\n o f : convert floating point number to fixed point\n notation. In this case, 'Precision' defines the number\n of digits to print after the decimal point. If there\n are not enough digits available, trailing zeroes are\n appended.\n\n Example: %1.2f\n\n prints a floating point number wiht 2 digits\n after the decimal point and uses as many\n characters as required.\n\n o e or E : Convert floating-point number to scientific\n notation in the form x.yyye±zz, where the number of\n y's is determined by the 'Precision' (default: 6). If\n the precision is 0 then no decimal point is output. If\n the E form is used then E is printed instead of e.\n\n Example: %1.5E\n\n prints a floating point number in the form\n x.yyyyy E±zz \n\n o g or G : If the exponent is less than -4 or greater than\n or equal to the precision, then convert floating-point\n number as for %e or %E. Otherwise convert as for\n %f. Trailing zeroes and a trailing decimal point are\n omitted. In this case the 'Precision' specifies the\n total number of digits to appear, including those on\n both sides of the decimal point\n\n Example: %1.4G\n\n prints 2345.0 as 2345\n prints 234567.0 as 2.346E+05\n prints 0.003456 as 0.003456\n prints 0.00003456 as 3.456E-05 report level report, "level" \N \. COPY pfm_link (linkname, sqlwhere, orderby, displayattrib, fromform, toform) FROM stdin; Report name='$(report)' level pfm_section pfm_report Sections report='$(name)' level name pfm_report pfm_section Attributes form='$(name)' nr name pfm_form pfm_attribute incoming links toform='$(name)' fromform name pfm_form pfm_link outgoing links fromform='$(name)' toform name pfm_form pfm_link Where used? valuelist='$(name)' name pfm_value_list pfm_attribute Values valuelist='$(name)' value name pfm_value_list pfm_value Value list name='$(valuelist)' attribute pfm_attribute pfm_value_list from Form name='$(fromform)' linkname pfm_link pfm_form to Form name='$(toform)' linkname pfm_link pfm_form Valuelist name='$(valuelist)' value pfm_value pfm_value_list Form name='$(form)' attribute pfm_attribute pfm_form Invoices invoice.customer = $(id) "date" DESC id name customer invoice Invoice invoice.id = $(invoice) order invoice Orders "order".product = $(id) id name product order Product id = $(product) invoice c_name order product Customer customer.id = $(customer) id date invoice customer Orders "order".invoice = $(id) id date name invoice order \. COPY pfm_report (name, description, sqlselect) FROM stdin; Invoices Invoices since a certain date SELECT i.id, i."date", i.customer, c.name AS c_name, c.street, c.town, c.country,\n o.product, p.name AS p_name, p.price, o.how_many,\n (p.price * o.how_many)::numeric(9,2) AS amount\nFROM invoice i\n LEFT OUTER JOIN customer c ON (i.customer = c.id)\n LEFT OUTER JOIN "order" o ON (i.id = o.invoice)\n LEFT OUTER JOIN product p ON (o.product = p.id)\nWHERE i."date" >= '$(since)'\nORDER BY c.name, i."date" DefinedForms Report showing the form definitions of this database SELECT f.name, f.tablename, f.sqlselect AS "SELECT", f.sqlfrom AS "FROM",\n f.groupby AS "GROUP BY", f.sqlorderby AS "ORDER BY",\n f.sqllimit AS "LIMIT", f.pkey AS "PRIM. KEY", f.showform, f."view",\n a.attribute, a.typeofattrib, a.typeofget, a.sqlselect, a.nr,\n a.valuelist, a."default"\nFROM pfm_form f LEFT OUTER JOIN pfm_attribute a ON (f.name = a.form)\nORDER BY f.showform DESC, f.name, a.nr DefinedLinks Report showing the link definitions of this database SELECT linkname, sqlwhere, orderby, displayattrib, fromform , toform\nFROM pfm_link\nORDER BY fromform, linkname FormHelp Report showing the on-line help for forms defined in this database SELECT name, help\nFROM pfm_form\nWHERE help <> ''\nORDER BY showform DESC, name DefinedReports Report showing the report definitions of this database SELECT r.name, r.description, r.sqlselect,\n s."level", s.fieldlist, s.layout, s.summary\nFROM pfm_report r LEFT OUTER JOIN pfm_section s ON (r.name = s.report)\nORDER BY r.name \. COPY pfm_section (report, "level", fieldlist, layout, summary) FROM stdin; Invoices 1 {customer CustNr l} {c_name Customer l} {street Street l} {town Town l} {country Country l} row {customer COUNT} Invoices 2 {id Invoice l} {date Date l} row {amount SUM %1.2f} {id COUNT %1d} Invoices 3 {product pNr r} {p_name Product l} {price Price r} {how_many HowMany r} {amount Amount r} table {amount SUM %1.2f} DefinedForms 1 {name Form l} {tablename Table l} {SELECT SELECT l 70} {FROM FROM l} {"GROUP BY" "GROUP BY" l 70} {"ORDER BY" "ORDER BY" l} {LIMIT LIMIT l} {"PRIM. KEY" "PRIM. KEY" l} {showform showform l} {view view l} column DefinedLinks 2 {linkname linkname l} {toform toform l} {sqlwhere sqlwhere l 30} {orderby orderby l 30} {displayattrib displayattrib l} table {linkname COUNT} DefinedLinks 1 {fromform "Links from form" l} row {fromform COUNT} DefinedReports 1 {name Report} {description Description} {sqlselect SQL} column DefinedReports 2 {level Section r} {fieldlist fieldlist l 40} {layout layout l} {summary summary l 30} table FormHelp 1 {name Form} row FormHelp 2 {help "On line help" l 80} table DefinedForms 2 {nr nr r} {attribute attribute l} {typeofattrib typeofattrib l} {typeofget typeofget l} {sqlselect sqlselect l 30} {valuelist valuelist l} {default default l 30} table \. COPY pfm_value (value, description, valuelist) FROM stdin; taQuoted Value must be enclosed in ' ' for SQL. typeofattribute taNotQuoted Value must not be enclosed in ' ' for SQL. typeofattribute tgDirect Value directly typed by user. typeofget tgExpression Value may be given as an expression. typeofget tgList Value comes from a valuelist. typeofget tgLink Value comes from 'sqlselect'. typeofget t TRUE boolean f FALSE boolean column A column for the labels, a second column for the corresponding values layout table A table with the labels as table header layout row Labels and values on 1 row layout tgReadOnly User cannot change the value of this attribute typeofget \. COPY pfm_value_list (name) FROM stdin; typeofattribute typeofget boolean layout none \. COPY product (id, name, description, nr_in_stock, price) FROM stdin; 2 SuSE 8.2 professional Linux distribution 12 81.50 3 Windows XP - new PC Microsoft windows XP for new PC 12 99.50 4 Windows XP upgrade Microsoft windows not bound to a particular PC 3 399.00 5 Tcl/Tk Tool Command Language and Toolkit 12 12.30 7 Open Office CD met Office pakekt voor Windows en Linux 12 10.00 1 SuSE 8.2 personal Linux distribution 12 23.20 6 PostgreSQL Relational database server 23 15.60 \. ALTER TABLE ONLY customer ADD CONSTRAINT customer_pkey PRIMARY KEY (id); ALTER TABLE ONLY invoice ADD CONSTRAINT invoice_pkey PRIMARY KEY (id); ALTER TABLE ONLY "order" ADD CONSTRAINT order_pkey PRIMARY KEY (invoice, product); ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT pfm_attribute_pkey PRIMARY KEY (form, attribute); ALTER TABLE ONLY pfm_form ADD CONSTRAINT pfm_form_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_link ADD CONSTRAINT pfm_link_pkey PRIMARY KEY (fromform, linkname); ALTER TABLE ONLY pfm_report ADD CONSTRAINT pfm_report_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_section ADD CONSTRAINT pfm_section_pkey PRIMARY KEY (report, "level"); ALTER TABLE ONLY pfm_value_list ADD CONSTRAINT pfm_value_list_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_value ADD CONSTRAINT pfm_value_pkey PRIMARY KEY (valuelist, value); ALTER TABLE ONLY pfm_version ADD CONSTRAINT pfm_version_pkey PRIMARY KEY (seqnr); ALTER TABLE ONLY product ADD CONSTRAINT product_pkey PRIMARY KEY (id); ALTER TABLE ONLY invoice ADD CONSTRAINT ref_customer FOREIGN KEY (customer) REFERENCES customer(id) ON UPDATE CASCADE ON DELETE RESTRICT; ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT ref_form FOREIGN KEY (form) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_link ADD CONSTRAINT ref_fromform FOREIGN KEY (fromform) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY "order" ADD CONSTRAINT ref_invoice FOREIGN KEY (invoice) REFERENCES invoice(id) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_value ADD CONSTRAINT ref_list FOREIGN KEY (valuelist) REFERENCES pfm_value_list(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY "order" ADD CONSTRAINT ref_product FOREIGN KEY (product) REFERENCES product(id) ON UPDATE CASCADE ON DELETE RESTRICT; ALTER TABLE ONLY pfm_section ADD CONSTRAINT ref_sections FOREIGN KEY (report) REFERENCES pfm_report(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_link ADD CONSTRAINT ref_toform FOREIGN KEY (toform) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT ref_value_list FOREIGN KEY (valuelist) REFERENCES pfm_value_list(name) ON UPDATE CASCADE ON DELETE RESTRICT; pfm-2.0.8/install_pfm.sql0000644000175000017500000007463111472160416013523 0ustar wimwim-- install_pfm.sql -- Version 1.5.0 -- The character encoding of this file is iso8859-1 (latin1). -- When imported via the 'Tools -> install pfm_* tables' menu, pfm -- converts it to UTF-8 before offering it to psql, which is always -- running with client_encoding 'UNICODE(=UTF-8)' when invoked by pfm. CREATE TABLE pfm_version ( seqnr serial NOT NULL, version text, date date, "comment" text ); INSERT INTO pfm_version (version, "date", comment) VALUES ('1.5.0', CURRENT_DATE, 'install_pfm.sql'); CREATE TABLE pfm_attribute ( attribute text NOT NULL, typeofattrib text, typeofget text, sqlselect text, nr integer, form text NOT NULL, valuelist text, "default" text ); CREATE TABLE pfm_form ( name text NOT NULL, tablename text, showform boolean DEFAULT true, "view" boolean DEFAULT false, sqlselect text, sqlfrom text, groupby text, help text, pkey text, sqlorderby text, sqllimit text ); CREATE TABLE pfm_link ( linkname text NOT NULL, sqlwhere text, orderby text, displayattrib text, fromform text NOT NULL, toform text ); CREATE TABLE pfm_report ( name text NOT NULL, description text, sqlselect text ); CREATE TABLE pfm_section ( report text NOT NULL, "level" integer NOT NULL, fieldlist text, layout text, summary text, CONSTRAINT level_min_1 CHECK (("level" >= 1)) ); CREATE TABLE pfm_value ( value text NOT NULL, description text, valuelist text NOT NULL ); CREATE TABLE pfm_value_list ( name text NOT NULL ); COPY pfm_attribute (attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default") FROM stdin; linkname taQuoted tgDirect 1 pfm_link none \N value taQuoted tgDirect \N 2 pfm_value none \N sqlwhere taQuoted tgDirect 4 pfm_link none \N orderby taQuoted tgDirect 5 pfm_link none \N displayattrib taQuoted tgDirect 6 pfm_link none \N description taQuoted tgDirect 3 pfm_value none \N name taQuoted tgDirect 1 pfm_report none \N description taQuoted tgDirect 2 pfm_report none \N report taQuoted tgLink select name, description from pfm_report order by name 1 pfm_section none \N nr taNotQuoted tgDirect 7 pfm_attribute none \N name taQuoted tgDirect 1 pfm_form none \N tablename taQuoted tgDirect 2 pfm_form none \N attribute taQuoted tgDirect 2 pfm_attribute none \N sqlselect taQuoted tgDirect 5 pfm_attribute none \N form taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 1 pfm_attribute none \N fromform taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 2 pfm_link none \N toform taQuoted tgLink SELECT name FROM pfm_form ORDER BY name 3 pfm_link none \N valuelist taQuoted tgLink SELECT name FROM pfm_value_list ORDER BY name 1 pfm_value none \N name taQuoted tgDirect 1 pfm_value_list none \N sqlselect taQuoted tgDirect 3 pfm_report none \N sqlselect taQuoted tgDirect \N 4 pfm_form none \N sqlfrom taQuoted tgDirect \N 5 pfm_form none \N groupby taQuoted tgDirect 6 pfm_form none \N pkey taQuoted tgDirect 3 pfm_form none default taQuoted tgDirect 8 pfm_attribute none typeofattrib taQuoted tgList 3 pfm_attribute typeofattribute taQuoted typeofget taQuoted tgList \N 4 pfm_attribute typeofget tgDirect valuelist taQuoted tgLink SELECT name FROM pfm_value_list ORDER BY name 6 pfm_attribute none none help taQuoted tgDirect 11 pfm_form none \N showform taQuoted tgList 9 pfm_form boolean t view taQuoted tgList 10 pfm_form boolean f sqlorderby taQuoted tgDirect 7 pfm_form none sqllimit taQuoted tgDirect 8 pfm_form none summary taQuoted tgDirect 6 pfm_section none fieldlist taQuoted tgDirect 5 pfm_section none \N layout taQuoted tgList 4 pfm_section layout table level taNotQuoted tgDirect 3 pfm_section none 1 sqlselect taQuoted tgReadOnly 2 pfm_section none \. COPY pfm_form (name, tablename, showform, "view", sqlselect, sqlfrom, groupby, help, pkey, sqlorderby, sqllimit) FROM stdin; pfm_attribute pfm_attribute f f attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default" pfm_attribute \N The table "pfm_attribute" defines all the properties of form attributes.\n\nIt has the following attributes:\n\n - form : the "name" of the form to which the attribute\n belongs;\n\n - attribute : the name of the attribute; this must be equal\n to the name of the corresponding attribute of the form's SQL\n SELECT statement;\n\n - typeofattrib : the type of attribute:\n\n o taQuoted: the value provided by the user is put\n between single quotes when it is transferred to SQL\n UPDATE or INSERT statements;\n \n o taNotQuoted: the value provided by the user is not\n quoted when it is transferred to SQL UPDATE or INSERT\n statements.\n\n Hint: In general, all attribute values must be quoted, exept\n the values or expressions for numeric attributes.\n\n - typeofget: defines how the user provides a value for the\n attribute; possible values are:\n\n o tgDirect: the user types the value directly;\n\n o tgExpression: the user types an expression which is first\n evaluated before it is passed to SQL UPDATE or INSERT;\n\n Note: Even with tgDirect it is possible to enter an\n expression as new value for an attribute, but then\n the expression is evaluated by postgresql whereas\n with tgExpression, the expression is first evaluated\n by Tcl before the SQL statement is sent to\n postgresql.\n\n o tgList: the user selects a value by means of a list box\n containing a list of values defined in table "pfm_value";\n\n o tgLink: the user selects a value by means of a list box\n containing a list of values which is the result from a\n query on another table.\n\n o tgReadOnly: this attribute cannot be modified by\n the user.\n\n Note: All calculated attributes and all attributes from\n tables other than the form's main table should be\n declared 'read-only'. If this rule is not observed,\n the Add and Update operations on this form will fail.\n\n - sqlselect: the SQL SELECT statement which is used to fill the\n list box with possible values for the attribute (only meaningful\n if typeofget = tgLink).\n\n Note :\n\n o The sqlselect may return more than 1 attribute. If so, all\n the attributes are displayed in the list-box, but only the\n first one is used for updating the attribute.\n\n - valuelist : the "name" of the value list defined in table\n "pfm_value_list" (only meaningful if typeofget = tgList);\n\n - nr: a number which determines the order in which attributes are\n displayed on the form;\n\n - default: a default value for this attribute which is used when\n adding a record. If the first character is an '=' sign, the\n following characters should be an SQL SELECT statement which\n returns just one value.\n\n Example:\n\n default: =SELECT nextval('seq_person_id')\n\n In this example the default value is the next value of the\n sequenece 'seq_person_id'.\n form attribute form, nr \N pfm_link pfm_link f f linkname, sqlwhere, orderby, displayattrib, fromform, toform pfm_link \N A link is a navigation tool which allows you to follow a "one-to-many"\nor "many-to-one" relationship from one form to another.\n\nEvery link is stored as a record in the pfm_link table, which has the\nfollowing attributes:\n\n - linkname : the name of the link, which is displayed on\n a link button on the "fromform";\n\n - fromform : the name of the form from which the link\n originates;\n\n - toform : the name of the form to which the link leads;\n\n - sqlwhere : the "WHERE"-clause which is used to open the\n "toform" and in which the value of an attribute of the\n "fromform" may be represented by $(attrib-x), where\n 'attrib-x' is the name of the attribute;\n\n - orderby : an 'order by' clause which determines the order of the\n records in the 'toform';\n\n - displayattrib : a space separated list of\n attributes of the 'fromform', the value of which is displayed on\n the 'toform' to remind the user from which record the link\n originated.\n\nNote: Postgres Forms does not provide any checks to safeguard\n the referential integrity of the data base in case of updates or\n deletions. However, postgreSQL provides these functions as\n 'foreign key' table constraints (see postgreSQL documentation). fromform linkname fromform, linkname \N pfm_report pfm_report f f name, description, sqlselect pfm_report \N The table pfm_report defines all the reports for the current data\nbase.\n\npfm_report has the following attributes:\n\n - name: the name of the report. This is the name that\n appears in the selection list of the "Run Report" function.\n\n - description: free text describing the purpose of the\n report in more detail.\n\n - sqlselect: an SQL SELECT statement that generates the\n data for the report.\n\nThe sqlselect may contain one or more parameters for which a\nvalue is requested at "Run report" time. A parameter in the sqlwhere\nmust be formatted as $(parameter_name).\n\nExample:\n\nsqlselect: \n\n SELECT g.name AS "group", g.description, p.id, p.name,\n p.christian_name, p.street, p."ZIPcode", p.town, p.country\n FROM "group" g\n LEFT JOIN memberlist m ON g.name = m."group"\n LEFT JOIN person p ON m.person = p.id\n WHERE "group" = '$(group)'\n ORDER BY g.name, p.name, p.christian_name\n\nWhen the report is run, the user is prompted to enter a value for the\nparameter "group". Then the report data are generated by executing the\nsqlselect statement in which $(group) is replaced with the value\nentered by the user. name name \N pfm_value pfm_value f f value, description, valuelist pfm_value \N The table "pfm_value" contains all the values of the lists defined in\npfm_value_list.\n\nIt has the following attributes:\n\n - valuelist : the name of the valuelist to which this value belongs\n\n - value : a character string;\n\n - description : a description of the value.\n valuelist value valuelist, value \N pfm_value_list pfm_value_list f f name pfm_value_list \N The table "pfm_value_list" contains all the value lists of all the forms.\n\nIts only attribute is\n\n - name : a name uniquely identifying the value list.\n name name \N pfm_form pfm_form f f name, tablename, sqlselect, sqlfrom, groupby, showform, "view", help, pkey, sqlorderby, sqllimit pfm_form \N A form allows the user to administer the data of just one table. This\ntable is henceforth referred to as "the form's main table".\n\nHowever, a form also has an SQL SELECT statement, which generates the\ndata that are displayed on it.\n\nIn the simplest case the SQL SELECT statement is just:\n\n SELECT FROM
\n\nIn that case, the data which can be administered and the data which\nare displayed on the form are the same.\n\nIn more complex cases, the
can be JOINED with other\ntables, which makes it possible to display data of other related\ntables as well. These data cannot be modified by means of the form.\n\nThe table "pfm_form" has the following attributes:\n\n - name : the name of the form (usually equal to the name of\n the form's table);\n\n - tablename : the name of the form's main table;\n\n - pkey : the primary key of the form's main table, which may\n consist of more than one attribute. In that case pkey is a SPACE\n separated list of the attributes of the primary key;\n\n Note: If pkey is empty, the form is read-only, since pfm is\n unable to uniquely identify a record. You can use the\n 'oid' as primary key, but according to the PostgreSQL\n documentation that is not recommended, unless you set a\n UNIQUE constraint on the 'oid'.\n\n - sqlselect : the attribute list of the form's SQL SELECT\n statement, not including the word 'SELECT';\n\n - sqlfrom : the FROM clause of the form's SQL SELECT statement,\n not including the word 'FROM';\n\n - groupby : an optional 'GROUP BY' clause, not including the words\n 'GROUP BY';\n\n - sqlorderby : an optional 'ORDER BY' clause, not including the\n words 'ORDER BY';\n\n - sqllimit : an optional 'LIMIT' clause, only specifying the limit\n value as a positive integer;\n\n Notes:\n\n - This enables the designer of the form to avoid excessive\n memory usage by limiting the number of records loaded in\n the form's internal buffer. This may be useful for\n handling large tables.\n\n - If sqllimit is a positive integer, a\n\n LIMIT sqllimit OFFSET 0\n\n is added to the form's SELECT when opening the form.\n\n This means that only 'sqllimit' records are loaded into\n the form's internal buffer. When the user moves beyond the\n last record in the internal buffer, the internal buffer is\n first cleared and then reloaded with the next 'sqllimit'\n records by re-executing the form's SELECT but now with\n another OFFSET in the LIMIT clause.\n\n - If sqllimit is an empty string, no LIMIT clause is\n appended to the form's SELECT.\n\n - Always specify an 'sqlorderby' if you specify an\n 'sqllimit'. See PostgreSQL documentation of LIMIT-clause\n in SELECT statement for more details.\n\n - showform : a boolean indicating whether the form is shown\n in "normal mode" (showform = 'true') or in "design mode"\n (showform = 'false'). Typically, showform is set 'true' for user\n defined forms and 'false' for the predefined pfm_* forms.\n\n - view : a boolean indicating whether or not the\n "tablename" is a view;\n\n - help : a text which is displayed when the user presses\n the [Help] key on the form.\n\nThe form's main table is defined by tablename. Only the data of\nthat table can be administered by using the form.\n\nAll the data generated by the form's SQL SELECT statement can be\ndisplayed on the form. The SQL SELECT statement is defined by:\n\n - the sqlselect, sqlfrom, groupby, sqlorderby and sqllimit\n attributes of pfm_form; and\n\n - the optional WHERE and ORDER BY clauses provided by the user\n when opening the form.\n\nNote: The WHERE clause provided by the user when opening the form, becomes\n a HAVING clause, if there is a GROUP BY clause.\n\nThe following rules should be observed when filling out sqlselect and\nsqlfrom:\n\n 1. The form's main table must appear in 'sqlfrom', and must not be\n aliased. Similarly, the main table's attributes appearing in\n 'sqlselect' must not be aliased. The other tables appearing in\n the 'sqlfrom' may be aliased.\n\n 2. The fields appearing in 'sqlselect' must have a unique, simple\n name without the need to precede them with a tablename. So,\n calculated fields must be given a name by aliasing and\n attributes of tables other than the main table may need to be\n aliased in order to have a unique, simple name.\n\n 3. The 'sqlfrom' is either just the name of the form's main table,\n or it is a JOIN clause in which the 'LEFT' table is the form's\n main table. Several join clauses can be nested in order to\n involve more than 2 tables. See examples below.\n\n\nExample 1: the SQL SELECT for the person form of the addressbook database\n\n\ntablename:\n person\n\npkey:\n id\n\nsqlselect:\n id, christian_name, name, street, town, "ZIPcode",\n country, category, description\n\nsqlfrom:\n person\n\ngroupby:\n -\n\n\nExample 2: the SQL SELECT for the memberlist form of the addressbook database\n\n\ntablename:\n memberlist\n\npkey:\n group person\n\nsqlselect:\n memberlist."group", memberlist.person, p.christian_name, p.name\n\nsqlfrom:\n memberlist LEFT OUTER JOIN person p ON (p.id = memberlist.person)\n\ngroupby:\n - name showform DESC, name \N pfm_section pfm_section f f pfm_section.report, r.sqlselect, pfm_section."level", pfm_section.fieldlist,\npfm_section.layout, pfm_section.summary pfm_section LEFT OUTER JOIN pfm_report r ON (pfm_section.report = r.name) \N The data returned by the report's SQL SELECT statement may be\nconsidered as a table with a column for each 'field' specified after\nthe word 'SELECT' and with a row for each record.\n\nBy specifying an 'ORDER BY' clause in the report's SQL SELECT\nstatement, it is possible to group rows with the same values for some\nfields together.\n\nThe report generator has an "economy" algorithm which avoids printing\nthe same data repeatedly.\n\nTo control this you have to distribute the fields (columns) of the\ntable over n sections such that section 1 contains the fields that are\nchanging least frequently (when moving from one row to the next),\nsection 2 contains the fields that are changing more frequently, and\nsection n contains the fields that are changing at every row.\n\nWhen the data of the first row of the table are printed, the data of\nsection 1 are printed first. Then, on the following line, indented by\none tab stop, the data of section 2 are printed. Then, on the\nfollowing line, indented by 2 tab stops, data of section 2 are\nprinted, etc.\n\n[section 1] <--- row 1\n\n [section 2] <--- row 1\n\n [section 3] <--- row 1\n\nThen, when the next rows are being printed, data of the lower numbered\nsections are only printed if they are different from the data of the\nlast printed section of the same number:\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 1\n [section 3] <--- row 2\n [section 3] <--- row 3\n\n [section 2]\n\n [section 3] <--- row 4\n [section 3] <--- row 5\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 6\n [section 3] <--- row 7\n\nThe report generator also enables you to print a summary at every\npoint where a higher numbered section is about to be followed by a\nlower numbered section:\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 1\n [section 3] <--- row 2\n [section 3] <--- row 3\n\n [summary 3]\n\n [section 2]\n\n [section 3] <--- row 4\n [section 3] <--- row 5\n\n [summary 3]\n\n [summary 2]\n\n[section 1]\n\n [section 2]\n\n [section 3] <--- row 6\n [section 3] <--- row 7\n\n [summary 3]\n\n [summary 2]\n\n[summary 1]\n\nA summary i is printed just before a lower numbered section j (j < i).\nIts data can be calculated:\n\n - by applying one of the aggregate funtions: COUNT, SUM, AVG,\n STDDEV, MIN, MAX;\n\n - on the fields of the sections j (j >= i), between the last\n printed lower numbered section k (k < i), till the next (not\n yet printed) lower numbered section k (k < i).\n\nIn particular, summary 1 is printed at the end of the report, is\ncalculated from all the sections of the report and may be calculated\nfrom all the fields.\n\nA record in pfm_section defines a section and a summary of a report.\n\nThe table pfm_section has the following attributes:\n\n - report: the name of the report to which the section belongs\n\n - level: a number 1, 2, 3, 4, ... . The first level must be\n '1'. The next levels must be numbered consecutively. In the most\n simple report, there is only a section with level 1.\n\n - layout: can be "row", "column" or "table".\n\n - fieldlist: a space separated list of field specifiers,\n one for each field to be printed in the sections of this level\n (see below for details).\n\n - summary: a space separated list of summary field\n specifiers (see below for details).\n\nThe fieldlist is a SPACE separated list of field specifiers\n\n field_spec_1 field_spec_2 ... field_spec_N\n\nwhere each field specifier is formatted as follows:\n\n {field_i label_i alignment_i max_length_i}\n\nwhere :\n\n - field_i is the name of one of the columns returned by the\n report's SQL SELECT statement;\n\n - label_i is a string which has to be used as label for printing\n the i-th field of this section; if it consists of more than 1\n word, it must be delimited by double quotes (" .... ");\n\n - alignment_i is optional; if present, it is either l or r,\n indicating whether this field should be left or right aligned.\n\n - max_length_i is optional: if present, it is the maximum number\n of characters per line for printing the data of this field;\n lines longer than max_length_i will be wrapped by inserting\n one or more line breaks before printing.\n\n Notes :\n\n o The alignment is optional. If it is left out, left\n alignment is assumed by default.\n\n o The alignment only influences the table layout. Column and\n row layouts are unaffected by the alignment indicator.\n\n o Multi-line fields, i.e. fields containing more than one\n line of text are only formatted properly in a column or\n table layout.\n\n o For a table layout, pfm automatically calculates the column\n width that is required to display all data. So, normally\n you don't have to worry about column widths. However,\n sometimes, the data of a few records, make the columns\n excessively wide. That is where you might consider using\n "max_length_i" in the field specifier. If the data do not\n exceed that maximum, it won't have any effect.\n\n o Although 'alignment' and 'max_length' are both optional,\n you have to specify 'alignment' if you want to specify\n max_length.\n\nFor every section, the layout can be defined as:\n\n - row: the section's field labels and field values are\n printed in one row in a format: label_1 : value_1; label_2 :\n value_2; ... etc.\n\n - column: the section's field labels are printed in a first\n column, the section's field values are printed in a second column.\n\n - table: the section's values are printed in a table with a\n column per field and a row per record, the section's field\n labels are used as column headers for the table.\n\nThe summary must be formatted as a space separated list of summary\nspecifiers:\n\n summary_spec_1 summary_sepc_2 .... summary_sepc_N\n\nwhere each summary_spec is formatted as follows:\n\n {field_i aggregate_i format_i}\n\nwhere:\n\n - field_i is the name of a field defined in the fieldlist of\n either this section, or another, higher numbered section;\n\n - aggregate_i is one of the aggregate functions: COUNT, SUM, AVG,\n STDDEV, MIN, MAX (see below for details); and\n\n - format_i is an optional 'ANSI C sprintf' formatting string (see\n below for details). If it is left out, the number is printed\n with maximum precision.\n\n\nAggregate functions:\n\nIn general, the aggregate functions, use the same "economy" algorithm\nthat is used for printing section data.\n\nWhen all the fields of a section, which is not the highest numbered\nsection of the report, have the same values for a number of\nconsecutive rows, this section's data are only printed once for these\nrows.\n\nSimilarly, these rows are only counted once by the aggregate functions\napplied to a field of this section.\n\nThe aggregate functions that can be used in a summary are:\n\n - COUNT: Counts the number of rows. In this case, the field_i that\n is specified only determines which section is counted.\n\n - SUM: Calculates the sum of all the values of the specified\n field.\n\n - AVG: Calculates the average of the values of the specified\n field.\n\n - STDDEV: Calculates the sample standard deviation for the values of the\n specified field:\n\n SQRT (SUM( (value_i - AVG(value))**2 ) / (N - 1))\n\n\t where :\n\n - value_1, value_2, ... value_N are the values of the\n considered field;\n\n - AVG(value) is the average of the considered values;\n\n - N is the number of values.\n\n - MIN: Calculates the minimum of the values of the specified\n field.\n\n - MAX: Calculated the maximum of the values of the specified\n field.\n\n\n'ANSI C sprintf' formatting string:\n\nHere is a short overview of the 'ANSI C sprintf' formatting string. In\ngeneral its form is:\n\n %'MinWidth'.'Precision''Conversion'\n\nwhere:\n\n - 'MinWidth' is an integer defining the minimum width (as number\n of characters) for the number to be printed. If the number does\n not need so much space, spaces are inserted in front of the\n number, unless MinWidth is negative. In that case, spaces are\n appended at the end. If the number needs more space than\n MinWidth, more space is used.\n\n - 'Precision' is an integer defining how many digits to print\n after the decimal point, or, in the case of g or G conversion,\n the total number of digits to appear, including those on both\n sides of the decimal point\n\n - 'Conversion' is one of:\n\n o d : convert integer to signed decimal string. In this case,\n there is no need to define a 'Precision'.\n\n Example: %1d\n\n prints an integer and uses as many characters\n as required.\n\n o f : convert floating point number to fixed point\n notation. In this case, 'Precision' defines the number\n of digits to print after the decimal point. If there\n are not enough digits available, trailing zeroes are\n appended.\n\n Example: %1.2f\n\n prints a floating point number wiht 2 digits\n after the decimal point and uses as many\n characters as required.\n\n o e or E : Convert floating-point number to scientific\n notation in the form x.yyye±zz, where the number of\n y's is determined by the 'Precision' (default: 6). If\n the precision is 0 then no decimal point is output. If\n the E form is used then E is printed instead of e.\n\n Example: %1.5E\n\n prints a floating point number in the form\n x.yyyyy E±zz \n\n o g or G : If the exponent is less than -4 or greater than\n or equal to the precision, then convert floating-point\n number as for %e or %E. Otherwise convert as for\n %f. Trailing zeroes and a trailing decimal point are\n omitted. In this case the 'Precision' specifies the\n total number of digits to appear, including those on\n both sides of the decimal point\n\n Example: %1.4G\n\n prints 2345.0 as 2345\n prints 234567.0 as 2.346E+05\n prints 0.003456 as 0.003456\n prints 0.00003456 as 3.456E-05 report level report, "level" \N \. COPY pfm_link (linkname, sqlwhere, orderby, displayattrib, fromform, toform) FROM stdin; Report name='$(report)' level pfm_section pfm_report Sections report='$(name)' level name pfm_report pfm_section Attributes form='$(name)' nr name pfm_form pfm_attribute incoming links toform='$(name)' fromform name pfm_form pfm_link outgoing links fromform='$(name)' toform name pfm_form pfm_link Where used? valuelist='$(name)' name pfm_value_list pfm_attribute Values valuelist='$(name)' value name pfm_value_list pfm_value Value list name='$(valuelist)' attribute pfm_attribute pfm_value_list from Form name='$(fromform)' linkname pfm_link pfm_form to Form name='$(toform)' linkname pfm_link pfm_form Valuelist name='$(valuelist)' value pfm_value pfm_value_list Form name='$(form)' attribute pfm_attribute pfm_form \. COPY pfm_report (name, description, sqlselect) FROM stdin; \. COPY pfm_section (report, "level", fieldlist, layout, summary) FROM stdin; \. COPY pfm_value (value, description, valuelist) FROM stdin; taQuoted Value must be enclosed in ' ' for SQL. typeofattribute taNotQuoted Value must not be enclosed in ' ' for SQL. typeofattribute tgDirect Value directly typed by user. typeofget tgExpression Value may be given as an expression. typeofget tgList Value comes from a valuelist. typeofget tgLink Value comes from 'sqlselect'. typeofget t TRUE boolean f FALSE boolean column A column for the labels, a second column for the corresponding values layout table A table with the labels as table header layout row Labels and values on 1 row layout tgReadOnly User cannot change the value of this attribute typeofget \. COPY pfm_value_list (name) FROM stdin; typeofattribute typeofget boolean layout none \. ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT pfm_attribute_pkey PRIMARY KEY (form, attribute); ALTER TABLE ONLY pfm_form ADD CONSTRAINT pfm_form_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_link ADD CONSTRAINT pfm_link_pkey PRIMARY KEY (fromform, linkname); ALTER TABLE ONLY pfm_report ADD CONSTRAINT pfm_report_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_section ADD CONSTRAINT pfm_section_pkey PRIMARY KEY (report, "level"); ALTER TABLE ONLY pfm_value_list ADD CONSTRAINT pfm_value_list_pkey PRIMARY KEY (name); ALTER TABLE ONLY pfm_value ADD CONSTRAINT pfm_value_pkey PRIMARY KEY (valuelist, value); ALTER TABLE ONLY pfm_version ADD CONSTRAINT pfm_version_pkey PRIMARY KEY (seqnr); ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT ref_form FOREIGN KEY (form) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_link ADD CONSTRAINT ref_fromform FOREIGN KEY (fromform) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_value ADD CONSTRAINT ref_list FOREIGN KEY (valuelist) REFERENCES pfm_value_list(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_section ADD CONSTRAINT ref_sections FOREIGN KEY (report) REFERENCES pfm_report(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_link ADD CONSTRAINT ref_toform FOREIGN KEY (toform) REFERENCES pfm_form(name) ON UPDATE CASCADE ON DELETE CASCADE; ALTER TABLE ONLY pfm_attribute ADD CONSTRAINT ref_value_list FOREIGN KEY (valuelist) REFERENCES pfm_value_list(name) ON UPDATE CASCADE ON DELETE RESTRICT; pfm-2.0.8/options.tcl0000644000175000017500000002557411476501600012671 0ustar wimwim# options.tcl class PfmOptions { protected common optionStructure { general {browser tmpdir printcmd printencoding theme} postgresql {psql dblist dbname host port user usePGPASSWORD} geometry {main sql form text} } protected variable optionDict protected variable optionArray protected variable window constructor {} { set optionDict {} dict for {type list} $optionStructure { set subDict {} foreach option $list { dict append subDict $option {} } dict append optionDict $type $subDict } return } destructor { return } public method initOptions {} { global env global tcl_platform if {$tcl_platform(platform) eq {windows}} then { set filename [file join $env(APPDATA) pfm pfm2.conf] } else { set filename "~/.pfm2" } if {[file exists $filename]} then { set chan [open $filename r] set fileDict [chan read -nonewline $chan] chan close $chan } else { set fileDict {} } dict for {type list} $optionStructure { foreach option $list { if {[dict exists $fileDict $type $option]} then { dict set optionDict $type $option \ [dict get $fileDict $type $option] } else { dict set optionDict $type $option \ [getDefault $type $option] } } } saveOptions return } public method saveOptions {} { global env global tcl_platform if {$tcl_platform(platform) eq {windows}} then { set filename [file join $env(APPDATA) pfm pfm2.conf] set dirname [file join $env(APPDATA) pfm] if {![file exists $dirname]} then { file mkdir $dirname } } else { set filename "~/.pfm2" } set chan [open $filename w] chan puts $chan $optionDict chan close $chan return } public method getDefault {type option} { switch $type { general { set value [getGeneralDefault $option] } postgresql { set value [getPostgresqlDefault $option] } geometry { set value [getGeometryDefault $option] } default { set value {} } } return $value } protected method getGeneralDefault {option} { global env global tcl_platform variable ::config::defaultBrowser variable ::config::defaultPrintcmd switch -- $option { "browser" { switch -- $tcl_platform(platform) { "unix" { set value $defaultBrowser } "windows" { if {[info exists env(ProgramFiles)]} then { set iexplorer \ [file normalize [file join $env(ProgramFiles) \ {Internet Explorer} \ {iexplore.exe}]] set value [list $iexplorer %s] } else { set value {iexplore.exe %s} } } default { set value {} } } } "printcmd" { switch -- $tcl_platform(platform) { "unix" { set value $defaultPrintcmd } "windows" { if {[info exists env(ProgramFiles)]} then { set wordpad \ [file normalize [file join $env(ProgramFiles) \ {Windows NT} \ {Bureau-accessoires} \ {wordpad.exe}]] set value [list [list $wordpad %txt]] } else { set value [list [list wordpad.exe %txt]] } } } } "printencoding" { set value [encoding system] } "tmpdir" { switch -- $tcl_platform(platform) { "unix" { set value {/tmp} } "windows" { if {[info exists env(TEMP)]} then { set value [file normalize $env(TEMP)] } else { set value [file normalize "~/tmp"] } } default { set value {} } } } "theme" { switch -- $tcl_platform(platform) { "unix" { set value default } "windows" { set value xpnative } default { set value default } } } default { set value {} } } return $value } protected method getGeometryDefault {option} { switch $option { main { set value {400 300} } sql { set value {600 700} } form { set value {640 480} } text { set value {600 400} } default { set value {600 400} } } return $value } public method getOption {type option} { return [dict get $optionDict $type $option] } public method setOption {type option newValue} { dict set optionDict $type $option $newValue return } public method editOptions {} { foreach type {general postgresql} { foreach option [dict get $optionStructure $type] { set optionArray($type,$option) [dict get $optionDict $type $option] } } set window [toplevel ".opt[namespace tail $this]"] wm transient $window {.} wm title $window [mc optionsTitle] set noteBook [ttk::notebook ${window}.nb -takefocus 0] set fGeneral [setupGeneral ${noteBook}.general] set fPostgresql [setupPostgresql ${noteBook}.postgresql] addNotebookTab $noteBook $fGeneral optTabGeneral addNotebookTab $noteBook $fPostgresql optTabPostgresql ttk::notebook::enableTraversal $noteBook pack $noteBook -side top -expand 1 -fill both set frmButtons [ttk::frame ${window}.frmbtns] set btnOK [defineButton ${frmButtons}.btnok $window btnOK \ [list $this onOK]] set btnCancel [defineButton ${frmButtons}.btncancel $window \ btnCancel [list destroy $window]] pack $btnCancel -side right pack $btnOK -side right pack $frmButtons -side top -fill x -pady {10 10} -padx {10 10} bindToplevelOnly $window [list $this onDestroyWindow] bind $window {focus [tk_focusNext [focus]]} bind $window {focus [tk_focusPrev [focus]]} bind $window [list destroy $window] return } protected method setupGeneral {pathname} { set frm [ttk::frame $pathname] set idx 0 foreach option [dict get $optionStructure general] { set lbl [ttk::label ${frm}.lb$idx -text $option] grid $lbl -column 0 -row $idx -sticky w switch -- $option { printencoding { set control [ttk::combobox ${frm}.con$idx \ -textvariable [scope optionArray(general,$option)] \ -values [lsort [encoding names]]] $control configure -state readonly } theme { set control [ttk::combobox ${frm}.con$idx \ -textvariable [scope optionArray(general,$option)] \ -values [ttk::style theme names]] $control configure -state readonly } default { set control [entry ${frm}.con$idx \ -textvariable [scope optionArray(general,$option)]] } } if {$option in {tmpdir}} then { set btnSelect [defineButton ${frm}.sel$idx $control \ btnSelect [list $this onSelect general $option]] $btnSelect configure -style SButton grid $control -column 1 -row $idx -sticky wens grid $btnSelect -column 2 -row $idx -sticky we $control configure -state {readonly} } else { grid $control -column 1 -columnspan 2 -row $idx \ -sticky wens } set btnExpand [defineButton ${frm}.exp$idx $control \ btnExpand [list $this onExpand general $option]] $btnExpand configure -style SButton grid $btnExpand -column 3 -row $idx -sticky we set btnDefault [defineButton ${frm}.def$idx $control \ btnDefault [list $this onDefault general $option]] $btnDefault configure -style SButton grid $btnDefault -column 4 -row $idx -sticky we set btnHelp [defineButton ${frm}.help$idx $control \ btnHelp [list $this onHelp general $option]] $btnHelp configure -style SButton grid $btnHelp -column 5 -row $idx -sticky we incr idx } grid columnconfigure $frm 1 -weight 1 grid anchor $frm center return $frm } protected method setupPostgresql {pathname} { set frm [ttk::frame $pathname] set idx 0 foreach option [dict get $optionStructure postgresql] { set lbl [ttk::label ${frm}.lb$idx -text $option] grid $lbl -column 0 -row $idx -sticky w switch -- $option { usePGPASSWORD { set control [ttk::checkbutton ${frm}.con$idx \ -text {} \ -variable [scope optionArray(postgresql,$option)] \ -onvalue 1 -offvalue 0] } default { set control [entry ${frm}.con$idx \ -textvariable [scope optionArray(postgresql,$option)]] } } if {$option in {psql}} then { set btnSelect [defineButton ${frm}.sel$idx $control \ btnSelect [list $this onSelect postgresql $option]] $btnSelect configure -style SButton grid $control -column 1 -row $idx -sticky wens grid $btnSelect -column 2 -row $idx -sticky we $control configure -state {readonly} } else { grid $control -column 1 -columnspan 2 -row $idx \ -sticky wens } set btnExpand [defineButton ${frm}.exp$idx $control \ btnExpand [list $this onExpand postgresql $option]] $btnExpand configure -style SButton grid $btnExpand -column 3 -row $idx -sticky we set btnDefault [defineButton ${frm}.def$idx $control \ btnDefault [list $this onDefault postgresql $option]] $btnDefault configure -style SButton grid $btnDefault -column 4 -row $idx -sticky we set btnHelp [defineButton ${frm}.help$idx $control \ btnHelp [list $this onHelp postgresql $option]] $btnHelp configure -style SButton grid $btnHelp -column 5 -row $idx -sticky we incr idx } grid columnconfigure $frm 1 -weight 1 grid anchor $frm center return $frm } public method onOK {} { foreach type {general postgresql} { foreach option [dict get $optionStructure $type] { dict set optionDict $type $option $optionArray($type,$option) } } saveOptions destroy $window installTheme [getOption general theme] return } public method onDestroyWindow {} { array unset optionArray return } public method onExpand {type option} { set textEdit [TextEdit "#auto" $window $option \ $optionArray($type,$option) 0] if {$option in {browser printcmd}} then { $textEdit addMenuItem [mc optPasteFilename] command { %T insert insert [tk_getOpenFile \ -parent [winfo toplevel %T]] } } if {[$textEdit wait result]} then { set optionArray($type,$option) $result } return } public method onDefault {type option} { set optionArray($type,$option) [getDefault $type $option] return } public method onHelp {type option} { set textEdit [TextEdit "#auto" $window [mc optHelpTitle $option] \ [mc optHelp_${option}] 1] return } public method onSelect {type option} { global env global tcl_platform switch -- $type { general { switch -- $option { tmpdir { set dirname [tk_chooseDirectory \ -initialdir $optionArray($type,$option) \ -parent $window] if {$dirname ne {}} then { set optionArray($type,$option) $dirname } } } } postgresql { switch -- $option { psql { if {$tcl_platform(platform) eq {windows}} then { set filetypes { {{Executables} {.exe}} {{All files} *} } set initialdir $env(ProgramFiles) } else { set filetypes {} set initialdir {/usr/bin} } set psqlLoc [tk_getOpenFile \ -filetypes $filetypes \ -initialdir $initialdir \ -parent $window] if {$psqlLoc ne {}} then { set optionArray($type,$option) $psqlLoc } } } } } return } } pfm-2.0.8/pfm.tcl0000755000175000017500000005605612110674645011770 0ustar wimwim#!/usr/bin/tclsh # From version 2.0.0 on, pfm.tcl is called without arguments ####################################################################### # This is Postgres Forms (pfm), a client application for PostgreSQL. # # Copyright (C) 2004-2013 Willem Herremans # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # The home page for the pfm project is at # # http://pgfoundry.org/projects/pfm/ # # There you can report bugs, request new features and get support. # ####################################################################### package require Tcl package require msgcat namespace import ::msgcat::mc package require Itcl namespace import itcl::class itcl::code itcl::delete itcl::scope package require Tk # config.tcl source [file join [file dirname [file normalize [info script]]] config.tcl] # options.tcl source [file join $config::installDir options.tcl] # misc.tcl source [file join $config::installDir misc.tcl] # postgresql.tcl source [file join $config::installDir postgresql.tcl] # database.tcl source [file join $config::installDir database.tcl] # sql.tcl source [file join $config::installDir sql.tcl] # forms.tcl source [file join $config::installDir forms.tcl] # reports.tcl source [file join $config::installDir report.tcl] class MainWin { public variable state protected variable noteBook public variable fForms public variable fDesign public variable fReports protected variable mnDatabase protected variable menubar protected variable mnTools protected variable mnHelp protected variable sqlObject {} constructor {} { wm title . [mc pfm_no_database] wm geometry {.} [join $::geometry::main {x}] set db {} initWindow setState closed set tpOnly [bindToplevelOnly {.} [list delete object $this]] bind $tpOnly {set ::geometry::main {%w %h}} return } destructor { # Cleanup before closing down application if {$state ne {closed}} then { $::dbObject closedb } $::pfmOptions setOption geometry main $::geometry::main $::pfmOptions setOption geometry sql $::geometry::sql $::pfmOptions setOption geometry form $::geometry::form $::pfmOptions setOption geometry text $::geometry::text $::pfmOptions saveOptions foreach file $::tmpFiles { file delete $file } # puts "on exit: [::itcl::find objects]" return } protected method setState {newstate} { set state $newstate switch $state { "open" { wm title . "pfm - [$::dbObject cget -dbname]" $menubar entryconfigure 1 -state normal $mnDatabase entryconfigure 0 -state disabled $mnDatabase entryconfigure 1 -state normal $mnTools entryconfigure 0 -state disabled $mnTools entryconfigure 1 -state disabled } "notables" { wm title . "pfm - [$::dbObject cget -dbname]" $menubar entryconfigure 1 -state normal $mnDatabase entryconfigure 0 -state disabled $mnDatabase entryconfigure 1 -state normal $mnTools entryconfigure 0 -state normal $mnTools entryconfigure 1 -state normal } default { wm title . [mc pfm_no_database] $menubar entryconfigure 1 -state disabled $mnDatabase entryconfigure 0 -state normal $mnDatabase entryconfigure 1 -state disabled $mnTools entryconfigure 0 -state disabled $mnTools entryconfigure 1 -state disabled } } return } public method openDatabase {} { if {$state eq {closed}} then { if {[$::dbObject opendb]} then { check_pfm_tables nrOfTables dbversion if {$nrOfTables > 0} then { switch -- [versionCompare $::config::dbversion $dbversion] { -1 { # pfm_tables are newer than required pfm_message [mc pfm_tables_newer \ $::config::dbversion $dbversion] {.} set newstate open } 0 { # pfm_tables have required version set newstate open } 1 { # pfm_tables are older than required set arg [dict create \ parent {.} \ title [mc convertDB] \ message [mc questionConvertDB \ $::config::dbversion $dbversion] \ msgWidth 400 \ defaultButton btnNo \ buttonList {btnYes btnNo}] set dlg [GenDialog "#auto" $arg] if {[$dlg wait] eq {btnYes}} then { if {[convertDB $dbversion]} then { set newstate open } else { set newstate open pfm_message [mc oldVersion \ $::config::dbversion $dbversion] {.} } } else { set newstate open pfm_message [mc oldVersion \ $::config::dbversion $dbversion] {.} } } } } else { set newstate notables pfm_message [mc noTables] {.} } setState $newstate $fForms setState $newstate $fDesign setState $newstate $fReports setState $newstate } } return } public method closeDatabase {} { FormWindow::closeAllWindows if {$sqlObject ne {}} then { $sqlObject destroyWindow delete object $sqlObject set sqlObject {} } if {$state ne {closed}} then { $::dbObject closedb setState closed $fForms setState closed $fDesign setState closed $fReports setState closed } # puts "on close: [::itcl::find objects]" return } public method openSql {} { if {$sqlObject eq {}} then { set sqlObject [Sql "#auto" {.}] } else { $sqlObject showWindow } return } public method displayHelp {} { set helpFolder [file join $::config::docDir en] foreach locale [lrange [::msgcat::mcpreferences] 0 end-1] { set translatedFolder [file join $::config::docDir $locale] if {[file exists $translatedFolder] && \ [file isdirectory $translatedFolder]} then { set helpFolder $translatedFolder break } } set url "file://${helpFolder}/index.html" set command {exec} set map {%s} lappend map $url foreach arg [$::pfmOptions getOption general browser] { lappend command [string map $map $arg] } lappend command {&} if { [catch $command errMsg]} then { pfm_message [mc browser_failed $command $errMsg] {.} } return } public method displayLicense {} { set filename [file join $::config::licenseDir gpl.txt] foreach locale [lrange [::msgcat::mcpreferences] 0 end-1] { set translatedFile [file join $::config::licenseDir $locale \ gpl.txt] if {[file exists $translatedFile]} then { set filename $translatedFile break } } if {[catch {open $filename r} chan]} then { set textEdit [TextEdit "#auto" {.} License $chan 1] } else { set textEdit [TextEdit "#auto" {.} License [chan read $chan] 1] chan close $chan } return } public method displayAbout {} { variable ::config::version variable ::config::installDir set arg [dict create \ parent {.} \ title pfm \ message [mc about_pfm $version $installDir $::config::API \ [info nameofexecutable] [info patchlevel]] \ msgWidth 500 \ defaultButton btnOK \ buttonList btnOK] set dlg [GenDialog "#auto" $arg] return } public method installPfmTables {} { if {$sqlObject eq {}} then { set sqlObject [Sql "#auto" {.}] } else { $sqlObject showWindow } set sqlWindow [$sqlObject cget -window] update set filename [file join $::config::installDir install_pfm.sql] if {[file exists $filename]} then { set message [mc watchScript install_pfm.sql] pfm_message $message $sqlWindow $sqlObject executeScript $filename {iso8859-1} set message [mc pressOkWhenFinished] pfm_message $message $sqlWindow setState open $fForms setState open $fDesign setState open $fReports setState open } else { pfm_message [mc scriptNotFound $filename] {.} } return } public method installExample {} { set initialDir $::config::exampleDir set title [mc selectExampleDB] set fromEncoding {iso8859-1} set fileTypes { {{SQL statements} {.sql} } {{All files} *} } set defaultExt ".sql" set filename [tk_getOpenFile -title $title -filetypes $fileTypes \ -defaultextension $defaultExt -parent {.} \ -initialdir $initialDir] if {($filename ne {}) && [file exists $filename]} then { if {$sqlObject eq {}} then { set sqlObject [Sql "#auto" {.}] } else { $sqlObject showWindow } set sqlWindow [$sqlObject cget -window] update set message [mc watchScript [file tail $filename]] pfm_message $message $sqlWindow $sqlObject executeScript $filename {iso8859-1} set message [mc pressOkWhenFinished] pfm_message $message $sqlWindow setState open $fForms setState open $fDesign setState open $fReports setState open } return } public method onTabChange {} { if {$state eq {open}} then { $fForms refreshList $fReports refreshList $fDesign refreshList } return } protected method initWindow {} { . configure -background $::themeBackground # Define menus set menubar [menu .mb -tearoff 0] # Database menu set mnDatabase [menu $menubar.db -tearoff 0] addMenuItem $mnDatabase mnuOpen command [list $this openDatabase] addMenuItem $mnDatabase mnuClose command [list $this closeDatabase] $mnDatabase add separator addMenuItem $mnDatabase mnuQuit command [list destroy .] # accelerators for Database menu $mnDatabase entryconfigure 0 -accelerator {Cntrl-o} bind . [list $this openDatabase] $mnDatabase entryconfigure 1 -accelerator {Cntrl-w} bind . [list $this closeDatabase] $mnDatabase entryconfigure 3 -accelerator {Cntrl-q} bind . [list destroy .] # Tools menu set mnTools [menu $menubar.tools -tearoff 0] addMenuItem $mnTools mnuInstallTables command [list $this installPfmTables] addMenuItem $mnTools mnuInstallExample command [list $this installExample] addMenuItem $mnTools mnuOptions command [list $::pfmOptions editOptions] $mnTools add separator addMenuItem $mnTools mnuIncrFont command [list $this changeFontSize 1] addMenuItem $mnTools mnuDecrFont command [list $this changeFontSize -1] $mnTools entryconfigure 4 -accelerator {Cntrl +} $mnTools entryconfigure 5 -accelerator {Cntrl -} bind all [list $this changeFontSize 1] bind all [list $this changeFontSize -1] # Help menu set mnHelp [menu $menubar.help -tearoff 0] addMenuItem $mnHelp mnuHelpFile command [list $this displayHelp] addMenuItem $mnHelp mnuLicense command [list $this displayLicense] addMenuItem $mnHelp mnuAbout command [list $this displayAbout] # Accelerators for Help menu $mnHelp entryconfigure 0 -accelerator {F1} bind . [list $this displayHelp] # connect submenus to menubar addMenuItem $menubar mnuDatabase cascade $mnDatabase addMenuItem $menubar mnuSQL command [list $this openSql] addMenuItem $menubar mnuTools cascade $mnTools addMenuItem $menubar mnuHelp cascade $mnHelp . configure -menu $menubar # Define notebook set noteBook [ttk::notebook .nb -takefocus 0] set fForms [ListTab "#auto" $noteBook forms] set fDesign [ListTab "#auto" $noteBook design] set fReports [ListTab "#auto" $noteBook reports] addNotebookTab $noteBook [$fForms cget -widget] tabForms addNotebookTab $noteBook [$fReports cget -widget] tabReports addNotebookTab $noteBook [$fDesign cget -widget] tabDesign ttk::notebook::enableTraversal $noteBook pack $noteBook -fill both -expand 1 pack [ttk::sizegrip .sg] -side top -anchor e bind $noteBook <> [list $this onTabChange] return } public method changeFontSize {increment} { foreach font {TkDefaultFont TkTextFont TkFixedFont TkMenuFont TkHeadingFont} { set size [font configure $font -size] if {$size > 0} then { font configure $font -size [expr $size + $increment] } else { font configure $font -size [expr $size - $increment] } } return } protected method convertDB {fromVersion} { if {$sqlObject eq {}} then { set sqlObject [Sql "#auto" {.}] } else { $sqlObject showWindow } set sqlWindow [$sqlObject cget -window] update switch -- $fromVersion { {1.0.4} { set scriptList {{1.0.4} {1.1.0} {1.2.0}} } {1.1.0} - {1.1.1} { set scriptList {{1.1.0} {1.2.0}} } {1.2.0} - {1.2.1} - {1.2.3} - {1.2.4} - {1.2.5} { set scriptList {1.2.0} } default { set scriptList {} } } foreach script $scriptList { set filename [file join $::config::installDir \ convert_from_${script}.sql] if {[file exists $filename]} then { set message [mc watchScript convert_from_${script}.sql] pfm_message $message $sqlWindow $sqlObject executeScript $filename {iso8859-1} } else { pfm_message [mc scriptNotFound $filename] {.} } } set message [mc pressOkWhenFinished] pfm_message $message $sqlWindow check_pfm_tables nrOfTables dbversion set converted [string equal $::config::dbversion $dbversion] return $converted } } class ListTab { public variable tabType public variable widget protected variable parent public variable treeview protected variable btn protected variable state protected variable itemList constructor {c_parent c_type} { set parent $c_parent set tabType $c_type set widget [ttk::frame $parent.[namespace tail $this] -takefocus 0] set frm1 [ttk::frame $widget.frm1 -takefocus 0] set frm2 [ttk::frame $widget.frm2 -takefocus 0] switch $tabType { forms { set treeview [ttk::treeview $frm1.tv -columns forms \ -show {headings} -selectmode browse -height 1] $treeview heading forms -text [mc lblForms] set btn [defineButton $frm2.btn $widget btnOpen \ [list $this onOpen]] pack $btn -side right } design { set treeview [ttk::treeview $frm1.tv -columns forms \ -show {headings} -selectmode browse -height 1] $treeview heading forms -text [mc lblDesign] set btn [defineButton $frm2.btn $widget btnOpen \ [list $this onOpen]] pack $btn -side right } reports { set treeview [ttk::treeview $frm1.tv \ -columns {reports description} \ -show {headings} -selectmode browse -height 1] $treeview heading reports -text [mc lblReports] $treeview heading description -text [mc lblDescription] $treeview column reports -stretch 0 -width 150 set btn [defineButton $frm2.btn $widget btnRun \ [list $this onRun]] pack $btn -side right } } set vsb [ttk::scrollbar $frm1.vsb -orient vertical \ -command [list $treeview yview]] $treeview configure -yscrollcommand [list $vsb set] grid $treeview -row 0 -column 0 -sticky wens grid $vsb -row 0 -column 1 -sticky ns grid rowconfigure $frm1 0 -weight 1 grid columnconfigure $frm1 0 -weight 1 pack $frm1 -side top -expand 1 -fill both pack $frm2 -side top -fill x -pady {10 10} -padx {10 10} setState closed focus $treeview recursiveAppendTag $widget $widget bind $widget \ [list $btn instate {!disabled} [list $btn invoke]] return } destructor { return } public method setState {newstate} { set state $newstate switch $newstate { "open" { $btn state {!disabled} $treeview state {!disabled} refreshList } "closed" { $btn state {disabled} $treeview delete [$treeview children {}] $treeview state {disabled} } "notables" { $btn state {disabled} $treeview state {disabled} } } return } public method refreshList {} { set itemList {} $treeview delete [$treeview children {}] foreach item [getFormsReports $::dbObject $tabType] { lappend itemList [$treeview insert {} end -values $item] } if {[llength $itemList]} then { $btn state {!disabled} $treeview state {!disabled} $treeview focus [lindex $itemList 0] $treeview selection set [lindex $itemList 0] } else { $btn state {disabled} $treeview state {disabled} } return } public method onOpen {} { set formName [lindex [$treeview item [$treeview selection] -values] 0] set form [FormWindow "#auto" {.} $formName] return } public method onRun {} { set reportName [lindex [$treeview item [$treeview selection] -values] 0] set reportObject [Report "#auto" {.} $reportName] return } } # Main # create and init options object set pfmOptions [PfmOptions "#auto"] $pfmOptions initOptions set tmpFiles {} namespace eval geometry { foreach window {main sql form text} { variable $window set $window [$::pfmOptions getOption geometry $window] } } # Style issues proc installTheme {theme} { global readonlyBackground global themeBackground global tcl_platform set readonlyBackground {#F3F0EB} if {[catch {ttk::style theme use $theme} errMsg]} then { ttk::style theme use default $::pfmOptions setOption general theme default } # smaller button ttk::style layout SButton [ttk::style layout TButton] ttk::style configure SButton {*}[ttk::style configure TButton] ttk::style configure SButton -width -6 -padding {4 1} # Left aligned smaller button ttk::style layout LButton [ttk::style layout TButton] ttk::style configure LButton {*}[ttk::style configure TButton] ttk::style configure LButton -anchor w -width -6 -padding {4 1} ttk::style map TCombobox -fieldbackground [list readonly $readonlyBackground] set themeBackground [ttk::style lookup TFrame -background] option clear option add *Canvas.background $themeBackground option add *Canvas.highlightThickness 0 option add *Toplevel.background $themeBackground option add *Message.background $themeBackground option add *Entry.highlightThickness 1 option add *Entry.highlightColor {SteelBlue4} option add *Entry.readonlyBackground $readonlyBackground option add *Entry.background {White} option add *Text.background {White} if {$tcl_platform(platform) eq {unix}} then { set activeBackground [ttk::style lookup TButton -background active] set activeForeground [ttk::style lookup TButton -foreground active] option add *Menu.background $themeBackground option add *Menu.activeBackground $activeBackground option add *Menu.activeForeground $activeForeground } return } installTheme [$::pfmOptions getOption general theme] bind TButton {event generate %W } bind TCheckbutton {event generate %W } bind TRadiobutton {event generate %W } bind Button {event generate %W } bind Checkbutton {event generate %W } bind Radiobutton {event generate %W } # Load user interface strings ::msgcat::mcload $::config::languageDir set dbObject [PostgresqlApi "#auto"] set pfmObject [MainWin "#auto"] ::ContextMenu::setup pfm-2.0.8/arrow-left.xbm0000644000175000017500000000023511004642701013242 0ustar wimwim#define arrow_left_width 5 #define arrow_left_height 9 static unsigned char arrow_left_bits[] = { 0x10, 0x18, 0x1c, 0x1e, 0x1f, 0x1e, 0x1c, 0x18, 0x10 }; pfm-2.0.8/pfm-postgresforms.desktop0000644000175000017500000000042111500130134015526 0ustar wimwim[Desktop Entry] Version=1.0 Encoding=UTF-8 Name=pfm Type=Application Exec=/usr/bin/tclsh /usr/share/pfm/pfm.tcl Icon=/usr/share/pfm/pfm-icon.png Terminal=false GenericName=Postgres Forms Comment=Graphical user interface for PostgreSQL database Categories=Office;Database; pfm-2.0.8/pfm-icon.png0000644000175000017500000000234110776111365012702 0ustar wimwim‰PNG  IHDR00Ø`nÐ pHYs  šœtIMEØ U0ˆötEXtCommentCreated with The GIMPïd%nWIDATXÃí˜KlU†ÿ{çáñcÛ‰‡ón%©pR‘ª„©bƒTÔbÛB‹Jl`Ù[„„AlØ@(/¡Vm#’´!±«4ŽÓz2‰“ŒgÆö<.‹É£Šhó ©ðYݹsÏ̧{îùÏ™!sË ÇÉ(Ž™Uª@U Ã6~ÛõôÔ¤ièGöúºx}²¥íQ@{5ÂaÀ¸T8øÚƒ§m6ÏÆ–&xÛôØ® ^b„P(è?}ªó¡~ÌåG.ÑôÞÞ”>3ØBï)…ôìÂÁq”ÖÈÁ‡ò+¥éŸJ…ߤm Ä/‡@Èêš~;D²XÑ8¦3—SHIãt•QÎ Ö2qTt®:&•H¯ëÈŒòla‘ØåU+šKy›÷‹ÍW.ØœT–b.åpNI*-ÆJ¾¨-vªŸû©iz@!Ö£Ì?Ñ™ñ›‹ŒÀ4¦»Î«uý„±¾[Ÿ†—&×*Åà7ï1BWkNŒ§.¶dh›ý¾(D» ±¾Ž;ßJ¥e—òj]ꯞׂÅ\×ÌW¡b–0ÄL÷ÄÏîÄÙ¦¯\P«Ž'ò×K9êÚA}áÔÄ'c§ßZ w •¢XYÛÌ2±² @°4œ]Zw_šH(7sB];¡ÜàmSÖ²¼mpF¾oò³éŽA`wi/Xz±ç%kàRÒä+s…9Þ6S‹£«O?Gc55#^ÀÄ@qø ×_C¤pª¡#PŒ"ã¹÷Ï,4 ×+ך³£‹-ß6ýñ©Þ× ³{¦>ç-C°tùþMtõï ¨$ÅÄsïÄã œ ­|ñ6_þVKD$-Ï2µ­4z|ñ§^ ‘FÏËžZ€²/2Ó}ÁB/5å~¥n ³/+‰'4.üV»40±˜ßm–š€D:€1sMSó ~¢›ž2Æ4Ý`Üzfqe‹Ûp·„›“¡\Êkr«·Ìá¥õˆ»Ö„ñædÆH¦:´1y}<­õ€‘?°,ûêØLIZòî¶ÏçO\*nhùºT2B·¤œ¹{.®Cá-ǵ»žÚBÀú§\=Šjï+/ŸLí7”ÈJº33âM®ÖœôqtÕ~Ól>˜Ìÿ‘Ì]Ùª£R¨éü»Ím˜z·ô;ˆ¢ðüPÿÖ¡³2‡³CkáûÜûDŽƒãÁñ$š^ýˆ¶öo A‚ ÷áÁ‚Êñ$‡=]ö–:œèp¢K…ÍÒ˨àM2Êmo¶}—ß}ØzûKj]*öqÅÕyP®N2n·ÝÅ\N9”~è¾RÈ`ª-¬'Ðx!Ö  (·4$¢¢°Ÿ&p÷!ÛHMe£Þx¸½é-Ñ!û=h¿\¬~—=^! ý±ˆ¼‡.¯hEÝ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. pfm-2.0.8/misc.tcl0000644000175000017500000013273711640356230012131 0ustar wimwim# misc.tcl # proc appendToPath # This procedure joins 2 widget paths taking into account that # the exception that {.} joined with "xxx" yields .xxx and not ..xxx proc appendToPath {path tail} { if {$path eq {.}} then { set result "${path}${tail}" } else { set result "${path}.${tail}" } return $result } # proc addMenuItem {menuName itemLabel itemType argument} # # - menuName: the pathname of the menu to which the item is to be added # # - itemLabel: the untranslated label see files in subdir 'msgs' for # translated labels # - itemType: one of 'command', 'cascade' # # - argument: if command, it is the command to bind to the menuitem # if cascade, it is the pathname of the menu to open # # This procedure adds a menu item to menu $menuName. It translates the # $itemLabel using the files in subdir 'msgs' and looks for "&" in the # translated label. If "&" is found, it is removed and a "-underline" # clause is added to underline the character following the "&". # # This procedure returns an empty string proc addMenuItem {menuName itemLabel itemType argument} { if {$itemType eq {cascade}} then { set thirdClause {-menu} } else { set thirdClause {-command} } set translated [mcunderline $itemLabel] if {[llength $translated] > 1} then { $menuName add $itemType \ -label [lindex $translated 0] -underline [lindex $translated 1] \ $thirdClause $argument } else { $menuName add $itemType \ -label [lindex $translated 0] $thirdClause $argument } return } # proc defineButton {btnName bindTag btnLabel btnCommand} # # - btnName: the pathname of the button to define # # - bindTag: the tag to use in the bind command for the shortcut # # - btnLabel: the untranslated string to display on the button # # - btnCommand: the command to bind to the button. # # This procedure defines a button with pathname $btnName and returns # $btnName. It translates the $btnLabel using the files in subdir 'msgs' # and looks for "&" in the translated label. If "&" is found, it is # removed, a "-underline" clause is added to underline the character # following the "&" and $btnCommand is also bound to # where 'x' is the underlined character. # # Nasty problem: The binding for the keyboard shortcut with AltUnderlined # is difficult to get right: # # - without 'after 200' it works on Windows, but on Linux when # the button command destroys the 'text' widget that received the # KeyPress event, Tk raises on error because it tries to do something # with the text widget that has already been destroyed. # # - with 'after idle' it works well on Linux but on Windows, # Tk derails completely when the command creates a new toplevel. # It goes into an endless loop with high CPU load. # # - with 'after 200' it seems to work well on both Windows and Linux, # but I can only hope that 200 ms will always be sufficient to # avoid the problem. # # - I have also tried to generate a virtual event <>, # but that path leads to the same troubles. proc defineButton {btnName bindTag btnLabel btnCommand} { set translation [mcunderline $btnLabel] if {[llength $translation] > 1} then { set widget [ttk::button $btnName -takefocus 0 -command $btnCommand \ -text [lindex $translation 0] -underline [lindex $translation 1]] bind $bindTag \ [list after 200 [list $btnName instate {!disabled} [list $btnName invoke]]] } else { set widget [ttk::button $btnName -takefocus 0 -command $btnCommand \ -text [lindex $translation 0]] } return $widget } # Same as define button, but for a checkbutton proc defineCheckbutton {btnName bindTag btnLabel btnCommand btnVariable OnValue OffValue} { set translation [mcunderline $btnLabel] if {[llength $translation] > 1} then { set widget [ttk::checkbutton $btnName -takefocus 0 -command $btnCommand \ -text [lindex $translation 0] -underline [lindex $translation 1] \ -variable $btnVariable -onvalue $OnValue -offvalue $OffValue] bind $bindTag \ [list after 200 [list $btnName instate {!disabled} [list $btnName invoke]]] } else { set widget [ttk::checkbutton $btnName -command $btnCommand \ -text [lindex $translation 0] \ -variable $btnVariable -onvalue $OnValue -offvalue $OffValue] } return $widget } # Same as define button, but for a radiobutton proc defineRadiobutton {btnName bindTag btnLabel btnCommand btnVariable value} { set translation [mcunderline $btnLabel] if {[llength $translation] > 1} then { set widget [ttk::radiobutton $btnName -takefocus 0 -command $btnCommand \ -text [lindex $translation 0] -underline [lindex $translation 1] \ -variable $btnVariable -value $value] bind $bindTag \ [list after 200 [list $btnName instate {!disabled} [list $btnName invoke]]] } else { set widget [ttk::radiobutton $btnName -command $btnCommand \ -text [lindex $translation 0] \ -variable $btnVariable -value $value] } return $widget } # Append newTag to a widget's bindtags proc appendBindTag {widget newTag} { set tags [bindtags $widget] lappend tags $newTag bindtags $widget $tags return } # bindToplevelOnly $topPath $event $script # # This procedure generates a new unique bindtag of the form # "tpOnly$counter" where counter is incremented at every invocation # of the procedure. Then it appends this bindtag to the toplevel # identified by $topPath and binds $event and $script to this new bindtag. # # Normally a toplevel receives the events from all its children. # Sometimes that is not what you want. E.g. to catch the event # from a toplevel, it is not a good idea to bind a script to the toplevel # because it will be called for every child of the toplevel that is # destroyed. namespace eval TpOnlyTags {variable counter 0} proc bindToplevelOnly {topPath event script} { variable TpOnlyTags::counter set newTag "tpOnly$counter" incr counter appendBindTag $topPath $newTag bind $newTag $event $script return $newTag } # Append a bindtag $tag to all descendants of $widget proc recursiveAppendTag {widget tag} { foreach child [winfo children $widget] { appendBindTag $child $tag recursiveAppendTag $child $tag } return } # proc mcunderline {untranslated} # # - untranslated: is the untranslated string # # This procedure translates the untranslated string using the files # in subdir msgs. It also looks for "&" in the translated string. # # If "&" is found: # the procedure returns a list in which: # - the 1st item is the translated string without "&" # - the index where the "&" was located # - the lower case character that was following the "&" (i.e. # the character that will be displayed underlined) # else: # the procedure returns a list in which the translated string # is the only element. proc mcunderline {untranslated} { set translated [mc $untranslated] set underline [string first {&} $translated] if {$underline >= 0} then { set translated [string replace $translated $underline $underline] set shortcut [string tolower [string index $translated $underline]] set result [list $translated $underline $shortcut] } else { set result [list $translated] } return $result } # proc addNotebookTab {nbName window tabLabel} # # - nbName: the name of the notebook to which a tab will be added # - window: the the name of the window that will be added as tab # - tabLabel: the untranslated label to display on the tab # # This procedure translates the tabLabel using the files in the msgs # subdirectory. It also looks for & to take care of underlining the # next character. Then it calls the normal notebook add command. proc addNotebookTab {nbName window tabLabel} { set translated [mcunderline $tabLabel] if {[llength $translated] > 1} then { $nbName add $window \ -text [lindex $translated 0] -underline [lindex $translated 1] } else { $nbName add $window -text [lindex $translated 0] } return } # proc pfm_message {msg parent} # This procedure reports an error message 'msg'. 'msg' must be a # translated string. proc pfm_message {msg parent} { if {[info command winfo] eq {winfo}} then { dict append arg parent $parent dict append arg title [mc pfm_message] dict append arg message $msg dict append arg msgWidth 500 dict append arg defaultButton btnOK dict append arg buttonList btnOK set dlg [GenDialog "#auto" $arg] $dlg wait } else { puts $msg } return } # A GenDialog object displays a toplevel window containing a message # and a number of buttons. The "wait" method waits for the user to press # one of the buttons and returns the label of the pressed button. # # A GenDialog object is created as follows: # # GenDialog "#auto" $arg # where $arg is a dictionary with the folling keys: # - parent: the toplevel parent window # - title: the title to give to the toplevel window that is created # - message: the text to display on the window. This is the translated # text. # - msgWidth: the width in pixels of the message # - defaultButton: the untranslated label of the default button, i.e. # the button that initially gets the focus # - buttonList: the list of untranslated labels for the buttons that # will be displayed. # # After creating a GenDialog object you can call the wait method to # get the result. It returns the label of the pressed button. # # You don't have to worry about deleting the object. It is automatically # deleted after returning from wait. class GenDialog { protected variable window protected variable buttonPressed protected variable waitCalled 0 constructor {arg} { set window [toplevel \ [appendToPath [dict get $arg parent] [namespace tail $this]]] set buttonPressed [dict get $arg defaultButton] wm title $window [dict get $arg title] set parent [dict get $arg parent] wm transient $window $parent set x [expr [winfo rootx $parent] + 100] set y [expr [winfo rooty $parent] + 50] wm geometry $window "+${x}+${y}" set msg [message ${window}.msg \ -width [dict get $arg msgWidth] \ -justify left \ -text [dict get $arg message]] set frm [ttk::frame ${window}.frm] set row 0 foreach btnLabel [dict get $arg buttonList] { set btn [defineButton $frm.$btnLabel $window $btnLabel \ [list $this onButton $btnLabel]] grid $btn -row 0 -column $row incr row } pack $msg -side top -expand 1 -fill both -padx {10 10} -pady {10 10} pack $frm -side top -pady {0 10} if {[dict get $arg defaultButton] in [dict get $arg buttonList]} then { focus $frm.[dict get $arg defaultButton] } bindToplevelOnly $window [list $this onDestroy] bind $window [list destroy $window] } destructor { return } public method onDestroy {} { if {!$waitCalled} then { after idle [list delete object $this] } return } public method onButton {btnLabel} { set buttonPressed $btnLabel destroy $window return } public method wait {} { set waitCalled 1 tkwait window $window after idle [list delete object $this] return $buttonPressed } } # Class GenForm # A GenForm object displays a toplevel window which enables the user to # see and modify a number of data. # # A GenForm object is created as follows: # # GenForm $id $parent $title $dataList # # where: $id: is the new object's name. If "#auto" is used # for this parameter, a new name is created automatically # # $parent: is the parent's window path # # $title: title for window # # $dataList is a list containing an item for each data item # that is displayed on the window, and where each item # is a dict with the folling keys: # -name: data item's name which must be unique # within this dialog # -type: the data item's type, which must be one of # string, bool, password # -value: the data item's initial value # -valuelist: a list of allowed values. If this # list is not empty, a combox is used, else # a normal text entry is used. # # After creating the object, call the object's wait method to get the # result: # # genFormObject wait resultName # # This method returns 1 or 0 depending on whether OK or cancel was # pressed. The results become available in the array with name # resultName. # # You don't have to worry about deleting the object. If the user presses # OK or Cancel, or if he destroys the window. The object is deleted # as well. class GenForm { protected variable data protected variable window protected variable pressedOK 0 protected variable waitCalled 0 protected variable frm1 constructor {parent title dataList} { set pressedOK 0 set window [toplevel \ [appendToPath $parent [namespace tail $this]]] wm transient $window $parent set x [expr [winfo rootx $parent] + 100] set y [expr [winfo rooty $parent] + 50] wm geometry $window "+${x}+${y}" wm title $window $title set frm1 [ttk::frame ${window}.frm1] set idx 0 foreach item $dataList { set name [dict get $item name] set type [dict get $item type] set value [dict get $item value] set valuelist [dict get $item valuelist] set data($name) $value set label [ttk::label $frm1.lb$idx -text $name] switch $type { bool { set control [ttk::checkbutton $frm1.cont$idx \ -variable [scope data($name)] \ -onvalue 1 -offvalue 0] set sticky w } password { set control [entry $frm1.cont$idx \ -textvariable [scope data($name)] \ -show "*"] set sticky we } default { if {[llength $valuelist] > 0} then { set control [ttk::combobox $frm1.cont$idx \ -textvariable [scope data($name)] \ -values $valuelist] set sticky we } else { set control [entry $frm1.cont$idx \ -textvariable [scope data($name)]] set sticky we } } } if {$idx == 0} then { focus $control } grid $label -column 0 -row $idx -sticky $sticky grid $control -column 1 -row $idx -sticky $sticky incr idx } grid columnconfigure $frm1 1 -weight 1 pack $frm1 -side top -padx {10 10} -pady {10 10} set frm2 [ttk::frame ${window}.frm2] set btnOK [defineButton $frm2.ok $window btnOK [list $this onOK]] set btnCancel [defineButton $frm2.cancel $window btnCancel \ [list $this onCancel]] $btnOK configure -takefocus 1 $btnCancel configure -takefocus 1 pack $btnCancel -side right pack $btnOK -side right pack $frm2 -side top -fill x -padx {10 10} -pady {0 10} bindToplevelOnly $window [list $this onDestroy] bind $window [list destroy $window] bind $window {focus [tk_focusNext [focus]]} bind $window {focus [tk_focusPrev [focus]]} bind $window [list $this onOK] } destructor { } public method onDestroy {} { if {!$waitCalled} then { after idle [list delete object $this] } return } public method wait {resultVar} { upvar $resultVar result set waitCalled 1 tkwait window $window array set result [array get data] after idle [list delete object $this] return $pressedOK } public method onOK {} { set pressedOK 1 destroy $window return } public method onCancel {} { set pressedOK 0 destroy $window return } public method displayHelpText {helpText} { set lbHelp [ttk::label ${window}.lbHelp -text $helpText \ -padding {10 10 10 10}] pack $lbHelp -side top -before $frm1 return } } # A TextEdit object displays a window in which the user can see # and possibly edit a text. To create a TextEdit object use: # # TextEdit #auto $parent $title $initialText $readOnly # # where: - $parent is the widget pathname of the parent window # - $title is the window title # - $initialText: is the text that will be displayed initially # - $readOnly: 0 or 1, indicating whether the user is allowed # to edit the text. # # After creating the object, there are 3 possible modes of use. # # 1. Normal mode: In this mode, the initial text is displayed, but # it is not possible to get any result. You don't # have to worry about deleting the object. It is # deleted automatically when the user pressed OK or # Cancel, or when he destroys the window. This mode # is only usefull for readOnly text. # # 2. Wait mode: In this mode, after creating the object, you call # the object's "wait" method. This method does not # return before the user has pressed OK or Cancel, or # has destroyed the window. The wait method should be # called as follows: # # textEditObject wait textVarName # # It returns 1 or 0 edpending on wheter OK or Cancel # was pressed, and it stores the result in the variable # with the name textVarName. You don't have to worry # about deleting the object. It is automatically deleted # after returning from the wait method. # # 3. CallBack mode: In this mode, after creating the object, you call # the object's "defineCallBack" method. This method is # called as follows: # # textEditObject defineCallBack callBackScript # # This callBackScript must call the object's getText # method as follows: # # textEditObject getText textVarName # # It returns 1 or 0 depending on whether OK or Cancel # was pressed, and it stores the result in the variable # with the name textVarName. After returning from this # method, the textEditObject no longer exists. So, you # can call this method only once. # # You can also add custom menus to the this widget using the method # addMenuItem # # textEditObject addMenuItem $btnLabel $type $arg # # where $type is either command or cascade # # $arg is then either a script to be called when the menuitem is invoked, # or the name of a menu in case of cascade. # # In case of command, you can use %T to represent the text widget's pathname. # # If you want to destroy the object, do not call delete object, but # call the destroyWindow method instead. class TextEdit { public variable window protected variable menubar protected variable readOnly protected variable txtWidget protected variable actualText {} protected variable wrap {none} protected variable btnFrame protected variable entSearch protected variable pressedOK 0 protected variable mode normal protected variable callback constructor {parent title initialText c_readOnly} { set readOnly $c_readOnly setupWindow $parent $title $initialText } destructor { } protected method setupWindow {parent title initialText} { set window [toplevel [appendToPath $parent [namespace tail $this]]] wm title $window $title wm geometry $window [join $::geometry::text {x}] set menubar [setupMenus] $window configure -menu $menubar set txtWidget [text $window.txt -width 1 -height 1 -wrap $wrap] $txtWidget tag configure blue -foreground {medium blue} $txtWidget tag configure red -foreground {red3} $txtWidget tag configure green -foreground {green4} if {$readOnly} then { $txtWidget configure -background $::readonlyBackground } set vsb [ttk::scrollbar $window.vsb -orient vertical \ -command [list $txtWidget yview]] set hsb [ttk::scrollbar $window.hsb -orient horizontal \ -command [list $txtWidget xview]] $txtWidget configure \ -yscrollcommand [list $vsb set] \ -xscrollcommand [list $hsb set] $txtWidget insert end $initialText $txtWidget mark set insert 1.0 $txtWidget yview 0 set btnFrame [ttk::frame $window.btnFrame] if {!$readOnly} then { set btnOK [defineButton $btnFrame.btnOK $window btnOK \ [list $this onOK]] $btnOK configure -style SButton } else { set lbReadOnly [ttk::label $btnFrame.rdonly \ -text [mc lbReadOnly] -foreground {medium blue}] $txtWidget configure -state disabled } set btnCancel [defineButton $btnFrame.btnCancel $window btnCancel \ [list $this onCancel]] $btnCancel configure -style SButton set btnWrap [defineCheckbutton $btnFrame.btnWrap $window btnWrap \ [list $this onWrap] [scope wrap] word none] set searchFrm [ttk::frame $btnFrame.search] set btnSearch [defineButton $searchFrm.btn $window btnSearch \ [list $this onSearch]] $btnSearch configure -style SButton set entSearch [entry $searchFrm.ent] bind $entSearch [list $this onSearch] pack $btnSearch -side right pack $entSearch -side right -expand 1 -fill both grid $txtWidget -column 0 -row 0 -sticky wens grid $vsb -column 1 -row 0 -sticky ns grid $hsb -column 0 -row 1 -sticky we grid $btnFrame -column 0 -columnspan 2 -row 2 -sticky we \ -pady {10 10} -padx {10 10} grid [ttk::sizegrip ${window}.sg] -column 0 -columnspan 2 \ -row 3 -sticky e grid columnconfigure $window 0 -weight 1 grid rowconfigure $window 0 -weight 1 pack $btnCancel -side right if {!$readOnly} then { pack $btnOK -side right } else { pack $lbReadOnly -side right } pack $searchFrm -side right -expand 1 -fill x pack $btnWrap -side right set tpOnly [bindToplevelOnly $window [list $this onDestroy]] bind $tpOnly {set ::geometry::text {%w %h}} bind $window [list destroy $window] focus $txtWidget return } protected method setupMenus {} { set menu [menu ${window}.menubar -tearoff 0] set mnuText [menu ${menu}.text -tearoff 0] ::addMenuItem $mnuText mnuTxtSave command [list $this onSave] ::addMenuItem $mnuText mnuTxtPrint command [list $this onPrint] $mnuText add separator ::addMenuItem $mnuText mnuTxtClose command [list destroy $window] $mnuText entryconfigure 3 -accelerator {Esc} ::addMenuItem $menu mnuText cascade $mnuText return $menu } public method onDestroy {} { switch $mode { normal { after idle [list delete object $this] } callback { eval $callback } } return } public method onPrint {} { printTextWidget $txtWidget $window return } public method onSave {} { saveTxtFromWidget $txtWidget $window return } public method onWrap {} { $txtWidget configure -wrap $wrap return } public method gotoBegin {} { $txtWidget mark set insert 1.0 $txtWidget yview 0 return } public method onSearch {} { focus $entSearch set pattern [$entSearch get] if {[string length $pattern]} then { set searchPosition [$txtWidget index insert] $txtWidget tag delete match set searchPosition [$txtWidget search -nocase \ $pattern $searchPosition end] if {$searchPosition ne {}} then { set endmatch [$txtWidget index \ "$searchPosition +[string length $pattern] chars"] $txtWidget tag add match $searchPosition $endmatch $txtWidget tag configure match -background yellow $txtWidget mark set insert $endmatch $txtWidget see insert } else { pfm_message [mc searchEOT] $window $txtWidget mark set insert 1.0 $txtWidget see insert } } return } public method onOK {} { set pressedOK 1 set actualText [$txtWidget get 1.0 "end - 1 chars"] destroy $window return } public method onCancel {} { set pressedOK 0 set actualText {} destroy $window return } public method addMenuItem {itemLabel itemType argument} { set argument [string map [list %T $txtWidget] $argument] ::addMenuItem $menubar $itemLabel $itemType $argument return } public method getText {textVar} { upvar $textVar result if {$pressedOK} then { set result $actualText } after idle [list delete object $this] return $pressedOK } public method setText {textVar} { upvar $textVar text if {$readOnly} then { $txtWidget configure -state normal } $txtWidget delete 1.0 end $txtWidget insert end $text if {$readOnly} then { $txtWidget configure -state disabled } return } public method appendText {text colour} { if {$readOnly} then { $txtWidget configure -state normal } if {$colour in {red green blue}} then { $txtWidget insert end $text $colour } else { $txtWidget insert end $text } if {$readOnly} then { $txtWidget configure -state disabled } return } public method wait {textVar} { upvar $textVar result set mode wait tkwait window $window set result $actualText after idle [list delete object $this] return $pressedOK } public method defineCallBack {callBackScript} { set callback $callBackScript set mode callback return } public method destroyWindow {} { destroy $window return } } # A ListBox object creates a toplevel window with a multicolumn listbox # (ttk::treeview control) to allow the user to select a value from # a list of values. Additionally, it has an entry and a button which # allows the user to search for a particular string in the listbox # values. # # To create a ListBox object call # # ListBox "#auto" $parent $title $headerlist $valuelist $selected # # where: -parent is the pathname of the parent toplevel window. # -title: the toplevel's title # -headerlist: the list of column headers. The length of # this list determines the number of columns # -valuelist: the list of values for the listbox where each item # is a list containing a value for each column # -selected: the index of the initially selected listbox item # # ListBox tries to estimate an optimum columnwidth and window size. # # After creating the ListBox object, call the wait method to # get the uer's choice: # # listBoxObject wait result # # where result is the name of the variable that will receive the # selected value(s). The return value of the wait method is 1 or 0 # depending on whether the user has really selected a value or # just destroyed the window. # # If you want to destroy the object, do not call delete object, but # call the destroyWindow method instead. class ListBox { protected variable window protected variable valuelist {} protected variable lsb protected variable entSearch protected variable waitCalled 0 protected variable itemSelected 0 protected variable selectedValues {} protected variable statusfield protected variable stringFound 0 constructor {parent title headerlist c_valuelist selected} { set valuelist $c_valuelist set window [toplevel [appendToPath $parent [namespace tail $this]]] wm transient $window $parent set x [expr [winfo rootx $parent] + 100] set y [expr [winfo rooty $parent] + 50] wm geometry $window "+${x}+${y}" wm title $window $title set frmSearch [ttk::frame $window.frmSearch] set entSearch [entry $frmSearch.ent] set btnSearch [defineButton $frmSearch.btn $window btnSearch \ [list $this onSearch]] $btnSearch configure -style SButton bind $entSearch [list $this onSearch] pack $btnSearch -side right pack $entSearch -side right -expand 1 -fill both set frmLsb [ttk::frame $window.frmlsb] set columnlist {} for {set idx 0} {$idx < [llength $headerlist]} {incr idx} { lappend columnlist col$idx } set lsb [ttk::treeview $frmLsb.lsb -columns $columnlist \ -selectmode browse -show headings] set idx 0 foreach heading $headerlist { $lsb heading col$idx -text $heading $lsb column col$idx -width [estimateColumnWidth $idx] incr idx } set idx 0 set selItem "I0" foreach tuple $valuelist { set item [$lsb insert {} end -id "I$idx" -values $tuple] if {$idx == $selected} then { set selItem $item } incr idx } set vsb [ttk::scrollbar $frmLsb.vsb -orient vertical \ -command [list $lsb yview]] $lsb configure -yscrollcommand [list $vsb set] grid $lsb -column 0 -row 0 -sticky wens grid $vsb -column 1 -row 0 -sticky ns grid columnconfigure $frmLsb 0 -weight 1 grid rowconfigure $frmLsb 0 -weight 1 set btnBar [ttk::frame $window.btnBar] set btnOK [defineButton $btnBar.btnOK $window btnOK \ [list $this onSelection]] $btnOK configure -style TButton set btnCancel [defineButton $btnBar.btnCancel $window btnCancel \ [list destroy $window]] $btnCancel configure -style TButton grid $btnOK -column 0 -row 0 grid $btnCancel -column 1 -row 0 grid anchor $btnBar center set statusbar [ttk::frame $window.sb] set statusfield [ttk::label $statusbar.sf] set grip [ttk::sizegrip $statusbar.sg] grid $statusfield -column 0 -row 0 grid $grip -column 1 -row 0 -sticky e grid columnconfigure $statusbar 0 -weight 1 pack $frmSearch -side top -fill x -pady 10 -padx 10 pack $frmLsb -side top -expand 1 -fill both pack $btnBar -side top -fill x -ipady 10 -ipadx 10 pack $statusbar -side top -fill x bindToplevelOnly $window [list $this onDestroy] # bind $lsb <1> [list after idle [list $this onSelection]] # Note: The above binding has the annoying side effect that # the user cannot adjust the column widths without destroying # the window. That is why it has been commented out. bind $lsb [list after idle [list $this onSelection]] bind $window [list destroy $window] update focus $window focus $lsb # next "if" statement has been added for bug 1073 if {[llength $valuelist] > 0} then { $lsb see $selItem $lsb selection set $selItem $lsb focus $selItem } return } destructor { return } protected method estimateColumnWidth {column} { set nrOfChars 0 set text {} foreach tuple $valuelist { set stringLength [string length [lindex $tuple $column]] if {$stringLength > $nrOfChars} then { set nrOfChars $stringLength set text [lindex $tuple $column] } } set width [font measure TkTextFont -displayof $window " $text "] return $width } public method onDestroy {} { if {!$waitCalled} then { after idle [list delete object $this] } return } public method destroyWindow {} { destroy $window return } public method onSelection {} { set itemSelected 1 set selectedValues [$lsb item [$lsb selection] -values] destroy $window return } public method wait {resultName} { upvar $resultName result set waitCalled 1 tkwait window $window set result $selectedValues after idle [list delete object $this] return $itemSelected } public method onSearch {} { focus $entSearch set searchString [$entSearch get] if {[string length $searchString]} then { set currentValues [$lsb item [$lsb selection] -values] set startIndex [lsearch -exact $valuelist $currentValues] if {$stringFound} then { set startIndex [expr $startIndex + 1] } set newIndex [lsearch -nocase -glob -start $startIndex \ $valuelist "*${searchString}*"] if {$newIndex >= 0} then { set stringFound 1 $lsb selection set "I${newIndex}" $lsb focus "I${newIndex}" $lsb see "I${newIndex}" $statusfield configure -text [mc lsbSearchFound $searchString] } else { set stringFound 0 $lsb selection set "I0" $lsb focus "I0" $lsb see "I0" $statusfield configure -text [mc lsbSearchNotFound] } } return } } proc convertToUTF-8 {fileName fromEncoding parent} { # This procedures converts $fileName from $fromEncoding to UTF-8. # It writes the converted file in pfmOptions(tmpdir) and returns # the name of the converted file. # # Even if the $fromEncoding = utf-8, we execute this conversion. # Tcl seems to be rather clever to recognise encodings such that # even if the user has specifed utf-8 when that is not correct, # Tcl converts it to utf-8. set outFileName {} set tmpdir [$::pfmOptions getOption general tmpdir] if {![file exists $tmpdir]} then { if {[catch {file mkdir $tmpdir} errMsg]} then { pfm_message $errMsg $parent } } if {[catch {open $fileName r} inFile]} then { pfm_message $inFile $parent } else { chan configure $inFile -encoding $fromEncoding set tail [file tail $fileName] set tmpName "pfm[pid]_${tail}" set outFileName [file join $tmpdir $tmpName] lappend ::tmpFiles $outFileName if {[catch {open $outFileName w} outFile]} then { pfm_message $outFile $parent set outFileName {} } else { # bug 1057 "-translation lf" added in version 1.5.2 # Without this modification, tcl would use CR LF # as line ending on the Windows platform. psql would # interpret LF as line ending and it would consider CR # as an extra character. chan configure $outFile -encoding utf-8 -translation lf while {![eof $inFile]} { chan puts $outFile [chan gets $inFile] } chan close $inFile chan close $outFile } } return $outFileName } proc versionCompare {v1 v2} { # This procedures compares 2 version numbers of the form x.y.z # It returns: # +1 if v1 > v2 # 0 if v1 = v2 # -1 if v1 < v2 set v1List [split $v1 "."] set v2List [split $v2 "."] set result 0 for {set i 0} {($i <= 2) && ($result == 0)} {incr i} { if {[lindex $v1List $i] < [lindex $v2List $i]} then { set result -1 } else { if {[lindex $v1List $i] > [lindex $v2List $i]} then { set result 1 } } } return $result } # ContextMenu defines a popup menu with the menu items: # 0: Copy # 1: Cut # 2: Paste namespace eval ContextMenu { variable menu proc setup {} { variable menu set menu [menu .mnEdit -tearoff 0] addMenuItem $menu mnuCopy command ::ContextMenu::onCopy addMenuItem $menu mnuCut command ::ContextMenu::onCut addMenuItem $menu mnuPaste command {} $menu entryconfigure 0 -accelerator {Cntrl-c} $menu entryconfigure 1 -accelerator {Cntrl-x} $menu entryconfigure 2 -accelerator {Cntrl-v} bind all [list ::ContextMenu::popUpMenu %W %X %Y] bind all ::ContextMenu::onCopy bind all ::ContextMenu::onCut # bind all [list ::ContextMenu::onPaste %W] # This is already a deafult Tk binding. If above line is # uncommented, text is pasted twice. return } proc popUpMenu {clickedWidget x y} { variable menu set owner [selectionOwner] if {$owner eq {}} then { $menu entryconfigure 0 -state disabled $menu entryconfigure 1 -state disabled } else { $menu entryconfigure 0 -state normal if {[getState $owner] eq {normal}} then { $menu entryconfigure 1 -state normal } else { $menu entryconfigure 1 -state disabled } } if {([getState $clickedWidget] eq {normal}) && [getClipboardText textToPaste]} then { $menu entryconfigure 2 \ -command [list ::ContextMenu::onPaste $clickedWidget] \ -state normal } else { $menu entryconfigure 2 -command {} -state disabled } tk_popup $menu $x $y return } proc onCopy {} { if {([selectionOwner] ne {}) && [getSelectedText selectedText]} then { clipboard clear -displayof . clipboard append -displayof . -format STRING -type STRING -- \ $selectedText selection clear -displayof . -selection PRIMARY } return } proc onCut {} { set owner [selectionOwner] if {($owner ne {}) && [getSelectedText selectedText]} then { clipboard clear -displayof . clipboard append -displayof . -format STRING -type STRING -- \ $selectedText if {[getState $owner] eq {normal}} then { $owner delete sel.first sel.last } selection clear -displayof . -selection PRIMARY } return } proc onPaste {widget} { set state [getState $widget] if {($state eq {normal}) && [getClipboardText textToPaste]} then { $widget insert insert $textToPaste } return } proc getState {widget} { if {[winfo exists $widget]} then { set class [winfo class $widget] } else { set class {} } switch -- $class { Entry - Text { set state [$widget cget -state] # state is one of {normal, disabled, readonly} } TEntry - TCombobox { if {[$widget instate {disabled}]} then { set state disabled } else { if {[$widget instate {readonly}]} then { set state readonly } else { set state normal } } } default { set state disabled } } return $state } proc getSelectedText {textName} { upvar $textName text if {[catch {selection get -displayof . -selection PRIMARY \ -type STRING} text]} then { set result 0 set text {} } else { set result 1 } return $result } proc selectionOwner {} { return [selection own -displayof . -selection PRIMARY] } proc getClipboardText {textName} { upvar $textName text if {[catch {clipboard get -displayof . -type STRING} text]} then { set result 0 set text {} } else { set result 1 } return $result } } proc printTextWidget {txtWidget parent} { proc longestLine {txtWidget} { set longest 0 set lastIndex [$txtWidget index end] set index [$txtWidget index 1.0] while { $index < $lastIndex } { set thisLineLength [string length [$txtWidget get $index "$index lineend"]] if { $longest < $thisLineLength } then { set longest $thisLineLength } set index [$txtWidget index "$index +1 lines"] } return $longest } proc getParms {cmd} { set parmList {} set moreParms 1 set startSearch 0 while {$moreParms} { set startOfParm [string first {$(} $cmd $startSearch] if {$startOfParm >= 0} then { set endOfParm [string first {)} $cmd $startOfParm] if {$endOfParm < 0} then { pfm_message [mc sqlErrCmd $cmd] $window set moreParms 0 } else { set startSearch $endOfParm set parm [string range $cmd $startOfParm $endOfParm] set equalSign [string first {=} $parm] if {$equalSign >= 0} then { set name [string range $parm 2 [expr $equalSign - 1]] set value [string range $parm [expr $equalSign + 1] end-1] } else { set name [string range $parm 2 end-1] set value {} } set parmDef [dict create \ full $parm \ name $name \ type string \ value $value \ valuelist {}] lappend parmList $parmDef } } else { set moreParms 0 } } return $parmList } set tmpdir [$::pfmOptions getOption general tmpdir] set tmptxt [file join $tmpdir pfm_print[pid].txt] lappend ::tmpFiles $tmptxt set tmpchan [open $tmptxt w] chan configure $tmpchan \ -encoding [$::pfmOptions getOption general printencoding] chan puts $tmpchan [$txtWidget get 1.0 "end -1 chars"] chan close $tmpchan set tmpps [file join $tmpdir pfm_print[pid].ps] set tmppdf [file join $tmpdir pfm_print[pid].pdf] lappend ::tmpFiles $tmpps lappend ::tmpFiles $tmppdf foreach cmd [$::pfmOptions getOption general printcmd] { set parmList [getParms $cmd] if {[llength $parmList] > 0} then { set dlg [GenForm "#auto" $parent [mc sqlPrintOptions] $parmList] $dlg displayHelpText [mc sqlPrintHelp [longestLine $txtWidget]] if {[$dlg wait result]} then { foreach parm $parmList { set full [dict get $parm full] set name [dict get $parm name] set value $result($name) set cmdList {} foreach cmdarg $cmd { lappend cmdList [string map [list $full $value] $cmdarg] } set cmd $cmdList } } else { break } } set cmd [string map [list %txt [list $tmptxt] %ps \ [list $tmpps] %pdf [list $tmppdf]] $cmd] catch [linsert $cmd 0 exec] execOut set message "${cmd}:\n${execOut}" pfm_message $message $parent } return } proc saveTxtFromWidget {txtWidget parent} { set fileTypes { {{Text} {.txt} } {{All files} *} } set defaultExt ".txt" set filename [tk_getSaveFile -title [mc miscSelectSaveText] \ -filetypes $fileTypes \ -defaultextension $defaultExt -parent $parent \ -initialdir [file normalize ~]] if {$filename ne {}} then { if {[catch {open $filename w} saveChan]} then { pfm_message $saveChan $parent } else { chan puts $saveChan [$txtWidget get 1.0 end] chan close $saveChan } } return } pfm-2.0.8/report.tcl0000644000175000017500000006043412110433462012477 0ustar wimwim# report.tcl class Report { protected variable parentWindow protected variable reportName protected variable reportDef protected variable reportData protected variable reportLastTuple protected variable sectionFields protected variable fieldLabel protected variable fieldAlignment protected variable fieldMaxLength protected variable sectionLayout protected variable lastLevel protected variable summaryList protected variable reportTree protected variable lastSeqnr protected variable summaryData protected variable MaxLabelWidth protected variable MaxColWidth protected variable MaxSummaryWidth protected variable textEdit constructor {c_parentWindow c_reportName} { set reportName $c_reportName set parentWindow $c_parentWindow set textEdit [TextEdit "#auto" $parentWindow [mc reportTitle $reportName] {} 0] $textEdit defineCallBack [list $this onDestroyTextEdit] if {[getReportDef]} then { set query $reportDef(sqlselect) if {[replaceParms query]} then { if {[runQuery $query errMsg]} then { if {[getSectionDef]} then { # printSectionDef wrapTooLongLines fillReportTree 0 $reportLastTuple 1 0 calculateSummaries 1 0 printReport $query $textEdit gotoBegin } } else { printReportHeader $query showError $errMsg } } } return } destructor { return } public method onDestroyTextEdit {} { delete object $textEdit delete object $this return } protected method printSectionDef {} { # for debugging only for {set level 1} {$level <= $lastLevel} {incr level} { puts "Section $level" puts " Layout: $sectionLayout($level)" foreach field $sectionFields($level) { puts " Field: $field" puts " Label: $fieldLabel($field)" puts " Alignment: $fieldAlignment($field)" puts " MaxLength: $fieldMaxLength($field)" } puts "Summary: $summaryList($level)" } return } protected method wrapTooLongLines {} { proc wrapLine {lineVar maxLength} { upvar 1 $lineVar line if {[string length $line] > $maxLength} then { # We try to find a space between 30% of maxLength and maxLength set breakTo [expr $maxLength - 1] set breakFrom [expr int(0.3 * $maxLength)] set wrappedLine {} while {[string length $line] > $maxLength} { set oneLine [string range $line 0 $breakTo] set breakAt [string last { } $oneLine] if {$breakAt >= $breakFrom} then { # If possible we replace a space with a line break # So, we don't copy the space set oneLine [string range $oneLine 0 [expr $breakAt - 1]] set line [string range $line [expr $breakAt + 1] end] } else { # If not possible we just insert a line break set line [string range $line [expr $breakTo + 1] end] } append wrappedLine "${oneLine}\n" } append wrappedLine $line set line $wrappedLine } return } for {set level 1} {$level <= $lastLevel} {incr level} { foreach field $sectionFields($level) { if {$fieldMaxLength($field) ne {}} then { for {set tuple 0} {$tuple <= $reportLastTuple} {incr tuple} { set data $reportData($tuple,$field) set newData {} set moreLines 1 while {$moreLines} { set moreLines 0 set newLineIndex [string first "\n" $data] if {$newLineIndex >= 0} then { set moreLines 1 set oneLine [string range $data 0 \ [expr $newLineIndex - 1]] set data [string range $data \ [expr $newLineIndex + 1] end] wrapLine oneLine $fieldMaxLength($field) append newData "${oneLine}\n" } else { set oneLine $data wrapLine oneLine $fieldMaxLength($field) append newData $oneLine } } set reportData($tuple,$field) $newData } } } } return } protected method runQuery {query errMsgName} { upvar $errMsgName errMsg if {[$::dbObject select_query $query numTuples reportData errMsg]} then { if {$numTuples > 0} then { set success 1 set reportLastTuple [expr $numTuples - 1] } else { set success 0 set errMsg [mc reportQueryNoData] } } else { set success 0 } return $success } protected method getReportDef {} { set query {SELECT name, description, sqlselect FROM pfm_report} append query " WHERE name = '$reportName'" if {[$::dbObject select_query_list $query numTuples attribList resultList errorMsg]} then { if {$numTuples == 1} then { set status 1 set idx 0 foreach attrib $attribList { set reportDef($attrib) [lindex $resultList 0 $idx] incr idx } } else { showError [mc getReportFailed $reportName "numTuples = $numTuples"] set status 0 } } else { showError [mc getReportFailed $reportName $errorMsg] set status 0 } return $status } protected method replaceParms {queryName} { upvar $queryName query proc getParmDef {query parmListName} { upvar $parmListName parmList set parmList {} set startOfParm [string first "\$(" $query 0] while {$startOfParm >= 0} { set endOfParm [string first ")" $query $startOfParm] if {$endOfParm >= 0} then { set parm [string range $query [expr $startOfParm + 2] \ [expr $endOfParm - 1]] if {$parm ni $parmList} then { lappend parmList $parm } set startOfParm \ [string first "\$(" $query [expr $endOfParm + 1]] } else { set startOfParm -1 } } return } getParmDef $query parmList if {[llength $parmList] > 0} then { if {[getParmValues $query $parmList value]} then { foreach parm $parmList { set query [string map \ [list [format {$(%s)} $parm] $value($parm)] $query] incr idx } set success 1 } else { set success 0 } } else { set success 1 } return $success } protected method getParmValues {query parmList valueName} { upvar $valueName value update set parmObject [ParmObject "#auto" [$textEdit cget -window] \ $query $parmList] if {[$parmObject wait value]} then { set success 1 } else { set success 0 } return $success } protected method getSectionDef {} { set query {SELECT level, fieldlist, layout, summary FROM pfm_section} append query " WHERE report = '${reportName}' ORDER BY level" if {[$::dbObject select_query $query numTuples sectionDef errMsg]} then { if {$numTuples >= 1} then { set level 0 for {set tuple 0} {$tuple < $numTuples} {incr tuple} { # We don't use the level numbers from pfm_section. We only # rely on the numbers for pfm_section for properly # ordering the sections. First level should be 1, next # levels should be consecutive integers. incr level set sectionLayout($level) $sectionDef(${tuple},layout) if {[catch { foreach item $sectionDef(${tuple},fieldlist) { lassign $item field label alignment maxLength } } errMsg]} then { showError [mc errorParsingFieldSpec \ $sectionDef(${tuple},fieldlist) $level $errMsg] set sectionDef(${tuple},fieldlist) {} } set sectionFields($level) {} foreach item $sectionDef(${tuple},fieldlist) { lassign $item field label alignment maxLength if {[info exists reportData(0,$field)]} then { lappend sectionFields($level) $field set fieldLabel($field) $label set fieldAlignment($field) $alignment set fieldMaxLength($field) $maxLength } else { showError [mc reportDataMissing $field $level] } } set summaryList($level) $sectionDef(${tuple},summary) } set lastLevel $level set success 1 } else { set success 0 showError [mc noReportSections] } } else { set success 0 showError "${errMsg}\n" } return $success } protected method showError {message} { $textEdit appendText "${message}\n" red return } protected method printString {string} { $textEdit appendText $string black return } protected method fillReportTree {firstTuple lastTuple level parent} { # the range $firstTuple .. $lastTuple is the range of tuples # in reportData that have the same vaules for all the fields # of all the levels lower than $level. # $parent is the node in the reportTree that points to a tuple # in reportData that has the values for the fields of all the # levels lower than $level. # We now create a node at $level for all records in the range # that have different values for at least one of the fields of # this level and we call fillReportTree recursively for each # node created at this level to also fill the higher levels. # At $lastLevel, we create a node for all tuples in the range, # and we don't call fillReportTree anymore. set seqnr 1 set reportTree(${parent}.${seqnr}) $firstTuple set nextLevelFirstTuple $firstTuple if {$level < $lastLevel} then { for {set tuple [expr $firstTuple + 1]} {$tuple <= $lastTuple} \ {incr tuple} { if {[differentData $level $tuple]} then { fillReportTree $nextLevelFirstTuple [expr $tuple - 1] \ [expr $level + 1] "${parent}.${seqnr}" incr seqnr set reportTree(${parent}.${seqnr}) $tuple set nextLevelFirstTuple $tuple } } fillReportTree $nextLevelFirstTuple $lastTuple \ [expr $level + 1] "${parent}.${seqnr}" } else { for {set tuple [expr $firstTuple + 1]} {$tuple <= $lastTuple} \ {incr tuple} { incr seqnr set reportTree(${parent}.${seqnr}) $tuple } } set lastSeqnr($parent) $seqnr return } protected method differentData {level tuple} { set different 0 foreach field $sectionFields($level) { if {$reportData([expr $tuple - 1],$field) ne \ $reportData($tuple,$field)} then { set different 1 break } } return $different } protected method calculateSummaries {level parent} { # Check summary definition if {[catch { foreach item $summaryList($level) { lassign $item field aggregate format } } tclerror]} then { showError [mc errorParsingSummary $summaryList($level) \ $level $tclerror] set summaryList($level) {} } # Now we really begin foreach item $summaryList($level) { lassign $item field aggregate format set values {} if {[accumulateValues $level $parent $field values]} then { if {[info commands ::aggregate::$aggregate] ne {}} then { set summaryData($parent,$field,$aggregate) \ [aggregate::$aggregate $values] } else { showError [mc unknownAggregateFunction $aggregate \ $field $level] set index [lsearch $summaryList($level) $item] set summaryList($level) \ [lreplace $summaryList($level) $index $index] } } else { showError [mc summaryWrongField $aggregate $field $level] set index [lsearch $summaryList($level) $item] set summaryList($level) \ [lreplace $summaryList($level) $index $index] } } if {$level < $lastLevel} then { for {set seqnr 1} {$seqnr <= $lastSeqnr($parent)} \ {incr seqnr} { calculateSummaries [expr $level + 1] "${parent}.${seqnr}" } } return } protected method accumulateValues {level parent field valueListName} { upvar 1 $valueListName values if {[lsearch $sectionFields($level) $field] >= 0} then { for {set seqnr 1} {$seqnr <= $lastSeqnr($parent)} \ {incr seqnr} { set tuple $reportTree(${parent}.${seqnr}) lappend values $reportData($tuple,$field) } set success 1 } else { if {$level < $lastLevel} then { for {set seqnr 1} {$seqnr <= $lastSeqnr($parent)} \ {incr seqnr} { set success [accumulateValues [expr $level + 1] \ "${parent}.${seqnr}" $field values] } } else { set success 0 } } return $success } protected method printReport {query} { labelAndColumnWidths printReportHeader $query printReportTree 1 0 return } protected method labelAndColumnWidths {} { # MaxLabelWidth($level) is needed for a column layout. # It determines the larges label for each level for {set level 1} {$level <= $lastLevel} {incr level} { set MaxLabelWidth($level) 0 foreach field $sectionFields($level) { set length [string length $fieldLabel($field)] if {$length > $MaxLabelWidth($level)} then { set MaxLabelWidth($level) $length } } } # MaxDataWith($field) is needed for a table layout # It takes into account that there may be line breaks in the data for {set level 1} {$level <= $lastLevel} {incr level} { foreach field $sectionFields($level) { set MaxColWidth($field) [string length $fieldLabel($field)] for {set tuple 0} {$tuple <= $reportLastTuple} {incr tuple} { set data $reportData($tuple,$field) set moreLines 1 while {$moreLines} { set moreLines 0 set newLineIndex [string first "\n" $data] if {$newLineIndex >= 0} then { set moreLines 1 set oneLine [string range $data 0 [expr $newLineIndex - 1]] set data [string range $data [expr $newLineIndex + 1] end] } else { set oneLine $data set data {} } set length [string length $oneLine] if {$length > $MaxColWidth($field)} then { set MaxColWidth($field) $length } } } } } # MaxSummaryWidth($level) is needed for properly formatting # the summaries for {set level 1} {$level <= $lastLevel} {incr level} { set MaxSummaryWidth($level) 0 foreach item $summaryList($level) { set field [lindex $item 0] set aggregate [lindex $item 1] set length [string length "$aggregate\($field\)"] if {$length > $MaxSummaryWidth($level)} then { set MaxSummaryWidth($level) $length } } } return } protected method printReportHeader {sqlselect} { printString "$reportDef(name)\n" printString \ "[string repeat - [string length $reportDef(name)]]\n\n" printString \ "Description: $reportDef(description)\n" set formattedSQL [string map {\n "\n "} $sqlselect] printString "SQL : $formattedSQL\n" printString \ "Date : [clock format [clock seconds] -format {%d-%b-%Y}]\n" return } protected method printReportTree {level parent} { set firstChild 1 for {set seqnr 1} {$seqnr <= $lastSeqnr($parent)} {incr seqnr} { printNode $level ${parent}.${seqnr} $firstChild set firstChild 0 if {$level < $lastLevel} then { printReportTree [expr $level + 1] ${parent}.${seqnr} } } printSummary $level $parent return } protected method printNode {level node firstRecord} { set tuple $reportTree($node) set margin [string repeat " " [expr 6 * $level]] set sectionNr "[string range $node 2 end]. " set nrLength [string length $sectionNr] set beforeSectionNr [string repeat " " [expr (6 * $level) - $nrLength]] set sectionNr "${beforeSectionNr}${sectionNr}" switch -- $sectionLayout($level) { "row" { printRow $level $tuple $firstRecord $margin $sectionNr } "table" { printTable $level $tuple $firstRecord $margin $sectionNr } default { printColumn $level $tuple $firstRecord $margin $sectionNr } } return } protected method printRow {level tuple firstRecord margin sectionNr} { if {$firstRecord || ($level != $lastLevel)} then { set output "\n" } else { set output {} } if {$level != $lastLevel} then { append output $sectionNr } else { append output $margin } foreach field $sectionFields($level) { append output "$fieldLabel($field): $reportData($tuple,$field); " } append output "\n" printString $output return } protected method printColumn {level tuple firstRecord margin sectionNr} { set output "\n" set firstLine 1 foreach field $sectionFields($level) { set formatString "%-$MaxLabelWidth($level)\s" if {$firstLine} then { set label $sectionNr } else { set label $margin } set firstLine 0 append label "[format $formatString $fieldLabel($field)] : " append output $label set nextLineOffset [string repeat { } [string length $label]] set startIdx 0 set nlIdx [string first "\n" $reportData($tuple,$field) $startIdx] while { $nlIdx >= 0 } { append output \ [string range $reportData($tuple,$field) $startIdx $nlIdx] append output $nextLineOffset set startIdx [expr $nlIdx + 1] set nlIdx [string first "\n" $reportData($tuple,$field) $startIdx] } append output \ "[string range $reportData($tuple,$field) $startIdx end]\n" } printString $output return } protected method printTable {level tuple firstRecord margin sectionNr} { if {$firstRecord || ($level != $lastLevel)} then { printTableHeader $level $margin $sectionNr } foreach field $sectionFields($level) { switch -- $fieldAlignment($field) { "r" { set formatString($field) "%$MaxColWidth($field)\s" } "l" - default { set formatString($field) "%-$MaxColWidth($field)\s" } } set data($field) $reportData($tuple,$field) } set moreLines 1 while {$moreLines} { set output {} set moreLines 0 foreach field $sectionFields($level) { set newLineIdx [string first "\n" $data($field)] if {$newLineIdx >= 0} then { set oneLine \ [string range $data($field) 0 [expr $newLineIdx - 1]] set data($field) \ [string range $data($field) [expr $newLineIdx + 1] end] set moreLines 1 } else { set oneLine $data($field) set data($field) {} } append output \ "| [format $formatString($field) $oneLine] " } set output "${margin}[string range $output 1 end]\n" printString $output } return } protected method printTableHeader {level margin sectionNr} { set header {} set underline {} foreach field $sectionFields($level) { switch $fieldAlignment($field) { r { set formatString "%$MaxColWidth($field)\s" } l - default { set formatString "%-$MaxColWidth($field)\s" } } append header \ "| [format $formatString $fieldLabel($field)] " append underline \ "+-[string repeat - $MaxColWidth($field)]-" } set header [string range $header 1 end] set underline [string range $underline 1 end] set output "\n${margin}${header}\n" set nrLength [string length $sectionNr] if {$level != $lastLevel} then { append output "${sectionNr}${underline}\n" } else { append output "${margin}${underline}\n" } printString $output return } protected method printSummary {level parent} { set margin [string repeat " " [expr 6 * ($level - 1)]] set summaryText "Summary [string range $parent 2 end] : " set summaryTextLength [string length $summaryText] set firstLine 1 foreach item $summaryList($level) { if {$firstLine} then { set output "\n${margin}${summaryText}" } else { set output \ "${margin}[string repeat { } $summaryTextLength]" } set firstLine 0 set field [lindex $item 0] set aggregate [lindex $item 1] set format [lindex $item 2] set label "${aggregate}\($field\)" set formatLabel "%-$MaxSummaryWidth($level)\s" append output "[format $formatLabel $label] = " set value $summaryData($parent,$field,$aggregate) if {$format ne {}} then { if {[catch {format $format $value} formattedValue]} then { showError [mc summaryFormatError $format $aggregate \ $field $level $formattedValue] set formattedValue $value } append output "$formattedValue\n" } else { append output "${value}\n" } printString $output } return } } class ParmObject { protected variable window protected variable parmList protected variable pressedOK 0 protected variable waitCalled 0 protected variable data protected variable dataIdx protected variable txtQuery constructor {parent query c_parmList} { set parmList $c_parmList set window [toplevel \ [appendToPath $parent [namespace tail $this]]] wm transient $window $parent set x [expr [winfo rootx $parent] + 100] set y [expr [winfo rooty $parent] + 50] wm geometry $window "500x400+${x}+${y}" wm title $window [mc getReportParms] set frm1 [ttk::frame ${window}.frm1] set txtQuery [text ${frm1}.txt -width 1 -height 1] set vsb [ttk::scrollbar ${frm1}.vsb -orient vertical \ -command [list $txtQuery yview]] $txtQuery configure -yscrollcommand [list $vsb set] $txtQuery insert end $query grid $txtQuery -column 0 -row 0 -sticky wens grid $vsb -column 1 -row 0 -sticky ns grid columnconfigure $frm1 0 -weight 1 grid rowconfigure $frm1 0 -weight 1 set frm2 [ttk::frame ${window}.frm2] set idx 0 foreach parm $parmList { # Bug 1011308 # Because of bugs 211 and 238 in Itcl 3.4 and 4.0 # (see http://sourceforge.net/p/incrtcl/bugs/211/ # and http://sourceforge.net/p/incrtcl/bugs/238/) # a translation table dataIdx is introduced. set dataIdx($parm) $idx set data($idx) {} set lb($idx) [ttk::label ${frm2}.lb$idx -text $parm] set ent($idx) [entry ${frm2}.ent$idx \ -textvariable [scope data($idx)]] grid $lb($idx) -column 0 -row $idx grid $ent($idx) -column 1 -row $idx # bug 1069: add next statement incr idx } set frm3 [ttk::frame ${window}.frm3] set btnOK [defineButton ${frm3}.btnOK $window btnOK \ [list $this onOK]] set btnCancel [defineButton ${frm3}.btnCancel $window btnCancel \ [list $this onCancel]] pack $btnCancel -side right pack $btnOK -side right pack $frm1 -side top -expand 1 -fill both pack $frm2 -side top -padx {10 10} -pady {10 10} pack $frm3 -side top -padx {10 10} -pady {10 10} -fill x pack [ttk::sizegrip ${window}.sg] -side top -anchor e return } destructor { return } public method onDestroy {} { if {!$waitCalled} then { after idle [list delete object $this] } return } public method wait {resultVar} { upvar $resultVar result set waitCalled 1 tkwait window $window foreach parm $parmList { set result($parm) $data($dataIdx($parm)) } after idle [list delete object $this] return $pressedOK } public method onOK {} { set pressedOK 1 destroy $window return } public method onCancel {} { set pressedOK 0 destroy $window return } } namespace eval aggregate { proc SUM {List} { set sum 0 foreach item $List { if {[string is double -strict $item]} then { set sum [expr $sum + $item] } } return $sum } proc COUNT {List} { return [llength $List] } proc AVG {List} { set sum 0.0 set count 0 foreach item $List { if {[string is double -strict $item]} then { set sum [expr $sum + $item] incr count } } if {$count != 0} then { set avg [expr double($sum) / double($count)] } else { set avg 0.0 } return $avg } proc STDDEV {List} { set sum 0.0 set count 0 foreach item $List { if {[string is double -strict $item]} then { set sum [expr $sum + $item] incr count } } if {$count != 0} then { set avg [expr double($sum) / double($count)] } else { set avg 0.0 } set squareDev 0.0 foreach item $List { if {[string is double -strict $item]} then { set dev [expr double($item) - double($avg)] set squareDev [expr $squareDev + $dev * $dev] } } if {$count != 1} then { set stddev [expr sqrt(double($squareDev) / double($count - 1))] } else { set stddev 0.0 } return $stddev } proc MIN {List} { set numeric 1 set min {} foreach item $List { if {$item ne {}} then { set min $item if {![string is double -strict $item]} then { set numeric 0 } } } if {$numeric} then { foreach item $List { if {$item ne {}} then { if {$item < $min} then { set min $item } } } } else { foreach item $List { if {[string compare -nocase $min $item] == 1} then { set min $item } } } return $min } proc MAX {List} { set numeric 1 set max {} foreach item $List { if {$item ne {}} then { set max $item if {![string is double -strict $item]} then { set numeric 0 } } } if {$numeric} then { foreach item $List { if {$item ne {}} then { if {$item > $max} then { set max $item } } } } else { foreach item $List { if {[string compare -nocase $max $item] == -1} then { set max $item } } } return $max } } # test aggregate functions # set lists { # {5 6 3 {} 8 11} # {} # {{}} # {23.5 een twee 13.0 7} # {-2 3.45 6.25 9} # {aarde Aarde aardappel {} appel grondpeer peer} # } # foreach list $lists { # puts "list = $list" # puts "COUNT = [COUNT $list]" # puts "SUM = [SUM $list]" # puts "AVG = [AVG $list]" # puts "STDDEV = [STDDEV $list]" # puts "MIN = [MIN $list]" # puts "MAX = [MAX $list]" # puts "------------------------" # } pfm-2.0.8/sql.tcl0000644000175000017500000003444411001104701011752 0ustar wimwim# sql.tcl # The purpose of this program is to implement a GUI frontend for a # command line sql program sql (like psql of postgresql). # Commands to sql are typed in the upper text widget. When pressing # Run, the command is transferred to sql via a command pipe. This pipe # is opened in the method connect_sql of the $::dbObject and creates # a channel psqlChan. Wirting to this pipe sends input to the sql # command line tool (e.g. psql in the case of postgresql). The # stdout and stderr from this pipe are redirected to channels outChan # and errChan respectively. # The are based on a simple script "cat.tcl" wich mimics # the UNIX cat utility: it prints everything it reads on stdin to # stdout. Each channel runs in a separate instance of the tcl interpreter. # They are setup in the methods openOutChannel and openErrChannel of # the Sql objects. # The following procedures in the global namespace read the channels # outChan and errChan, and display everyting that is received on a # txtWidget. The errChan writes with tag "redTag". These procedures # are bound to the "channel readable event". # Before deleting this object, first call the destroyWindow method. # Otherwise, the window and the psql connection become orphans. class Sql { public variable window protected variable windowTag protected variable txtSQL protected variable txtResult protected variable wrapOutput protected variable parent protected variable dbname protected variable errChan protected variable outChan protected variable sqlChan protected variable history protected variable top protected variable cursor protected variable btn constructor {c_parent} { set parent $c_parent set dbname [$::dbObject cget -dbname] initHistory setupWindow connectSql return } destructor { return } public method showWindow {} { if {$window ne {}} then { wm deiconify $window raise $window focus $window } else { setupWindow connectSql } return } public method destroyWindow {} { destroy $window return } public method onDestroyWindow {} { set window {} disconnectSql return } public method processOutChan {} { if {![chan eof $outChan]} then { $txtResult insert end "[chan gets $outChan]\n" $txtResult see end } else { chan event $outChan readable {} } return } public method processErrChan {} { if {![chan eof $errChan]} then { $txtResult insert end "[chan gets $errChan]\n" {redTag} $txtResult see end } else { chan event $errChan readable {} } return } public method onClear {} { $txtSQL delete 1.0 end set history($top) {} set cursor $top if {$top > 0} then { $btn(Prev) state {!disabled} } else { $btn(Prev) state {disabled} } $btn(Next) state {disabled} return } public method onPrev {} { if {$cursor == $top} then { set history($top) [$txtSQL get 1.0 "end - 1 chars"] } if {$cursor > 0} then { incr cursor -1 $txtSQL delete 1.0 end $txtSQL insert end $history($cursor) $btn(Next) state {!disabled} if {$cursor == 0} then { $btn(Prev) state {disabled} } } else { bell } return } public method onNext {} { if {$cursor < $top} then { incr cursor $txtSQL delete 1.0 end $txtSQL insert end $history($cursor) if {$cursor == $top} then { $btn(Next) state {disabled} } $btn(Prev) state {!disabled} } else { bell } return } public method onReturn {} { if {([$txtSQL get "end - 3 chars"] eq {;}) || \ ([$txtSQL get 1.0] eq "\\")} then { onRun } return } public method onRun {} { set cmd [string trim [$txtSQL get 1.0 end]] storeCommand $cmd chan puts $sqlChan $cmd chan flush $sqlChan $txtSQL delete 1.0 end if {$cmd eq [$::dbObject getSpecialCommand quit]} then { destroy $window } return } public method connectSql {} { set errChan [openErrChannel] set outChan [openOutChannel] set status [$::dbObject connect_sql $errChan $outChan sqlChan] return $status } public method disconnectSql {} { chan close $sqlChan chan close $outChan chan close $errChan return } public method wrapChanged {} { $txtResult configure -wrap $wrapOutput return } public method executeScript {filename encoding} { set convertedFile [convertToUTF-8 $filename $encoding $window] $txtSQL delete 1.0 end set cmd [$::dbObject getSpecialCommand importFile] $txtSQL insert end "$cmd '${convertedFile}'" onRun return } public method onListDatabases {} { set cmd [$::dbObject getSpecialCommand listDatabases] $txtSQL delete 1.0 end $txtSQL insert end $cmd onRun return } public method onListTables {} { set cmd [$::dbObject getSpecialCommand listTables] $txtSQL delete 1.0 end $txtSQL insert end $cmd onRun return } public method onImport {} { set initialDir [file normalize ~] set fromEncoding [encoding system] set fileTypes { {{SQL statements} {.sql} } {{All files} *} } set defaultExt ".sql" set fileName [tk_getOpenFile -title [mc sqlGetImportFile] \ -filetypes $fileTypes \ -defaultextension $defaultExt -parent $window \ -initialdir $initialDir] if {$fileName ne {}} then { set dlg [ImportDlg "#auto" $window] if {[$dlg wait fromEncoding importType]} then { switch -- $importType { direct { if {[catch {open $fileName r} inFile]} then { pfm_message $inFile $window } else { chan configure $inFile -encoding $fromEncoding $txtSQL delete 1.0 end $txtSQL insert end [chan read -nonewline $inFile] chan close $inFile } } execute { executeScript $fileName $fromEncoding } } } } return } public method onSaveSQL {} { set fileTypes { {{SQL statements} {.sql} } {{All files} *} } set defaultExt ".sql" set filename [tk_getSaveFile -title [mc sqlSelectSaveSQL] \ -filetypes $fileTypes \ -defaultextension $defaultExt -parent $window \ -initialdir [file normalize ~]] if {$filename ne {}} then { if {[catch {open $filename w} saveChan]} then { pfm_message $saveChan $window } else { chan puts $saveChan [$txtSQL get 1.0 end] chan close $saveChan } } return } public method onSaveOutput {} { saveTxtFromWidget $txtResult $window return } public method onClearOutput {} { $txtResult delete 1.0 end $txtResult yview 0 return } public method onPrintOutput {} { printTextWidget $txtResult $window return } public method onHelpSql {} { set cmd [$::dbObject getSpecialCommand helpSQL] $txtSQL delete 1.0 end $txtSQL insert end $cmd onRun return } public method onHelpSqlCommand {} { set cmd "--[mc sqlTypeCmd]\n[$::dbObject getSpecialCommand helpSQL] " $txtSQL delete 1.0 end $txtSQL insert end $cmd return } public method onHelpSpecial {} { set cmd [$::dbObject getSpecialCommand helpTool] $txtSQL delete 1.0 end $txtSQL insert end $cmd onRun return } protected method setupWindow {} { set window [toplevel [appendToPath $parent [namespace tail $this]]] lappend windowList $window wm title $window [mc sqlTitle $dbname] wm geometry $window [join $::geometry::sql {x}] setupMenus set pw [ttk::panedwindow $window.pw -orient vertical] set frm1 [ttk::labelframe $pw.frm1 -text [mc sqlStatement] \ -labelanchor n] set txtSQL [text $frm1.txt -wrap word -width 1 -height 1] set vsbSQL [ttk::scrollbar $frm1.vsb -orient vertical \ -command [list $txtSQL yview]] $txtSQL configure -yscrollcommand [list $vsbSQL set] grid $txtSQL -column 0 -row 0 -sticky wens grid $vsbSQL -column 1 -row 0 -sticky ns grid columnconfigure $frm1 0 -weight 1 grid rowconfigure $frm1 0 -weight 1 set frmButtons [ttk::frame $frm1.btns] foreach name {Run Next Prev Clear} { set btn($name) [defineButton $frmButtons.btn$name $window btn$name \ [list $this on$name]] pack $btn($name) -side right } bind $window \ [list $btn(Prev) instate {!disabled} [list $btn(Prev) invoke]] bind $window \ [list $btn(Next) instate {!disabled} [list $btn(Next) invoke]] if {$cursor < $top} then { $btn(Next) state {!disabled} } else { $btn(Next) state {disabled} } if {$cursor > 0} then { $btn(Prev) state {!disabled} } else { $btn(Prev) state {disabled} } grid $frmButtons -column 0 -columnspan 2 -row 1 -sticky we \ -pady {10 10} -padx {10 10} $pw add $frm1 -weight 2 set frm2 [ttk::labelframe $pw.frm2 -text [mc sqlOutput] \ -labelanchor n] set txtResult [text $frm2.txt -wrap none -width 1 -height 1 \ -takefocus 0] set vsbResult [ttk::scrollbar $frm2.vsb -orient vertical \ -command [list $txtResult yview]] set hsbResult [ttk::scrollbar $frm2.hsb -orient horizontal \ -command [list $txtResult xview]] $txtResult configure \ -xscrollcommand [list $hsbResult set] \ -yscrollcommand [list $vsbResult set] $txtResult tag configure redTag -foreground {red3} set btnWrap [defineCheckbutton $frm2.btnWrap $window \ btnWrap [list $this wrapChanged] [scope wrapOutput] \ word none] grid $txtResult -column 0 -row 0 -sticky wens grid $vsbResult -column 1 -row 0 -sticky ns grid $hsbResult -column 0 -row 1 -sticky we grid $btnWrap -column 0 -row 2 -sticky w grid columnconfigure $frm2 0 -weight 1 grid rowconfigure $frm2 0 -weight 1 $pw add $frm2 -weight 3 pack $pw -side top -expand 1 -fill both pack [ttk::sizegrip ${window}.sg] -side top -anchor e set tpOnly [bindToplevelOnly $window \ [list $this onDestroyWindow]] bind $tpOnly {set ::geometry::sql {%w %h}} bind $window [list destroy $window] focus $txtSQL bind $txtSQL [list after idle [list $this onReturn]] return } protected method setupMenus {} { set menubar [menu ${window}.mb -tearoff 0] # Menu SQL set mnuSql [menu ${menubar}.sql -tearoff 0] addMenuItem $mnuSql sqlMnuDatabases command [list $this onListDatabases] addMenuItem $mnuSql sqlMnuImport command [list $this onImport] addMenuItem $mnuSql sqlMnuListTables command [list $this onListTables] addMenuItem $mnuSql sqlMnuSaveToFile command [list $this onSaveSQL] $mnuSql add separator addMenuItem $mnuSql sqlMnuClose command [list destroy $window] # Menu Output set mnuOutput [menu ${menubar}.output -tearoff 0] addMenuItem $mnuOutput sqlMnuOutputSave command [list $this onSaveOutput] addMenuItem $mnuOutput sqlMnuOutputPrint command [list $this onPrintOutput] addMenuItem $mnuOutput sqlMnuOutputClear command [list $this onClearOutput] # Menu Help set mnuHelp [menu ${menubar}.help -tearoff 0] addMenuItem $mnuHelp sqlMnuHelpSQL command [list $this onHelpSql] addMenuItem $mnuHelp sqlMnuHelpSpecial command [list $this onHelpSpecial] addMenuItem $mnuHelp sqlMnuHelpSQLcommand command [list $this onHelpSqlCommand] # Add alle menus to menubar addMenuItem $menubar sqlMnuSql cascade $mnuSql addMenuItem $menubar sqlMnuOutput cascade $mnuOutput addMenuItem $menubar sqlMnuHelp cascade $mnuHelp $window configure -menu $menubar return } protected method openErrChannel {} { global tcl_platform set cat [file join $::config::installDir cat.tcl] if {$tcl_platform(platform) eq {windows}} then { set tclshell [file join $::config::installDir tclkitsh.exe] } else { set tclshell [info nameofexecutable] } set errChan [open [list | $tclshell $cat] RDWR] chan configure $errChan -encoding utf-8 -buffering line chan event $errChan readable [list $this processErrChan] return $errChan } protected method openOutChannel {} { global tcl_platform set cat [file join $::config::installDir cat.tcl] if {$tcl_platform(platform) eq {windows}} then { set tclshell [file join $::config::installDir tclkitsh.exe] } else { set tclshell [info nameofexecutable] } set outChan [open [list | $tclshell $cat] RDWR] chan configure $outChan -encoding utf-8 -buffering line chan event $outChan readable [list $this processOutChan] return $outChan } protected method initHistory {} { set top 0 set cursor 0 set history(0) {} return } protected method storeCommand {cmd} { if {[string range $cmd 0 1] ne \ [$::dbObject getSpecialCommand importFile]} then { set history($top) $cmd incr top set history($top) {} set cursor $top $btn(Prev) state {!disabled} $btn(Next) state {disabled} } return } } class ImportDlg { protected variable window protected variable charEncoding protected variable importType protected variable pressedOK 0 constructor {parent} { set charEncoding [encoding system] set importType execute setupWindow $parent return } destructor { return } public method wait {fromEncodingName importTypeName} { upvar $fromEncodingName charEncodingToReturn upvar $importTypeName importTypeToReturn tkwait window $window set charEencodingToReturn $charEncoding set importTypeToReturn $importType after idle [list delete object $this] return $pressedOK } public method onOK {} { set pressedOK 1 destroy $window return } protected method setupWindow {parent} { set window [toplevel ${parent}.import] wm title $window [mc sqlImportTitle] wm transient $window $parent set lbHelpEncoding [ttk::label ${window}.lb1 \ -text [mc sqlHelpEncoding] -padding {10 10 10 10}] set lbCharEncoding [ttk::label ${window}.lb2 \ -text [mc sqlCharEncoding]] set cmbEncoding [ttk::combobox ${window}.cmbenc \ -textvariable [scope charEncoding] \ -values [encoding names]] set cmd [$::dbObject getSpecialCommand importFile] set lbHelpType [ttk::label ${window}.lb3 \ -text [mc sqlHelpImportType $cmd] -padding {10 10 10 10}] set rbExecute [defineRadiobutton ${window}.rb0 $window \ [mc sqlRbExecute $cmd] {} [scope importType] execute] set rbImport [defineRadiobutton ${window}.rb1 $window \ [mc sqlRbImport] {} [scope importType] direct] set frmBtns [ttk::frame ${window}.frmbtns] set btnOK [defineButton ${frmBtns}.btnOK $window btnOK \ [list $this onOK]] set btnCancel [defineButton ${frmBtns}.btnCancel $window btnCancel \ [list destroy $window]] grid $lbHelpEncoding -column 0 -row 0 -columnspan 2 grid $lbCharEncoding -column 0 -row 1 grid $cmbEncoding -column 1 -row 1 grid $lbHelpType -column 0 -row 2 -columnspan 2 grid $rbExecute -column 0 -columnspan 2 -row 3 -sticky w -padx 10 grid $rbImport -column 0 -columnspan 2 -row 4 -sticky w -padx 10 pack $btnCancel -side right pack $btnOK -side right grid $frmBtns -column 1 -row 5 -sticky e return } } pfm-2.0.8/config.tcl0000644000175000017500000000441012110362373012423 0ustar wimwim# config.tcl namespace eval config { variable installDir variable docDir variable licenseDir variable exampleDir variable languageDir variable defaultBrowser variable defaultPrintcmd variable version {2.0.8} variable dbversion {1.5.0} set installDir [file dirname [file normalize [info script]]] if {[string equal $tcl_platform(platform) "unix"]} then { set configFile [file join $installDir {pfm.conf}] if {![file exists $configFile]} then { set configFile {/etc/pfm.conf} } if {[catch {open $configFile r} confChan]} then { tk_messageBox -type ok -icon error -message $confChan exit } else { while {![eof $confChan]} { set line [gets $confChan] if {([string length $line] != 0) && \ ([string compare -length 1 $line "#"] != 0)} then { set [lindex $line 0] [lindex $line 1] } } close $confChan if {[string compare -length 1 $docDir "/"] != 0} then { # The directory indicated is relative to the installDir set docDir [file join $installDir $docDir] } if {[string compare -length 1 $licenseDir "/"] != 0} then { # The directory indicated is relative to the installDir set licenseDir [file join $installDir $licenseDir] } if {[string compare -length 1 $exampleDir "/"] != 0} then { # The directory indicated is relative to the installDir set exampleDir [file join $installDir $exampleDir] } if {[string compare -length 1 $languageDir "/"] != 0} then { # The directory indicated is relative to the installDir set languageDir [file join $installDir $languageDir] } } } else { # On non-UNIX platforms there is no configuration file. set docDir [file join $installDir {doc}] set licenseDir [file join $installDir {license}] set exampleDir [file join $installDir {examples}] set languageDir [file join $installDir {msgs}] set defaultBrowser {} set defaultPrintcmd {} } } pfm-2.0.8/convert_from_1.0.4.sql0000644000175000017500000003612210661565715014441 0ustar wimwim-- convert_from_1.0.4.sql -- -- The character encoding of this file is iso8859-1 (latin1) -- register pfm_version CREATE TABLE pfm_version ( seqnr serial primary key, version text, "date" date, comment text); INSERT INTO pfm_version (version, "date", comment) VALUES ('1.1.1', CURRENT_DATE, 'convert_from_1.0.4.sql'); -- Replace groupby_having with groupby in form definition ALTER TABLE pfm_form RENAME groupby_having TO groupby; UPDATE pfm_attribute SET attribute = 'groupby' WHERE (form = 'pfm_form') AND (attribute = 'groupby_having'); UPDATE pfm_form SET sqlselect = 'oid, name, tablename, sqlselect, sqlfrom, groupby, showform, "view", help' WHERE name = 'pfm_form'; -- Convert old pfm_report, to new pfm_report UPDATE pfm_report SET table_or_view = 'SELECT * FROM ' || table_or_view || CASE WHEN sqlwhere <> '' THEN ' WHERE ' || sqlwhere ELSE '' END || CASE WHEN orderby <> '' THEN ' ORDER BY ' || orderby ELSE '' END; ALTER TABLE pfm_report RENAME table_or_view TO sqlselect; ALTER TABLE pfm_report DROP sqlwhere CASCADE; ALTER TABLE pfm_report DROP orderby CASCADE; -- Modify the attributes of the form pfm_report UPDATE pfm_attribute SET attribute = 'sqlselect', typeofget = 'tgDirect', sqlselect = '' WHERE (form = 'pfm_report') AND (attribute = 'table_or_view'); DELETE FROM pfm_attribute WHERE (form = 'pfm_report') AND ((attribute = 'sqlwhere') OR (attribute = 'orderby')); -- Modify the data of pfm_report in pfm_form UPDATE pfm_form SET sqlselect = 'oid, name, description, sqlselect' WHERE name = 'pfm_report'; -- Modify the "tablename" attribute of the form for pfm_form UPDATE pfm_attribute SET typeofget = 'tgDirect', sqlselect = '' WHERE (form = 'pfm_form') AND (attribute = 'tablename'); -- Update the help texts in pfm_form -- pfm_attribute UPDATE pfm_form SET help = 'The table "pfm_attribute" defines all the properties of form attributes. It has the following attributes: - form : the "name" of the form to which the attribute belongs; - attribute : the name of the attribute; this must be equal to the name of the corresponding attribute of the form''s SQL SELECT statement; - typeofattrib : the type of attribute: o taQuoted: the value provided by the user is put between single quotes when it is transferred to SQL UPDATE or INSERT statements; o taNotQuoted: the value provided by the user is not quoted when it is transferred to SQL UPDATE or INSERT statements. Hint: In general, all attribute values must be quoted, exept the values or expressions for numeric attributes. - typeofget: defines how the user provides a value for the attribute; possible values are: o tgDirect: the user types the value directly; o tgExpression: the user types an expression which is first evaluated before it is passed to SQL UPDATE or INSERT; Note: Even with tgDirect it is possible to enter an expression as new value for an attribute, but then the expression is evaluated by postgresql whereas with tgExpression, the expression is first evaluated by Tcl before the SQL statement is sent to postgresql. o tgList: the user selects a value by means of a list box containing a list of values defined in table "pfm_value"; o tgLink: the user selects a value by means of a list box containing a list of values which is the result from a query on another table. o tgReadOnly: this attribute cannot be modified by the user. Note: All calculated attributes and all attributes from tables other than the form''s main table should be declared ''read-only''. If this rule is not observed, the Add and Update operations on this form will fail. - sqlselect: the SQL SELECT statement which is used to fill the list box with possible values for the attribute (only meaningful if typeofget = tgLink). Note : o The sqlselect may return more than 1 attribute. If so, all the attributes are displayed in the list-box, but only the first one is used for updating the attribute. - valuelist : the "name" of the value list defined in table "pfm_value_list" (only meaningful if typeofget = tgList); - nr: a number which determines the order in which attributes are displayed on the form; ' WHERE name = 'pfm_attribute'; -- pfm_form UPDATE pfm_form SET help = 'A form has a one-to-one relation with just 1 database table. Only the data of that table can be administered by means of the form. This table is henceforth referred to as "the form''s main table". However, the form also has a one-to-one relationship with just one SQL SELECT statement, which generates the data that are displayed on the form. In the simplest case the SQL SELECT statement is just: SELECT oid, FROM
In that case, the data which can be administered and the data which are displayed on the form are the same. In more complex cases, the
can be JOINED with other tables, which makes it possible to display data of other related tables as well. These data cannot be modified by means of the form. The table "pfm_form" has the following attributes: - name : the name of the form (usually equal to the name of the form''s table); - tablename : the name of the form''s main table; - sqlselect : the attribute list of the form''s SQL SELECT statement, not including the word ''SELECT''; - sqlfrom : the FROM clause of the form''s SQL SELECT statement, not including the word ''FROM''; - groupby : an optional ''GROUP BY'' clause, not including the words ''GROUP BY''; - view : a boolean indicating whether or not the "tablename" is a view; - showform : a boolean indicating whether the form is shown in "normal mode" (showform = ''true'') or in "design mode" (showform = ''false''). Typically, showform is set ''true'' for user defined forms and ''false'' for the predefined pfm_* forms. - help : a text which is displayed when the user presses the [Help] key on the form. The form''s main table is defined by tablename. Only the data of that table can be administered by using the form. All the data generated by the form''s SQL SELECT statement can be displayed on the form. The SQL SELECT statement is defined by: - the sqlselect, sqlfrom and groupby attributes of pfm_form; and - the optional WHERE and ORDER BY clauses provided by the user when opening the form. Note: The WHERE clause provided by the user when opening the form, is automatically converted to a HAVING clause, if there is a GROUP BY clause. The following rules should be observed when filling out sqlselect and sqlfrom: 1. The form''s main table must appear in ''sqlfrom'', and must not be aliased. Similarly, the main table''s attributes appearing in ''sqlselect'' must not be aliased. The other tables appearing in the ''sqlfrom'' may be aliased. 2. The fields appearing in ''sqlselect'' must have a unique, simple name without the need to precede them with a tablename. So, calculated fields must be given a name by aliasing and attributes of tables other than the main table may need to be aliased in order to have a unique, simple name. 3. If the form''s main table is not a view, the ''sqlselect'' must contain the ''oid'' of the main table. If the form''s main table is a view, the ''sqlselect'' must not contain the ''oid'' of the main table. Note: The ''oid'' is the ''object identifier''. It uniquely identifies a row in a table. All tables (not views) implicitly have an oid in postgreSQL. Postgres Forms needs it for identifying a row when issuing an SQL UPDATE or DELETE FROM command. There is no need to display this attribute on the form, i.e. it is not necessary to define it as an attribute in pfm_attribute. 4. The ''sqlfrom'' is either just the name of the form''s main table, or it is a JOIN clause in which 1 of the tables is the form''s main table. Several join clauses can be nested in order to involve more than 2 tables. See examples below. Example 1: the SQL SELECT for the person form of the addressbook database tablename: person sqlselect: oid, id, christian_name, name, street, town, "ZIPcode", country, category, description sqlfrom: person groupby: - Example 2: the SQL SELECT for the memberlist form of the addressbook database tablename: memberlist sqlselect: memberlist.oid, memberlist."group", memberlist.person, p.christian_name, p.name sqlfrom: memberlist LEFT OUTER JOIN person p ON (p.id = memberlist.person) groupby: -' WHERE name = 'pfm_form'; -- pfm_link UPDATE pfm_form SET help = 'A link is a navigation tool which allows you to follow a "one-to-many" or "many-to-one" relationship from one form to another. Every link is stored as a record in the pfm_link table, which has the following attributes: - linkname : the name of the link, which is displayed on a link button on the "fromform"; - fromform : the name of the form from which the link originates; - toform : the name of the form to which the link leads; - sqlwhere : the "WHERE"-clause which is used to open the "toform" and in which the value of an attribute of the "fromform" may be represented by $(attrib-x), where ''attrib-x'' is the name of the attribute; - orderby : an ''order by'' clause which determines the order of the records in the ''toform''; - displayattrib : a space separated list of attributes of the ''fromform'', the value of which is displayed on the ''toform'' to remind the user from which record the link originated. Note: Postgres Forms does not provide any checks to safeguard the referential integrity of the data base in case of updates or deletions. However, postgreSQL provides these functions as ''foreign key'' table constraints (see postgreSQL documentation).' WHERE name = 'pfm_link'; -- pfm_report UPDATE pfm_form SET help = 'The table pfm_report defines all the reports for the current data base. pfm_report has the following attributes: - name: the name of the report. This is the name that appears in the selection list of the "Run Report" function. - description: free text describing the purpose of the report in more detail. - sqlselect: an SQL SELECT statement that generates the data for the report. The sqlselect may contain one or more parameters for which a value is requested at "Run report" time. A parameter in the sqlwhere must be formatted as $(parameter_name). Example: sqlselect: SELECT g.name AS "group", g.description, p.id, p.name, p.christian_name, p.street, p."ZIPcode", p.town, p.country FROM "group" g LEFT JOIN memberlist m ON g.name = m."group" LEFT JOIN person p ON m.person = p.id WHERE "group" = ''$(group)'' ORDER BY g.name, p.name, p.christian_name When the report is run, the user is prompted to enter a value for the parameter "group". Then the report data are generated by executing the sqlselect statement in which $(group) is replaced with the value entered by the user.' WHERE name = 'pfm_report'; -- pfm_section UPDATE pfm_form SET help = 'The table pfm_section contains all the sections of the reports defined in pfm_report. A report must have one section at least. The table pfm_section has the following attributes: - report: the name of the report to which the section belongs - level: a number 1, 2, 3, 4, ... which uniquely identifies a section within a report and which also defines an order of the sections. - layout: can be "row", "column" or "table". - fieldlist: a space separated list of field specifiers, one for each field to be printed in this section of the report (see below for details). The fieldlist must be formatted as follows: {attribute_1 label_1 alignment_1} {attribute_2 label_2 alignment_2}... {attribute_N label_N alignment_N} where : - attribute_i is the name of the view attribute that has to be printed in the i-th field of this section; - label_i is a string which has to be used as label for printing the i-th field of this section; if it consists of more than 1 word, it must be delimited by double quotes (" .... "); - alignment_i is optional; if present, it is either l or r, indicating whether this field should be left or right aligned. Notes : o The alignment is optional. If it is left out, left alignment is assumed by default. o The alignment only influences the table layout. Column and row layouts are unaffected by the alignment indicator. o Multi-line fields, i.e. fields containing more than one line of text are only formatted properly in a column layout. All sections of a report, except the last one (i.e. the one with the highest level), are "group level sections". When the report is being generated, first the records resulting from executing the report''s SQL SELECT statement are stored internally. Then the data of the first record are printed, first the fields of the level 1 section, then the fields of the level 2 section, etc. up to the fields of the last section. When the next records are being printed, the "group level sections" are only printed when at least one of the fields belonging to that section has a value different from the corresponding field of the previously printed section of the same level. Only the highest level section (which is not a "group level section") is printed for all records. For every section, the layout can be defined as: - row: the section''s field labels and field values are printed in one row in a format: label_1 : value_1; label_2 : value_2; ... etc. - column: the section''s field labels are printed in a first column, the section''s field values are printed in a second column. - table: the section''s values are printed in a table with a column per field and a row per record, the section''s field labels are used as column headers for the table. ' WHERE name = 'pfm_section'; -- pfm_value UPDATE pfm_form SET help = 'The table "pfm_value" contains all the values of the lists defined in pfm_value_list. It has the following attributes: - valuelist : the name of the valuelist to which this value belongs - value : a character string; - description : a description of the value. ' WHERE name = 'pfm_value'; -- pfm_value_list UPDATE pfm_form SET help = 'The table "pfm_value_list" contains all the value lists of all the forms. Its only attribute is - name : a name uniquely identifying the value list. ' WHERE name = 'pfm_value_list'; pfm-2.0.8/README.txt0000644000175000017500000001133712110431407012151 0ustar wimwimContents: --------- 1. Copyright and contact information 2. Introduction 3. Installation 1. Copyright and contact information ------------------------------------- Postgres Forms (pfm) is a client application for PostgreSQL. Copyright © 2004-2013 Willem Herremans 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA A copy of the GNU General Public License is included in the 'help' subdirectory of this software package. The home page of Postgres Forms (pfm) can be found at: http://pgfoundry.org/projects/pfm/ There you can report bugs, request new features and get support. 2. Introduction ---------------- Postgres Forms (pfm) has been designed and tested on Linux. It may work on other UNIXes as well. It has also been tested on Windows XP. This is the source distribution: it does neither contain the Tclkit binaries, nor the pgintcl interface to PostgreSQL. If you are looking for a quick installation of pfm, you may be better off with the binary distributions (pfm-x.y.x-linux-x86.tar.gz or pfm-x.y.z-windows-32.exe) To get Postgres Forms (pfm) running you need to have access to at least one database on a PostgreSQL database server, which may be running either on your machine, or on another machine that you can access via TCP/IP. You also need the PostgreSQL client application 'psql' on your machine. See PostgreSQL documentation for details. Postgres Forms has been designed and tested with PostgreSQL version 7.4.2, but it probably works with some older and newer versions as well. It has also been tested with PostgreSQL 8.x and 9.0. You can obtain PostgreSQL from http://www.postgresql.org. pfm needs one of the packages: Pgtcl or pgintcl to communicate with the PostgreSQL database server. Pgtcl can be obtained from: http://pgfoundry.org/projects/pgtcl/ or, the new generation Pgtcl, from: http://sourceforge.net/projects/pgtclng/ pgintcl can be obtained from: http://sourceforge.net/projects/pgintcl/ Notes: - The Pgtcl package offers better performance, but needs to be built, whereas the pgintcl package is just a Tcl script that can be called by pfm. Pgtcl is included in most Linux distributions, often with the package name "libpgtcl". In the case of pfm, the difference in performance between Pgtcl and pgintcl is hardly noticeable. Moreover, I have had problems with Pgtcl on some Linux distributions. So, I recommend using pgintcl. - If the pgin.tcl file is present in the installation directory, pfm sources it and does not attempt to load the Pgtcl package. - Alternatively, you can also install pgintcl as a Tcl package. Then pfm will also find and load it as a Tcl package. From version 2.0.0. on Postgres Forms requires version 8.5 of Tcl/Tk. It also needs the packages Itcl 3.4 and msgcat 1.4.2. 3. Installation --------------- If you are looking for an easy installation of pfm, you may be better off with the binary distributions pfm-x.y.z-linux-x86 or pfm-x.y.z-windows-32. This is the source only distribution. But since pfm is written in Tcl, no compilation is required. To get pfm running on your system, you can do the following: 1. Install Tcl/Tk 8.5 or higher. 2. Make sure you have the Tcl packages Itcl 3.4 or higher and msgcat 1.4.2 or higher. 3. Copy all files and directories in this folder to where you want to install pfm (e.g. /usr/share/pfm). Alternatively, you can copy the subdirectories doc and license to other locations (e.g. /usr/share/doc/pfm and /usr/share/doc/licenses). In that case you also have to modify pfm.conf. You can keep pfm.conf in the installation directory (e.g. /usr/share/pfm) or you can move it to /etc. 4. Download pgintcl from http://sourceforge.net/projects/pgintcl/ 5. Copy the pgin.tcl script to the installation directory (e.g. /usr/share/pfm). Alternatively, you can install pgintcl as a Tcl package. 6. Make a pfm launch script in a directory in your PATH (e.g. /usr/bin/pfm). It should contain something like /usr/bin/tclsh /usr/share/pfm/pfm.tcl 7. Modify pfm-postgresforms.desktop as required. You can use it to make a desktop menu entry or a desktop icon, using the xdg-utils. pfm-2.0.8/arrow-home.xbm0000644000175000017500000000023511004643264013245 0ustar wimwim#define arrow_home_width 6 #define arrow_home_height 9 static unsigned char arrow_home_bits[] = { 0x21, 0x31, 0x39, 0x3d, 0x3f, 0x3d, 0x39, 0x31, 0x21 }; pfm-2.0.8/msgs/0000755000175000017500000000000012110362244011421 5ustar wimwimpfm-2.0.8/msgs/ROOT.msg0000644000175000017500000004234111604012573012724 0ustar wimwim# msgs/ROOT.msg # # To translate the user interface strings to another language, # make a copy of this file and store it in the same directory. Its # name should be something like: # # en.msg for English, or # en_us.msg for US English, or # en_gb.msg for UK English # nl.msg for Dutch, or # de.msg for German, or # de_ch.msg for Swiss German, etc. # # ROOT.msg is the default language file and will be used if there # is no msg-file matching the user's locale. # # This file must be encoded in UTF-8. Always check that your text editor # displays the following characters correctly: # ë : ë # &oelig : Å“ # ç: ç # &euro : € package require Tcl 8.5 package require msgcat 1.4.2 # specify the locale in the next statement: # {} for any, en for English, en_us for US English, en_gb for UK English # nl for Dutch, or de_ch for Swiss German, etc. set locale {} # In defintions for menus, buttons and notebook tabs, 1 character can # be underlined, which has a special meaning for keyboard shortcuts: # pressing ALT- invokes the menu, button or notebook # tab. In this file, underlined characters are indicated by a "&" before # the character that has to be underlined. # Menu definitions ::msgcat::mcmset $locale { mnuDatabase {&Database} mnuOpen {&Open} mnuClose {&Close} mnuQuit {&Quit} mnuSQL {&SQL} mnuTools {&Tools} mnuInstallTables {install &pfm_* tables} mnuInstallExample {install &example database ...} mnuOptions {&Options} mnuIncrFont {&Bigger fonts} mnuDecrFont {&Smaller fonts} mnuHelp {&Help} mnuHelpFile {&Help file} mnuLicense {&License} mnuAbout {&About} } # Button defintions ::msgcat::mcmset $locale { btnOK {&OK} btnCancel {&Cancel} btnYes {&Yes} btnNo {&No} btnDefault {&Default} btnHelp {&Help} btnOpen {&Open} btnRun {&Run} btnAdd {&Add} btnUpdate {&Update} btnDelete {&Delete} btnNext {&Next} btnPrev {&Previous} btnClear {&Clear} btnWrap {&Wrap} btnExpand {E&xpand} btnSelect {&Select} btnSearch {&Search} lbReadOnly {READ ONLY} } # Main window ::msgcat::mcmset $locale { pfm_no_database {pfm - No database opened} tabForms {&Forms} tabDesign {D&esign} tabReports {&Reports} lblForms {List of forms} lblDesign {Forms for designing forms and reports} lblReports {Reports} lblDescription {Description} browser_failed {Command %1$s has failed: %2$s} about_pfm {Postgres Forms (pfm) version %1$s Copyright © Willem Herremans 2004-2011 Postgres Forms comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See 'Help -> License' for details. The pfm home page is at http://pgfoundry.org/projects/pfm. There you can report bugs, request new features and get support. pfm is using %4$s as Tcl run time environment. The Tcl version is %5$s. pfm is using %3$s to communicate with PostgreSQL. pfm is installed in %2$s} OpenDialog {pfm - Open database} lb_host {host} lb_port {port} lb_dbname {dbname} lb_user {user} pfm_tables_newer {This database contains pfm_* tables of version %2$s whereas this version of pfm can only work with tables of version %1$s. You are strongly advised to upgrade to a newer version of pfm} noTables {This database does not contain the pfm_* tables. You can run SQL commands, but you cannot use or design forms or reports. Hint: Use 'Tools -> Install pfm_* tables' or 'Tools -> Install example database...'} convertDB {pfm - Convert database?} questionConvertDB {This database contains pfm_* tables of version %2$s whereas this version of pfm requires pfm_* tables of version %1$s. Do you want to convert the pfm_* tables to the newer version?} oldVersion {This database contains pfm_* tables of version %2$s whereas this version of pfm requires pfm_* tables of version %1$s. Continue at your own risk.} watchScript {The script %s is going to be executed. Please check the output for errors.} scriptNotFound {The script %s does not exist.} pressOkWhenFinished {Please press OK when the script has finished.} selectExampleDB {Select an example database} } # options.tcl ::msgcat::mcmset $locale { optionsTitle {pfm - Options} optHelpTitle {pfm - Help for option %s} optTabGeneral {&General} optTabPostgresql {&PostgreSQL} optPasteFilename {&Paste filename} optHelp_browser { The command by which your favourite browser is invoked. It is used by pfm to display the on-line help. Use '%s' as a placeholder for the URL. Use curly braces { and } to delimit filenames containing spaces. Examples: - UNIX: mozilla %s firefox %s konqueror %s iceweasel %s - Windows: {C:/Program Files/Internet Explorer/iexplore.exe} %s {C:/Program Files/mozilla.org/Mozilla/firefox.exe} %s Note: Use '/' instead of '\' as directory name separator. } optHelp_dblist { A space separated list of the database names available in the 'Database -> Open' dialogue. When a database that is not in the list yet, is opened successfully, it is automatically added to the list. } optHelp_dbname { The default database name in the 'Database -> Open' dialogue. pfm sets this to the last opened database. } optHelp_host { The default host name or host address in the 'Database -> Open' dialogue. } optHelp_port { The default port number in the 'Database -> Open' dialogue. The default is : 5432 } optHelp_printcmd { The print command issued by pfm when printing reports or results from queries. It is a SPACE separated list of commands that are executed one after the other to print plain text. If a command includes spaces, enclose it in curly braces. Example: {cmd1 args... } {cmd2 args ...} {cmd3 args...} You can use the following placeholders within these commands: - %txt : the temporary file in which pfm will store the text to be printed; - %ps : a temporary postscript file which will be generated by a text to postscript tool such as a2ps or enscript; - %pdf : a temporary PDF file which will be generated from the temporary postscript file %ps by a tool such as ps2pdf and wich can then be viewed by a PDF viewer such as evince, kpdf or Adobe Reader. Note: From version 2.0.0 on, the printcmd option always has to use the temporary text file %txt as its source of input. pfm no longer sends the text to be printed to the standard input of the first command. The commands may also contain parameters for which the user is prompted to provide a value when the command is called. The syntax is as follows: $(parameter_name=default_value) where '=default_value' is optional. Examples: - UNIX: {a2ps --output=%ps --$(portrait_or_landscape=portrait) --rows=$(nr-of-rows=1) --columns=$(nr-of-columns=1) --major=rows --chars-per-line=$(nr_of_chars_per_line=90) --center-title=$(title=Report) %txt} {ps2pdf -sPAPERSIZE=a4 %ps %pdf} {evince %pdf} This is a list of 3 commands: 1. a2ps converts the text contained in the temporary file %txt to a temporary postscript file %ps; 2. ps2pdf converts the temporary postscript file %ps to a temporary PDF file %pdf; 3. evince opens the temporary PDF file %pdf for viewing and printing. Notes: - If you use this printcmd, you should put the printencoding option to iso8859-1. a2ps does not support the utf-8 encoding which is now the default character encoding on most Linux systems. - Instead of this complicated "printcmd" you can use something as simple as {gedit %txt} or {soffice %txt} or {kwrite %txt}. - Windows: {{C:/Program Files/Windows NT/Bureau-accessoires/wordpad.exe} %txt} The temporary text file %txt will be loaded in Wordpad in which you can then choose the font, margin, page orientation etc. Remark the curly braces enclosing the full filename of wordpad.exe. They are necessary because of the spaces within the filename. Notes: - This command is also the default for Windows platforms, but the full filename may differ from system to system. Use the 'Paste filename' button to paste a new filename into the option. - Use '/' instead of '\' as directory name separator. } optHelp_printencoding { The character encoding used by pfm for sending text to the print command. The default value is your system's default encoding. That should be alright in most cases. Note: If you are using a2ps in the printcmd option, you should put the printencoding option to an encoding that is supported by a2ps (e.g. iso8859-1). } optHelp_psql { The command by which the PostgreSQL interactive terminal 'psql' is called. If the PostgreSQL binaries are in your system's PATH, the command can be as simple as 'psql' (on UNIX) or 'psql.exe' (on Windows). It may be necessary to provide the full filename. Examples: - UNIX: /usr/bin/psql /opt/postgresql/bin/psql - Windows: C:/Program Files/PostgreSQL/8.2/bin/psql.exe } optHelp_theme { Here you can choose the 'theme', i.e. the overall look of the application. } optHelp_tmpdir { The directory in which pfm stores the temporary files it generates. Normally, pfm also deletes its temporary files on exit. Defaults: - UNIX: /tmp - Windows: TEMP where TEMP is the user's TEMP directory which is something like C:/Documents and Settings/'username'/Local Settings/Temp Note: Use '/' instead of '\' as directory name separator. } optHelp_usePGPASSWORD { - If enabled, the environment variable PGPASSWORD is used to temporarily store the password you type on the "Open database window", between pressing the 'OK' button and the actual opening of the database. - If disabled, the environment variable PGPASSWORD is not used and you are not prompted for a password when opening a database, but you need a properly configured password file. See subsection "The Password File" in the libpq C-library of the PostgreSQL documentation for more details. Default: 'enabled' Note: According to the postgresql documentation, the use of the environment variable PGPASSWORD is deprecated for security reasons. See subsection "Environment variables" in the "libpq C-library" section of the PostgreSQL documentation for more details. } optHelp_user { The default value for 'user' in the 'Database -> Open' dialogue. The default value is your login name. } } # misc.tcl ::msgcat::mcmset $locale { pfm_message {pfm - message} searchEOT {End of text reached. Find will return to begin of file now.} lsbSearchFound {'%s' found} lsbSearchNotFound {Begin of list} mnuCopy {Copy} mnuCut {Cut} mnuPaste {Paste} mnuText {&Text} mnuTxtSave {&Save as ...} mnuTxtPrint {&Print} mnuTxtClose {&Close} mnuExtra {&Extra} miscSelectSaveText {pfm - Specify filename for saving text} } # sql.tcl ::msgcat::mcmset $locale { sqlTitle {SQL - %s} sqlStatement {SQL statement} sqlOutput {Output} sqlMnuSql {&SQL} sqlMnuDatabases {List of &Databases} sqlMnuImport {&Import SQL from file} sqlMnuListTables {List of &Relations} sqlMnuSaveToFile {&Save SQL to file} sqlMnuClose {&Close window} sqlMnuOutput {&Output} sqlMnuOutputClear {&Clear} sqlMnuOutputPrint {&Print} sqlMnuOutputSave {&Save} sqlMnuOutputScrollUp {Scroll &Up} sqlMnuOutputScrollDown {Scroll &Down} sqlMnuOutputScrollRight {Scroll &Right} sqlMnuOutputScrollLeft {Scroll &Left} sqlMnuHelp {&Help} sqlMnuHelpSQL {List of &SQL commands} sqlMnuHelpSpecial {Help for &Special commands} sqlMnuHelpSQLcommand {&Help for a particular SQL command} sqlTypeCmd {Complete the next line with the SQL command you want help about} sqlImportTitle {pfm - Import SQL from file} sqlHelpEncoding {Select the character encoding that was used to create the file. The default encoding is normally OK, but if you know it was made with a different encoding, you can choose it here.} sqlCharEncoding {Character encoding} sqlHelpImportType {Select whether you want to import the file in the SQL text area or to execute the script file using the '%s' command.} sqlRbExecute {&Execute script file using '%s' (recommended for large files)} sqlRbImport {&Import script file in SQL text area} sqlGetImportFile {pfm - Select SQL file to import} sqlSelectSaveSQL {pfm - Specify filename for saving SQL} sqlErrCmd {Error parsing print command %s} sqlPrintOptions {pfm - Print options} sqlPrintHelp {The longest line to print contains %s characters} } # postgresql ::msgcat::mcmset $locale { no_api {%1$s %2$s Neither Pgtcl, nor pgintcl were found. pfm cannot connect to postgreSQL} pg_connect_failed {Connection to database %1$s has failed: %2$s} wrongPermissions {The permissions on ~/.pgpass are %1$s. They should be 'rw-------'} psqlFailed {Starting psql has failed: %s} } # database.tcl ::msgcat::mcmset $locale { getFormList {List of forms could not be retrieved: %s} getFormFailed {Retrieval of form definition failed for form %1$s: %2$s} getAttribFailed {Retrieval of attributes definition failed for form %1$s: %2$s} getLinkDefFailed {Retrieval of links failed for form %1$s: %2$s} } # forms.tcl ::msgcat::mcmset $locale { titleOpenForm {pfm - Open form: %s} tabOpen {Query} mnuCloseForm {&Close form} mnuCloseTab {Close &tab} mnuUnlockTab {Un&lock tab} mnuForm {&Form} mnuPasteName {paste attribute &Name} mnuPasteValue {paste attribute &Value} mnuUpdateRecord {&Update} mnuAddRecord {&Add} mnuDeleteRecord {&Delete} mnuGoTonext {&Next} mnuGoToprev {&Prev} mnuGoTofirst {&First} mnuGoTolast {&Last} mnuRecordMenu {&Record} mnuGoToMenu {&Go to} mnuSearchMenu {&Search} mnuSearchHide {&Hide search bar} mnuSearchCase {&Match case} mnuFormHelp {&Help} formHelp {Help text for form %s} lbSearchFor {Search for} lbLinks {Links} lbTabLocked {Tab locked} btnUnlockTab {Un&lock tab} btnCloseTab {Close &tab} btnMatchCase {&Match case} btnFindNext {Next F3} btnHide {Hide} lbSearchFor {for} lbSearchIn {Search in} histOpenForm {Open form %s} histLink {Link %s} queryOK {Query OK} attribMissing {Query did not return any value for attribute %s. Check the form definition} pkeyMissing {Query did not reurn any value for the primary key attribute %s. Check the form definition} attribDefPkeyMissing {The primary key attribute %s does not have any defintion in pfm_attribute. Check the form definition} expandSqlWhereErr {Error evaluating the sqlwhere of link %1$s. No value for %2$s in form %3$s} editWindowsOpen {There are still edit windows open for the following attributes: %s Please decide first what you want to do with them.} listBoxOpen {There are still listboxes open for selecting a value for the following attributes: %s Please decide first what you want to do with them.} selectValueFor {Select value for %s} wrongNumTuples {Reload query has failed. It returns %s tuples instead of 1.} statUpdated {Updated} statAdded {Added} statDeleted {Deleted} statUpdating {Updating} statAdding {Adding} statNotAdded {Not added} statAfterLast {After last} statNotModified {Not modified} reloadRecord {Reload record} reloadFailed {Reload record has failed. Update is cancelled} selectForUpdate {Select for update} deleteRecord {Delete record} loadDataChunk {Load buffer} noValueForPkey {The form's query does not return a value for primary key '%s'. Check the form's definition} recordDeleted {Record was deleted by another user.} recordModified {Record was modified by another user, after you pressed the "Update" button, but before you pressed the "OK" button. Reconsider your input.} commandOK {Command succesful} noUpdates {You did not provide any new values. No updates were done.} startTransaction {Start of transaction} commitTransaction {Commit transaction} rollBack {Rollback transaction} updateRecord {Update record} questionDeleteTitle {Delete record?} questionDeleteMessage {Are you sure you want to delete this record?} defaultNumTuplesErr {The query specified for the default value of attribute '%1$s' returns %2$s values instead of 1. Check the default value definition for this attribute.} getDefaultValue {Get default for attribute %s.} } # report.tcl ::msgcat::mcmset $locale { reportTitle {Report %s} getReportFailed {Retrieval of report definition failed for report %1$s: %2$s} errorParsingFieldSpec {ERROR: %1$s in %2$s could not be parsed. %3$s} reportDataMissing {ERROR: The report's SQL SELECT statement does not return data for field '%1$s' defined in section '%2$s' of the report. Check the report definition!} noReportSections {ERROR: This report has no section definition!} getReportParms {pfm - Get report parameters} errorParsingSummary {ERROR: summary '%1$s' of section %2$s could not be parsed. %3$s} unkownAggregateFunction {ERROR: Summary field %1$s(%2$s) defined in section %3$s. Aggregate function %1$s is unkown.} summaryWrongField {ERROR: Summary field %1$s(%2$s) defined in section %3$s. %2$s does not appear in this or any higher numbered section} summaryFormatError {ERROR: Format string '%1$s' in summary field %2$s(%3$s) in section %4$s. %5$s} reportQueryNoData {The report's query did not return any data.} } pfm-2.0.8/database.tcl0000644000175000017500000000746310772665465012761 0ustar wimwim# database.tcl proc getFormsReports {db tabType} { switch $tabType { forms { set query "SELECT name FROM pfm_form WHERE showform ORDER BY name" } design { set query "SELECT name FROM pfm_form WHERE NOT showform ORDER BY name" } reports { set query "SELECT name, description FROM pfm_report ORDER BY name" } } if {![$db select_query_list $query numTuples headerList formsList errorMsg]} then { set formsList {} # pfm_message [mc getFormList $errorMsg] {.} } return $formsList } proc getFormDef {db formName parent formDefName} { upvar $formDefName formDef set query {SELECT name, tablename, sqlselect, sqlfrom, groupby, showform, } append query {"view", pkey, sqlorderby, sqllimit } append query {FROM pfm_form } append query "WHERE name = '$formName'" if {[$db select_query_list $query numTuples attribList resultList errorMsg]} then { if {$numTuples == 1} then { set status 1 set idx 0 foreach attrib $attribList { set formDef($attrib) [lindex $resultList 0 $idx] incr idx } } else { pfm_message [mc getFormFailed $formName "numTuples = $numTuples"] $parent set status 0 } } else { pfm_message [mc getFormFailed $formName $errorMsg] $parent set status 0 } return $status } proc getAttribDef {db formName parent attribDefName attribListName modAttribListName} { upvar $attribDefName attribDef upvar $attribListName attribList upvar $modAttribListName modAttribList set attribList {} set modAttribList {} set query {SELECT attribute, typeofattrib, typeofget, sqlselect, nr, valuelist, "default" } append query "FROM pfm_attribute WHERE form = '${formName}' " append query {ORDER BY nr} if {[$db select_query $query numTuples resultArray errorMsg]} then { set status 1 for {set tuple 0} {$tuple < $numTuples} {incr tuple} { set attrib [string trim $resultArray($tuple,attribute)] lappend attribList $attrib foreach property {typeofattrib typeofget sqlselect nr valuelist default} { set attribDef($attrib,$property) \ [string trim $resultArray($tuple,$property)] } if {($attribDef($attrib,typeofget) ne {tgReadOnly}) || \ ($attribDef($attrib,default) ne {})} then { lappend modAttribList $attrib } } } else { pfm_message [mc getAttribFailed $formName $errorMsg] $parent set status 0 } return $status } proc getLinkDef {db formName parent linkDefName lastLinkName} { upvar $linkDefName linkDef upvar $lastLinkName lastLink set query {SELECT linkname, sqlwhere, orderby, displayattrib, toform} append query " FROM pfm_link WHERE fromform = '$formName'" append query { ORDER BY linkname} if {[$db select_query $query numTuples linkDef errorMsg]} then { set lastLink [expr $numTuples - 1] set status 1 } else { set status 0 pfm_message [mc getLinkDefFailed $formName $errorMsg] $parent } return $status } proc check_pfm_tables {tablesInstalledName dbVersionName} { upvar $tablesInstalledName tablesInstalled upvar $dbVersionName dbVersion set query {SELECT COUNT(*) AS nr_of_tables FROM pg_tables } append query {WHERE tablename IN ('pfm_form', 'pfm_attribute', } append query {'pfm_value', 'pfm_value_list', 'pfm_link', } append query {'pfm_report', 'pfm_section')} if {[$::dbObject select_query_list $query numTuples names \ resultList errMsg]} then { set tablesInstalled [lindex $resultList 0 0] if {$tablesInstalled > 0} then { set query {SELECT version FROM pfm_version ORDER BY seqnr DESC} if {[$::dbObject select_query_list $query numTuples names \ resultList errMsg]} then { set dbVersion [lindex $resultList 0 0] } else { # versions 1.0.4 and earlier did not have pfm_version table set dbVersion {1.0.4} pfm_message "${query}\n${errMsg}" {.} } } else { set dbVersion {} } } else { set dbVersion {} set tablesInstalled 0 pfm_message "${query}\n${errMsg}" {.} } return } pfm-2.0.8/convert_from_1.1.0.sql0000644000175000017500000005307510661565715014444 0ustar wimwim-- convert_from_1.1.0.sql -- The character encoding of this file is iso8859-1 (latin1) -- This script converts the pfm_* tables of versions 1.1.* -- to the format of version 1.2.0 -- From pfm version 1.3.0 on, we do not mark the pfm_tables -- with the last version of pfm, but with the earliest compatible -- version of pfm. In this way, earlier versions of pfm, from 1.2.0 on, -- will be able to work with pfm_tables created by this version -- of pfm. This will only change when there is a need to change -- the structure of the pfm_tables. INSERT INTO pfm_version (version, "date", comment) VALUES ('1.2.0', CURRENT_DATE, 'convert_from_1.1.0.sql'); -- Correction of error in pfm_link UPDATE pfm_link SET sqlwhere = 'name=''$(form)''' WHERE (fromform = 'pfm_attribute') AND (linkname = 'Form'); -- Alter table definitions: new attributes for pfm_form and pfm_attribute ALTER TABLE pfm_form ADD COLUMN pkey text; ALTER TABLE pfm_attribute ADD COLUMN "default" text; -- Add primary keys to pfm_* tables ALTER TABLE pfm_attribute ADD CONSTRAINT pfm_attribute_pkey PRIMARY KEY (form, attribute); ALTER TABLE pfm_link ADD CONSTRAINT pfm_link_pkey PRIMARY KEY (fromform, linkname); ALTER TABLE pfm_section ADD CONSTRAINT pfm_section_pkey PRIMARY KEY (report, "level"); ALTER TABLE pfm_value ADD CONSTRAINT pfm_value_pkey PRIMARY KEY (valuelist, value); -- Update pfm_tables -- Set pkey for all forms UPDATE pfm_form SET pkey = 'oid' WHERE NOT "view"; UPDATE pfm_form SET pkey = 'name' WHERE name = 'pfm_form'; UPDATE pfm_form SET pkey = 'form attribute' WHERE name = 'pfm_attribute'; UPDATE pfm_form SET pkey = 'fromform linkname' WHERE name = 'pfm_link'; UPDATE pfm_form SET pkey = 'name' WHERE name = 'pfm_report'; UPDATE pfm_form SET pkey = 'report level' WHERE name = 'pfm_section'; UPDATE pfm_form SET pkey = 'name' WHERE name = 'pfm_value_list'; UPDATE pfm_form SET pkey = 'valuelist value' WHERE name = 'pfm_value'; -- Add attribute pkey to form of pfm_form UPDATE pfm_attribute SET nr = nr + 1 WHERE (form = 'pfm_form') AND (nr >= 3); INSERT INTO pfm_attribute (attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default") VALUES ('pkey', 'taQuoted', 'tgDirect', '', 3, 'pfm_form', 'none', ''); -- Add attribute 'default' to form of pfm_attribute INSERT INTO pfm_attribute (attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default") VALUES ('default', 'taQuoted', 'tgDirect', '', 8, 'pfm_attribute', 'none', ''); -- Drop 'oid' from the sqlselect of all pfm_* forms UPDATE pfm_form SET sqlselect = 'attribute, typeofattrib, typeofget, sqlselect, nr, form, valuelist, "default"' WHERE name = 'pfm_attribute'; UPDATE pfm_form SET sqlselect = 'name, tablename, sqlselect, sqlfrom, groupby, showform, "view", help, pkey' WHERE name = 'pfm_form'; UPDATE pfm_form SET sqlselect = 'linkname, sqlwhere, orderby, displayattrib, fromform, toform' WHERE name = 'pfm_link'; UPDATE pfm_form SET sqlselect = 'name, description, sqlselect' WHERE name = 'pfm_report'; UPDATE pfm_form SET sqlselect = 'report, "level", fieldlist, layout' WHERE name = 'pfm_section'; UPDATE pfm_form SET sqlselect = 'value, description, valuelist' WHERE name = 'pfm_value'; UPDATE pfm_form SET sqlselect = 'name' WHERE name = 'pfm_value_list'; -- Update help text for pfm_form UPDATE pfm_form SET help ='A form allows the user to administer the data of just one table. This table is henceforth referred to as "the form''s main table". However, a form also has an SQL SELECT statement, which generates the data that are displayed on it. In the simplest case the SQL SELECT statement is just: SELECT FROM
In that case, the data which can be administered and the data which are displayed on the form are the same. In more complex cases, the
can be JOINED with other tables, which makes it possible to display data of other related tables as well. These data cannot be modified by means of the form. The table "pfm_form" has the following attributes: - name : the name of the form (usually equal to the name of the form''s table); - tablename : the name of the form''s main table; - pkey : the primary key of the form''s main table, which may consist of more than one attribute. In that case pkey is a SPACE separated list of the attributes of the primary key; Note: If pkey is empty, the form is read-only, since pfm is unable to uniquely identify a record. You can use the ''oid'' as primary key, but according to the PostgreSQL documentation that is not recommended, unless you set a UNIQUE constraint on the ''oid''. - sqlselect : the attribute list of the form''s SQL SELECT statement, not including the word ''SELECT''; - sqlfrom : the FROM clause of the form''s SQL SELECT statement, not including the word ''FROM''; - groupby : an optional ''GROUP BY'' clause, not including the words ''GROUP BY''; - view : a boolean indicating whether or not the "tablename" is a view; - showform : a boolean indicating whether the form is shown in "normal mode" (showform = ''true'') or in "design mode" (showform = ''false''). Typically, showform is set ''true'' for user defined forms and ''false'' for the predefined pfm_* forms. - help : a text which is displayed when the user presses the [Help] key on the form. The form''s main table is defined by tablename. Only the data of that table can be administered by using the form. All the data generated by the form''s SQL SELECT statement can be displayed on the form. The SQL SELECT statement is defined by: - the sqlselect, sqlfrom and groupby attributes of pfm_form; and - the optional WHERE and ORDER BY clauses provided by the user when opening the form. Note: The WHERE clause provided by the user when opening the form, is automatically converted to a HAVING clause, if there is a GROUP BY clause. The following rules should be observed when filling out sqlselect and sqlfrom: 1. The form''s main table must appear in ''sqlfrom'', and must not be aliased. Similarly, the main table''s attributes appearing in ''sqlselect'' must not be aliased. The other tables appearing in the ''sqlfrom'' may be aliased. 2. The fields appearing in ''sqlselect'' must have a unique, simple name without the need to precede them with a tablename. So, calculated fields must be given a name by aliasing and attributes of tables other than the main table may need to be aliased in order to have a unique, simple name. 3. The ''sqlfrom'' is either just the name of the form''s main table, or it is a JOIN clause in which 1 of the tables is the form''s main table. Several join clauses can be nested in order to involve more than 2 tables. See examples below. Example 1: the SQL SELECT for the person form of the addressbook database tablename: person pkey: id sqlselect: id, christian_name, name, street, town, "ZIPcode", country, category, description sqlfrom: person groupby: - Example 2: the SQL SELECT for the memberlist form of the addressbook database tablename: memberlist pkey: group person sqlselect: memberlist."group", memberlist.person, p.christian_name, p.name sqlfrom: memberlist LEFT OUTER JOIN person p ON (p.id = memberlist.person) groupby: -' WHERE name = 'pfm_form'; -- Set default values for some attributes of pfm_* forms UPDATE pfm_attribute SET "default" = 'taQuoted' WHERE (form = 'pfm_attribute') AND ("attribute" = 'typeofattrib'); UPDATE pfm_attribute SET "default" = 'tgDirect' WHERE (form = 'pfm_attribute') AND ("attribute" = 'typeofget'); UPDATE pfm_attribute SET "default" = 'none' WHERE (form = 'pfm_attribute') AND ("attribute" = 'valuelist'); UPDATE pfm_attribute SET "default" = 't' WHERE (form = 'pfm_form') AND ("attribute" = 'showform'); UPDATE pfm_attribute SET "default" = 'f' WHERE (form = 'pfm_form') AND ("attribute" = 'view'); UPDATE pfm_attribute SET "default" = '1' WHERE (form = 'pfm_section') AND ("attribute" = 'level'); UPDATE pfm_attribute SET "default" = 'table' WHERE (form = 'pfm_section') AND ("attribute" = 'layout'); -- Update help text for the form of pfm_attribute UPDATE pfm_form SET help = 'The table "pfm_attribute" defines all the properties of form attributes. It has the following attributes: - form : the "name" of the form to which the attribute belongs; - attribute : the name of the attribute; this must be equal to the name of the corresponding attribute of the form''s SQL SELECT statement; - typeofattrib : the type of attribute: o taQuoted: the value provided by the user is put between single quotes when it is transferred to SQL UPDATE or INSERT statements; o taNotQuoted: the value provided by the user is not quoted when it is transferred to SQL UPDATE or INSERT statements. Hint: In general, all attribute values must be quoted, exept the values or expressions for numeric attributes. - typeofget: defines how the user provides a value for the attribute; possible values are: o tgDirect: the user types the value directly; o tgExpression: the user types an expression which is first evaluated before it is passed to SQL UPDATE or INSERT; Note: Even with tgDirect it is possible to enter an expression as new value for an attribute, but then the expression is evaluated by postgresql whereas with tgExpression, the expression is first evaluated by Tcl before the SQL statement is sent to postgresql. o tgList: the user selects a value by means of a list box containing a list of values defined in table "pfm_value"; o tgLink: the user selects a value by means of a list box containing a list of values which is the result from a query on another table. o tgReadOnly: this attribute cannot be modified by the user. Note: All calculated attributes and all attributes from tables other than the form''s main table should be declared ''read-only''. If this rule is not observed, the Add and Update operations on this form will fail. - sqlselect: the SQL SELECT statement which is used to fill the list box with possible values for the attribute (only meaningful if typeofget = tgLink). Note : o The sqlselect may return more than 1 attribute. If so, all the attributes are displayed in the list-box, but only the first one is used for updating the attribute. - valuelist : the "name" of the value list defined in table "pfm_value_list" (only meaningful if typeofget = tgList); - nr: a number which determines the order in which attributes are displayed on the form; - default: a default value for this attribute which is used when adding a record. If the first character is an ''='' sign, the following characters should be an SQL SELECT statement which returns just one value. Example: default: =SELECT nextval(''seq_person_id'') In this example the default value is the next value of the sequenece ''seq_person_id''. ' WHERE name = 'pfm_attribute'; -- Add summary to table pfm_section ALTER TABLE pfm_section ADD COLUMN summary text; -- Add constraint on level of pfm_section ALTER TABLE pfm_section ADD CONSTRAINT level_min_1 CHECK("level" >= 1); -- Add summary to form pfm_section UPDATE pfm_form SET sqlselect = 'report, "level", fieldlist, layout, summary' WHERE name = 'pfm_section'; INSERT INTO pfm_attribute (form, attribute, typeofattrib, typeofget, sqlselect, valuelist, nr, "default") VALUES ('pfm_section', 'summary', 'taQuoted', 'tgDirect', '', 'none', 5, ''); UPDATE pfm_form SET HELP = 'The data returned by the report''s SQL SELECT statement may be considered as a table with a column for each ''field'' specified after the word ''SELECT'' and with a row for each record. By specifying an ''ORDER BY'' clause in the report''s SQL SELECT statement, it is possible to group rows with the same values for some fields together. The report generator has an "economy" algorithm which avoids printing the same data repeatedly. To control this you have to distribute the fields (columns) of the table over n sections such that section 1 contains the fields that are changing least frequently (when moving from one row to the next), section 2 contains the fields that are changing more frequently, and section n contains the fields that are changing at every row. When the data of the first row of the table are printed, the data of section 1 are printed first. Then, on the following line, indented by one tab stop, the data of section 2 are printed. Then, on the following line, indented by 2 tab stops, data of section 2 are printed, etc. [section 1] <--- row 1 [section 2] <--- row 1 [section 3] <--- row 1 Then, when the next rows are being printed, data of the lower numbered sections are only printed if they are different from the data of the last printed section of the same number: [section 1] [section 2] [section 3] <--- row 1 [section 3] <--- row 2 [section 3] <--- row 3 [section 2] [section 3] <--- row 4 [section 3] <--- row 5 [section 1] [section 2] [section 3] <--- row 6 [section 3] <--- row 7 The report generator also enables you to print a summary at every point where a higher numbered section is about to be followed by a lower numbered section: [section 1] [section 2] [section 3] <--- row 1 [section 3] <--- row 2 [section 3] <--- row 3 [summary 3] [section 2] [section 3] <--- row 4 [section 3] <--- row 5 [summary 3] [summary 2] [section 1] [section 2] [section 3] <--- row 6 [section 3] <--- row 7 [summary 3] [summary 2] [summary 1] A summary i is printed just before a lower numbered section j (j < i). Its data can be calculated: - by applying one of the aggregate funtions: COUNT, SUM, AVG, STDDEV, MIN, MAX; - on the fields of the sections j (j >= i), between the last printed lower numbered section k (k < i), till the next (not yet printed) lower numbered section k (k < i). In particular, summary 1 is printed at the end of the report, is calculated from all the sections of the report and may be calculated from all the fields. A record in pfm_section defines a section and a summary of a report. The table pfm_section has the following attributes: - report: the name of the report to which the section belongs - level: a number 1, 2, 3, 4, ... . The first level must be ''1''. The next levels must be numbered consecutively. In the most simple report, there is only a section with level 1. - layout: can be "row", "column" or "table". - fieldlist: a space separated list of field specifiers, one for each field to be printed in the sections of this level (see below for details). - summary: a space separated list of summary field specifiers (see below for details). The fieldlist must be formatted as follows: {field_1 label_1 alignment_1} {field_2 label_2 alignment_2}... {field_N label_N alignment_N} where : - field_i is the name of one of the columns returned by the report''s SQL SELECT statement; - label_i is a string which has to be used as label for printing the i-th field of this section; if it consists of more than 1 word, it must be delimited by double quotes (" .... "); - alignment_i is optional; if present, it is either l or r, indicating whether this field should be left or right aligned. Notes : o The alignment is optional. If it is left out, left alignment is assumed by default. o The alignment only influences the table layout. Column and row layouts are unaffected by the alignment indicator. o Multi-line fields, i.e. fields containing more than one line of text are only formatted properly in a column layout. For every section, the layout can be defined as: - row: the section''s field labels and field values are printed in one row in a format: label_1 : value_1; label_2 : value_2; ... etc. - column: the section''s field labels are printed in a first column, the section''s field values are printed in a second column. - table: the section''s values are printed in a table with a column per field and a row per record, the section''s field labels are used as column headers for the table. The summary must be formatted as follows: {field_1 aggregate_1 format_1} {field_2 aggregate_2 format_2}... {field_N aggregate_N format_N} where: - field_i is the name of a field defined in the fieldlist of either this section, or another, higher numbered section; - aggregate_i is one of the aggregate functions: COUNT, SUM, AVG, STDDEV, MIN, MAX (see below for details); and - format_i is an optional ''ANSI C sprintf'' formatting string (see below for details). If it is left out, the number is printed with maximum precision. Aggregate functions: In general, the aggregate functions, use the same "economy" algorithm that is used for printing section data. When all the fields of a section, which is not the highest numbered section of the report, have the same values for a number of consecutive rows, this section''s data are only printed once for these rows. Similarly, these rows are only counted once by the aggregate functions applied to a field of this section. The aggregate functions that can be used in a summary are: - COUNT: Counts the number of rows. In this case, the field_i that is specified only determines which section is counted. - SUM: Calculates the sum of all the values of the specified field. - AVG: Calculates the average of the values of the specified field. - STDDEV: Calculates the standard deviation for the values of the specified field: SQRT (SUM( (value_i - AVG(value))**2 ) / N) where : - value_1, value_2, ... value_N are the values of the considered field; - AVG(value) is the average of the considered values; - N is the number of values. - MIN: Calculates the minimum of the values of the specified field. - MAX: Calculated the maximum of the values of the specified field. ''ANSI C sprintf'' formatting string: Here is a short overview of the ''ANSI C sprintf'' formatting string. In general its form is: %''MinWidth''.''Precision''''Conversion'' where: - ''MinWidth'' is an integer defining the minimum width (as number of characters) for the number to be printed. If the number does not need so much space, spaces are inserted in front of the number, unless MinWidth is negative. In that case, spaces are appended at the end. If the number needs more space than MinWidth, more space is used. - ''Precision'' is an integer defining how many digits to print after the decimal point, or, in the case of g or G conversion, the total number of digits to appear, including those on both sides of the decimal point - ''Conversion'' is one of: o d : convert integer to signed decimal string. In this case, there is no need to define a ''Precision''. Example: %1d prints an integer and uses as many characters as required. o f : convert floating point number to fixed point notation. In this case, ''Precision'' defines the number of digits to print after the decimal point. If there are not enough digits available, trailing zeroes are appended. Example: %1.2f prints a floating point number wiht 2 digits after the decimal point and uses as many characters as required. o e or E : Convert floating-point number to scientific notation in the form x.yyye±zz, where the number of y''s is determined by the ''Precision'' (default: 6). If the precision is 0 then no decimal point is output. If the E form is used then E is printed instead of e. Example: %1.5E prints a floating point number in the form x.yyyyy E±zz o g or G : If the exponent is less than -4 or greater than or equal to the precision, then convert floating-point number as for %e or %E. Otherwise convert as for %f. Trailing zeroes and a trailing decimal point are omitted. In this case the ''Precision'' specifies the total number of digits to appear, including those on both sides of the decimal point Example: %1.4G prints 2345.0 as 2345 prints 234567.0 as 2.346E+05 prints 0.003456 as 0.003456 prints 0.00003456 as 3.456E-05' WHERE name = 'pfm_section'; pfm-2.0.8/cat.tcl0000644000175000017500000000027112110435333011723 0ustar wimwim# cat.tcl # to be used in a pipe to catch stdout or stderr from an external program package require Tcl while {![chan eof stdin]} { catch {chan puts stdout [chan gets stdin]} } exit