This document is released under the GNU Free Documention
- Licence, Version 1.1 or any later version published by the Free
- Software Foundation; with no Invariant Sections, no Front-Cover
- Texts. and no Back-Cover Texts.
This document is licensed under the GNU General Public
+ License, version 2 or greater. Please see the file COPYING for
+ details, or see http://www.gnu.org/licenses/gpl-2.0.html.
polar - plot polar data or
+ functions. This is a non-orthogonal plot and is placed
+ directly on the page rather than in a graph.
ternary - plot data of three
+ variables which add up to 100 per cent.This is a
+ non-orthogonal plot and is placed directly on the page
+ rather than in a graph.
In recent versions there also exists a dataset browsing
+ window, by default to the right of the screen. This window
+ allows you to view the datasets currently loaded, their
+ dimensions and type. Hovering a mouse over the size of the
+ dataset will give you a preview of the data.
Currently Veusz supports reading data from a text file, FITS
- format files or from CSV files. Reading data is supported using
- the "Data, Import" dialog, or using
- the ImportFile
@@ -1432,8 +1474,8 @@
commands which read data from files or an existing Python string
(allowing data to be embedded in a Python script). In addition,
the user can load or write plugins in Python which load data into
- Veusz in an arbitrary format. At the moment QDP files are
- supported with this method.
+ Veusz in an arbitrary format. At the moment QDP, binary and
+ NPY/NPZ files are supported with this method.
If a descriptor is left blank, Veusz will automatically
+ create dataset names. If the prefix and suffix settings are
+ blank, they are assigned names col1, col2, etc. If prefix and
+ suffix are not blank, the datasets are called
+ prefix+number+suffix.
When reading in data, Veusz treats any whitespace as
separating columns. The columns do not actually need to be in
columns! Furthermore a "\" symbol can be placed at the end of
@@ -1680,7 +1728,7 @@
>
Text datasets are not yet autodetected for CSV files. You
- can specify a text dataset by using "(text)" after the name of the
- dataset at the top of the column. A future release show allow a
- more general method for autodetecting data or specifying, as the
- standard Veusz file format currently does.
The data type in CSV files are automatically detected unless
+ specified. The data type can be given in brackets after the column
+ name, e.g. "name (text)", where the data type is "date", "numeric"
+ or "text". Explicit data types are needed if the data look like a
+ different data type (e.g. a text item of "1.23"). The date format
+ in CSV files can be specified in the import dialog box - see the
+ examples given. In addition CSV files support numbers in European
+ format (e.g. 2,34 rather than 2.34), depending on the setting in
+ the dialog box.
In addition to creating datasets based on expressions, a
+ variety of dataset plugins exist, which make certain
+ operations on datasets much more convenient. See the Data,
+ Operations menu for a list of the default plugins. The user
+ can easily create new plugins. See
+ http://barmag.net/veusz-wiki/DatasetPlugins for details.
+
Add a custom function or value for evaluating
- expressions, or import external python functions.
Add a custom definition for evaluation of
+ expressions. This can define a constant (can be in terms of
+ other constants), a function of 1 or more variables, or a
+ function imported from an external python module.
For a constant value name should be the name of the
- constant. type should be 'constant'. value is the value of the
- constant.
ctype is "constant", "function" or "import".
For a function, name should be of the format
- 'functionname(x,y,z)' where x, y and z are its arguments. type
- should be 'function'. value is the definition of the function,
- e.g. 'x+y+z'.
name is name of constant, or "function(x, y, ...)" or
+ module name.
For a Python import, name should be the module to import
- from. type is 'import'. value is the name of the symbol to
- import.
val is definition for constant or function (both are
+ _strings_), or is a list of symbols for a module (comma
+ separated items in a string).
If mode is 'appendalways', the custom value is appended
+ to the end of the list even if there is one with the same
+ name. If mode is 'replace', it replaces any existing
+ definition in the same place in the list or is appended
+ otherwise. If mode is 'append', then an existing definition is
+ deleted, and the new one appended to the end.
Export(filename, color=True,
- page=0)
Export the page given to the filename given. The
- filename must end with the correct extension
- to get the right sort of output file. Currrenly supported
- extensions are '.eps', '.pdf', '.svg', '.jpg', '.jpeg', '.bmp'
- and '.png'. If
- must end with the correct
+ extension to get the right sort of output file. Currrenly
+ supported extensions are '.eps', '.pdf', '.svg', '.jpg',
+ '.jpeg', '.bmp' and '.png'. If color is True, then the output is in colour,
- else greyscale. is
+ True, then the output is in colour, else
+ greyscale. page is the page number of
- the document to export (starting from 0 for the first
- page!).
is the page number of the
+ document to export (starting from 0 for the first page!).
+ dpi is the number of dots per inch for
+ bitmap output files. antialias -
+ antialiases output if True. quality is a
+ quality parameter for jpeg
+ output. backcolor is the background color
+ for bitmap files, which is a name or a #RRGGBBAA value (red,
+ green, blue, alpha). pdfdpi is the dpi to
+ use when exporting EPS or PDF files.
+
ForceUpdate
ForceUpdate()
Force the window to be updated to reflect the current
+ state of the document. Often used when periodic updates have
+ been disabled (see SetUpdateInterval). This command is only
+ supported in embedded mode or from veusz_listen.
Tells window to update every interval milliseconds at
most. The value 0 disables updates until this function is
- called with a positive value.
Note: this command is only supported in the embedding
interface or veusz_listen.
-
-
Veusz - a scientific plotting package
@@ -17,18 +15,18 @@
- 2010
+ 2011
- This document is released under the GNU Free Documention
- Licence, Version 1.1 or any later version published by the Free
- Software Foundation; with no Invariant Sections, no Front-Cover
- Texts. and no Back-Cover Texts.
+
+ This document is licensed under the GNU General Public
+ License, version 2 or greater. Please see the file COPYING for
+ details, or see .
-
Introduction
@@ -336,6 +334,19 @@
of points in a dataset.
+
+ polar - plot polar data or
+ functions. This is a non-orthogonal plot and is placed
+ directly on the page rather than in a graph.
+
+
+
+ ternary - plot data of three
+ variables which add up to 100 per cent.This is a
+ non-orthogonal plot and is placed directly on the page
+ rather than in a graph.
+
+
@@ -450,7 +461,8 @@
appropriate date formatting is used so that the interval shown
is correct. A format can be given for an axis in the axis
number formatting panel can be given to explicitly choose a
- format.
+ format. Some examples are given in the drop down axis
+ menu. Hold the mouse over the example for detail.
C-style number formatting is used with a few Veusz
specific extensions. Text can be mixed with format specifiers,
@@ -555,6 +567,12 @@
history like many Unix shells. Press the up and down cursor keys
to browse through the history. Command line completion is not
available yet!
+
+ In recent versions there also exists a dataset browsing
+ window, by default to the right of the screen. This window
+ allows you to view the datasets currently loaded, their
+ dimensions and type. Hovering a mouse over the size of the
+ dataset will give you a preview of the data.
@@ -645,20 +663,19 @@
-
Reading dataCurrently Veusz supports reading data from a text file, FITS
- format files or from CSV files. Reading data is supported using
- the "Data, Import" dialog, or using
- the ImportFile
+ format files, CSV files, QDP files, binary files and NPY/NPZ
+ files. Reading data is supported using the "Data, Import" dialog,
+ or using the ImportFile
and ImportString
commands which read data from files or an existing Python string
(allowing data to be embedded in a Python script). In addition,
the user can load or write plugins in Python which load data into
- Veusz in an arbitrary format. At the moment QDP files are
- supported with this method.
+ Veusz in an arbitrary format. At the moment QDP, binary and
+ NPY/NPZ files are supported with this method.
@@ -761,6 +778,12 @@
errors. The signs on positive or negative errors are
automatically set to be correct.
+ If a descriptor is left blank, Veusz will automatically
+ create dataset names. If the prefix and suffix settings are
+ blank, they are assigned names col1, col2, etc. If prefix and
+ suffix are not blank, the datasets are called
+ prefix+number+suffix.
+
When reading in data, Veusz treats any whitespace as
separating columns. The columns do not actually need to be in
columns! Furthermore a "\" symbol can be placed at the end of
@@ -870,11 +893,15 @@
updated when the file changes. See the example CSV import for
details.
- Text datasets are not yet autodetected for CSV files. You
- can specify a text dataset by using "(text)" after the name of the
- dataset at the top of the column. A future release show allow a
- more general method for autodetecting data or specifying, as the
- standard Veusz file format currently does.
+ The data type in CSV files are automatically detected unless
+ specified. The data type can be given in brackets after the column
+ name, e.g. "name (text)", where the data type is "date", "numeric"
+ or "text". Explicit data types are needed if the data look like a
+ different data type (e.g. a text item of "1.23"). The date format
+ in CSV files can be specified in the import dialog box - see the
+ examples given. In addition CSV files support numbers in European
+ format (e.g. 2,34 rather than 2.34), depending on the setting in
+ the dialog box.
@@ -990,6 +1017,7 @@
dataset to an expression, so if the expression changes the
dataset changes with it, like in a spreadsheet.
+
Splitting data
@@ -1021,11 +1049,22 @@
box.
+
+ Dataset plugins
+ In addition to creating datasets based on expressions, a
+ variety of dataset plugins exist, which make certain
+ operations on datasets much more convenient. See the Data,
+ Operations menu for a list of the default plugins. The user
+ can easily create new plugins. See
+ for details.
+
+
+
-
Command line interface
@@ -1112,23 +1151,28 @@
AddCustom
- AddCustom(name, type, value)
+ AddCustom(type, name, value)
- Add a custom function or value for evaluating
- expressions, or import external python functions.
-
- For a constant value name should be the name of the
- constant. type should be 'constant'. value is the value of the
- constant.
-
- For a function, name should be of the format
- 'functionname(x,y,z)' where x, y and z are its arguments. type
- should be 'function'. value is the definition of the function,
- e.g. 'x+y+z'.
-
- For a Python import, name should be the module to import
- from. type is 'import'. value is the name of the symbol to
- import.
+ Add a custom definition for evaluation of
+ expressions. This can define a constant (can be in terms of
+ other constants), a function of 1 or more variables, or a
+ function imported from an external python module.
+
+ ctype is "constant", "function" or "import".
+
+ name is name of constant, or "function(x, y, ...)" or
+ module name.
+
+ val is definition for constant or function (both are
+ _strings_), or is a list of symbols for a module (comma
+ separated items in a string).
+
+ If mode is 'appendalways', the custom value is appended
+ to the end of the list even if there is one with the same
+ name. If mode is 'replace', it replaces any existing
+ definition in the same place in the list or is appended
+ otherwise. If mode is 'append', then an existing definition is
+ deleted, and the new one appended to the end.
@@ -1210,17 +1254,38 @@
ExportExport(filename, color=True,
- page=0)
+ page=0 dpi=100,
+ antialias=True, quality=85, backcolor='#ffffff00',
+ pdfdpi=150)
Export the page given to the filename given. The
- filename must end with the correct extension
- to get the right sort of output file. Currrenly supported
- extensions are '.eps', '.pdf', '.svg', '.jpg', '.jpeg', '.bmp'
- and '.png'. If
- color is True, then the output is in colour,
- else greyscale. page is the page number of
- the document to export (starting from 0 for the first
- page!).
+ filename must end with the correct
+ extension to get the right sort of output file. Currrenly
+ supported extensions are '.eps', '.pdf', '.svg', '.jpg',
+ '.jpeg', '.bmp' and '.png'. If color is
+ True, then the output is in colour, else
+ greyscale. page is the page number of the
+ document to export (starting from 0 for the first page!).
+ dpi is the number of dots per inch for
+ bitmap output files. antialias -
+ antialiases output if True. quality is a
+ quality parameter for jpeg
+ output. backcolor is the background color
+ for bitmap files, which is a name or a #RRGGBBAA value (red,
+ green, blue, alpha). pdfdpi is the dpi to
+ use when exporting EPS or PDF files.
+
+
+
+
+ ForceUpdate
+
+ ForceUpdate()
+
+ Force the window to be updated to reflect the current
+ state of the document. Often used when periodic updates have
+ been disabled (see SetUpdateInterval). This command is only
+ supported in embedded mode or from veusz_listen.
@@ -1775,7 +1840,11 @@
Tells window to update every interval milliseconds at
most. The value 0 disables updates until this function is
- called with a positive value.
+ called with a non-zero. The value -1 tells Veusz to update the
+ window every time the document has changed. This will make
+ things slow if repeated changes are made to the
+ document. Disabling updates and using the ForceUpdate command
+ will allow the user to control updates directly.
Note: this command is only supported in the embedding
interface or veusz_listen.
@@ -1877,7 +1946,6 @@
-
Using Veusz from other programs
@@ -1964,7 +2032,7 @@
command line by doing something like:
-veusz_listen < in.vsz
+veusz_listen < in.vsz
where in.vsz contains:
diff -Nru veusz-1.10/Documents/veusz.1 veusz-1.14/Documents/veusz.1
--- veusz-1.10/Documents/veusz.1 2010-12-12 12:41:53.000000000 +0000
+++ veusz-1.14/Documents/veusz.1 2011-11-22 20:24:17.000000000 +0000
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.23 (Pod::Simple 3.14)
+.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -124,7 +124,7 @@
.\" ========================================================================
.\"
.IX Title "VEUSZ 1"
-.TH VEUSZ 1 "2010-12-12" "1.10" "Veusz"
+.TH VEUSZ 1 "2011-11-22" "1.13.999" "Veusz"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
@@ -168,9 +168,14 @@
.IX Item "--export=FILE"
Export the next Veusz document file on the command line to the
graphics file \fI\s-1FILE\s0\fR. Supported file types include \s-1EPS\s0, \s-1PDF\s0, \s-1SVG\s0,
-\&\s-1PNG\s0, \s-1BMP\s0 and \s-1JPG\s0. The extension of the output file is used to
+\&\s-1PNG\s0, \s-1BMP\s0, \s-1JPG\s0 and \s-1XPM\s0. The extension of the output file is used to
determine the output file format. There should be as many export
options specified as input Veusz documents on the command line.
+.IP "\fB\-\-plugin\fR=\fI\s-1FILE\s0\fR" 8
+.IX Item "--plugin=FILE"
+Loads the Veusz plugin \fI\s-1FILE\s0\fR when starting Veusz. This option
+provides a per-session alternative to adding the plugin in the
+preferences dialog box.
.IP "\fB\-\-help\fR" 8
.IX Item "--help"
Displays the options to the program and exits.
@@ -187,7 +192,7 @@
This manual page was written by Jeremy Sanders .
.SH "COPYRIGHT"
.IX Header "COPYRIGHT"
-Copyright (C) 2003\-2010 Jeremy Sanders .
+Copyright (C) 2003\-2011 Jeremy Sanders .
.PP
This program is free software; you can redistribute it and/or modify it
under the terms of the \s-1GNU\s0 General Public License as published by the
diff -Nru veusz-1.10/Documents/veusz_listen.1 veusz-1.14/Documents/veusz_listen.1
--- veusz-1.10/Documents/veusz_listen.1 2010-12-12 12:41:53.000000000 +0000
+++ veusz-1.14/Documents/veusz_listen.1 2011-11-22 20:24:17.000000000 +0000
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pod::Man 2.23 (Pod::Simple 3.14)
+.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
.\"
.\" Standard preamble:
.\" ========================================================================
@@ -124,7 +124,7 @@
.\" ========================================================================
.\"
.IX Title "VEUSZ_LISTEN 1"
-.TH VEUSZ_LISTEN 1 "2010-12-12" "1.10" "Veusz"
+.TH VEUSZ_LISTEN 1 "2011-11-22" "1.13.999" "Veusz"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
@@ -159,7 +159,7 @@
This manual page was written by Jeremy Sanders .
.SH "COPYRIGHT"
.IX Header "COPYRIGHT"
-Copyright (C) 2003\-2010 Jeremy Sanders .
+Copyright (C) 2003\-2011 Jeremy Sanders .
.PP
This program is free software; you can redistribute it and/or modify it
under the terms of the \s-1GNU\s0 General Public License as published by the
diff -Nru veusz-1.10/Documents/veusz_listen.pod veusz-1.14/Documents/veusz_listen.pod
--- veusz-1.10/Documents/veusz_listen.pod 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/Documents/veusz_listen.pod 2011-11-22 20:23:31.000000000 +0000
@@ -35,7 +35,7 @@
=head1 COPYRIGHT
-Copyright (C) 2003-2010 Jeremy Sanders .
+Copyright (C) 2003-2011 Jeremy Sanders .
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
diff -Nru veusz-1.10/Documents/veusz.pod veusz-1.14/Documents/veusz.pod
--- veusz-1.10/Documents/veusz.pod 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/Documents/veusz.pod 2011-11-22 20:23:31.000000000 +0000
@@ -46,10 +46,16 @@
Export the next Veusz document file on the command line to the
graphics file I. Supported file types include EPS, PDF, SVG,
-PNG, BMP and JPG. The extension of the output file is used to
+PNG, BMP, JPG and XPM. The extension of the output file is used to
determine the output file format. There should be as many export
options specified as input Veusz documents on the command line.
+=item B<--plugin>=I
+
+Loads the Veusz plugin I when starting Veusz. This option
+provides a per-session alternative to adding the plugin in the
+preferences dialog box.
+
=item B<--help>
Displays the options to the program and exits.
@@ -72,7 +78,7 @@
=head1 COPYRIGHT
-Copyright (C) 2003-2010 Jeremy Sanders .
+Copyright (C) 2003-2011 Jeremy Sanders .
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
diff -Nru veusz-1.10/embed.py veusz-1.14/embed.py
--- veusz-1.10/embed.py 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/embed.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: embed.py 1331 2010-07-15 20:08:41Z jeremysanders $
-
"""This module allows veusz to be embedded within other Python programs.
For example:
@@ -49,9 +47,9 @@
import new
import cPickle
import socket
-import random
import subprocess
import time
+import uuid
# check remote process has this API version
API_VERSION = 1
@@ -196,14 +194,14 @@
# here is the list of commands to try
possiblecommands = [
- [findpython, os.path.join(thisdir, 'embed_remote.py')],
+ [findpython, os.path.join(thisdir, 'veusz_main.py')],
[findexe] ]
else:
# try embed_remote.py in this directory, veusz in this directory
# or veusz on the path in order
possiblecommands = [ [sys.executable,
- os.path.join(thisdir, 'embed_remote.py')],
+ os.path.join(thisdir, 'veusz_main.py')],
[os.path.join(thisdir, 'veusz')],
[findOnPath('veusz')] ]
@@ -251,17 +249,15 @@
if waitaccept:
cls.serv_socket, address = cls.serv_socket.accept()
- # send a secret to the remote program by secure route and
- # check it comes back
- # this is to check that no program has secretly connected
- # on our port, which isn't really useful for AF_UNIX sockets
- secret = ''.join([random.choice('ABCDEFGHUJKLMNOPQRSTUVWXYZ'
- 'abcdefghijklmnopqrstuvwxyz'
- '0123456789')
- for i in xrange(16)]) + '\n'
+ # Send a secret to the remote program by secure route and
+ # check it comes back. This is to check that no program has
+ # secretly connected on our port, which isn't really useful
+ # for AF_UNIX sockets.
+ secret = str(uuid.uuid4()) + '\n'
stdin.write(secret)
secretback = cls.readLenFromSocket(cls.serv_socket, len(secret))
- assert secret == secretback
+ if secret != secretback:
+ raise RuntimeError, "Security between client and server broken"
# packet length for command bytes
cls.cmdlen = struct.calcsize('>sys.stderr, ("This program must be run from "
- "the Veusz embedding module")
- sys.exit(1)
-
+def runremote():
+ """Run remote end of embedding module."""
# get connection parameters
params = sys.stdin.readline().split()
@@ -277,6 +283,3 @@
app = EmbedApplication(listensocket, [])
app.setQuitOnLastWindowClosed(False)
app.exec_()
-
-if __name__ == '__main__':
- main()
diff -Nru veusz-1.10/examples/coloredpoints.vsz veusz-1.14/examples/coloredpoints.vsz
--- veusz-1.10/examples/coloredpoints.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/examples/coloredpoints.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,360 @@
+# Veusz saved document (version 1.13.99)
+# Saved at 2011-11-09T19:53:17.746203
+
+ImportString('c(numeric)','''
+1.459606e+02
+1.702896e+02
+7.934817e+01
+1.227687e+01
+5.599601e+03
+1.539531e+01
+1.828166e+04
+5.054989e+03
+3.191051e+02
+7.949710e+00
+2.216535e+03
+5.143240e+03
+3.148290e+01
+4.965444e+01
+7.777005e+02
+3.185130e+02
+1.588387e+04
+2.813152e+02
+1.339360e+02
+4.215790e+03
+6.843036e+03
+8.415999e+01
+1.362926e+02
+6.243027e+03
+6.179793e+02
+1.897636e+03
+2.796312e+02
+1.026779e+01
+4.765936e+03
+1.570359e+04
+1.467323e+02
+1.164478e+03
+1.902282e+02
+1.520207e+04
+2.973087e+00
+2.337408e+00
+4.134565e+02
+7.166886e+03
+4.217587e+03
+9.953222e+02
+8.267064e+01
+5.035324e+00
+9.556994e+00
+8.546561e+01
+1.500701e+01
+1.351431e+01
+4.917555e+03
+2.496430e+00
+2.103088e+01
+1.077053e+03
+1.563147e+02
+3.163246e+03
+1.626294e+04
+4.627788e+03
+8.483016e+03
+5.805220e+03
+1.600690e+01
+2.339657e+02
+1.526946e+02
+4.282832e+02
+2.799937e+02
+3.737549e+02
+2.842098e+01
+1.275867e+03
+4.526153e+04
+2.403380e+04
+4.756101e+01
+2.977493e+04
+5.519853e+01
+2.365612e+02
+4.460124e+03
+2.131700e+03
+6.597176e+03
+8.787685e+02
+3.755261e+04
+5.750869e+02
+7.043527e+02
+8.845572e+03
+1.043050e+02
+5.741879e+00
+2.603017e+02
+1.178276e+01
+9.850848e+00
+9.117799e+02
+1.102115e+02
+9.833058e+01
+2.374284e+02
+1.129614e+04
+2.973567e+03
+2.594940e+02
+3.478364e+01
+2.132164e+00
+7.268064e+03
+1.879249e+01
+1.321168e+02
+1.057914e+03
+7.070878e+01
+4.039144e+00
+7.826971e+01
+1.123204e+04
+''')
+SetDataExpression(u'sizef', u'abs(x-0.5)+0.5', linked=True)
+ImportString('x(numeric)','''
+3.259640e-01
+2.936538e-01
+3.162201e-01
+2.638131e-01
+8.803033e-01
+2.577980e-01
+9.479319e-01
+5.931048e-01
+5.222732e-01
+1.018496e-01
+7.321032e-01
+7.753362e-01
+2.971560e-01
+3.875175e-01
+4.714681e-01
+5.461489e-01
+8.912572e-01
+3.138063e-01
+3.838737e-01
+7.173045e-01
+9.487288e-01
+3.029883e-01
+4.863407e-01
+8.589109e-01
+5.710536e-01
+7.576283e-01
+5.049896e-01
+2.308246e-01
+8.570804e-01
+8.612760e-01
+4.759630e-01
+5.248471e-01
+4.719874e-01
+7.990987e-01
+9.636075e-02
+6.375893e-02
+6.057062e-01
+9.102558e-01
+7.864667e-01
+6.260576e-01
+4.087506e-01
+1.741066e-01
+1.165042e-01
+3.446296e-01
+2.170302e-01
+9.573020e-02
+8.219256e-01
+9.182756e-02
+2.018576e-01
+6.840850e-01
+3.888053e-01
+8.064366e-01
+9.381433e-01
+8.748492e-01
+8.716737e-01
+8.351529e-01
+1.047454e-01
+4.707636e-01
+4.919583e-01
+5.672392e-01
+4.050533e-01
+4.783146e-01
+3.282565e-01
+6.755866e-01
+9.435113e-01
+9.404685e-01
+2.656279e-01
+9.456492e-01
+3.985884e-01
+5.263482e-01
+6.925591e-01
+6.756365e-01
+9.412488e-01
+6.768923e-01
+8.147229e-01
+5.731504e-01
+5.591458e-01
+8.806983e-01
+4.030426e-01
+1.638092e-01
+4.160340e-01
+2.343386e-01
+1.942402e-01
+6.532868e-01
+3.740195e-01
+3.338149e-01
+3.336983e-01
+8.617688e-01
+7.426878e-01
+4.504087e-01
+2.690837e-01
+5.143151e-02
+9.142272e-01
+1.996254e-01
+5.223122e-01
+7.060125e-01
+3.945721e-01
+1.191278e-01
+3.037703e-01
+8.536895e-01
+''')
+ImportString('y(numeric)','''
+1.720759e+00
+-2.113146e+00
+-1.269313e+00
+6.767068e-02
+-4.538878e-01
+-3.123929e-01
+-9.405761e-01
+-2.662602e+00
+-8.296818e-01
+-9.859058e-01
+-8.345237e-01
+1.219784e+00
+6.189014e-01
+2.917761e-01
+-2.009880e+00
+-6.370632e-01
+1.271855e+00
+-2.387936e+00
+-1.182805e+00
+1.511322e+00
+8.066724e-02
+-1.426305e+00
+-3.782192e-01
+-7.195032e-01
+-1.013519e+00
+-4.953992e-01
+-8.532545e-01
+-1.763571e-01
+4.996532e-01
+-1.501790e+00
+-5.253474e-01
+-1.933486e+00
+7.826508e-01
+1.971016e+00
+1.755292e-01
+2.273975e-01
+3.872100e-01
+-4.286146e-01
+9.583945e-01
+-9.874666e-01
+5.646978e-01
+1.120199e-02
+1.028609e+00
+-1.106546e+00
+6.163468e-01
+-1.495746e+00
+-8.080937e-01
+6.001841e-02
+-1.030854e+00
+-5.917945e-01
+-1.277557e+00
+-5.487731e-01
+-9.172515e-01
+3.319534e-01
+-8.837110e-01
+8.464141e-01
+1.570651e+00
+-9.721957e-01
+-4.319809e-01
+7.255485e-01
+1.653870e+00
+-1.318657e+00
+2.812261e-01
+-8.069181e-01
+-1.763368e+00
+-1.237897e+00
+-1.229479e+00
+-1.382508e+00
+2.951479e-01
+-5.371013e-01
+-1.758221e+00
+-1.252360e+00
+-1.087257e-01
+-4.726106e-01
+-2.631497e+00
+-9.342638e-01
+1.222414e+00
+-8.478654e-01
+8.122695e-01
+2.076344e-01
+-1.502682e+00
+2.677853e-01
+4.330256e-01
+-6.934856e-01
+1.092298e+00
+1.314858e+00
+-2.081479e+00
+-1.211710e+00
+1.005053e+00
+-1.224985e+00
+-9.300805e-01
+2.461890e-01
+-4.090199e-01
+9.509655e-01
+-6.341827e-02
+4.008008e-01
+5.423699e-01
+2.595563e-01
+1.357025e+00
+-1.271401e+00
+''')
+Set('StyleSheet/Font/font', u'Arial')
+Set('StyleSheet/axis/Line/width', u'1pt')
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Set('Background/color', u'#e5e9ff')
+Set('Border/width', u'1pt')
+Add('colorbar', name='colorbar1', autoadd=False)
+To('colorbar1')
+Set('widgetName', u'xy1')
+Set('label', u'Power (W)')
+Set('autoExtend', False)
+Set('autoExtendZero', False)
+Set('lowerPosition', 0.0)
+Set('upperPosition', 1.0)
+Set('otherPosition', 0.0)
+Set('TickLabels/format', u'%VE')
+Set('horzPosn', u'centre')
+Set('vertPosn', u'top')
+Set('width', u'8cm')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('marker', u'circle')
+Set('markerSize', u'5pt')
+Set('scalePoints', u'sizef')
+Set('Color/points', u'c')
+Set('Color/min', 2.0)
+Set('Color/max', 4500.0)
+Set('Color/scaling', u'log')
+Set('PlotLine/hide', True)
+Set('MarkerFill/colorMap', u'complement')
+To('..')
+Add('axis', name='x', autoadd=False)
+To('x')
+Set('label', u'Time (yr)')
+Set('GridLines/hide', False)
+To('..')
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('label', u'Offset (m)')
+Set('min', -3.0)
+Set('max', 3.0)
+Set('autoExtend', False)
+Set('direction', 'vertical')
+Set('GridLines/hide', False)
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/examples/embedexample.py veusz-1.14/examples/embedexample.py
--- veusz-1.10/examples/embedexample.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/examples/embedexample.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: embedexample.py 964 2009-05-10 11:26:12Z jeremysanders $
-
"""An example embedding program. Veusz needs to be installed into
the Python path for this to work (use setup.py)
diff -Nru veusz-1.10/examples/linked_datasets.vsz veusz-1.14/examples/linked_datasets.vsz
--- veusz-1.10/examples/linked_datasets.vsz 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/examples/linked_datasets.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -1,127 +1,33 @@
-# Veusz saved document (version 0.10.cvs)
-# User: jss
-# Date: Wed, 14 Jun 2006 20:08:16 +0000
+# Veusz saved document (version 1.12.999)
+# Saved at 2011-08-18T18:35:23.805605
-SetDataExpression(u'xhalf', u'x/2*y', linked=True)
+SetDataRange(u't', 100, (-3.141592, 3.141592), linked=True)
+SetDataExpression(u'x', u'sin(t)', linked=True)
SetDataExpression(u'x2', u'sin(t*8)', linked=True)
-ImportString(u't','''
--3.141593e+00
--3.078126e+00
--3.014660e+00
--2.951193e+00
--2.887727e+00
--2.824260e+00
--2.760793e+00
--2.697327e+00
--2.633860e+00
--2.570394e+00
--2.506927e+00
--2.443461e+00
--2.379994e+00
--2.316528e+00
--2.253061e+00
--2.189595e+00
--2.126128e+00
--2.062662e+00
--1.999195e+00
--1.935729e+00
--1.872262e+00
--1.808796e+00
--1.745329e+00
--1.681863e+00
--1.618396e+00
--1.554930e+00
--1.491463e+00
--1.427997e+00
--1.364530e+00
--1.301064e+00
--1.237597e+00
--1.174131e+00
--1.110664e+00
--1.047198e+00
--9.837310e-01
--9.202645e-01
--8.567980e-01
--7.933315e-01
--7.298649e-01
--6.663984e-01
--6.029319e-01
--5.394654e-01
--4.759989e-01
--4.125324e-01
--3.490658e-01
--2.855993e-01
--2.221328e-01
--1.586663e-01
--9.519978e-02
--3.173326e-02
-3.173326e-02
-9.519978e-02
-1.586663e-01
-2.221328e-01
-2.855993e-01
-3.490658e-01
-4.125324e-01
-4.759989e-01
-5.394654e-01
-6.029319e-01
-6.663984e-01
-7.298649e-01
-7.933315e-01
-8.567980e-01
-9.202645e-01
-9.837310e-01
-1.047198e+00
-1.110664e+00
-1.174131e+00
-1.237597e+00
-1.301064e+00
-1.364530e+00
-1.427997e+00
-1.491463e+00
-1.554930e+00
-1.618396e+00
-1.681863e+00
-1.745329e+00
-1.808796e+00
-1.872262e+00
-1.935729e+00
-1.999195e+00
-2.062662e+00
-2.126128e+00
-2.189595e+00
-2.253061e+00
-2.316528e+00
-2.379994e+00
-2.443461e+00
-2.506927e+00
-2.570394e+00
-2.633860e+00
-2.697327e+00
-2.760793e+00
-2.824260e+00
-2.887727e+00
-2.951193e+00
-3.014660e+00
-3.078126e+00
-3.141593e+00
-''')
SetDataExpression(u'x3', u'sin(x*2)', linked=True)
+SetDataExpression(u'xhalf', u'x/2*y', linked=True)
SetDataExpression(u'y', u'cos(t)', linked=True)
-SetDataExpression(u'x', u'sin(t)', linked=True)
SetDataExpression(u'y2', u'cos(t*16)', linked=True)
SetDataExpression(u'yhalf', u'y/2', linked=True)
+Set('StyleSheet/Line/width', u'1pt')
+Set('StyleSheet/Font/font', u'Arial')
+Set('StyleSheet/axis/Label/size', u'18pt')
+Set('StyleSheet/axis/MajorTicks/number', 8)
+Set('StyleSheet/xy/markerSize', u'4pt')
Add('page', name='page1', autoadd=False)
To('page1')
Add('graph', name='graph1', autoadd=False)
To('graph1')
+Set('Background/color', u'#e9ffff')
Add('axis', name='x', autoadd=False)
To('x')
Set('label', u'Experiments with linked datasets')
+Set('autoExtend', False)
To('..')
Add('axis', name='y', autoadd=False)
To('y')
-Set('label', u'Funky chicken')
+Set('label', u'Another axis')
+Set('autoExtend', False)
Set('direction', 'vertical')
To('..')
Add('xy', name='xy1', autoadd=False)
@@ -133,14 +39,18 @@
To('xy2')
Set('xData', u'x2')
Set('yData', u'y2')
+Set('PlotLine/color', u'#00aa00')
Set('MarkerFill/color', u'green')
To('..')
Add('xy', name='xy3', autoadd=False)
To('xy3')
Set('xData', u'x3')
Set('marker', u'star')
+Set('markerSize', u'6pt')
Set('PlotLine/color', u'red')
Set('PlotLine/width', u'1pt')
+Set('MarkerLine/hide', False)
+Set('MarkerFill/color', u'red')
To('..')
Add('xy', name='xy4', autoadd=False)
To('xy4')
diff -Nru veusz-1.10/examples/starchart.vsz veusz-1.14/examples/starchart.vsz
--- veusz-1.10/examples/starchart.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/examples/starchart.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,1268 @@
+# Veusz saved document (version 1.12.99)
+# Saved at 2011-08-17T21:38:56.668330
+
+ImportString('size(numeric)','''
+4.050e-01
+3.455e-01
+5.800e-01
+8.497e-01
+8.651e-01
+8.397e-01
+7.715e-01
+3.617e-01
+2.565e-01
+6.906e-01
+5.932e-01
+8.708e-01
+2.335e-01
+2.521e-01
+4.786e-01
+7.606e-01
+3.874e-01
+3.600e-01
+5.842e-01
+7.647e-01
+4.191e-01
+7.555e-01
+8.471e-01
+4.407e-01
+4.300e-01
+1.249e-01
+6.256e-02
+4.905e-01
+2.311e-02
+3.583e-01
+7.830e-01
+5.041e-01
+3.003e-01
+6.946e-01
+4.093e-01
+3.897e-01
+4.539e-01
+6.352e-01
+8.445e-01
+2.948e-01
+5.420e-01
+4.801e-01
+8.155e-01
+4.433e-02
+3.789e-01
+5.625e-01
+6.695e-02
+7.941e-01
+3.804e-01
+9.344e-01
+8.059e-01
+1.631e-01
+4.861e-02
+6.394e-01
+3.932e-01
+2.929e-01
+8.481e-01
+3.624e-01
+3.574e-02
+1.552e-01
+3.909e-01
+4.646e-02
+8.587e-01
+1.063e-02
+9.837e-01
+7.219e-01
+7.637e-01
+6.057e-01
+8.209e-01
+4.198e-01
+6.915e-01
+9.860e-01
+9.955e-01
+3.070e-01
+4.891e-01
+9.027e-02
+1.901e-01
+4.686e-01
+4.856e-01
+8.366e-01
+2.897e-02
+9.239e-01
+7.923e-01
+4.719e-01
+7.960e-01
+9.449e-01
+5.453e-01
+6.638e-01
+7.995e-01
+9.712e-02
+3.511e-01
+5.018e-01
+1.632e-01
+1.294e-02
+6.344e-01
+6.543e-02
+7.970e-01
+2.727e-01
+2.513e-01
+8.460e-01
+3.325e-02
+7.681e-02
+5.476e-01
+7.590e-01
+8.837e-01
+1.755e-01
+9.289e-01
+1.040e-01
+4.746e-01
+6.847e-02
+6.449e-01
+6.703e-01
+5.274e-02
+4.158e-01
+9.168e-01
+1.963e-01
+4.417e-01
+4.567e-01
+2.895e-01
+5.929e-03
+9.152e-01
+8.444e-01
+8.529e-01
+5.076e-01
+4.550e-01
+6.566e-01
+7.721e-01
+3.514e-01
+3.746e-01
+9.664e-01
+1.667e-01
+9.201e-02
+4.317e-01
+4.654e-01
+6.015e-01
+6.814e-01
+4.866e-01
+9.389e-01
+8.908e-01
+4.130e-02
+4.716e-01
+5.987e-01
+4.767e-02
+8.905e-01
+8.800e-01
+7.935e-01
+7.342e-01
+3.535e-01
+9.705e-01
+1.858e-01
+9.075e-01
+9.458e-02
+1.833e-01
+2.194e-01
+1.657e-01
+2.087e-01
+4.359e-01
+3.506e-01
+9.515e-01
+8.851e-01
+4.048e-02
+1.991e-01
+3.447e-01
+2.877e-01
+1.286e-01
+3.182e-01
+4.290e-01
+7.130e-01
+5.366e-01
+5.687e-01
+5.497e-01
+7.495e-01
+7.154e-01
+2.551e-01
+4.554e-01
+6.916e-01
+6.407e-01
+2.440e-01
+9.261e-01
+5.933e-02
+8.469e-01
+3.196e-01
+8.569e-01
+6.384e-01
+2.937e-01
+5.292e-02
+1.931e-01
+2.760e-01
+2.912e-02
+3.069e-01
+9.244e-01
+1.239e-01
+2.516e-01
+4.644e-01
+9.013e-02
+9.745e-01
+3.796e-01
+2.707e-02
+9.780e-01
+2.674e-01
+''')
+ImportString('size2(numeric)','''
+4.149e-01
+6.146e-01
+2.448e-01
+7.628e-02
+1.768e-01
+2.063e-01
+5.393e-01
+2.900e-01
+3.645e-01
+8.713e-02
+5.542e-01
+8.222e-02
+4.418e-01
+6.340e-01
+6.466e-01
+6.498e-01
+6.988e-01
+1.329e-01
+3.229e-01
+5.532e-01
+4.700e-01
+1.371e-01
+3.681e-01
+5.719e-01
+6.000e-01
+5.295e-01
+3.881e-01
+2.361e-01
+1.955e-01
+1.751e-01
+7.426e-01
+5.149e-01
+3.111e-01
+2.939e-01
+1.782e-01
+3.758e-01
+6.307e-01
+5.059e-01
+7.406e-01
+4.687e-01
+8.772e-03
+6.461e-02
+4.382e-02
+9.037e-02
+4.053e-01
+5.267e-01
+9.926e-02
+5.119e-01
+4.667e-01
+2.564e-01
+1.839e-01
+3.433e-01
+3.153e-01
+3.838e-01
+5.806e-01
+4.922e-01
+3.729e-02
+1.960e-01
+6.638e-01
+5.121e-01
+6.760e-01
+2.950e-01
+2.766e-01
+2.515e-01
+7.347e-01
+1.599e-02
+2.976e-01
+1.024e-01
+3.245e-01
+2.576e-02
+4.938e-01
+5.979e-01
+6.476e-01
+5.961e-01
+2.611e-01
+5.783e-01
+4.673e-01
+3.781e-01
+7.286e-01
+2.363e-01
+6.622e-01
+4.862e-01
+7.304e-01
+3.534e-01
+6.367e-01
+1.068e-01
+3.883e-01
+6.653e-01
+5.703e-01
+2.747e-01
+3.201e-01
+5.755e-01
+6.744e-01
+3.886e-01
+7.911e-02
+8.700e-02
+3.535e-01
+1.245e-01
+1.195e-01
+5.841e-01
+6.398e-01
+1.762e-01
+5.250e-01
+5.847e-01
+4.941e-01
+6.708e-01
+3.526e-01
+4.299e-01
+4.609e-01
+4.045e-02
+3.338e-01
+4.018e-01
+5.591e-01
+2.480e-01
+3.281e-01
+2.539e-01
+6.600e-01
+7.943e-02
+3.481e-01
+1.625e-02
+5.123e-01
+3.390e-01
+3.904e-01
+4.350e-01
+5.448e-01
+4.653e-01
+4.141e-01
+1.425e-01
+4.031e-01
+3.517e-01
+6.214e-01
+7.483e-01
+6.904e-01
+5.430e-02
+2.179e-01
+4.177e-01
+1.834e-01
+5.792e-02
+2.297e-01
+5.871e-01
+2.647e-01
+1.269e-01
+2.579e-01
+7.247e-01
+4.171e-01
+2.275e-01
+7.145e-01
+6.673e-01
+3.532e-01
+6.283e-01
+9.327e-02
+1.819e-01
+6.711e-01
+2.019e-01
+5.201e-01
+3.551e-01
+8.790e-02
+4.421e-01
+6.433e-01
+5.816e-01
+4.612e-01
+5.547e-02
+2.555e-01
+6.520e-01
+2.823e-01
+4.777e-01
+4.286e-01
+3.108e-01
+1.322e-01
+4.525e-01
+3.051e-01
+1.148e-01
+6.799e-01
+7.144e-01
+1.421e-01
+2.629e-01
+1.992e-01
+4.288e-01
+2.898e-01
+5.219e-01
+1.484e-01
+6.617e-01
+2.670e-01
+7.308e-01
+4.514e-01
+5.690e-01
+2.678e-01
+7.160e-02
+3.514e-02
+2.425e-01
+3.181e-01
+3.302e-01
+3.846e-01
+2.463e-01
+5.676e-02
+5.476e-01
+2.593e-01
+1.812e-01
+2.168e-01
+5.940e-01
+''')
+ImportString('x(numeric)','''
+-1.717564e-01
+4.242809e-01
+1.582449e+00
+9.042384e-01
+-7.558175e-01
+9.180096e-01
+1.688422e+00
+-5.624878e-01
+1.148113e+00
+-6.757747e-01
+-7.184862e-01
+9.559501e-02
+-7.735163e-01
+-1.368390e+00
+2.839300e-01
+3.863967e-01
+-1.018726e+00
+-1.057551e+00
+1.480676e+00
+-1.436318e+00
+-6.077504e-01
+-6.134922e-01
+-1.451637e+00
+6.367692e-01
+-2.051966e+00
+-1.061056e-01
+1.132593e+00
+-2.332081e+00
+-1.483644e-01
+-1.709232e+00
+7.715420e-01
+1.825963e-01
+-1.242801e+00
+5.126108e-01
+7.855827e-01
+-1.391835e+00
+3.679549e-01
+-1.126583e+00
+7.381246e-01
+-7.205989e-01
+2.466142e-01
+-1.842360e+00
+-1.307182e+00
+7.427424e-01
+-3.075417e-01
+1.075113e+00
+-1.109696e+00
+-3.353667e-02
+-1.087587e+00
+1.517819e-03
+1.402492e+00
+-4.629109e-01
+-2.314234e-01
+-1.404300e+00
+-7.136651e-01
+1.143241e-01
+-2.593171e-01
+5.261581e-01
+7.312414e-01
+9.634989e-01
+6.653014e-02
+-1.525272e+00
+-1.979862e+00
+8.790669e-02
+-3.367087e-01
+-1.487463e+00
+-6.323704e-01
+-2.691609e-01
+-1.192839e+00
+7.129419e-01
+-2.026876e+00
+-1.670084e+00
+-1.105418e+00
+4.942608e-01
+1.440158e+00
+9.895613e-01
+-5.584712e-01
+3.488847e-02
+-5.603513e-03
+9.674879e-01
+6.670954e-02
+9.170016e-01
+-1.313607e+00
+-2.246257e+00
+2.042009e+00
+1.025452e+00
+-8.301401e-01
+4.508979e-01
+5.296303e-01
+1.255025e+00
+1.236309e+00
+-8.178501e-01
+1.658664e+00
+-1.020029e+00
+-7.597083e-01
+1.642019e+00
+1.163782e+00
+3.648611e-01
+1.205375e+00
+-2.208854e+00
+1.961078e-01
+2.150545e-01
+1.335152e+00
+2.279445e-01
+-3.136242e-02
+9.497465e-01
+2.207430e-01
+-7.895290e-01
+-8.275847e-01
+7.666461e-01
+-1.008892e-01
+-1.343381e+00
+-1.484571e+00
+5.167052e-01
+-3.572661e-01
+1.440624e+00
+9.023146e-01
+-3.818540e-01
+-1.613737e+00
+-1.362967e-01
+7.986278e-01
+5.329052e-01
+-7.147625e-02
+1.977507e+00
+6.095448e-01
+-1.774797e+00
+-5.755160e-01
+-6.896392e-01
+3.003489e-01
+1.888296e-01
+7.108283e-01
+8.797144e-01
+2.526157e-01
+3.947440e-01
+8.264080e-01
+-2.686278e-01
+-5.568894e-01
+-8.562743e-01
+-6.682676e-01
+1.613319e-01
+8.180479e-02
+7.551501e-01
+1.345002e-01
+-5.441405e-01
+-4.750194e-01
+-1.144256e+00
+-3.917083e-02
+5.153497e-01
+-3.864621e-01
+-9.947071e-01
+4.151202e-01
+1.694649e-01
+8.304631e-01
+-2.744186e+00
+-5.189704e-01
+5.950722e-01
+4.409983e-01
+-3.860202e-01
+2.654908e+00
+-4.632929e-01
+3.676008e-02
+3.220285e-01
+-1.910937e-01
+-7.468510e-01
+-8.650068e-01
+-9.713170e-01
+-1.130643e-01
+-3.678296e-01
+-4.921382e-01
+1.181965e-01
+6.255350e-01
+-1.242620e+00
+1.156373e+00
+7.030362e-01
+9.894105e-02
+5.791196e-01
+-8.927444e-01
+1.267695e+00
+-7.141264e-01
+2.025816e+00
+-1.293909e+00
+-1.477149e+00
+-3.851663e-02
+-6.038046e-01
+-5.637100e-01
+-7.411827e-01
+-1.302341e+00
+-9.121214e-01
+1.058442e+00
+1.873947e+00
+-6.876215e-01
+-9.217540e-02
+-1.163100e+00
+1.145855e+00
+-4.506662e-01
+-1.423829e+00
+1.236884e-01
+2.303835e-01
+1.388673e+00
+9.321516e-01
+''')
+ImportString('x2(numeric)','''
+-5.904152e-01
+6.036018e-01
+1.861803e-01
+-1.182390e-01
+4.273179e-01
+-5.329131e-01
+3.477783e-01
+-4.442659e-01
+4.112825e-01
+-6.975073e-01
+-7.894340e-02
+3.737914e-01
+5.502770e-01
+-1.807715e-01
+2.749290e-01
+8.347531e-01
+-2.566171e-01
+4.480129e-02
+-1.661492e-01
+-1.005103e+00
+6.827219e-02
+1.267272e-01
+-7.289300e-02
+7.991350e-02
+-4.951967e-01
+1.395421e+00
+-1.094929e+00
+3.143240e-01
+1.436579e-01
+5.987043e-01
+8.988077e-04
+6.491476e-01
+-1.945068e-01
+-1.464254e-01
+1.318037e-01
+5.736032e-01
+-6.424045e-01
+1.929405e-02
+1.083004e-01
+-4.876157e-01
+-1.975081e-01
+-9.470392e-01
+7.348370e-02
+-3.555848e-01
+-4.303004e-01
+-1.937558e-01
+-3.060530e-01
+-1.239214e+00
+-7.159538e-01
+-1.180139e-01
+-8.582931e-01
+9.332523e-01
+8.186905e-02
+1.658889e-01
+3.878644e-01
+4.596426e-01
+-2.580098e-01
+1.466808e-01
+-2.334527e-01
+-7.628955e-01
+-4.923554e-02
+-1.866111e-01
+-1.203111e-01
+7.535110e-01
+3.363780e-01
+-7.143939e-01
+9.262992e-01
+4.826713e-02
+-3.288597e-01
+-3.829378e-01
+6.682712e-01
+-7.421102e-01
+4.511552e-02
+-7.936474e-01
+5.949329e-01
+6.510491e-02
+-9.386907e-02
+7.775805e-01
+1.218719e-01
+-1.494402e-02
+-6.653168e-03
+-1.096615e+00
+-5.639730e-01
+-2.858281e-01
+4.137544e-01
+9.787053e-02
+-3.736432e-02
+1.178890e-01
+8.644677e-02
+8.907128e-01
+-2.575110e-01
+8.681292e-01
+3.192787e-01
+-4.730755e-02
+3.308247e-02
+5.196318e-01
+1.612190e-02
+-9.430100e-02
+-3.501541e-01
+1.988158e-01
+-6.610444e-02
+3.183876e-01
+9.110975e-01
+3.537435e-01
+2.405701e-02
+-1.046873e+00
+3.043448e-01
+1.020531e-01
+-5.537825e-01
+-1.344010e-01
+-4.608990e-01
+9.335337e-01
+-9.321411e-02
+3.717192e-01
+3.558881e-01
+-2.581769e-01
+7.042958e-01
+4.890943e-02
+-3.936620e-01
+-1.006861e+00
+-4.333877e-01
+7.150886e-01
+-4.349794e-01
+4.448926e-01
+4.621154e-01
+1.543000e-01
+-1.210074e-01
+-9.640434e-02
+6.018297e-01
+-1.054106e-02
+-9.582333e-02
+-1.221224e+00
+-2.510498e-01
+8.605007e-01
+-2.235232e-01
+6.005776e-01
+5.389756e-02
+2.774058e-01
+-7.967761e-02
+-2.286575e-01
+-2.433566e-01
+-1.338816e-01
+-1.915733e-01
+-9.346132e-02
+-3.945893e-01
+-1.000764e+00
+-4.389132e-01
+-5.512491e-01
+-3.093361e-01
+-5.310395e-01
+5.232963e-01
+-4.543161e-01
+1.269040e-01
+-2.244547e-01
+6.759115e-01
+2.601059e-01
+-4.742974e-01
+7.627344e-02
+1.575795e-01
+2.050366e-01
+3.363180e-01
+5.956497e-01
+7.390307e-01
+6.510501e-01
+-7.047546e-01
+8.470191e-01
+-2.368321e-01
+-2.557962e-01
+9.486894e-01
+-3.198666e-01
+-6.815602e-02
+-2.756185e-01
+9.079499e-01
+6.653649e-01
+1.566721e-01
+-1.089555e+00
+-1.078753e-01
+-1.503253e-01
+1.093731e+00
+6.795102e-01
+9.591976e-01
+7.418597e-02
+-4.870427e-01
+-1.311627e-01
+3.575294e-01
+2.585647e-01
+-1.156918e-01
+-9.740063e-02
+7.856365e-01
+7.415529e-01
+2.188530e-01
+5.858654e-01
+6.431388e-01
+7.472439e-01
+-3.185660e-01
+1.466183e-01
+-3.765560e-01
+-2.363125e-01
+-7.311099e-03
+4.543543e-01
+''')
+ImportString('y(numeric)','''
+-1.179418e+00
+9.963364e-01
+1.035245e+00
+-4.930856e-01
+-3.079140e-01
+-8.363301e-01
+3.772350e-01
+-1.793423e+00
+-3.955435e-01
+-7.903513e-01
+2.248519e-01
+8.868708e-01
+1.385193e+00
+-9.564458e-02
+-2.326000e-01
+-1.571620e-01
+6.207154e-01
+3.515783e-01
+-6.039606e-01
+-4.724230e-01
+-2.933388e-01
+-8.633148e-01
+-1.216204e-01
+-9.216055e-01
+-1.218777e+00
+7.417790e-01
+-5.619947e-02
+5.039686e-01
+-2.296762e-01
+-6.494386e-01
+1.621411e+00
+-2.070276e-01
+3.945420e-01
+-6.584078e-02
+-1.396862e-01
+1.662038e+00
+-1.202367e+00
+2.562593e-01
+-2.478231e-01
+-8.922410e-01
+1.175726e+00
+2.447118e-01
+1.086628e+00
+-1.745885e+00
+1.342561e+00
+-9.685250e-01
+1.514767e+00
+-8.541269e-01
+-7.186263e-01
+-2.286539e-01
+-4.432723e-03
+-2.748991e-01
+-1.319403e-01
+-1.297866e+00
+-1.293355e+00
+-2.800920e+00
+4.857350e-01
+1.273159e+00
+-8.956792e-01
+1.418659e+00
+1.314727e+00
+9.482291e-01
+8.534508e-01
+2.193894e+00
+8.981459e-01
+-8.840747e-02
+-8.619552e-01
+4.299827e-01
+-4.838336e-01
+1.537284e+00
+-1.290272e+00
+4.619874e-01
+-1.756571e-01
+1.502658e+00
+-1.084621e+00
+1.366688e+00
+6.150953e-01
+-1.909314e+00
+1.499255e+00
+-5.910319e-01
+1.289383e-01
+-1.935533e+00
+-7.818810e-02
+1.060331e+00
+-8.665276e-01
+3.874504e-01
+5.042166e-01
+6.286272e-01
+1.567413e+00
+3.106100e-01
+7.147362e-01
+-1.696396e+00
+9.068055e-01
+-1.004093e+00
+-5.714048e-01
+-9.721016e-01
+5.559200e-01
+2.595251e+00
+4.741763e-01
+-4.348529e-01
+-1.789185e+00
+-3.293816e-01
+-6.850946e-01
+2.887457e-01
+6.801073e-01
+-3.200756e-01
+5.279586e-01
+-1.002568e+00
+-1.523962e+00
+-8.351697e-01
+1.209814e+00
+-7.876147e-01
+6.289090e-01
+-2.341422e-01
+-3.740775e-01
+2.385541e-01
+-8.140004e-02
+9.481686e-02
+1.225121e+00
+-3.815732e-01
+-7.840012e-01
+2.927405e+00
+9.265183e-01
+7.812431e-01
+1.302915e+00
+6.347094e-01
+5.027754e-01
+-7.110444e-01
+-4.297201e-01
+1.828868e+00
+-4.414072e-02
+-1.110879e+00
+3.847165e-01
+-4.791328e-01
+3.638377e-01
+-1.728797e+00
+-4.163380e-02
+2.300275e-01
+-8.915757e-01
+-6.725092e-01
+9.569900e-01
+-4.703625e-01
+1.274738e+00
+1.264137e+00
+-4.649065e-01
+-4.831641e-02
+-1.410860e+00
+6.752751e-01
+-2.005785e+00
+-8.789505e-01
+-5.581799e-01
+6.318567e-01
+-7.021317e-01
+2.698140e-01
+-2.171639e+00
+-4.838061e-01
+-4.254585e-02
+7.594293e-01
+1.628929e+00
+-7.460201e-01
+-3.770777e-01
+8.849620e-02
+6.164952e-02
+1.475529e+00
+-1.113757e+00
+-1.621159e+00
+4.006426e-01
+1.077418e-01
+-1.913829e+00
+-1.317677e+00
+4.880339e-01
+-4.312087e-01
+-1.442873e-01
+-2.004930e-02
+5.019437e-01
+-7.684092e-01
+1.541577e+00
+-3.599171e-01
+-9.827931e-01
+-1.514225e-01
+3.947186e-02
+1.175757e+00
+1.097290e+00
+-5.108434e-01
+-2.445287e+00
+-7.330248e-02
+-4.411181e-01
+9.852479e-01
+-1.161989e+00
+-1.082814e+00
+-5.290654e-02
+1.425348e+00
+-2.933696e-01
+-8.431636e-01
+1.201905e+00
+7.220561e-02
+-6.565752e-02
+2.359271e+00
+4.880444e-01
+1.560229e+00
+''')
+ImportString('y2(numeric)','''
+3.277343e-01
+-4.791839e-01
+-5.622894e-01
+-8.147905e-01
+1.461145e-01
+-4.588977e-01
+3.968568e-01
+1.423664e-01
+5.319692e-01
+5.065758e-01
+-6.637735e-01
+-2.946463e-01
+-4.624716e-01
+6.319409e-01
+4.678516e-02
+2.219609e-01
+1.170330e+00
+7.035864e-01
+-3.346529e-01
+1.734514e-03
+7.248274e-01
+6.135434e-01
+1.028304e-01
+-1.206416e-01
+2.208649e-02
+6.919419e-01
+3.821196e-01
+2.178115e-01
+1.770095e-01
+-1.652230e-02
+9.046649e-01
+-4.432710e-01
+2.206628e-01
+-4.543656e-01
+1.301324e+00
+5.292525e-01
+-4.944746e-01
+2.174136e-01
+-3.947546e-01
+4.118183e-01
+3.404201e-01
+6.837868e-01
+8.153092e-01
+-1.681122e-01
+-8.328832e-01
+-9.128235e-02
+-7.871977e-01
+1.963190e-01
+6.868842e-01
+7.185369e-01
+-1.860621e-01
+9.277049e-01
+-5.712796e-01
+9.867310e-01
+-7.430959e-01
+-4.200795e-01
+-3.312437e-01
+3.417390e-01
+5.875478e-02
+-8.900409e-01
+4.459047e-01
+-2.478977e-01
+2.576785e-01
+-1.180131e+00
+3.503088e-01
+2.095792e-01
+-5.624632e-01
+-1.504682e-01
+1.082096e-01
+2.748657e-02
+-2.188184e-01
+-3.769354e-01
+3.687838e-01
+-1.177511e-01
+-2.714600e-01
+-1.006103e-01
+-1.509157e-01
+7.425458e-02
+1.376503e-01
+-4.981824e-01
+-4.858082e-01
+2.773604e-01
+1.011194e-02
+7.998077e-01
+5.213352e-01
+-2.153188e-01
+7.149105e-01
+-4.479349e-02
+4.948704e-02
+-3.989437e-01
+-2.229656e-01
+2.417801e-01
+4.207912e-01
+-2.746693e-01
+2.094705e-01
+1.712305e-01
+-5.440275e-01
+2.324411e-01
+1.052073e+00
+-1.872281e-01
+6.171029e-01
+1.811711e-02
+3.791602e-01
+-8.040386e-02
+2.399947e-01
+-3.413228e-01
+1.895484e-01
+-2.544584e-01
+-4.259011e-02
+6.655630e-01
+-7.508104e-01
+3.565060e-01
+6.546992e-01
+-5.122739e-01
+-5.014537e-01
+4.045582e-01
+9.928116e-01
+6.045285e-01
+5.509190e-02
+6.477872e-01
+4.653931e-01
+3.743647e-01
+8.731676e-01
+2.466262e-01
+1.092800e-01
+-2.989571e-02
+1.348799e-01
+1.836956e-01
+2.409095e-01
+4.300016e-01
+-1.057074e+00
+-6.740874e-01
+-9.931708e-02
+-1.897434e-01
+8.633123e-01
+1.121861e+00
+7.219913e-01
+5.390399e-01
+-1.552721e-02
+-2.794775e-01
+1.651937e-01
+3.403011e-01
+-4.625169e-01
+2.946608e-01
+2.377226e-01
+-3.711845e-01
+1.413453e-01
+2.804835e-01
+-1.872180e-01
+4.300357e-01
+-4.654751e-01
+4.160271e-01
+1.007708e+00
+-1.936004e-02
+2.207483e-01
+-6.654413e-01
+-4.479407e-01
+-1.364038e-01
+4.005023e-01
+-2.469230e-01
+-4.057847e-01
+-4.814272e-01
+-4.971257e-01
+-2.414501e-01
+-1.648537e-01
+8.348442e-02
+-1.048533e-01
+3.140404e-01
+3.173624e-01
+2.430033e-02
+-2.236744e-01
+-2.959903e-01
+-2.569196e-02
+5.527547e-01
+-2.281922e-01
+-2.492468e-01
+4.654103e-01
+7.154733e-01
+-5.553536e-01
+-6.881153e-01
+8.689605e-01
+6.762373e-01
+-1.865834e-01
+3.385609e-01
+1.006165e-01
+-1.133610e+00
+3.109142e-01
+1.346475e-01
+-2.651954e-01
+7.159829e-02
+-4.473595e-01
+-8.366994e-01
+4.135409e-01
+-6.879046e-01
+-2.671874e-01
+5.195351e-01
+-2.111901e-01
+5.907788e-01
+6.770766e-02
+-5.751893e-02
+''')
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Set('leftMargin', u'0.2cm')
+Set('bottomMargin', u'0.2cm')
+Add('axis', name='x', autoadd=False)
+To('x')
+Set('TickLabels/hide', True)
+To('..')
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+Set('TickLabels/hide', True)
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('scalePoints', u'size')
+Set('PlotLine/hide', True)
+To('..')
+Add('xy', name='xy2', autoadd=False)
+To('xy2')
+Set('xData', u'x2')
+Set('yData', u'y2')
+Set('marker', u'circle')
+Set('scalePoints', u'size2')
+Set('PlotLine/hide', True)
+Set('MarkerLine/hide', True)
+Set('MarkerFill/color', u'#5555ff')
+To('..')
+Add('function', name=u'horz', autoadd=False)
+To(u'horz')
+Set('function', u'0')
+Set('Line/color', u'lightgrey')
+To('..')
+Add('function', name=u'vert', autoadd=False)
+To(u'vert')
+Set('function', u'0')
+Set('variable', u'y')
+Set('Line/color', u'lightgrey')
+To('..')
+Add('ellipse', name='ellipse1', autoadd=False)
+To('ellipse1')
+Set('xPos', [0.5])
+Set('yPos', [0.5])
+Set('width', [0.8])
+Set('height', [0.8])
+Set('rotate', [0.0])
+Set('Border/color', u'grey')
+Set('Border/width', u'0.25pt')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/examples/ternary.vsz veusz-1.14/examples/ternary.vsz
--- veusz-1.10/examples/ternary.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/examples/ternary.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,435 @@
+# Veusz saved document (version 1.12.99)
+# Saved at 2011-08-14T11:00:24.842710
+
+ImportString('a1(numeric)','''
+3.785032e+01
+2.887586e+01
+3.090787e+01
+3.991348e+01
+2.194688e+01
+6.907754e+00
+3.731477e+01
+2.729718e+01
+4.962433e+01
+3.593354e+01
+2.980925e+01
+1.883289e+01
+2.864022e+01
+2.385115e+01
+1.916651e+01
+1.163294e+01
+3.201094e+01
+4.940456e+01
+2.211055e+01
+5.347282e+01
+3.966645e+01
+1.129016e+01
+2.900517e+01
+2.285825e+01
+1.434235e+01
+2.491757e+01
+4.324933e+01
+-5.615836e-01
+1.333683e+01
+1.169526e+01
+5.702774e+01
+-4.698968e+00
+2.472287e+01
+3.090273e+01
+2.121293e+01
+2.293004e+00
+3.181916e+01
+3.576081e+01
+2.548760e+01
+3.387097e+01
+1.090547e+01
+4.670771e+01
+3.679762e+01
+1.870816e+01
+4.207707e+01
+8.373339e+00
+1.622552e+01
+4.240925e+01
+1.243271e+01
+9.907227e+00
+''')
+ImportString('a2(numeric)','''
+3.927892e+01
+3.147607e+01
+3.585074e+01
+2.532616e+01
+2.748505e+01
+3.089898e+01
+3.265939e+01
+2.699355e+01
+2.724263e+01
+3.627490e+01
+3.543455e+01
+2.613919e+01
+2.955639e+01
+2.698287e+01
+3.423267e+01
+2.446838e+01
+3.444203e+01
+4.034653e+01
+2.890829e+01
+2.838100e+01
+2.620180e+01
+3.366264e+01
+2.950382e+01
+3.507761e+01
+2.963094e+01
+3.659981e+01
+2.953002e+01
+3.010142e+01
+2.482832e+01
+4.350083e+01
+2.834519e+01
+2.657412e+01
+3.719120e+01
+3.122923e+01
+3.240051e+01
+2.650656e+01
+3.104269e+01
+3.162769e+01
+3.344840e+01
+3.266331e+01
+2.874867e+01
+2.454526e+01
+3.835166e+01
+3.302628e+01
+3.290526e+01
+3.631759e+01
+3.109008e+01
+3.474628e+01
+3.992190e+01
+3.665463e+01
+''')
+ImportString('a3(numeric)','''
+2.974402e+01
+2.992560e+01
+2.879212e+01
+2.289509e+01
+3.151959e+01
+2.514551e+01
+2.878709e+01
+3.809324e+01
+2.142433e+01
+3.253007e+01
+3.495113e+01
+3.219936e+01
+3.676403e+01
+2.577995e+01
+2.996285e+01
+2.957746e+01
+2.770419e+01
+2.726770e+01
+3.137323e+01
+3.328274e+01
+2.981133e+01
+2.227267e+01
+3.234312e+01
+2.694048e+01
+2.859402e+01
+3.514191e+01
+3.444068e+01
+2.544261e+01
+2.895935e+01
+2.397857e+01
+2.591309e+01
+3.282064e+01
+2.463678e+01
+3.006554e+01
+3.302107e+01
+3.165531e+01
+3.265907e+01
+2.977074e+01
+2.642066e+01
+2.612206e+01
+2.921875e+01
+3.187261e+01
+2.956203e+01
+2.660354e+01
+3.243911e+01
+3.434163e+01
+3.642871e+01
+2.220277e+01
+3.788404e+01
+2.830237e+01
+''')
+ImportString('a4(numeric)','''
+6.023658e+01
+6.951139e+01
+6.130916e+01
+5.480901e+01
+5.694097e+01
+5.796104e+01
+5.963835e+01
+5.135586e+01
+6.062245e+01
+5.609685e+01
+5.564031e+01
+6.290610e+01
+5.559147e+01
+5.874261e+01
+6.085174e+01
+5.703215e+01
+5.848376e+01
+6.636230e+01
+6.031409e+01
+5.848922e+01
+6.486602e+01
+5.198642e+01
+5.658263e+01
+6.206651e+01
+6.050066e+01
+6.178179e+01
+5.837051e+01
+6.618575e+01
+6.036681e+01
+6.307698e+01
+5.658911e+01
+5.888594e+01
+6.263513e+01
+6.270467e+01
+6.305208e+01
+5.303999e+01
+4.733556e+01
+6.382467e+01
+5.859786e+01
+6.703914e+01
+5.636757e+01
+5.305304e+01
+6.576543e+01
+4.626336e+01
+5.763185e+01
+5.699938e+01
+6.279347e+01
+6.506666e+01
+5.402032e+01
+5.895224e+01
+''')
+ImportString('b1(numeric)','''
+6.650787e+00
+1.687328e+01
+1.010450e+01
+1.502892e+01
+8.124262e+00
+1.579514e+01
+2.195101e+01
+3.239890e+00
+1.600109e+01
+4.296486e+00
+2.970698e+00
+9.968865e+00
+1.679330e+01
+6.402929e+00
+1.273309e+01
+1.358886e+01
+1.144324e+01
+-1.422098e+00
+2.708607e+00
+1.120078e+01
+8.676094e+00
+9.079920e+00
+9.543885e+00
+1.468984e+01
+1.288232e+01
+8.349164e+00
+1.372535e+01
+5.718161e+00
+1.638648e+01
+7.551304e+00
+1.155270e+01
+1.067230e+01
+8.879579e+00
+1.171511e+01
+6.194712e+00
+1.445493e+01
+8.503971e+00
+1.397565e+01
+8.748016e+00
+1.188018e+01
+1.910491e+01
+3.901901e+00
+4.879108e+00
+1.863455e+01
+1.608425e+01
+1.645056e+01
+-5.258673e+00
+8.866189e+00
+3.559357e-01
+6.020759e+00
+''')
+ImportString('b2(numeric)','''
+2.738027e+01
+1.836565e+01
+1.566644e+01
+1.609443e+01
+2.403639e+01
+8.289908e+00
+3.651296e+01
+5.184957e+01
+2.844856e+00
+5.319719e+00
+1.482401e+01
+3.813406e+01
+8.649367e+00
+-1.039173e+01
+2.710390e+01
+2.282277e+01
+2.110841e+01
+2.748859e+01
+2.756449e+01
+2.152050e+01
+1.344263e+01
+2.089343e+01
+1.775140e+01
+1.806021e+01
+4.594484e+01
+9.522575e+00
+2.831632e+01
+2.687223e+01
+5.372986e+01
+3.397279e+01
+1.318980e+01
+2.891465e+00
+2.525563e+01
+2.537575e+01
+2.810802e+01
+3.799986e+01
+2.522245e+01
+3.272764e+01
+1.361898e+01
+3.343623e+01
+2.053961e+01
+3.908754e+01
+5.705844e+01
+2.483616e+01
+3.003679e+01
+9.125044e+00
+1.994561e+01
+2.138206e+01
+1.927962e+01
+1.461585e+01
+''')
+ImportString(u'label(text)',r'''
+u'Nougat'
+u'Chocolate'
+''')
+ImportString(u'lx(numeric)','''
+6.000000e+01
+4.500000e+01
+''')
+ImportString(u'ly(numeric)','''
+2.500000e+01
+5.000000e+01
+''')
+ImportString('sizes(numeric)','''
+3.703217e-01
+4.291845e-01
+2.342960e-01
+1.000000e-01
+7.013704e-01
+4.164477e-01
+1.065102e+00
+7.756953e-01
+9.482757e-01
+3.618510e-01
+7.314487e-01
+4.392592e-01
+2.367437e-01
+1.065933e-01
+6.584510e-01
+4.378377e-01
+1.532375e-01
+7.235843e-01
+3.666900e-01
+7.430943e-01
+4.697697e-01
+4.473618e-01
+3.818643e-01
+5.482129e-01
+3.484666e-01
+2.090011e-01
+2.950832e-01
+2.944881e-01
+4.601784e-01
+7.396155e-01
+4.672722e-01
+1.195206e-01
+2.994085e-01
+3.087374e-01
+5.446024e-01
+3.683946e-01
+4.009373e-01
+8.466384e-01
+2.609215e-01
+5.970062e-01
+4.180349e-01
+5.768238e-01
+2.538321e-01
+8.176581e-01
+7.842167e-01
+6.022510e-01
+3.519089e-01
+4.928984e-01
+8.822681e-01
+3.058641e-01
+''')
+Set('width', '15cm')
+Set('height', '13.9cm')
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('ternary', name='ternary1', autoadd=False)
+To('ternary1')
+Set('topMargin', '0.72cm')
+Set('bottomMargin', '1.28cm')
+Set('labelbottom', u'Earth')
+Set('labelleft', u'Air')
+Set('labelright', u'Fire')
+Set('Label/size', u'20pt')
+Set('Label/italic', True)
+Add('nonorthpoint', name='nonorthpoint4', autoadd=False)
+To('nonorthpoint4')
+Set('marker', u'star')
+Set('markerSize', u'5pt')
+Set('data1', u'lx')
+Set('data2', u'ly')
+Set('labels', u'label')
+Set('PlotLine/hide', True)
+Set('Label/size', u'18pt')
+To('..')
+Add('nonorthpoint', name='nonorthpoint1', autoadd=False)
+To('nonorthpoint1')
+Set('data1', u'a1')
+Set('data2', u'a2')
+Set('PlotLine/hide', True)
+Set('MarkerFill/color', u'#aaffff')
+To('..')
+Add('nonorthpoint', name='nonorthpoint2', autoadd=False)
+To('nonorthpoint2')
+Set('marker', u'diamond')
+Set('data1', u'a3')
+Set('data2', u'a4')
+Set('PlotLine/hide', True)
+Set('MarkerFill/color', u'red')
+To('..')
+Add('nonorthpoint', name='nonorthpoint3', autoadd=False)
+To('nonorthpoint3')
+Set('markerSize', u'5pt')
+Set('data1', u'b2')
+Set('data2', u'b1')
+Set('scalePoints', u'sizes')
+Set('PlotLine/hide', True)
+Set('MarkerFill/color', u'blue')
+To('..')
+Add('nonorthfunc', name='nonorthfunc1', autoadd=False)
+To('nonorthfunc1')
+Set('function', u'40')
+Set('PlotLine/color', u'#aa00ff')
+Set('PlotLine/width', u'1.5pt')
+Set('PlotLine/style', u'dotted')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/examples/tutorialdata.csv veusz-1.14/examples/tutorialdata.csv
--- veusz-1.10/examples/tutorialdata.csv 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/examples/tutorialdata.csv 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,7 @@
+"alpha","beta","gamma"
+1,2,4
+2,5,6
+3,6,5
+4,13,10
+5,9,6
+6,3,14
diff -Nru veusz-1.10/helpers/__init__.py veusz-1.14/helpers/__init__.py
--- veusz-1.10/helpers/__init__.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/helpers/__init__.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,7 +16,5 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: __init__.py 872 2008-12-29 12:51:59Z jeremysanders $
-
"""Helper compiled routines."""
diff -Nru veusz-1.10/helpers/src/isnan.h veusz-1.14/helpers/src/isnan.h
--- veusz-1.10/helpers/src/isnan.h 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/helpers/src/isnan.h 2011-11-22 20:23:31.000000000 +0000
@@ -22,6 +22,7 @@
* for the code itself.
*/
+#include
#include
/* You might try changing the above to if you have problems.
* Whether you use math.h or cmath, you may need to edit the .cpp file
@@ -30,7 +31,7 @@
#if defined(__isnan)
# define isNaN(_a) (__isnan(_a)) /* MacOSX/Darwin definition < 10.4 */
-#elif defined(WIN32) || defined(_isnan)
+#elif defined(WIN32) || defined(_isnan) || defined(_MSC_VER)
# define isNaN(_a) (_isnan(_a)) /* Win32 definition */
#elif defined(isnan) || defined(__FreeBSD__) || defined(__osf__)
# define isNaN(_a) (isnan(_a)) /* GNU definition */
@@ -45,6 +46,8 @@
#if defined(__isfinite)
# define isFinite(_a) (__isfinite(_a)) /* MacOSX/Darwin definition < 10.4 */
+#elif defined(WIN32) || defined(_finite) || defined(_MSC_VER)
+# define isFinite(_a) (_finite(_a)) /* Win32 definition */
#elif defined(__sgi)
# define isFinite(_a) (_isfinite(_a))
#elif defined(isfinite)
diff -Nru veusz-1.10/helpers/src/_nc_cntr.c veusz-1.14/helpers/src/_nc_cntr.c
--- veusz-1.10/helpers/src/_nc_cntr.c 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/helpers/src/_nc_cntr.c 2011-11-22 20:23:31.000000000 +0000
@@ -10,8 +10,6 @@
the entirely different framework of a Python type. It
was written by following the Python "Extending and Embedding"
tutorial.
-
- $Id: _nc_cntr.c 1280 2010-06-13 14:53:17Z jeremysanders $
*/
/*
diff -Nru veusz-1.10/helpers/src/paintelement.h veusz-1.14/helpers/src/paintelement.h
--- veusz-1.10/helpers/src/paintelement.h 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/helpers/src/paintelement.h 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,30 @@
+// Copyright (C) 2011 Jeremy S. Sanders
+// Email: Jeremy Sanders
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef PAINTELEMENT_H
+#define PAINTELEMENT_H
+
+class QPainter;
+
+class PaintElement {
+public:
+ virtual ~PaintElement() {};
+ virtual void paint(QPainter& painter) = 0;
+};
+
+#endif
diff -Nru veusz-1.10/helpers/src/polylineclip.cpp veusz-1.14/helpers/src/polylineclip.cpp
--- veusz-1.10/helpers/src/polylineclip.cpp 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/helpers/src/polylineclip.cpp 2011-11-22 20:23:31.000000000 +0000
@@ -138,7 +138,7 @@
// is difference between points very small?
inline bool smallDelta(const QPointF& pt1, const QPointF& pt2)
{
- return fabs(pt1.x() - pt2.x()) < 0.01 and
+ return fabs(pt1.x() - pt2.x()) < 0.01 &&
fabs(pt1.y()- pt2.y()) < 0.01;
}
}
@@ -154,6 +154,10 @@
const QPolygonF& poly,
bool autoexpand)
{
+ // exit if fewer than 2 points in polygon
+ if ( poly.size() < 2 )
+ return;
+
// if autoexpand, expand rectangle by line width
if ( autoexpand )
{
@@ -183,14 +187,14 @@
{
// add first line
pout << p1;
- if( not smallDelta(p1, p2) )
+ if( ! smallDelta(p1, p2) )
pout << p2;
}
else
{
if( p1 == pout.last() )
{
- if( not smallDelta(p1, p2) )
+ if( ! smallDelta(p1, p2) )
// extend polyline
pout << p2;
}
@@ -203,7 +207,7 @@
// start new line
pout.clear();
pout << p1;
- if( not smallDelta(p1, p2) )
+ if( ! smallDelta(p1, p2) )
pout << p2;
}
}
diff -Nru veusz-1.10/helpers/src/qtloops.cpp veusz-1.14/helpers/src/qtloops.cpp
--- veusz-1.10/helpers/src/qtloops.cpp 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/helpers/src/qtloops.cpp 2011-11-22 20:23:31.000000000 +0000
@@ -26,13 +26,15 @@
#include
#include
#include
+#include
+#include
namespace
{
// is difference between points very small?
inline bool smallDelta(const QPointF& pt1, const QPointF& pt2)
{
- return fabs(pt1.x() - pt2.x()) < 0.01 and
+ return fabs(pt1.x() - pt2.x()) < 0.01 &&
fabs(pt1.y()- pt2.y()) < 0.01;
}
@@ -69,7 +71,7 @@
if( row < d.dims[col] && row < d.dims[col+1] )
{
const QPointF pt(d.data[col][row], d.data[col+1][row]);
- if( not smallDelta(pt, lastpt) )
+ if( ! smallDelta(pt, lastpt) )
{
poly << pt;
lastpt = pt;
@@ -85,7 +87,9 @@
void plotPathsToPainter(QPainter& painter, QPainterPath& path,
const Numpy1DObj& x, const Numpy1DObj& y,
- const QRectF* clip)
+ const Numpy1DObj* scaling,
+ const QRectF* clip,
+ const QImage* colorimg)
{
QRectF cliprect( QPointF(-32767,-32767), QPointF(32767,32767) );
if( clip != 0 )
@@ -98,16 +102,43 @@
cliprect.adjust(pathbox.left(), pathbox.top(),
pathbox.bottom(), pathbox.right());
- const int size = min(x.dim, y.dim);
+ // keep track of duplicate points
QPointF lastpt(-1e6, -1e6);
+ // keep original transformation for restoration after each iteration
+ QTransform origtrans(painter.worldTransform());
+
+ // number of iterations
+ int size = min(x.dim, y.dim);
+
+ // if few color points, trim down number of paths
+ if( colorimg != 0 )
+ size = min(size, colorimg->width());
+ // too few scaling points
+ if( scaling != 0 )
+ size = min(size, scaling->dim);
+
+ // draw each path
for(int i = 0; i < size; ++i)
{
const QPointF pt(x(i), y(i));
- if( cliprect.contains(pt) and not smallDelta(lastpt, pt) )
+ if( cliprect.contains(pt) && ! smallDelta(lastpt, pt) )
{
painter.translate(pt);
+ if( scaling != 0 )
+ {
+ // scale point if requested
+ const qreal s = (*scaling)(i);
+ painter.scale(s, s);
+ }
+ if( colorimg != 0 )
+ {
+ // get color from pixel and create a new brush
+ QBrush b( QColor::fromRgba(colorimg->pixel(i, 0)) );
+ painter.setBrush(b);
+ }
+
painter.drawPath(path);
- painter.translate(-pt);
+ painter.setWorldTransform(origtrans);
lastpt = pt;
}
}
@@ -122,7 +153,7 @@
// if autoexpand, expand rectangle by line width
QRectF clipcopy;
- if ( clip != 0 and autoexpand )
+ if ( clip != 0 && autoexpand )
{
const qreal lw = painter.pen().widthF();
qreal x1, y1, x2, y2;
@@ -158,7 +189,7 @@
{
// if autoexpand, expand rectangle by line width
QRectF clipcopy(QPointF(-32767,-32767), QPointF(32767,32767));
- if ( clip != 0 and autoexpand )
+ if ( clip != 0 && autoexpand )
{
const qreal lw = painter.pen().widthF();
qreal x1, y1, x2, y2;
diff -Nru veusz-1.10/helpers/src/qtloops.h veusz-1.14/helpers/src/qtloops.h
--- veusz-1.10/helpers/src/qtloops.h 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/helpers/src/qtloops.h 2011-11-22 20:23:31.000000000 +0000
@@ -37,9 +37,16 @@
void addNumpyToPolygonF(QPolygonF& poly,
const Tuple2Ptrs& v);
+// plot paths to painter
+// x and y locations are given in x and y
+// if scaling is not 0, is an array to scale the data points by
+// if colorimg is not 0, is a Nx1 image containing color points for path fills
+// clip is a clipping rectangle if set
void plotPathsToPainter(QPainter& painter, QPainterPath& path,
const Numpy1DObj& x, const Numpy1DObj& y,
- const QRectF* clip = 0);
+ const Numpy1DObj* scaling = 0,
+ const QRectF* clip = 0,
+ const QImage* colorimg = 0);
void plotLinesToPainter(QPainter& painter,
const Numpy1DObj& x1, const Numpy1DObj& y1,
diff -Nru veusz-1.10/helpers/src/qtloops_helpers.h veusz-1.14/helpers/src/qtloops_helpers.h
--- veusz-1.10/helpers/src/qtloops_helpers.h 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/helpers/src/qtloops_helpers.h 2011-11-22 20:23:31.000000000 +0000
@@ -58,7 +58,7 @@
inline double operator()(const int x) const
{
- if( DEBUG and (x < 0 or x >= dim) )
+ if( DEBUG && (x < 0 || x >= dim) )
throw "Invalid index in array";
return data[x];
}
@@ -79,7 +79,7 @@
inline double operator()(const int x, const int y) const
{
- if( DEBUG and (x < 0 or x >= dims[0] or y < 0 or y >= dims[1]) )
+ if( DEBUG && (x < 0 || x >= dims[0] || y < 0 || y >= dims[1]) )
throw "Invalid index in array";
return data[x+y*dims[1]];
}
@@ -100,7 +100,7 @@
inline int operator()(const int x, const int y) const
{
- if( DEBUG and (x < 0 or x >= dims[0] or y < 0 or y >= dims[1]) )
+ if( DEBUG && (x < 0 || x >= dims[0] || y < 0 || y >= dims[1]) )
throw "Invalid index in array";
return data[x+y*dims[1]];
}
diff -Nru veusz-1.10/helpers/src/qtloops.sip veusz-1.14/helpers/src/qtloops.sip
--- veusz-1.10/helpers/src/qtloops.sip 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/helpers/src/qtloops.sip 2011-11-22 20:23:31.000000000 +0000
@@ -59,20 +59,34 @@
%End
void plotPathsToPainter(QPainter&, QPainterPath&, SIP_PYOBJECT, SIP_PYOBJECT,
- const QRectF* clip=0);
+ SIP_PYOBJECT,
+ const QRectF* clip=0,
+ const QImage* colorimg=0);
%MethodCode
- {
- try
- {
- Numpy1DObj x(a2);
- Numpy1DObj y(a3);
- plotPathsToPainter(*a0, *a1, x, y, a4);
- }
- catch( const char *msg )
- {
- sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg);
- }
- }
+{
+ Numpy1DObj* scaling = 0;
+
+ try
+ {
+ // x and y coordinates
+ Numpy1DObj x(a2);
+ Numpy1DObj y(a3);
+
+ // a4 is scaling or None
+ if (a4 != Py_None) {
+ scaling = new Numpy1DObj(a4);
+ }
+
+ plotPathsToPainter(*a0, *a1, x, y, scaling, a5, a6);
+ }
+ catch( const char *msg )
+ {
+ sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg);
+ }
+
+ if( scaling )
+ delete scaling;
+}
%End
void plotLinesToPainter(QPainter& painter,
@@ -169,3 +183,20 @@
QPolygonF bezier_fit_cubic_multi(const QPolygonF& data, double error,
unsigned max_beziers);
+
+class RecordPaintDevice : QPaintDevice
+ {
+%TypeHeaderCode
+#include
+%End
+
+public:
+ RecordPaintDevice(int width, int height, int dpix, int dpiy);
+ ~RecordPaintDevice();
+ void play(QPainter& painter);
+
+ QPaintEngine* paintEngine() const;
+
+ int metric(QPaintDevice::PaintDeviceMetric metric) const;
+ int drawItemCount() const;
+ };
diff -Nru veusz-1.10/helpers/src/recordpaintdevice.cpp veusz-1.14/helpers/src/recordpaintdevice.cpp
--- veusz-1.10/helpers/src/recordpaintdevice.cpp 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/helpers/src/recordpaintdevice.cpp 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,76 @@
+// Copyright (C) 2011 Jeremy S. Sanders
+// Email: Jeremy Sanders
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+/////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+#include "recordpaintdevice.h"
+#include "recordpaintengine.h"
+
+#define INCH_MM 25.4
+
+RecordPaintDevice::RecordPaintDevice(int width, int height,
+ int dpix, int dpiy)
+ :_width(width), _height(height), _dpix(dpix), _dpiy(dpiy),
+ _engine(new RecordPaintEngine)
+{
+}
+
+RecordPaintDevice::~RecordPaintDevice()
+{
+ delete _engine;
+ qDeleteAll(_elements);
+}
+
+QPaintEngine* RecordPaintDevice::paintEngine() const
+{
+ return _engine;
+}
+
+int RecordPaintDevice::metric(QPaintDevice::PaintDeviceMetric metric) const
+{
+ switch(metric) {
+ case QPaintDevice::PdmWidth:
+ return _width;
+ case QPaintDevice::PdmHeight:
+ return _height;
+ case QPaintDevice::PdmWidthMM:
+ return int(_width * INCH_MM / _dpix);
+ case QPaintDevice::PdmHeightMM:
+ return int(_height * INCH_MM / _dpiy);
+ case QPaintDevice::PdmNumColors:
+ return std::numeric_limits::max();
+ case QPaintDevice::PdmDepth:
+ return 24;
+ case QPaintDevice::PdmDpiX:
+ case QPaintDevice::PdmPhysicalDpiX:
+ return _dpix;
+ case QPaintDevice::PdmDpiY:
+ case QPaintDevice::PdmPhysicalDpiY:
+ return _dpiy;
+ default:
+ return -1;
+ }
+}
+
+void RecordPaintDevice::play(QPainter& painter)
+{
+ foreach(PaintElement* el, _elements)
+ {
+ el->paint(painter);
+ }
+}
diff -Nru veusz-1.10/helpers/src/recordpaintdevice.h veusz-1.14/helpers/src/recordpaintdevice.h
--- veusz-1.10/helpers/src/recordpaintdevice.h 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/helpers/src/recordpaintdevice.h 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,57 @@
+// Copyright (C) 2011 Jeremy S. Sanders
+// Email: Jeremy Sanders
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef RECORD_PAINT_DEVICE__H
+#define RECORD_PAINT_DEVICE__H
+
+#include
+#include
+#include "paintelement.h"
+#include "recordpaintengine.h"
+
+class RecordPaintDevice : public QPaintDevice
+{
+public:
+ RecordPaintDevice(int width, int height, int dpix, int dpiy);
+ ~RecordPaintDevice();
+ QPaintEngine* paintEngine() const;
+
+ // play back all
+ void play(QPainter& painter);
+
+ int metric(QPaintDevice::PaintDeviceMetric metric) const;
+
+ int drawItemCount() const { return _engine->drawItemCount(); }
+
+public:
+ friend class RecordPaintEngine;
+
+private:
+ // add an element to the list of maintained elements
+ void addElement(PaintElement* el)
+ {
+ _elements.push_back(el);
+ }
+
+private:
+ int _width, _height, _dpix, _dpiy;
+ RecordPaintEngine* _engine;
+ QVector _elements;
+};
+
+#endif
diff -Nru veusz-1.10/helpers/src/recordpaintengine.cpp veusz-1.14/helpers/src/recordpaintengine.cpp
--- veusz-1.10/helpers/src/recordpaintengine.cpp 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/helpers/src/recordpaintengine.cpp 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,601 @@
+// Copyright (C) 2011 Jeremy S. Sanders
+// Email: Jeremy Sanders
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+/////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "paintelement.h"
+#include "recordpaintengine.h"
+#include "recordpaintdevice.h"
+
+namespace {
+
+ //////////////////////////////////////////////////////////////
+ // Drawing Elements
+ // these are defined for each type of painting
+ // the QPaintEngine does
+
+ // draw an ellipse (QRect and QRectF)
+ template
+ class ellipseElement : public PaintElement {
+ public:
+ ellipseElement(const T &rect) : _ellipse(rect) {}
+
+ void paint(QPainter& painter)
+ {
+ painter.drawEllipse(_ellipse);
+ }
+
+ private:
+ T _ellipse;
+ };
+ typedef ellipseElement EllipseElement;
+ typedef ellipseElement EllipseFElement;
+
+ // draw QImage
+ class ImageElement : public PaintElement {
+ public:
+ ImageElement(const QRectF& rect, const QImage& image,
+ const QRectF& sr, Qt::ImageConversionFlags flags)
+ : _image(image), _rect(rect), _sr(sr), _flags(flags)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.drawImage(_rect, _image, _sr, _flags);
+ }
+
+ private:
+ QImage _image;
+ QRectF _rect, _sr;
+ Qt::ImageConversionFlags _flags;
+ };
+
+ // draw lines
+ // this is for painting QLine and QLineF
+ template
+ class lineElement : public PaintElement {
+ public:
+ lineElement(const T *lines, int linecount)
+ {
+ for(int i = 0; i < linecount; i++)
+ _lines << lines[i];
+ }
+
+ void paint(QPainter& painter)
+ {
+ painter.drawLines(_lines);
+ }
+
+ private:
+ QVector _lines;
+ };
+ // specific Line and LineF variants
+ typedef lineElement LineElement;
+ typedef lineElement LineFElement;
+
+ // draw QPainterPath
+ class PathElement : public PaintElement {
+ public:
+ PathElement(const QPainterPath& path)
+ : _path(path) {}
+
+ void paint(QPainter& painter)
+ {
+ painter.drawPath(_path);
+ }
+
+ private:
+ QPainterPath _path;
+ };
+
+ // draw Pixmap
+ class PixmapElement : public PaintElement {
+ public:
+ PixmapElement(const QRectF& r, const QPixmap& pm,
+ const QRectF& sr) :
+ _r(r), _pm(pm), _sr(sr) {}
+
+ void paint(QPainter& painter)
+ {
+ painter.drawPixmap(_r, _pm, _sr);
+ }
+
+ private:
+ QRectF _r;
+ QPixmap _pm;
+ QRectF _sr;
+ };
+
+ // draw points (QPoint and QPointF)
+ template
+ class pointElement : public PaintElement {
+ public:
+ pointElement(const T* points, int pointcount)
+ {
+ for(int i=0; i PointElement;
+ typedef pointElement PointFElement;
+
+ // for QPolygon and QPolygonF
+ template
+ class polyElement: public PaintElement {
+ public:
+ polyElement(const T* points, int pointcount,
+ QPaintEngine::PolygonDrawMode mode)
+ : _mode(mode)
+ {
+ for(int i=0; i PolygonElement;
+ typedef polyElement PolygonFElement;
+
+ // for QRect and QRectF
+ template
+ class rectElement : public PaintElement {
+ public:
+ rectElement(const T* rects, int rectcount)
+ {
+ for(int i=0; i _rects;
+ };
+ typedef rectElement RectElement;
+ typedef rectElement RectFElement;
+
+ // draw Text
+ class TextElement : public PaintElement {
+ public:
+ TextElement(const QPointF& pt, const QTextItem& txt)
+ : _pt(pt), _text(txt.text())
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.drawText(_pt, _text);
+ }
+
+ private:
+ QPointF _pt;
+ QString _text;
+ };
+
+ class TiledPixmapElement : public PaintElement {
+ public:
+ TiledPixmapElement(const QRectF& rect, const QPixmap& pixmap,
+ const QPointF& pt)
+ : _rect(rect), _pixmap(pixmap), _pt(pt)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.drawTiledPixmap(_rect, _pixmap, _pt);
+ }
+
+ private:
+ QRectF _rect;
+ QPixmap _pixmap;
+ QPointF _pt;
+ };
+
+ ///////////////////////////////////////////////////////////////////
+ // State paint elements
+
+ // these define and change the state of the painter
+
+ class BackgroundBrushElement : public PaintElement {
+ public:
+ BackgroundBrushElement(const QBrush& brush)
+ : _brush(brush)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setBackground(_brush);
+ }
+
+ private:
+ QBrush _brush;
+ };
+
+ class BackgroundModeElement : public PaintElement {
+ public:
+ BackgroundModeElement(Qt::BGMode mode)
+ : _mode(mode)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setBackgroundMode(_mode);
+ }
+
+ private:
+ Qt::BGMode _mode;
+ };
+
+ class BrushElement : public PaintElement {
+ public:
+ BrushElement(const QBrush& brush)
+ : _brush(brush)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setBrush(_brush);
+ }
+
+ private:
+ QBrush _brush;
+ };
+
+ class BrushOriginElement : public PaintElement {
+ public:
+ BrushOriginElement(const QPointF& origin)
+ : _origin(origin)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setBrushOrigin(_origin);
+ }
+
+ private:
+ QPointF _origin;
+ };
+
+ class ClipRegionElement : public PaintElement {
+ public:
+ ClipRegionElement(Qt::ClipOperation op,
+ const QRegion& region)
+ : _op(op), _region(region)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setClipRegion(_region, _op);
+ }
+
+ private:
+ Qt::ClipOperation _op;
+ QRegion _region;
+ };
+
+ class ClipPathElement : public PaintElement {
+ public:
+ ClipPathElement(Qt::ClipOperation op,
+ const QPainterPath& region)
+ : _op(op), _region(region)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setClipPath(_region, _op);
+ }
+
+ private:
+ Qt::ClipOperation _op;
+ QPainterPath _region;
+ };
+
+ class CompositionElement : public PaintElement {
+ public:
+ CompositionElement(QPainter::CompositionMode mode)
+ : _mode(mode)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setCompositionMode(_mode);
+ }
+
+ private:
+ QPainter::CompositionMode _mode;
+ };
+
+ class FontElement : public PaintElement {
+ public:
+ FontElement(const QFont& font)
+ : _font(font)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setFont(_font);
+ }
+
+ private:
+ QFont _font;
+ };
+
+ class TransformElement : public PaintElement {
+ public:
+ TransformElement(const QTransform& t)
+ : _t(t)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setWorldTransform(_t);
+ }
+
+ private:
+ QTransform _t;
+ };
+
+ class ClipEnabledElement : public PaintElement {
+ public:
+ ClipEnabledElement(bool enabled)
+ : _enabled(enabled)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setClipping(_enabled);
+ }
+
+ private:
+ bool _enabled;
+ };
+
+ class PenElement : public PaintElement {
+ public:
+ PenElement(const QPen& pen)
+ : _pen(pen)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setPen(_pen);
+ }
+
+ private:
+ QPen _pen;
+ };
+
+ class HintsElement : public PaintElement {
+ public:
+ HintsElement(QPainter::RenderHints hints)
+ : _hints(hints)
+ {}
+
+ void paint(QPainter& painter)
+ {
+ painter.setRenderHints(_hints);
+ }
+
+ private:
+ QPainter::RenderHints _hints;
+ };
+
+
+ // end anonymous block
+}
+
+///////////////////////////////////////////////////////////////////
+// Paint engine follows
+
+RecordPaintEngine::RecordPaintEngine()
+ : QPaintEngine(QPaintEngine::AllFeatures),
+ _drawitemcount(0),
+ _pdev(0)
+{
+}
+
+bool RecordPaintEngine::begin(QPaintDevice* pdev)
+{
+ // old style C cast - probably should use dynamic_cast
+ _pdev = (RecordPaintDevice*)(pdev);
+
+ // signal started ok
+ return 1;
+}
+
+// for each type of drawing command we add a new element
+// to the list maintained by the device
+
+void RecordPaintEngine::drawEllipse(const QRectF& rect)
+{
+ _pdev->addElement( new EllipseFElement(rect) );
+ _drawitemcount++;
+}
+
+void RecordPaintEngine::drawEllipse(const QRect& rect)
+{
+ _pdev->addElement( new EllipseElement(rect) );
+ _drawitemcount++;
+}
+
+void RecordPaintEngine::drawImage(const QRectF& rectangle,
+ const QImage& image,
+ const QRectF& sr,
+ Qt::ImageConversionFlags flags)
+{
+ _pdev->addElement( new ImageElement(rectangle, image, sr, flags) );
+ _drawitemcount++;
+}
+
+void RecordPaintEngine::drawLines(const QLineF* lines, int lineCount)
+{
+ _pdev->addElement( new LineFElement(lines, lineCount) );
+ _drawitemcount += lineCount;
+}
+
+void RecordPaintEngine::drawLines(const QLine* lines, int lineCount)
+{
+ _pdev->addElement( new LineElement(lines, lineCount) );
+ _drawitemcount += lineCount;
+}
+
+void RecordPaintEngine::drawPath(const QPainterPath& path)
+{
+ _pdev->addElement( new PathElement(path) );
+ _drawitemcount++;
+}
+
+void RecordPaintEngine::drawPixmap(const QRectF& r,
+ const QPixmap& pm, const QRectF& sr)
+{
+ _pdev->addElement( new PixmapElement(r, pm, sr) );
+ _drawitemcount++;
+}
+
+void RecordPaintEngine::drawPoints(const QPointF* points, int pointCount)
+{
+ _pdev->addElement( new PointFElement(points, pointCount) );
+ _drawitemcount += pointCount;
+}
+
+void RecordPaintEngine::drawPoints(const QPoint* points, int pointCount)
+{
+ _pdev->addElement( new PointElement(points, pointCount) );
+ _drawitemcount += pointCount;
+}
+
+void RecordPaintEngine::drawPolygon(const QPointF* points, int pointCount,
+ QPaintEngine::PolygonDrawMode mode)
+{
+ _pdev->addElement( new PolygonFElement(points, pointCount, mode) );
+ _drawitemcount += pointCount;
+}
+
+void RecordPaintEngine::drawPolygon(const QPoint* points, int pointCount,
+ QPaintEngine::PolygonDrawMode mode)
+{
+ _pdev->addElement( new PolygonElement(points, pointCount, mode) );
+ _drawitemcount += pointCount;
+}
+
+void RecordPaintEngine::drawRects(const QRectF* rects, int rectCount)
+{
+ _pdev->addElement( new RectFElement( rects, rectCount ) );
+ _drawitemcount += rectCount;
+}
+
+void RecordPaintEngine::drawRects(const QRect* rects, int rectCount)
+{
+ _pdev->addElement( new RectElement( rects, rectCount ) );
+ _drawitemcount += rectCount;
+}
+
+void RecordPaintEngine::drawTextItem(const QPointF& p,
+ const QTextItem& textItem)
+{
+ _pdev->addElement( new TextElement(p, textItem) );
+ _drawitemcount += textItem.text().length();
+}
+
+void RecordPaintEngine::drawTiledPixmap(const QRectF& rect,
+ const QPixmap& pixmap,
+ const QPointF& p)
+{
+ _pdev->addElement( new TiledPixmapElement(rect, pixmap, p) );
+ _drawitemcount += 1;
+}
+
+bool RecordPaintEngine::end()
+{
+ // signal finished ok
+ return 1;
+}
+
+QPaintEngine::Type RecordPaintEngine::type () const
+{
+ // some sort of random number for the ID of the engine type
+ return QPaintEngine::Type(int(QPaintEngine::User)+34);
+}
+
+void RecordPaintEngine::updateState(const QPaintEngineState& state)
+{
+ // we add a new element for each change of state
+ // these are replayed later
+ const int flags = state.state();
+ if( flags & QPaintEngine::DirtyBackground )
+ _pdev->addElement( new BackgroundBrushElement( state.backgroundBrush() ) );
+ if( flags & QPaintEngine::DirtyBackgroundMode )
+ _pdev->addElement( new BackgroundModeElement( state.backgroundMode() ) );
+ if( flags & QPaintEngine::DirtyBrush )
+ _pdev->addElement( new BrushElement( state.brush() ) );
+ if( flags & QPaintEngine::DirtyBrushOrigin )
+ _pdev->addElement( new BrushOriginElement( state.brushOrigin() ) );
+ if( flags & QPaintEngine::DirtyClipRegion )
+ _pdev->addElement( new ClipRegionElement( state.clipOperation(),
+ state.clipRegion() ) );
+ if( flags & QPaintEngine::DirtyClipPath )
+ _pdev->addElement( new ClipPathElement( state.clipOperation(),
+ state.clipPath() ) );
+ if( flags & QPaintEngine::DirtyCompositionMode )
+ _pdev->addElement( new CompositionElement( state.compositionMode() ) );
+ if( flags & QPaintEngine::DirtyFont )
+ _pdev->addElement( new FontElement( state.font() ) );
+ if( flags & QPaintEngine::DirtyTransform )
+ _pdev->addElement( new TransformElement( state.transform() ) );
+ if( flags & QPaintEngine::DirtyClipEnabled )
+ _pdev->addElement( new ClipEnabledElement( state.isClipEnabled() ) );
+ if( flags & QPaintEngine::DirtyPen )
+ _pdev->addElement( new PenElement( state.pen() ) );
+ if( flags & QPaintEngine::DirtyHints )
+ _pdev->addElement( new HintsElement( state.renderHints() ) );
+}
diff -Nru veusz-1.10/helpers/src/recordpaintengine.h veusz-1.14/helpers/src/recordpaintengine.h
--- veusz-1.10/helpers/src/recordpaintengine.h 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/helpers/src/recordpaintengine.h 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,75 @@
+// Copyright (C) 2011 Jeremy S. Sanders
+// Email: Jeremy Sanders
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef RECORD_PAINT_ENGINE__H
+#define RECORD_PAINT_ENGINE__H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class RecordPaintDevice;
+
+class RecordPaintEngine : public QPaintEngine
+{
+public:
+ RecordPaintEngine();
+
+ // standard methods to be overridden in engine
+ bool begin(QPaintDevice* pdev);
+
+ void drawEllipse(const QRectF& rect);
+ void drawEllipse(const QRect& rect);
+ void drawImage(const QRectF& rectangle, const QImage& image,
+ const QRectF& sr,
+ Qt::ImageConversionFlags flags = Qt::AutoColor);
+ void drawLines(const QLineF* lines, int lineCount);
+ void drawLines(const QLine* lines, int lineCount);
+ void drawPath(const QPainterPath& path);
+ void drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr);
+ void drawPoints(const QPointF* points, int pointCount);
+ void drawPoints(const QPoint* points, int pointCount);
+ void drawPolygon(const QPointF* points, int pointCount,
+ QPaintEngine::PolygonDrawMode mode);
+ void drawPolygon(const QPoint* points, int pointCount,
+ QPaintEngine::PolygonDrawMode mode);
+ void drawRects(const QRectF* rects, int rectCount);
+ void drawRects(const QRect* rects, int rectCount);
+ void drawTextItem(const QPointF& p, const QTextItem& textItem);
+ void drawTiledPixmap(const QRectF& rect, const QPixmap& pixmap,
+ const QPointF& p);
+ bool end ();
+ QPaintEngine::Type type () const;
+ void updateState(const QPaintEngineState& state);
+
+ // return an estimate of number of items drawn
+ int drawItemCount() const { return _drawitemcount; }
+
+private:
+ int _drawitemcount;
+ RecordPaintDevice* _pdev;
+};
+
+#endif
diff -Nru veusz-1.10/__init__.py veusz-1.14/__init__.py
--- veusz-1.10/__init__.py 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/__init__.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,6 +16,4 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: __init__.py 872 2008-12-29 12:51:59Z jeremysanders $
-
"""Main veusz module."""
diff -Nru veusz-1.10/INSTALL veusz-1.14/INSTALL
--- veusz-1.10/INSTALL 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/INSTALL 2011-11-22 20:23:31.000000000 +0000
@@ -1,31 +1,70 @@
Veusz Installation
==================
-Veusz uses distutils for its installation. See below for how to use
-it.
+1. INSTALLING FROM SOURCE
+*************************
+
+Veusz uses distutils for its installation. See below for how to use it.
Requirements:
python >= 2.4 http://www.python.org/
PyQt >= 4.3 http://www.riverbankcomputing.co.uk/pyqt/
numpy >= 1.0 http://numpy.scipy.org/
-(PyQt requires
+PyQt requires
Qt4 http://www.trolltech.com/products/qt/ (free version)
- version >= 4.3 recommended
- SIP http://www.riverbankcomputing.co.uk/sip/ )
+ version >= 4.4 recommended
+ SIP http://www.riverbankcomputing.co.uk/sip/
Optional requirements:
PyFITS>=1.1 http://www.stsci.edu/resources/software_hardware/pyfits
pyemf >= 2.0.0 http://pyemf.sourceforge.net/
Corefonts http://corefonts.sourceforge.net/
+ PyMinuit http://code.google.com/p/pyminuit/
+
+1.1 Full installation with distutils
+====================================
+There are a number of ways to install programs using distutils. I will
+list a few of the possible method here:
+
+To install on linux to the standard location on the hard disk
+
+# cd veusz-1.14
+# python setup.py build
+# su
+[enter root password]
+# python setup.py install
+# exit
+
+If you do not have a root account (as is default on Ubuntu), do
+# sudo python setup.py install
+instead of the final three lines
+
+On Windows, it should just be a matter of running the python setup.py
+build and install steps with the requirements installed.
+
+1.2 Testing
+===========
+After veusz has been installed into the Python path (in the standard
+location or in PYTHONPATH), you can run the runselftest.py executable
+in the tests directory. This will compare the generated output of
+example documents with the expected output. The return code of the
+runselftest.py script is the number of tests that have failed (0 for
+success).
+
+On Unix/Linux, Qt requires the DISPLAY environment to be set to an X11
+server for the self test to run. In a non graphical environment Xvfb
+can be used to create a hidden X11 server:
+# xvfb-run -a --server-args "-screen 0 640x480x24" \
+ python tests/runselftest.py
-Simple source use (if requirements installed)
-=============================================
+1.3 Simple source use (if requirements installed)
+=================================================
If you don't want to bother installing veusz fully, it can be run from
its own directory (at the moment). Simply do:
-# tar xzf veusz-1.9.tar.gz [change version here]
-# cd veusz-1.9
+# tar xzf veusz-1.14.tar.gz [change version here]
+# cd veusz-1.14
# ./veusz_main.py
Certain features will be disabled if you do this. You will not be able
@@ -37,55 +76,28 @@
# python setup.py build
# cp build/*/veusz/helpers/*.so helpers/
-Full installation with distutils
-================================
-There are a number of ways to install programs using distutils. I will
-list a few of the possible method here:
+2. BINARY INSTALL
+*****************
-To install to the standard location on the hard disk (it's better to use
-rpms if you have an rpm-based linux distibution)
-------------------------------------------------------------------------
-# cd veusz-1.9
-# python setup.py build
-# su
-[enter root password]
-# python setup.py install
-# exit
-
-Linux binary
-============
-If you do not have the requirements, you can use the Linux binary
-instead (if available). Note that this may not work on all
-distributions due to glibc/library incompatibilities. You need to
-simply unpack the tar file and run the main executable:
+2.1 Linux binary
+================
+If your distribution does not include an up to date package, you can
+use the Linux binary instead (for i386/x86_64). Note that this may not
+work on all distributions due to glibc/library
+incompatibilities. Simply unpack the tar file and run the main
+executable:
-# tar xzf veusz-linux-i386-1.9.tar.gz [change version here]
-# cd veusz-linux-i386-1.9
+# tar xzf veusz-linux-i386-1.14.tar.gz [change version here]
+# cd veusz-linux-i386-1.14
# ./veusz
-Installing in Windows
-=====================
-Simply run the setup.exe binary installer. This does not provide the
-embedding interface, however.
-
-Installing on Mac OS X
-======================
-A binary is available for Mac OS X. It does not provide the embedding
-interface. Simply drag the Veusz application into your Applications
-directory.
-
-Veusz can also be installed from source on Mac OS X. The requirements
-can be obtained using a system such as MacPorts. You can install them
-with MacPorts using:
-
-$ sudo port install qt4-mac
-$ sudo port install py-numpy
-
-Once these have successfully built and installed, you can unpack veusz
-and install as above.
-
-Qt is available from TrollTech as a binary, but SIP and PyQt are not
-available as a binary.
-
--------------------------------------------------------------------------------
-$Id: INSTALL 1375 2010-08-22 19:14:45Z jeremysanders $
+2.2 Installing in Windows
+=========================
+Simply run the setup.exe binary installer. Add the location of the
+embed.py file to your PYTHONPATH if you want to use the embedding
+module.
+
+2.3 Installing on Mac OS X
+==========================
+A binary is available for Mac OS X. Simply drag the Veusz application
+into your Applications directory.
diff -Nru veusz-1.10/MANIFEST.in veusz-1.14/MANIFEST.in
--- veusz-1.10/MANIFEST.in 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/MANIFEST.in 2011-11-22 20:32:03.000000000 +0000
@@ -1,10 +1,9 @@
include VERSION AUTHORS ChangeLog COPYING INSTALL README
include MANIFEST.in setup.py setup.cfg
include scripts/veusz scripts/veusz_listen
-recursive-include tests *.py *.sh *.vsz
+recursive-include tests *.py *.sh *.vsz *.selftest *.csv *.dat *.npy *.npz
recursive-include Documents *.xml *.sh *.png *.txt *.pdf *.html *.xsl *.py *.pod *.1
recursive-include windows *.png *.ico *.svg README
recursive-include dialogs *.ui
recursive-include examples *.vsz *.py *.csv *.dat
-recursive-include widgets *.dat
recursive-include helpers *.c *.cpp *.h README LICENSE_*
diff -Nru veusz-1.10/PKG-INFO veusz-1.14/PKG-INFO
--- veusz-1.10/PKG-INFO 2010-12-12 12:41:59.000000000 +0000
+++ veusz-1.14/PKG-INFO 2011-11-22 20:32:14.000000000 +0000
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: veusz
-Version: 1.10
+Version: 1.14
Summary: A scientific plotting package
Home-page: http://home.gna.org/veusz/
Author: Jeremy Sanders
diff -Nru veusz-1.10/plugins/datasetplugin.py veusz-1.14/plugins/datasetplugin.py
--- veusz-1.10/plugins/datasetplugin.py 2010-12-12 12:41:06.000000000 +0000
+++ veusz-1.14/plugins/datasetplugin.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,14 +18,14 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: datasetplugin.py 1373 2010-08-22 18:43:32Z jeremysanders $
-
"""Plugins for creating datasets."""
import numpy as N
from itertools import izip
import field
+import veusz.utils as utils
+
# add an instance of your class to this list to be registered
datasetpluginregistry = []
@@ -104,6 +104,44 @@
import veusz.document as document
return document.Dataset2DPlugin(manager, self)
+class DatasetDateTime(object):
+ """Date-time dataset for ImportPlugin or DatasetPlugin."""
+
+ def __init__(self, name, data=[]):
+ """A date dataset
+ name: name of dataset
+ data: list of datetime objects
+ """
+ self.name = name
+ self.update(data=data)
+
+ def update(self, data=[]):
+ self.data = N.array(data)
+
+ @staticmethod
+ def datetimeToFloat(datetimeval):
+ """Return a python datetime object to the required float type."""
+ return utils.datetimeToFloat(datetimeval)
+
+ @staticmethod
+ def dateStringToFloat(text):
+ """Try to convert an iso or local date time to the float type."""
+ return utils.dateStringToDate(text)
+
+ @staticmethod
+ def floatToDateTime(val):
+ """Convert float format datetime to Python datetime."""
+ return utils.floatToDateTime(val)
+
+ def _null(self):
+ """Empty data contents."""
+ self.data = N.array([])
+
+ def _makeVeuszDataset(self, manager):
+ """Make a Veusz dataset from the plugin dataset."""
+ import veusz.document as document
+ return document.DatasetDatePlugin(manager, self)
+
class DatasetText(object):
"""Text dataset for ImportPlugin or DatasetPlugin."""
def __init__(self, name, data=[]):
@@ -126,6 +164,26 @@
import veusz.document as document
return document.DatasetTextPlugin(manager, self)
+class Constant(object):
+ """Dataset to return to set a Veusz constant after import.
+ This is only useful in an ImportPlugin, not a DatasetPlugin
+ """
+ def __init__(self, name, val):
+ """Map string value val to name.
+ Convert float vals to strings first!"""
+ self.name = name
+ self.val = val
+
+class Function(object):
+ """Dataset to return to set a Veusz function after import."""
+ def __init__(self, name, val):
+ """Map string value val to name.
+ name is "funcname(param,...)", val is a text expression of param.
+ This is only useful in an ImportPlugin, not a DatasetPlugin
+ """
+ self.name = name
+ self.val = val
+
# class to pass to plugin to give parameters
class DatasetPluginHelper(object):
"""Helpers to get existing datasets for plugins."""
@@ -152,6 +210,13 @@
return [name for name, ds in self._doc.data.iteritems() if
(ds.dimensions == 1 and ds.datatype == 'text')]
+ @property
+ def datasetsdatetime(self):
+ """Return list of existing date-time datesets"""
+ import veusz.document as document
+ return [name for name, ds in self._doc.data.iteritems() if
+ isinstance(ds, document.DatasetDateTime)]
+
def evaluateExpression(self, expr, part='data'):
"""Return results of evaluating a 1D dataset expression.
part is 'data', 'serr', 'perr' or 'nerr' - these are the
@@ -169,6 +234,7 @@
name not found: raise a DatasetPluginException
dimensions not right: raise a DatasetPluginException
"""
+ import veusz.document as document
try:
ds = self._doc.data[name]
except KeyError:
@@ -181,7 +247,9 @@
raise DatasetPluginException(
"Dataset '%s' is not a numerical dataset" % name)
- if ds.dimensions == 1:
+ if isinstance(ds, document.DatasetDateTime):
+ return DatasetDateTime(name, data=ds.data)
+ elif ds.dimensions == 1:
return Dataset1D(name, data=ds.data, serr=ds.serr,
perr=ds.perr, nerr=ds.nerr)
elif ds.dimensions == 2:
diff -Nru veusz-1.10/plugins/field.py veusz-1.14/plugins/field.py
--- veusz-1.10/plugins/field.py 2010-12-12 12:41:06.000000000 +0000
+++ veusz-1.14/plugins/field.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: field.py 1433 2010-11-04 19:12:00Z jeremysanders $
-
"""Data entry fields for plugins."""
import veusz.qtall as qt4
diff -Nru veusz-1.10/plugins/importplugin.py veusz-1.14/plugins/importplugin.py
--- veusz-1.10/plugins/importplugin.py 2010-12-12 12:41:06.000000000 +0000
+++ veusz-1.14/plugins/importplugin.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,24 +16,13 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: importplugin.py 1422 2010-09-28 09:15:27Z jeremysanders $
-
"""Import plugin base class and helpers."""
import numpy as N
import veusz.utils as utils
-from field import Field as ImportField
-from field import FieldBool as ImportFieldCheck
-from field import FieldText as ImportFieldText
-from field import FieldFloat as ImportFieldFloat
-from field import FieldInt as ImportFieldInt
-from field import FieldCombo as ImportFieldCombo
import field
-
-from datasetplugin import Dataset1D as ImportDataset1D
-from datasetplugin import Dataset2D as ImportDataset2D
-from datasetplugin import DatasetText as ImportDatasetText
+import datasetplugin
# add an instance of your class to this list to get it registered
importpluginregistry = []
@@ -55,17 +44,26 @@
class ImportPlugin(object):
"""Define a plugin to read data in a particular format.
- override doImport and optionally getPreview to define a new plugin
- register the class by adding to the importpluginregistry list
+ Override doImport and optionally getPreview to define a new plugin.
+ Register the class by adding it to the importpluginregistry list.
+ Of promote_tab is set to some text, put the plugin on its own tab
+ in the import dialog using that text as the tab name.
"""
name = 'Import plugin'
author = ''
description = ''
+ # if set to some text, use this plugin on its own tab
+ promote_tab = None
+
+ # set these to get focus if a file is selected with these extensions
+ # include the dot in the extension names
+ file_extensions = set()
+
def __init__(self):
"""Override this to declare a list of input fields if required."""
- # a list of ImportField objects to display
+ # a list of Field objects to display
self.fields = []
def getPreview(self, params):
@@ -79,7 +77,7 @@
def doImport(self, params):
"""Actually import data
params is a ImportPluginParams object.
- Return a list of ImportDataset1D, ImportDataset2D objects
+ Return a list of datasetplugin.Dataset1D, datasetplugin.Dataset2D objects
"""
return []
@@ -95,32 +93,64 @@
def __init__(self):
self.fields = [
- ImportFieldText("name", descr="Dataset name", default="name"),
- ImportFieldCheck("invert", descr="invert values"),
- ImportFieldFloat("mult", descr="Multiplication factor", default=1),
- ImportFieldInt("skip", descr="Skip N lines",
+ field.FieldText("name", descr="Dataset name", default="name"),
+ field.FieldBool("invert", descr="invert values"),
+ field.FieldFloat("mult", descr="Multiplication factor", default=1),
+ field.FieldInt("skip", descr="Skip N lines",
default=0, minval=0),
- ImportFieldCombo("subtract", items=("0", "1", "2"),
+ field.FieldCombo("subtract", items=("0", "1", "2"),
editable=False, default="0")
]
def doImport(self, params):
"""Actually import data
params is a ImportPluginParams object.
- Return a list of ImportDataset1D, ImportDataset2D objects
+ Return a list of datasetplugin.Dataset1D, datasetplugin.Dataset2D objects
+ """
+ try:
+ f = params.openFileWithEncoding()
+ data = []
+ mult = params.field_results["mult"]
+ sub = float(params.field_results["subtract"])
+ if params.field_results["invert"]:
+ mult *= -1
+ for i in xrange(params.field_results["skip"]):
+ f.readline()
+ for line in f:
+ data += [float(x)*mult-sub for x in line.split()]
+
+ return [datasetplugin.Dataset1D(params.field_results["name"], data),
+ datasetplugin.Constant("testconst", "42"),
+ datasetplugin.Function("testfunc(x)", "testconst*x**2")]
+ except Exception, e:
+ raise ImportPluginException(unicode(e))
+
+class ImportPluginDateTime(ImportPlugin):
+ """An example plugin for reading a set of iso date-times from a
+ file."""
+
+ name = "Example plugin for date/times"
+ author = "Jeremy Sanders"
+ description = "Reads a list of ISO date times in a text file"
+
+ def __init__(self):
+ self.fields = [
+ field.FieldText("name", descr="Dataset name", default="name"),
+ ]
+
+ def doImport(self, params):
+ """Actually import data
+ params is a ImportPluginParams object.
+ Return a list of datasetplugin.Dataset1D, datasetplugin.Dataset2D objects
"""
f = params.openFileWithEncoding()
data = []
- mult = params.field_results["mult"]
- sub = float(params.field_results["subtract"])
- if params.field_results["invert"]:
- mult *= -1
- for i in xrange(params.field_results["skip"]):
- f.readline()
for line in f:
- data += [float(x)*mult-sub for x in line.split()]
-
- return [ImportDataset1D(params.field_results["name"], data)]
+ data.append( datasetplugin.DatasetDateTime.
+ dateStringToFloat(line.strip()) )
+ return [ datasetplugin.DatasetDateTime(params.field_results["name"],
+ data) ]
+#importpluginregistry.append( ImportPluginDateTime )
class QdpFile(object):
"""Handle reading of a Qdp file."""
@@ -202,16 +232,16 @@
a = N.array(self.data[i])
if len(a.shape) == 1:
# no error bars
- ds = ImportDataset1D(name, data=a)
+ ds = datasetplugin.Dataset1D(name, data=a)
elif a.shape[1] == 2:
# serr
- ds = ImportDataset1D(name, data=a[:,0], serr=a[:,1])
+ ds = datasetplugin.Dataset1D(name, data=a[:,0], serr=a[:,1])
elif a.shape[1] == 3:
# perr/nerr
p = N.where(a[:,1] < a[:,2], a[:,2], a[:,1])
n = N.where(a[:,1] < a[:,2], a[:,1], a[:,2])
- ds = ImportDataset1D(name, data=a[:,0], perr=p, nerr=n)
+ ds = datasetplugin.Dataset1D(name, data=a[:,0], perr=p, nerr=n)
else:
raise RuntimeError
@@ -296,6 +326,7 @@
name = "QDP import"
author = "Jeremy Sanders"
description = "Reads datasets from QDP files"
+ file_extensions = set(['.qdp'])
def __init__(self):
self.fields = [
@@ -306,7 +337,7 @@
def doImport(self, params):
"""Actually import data
params is a ImportPluginParams object.
- Return a list of ImportDataset1D, ImportDataset2D objects
+ Return a list of datasetplugin.Dataset1D, datasetplugin.Dataset2D objects
"""
names = [x.strip() for x in params.field_results["names"]
if x.strip()]
@@ -318,7 +349,237 @@
return rqdp.retndata
+def cnvtImportNumpyArray(name, val, errorsin2d=True):
+ """Convert a numpy array to plugin returns."""
+
+ try:
+ val.shape
+ except AttributeError:
+ raise ImportPluginException("Not the correct format file")
+ try:
+ val + 0.
+ val = val.astype(N.float64)
+ except TypeError:
+ raise ImportPluginException("Unsupported array type")
+
+ if val.ndim == 1:
+ return datasetplugin.Dataset1D(name, val)
+ elif val.ndim == 2:
+ if errorsin2d and val.shape[1] in (2, 3):
+ # return 1d array
+ if val.shape[1] == 2:
+ # use as symmetric errors
+ return datasetplugin.Dataset1D(name, val[:,0], serr=val[:,1])
+ else:
+ # asymmetric errors
+ # unclear on ordering here...
+ return datasetplugin.Dataset1D(name, val[:,0], perr=val[:,1],
+ nerr=val[:,2])
+ else:
+ return datasetplugin.Dataset2D(name, val)
+ else:
+ raise ImportPluginException("Unsupported dataset shape")
+
+class ImportPluginNpy(ImportPlugin):
+ """For reading single datasets from NPY numpy saved files."""
+
+ name = "Numpy NPY import"
+ author = "Jeremy Sanders"
+ description = "Reads a 1D/2D numeric dataset from a Numpy NPY file"
+ file_extensions = set(['.npy'])
+
+ def __init__(self):
+ self.fields = [
+ field.FieldText("name", descr="Dataset name",
+ default=''),
+ field.FieldBool("errorsin2d",
+ descr="Treat 2 and 3 column 2D arrays as\n"
+ "data with error bars",
+ default=True),
+ ]
+
+ def getPreview(self, params):
+ """Get data to show in a text box to show a preview.
+ params is a ImportPluginParams object.
+ Returns (text, okaytoimport)
+ """
+ try:
+ retn = N.load(params.filename)
+ except Exception, e:
+ return "Cannot read file", False
+
+ try:
+ text = 'Array shape: %s\n' % str(retn.shape)
+ text += 'Array datatype: %s (%s)\n' % (retn.dtype.str,
+ str(retn.dtype))
+ text += str(retn)
+ return text, True
+ except AttributeError:
+ return "Not an NPY file", False
+
+ def doImport(self, params):
+ """Actually import data.
+ """
+
+ name = params.field_results["name"].strip()
+ if not name:
+ raise ImportPluginException("Please provide a name for the dataset")
+
+ try:
+ retn = N.load(params.filename)
+ except Exception, e:
+ raise ImportPluginException("Error while reading file: %s" %
+ unicode(e))
+
+ return [ cnvtImportNumpyArray(
+ name, retn, errorsin2d=params.field_results["errorsin2d"]) ]
+
+class ImportPluginNpz(ImportPlugin):
+ """For reading single datasets from NPY numpy saved files."""
+
+ name = "Numpy NPZ import"
+ author = "Jeremy Sanders"
+ description = "Reads datasets from a Numpy NPZ file."
+ file_extensions = set(['.npz'])
+
+ def __init__(self):
+ self.fields = [
+ field.FieldBool("errorsin2d",
+ descr="Treat 2 and 3 column 2D arrays as\n"
+ "data with error bars",
+ default=True),
+ ]
+
+ def getPreview(self, params):
+ """Get data to show in a text box to show a preview.
+ params is a ImportPluginParams object.
+ Returns (text, okaytoimport)
+ """
+ try:
+ retn = N.load(params.filename)
+ except Exception, e:
+ return "Cannot read file", False
+
+ # npz files should define this attribute
+ try:
+ retn.files
+ except AttributeError:
+ return "Not an NPZ file", False
+
+ text = []
+ for f in sorted(retn.files):
+ a = retn[f]
+ text.append('Name: %s' % f)
+ text.append(' Shape: %s' % str(a.shape))
+ text.append(' Datatype: %s (%s)' % (a.dtype.str, str(a.dtype)))
+ text.append('')
+ return '\n'.join(text), True
+
+ def doImport(self, params):
+ """Actually import data.
+ """
+
+ try:
+ retn = N.load(params.filename)
+ except Exception, e:
+ raise ImportPluginException("Error while reading file: %s" %
+ unicode(e))
+
+ try:
+ retn.files
+ except AttributeError:
+ raise ImportPluginException("File is not in NPZ format")
+
+ # convert each of the imported arrays
+ out = []
+ for f in sorted(retn.files):
+ out.append( cnvtImportNumpyArray(
+ f, retn[f], errorsin2d=params.field_results["errorsin2d"]) )
+
+ return out
+
+class ImportPluginBinary(ImportPlugin):
+
+ name = "Binary import"
+ author = "Jeremy Sanders"
+ description = "Reads numerical binary files."
+ file_extensions = set(['.bin'])
+
+ def __init__(self):
+ self.fields = [
+ field.FieldText("name", descr="Dataset name",
+ default=""),
+ field.FieldCombo("datatype", descr="Data type",
+ items = ("float32", "float64",
+ "int8", "int16", "int32", "int64",
+ "uint8", "uint16", "uint32", "uint64"),
+ default="float64", editable=False),
+ field.FieldCombo("endian", descr="Endian (byte order)",
+ items = ("little", "big"), editable=False),
+ field.FieldInt("offset", descr="Offset (bytes)", default=0, minval=0),
+ field.FieldInt("length", descr="Length (values)", default=-1)
+ ]
+
+ def getNumpyDataType(self, params):
+ """Convert params to numpy datatype."""
+ t = N.dtype(str(params.field_results["datatype"]))
+ return t.newbyteorder( {"little": "<", "big": ">"} [
+ params.field_results["endian"]] )
+
+ def getPreview(self, params):
+ """Preview of data files."""
+ try:
+ f = open(params.filename, "rb")
+ data = f.read()
+ f.close()
+ except IOError:
+ return "Cannot read file", False
+
+ text = ['File length: %i bytes' % len(data)]
+
+ def filtchr(c):
+ """Filtered character to ascii range."""
+ if ord(c) <= 32 or ord(c) > 127:
+ return '.'
+ else:
+ return c
+
+ # do a hex dump (like in CP/M)
+ for i in xrange(0, min(65536, len(data)), 16):
+ hdr = '%04X ' % i
+ subset = data[i:i+16]
+ hexdata = ('%02X '*len(subset)) % tuple([ord(x) for x in subset])
+ chrdata = ''.join([filtchr(c) for c in subset])
+
+ text.append(hdr+hexdata + ' ' + chrdata)
+
+ return '\n'.join(text), True
+
+ def doImport(self, params):
+ """Import the data."""
+
+ name = params.field_results["name"].strip()
+ if not name:
+ raise ImportPluginException("Please provide a name for the dataset")
+
+ try:
+ f = open(params.filename, "rb")
+ f.seek( params.field_results["offset"] )
+ retn = f.read()
+ f.close()
+ except IOError, e:
+ raise ImportPluginException("Error while reading file: %s" %
+ unicode(e))
+
+ data = N.fromstring(retn, dtype=self.getNumpyDataType(params),
+ count=params.field_results["length"])
+ data = data.astype(N.float64)
+ return [ datasetplugin.Dataset1D(name, data) ]
+
importpluginregistry += [
- ImportPluginQdp(),
- ImportPluginExample(),
+ ImportPluginNpy,
+ ImportPluginNpz,
+ ImportPluginQdp,
+ ImportPluginBinary,
+ ImportPluginExample,
]
diff -Nru veusz-1.10/plugins/__init__.py veusz-1.14/plugins/__init__.py
--- veusz-1.10/plugins/__init__.py 2010-12-12 12:41:06.000000000 +0000
+++ veusz-1.14/plugins/__init__.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,9 +16,18 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: __init__.py 1334 2010-07-19 20:25:08Z jeremysanders $
-
from field import *
from datasetplugin import *
from importplugin import *
from toolsplugin import *
+
+# backward compatibility
+ImportDataset1D = Dataset1D
+ImportDataset2D = Dataset2D
+ImportDatasetText = DatasetText
+ImportField = Field
+ImportFieldCheck = FieldBool
+ImportFieldText = FieldText
+ImportFieldFloat = FieldFloat
+ImportFieldInt = FieldInt
+ImportFieldCombo = FieldCombo
diff -Nru veusz-1.10/plugins/toolsplugin.py veusz-1.14/plugins/toolsplugin.py
--- veusz-1.10/plugins/toolsplugin.py 2010-12-12 12:41:06.000000000 +0000
+++ veusz-1.14/plugins/toolsplugin.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: toolsplugin.py 1408 2010-09-16 19:33:07Z jeremysanders $
-
"""Plugins for general operations."""
import random
@@ -183,7 +181,7 @@
idx += 1
class ColorsReplace(ToolsPlugin):
- """Randomize the colors used in plotting."""
+ """Replace one color by another."""
menu = ('Colors', 'Replace')
name = 'Replace colors'
@@ -228,6 +226,55 @@
fromwidget = ifc.Root.fromPath(fields['widget'])
walkNodes(fromwidget)
+class ColorsSwap(ToolsPlugin):
+ """Swap colors used in plotting."""
+
+ menu = ('Colors', 'Swap')
+ name = 'Swap colors'
+ description_short = 'Swap two colors'
+ description_full = 'Swaps two colors in the plot'
+
+ def __init__(self):
+ """Construct plugin."""
+ self.fields = [
+ field.FieldWidget("widget", descr="Start from widget",
+ default="/"),
+ field.FieldBool("follow", descr="Change references and defaults",
+ default=True),
+ field.FieldColor('color1', descr="First color",
+ default='black'),
+ field.FieldColor('color2', descr="Second color",
+ default='red'),
+ ]
+
+ def apply(self, ifc, fields):
+ """Do the color search and replace."""
+
+ col1 = qt4.QColor(fields['color1'])
+ col2 = qt4.QColor(fields['color2'])
+
+ def walkNodes(node):
+ """Walk nodes, changing values."""
+ if node.type == 'setting' and node.settingtype == 'color':
+ # only follow references if requested
+ if node.isreference:
+ if fields['follow']:
+ node = node.resolveReference()
+ else:
+ return
+
+ # evaluate into qcolor to make sure is a true match
+ if qt4.QColor(node.val) == col1:
+ node.val = fields['color2']
+ elif qt4.QColor(node.val) == col2:
+ node.val = fields['color1']
+ else:
+ for c in node.children:
+ walkNodes(c)
+
+ fromwidget = ifc.Root.fromPath(fields['widget'])
+ walkNodes(fromwidget)
+
class TextReplace(ToolsPlugin):
"""Randomize the colors used in plotting."""
@@ -469,6 +516,7 @@
ColorsRandomize,
ColorsSequence,
ColorsReplace,
+ ColorsSwap,
TextReplace,
WidgetsClone,
FontSizeIncrease,
diff -Nru veusz-1.10/qtall.py veusz-1.14/qtall.py
--- veusz-1.10/qtall.py 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/qtall.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: qtall.py 1243 2010-05-29 13:35:43Z jeremysanders $
-
"""A convenience module to import both the used Qt symbols from."""
import sys
@@ -28,4 +26,5 @@
from PyQt4.QtCore import *
from PyQt4.QtGui import *
+from PyQt4.QtSvg import *
from PyQt4.uic import loadUi
diff -Nru veusz-1.10/qtwidgets/datasetbrowser.py veusz-1.14/qtwidgets/datasetbrowser.py
--- veusz-1.10/qtwidgets/datasetbrowser.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/datasetbrowser.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,677 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+###############################################################################
+
+"""A widget for navigating datasets."""
+
+import os.path
+import numpy as N
+import textwrap
+
+import veusz.qtall as qt4
+import veusz.setting as setting
+import veusz.document as document
+import veusz.utils as utils
+
+from lineeditwithclear import LineEditWithClear
+from veusz.utils.treemodel import TMNode, TreeModel
+
+def datasetLinkFile(ds):
+ """Get a linked filename from a dataset."""
+ if ds.linked is None:
+ return "/"
+ else:
+ return ds.linked.filename
+
+class DatasetNode(TMNode):
+ """Node for a dataset."""
+
+ def __init__(self, doc, dsname, cols, parent):
+ ds = doc.data[dsname]
+ data = []
+ assert cols[0] == "name"
+ for c in cols:
+ if c == "name":
+ data.append( dsname )
+ elif c == "size":
+ data.append( ds.userSize() )
+ elif c == "type":
+ data.append( ds.dstype )
+ elif c == "linkfile":
+ data.append( os.path.basename(datasetLinkFile(ds)) )
+
+ TMNode.__init__(self, tuple(data), parent)
+ self.doc = doc
+ self.cols = cols
+
+ def getPreviewPixmap(self, ds):
+ """Get a preview pixmap for a dataset."""
+ size = (140, 70)
+ if ds.dimensions != 1 or ds.datatype != "numeric":
+ return None
+
+ pixmap = qt4.QPixmap(*size)
+ pixmap.fill(qt4.Qt.transparent)
+ p = qt4.QPainter(pixmap)
+ p.setRenderHint(qt4.QPainter.Antialiasing)
+
+ # calculate data points
+ try:
+ if len(ds.data) < size[1]:
+ y = ds.data
+ else:
+ intvl = len(ds.data)/size[1]+1
+ y = ds.data[::intvl]
+ x = N.arange(len(y))
+
+ # plot data points on image
+ minval, maxval = N.nanmin(y), N.nanmax(y)
+ y = (y-minval) / (maxval-minval) * size[1]
+ finite = N.isfinite(y)
+ x, y = x[finite], y[finite]
+ x = x * (1./len(x)) * size[0]
+
+ poly = qt4.QPolygonF()
+ utils.addNumpyToPolygonF(poly, x, size[1]-y)
+ p.setPen( qt4.QPen(qt4.Qt.blue) )
+ p.drawPolyline(poly)
+
+ # draw x axis if span 0
+ p.setPen( qt4.QPen(qt4.Qt.black) )
+ if minval <= 0 and maxval > 0:
+ y0 = size[1] - (0-minval)/(maxval-minval)*size[1]
+ p.drawLine(x[0], y0, x[-1], y0)
+ else:
+ p.drawLine(x[0], size[1], x[-1], size[1])
+ p.drawLine(x[0], 0, x[0], size[1])
+
+ except (ValueError, ZeroDivisionError):
+ # zero sized array after filtering or min == max, so return None
+ p.end()
+ return None
+
+ p.end()
+ return pixmap
+
+ def toolTip(self, column):
+ """Return tooltip for column."""
+ try:
+ ds = self.doc.data[self.data[0]]
+ except KeyError:
+ return qt4.QVariant()
+
+ c = self.cols[column]
+ if c == "name":
+ return qt4.QVariant(textwrap.fill(ds.description(), 40))
+ elif c == "size" or (c == 'type' and 'size' not in self.cols):
+ text = ds.userPreview()
+ # add preview of dataset if possible
+ pix = self.getPreviewPixmap(ds)
+ if pix:
+ text = text.replace("\n", " ")
+ text = "%s %s" % (text, utils.pixmapAsHtml(pix))
+ return qt4.QVariant(text)
+ elif c == "linkfile" or c == "type":
+ return qt4.QVariant(textwrap.fill(ds.linkedInformation(), 40))
+ return qt4.QVariant()
+
+ def dataset(self):
+ """Get associated dataset."""
+ try:
+ return self.doc.data[self.data[0]]
+ except KeyError:
+ return None
+
+ def datasetName(self):
+ """Get dataset name."""
+ return self.data[0]
+
+ def cloneTo(self, newroot):
+ """Make a clone of self at the root given."""
+ return self.__class__(self.doc, self.data[0], self.cols, newroot)
+
+class FilenameNode(TMNode):
+ """A special node for holding filenames of files."""
+
+ def nodeData(self, column):
+ """basename of filename for data."""
+ if column == 0:
+ if self.data[0] == "/":
+ return qt4.QVariant("/")
+ else:
+ return qt4.QVariant(os.path.basename(self.data[0]))
+ return qt4.QVariant()
+
+ def filename(self):
+ """Return filename."""
+ return self.data[0]
+
+ def toolTip(self, column):
+ """Full filename for tooltip."""
+ if column == 0:
+ return qt4.QVariant(self.data[0])
+ return qt4.QVariant()
+
+def treeFromList(nodelist, rootdata):
+ """Construct a tree from a list of nodes."""
+ tree = TMNode( rootdata, None )
+ for node in nodelist:
+ tree.insertChildSorted(node)
+ return tree
+
+class DatasetRelationModel(TreeModel):
+ """A model to show how the datasets are related to each file."""
+ def __init__(self, doc, grouping="filename", readonly=False,
+ filterdims=None, filterdtype=None):
+ """Model parameters:
+ doc: document
+ group: how to group datasets
+ readonly: no modification of data
+ filterdims/filterdtype: filter dimensions and datatypes.
+ """
+
+ TreeModel.__init__(self, ("Dataset", "Size", "Type"))
+ self.doc = doc
+ self.linkednodes = {}
+ self.grouping = grouping
+ self.filter = ""
+ self.readonly = readonly
+ self.filterdims = filterdims
+ self.filterdtype = filterdtype
+ self.refresh()
+
+ self.connect(doc, qt4.SIGNAL("sigModified"), self.refresh)
+
+ def datasetFilterOut(self, ds, node):
+ """Should dataset be filtered out by filter options."""
+ filterout = False
+
+ # is filter text not in node text
+ if ( self.filter != "" and
+ all([d.find(self.filter)<0 for d in node.data]) ):
+ filterout = True
+ if ( self.filterdims is not None and
+ ds.dimensions not in self.filterdims ):
+ filterout = True
+ if ( self.filterdtype is not None and
+ ds.datatype not in self.filterdtype ):
+ filterout = True
+
+ return filterout
+
+ def makeGrpTreeNone(self):
+ """Make tree with no grouping."""
+ tree = TMNode( ("Dataset", "Size", "Type", "File"), None )
+ for name, ds in self.doc.data.iteritems():
+ child = DatasetNode( self.doc, name,
+ ("name", "size", "type", "linkfile"),
+ None )
+
+ # add if not filtered for filtering
+ if not self.datasetFilterOut(ds, child):
+ tree.insertChildSorted(child)
+ return tree
+
+ def makeGrpTree(self, coltitles, colitems, grouper, GrpNodeClass):
+ """Make a tree grouping with function:
+ coltitles: tuple of titles of columns for user
+ colitems: tuple of items to lookup in DatasetNode
+ grouper: function of dataset to return text for grouping
+ GrpNodeClass: class for creating grouping nodes
+ """
+ grpnodes = {}
+ for name, ds in self.doc.data.iteritems():
+ child = DatasetNode(self.doc, name, colitems, None)
+
+ # check whether filtered out
+ if not self.datasetFilterOut(ds, child):
+ # get group
+ grp = grouper(ds)
+ if grp not in grpnodes:
+ grpnodes[grp] = GrpNodeClass( (grp,), None )
+ # add to group
+ grpnodes[grp].insertChildSorted(child)
+
+ return treeFromList(grpnodes.values(), coltitles)
+
+ def makeGrpTreeFilename(self):
+ """Make a tree of datasets grouped by linked file."""
+ return self.makeGrpTree(
+ ("Dataset", "Size", "Type"),
+ ("name", "size", "type"),
+ lambda ds: datasetLinkFile(ds),
+ FilenameNode
+ )
+
+ def makeGrpTreeSize(self):
+ """Make a tree of datasets grouped by dataset size."""
+ return self.makeGrpTree(
+ ("Dataset", "Type", "Filename"),
+ ("name", "type", "linkfile"),
+ lambda ds: ds.userSize(),
+ TMNode
+ )
+
+ def makeGrpTreeType(self):
+ """Make a tree of datasets grouped by dataset type."""
+ return self.makeGrpTree(
+ ("Dataset", "Size", "Filename"),
+ ("name", "size", "linkfile"),
+ lambda ds: ds.dstype,
+ TMNode
+ )
+
+ def flags(self, idx):
+ """Return model flags for index."""
+ f = TreeModel.flags(self, idx)
+ # allow dataset names to be edited
+ if ( idx.isValid() and isinstance(self.objFromIndex(idx), DatasetNode)
+ and not self.readonly and idx.column() == 0 ):
+ f |= qt4.Qt.ItemIsEditable
+ return f
+
+ def setData(self, idx, data, role):
+ """Rename dataset."""
+ dsnode = self.objFromIndex(idx)
+ newname = unicode(data.toString())
+ if not utils.validateDatasetName(newname) or newname in self.doc.data:
+ return False
+
+ self.doc.applyOperation(
+ document.OperationDatasetRename(dsnode.data[0], newname))
+ self.emit(
+ qt4.SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"),
+ idx, idx)
+ return True
+
+ def refresh(self):
+ """Update tree of datasets when document changes."""
+
+ header = self.root.data
+ tree = {
+ "none": self.makeGrpTreeNone,
+ "filename": self.makeGrpTreeFilename,
+ "size": self.makeGrpTreeSize,
+ "type": self.makeGrpTreeType,
+ }[self.grouping]()
+
+ self.syncTree(tree)
+
+class DatasetsNavigatorTree(qt4.QTreeView):
+ """Tree view for dataset names."""
+
+ def __init__(self, doc, mainwin, grouping, parent,
+ readonly=False, filterdims=None, filterdtype=None):
+ """Initialise the dataset tree view.
+ doc: veusz document
+ mainwin: veusz main window (or None if readonly)
+ grouping: grouping mode of datasets
+ parent: parent window or None
+ filterdims: if set, only show datasets with dimensions given
+ filterdtype: if set, only show datasets with type given
+ """
+
+ qt4.QTreeView.__init__(self, parent)
+ self.doc = doc
+ self.mainwindow = mainwin
+ self.model = DatasetRelationModel(doc, grouping, readonly=readonly,
+ filterdims=filterdims,
+ filterdtype=filterdtype)
+
+ self.setModel(self.model)
+ self.setSelectionBehavior(qt4.QTreeView.SelectRows)
+ self.setUniformRowHeights(True)
+ self.setContextMenuPolicy(qt4.Qt.CustomContextMenu)
+ if not readonly:
+ self.connect(self, qt4.SIGNAL("customContextMenuRequested(QPoint)"),
+ self.showContextMenu)
+ self.model.refresh()
+ self.expandAll()
+
+ # stretch of columns
+ hdr = self.header()
+ hdr.setStretchLastSection(False)
+ hdr.setResizeMode(0, qt4.QHeaderView.Stretch)
+ for col in xrange(1, 3):
+ hdr.setResizeMode(col, qt4.QHeaderView.ResizeToContents)
+
+ # when documents have finished opening, expand all nodes
+ if mainwin is not None:
+ self.connect(mainwin, qt4.SIGNAL("documentopened"), self.expandAll)
+
+ # keep track of selection
+ self.connect( self.selectionModel(),
+ qt4.SIGNAL("selectionChanged(const QItemSelection&, "
+ "const QItemSelection&)"),
+ self.slotNewSelection )
+
+ # expand nodes by default
+ self.connect( self.model,
+ qt4.SIGNAL("rowsInserted(const QModelIndex&, int, int)"),
+ self.slotNewRow )
+
+ def changeGrouping(self, grouping):
+ """Change the tree grouping behaviour."""
+ self.model.grouping = grouping
+ self.model.refresh()
+ self.expandAll()
+
+ def changeFilter(self, filtertext):
+ """Change filtering text."""
+ self.model.filter = filtertext
+ self.model.refresh()
+ self.expandAll()
+
+ def selectDataset(self, dsname):
+ """Find, and if possible select dataset name."""
+
+ matches = self.model.match(
+ self.model.index(0, 0, qt4.QModelIndex()),
+ qt4.Qt.DisplayRole, qt4.QVariant(dsname), -1,
+ qt4.Qt.MatchFixedString | qt4.Qt.MatchCaseSensitive |
+ qt4.Qt.MatchRecursive )
+ for idx in matches:
+ if isinstance(self.model.objFromIndex(idx), DatasetNode):
+ self.selectionModel().setCurrentIndex(
+ idx, qt4.QItemSelectionModel.SelectCurrent |
+ qt4.QItemSelectionModel.Clear |
+ qt4.QItemSelectionModel.Rows )
+
+ def showContextMenu(self, pt):
+ """Context menu for nodes."""
+ idx = self.currentIndex()
+ if idx.isValid():
+ node = self.model.objFromIndex(idx)
+ else:
+ node = None
+
+ menu = qt4.QMenu()
+ if isinstance(node, DatasetNode):
+ self.datasetContextMenu(node, menu)
+ elif isinstance(node, FilenameNode):
+ self.filenameContextMenu(node, menu)
+
+ def _paste():
+ """Paste dataset."""
+ if document.isDataMime():
+ mime = qt4.QApplication.clipboard().mimeData()
+ self.doc.applyOperation(document.OperationDataPaste(mime))
+
+ # if there is data to paste, add menu item
+ if document.isDataMime():
+ menu.addAction("Paste", _paste)
+
+ if len( menu.actions() ) != 0:
+ menu.exec_(self.mapToGlobal(pt))
+
+ def datasetContextMenu(self, dsnode, menu):
+ """Return context menu for datasets."""
+ import veusz.dialogs.dataeditdialog as dataeditdialog
+ dataset = dsnode.dataset()
+ dsname = dsnode.datasetName()
+
+ def _edit():
+ """Open up dialog box to recreate dataset."""
+ dataeditdialog.recreate_register[type(dataset)](
+ self.mainwindow, self.doc, dataset, dsname)
+ def _edit_data():
+ """Open up data edit dialog."""
+ dialog = self.mainwindow.slotDataEdit(editdataset=dsname)
+ def _delete():
+ """Simply delete dataset."""
+ self.doc.applyOperation(document.OperationDatasetDelete(dsname))
+ def _unlink_file():
+ """Unlink dataset from file."""
+ self.doc.applyOperation(document.OperationDatasetUnlinkFile(dsname))
+ def _unlink_relation():
+ """Unlink dataset from relation."""
+ self.doc.applyOperation(document.OperationDatasetUnlinkRelation(dsname))
+ def _copy():
+ """Copy data to clipboard."""
+ mime = document.generateDatasetsMime([dsname], self.doc)
+ qt4.QApplication.clipboard().setMimeData(mime)
+
+ if type(dataset) in dataeditdialog.recreate_register:
+ menu.addAction("Edit", _edit)
+ else:
+ menu.addAction("Edit data", _edit_data)
+
+ menu.addAction("Delete", _delete)
+ if dataset.canUnlink():
+ if dataset.linked:
+ menu.addAction("Unlink file", _unlink_file)
+ else:
+ menu.addAction("Unlink relation", _unlink_relation)
+
+ menu.addAction("Copy", _copy)
+
+ useasmenu = menu.addMenu("Use as")
+ if dataset is not None:
+ self.getMenuUseAs(useasmenu, dataset)
+
+ def filenameContextMenu(self, node, menu):
+ """Return context menu for filenames."""
+
+ from veusz.dialogs.reloaddata import ReloadData
+ filename = node.filename()
+ if filename == '/':
+ # non linked filename node
+ return None
+
+ def _reload():
+ """Reload data in this file."""
+ d = ReloadData(self.doc, self.mainwindow, filenames=set([filename]))
+ self.mainwindow.showDialog(d)
+ def _unlink_all():
+ """Unlink all datasets associated with file."""
+ self.doc.applyOperation(
+ document.OperationDatasetUnlinkByFile(filename))
+ def _delete_all():
+ """Delete all datasets associated with file."""
+ self.doc.applyOperation(
+ document.OperationDatasetDeleteByFile(filename))
+
+ menu.addAction("Reload", _reload)
+ menu.addAction("Unlink all", _unlink_all)
+ menu.addAction("Delete all", _delete_all)
+
+ def getMenuUseAs(self, menu, dataset):
+ """Build up menu of widget settings to use dataset in."""
+
+ def addifdatasetsetting(path, setn):
+ def _setdataset():
+ self.doc.applyOperation(
+ document.OperationSettingSet(
+ path, self.doc.datasetName(dataset)) )
+
+ if ( isinstance(setn, setting.Dataset) and
+ setn.dimensions == dataset.dimensions and
+ setn.datatype == dataset.datatype and
+ path[:12] != "/StyleSheet/" ):
+ menu.addAction(path, _setdataset)
+
+ self.doc.walkNodes(addifdatasetsetting, nodetypes=("setting",))
+
+ def keyPressEvent(self, event):
+ """Enter key selects widget."""
+ if event.key() in (qt4.Qt.Key_Return, qt4.Qt.Key_Enter):
+ self.emit(qt4.SIGNAL("updateitem"))
+ return
+ qt4.QTreeView.keyPressEvent(self, event)
+
+ def mouseDoubleClickEvent(self, event):
+ """Emit updateitem signal if double clicked."""
+ retn = qt4.QTreeView.mouseDoubleClickEvent(self, event)
+ self.emit(qt4.SIGNAL("updateitem"))
+ return retn
+
+ def slotNewSelection(self, selected, deselected):
+ """Emit selecteditem signal on new selection."""
+ self.emit(qt4.SIGNAL("selecteditem"), self.getSelectedDataset())
+
+ def slotNewRow(self, parent, start, end):
+ """Expand parent if added."""
+ self.expand(parent)
+
+ def getSelectedDataset(self):
+ """Return selected dataset."""
+ name = None
+ sel = self.selectionModel().selection()
+ try:
+ modelidx = sel.indexes()[0]
+ node = self.model.objFromIndex(modelidx)
+ if isinstance(node, DatasetNode):
+ name = node.datasetName()
+ except IndexError:
+ pass
+ return name
+
+class DatasetBrowser(qt4.QWidget):
+ """Widget which shows the document's datasets."""
+
+ # how datasets can be grouped
+ grpnames = ("none", "filename", "type", "size")
+ grpentries = {
+ "none": "None",
+ "filename": "Filename",
+ "type": "Type",
+ "size": "Size"
+ }
+
+ def __init__(self, thedocument, mainwin, parent, readonly=False,
+ filterdims=None, filterdtype=None):
+ """Initialise widget:
+ thedocument: document to show
+ mainwin: main window of application (or None if readonly)
+ parent: parent of widget.
+ readonly: for choosing datasets only
+ filterdims: if set, only show datasets with dimensions given
+ filterdtype: if set, only show datasets with type given
+ """
+
+ qt4.QWidget.__init__(self, parent)
+ self.layout = qt4.QVBoxLayout()
+ self.setLayout(self.layout)
+
+ # options for navigator are in this layout
+ self.optslayout = qt4.QHBoxLayout()
+
+ # grouping options - use a menu to choose the grouping
+ self.grpbutton = qt4.QPushButton("Group")
+ self.grpmenu = qt4.QMenu()
+ self.grouping = setting.settingdb.get("navtree_grouping", "filename")
+ self.grpact = qt4.QActionGroup(self)
+ self.grpact.setExclusive(True)
+ for name in self.grpnames:
+ a = self.grpmenu.addAction(self.grpentries[name])
+ a.grpname = name
+ a.setCheckable(True)
+ if name == self.grouping:
+ a.setChecked(True)
+ self.grpact.addAction(a)
+ self.connect(self.grpact, qt4.SIGNAL("triggered(QAction*)"),
+ self.slotGrpChanged)
+ self.grpbutton.setMenu(self.grpmenu)
+ self.grpbutton.setToolTip("Group datasets with property given")
+ self.optslayout.addWidget(self.grpbutton)
+
+ # filtering by entering text
+ self.optslayout.addWidget(qt4.QLabel("Filter"))
+ self.filteredit = LineEditWithClear()
+ self.filteredit.setToolTip("Enter text here to filter datasets")
+ self.connect(self.filteredit, qt4.SIGNAL("textChanged(const QString&)"),
+ self.slotFilterChanged)
+ self.optslayout.addWidget(self.filteredit)
+
+ self.layout.addLayout(self.optslayout)
+
+ # the actual widget tree
+ self.navtree = DatasetsNavigatorTree(
+ thedocument, mainwin, self.grouping, None,
+ readonly=readonly, filterdims=filterdims, filterdtype=filterdtype)
+ self.layout.addWidget(self.navtree)
+
+ def slotGrpChanged(self, action):
+ """Grouping changed by user."""
+ self.navtree.changeGrouping(action.grpname)
+ setting.settingdb["navtree_grouping"] = action.grpname
+
+ def slotFilterChanged(self, filtertext):
+ """Filtering changed by user."""
+ self.navtree.changeFilter(unicode(filtertext))
+
+ def selectDataset(self, dsname):
+ """Find, and if possible select dataset name."""
+ self.navtree.selectDataset(dsname)
+
+class DatasetBrowserPopup(DatasetBrowser):
+ """Popup window for dataset browser for selecting datasets.
+ This is used by setting.controls.Dataset
+ """
+
+ def __init__(self, document, dsname, parent,
+ filterdims=None, filterdtype=None):
+ """Open popup window for document
+ dsname: dataset name
+ parent: window parent
+ filterdims: if set, only show datasets with dimensions given
+ filterdtype: if set, only show datasets with type given
+ """
+
+ DatasetBrowser.__init__(self, document, None, parent, readonly=True,
+ filterdims=filterdims, filterdtype=filterdtype)
+ self.setWindowFlags(qt4.Qt.Popup)
+ self.setAttribute(qt4.Qt.WA_DeleteOnClose)
+ self.spacing = self.fontMetrics().height()
+
+ utils.positionFloatingPopup(self, parent)
+ self.selectDataset(dsname)
+ self.installEventFilter(self)
+
+ self.navtree.setFocus()
+
+ self.connect(self.navtree, qt4.SIGNAL("updateitem"),
+ self.slotUpdateItem)
+
+ def eventFilter(self, node, event):
+ """Grab clicks outside this window to close it."""
+ if ( isinstance(event, qt4.QMouseEvent) and
+ event.buttons() != qt4.Qt.NoButton ):
+ frame = qt4.QRect(0, 0, self.width(), self.height())
+ if not frame.contains(event.pos()):
+ self.close()
+ return True
+ return qt4.QTextEdit.eventFilter(self, node, event)
+
+ def sizeHint(self):
+ """A reasonable size for the text editor."""
+ return qt4.QSize(self.spacing*30, self.spacing*20)
+
+ def closeEvent(self, event):
+ """Tell the calling widget that we are closing."""
+ self.emit(qt4.SIGNAL("closing"))
+ event.accept()
+
+ def slotUpdateItem(self):
+ """Emit new dataset signal."""
+ selected = self.navtree.selectionModel().currentIndex()
+ if selected.isValid():
+ n = self.navtree.model.objFromIndex(selected)
+ if isinstance(n, DatasetNode):
+ self.emit(qt4.SIGNAL("newdataset"), n.data[0])
+ self.close()
diff -Nru veusz-1.10/qtwidgets/historycheck.py veusz-1.14/qtwidgets/historycheck.py
--- veusz-1.10/qtwidgets/historycheck.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/historycheck.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,63 @@
+# Copyright (C) 2009 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+import veusz.qtall as qt4
+import veusz.setting as setting
+
+class HistoryCheck(qt4.QCheckBox):
+ """Checkbox remembers its setting between calls
+ """
+
+ def __init__(self, *args):
+ qt4.QCheckBox.__init__(self, *args)
+ self.default = False
+
+ def getSettingName(self):
+ """Get name for saving in settings."""
+ # get dialog for widget
+ dialog = self.parent()
+ while not isinstance(dialog, qt4.QDialog):
+ dialog = dialog.parent()
+
+ # combine dialog and object names to make setting
+ return '%s_%s_HistoryCheck' % ( dialog.objectName(),
+ self.objectName() )
+
+ def loadHistory(self):
+ """Load contents of HistoryCheck from settings."""
+ checked = setting.settingdb.get(self.getSettingName(), self.default)
+ # this is to ensure toggled() signals get sent
+ self.setChecked(not checked)
+ self.setChecked(checked)
+
+ def saveHistory(self):
+ """Save contents of HistoryCheck to settings."""
+ setting.settingdb[self.getSettingName()] = self.isChecked()
+
+ def showEvent(self, event):
+ """Show HistoryCheck and load history."""
+ qt4.QCheckBox.showEvent(self, event)
+ # we do this now rather than in __init__ because the widget
+ # has no name set at __init__
+ self.loadHistory()
+
+ def hideEvent(self, event):
+ """Save history as widget is hidden."""
+ qt4.QCheckBox.hideEvent(self, event)
+ self.saveHistory()
+
diff -Nru veusz-1.10/qtwidgets/historycombo.py veusz-1.14/qtwidgets/historycombo.py
--- veusz-1.10/qtwidgets/historycombo.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/historycombo.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,134 @@
+# Copyright (C) 2009 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+"""A combobox which remembers its history.
+
+The history is stored in the Veusz settings database.
+"""
+
+import veusz.qtall as qt4
+import veusz.setting as setting
+
+class HistoryCombo(qt4.QComboBox):
+ """This combobox records what items have been entered into it so the
+ user can choose them again.
+
+ Duplicates and blanks are ignored.
+ """
+
+ def __init__(self, *args):
+ qt4.QComboBox.__init__(self, *args)
+
+ # sane defaults
+ self.setEditable(True)
+ self.setAutoCompletion(True)
+ self.setMaxCount(50)
+ self.setInsertPolicy(qt4.QComboBox.InsertAtTop)
+ self.setDuplicatesEnabled(False)
+ self.setSizePolicy( qt4.QSizePolicy(qt4.QSizePolicy.MinimumExpanding,
+ qt4.QSizePolicy.Fixed) )
+
+ # stops combobox readjusting in size to fit contents
+ self.setSizeAdjustPolicy(
+ qt4.QComboBox.AdjustToMinimumContentsLengthWithIcon)
+
+ self.default = []
+ self.hasshown = False
+
+ def text(self):
+ """Get text in combobox
+ - this gives it the same interface as QLineEdit."""
+ return self.currentText()
+
+ def setText(self, text):
+ """Set text in combobox
+ - gives same interface as QLineEdit."""
+ self.lineEdit().setText(text)
+
+ def hasAcceptableInput(self):
+ """Input valid?
+ - gives same interface as QLineEdit."""
+ return self.lineEdit().hasAcceptableInput()
+
+ def replaceAndAddHistory(self, item):
+ """Replace the text and place item at top of history."""
+
+ self.lineEdit().setText(item)
+ index = self.findText(item) # lookup for existing item (if any)
+ if index != -1:
+ # remove any old items matching this
+ self.removeItem(index)
+
+ # put new item in
+ self.insertItem(0, item)
+ # set selected item in drop down list match current item
+ self.setCurrentIndex(0)
+
+ def getSettingName(self):
+ """Get name for saving in settings."""
+
+ # get dialog for widget
+ dialog = self.parent()
+ while not isinstance(dialog, qt4.QDialog):
+ dialog = dialog.parent()
+
+ # combine dialog and object names to make setting
+ return '%s_%s_HistoryCombo' % ( dialog.objectName(),
+ self.objectName() )
+
+ def loadHistory(self):
+ """Load contents of history combo from settings."""
+ self.clear()
+ history = setting.settingdb.get(self.getSettingName(), self.default)
+ self.insertItems(0, history)
+
+ self.hasshown = True
+
+ def saveHistory(self):
+ """Save contents of history combo to settings."""
+
+ # only save history if it has been loaded
+ if not self.hasshown:
+ return
+
+ # collect current items
+ history = [ unicode(self.itemText(i)) for i in xrange(self.count()) ]
+ history.insert(0, unicode(self.currentText()))
+
+ # remove dups
+ histout = []
+ histset = set()
+ for item in history:
+ if item not in histset:
+ histout.append(item)
+ histset.add(item)
+
+ # save the history
+ setting.settingdb[self.getSettingName()] = histout
+
+ def showEvent(self, event):
+ """Show HistoryCombo and load history."""
+ qt4.QComboBox.showEvent(self, event)
+ # we do this now rather than in __init__ because the widget
+ # has no name set at __init__
+ self.loadHistory()
+
+ def hideEvent(self, event):
+ """Save history as widget is hidden."""
+ qt4.QComboBox.hideEvent(self, event)
+ self.saveHistory()
diff -Nru veusz-1.10/qtwidgets/historygroupbox.py veusz-1.14/qtwidgets/historygroupbox.py
--- veusz-1.10/qtwidgets/historygroupbox.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/historygroupbox.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,76 @@
+# Copyright (C) 2010 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+import veusz.qtall as qt4
+import veusz.setting as setting
+
+class HistoryGroupBox(qt4.QGroupBox):
+ """Group box remembers settings of radio buttons inside it.
+
+ emits radioClicked(radiowidget) when clicked
+ """
+
+ def getSettingName(self):
+ """Get name for saving in settings."""
+ # get dialog for widget
+ dialog = self.parent()
+ while not isinstance(dialog, qt4.QDialog):
+ dialog = dialog.parent()
+
+ # combine dialog and object names to make setting
+ return '%s_%s_HistoryGroup' % ( dialog.objectName(),
+ self.objectName() )
+
+ def loadHistory(self):
+ """Load from settings."""
+ # connect up radio buttons to emit clicked signal
+ for w in self.children():
+ if isinstance(w, qt4.QRadioButton):
+ def doemit(w=w):
+ self.emit(qt4.SIGNAL("radioClicked"), w)
+ self.connect( w, qt4.SIGNAL('clicked()'), doemit)
+
+ # set item to be checked
+ checked = setting.settingdb.get(self.getSettingName(), "")
+ for w in self.children():
+ if isinstance(w, qt4.QRadioButton) and (
+ w.objectName() == checked or checked == ""):
+ w.click()
+ return
+
+ def getRadioChecked(self):
+ """Get name of radio button checked."""
+ for w in self.children():
+ if isinstance(w, qt4.QRadioButton) and w.isChecked():
+ return w
+ return None
+
+ def saveHistory(self):
+ """Save to settings."""
+ name = unicode(self.getRadioChecked().objectName())
+ setting.settingdb[self.getSettingName()] = name
+
+ def showEvent(self, event):
+ """Show and load history."""
+ qt4.QGroupBox.showEvent(self, event)
+ self.loadHistory()
+
+ def hideEvent(self, event):
+ """Save history as widget is hidden."""
+ qt4.QGroupBox.hideEvent(self, event)
+ self.saveHistory()
diff -Nru veusz-1.10/qtwidgets/historyspinbox.py veusz-1.14/qtwidgets/historyspinbox.py
--- veusz-1.10/qtwidgets/historyspinbox.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/historyspinbox.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,57 @@
+# Copyright (C) 2010 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+import veusz.qtall as qt4
+import veusz.setting as setting
+
+class HistorySpinBox(qt4.QSpinBox):
+ """A SpinBox which remembers its setting between calls."""
+
+ def __init__(self, *args):
+ qt4.QSpinBox.__init__(self, *args)
+ self.default = 0
+
+ def getSettingName(self):
+ """Get name for saving in settings."""
+ # get dialog for widget
+ dialog = self.parent()
+ while not isinstance(dialog, qt4.QDialog):
+ dialog = dialog.parent()
+
+ # combine dialog and object names to make setting
+ return "%s_%s_HistorySpinBox" % ( dialog.objectName(),
+ self.objectName() )
+
+ def loadHistory(self):
+ """Load contents of HistorySpinBox from settings."""
+ num = setting.settingdb.get(self.getSettingName(), self.default)
+ self.setValue(num)
+
+ def saveHistory(self):
+ """Save contents of HistorySpinBox to settings."""
+ setting.settingdb[self.getSettingName()] = self.value()
+
+ def showEvent(self, event):
+ """Show HistorySpinBox and load history."""
+ qt4.QSpinBox.showEvent(self, event)
+ self.loadHistory()
+
+ def hideEvent(self, event):
+ """Save history as widget is hidden."""
+ qt4.QSpinBox.hideEvent(self, event)
+ self.saveHistory()
diff -Nru veusz-1.10/qtwidgets/historyvaluecombo.py veusz-1.14/qtwidgets/historyvaluecombo.py
--- veusz-1.10/qtwidgets/historyvaluecombo.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/historyvaluecombo.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,92 @@
+# Copyright (C) 2009 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+"""A combobox which remembers previous setting
+"""
+
+import veusz.qtall as qt4
+import veusz.setting as setting
+
+class HistoryValueCombo(qt4.QComboBox):
+ """This combobox records what value was previously saved
+ """
+
+ def __init__(self, *args):
+ qt4.QComboBox.__init__(self, *args)
+ self.defaultlist = []
+ self.defaultval = None
+ self.hasshown = False
+
+ def getSettingName(self):
+ """Get name for saving in settings."""
+
+ # get dialog for widget
+ dialog = self.parent()
+ while not isinstance(dialog, qt4.QDialog):
+ dialog = dialog.parent()
+
+ # combine dialog and object names to make setting
+ return '%s_%s_HistoryValueCombo' % ( dialog.objectName(),
+ self.objectName() )
+
+ def saveHistory(self):
+ """Save contents of history combo to settings."""
+
+ # only save history if it has been loaded
+ if not self.hasshown:
+ return
+
+ # collect current items
+ history = [ unicode(self.itemText(i)) for i in xrange(self.count()) ]
+ history.insert(0, unicode(self.currentText()))
+
+ # remove dups
+ histout = []
+ histset = set()
+ for item in history:
+ if item not in histset:
+ histout.append(item)
+ histset.add(item)
+
+ # save the history
+ setting.settingdb[self.getSettingName()] = histout
+
+ def showEvent(self, event):
+ """Show HistoryCombo and load history."""
+ qt4.QComboBox.showEvent(self, event)
+
+ self.clear()
+ self.addItems(self.defaultlist)
+ text = setting.settingdb.get(self.getSettingName(), self.defaultval)
+ if text is not None:
+ indx = self.findText(text)
+ if indx < 0:
+ if self.isEditable():
+ self.insertItem(0, text)
+ indx = 0
+ self.setCurrentIndex(indx)
+ self.hasshown = True
+
+ def hideEvent(self, event):
+ """Save history as widget is hidden."""
+ qt4.QComboBox.hideEvent(self, event)
+
+ if self.hasshown:
+ text = unicode(self.currentText())
+ setting.settingdb[self.getSettingName()] = text
+
diff -Nru veusz-1.10/qtwidgets/__init__.py veusz-1.14/qtwidgets/__init__.py
--- veusz-1.10/qtwidgets/__init__.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/__init__.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,38 @@
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+"""Veusz qtwidgets module."""
+
+# insert history combo into the list of modules so that it can be found
+# by loadUi - yuck
+import sys
+import historycombo
+import historycheck
+import historyvaluecombo
+import historygroupbox
+import historyspinbox
+import recentfilesbutton
+import lineeditwithclear
+
+sys.modules['historycombo'] = historycombo
+sys.modules['historycheck'] = historycheck
+sys.modules['historyvaluecombo'] = historyvaluecombo
+sys.modules['historygroupbox'] = historygroupbox
+sys.modules['historyspinbox'] = historyspinbox
+sys.modules['recentfilesbutton'] = recentfilesbutton
+sys.modules['lineeditwithclear'] = lineeditwithclear
diff -Nru veusz-1.10/qtwidgets/lineeditwithclear.py veusz-1.14/qtwidgets/lineeditwithclear.py
--- veusz-1.10/qtwidgets/lineeditwithclear.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/lineeditwithclear.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+###############################################################################
+
+import veusz.qtall as qt4
+import veusz.utils as utils
+
+class LineEditWithClear(qt4.QLineEdit):
+ """This is a line edit widget which supplies a clear button
+ to delete the text if it is clicked.
+
+ Adapted from:
+ http://labs.qt.nokia.com/2007/06/06/lineedit-with-a-clear-button/
+ """
+
+ def __init__(self, *args):
+ """Initialise the line edit."""
+ qt4.QLineEdit.__init__(self, *args)
+
+ # the clear button itself, with no padding
+ self.clearbutton = cb = qt4.QToolButton(self)
+ cb.setIcon( utils.getIcon('kde-edit-delete') )
+ cb.setCursor(qt4.Qt.ArrowCursor)
+ cb.setStyleSheet('QToolButton { border: none; padding: 0px; }')
+ cb.setToolTip("Clear text")
+ cb.hide()
+
+ # make clicking on the button clear the text
+ self.connect(cb, qt4.SIGNAL('clicked()'), self, qt4.SLOT("clear()"))
+
+ # button should appear if there is text
+ self.connect(self, qt4.SIGNAL('textChanged(const QString&)'),
+ self.updateCloseButton)
+
+ # positioning of the button
+ fw = self.style().pixelMetric(qt4.QStyle.PM_DefaultFrameWidth)
+ self.setStyleSheet("QLineEdit { padding-right: %ipx; } " %
+ (cb.sizeHint().width() + fw + 1))
+ msz = self.minimumSizeHint()
+ mx = cb.sizeHint().height()+ fw*2 + 2
+ self.setMinimumSize( max(msz.width(), mx), max(msz.height(), mx) )
+
+ def resizeEvent(self, evt):
+ """Move button if widget resized."""
+ sz = self.clearbutton.sizeHint()
+ fw = self.style().pixelMetric(qt4.QStyle.PM_DefaultFrameWidth)
+ r = self.rect()
+ self.clearbutton.move( r.right() - fw - sz.width(),
+ (r.bottom() + 1 - sz.height())/2 )
+
+ def updateCloseButton(self, text):
+ """Button should only appear if there is text."""
+ self.clearbutton.setVisible( not text.isEmpty() )
diff -Nru veusz-1.10/qtwidgets/recentfilesbutton.py veusz-1.14/qtwidgets/recentfilesbutton.py
--- veusz-1.10/qtwidgets/recentfilesbutton.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/qtwidgets/recentfilesbutton.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,81 @@
+# Copyright (C) 2009 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+import os.path
+
+import veusz.qtall as qt4
+import veusz.setting as setting
+
+def removeBadRecents(itemlist):
+ """Remove duplicates from list and bad entries."""
+ previous = set()
+ i = 0
+ while i < len(itemlist):
+ if itemlist[i] in previous:
+ del itemlist[i]
+ elif not os.path.exists(itemlist[i]):
+ del itemlist[i]
+ else:
+ previous.add(itemlist[i])
+ i += 1
+
+ # trim list
+ del itemlist[10:]
+
+class RecentFilesButton(qt4.QPushButton):
+ """A button for remembering recent files.
+
+ emits filechosen(filename) if a file is chosen
+ """
+
+ def __init__(self, *args):
+ qt4.QPushButton.__init__(self, *args)
+
+ self.menu = qt4.QMenu()
+ self.setMenu(self.menu)
+ self.settingname = None
+
+ def setSetting(self, name):
+ """Specify settings to use when loading menu.
+ Should be called before use."""
+ self.settingname = name
+ self.fillMenu()
+
+ def fillMenu(self):
+ """Add filenames to menu."""
+ self.menu.clear()
+ recent = setting.settingdb.get(self.settingname, [])
+ removeBadRecents(recent)
+ setting.settingdb[self.settingname] = recent
+
+ for filename in recent:
+ if os.path.exists(filename):
+ act = self.menu.addAction( os.path.basename(filename) )
+ def loadRecentFile(filename=filename):
+ self.emit(qt4.SIGNAL('filechosen'), filename)
+ self.connect( act, qt4.SIGNAL('triggered()'),
+ loadRecentFile )
+
+ def addFile(self, filename):
+ """Add filename to list of recent files."""
+ recent = setting.settingdb.get(self.settingname, [])
+ recent.insert(0, os.path.abspath(filename))
+ setting.settingdb[self.settingname] = recent
+ self.fillMenu()
+
+
diff -Nru veusz-1.10/README veusz-1.14/README
--- veusz-1.10/README 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/README 2011-11-22 20:23:31.000000000 +0000
@@ -1,10 +1,12 @@
-Veusz 1.10
+Veusz 1.14
----------
Velvet Ember Under Sky Zenith
-----------------------------
http://home.gna.org/veusz/
-Veusz is Copyright (C) 2003-2010 Jeremy Sanders
+Copyright (C) 2003-2011 Jeremy Sanders
+and contributors.
+
Licenced under the GPL (version 2 or greater).
Veusz is a Qt4 based scientific plotting package. It is written in
@@ -16,41 +18,63 @@
Veusz provides a GUI, command line, embedding and scripting interface
(based on Python) to its plotting facilities. It also allows for
manipulation and editing of datasets. Data can be captured from
-external sources such as internet sockets or other programs.
+external sources such as Internet sockets or other programs.
-Changes in 1.10:
- * Box plot widget added, which can be given statistics to plot or
- calculated from datasets
- * Polar plot widget added
- * Datasets are now easier to construct and edit in the Data->Edit
- dialog box
- * CSV reader will assume a text dataset if it cannot convert first item
- to a number
- * Add color sequence plugin for making a range of widget colors
- * Import plugin for QDP files added
- * Date and times can be also written in local formats
- * Reload data dialog box can reload at intervals and is now non-modal
- * 2D datasets can be created based on expressions of other 2D datasets
-
-Minor changes:
- * Option to change size of ends of error bars
- * Margin size option added for key widget
- * Add --listen option to veusz command to replace veusz_listen.
- * Add --quiet option to run commands without displaying a window
- * Add --export option to export documents to graphics files and exit
- * PNG export compression increased
- * Add option to ignore number of lines after headers in CSV files
-
-Bug fixes:
- * Multiple datasets can now be properly created from dataset plugin dialog
- * X and Y ranges of 2D datasets are now correct when converted from
- X,Y,Z 1D datasets
- * Bounding boxes of resizing rectangles, ellipses and images are fixed
- * min and max coordinate range now works for plotting functions of y
- * Remove duplicate linked files when using import plugins
- * Several crash reports fixed
- * More robust code in data->edit dialog box
- * veusz_listen now works in Windows (not in binary package yet)
+Changes in 1.14:
+ * Added interactive tutorial
+ * Points in graphs can be colored depending on another dataset and
+ the scale shown in a colorbar widget
+ * Improved CSV import
+ - better data type detection
+ - locale-specific numeric and date formats
+ - single/multiple/none header modes
+ - option to skip lines at top of file
+ - better handling of missing values
+ * Data can be imported from clipboard
+ * Substantially reduced size of output SVG files
+ * In standard data import, descriptor can be left blank to generate
+ dataset names colX
+ * Axis plotting range can be interactively manipulated
+ * If axis is in date-time format, show and allow the min and max
+ values to be in date-time format
+ * ImageFile widget can have image data embedded in document file
+ * Fit widget can update the fit parameters and fit quality to a
+ label widget
+ * Allow editing of 2D datasets in data edit dialog
+ * Add copy and paste dataset command to dataset browser context menu
+
+Minor and API changes:
+ * Examples added to help menu
+ * Picker shows date values as dates
+ * Allow descriptor statement in standard data files after a comment
+ character, e.g. "#descriptor x y"
+ * Added some further color maps
+ * Draw key symbols for vector field widget
+ * Import plugin changes
+ - Register classes rather than instances (backward compatibility
+ is retained)
+ - Plugins can return constants and functions (see Constant and
+ Function types)
+ - Add DatasetDateTime for returning date-time datasets
+ * Custom definitions
+ - Add RemoveCustom API to remove custom definitions
+ - AddCustom API can specify order where custom definition is added
+ * C++ code to speed up plotting points of different sizes / colors
+ * Expand files by default in data navigator window
+ * Select created datasets in data edit dialog
+ * Tooltip wrapping used in data navigator window
+ * Grid lines are dropped if they overlap with edge of graph
+
+Bug fixes
+ * Fix initial extension in export dialog
+ * Fix crash on hiding pages
+ * Fixed validation for numeric values
+ * Position of grid lines in perpendicular direction for non default
+ positions
+ * Catch errors in example import plugin
+ * Fix crash for non existent key symbols
+ * Fix crash when mismatch of dataset sizes when combining 1D datasets
+ to make 2D dataset
Features of package:
* X-Y plots (with errorbars)
@@ -62,6 +86,7 @@
* Vector field plots
* Box plots
* Polar plots
+ * Ternary plots
* Plotting dates
* Fitting functions to data
* Stacked plots and arrays of plots
@@ -73,18 +98,21 @@
* Scripting interface
* Dataset creation/manipulation
* Embed Veusz within other programs
- * Text, CSV, FITS and user-plugin importing
+ * Text, CSV, FITS, NPY/NPZ, QDP, binary and user-plugin importing
* Data can be captured from external sources
* User defined functions, constants and can import external Python functions
* Plugin interface to allow user to write or load code to
- import data using new formats
- make new datasets, optionally linked to existing datasets
- arbitrarily manipulate the document
+ * Data picker
+ * Interactive tutorial
+ * Multithreaded rendering
Requirements for source install:
Python (2.4 or greater required)
http://www.python.org/
- Qt >= 4.3 (free edition)
+ Qt >= 4.4 (free edition)
http://www.trolltech.com/products/qt/
PyQt >= 4.3 (SIP is required to be installed first)
http://www.riverbankcomputing.co.uk/pyqt/
@@ -99,8 +127,11 @@
http://www.stsci.edu/resources/software_hardware/pyfits
pyemf >= 2.0.0 (optional for EMF export)
http://pyemf.sourceforge.net/
+ PyMinuit >= 1.1.2 (optional improved fitting)
+ http://code.google.com/p/pyminuit/
For EMF and better SVG export, PyQt >= 4.6 or better is
required, to fix a bug in the C++ wrapping
+
For documentation on using Veusz, see the "Documents" directory. The
manual is in PDF, HTML and text format (generated from docbook). The
@@ -109,23 +140,15 @@
Issues with the current version:
- * Plots can sometimes be slow using antialiasing. Go to the
- preferences dialog or right click on the plot to disable
- antialiasing.
-
* Some recent versions of PyQt/SIP will causes crashes when exporting
SVG files. Update to 4.7.4 (if released) or a recent snapshot to
solve this problem.
-If you enjoy using Veusz, I would love to hear from you. Please join
+If you enjoy using Veusz, we would love to hear from you. Please join
the mailing lists at
https://gna.org/mail/?group=veusz
to discuss new features or if you'd like to contribute code. The
-latest code can always be found in the SVN repository.
-
-Jeremy Sanders
-
--------------------------------------------------------------------------------
-$Id: README 1472 2010-12-11 21:31:00Z jeremysanders $
+latest code can always be found in the Git repository
+at https://github.com/jeremysanders/veusz.git.
diff -Nru veusz-1.10/scripts/veusz veusz-1.14/scripts/veusz
--- veusz-1.10/scripts/veusz 2010-12-12 12:41:07.000000000 +0000
+++ veusz-1.14/scripts/veusz 2011-11-22 20:23:31.000000000 +0000
@@ -20,7 +20,5 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: veusz 872 2008-12-29 12:51:59Z jeremysanders $
-
import veusz.veusz_main
veusz.veusz_main.run()
diff -Nru veusz-1.10/scripts/veusz_listen veusz-1.14/scripts/veusz_listen
--- veusz-1.10/scripts/veusz_listen 2010-12-12 12:41:07.000000000 +0000
+++ veusz-1.14/scripts/veusz_listen 2011-11-22 20:23:31.000000000 +0000
@@ -20,7 +20,5 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: veusz_listen 872 2008-12-29 12:51:59Z jeremysanders $
-
import veusz.veusz_listen
veusz.veusz_listen.run()
diff -Nru veusz-1.10/setting/collections.py veusz-1.14/setting/collections.py
--- veusz-1.10/setting/collections.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/setting/collections.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: collections.py 1449 2010-11-22 09:26:58Z jeremysanders $
-
"""Collections of predefined settings for common settings."""
import veusz.qtall as qt4
@@ -51,14 +49,14 @@
descr = 'Hide the line',
usertext='Hide') )
- def makeQPen(self, painter):
+ def makeQPen(self, painthelper):
'''Make a QPen from the description.
This currently ignores the hide attribute
'''
color = qt4.QColor(self.color)
color.setAlphaF( (100-self.transparency) / 100.)
- width = self.get('width').convert(painter)
+ width = self.get('width').convert(painthelper)
style, dashpattern = setting.LineStyle._linecnvt[self.style]
pen = qt4.QPen( color, width, style )
@@ -67,12 +65,12 @@
return pen
- def makeQPenWHide(self, painter):
+ def makeQPenWHide(self, painthelper):
"""Make a pen, taking account of hide attribute."""
if self.hide:
return qt4.QPen(qt4.Qt.NoPen)
else:
- return self.makeQPen(painter)
+ return self.makeQPen(painthelper)
class XYPlotLine(Line):
'''A plot line for plotting data, allowing histogram-steps
@@ -238,10 +236,10 @@
c.families = self.families
return c
- def makeQFont(self, painter):
+ def makeQFont(self, painthelper):
'''Return a qt4.QFont object corresponding to the settings.'''
- size = self.get('size').convertPts(painter)
+ size = self.get('size').convertPts(painthelper)
weight = qt4.QFont.Normal
if self.bold:
weight = qt4.QFont.Bold
@@ -249,7 +247,7 @@
f = qt4.QFont(self.font, size, weight, self.italic)
if self.underline:
f.setUnderline(True)
- f.setStyleHint( qt4.QFont.Times, qt4.QFont.PreferDevice )
+ f.setStyleHint(qt4.QFont.Times)
return f
diff -Nru veusz-1.10/setting/controls.py veusz-1.14/setting/controls.py
--- veusz-1.10/setting/controls.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/setting/controls.py 2011-11-22 20:23:31.000000000 +0000
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
# Copyright (C) 2005 Jeremy S. Sanders
# Email: Jeremy Sanders
#
@@ -16,8 +17,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: controls.py 1469 2010-12-08 22:15:00Z jeremysanders $
-
"""Module for creating QWidgets for the settings, to enable their values
to be changed.
@@ -27,10 +26,12 @@
from itertools import izip
import re
+import numpy as N
import veusz.qtall as qt4
import setting
+
import veusz.utils as utils
def styleClear(widget):
@@ -42,6 +43,15 @@
widget.setStyleSheet("background-color: " +
setting.settingdb.color('error').name() )
+class DotDotButton(qt4.QPushButton):
+ """A button for opening up more complex editor."""
+ def __init__(self, tooltip=None, checkable=True):
+ qt4.QPushButton.__init__(self, "..", flat=True, checkable=checkable,
+ maximumWidth=16)
+ if tooltip:
+ self.setToolTip(tooltip)
+ self.setSizePolicy(qt4.QSizePolicy.Maximum, qt4.QSizePolicy.Maximum)
+
class Edit(qt4.QLineEdit):
"""Main control for editing settings which are text."""
@@ -69,11 +79,7 @@
try:
val = self.setting.fromText(text)
styleClear(self)
-
- # value has changed
- if self.setting.val != val:
- self.emit( qt4.SIGNAL('settingChanged'),
- self, self.setting, val )
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
except setting.InvalidType:
styleError(self)
@@ -107,7 +113,7 @@
if readonly:
self.setReadOnly(True)
- self.positionSelf(parent)
+ utils.positionFloatingPopup(self, parent)
self.installEventFilter(self)
@@ -138,42 +144,13 @@
"""A reasonable size for the text editor."""
return qt4.QSize(self.spacing*40, self.spacing*3)
- def positionSelf(self, widget):
- """Open the edit box below the widget."""
-
- pos = widget.parentWidget().mapToGlobal( widget.pos() )
- desktop = qt4.QApplication.desktop()
-
- # recalculates out position so that size is correct below
- self.adjustSize()
-
- # is there room to put this widget besides the widget?
- if pos.y() + self.height() + 1 < desktop.height():
- # put below
- y = pos.y() + 1
- else:
- # put above
- y = pos.y() - self.height() - 1
-
- # is there room to the left for us?
- if ( (pos.x() + widget.width() + self.width() < desktop.width()) or
- (pos.x() + widget.width() < desktop.width()/2) ):
- # put left justified with widget
- x = pos.x() + widget.width()
- else:
- # put extending to left
- x = pos.x() - self.width() - 1
-
- self.move(x, y)
- self.setFocus()
-
def closeEvent(self, event):
"""Tell the calling widget that we are closing, and provide
the new text."""
text = unicode(self.toPlainText())
text = text.replace('\n', '')
- self.emit( qt4.SIGNAL('closing'), text)
+ self.emit(qt4.SIGNAL('closing'), text)
event.accept()
class String(qt4.QWidget):
@@ -191,11 +168,7 @@
self.edit = qt4.QLineEdit()
layout.addWidget(self.edit)
- b = self.button = qt4.QPushButton('..')
- b.setFlat(True)
- b.setSizePolicy(qt4.QSizePolicy.Maximum, qt4.QSizePolicy.Maximum)
- b.setMaximumWidth(16)
- b.setCheckable(True)
+ b = self.button = DotDotButton(tooltip="Edit text")
layout.addWidget(b)
# set the text of the widget to the
@@ -242,10 +215,7 @@
try:
val = self.setting.fromText(text)
styleClear(self.edit)
-
- # value has changed
- if self.setting.val != val:
- self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val)
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
except setting.InvalidType:
styleError(self.edit)
@@ -260,6 +230,7 @@
def __init__(self, setting, parent):
qt4.QSpinBox.__init__(self, parent)
+ self.ignorechange = False
self.setting = setting
self.setMinimum(setting.minval)
self.setMaximum(setting.maxval)
@@ -273,11 +244,15 @@
def slotChanged(self, value):
"""If check box changes."""
- self.emit(qt4.SIGNAL('settingChanged'), self, self.setting, value)
+ # this is emitted by setValue, so ignore onModified doing this
+ if not self.ignorechange:
+ self.emit(qt4.SIGNAL('settingChanged'), self, self.setting, value)
def onModified(self, mod):
"""called when the setting is changed remotely"""
+ self.ignorechange = True
self.setValue( self.setting.val )
+ self.ignorechange = False
class Bool(qt4.QCheckBox):
"""A check box for changing a bool setting."""
@@ -285,6 +260,7 @@
def __init__(self, setting, parent):
qt4.QCheckBox.__init__(self, parent)
+ self.ignorechange = False
self.setting = setting
self.setChecked(setting.val)
@@ -299,11 +275,15 @@
def slotToggled(self, state):
"""Emitted when checkbox toggled."""
- self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, state )
+ # this is emitted by setChecked, so ignore onModified doing this
+ if not self.ignorechange:
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, state )
def onModified(self, mod):
"""called when the setting is changed remotely"""
+ self.ignorechange = True
self.setChecked( self.setting.val )
+ self.ignorechange = False
class BoolSwitch(Bool):
"""Bool for switching off/on other settings."""
@@ -374,6 +354,10 @@
if setting.readonly:
self.setEnabled(False)
+ # make completion case sensitive (to help fix case typos)
+ if self.completer():
+ self.completer().setCaseSensitivity(qt4.Qt.CaseSensitive)
+
def focusOutEvent(self, *args):
"""Allows us to check the contents of the widget."""
qt4.QComboBox.focusOutEvent(self, *args)
@@ -386,10 +370,7 @@
try:
val = self.setting.fromText(text)
styleClear(self)
-
- # value has changed
- if self.setting.val != val:
- self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
except setting.InvalidType:
styleError(self)
@@ -431,10 +412,7 @@
try:
val = self.setting.fromText(text)
styleClear(self)
-
- # value has changed
- if self.setting.val != val:
- self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
except setting.InvalidType:
styleError(self)
@@ -448,7 +426,7 @@
# used to remove non-numerics from the string
# we also remove X/ from X/num
- stripnumre = re.compile(r"[0-9]*/|[^0-9.]")
+ stripnumre = re.compile(r"[0-9]*/|[^0-9.,]")
# remove spaces
stripspcre = re.compile(r"\s")
@@ -519,7 +497,7 @@
'''Initialise with blank list, then populate with sensible units.'''
Choice.__init__(self, setting, True, DistancePt.points, parent)
-class Dataset(Choice):
+class Dataset(qt4.QWidget):
"""Allow the user to choose between the possible datasets."""
def __init__(self, setting, document, dimensions, datatype, parent):
@@ -529,13 +507,33 @@
Changes on the document refresh the list of datasets."""
- Choice.__init__(self, setting, True, [], parent)
+ qt4.QWidget.__init__(self, parent)
+
+ self.choice = Choice(setting, True, [], None)
+ self.connect( self.choice, qt4.SIGNAL("settingChanged"),
+ self.slotSettingChanged )
+
+ b = self.button = DotDotButton(tooltip="Select using dataset browser")
+ self.connect(b, qt4.SIGNAL("toggled(bool)"),
+ self.slotButtonToggled)
+
self.document = document
self.dimensions = dimensions
self.datatype = datatype
self.lastdatasets = None
self._populateEntries()
- self.connect(document, qt4.SIGNAL('sigModified'), self.slotModified)
+ self.connect(document, qt4.SIGNAL("sigModified"), self.slotModified)
+
+ layout = qt4.QHBoxLayout()
+ layout.setSpacing(0)
+ layout.setMargin(0)
+ layout.addWidget(self.choice)
+ layout.addWidget(b)
+ self.setLayout(layout)
+
+ def slotSettingChanged(self, *args):
+ """Reemit setting changed signal if combo box changes."""
+ self.emit( qt4.SIGNAL("settingChanged"), *args )
def _populateEntries(self):
"""Put the list of datasets into the combobox."""
@@ -548,67 +546,69 @@
datasets.sort()
if datasets != self.lastdatasets:
- utils.populateCombo(self, datasets)
+ utils.populateCombo(self.choice, datasets)
self.lastdatasets = datasets
def slotModified(self, modified):
"""Update the list of datasets if the document is modified."""
self._populateEntries()
-class DatasetOrString(qt4.QWidget):
+ def slotButtonToggled(self, on):
+ """Bring up list of datasets."""
+ if on:
+ from veusz.qtwidgets.datasetbrowser import DatasetBrowserPopup
+ d = DatasetBrowserPopup(self.document,
+ unicode(self.choice.currentText()),
+ self.button,
+ filterdims=set((self.dimensions,)),
+ filterdtype=set((self.datatype,)) )
+ self.connect(d, qt4.SIGNAL("closing"), self.boxClosing)
+ self.connect(d, qt4.SIGNAL("newdataset"), self.newDataset)
+ d.show()
+
+ def boxClosing(self):
+ """Called when the popup edit box closes."""
+ self.button.setChecked(False)
+
+ def newDataset(self, dsname):
+ """New dataset selected."""
+ self.emit( qt4.SIGNAL("settingChanged"), self,
+ self.choice.setting, dsname )
+
+class DatasetOrString(Dataset):
"""Allow use to choose a dataset or enter some text."""
def __init__(self, setting, document, dimensions, datatype, parent):
- qt4.QWidget.__init__(self, parent)
- self.datachoose = Dataset(setting, document, dimensions, datatype,
- None)
-
- b = self.button = qt4.QPushButton('..')
- b.setFlat(True)
- b.setSizePolicy(qt4.QSizePolicy.Maximum, qt4.QSizePolicy.Maximum)
- b.setMaximumHeight(self.datachoose.height())
- b.setMaximumWidth(16)
- b.setCheckable(True)
-
- layout = qt4.QHBoxLayout()
- self.setLayout(layout)
- layout.setSpacing(0)
- layout.setMargin(0)
- layout.addWidget(self.datachoose)
- layout.addWidget(b)
+ Dataset.__init__(self, setting, document, dimensions, datatype, parent)
- self.connect(b, qt4.SIGNAL('toggled(bool)'),
- self.buttonToggled)
- self.connect(self.datachoose, qt4.SIGNAL('settingChanged'),
- self.slotSettingChanged)
+ b = self.textbutton = DotDotButton()
+ b.setCheckable(True)
+ self.layout().addWidget(b)
+ self.connect(b, qt4.SIGNAL('toggled(bool)'), self.textButtonToggled)
- def slotSettingChanged(self, *args):
- """When datachoose changes, inform any listeners."""
- self.emit( qt4.SIGNAL('settingChanged'), *args )
-
- def buttonToggled(self, on):
+ def textButtonToggled(self, on):
"""Button is pressed to bring popup up / down."""
# if button is down and there's no existing popup, bring up a new one
if on:
- e = _EditBox( unicode(self.datachoose.currentText()),
- self.datachoose.setting.readonly, self.button)
+ e = _EditBox( unicode(self.choice.currentText()),
+ self.choice.setting.readonly, self.textbutton)
# we get notified with text when the popup closes
- self.connect(e, qt4.SIGNAL('closing'), self.boxClosing)
+ self.connect(e, qt4.SIGNAL("closing"), self.textBoxClosing)
e.show()
- def boxClosing(self, text):
+ def textBoxClosing(self, text):
"""Called when the popup edit box closes."""
+ self.textbutton.setChecked(False)
+
# update the text if we can
- if not self.datachoose.setting.readonly:
- self.datachoose.setEditText(text)
- self.datachoose.setFocus()
+ if not self.choice.setting.readonly:
+ self.choice.setEditText(text)
+ self.choice.setFocus()
self.parentWidget().setFocus()
- self.datachoose.setFocus()
-
- self.button.setChecked(False)
+ self.choice.setFocus()
class FillStyle(Choice):
"""For choosing between fill styles."""
@@ -735,6 +735,7 @@
# import later for dependency issues
import veusz.setting.collections
+ import veusz.document
icons = []
size = cls.size
@@ -745,12 +746,15 @@
for lstyle in cls._lines:
pix = qt4.QPixmap(*size)
pix.fill()
+
+ ph = veusz.document.PaintHelper( (1, 1) )
+
painter = qt4.QPainter(pix)
painter.setRenderHint(qt4.QPainter.Antialiasing)
setn.get('style').set(lstyle)
- painter.setPen( setn.makeQPen(painter) )
+ painter.setPen( setn.makeQPen(ph) )
painter.drawLine( int(size[0]*0.1), size[1]/2,
int(size[0]*0.9), size[1]/2 )
painter.end()
@@ -843,19 +847,14 @@
if col.isValid():
# change setting
val = unicode( col.name() )
- if self.setting.val != val:
- self.emit( qt4.SIGNAL('settingChanged'), self,
- self.setting, val)
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
def slotActivated(self, val):
"""A different value is selected."""
text = unicode(self.combo.currentText())
val = self.setting.fromText(text)
-
- # value has changed
- if self.setting.val != val:
- self.emit(qt4.SIGNAL('settingChanged'), self, self.setting, val)
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
def setColor(self, color):
"""Update control with color given."""
@@ -894,8 +893,8 @@
"""Update list of axes."""
self._populateEntries()
-class Image(WidgetSelector):
- """Choose an image."""
+class WidgetChoice(WidgetSelector):
+ """Choose a widget."""
def __init__(self, setting, document, parent):
"""Initialise and populate combobox."""
@@ -904,12 +903,12 @@
self._populateEntries()
def _populateEntries(self):
- """Build up a list of images for combobox."""
+ """Build up a list of widgets for combobox."""
- images = self.setting.getImageList()
+ widgets = self.setting.getWidgetList()
# we only need the list of names
- names = images.keys()
+ names = widgets.keys()
names.sort()
utils.populateCombo(self, names)
@@ -1483,12 +1482,9 @@
self.edit.setText( setting.toText() )
layout.addWidget(self.edit)
- # get a sensible shape for the button - yawn
- b = self.button = qt4.QPushButton('..')
- b.setFlat(True)
+ b = self.button = DotDotButton(checkable=False,
+ tooltip="Browse for file")
layout.addWidget(b)
- b.setSizePolicy(qt4.QSizePolicy.Maximum, qt4.QSizePolicy.Maximum)
- b.setMaximumWidth(16)
# connect up signals
self.connect(self.edit, qt4.SIGNAL('editingFinished()'),
@@ -1524,9 +1520,7 @@
if filename:
val = unicode(filename)
- if self.setting.val != val:
- self.emit( qt4.SIGNAL('settingChanged'), self, self.setting,
- val )
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
def validateAndSet(self):
"""Check the text is a valid setting and update it."""
@@ -1535,11 +1529,7 @@
try:
val = self.setting.fromText(text)
styleClear(self.edit)
-
- # value has changed
- if self.setting.val != val:
- self.emit( qt4.SIGNAL('settingChanged'), self, self.setting,
- val )
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val )
except setting.InvalidType:
styleError(self.edit)
@@ -1579,8 +1569,7 @@
def slotActivated(self, val):
"""Update setting if a different item is chosen."""
newval = unicode(self.currentText())
- if self.setting.val != newval:
- self.emit(qt4.SIGNAL('settingChanged'), self, self.setting, newval)
+ self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, newval )
def onModified(self, mod):
"""Make control reflect chosen setting."""
@@ -1605,3 +1594,73 @@
cls._icons = []
for errstyle in cls._errorstyles:
cls._icons.append( utils.getIcon('error_%s' % errstyle) )
+
+class Colormap(Choice):
+ """Give the user a preview of colormaps.
+
+ Based on Choice to make life easier
+ """
+
+ _icons = {}
+
+ size = (32, 12)
+
+ def __init__(self, setn, document, parent):
+ names = sorted(document.colormaps.keys())
+
+ icons = Colormap._generateIcons(document, names)
+ setting.controls.Choice.__init__(self, setn, True,
+ names, parent,
+ icons=icons)
+ self.setIconSize( qt4.QSize(*self.size) )
+
+ @classmethod
+ def _generateIcons(kls, document, names):
+ """Generate a list of icons for drop down menu."""
+
+ # create a fake dataset smoothly varying from 0 to size[0]-1
+ size = kls.size
+ fakedataset = N.fromfunction(lambda x, y: y, (size[1], size[0]))
+
+ # keep track of icons to return
+ retn = []
+
+ # iterate over colour maps
+ for name in names:
+ val = document.colormaps.get(name, None)
+ if val in kls._icons:
+ icon = kls._icons[val]
+ else:
+ if val is None:
+ # empty icon
+ pixmap = qt4.QPixmap(*size)
+ pixmap.fill(qt4.Qt.transparent)
+ else:
+ # generate icon
+ image = utils.applyColorMap(val, 'linear',
+ fakedataset,
+ 0., size[0]-1., 0)
+ pixmap = qt4.QPixmap.fromImage(image)
+ icon = qt4.QIcon(pixmap)
+ kls._icons[val] = icon
+ retn.append(icon)
+ return retn
+
+class AxisBound(Choice):
+ """Control for setting bounds of axis.
+
+ This is to allow dates etc
+ """
+
+ def __init__(self, setting, *args):
+ Choice.__init__(self, setting, True, ['Auto'], *args)
+
+ modesetn = setting.parent.get('mode')
+ modesetn.setOnModified(self.modeChange)
+
+ def modeChange(self, changed):
+ """Called if the mode of the axis changes.
+ Re-set text as float or date."""
+
+ if unicode(self.currentText()).lower() != 'auto':
+ self.setEditText( self.setting.toText() )
diff -Nru veusz-1.10/setting/__init__.py veusz-1.14/setting/__init__.py
--- veusz-1.10/setting/__init__.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/setting/__init__.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: __init__.py 1023 2009-07-11 17:52:27Z jeremysanders $
-
from settingdb import *
from reference import Reference
from setting import *
diff -Nru veusz-1.10/setting/reference.py veusz-1.14/setting/reference.py
--- veusz-1.10/setting/reference.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/setting/reference.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: reference.py 1056 2009-09-05 16:51:59Z jeremysanders $
-
class Reference(object):
"""A value a setting can have to point to another setting.
diff -Nru veusz-1.10/setting/settingdb.py veusz-1.14/setting/settingdb.py
--- veusz-1.10/setting/settingdb.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/setting/settingdb.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,12 +16,9 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: settingdb.py 1320 2010-07-09 21:18:52Z jeremysanders $
-
"""A database for default values of settings."""
import sys
-import atexit
import veusz.qtall as qt4
@@ -29,14 +26,16 @@
defaultValues = {
# export options
'export_DPI': 100,
+ 'export_DPI_PDF': 150,
'export_color': True,
'export_antialias': True,
'export_quality': 85,
'export_background': '#ffffff00',
# plot options
- 'plot_updateinterval': 500,
+ 'plot_updatepolicy': -1, # update on document changed
'plot_antialias': True,
+ 'plot_numthreads': 2,
# recent files list
'main_recentfiles': [],
@@ -56,8 +55,14 @@
# further ui options
'toolbar_size': 24,
+ # if set to true, do UI formatting in US/English
+ 'ui_english': False,
+
# use cwd as starting directory
'dirname_usecwd': False,
+
+ # ask tutorial before?
+ 'ask_tutorial': False,
}
class _SettingDB(object):
@@ -144,7 +149,7 @@
def writeSettings(self):
"""Write the settings using QSettings.
- This is called by the atexit handler below
+ This is called by the mainwindow on close
"""
s = qt4.QSettings(self.domain, self.product)
@@ -154,6 +159,11 @@
for key, value in self.database.iteritems():
cleankey = key.replace('/', self.sepchars)
cleankeys.append(cleankey)
+
+ # repr doesn't work on QStrings
+ if isinstance(value, qt4.QString):
+ value = unicode(value)
+
s.setValue(cleankey, qt4.QVariant(repr(value)))
# now remove all the values which have been removed
@@ -189,5 +199,16 @@
# (e.g. disable safe mode)
transient_settings = {}
-# write out settings at exit
-atexit.register(settingdb.writeSettings)
+def updateUILocale():
+ """Update locale to one given in preferences."""
+ global uilocale
+
+ if settingdb['ui_english']:
+ uilocale = qt4.QLocale.c()
+ else:
+ uilocale = qt4.QLocale.system()
+ uilocale.setNumberOptions(qt4.QLocale.OmitGroupSeparator)
+
+ qt4.QLocale.setDefault(uilocale)
+
+updateUILocale()
diff -Nru veusz-1.10/setting/setting.py veusz-1.14/setting/setting.py
--- veusz-1.10/setting/setting.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/setting/setting.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: setting.py 1469 2010-12-08 22:15:00Z jeremysanders $
-
"""Module for holding setting values.
e.g.
@@ -34,7 +32,7 @@
import veusz.qtall as qt4
import controls
-from settingdb import settingdb
+from settingdb import settingdb, uilocale
from reference import Reference
import veusz.utils as utils
@@ -44,6 +42,11 @@
pass
class Setting(object):
+ """A class to store a value with a particular type."""
+
+ # differentiate widgets, settings and setting
+ nodetype = 'setting'
+
typename = 'setting'
def __init__(self, name, value, descr='', usertext='',
@@ -55,6 +58,7 @@
descr: description of the setting
usertext: name of setting for user
formatting: whether setting applies to formatting
+ hidden: hide widget from user
"""
self.readonly = False
self.parent = None
@@ -62,6 +66,7 @@
self.descr = descr
self.usertext = usertext
self.formatting = formatting
+ self.hidden = hidden
self.default = value
self.onmodified = qt4.QObject()
self._val = None
@@ -92,6 +97,7 @@
opt['descr'] = self.descr
opt['usertext'] = self.usertext
opt['formatting'] = self.formatting
+ opt['hidden'] = self.hidden
obj = self.__class__(*args, **opt)
obj.readonly = self.readonly
@@ -445,7 +451,6 @@
def makeControl(self, *args):
return controls.Bool(self, *args)
-
# Storing integers
class Int(Setting):
"""Integer settings."""
@@ -481,21 +486,30 @@
raise InvalidType
def toText(self):
- return str(self.val)
+ return unicode( uilocale.toString(self.val) )
def fromText(self, text):
- try:
- i = int(text)
- if i >= self.minval and i <= self.maxval:
- return i
- else:
- raise InvalidType, 'Out of range allowed'
- except ValueError:
- raise InvalidType
+ i, ok = uilocale.toLongLong(text)
+ if not ok:
+ raise ValueError
+
+ if i >= self.minval and i <= self.maxval:
+ return i
+ else:
+ raise InvalidType, 'Out of range allowed'
def makeControl(self, *args):
return controls.Int(self, *args)
+def _finiteRangeFloat(f, minval=-1e300, maxval=1e300):
+ """Return a finite float in range or raise exception otherwise."""
+ f = float(f)
+ if not N.isfinite(f):
+ raise InvalidType, 'Finite values only allowed'
+ if f < minval or f > maxval:
+ raise InvalidType, 'Out of range allowed'
+ return f
+
# for storing floats
class Float(Setting):
"""Float settings."""
@@ -524,19 +538,16 @@
def convertTo(self, val):
if isinstance(val, int) or isinstance(val, float):
- if val >= self.minval and val <= self.maxval:
- return float(val)
- else:
- raise InvalidType, 'Out of range allowed'
+ return _finiteRangeFloat(val,
+ minval=self.minval, maxval=self.maxval)
raise InvalidType
def toText(self):
- return str(self.val)
+ return unicode(uilocale.toString(self.val))
def fromText(self, text):
- try:
- f = float(text)
- except ValueError:
+ f, ok = uilocale.toDouble(text)
+ if not ok:
# try to evaluate
f = self.safeEvalHelper(text)
return self.convertTo(f)
@@ -551,7 +562,7 @@
def convertTo(self, val):
if type(val) in (int, float):
- return float(val)
+ return _finiteRangeFloat(val)
elif isinstance(val, basestring) and val.strip().lower() == 'auto':
return None
else:
@@ -564,18 +575,20 @@
return val
def toText(self):
- if self.val is None:
+ if self.val is None or (isinstance(self.val, basestring) and
+ self.val.lower() == 'auto'):
return 'Auto'
else:
- return str(self.val)
+ return unicode(uilocale.toString(self.val))
def fromText(self, text):
if text.strip().lower() == 'auto':
return 'Auto'
else:
- try:
- return float(text)
- except ValueError:
+ f, ok = uilocale.toDouble(text)
+ if ok:
+ return self.convertTo(f)
+ else:
# try to evaluate
return self.safeEvalHelper(text)
@@ -602,19 +615,20 @@
return val
def toText(self):
- if self.val is None:
+ if self.val is None or (isinstance(self.val, basestring) and
+ self.val.lower() == 'auto'):
return 'Auto'
else:
- return str(self.val)
+ return unicode( uilocale.toString(self.val) )
def fromText(self, text):
if text.strip().lower() == 'auto':
return 'Auto'
else:
- try:
- return int(text)
- except ValueError:
+ i, ok = uilocale.toLongLong(text)
+ if not ok:
raise InvalidType
+ return i
def makeControl(self, *args):
return controls.Choice(self, True, ['Auto'], *args)
@@ -622,128 +636,124 @@
# these are functions used by the distance setting below.
# they don't work as class methods
-def _calcPixPerPt(painter):
- """Calculate the numbers of pixels per point for the painter.
-
- This is stored in the variable veusz_pixperpt."""
-
- dpi = painter.device().logicalDpiY()
- if dpi == 0: dpi = 72
- painter.veusz_pixperpt = dpi / 72.
-
def _distPhys(match, painter, mult):
"""Convert a physical unit measure in multiples of points."""
-
- if not hasattr(painter, 'veusz_pixperpt'):
- _calcPixPerPt(painter)
-
- return (painter.veusz_pixperpt * mult *
- float(match.group(1)) * painter.veusz_scaling)
+ return (painter.pixperpt * mult *
+ float(match.group(1)) * painter.scaling)
def _distInvPhys(pixdist, painter, mult, unit):
"""Convert number of pixels into physical distance."""
- dist = pixdist / (mult * painter.veusz_pixperpt *
- painter.veusz_scaling)
+ dist = pixdist / (mult * painter.pixperpt * painter.scaling)
return "%.3g%s" % (dist, unit)
-def _distPerc(match, painter, maxsize):
+def _distPerc(match, painter):
"""Convert from a percentage of maxsize."""
- return maxsize * 0.01 * float(match.group(1))
+ return painter.maxsize * 0.01 * float(match.group(1))
-def _distInvPerc(pixdist, painter, maxsize):
+def _distInvPerc(pixdist, painter):
"""Convert pixel distance into percentage."""
- perc = pixdist * 100. / maxsize
+ perc = pixdist * 100. / painter.maxsize
return "%.3g%%" % perc
-def _distFrac(match, painter, maxsize):
+def _distFrac(match, painter):
"""Convert from a fraction a/b of maxsize."""
- return maxsize * float(match.group(1)) / float(match.group(2))
+ try:
+ return painter.maxsize * float(match.group(1))/float(match.group(4))
+ except ZeroDivisionError:
+ return 0.
-def _distRatio(match, painter, maxsize):
+def _distRatio(match, painter):
"""Convert from a simple 0.xx ratio of maxsize."""
# if it's greater than 1 then assume it's a point measurement
if float(match.group(1)) > 1.:
return _distPhys(match, painter, 1)
- return maxsize * float(match.group(1))
+ return painter.maxsize * float(match.group(1))
+
+# regular expression to match distances
+distre_expr = r'''^
+ [ ]* # optional whitespace
+
+ (\.?[0-9]+|[0-9]+\.[0-9]*) # a floating point number
+
+ [ ]* # whitespace
+
+ (cm|pt|mm|inch|in|"|%|| # ( unit, no unit,
+ (?P/) ) # or / )
+
+ (?(slash)[ ]* # if it was a slash, match any whitespace
+ (\.?[0-9]+|[0-9]+\.[0-9]*)) # and match following fp number
+
+ [ ]* # optional whitespace
+$'''
class Distance(Setting):
"""A veusz distance measure, e.g. 1pt or 3%."""
typename = 'distance'
- # mappings from regular expressions to function to convert distance
- # the recipient function takes regexp match,
- # painter and maximum size of frac
-
- # the second function is to do the inverse calculation
- distregexp = [
- # cm distance
- ( re.compile('^([0-9\.]+) *cm$'),
- lambda match, painter, t:
- _distPhys(match, painter, 28.452756),
- lambda pixdist, painter, t:
- _distInvPhys(pixdist, painter, 28.452756, 'cm') ),
-
- # point size
- ( re.compile('^([0-9\.]+) *pt$'),
- lambda match, painter, t:
- _distPhys(match, painter, 1.),
- lambda pixdist, painter, t:
- _distInvPhys(pixdist, painter, 1., 'pt') ),
-
- # mm distance
- ( re.compile('^([0-9\.]+) *mm$'),
- lambda match, painter, t:
- _distPhys(match, painter, 2.8452756),
- lambda pixdist, painter, t:
- _distInvPhys(pixdist, painter, 2.8452756, 'mm') ),
-
- # inch distance
- ( re.compile('^([0-9\.]+) *(inch|in|")$'),
- lambda match, painter, t:
- _distPhys(match, painter, 72.27),
- lambda pixdist, painter, t:
- _distInvPhys(pixdist, painter, 72.27, 'in') ),
-
- # plain fraction
- ( re.compile('^([0-9\.]+)$'),
- _distRatio,
- _distInvPerc ),
-
- # percentage
- ( re.compile('^([0-9\.]+) *%$'),
- _distPerc,
- _distInvPerc ),
-
- # fractional
- ( re.compile('^([0-9\.]+) */ *([0-9\.]+)$'),
- _distFrac,
- _distInvPerc ),
- ]
+ # match a distance
+ distre = re.compile(distre_expr, re.VERBOSE)
+
+ # functions to convert from unit values to pixels
+ unit_func = {
+ 'cm': lambda match, painter:
+ _distPhys(match, painter, 28.452756),
+ 'pt': lambda match, painter:
+ _distPhys(match, painter, 1.),
+ 'mm': lambda match, painter:
+ _distPhys(match, painter, 2.8452756),
+ 'in': lambda match, painter:
+ _distPhys(match, painter, 72.27),
+ 'inch': lambda match, painter:
+ _distPhys(match, painter, 72.27),
+ '"': lambda match, painter:
+ _distPhys(match, painter, 72.27),
+ '%': _distPerc,
+ '/': _distFrac,
+ '': _distRatio
+ }
+
+ # inverse functions for converting pixels to units
+ inv_unit_func = {
+ 'cm': lambda match, painter:
+ _distInvPhys(match, painter, 28.452756, 'cm'),
+ 'pt': lambda match, painter:
+ _distInvPhys(match, painter, 1., 'pt'),
+ 'mm': lambda match, painter:
+ _distInvPhys(match, painter, 2.8452756, 'mm'),
+ 'in': lambda match, painter:
+ _distInvPhys(match, painter, 72.27, 'in'),
+ 'inch': lambda match, painter:
+ _distInvPhys(match, painter, 72.27, 'in'),
+ '"': lambda match, painter:
+ _distInvPhys(match, painter, 72.27, 'in'),
+ '%': _distInvPerc,
+ '/': _distInvPerc,
+ '': _distInvPerc
+ }
@classmethod
def isDist(kls, dist):
"""Is the text a valid distance measure?"""
- dist = dist.strip()
- for reg, fn, fninv in kls.distregexp:
- if reg.match(dist):
- return True
-
- return False
+ return kls.distre.match(dist) is not None
def convertTo(self, val):
- if self.isDist(val):
+ if self.distre.match(val) is not None:
return val
else:
raise InvalidType
def toText(self):
- return self.val
+ # convert decimal point to display locale
+ return self.val.replace('.', qt4.QString(uilocale.decimalPoint()))
def fromText(self, text):
+ # convert decimal point from display locale
+ text = text.replace(qt4.QString(uilocale.decimalPoint()), '.')
+
if self.isDist(text):
return text
else:
@@ -753,7 +763,7 @@
return controls.Distance(self, *args)
@classmethod
- def convertDistance(kls, painter, distance):
+ def convertDistance(kls, painter, dist):
'''Convert a distance to plotter units.
dist: eg 0.1 (fraction), 10% (percentage), 1/10 (fraction),
@@ -762,26 +772,12 @@
painter: painter to get metrics to convert physical sizes
'''
- # we set a scaling variable in the painter if it's not set
- if not hasattr(painter, 'veusz_scaling'):
- painter.veusz_scaling = 1.
-
- # work out maximum size
- try:
- maxsize = max( *painter.veusz_page_size )
- except AttributeError:
- w = painter.window()
- maxsize = max(w.width(), w.height())
-
- dist = distance.strip()
-
- # compare string against each regexp
- for reg, fn, fninv in kls.distregexp:
- m = reg.match(dist)
-
- # if there's a match, then call the appropriate conversion fn
- if m:
- return fn(m, painter, maxsize)
+ # match distance against expression
+ m = kls.distre.match(dist)
+ if m is not None:
+ # lookup function to call to do conversion
+ func = kls.unit_func[m.group(2)]
+ return func(m, painter)
# none of the regexps match
raise ValueError( "Cannot convert distance in form '%s'" %
@@ -789,37 +785,26 @@
def convert(self, painter):
"""Convert this setting's distance as above"""
-
return self.convertDistance(painter, self.val)
def convertPts(self, painter):
"""Get the distance in points."""
- if not hasattr(painter, 'veusz_pixperpt'):
- _calcPixPerPt(painter)
-
- return self.convert(painter) / painter.veusz_pixperpt
+ return self.convert(painter) / painter.pixperpt
def convertInverse(self, distpix, painter):
"""Convert distance in pixels into units of this distance.
-
- Not that great coding as takes "painter" containing veusz
- scaling parameters. Should be cleaned up.
"""
- # identify units and get inverse mapping
- v = self.val
- inversefn = None
- for reg, fn, fninv in self.distregexp:
- if reg.match(v):
- inversefn = fninv
- break
- if not inversefn:
- inversefn = self.distregexp[0][2]
-
- maxsize = max( *painter.veusz_page_size )
+ m = self.distre.match(self.val)
+ if m is not None:
+ # if it matches convert back
+ inversefn = self.inv_unit_func[m.group(2)]
+ else:
+ # otherwise force unit
+ inversefn = self.inv_unit_func['cm']
# do inverse mapping
- return inversefn(distpix, painter, maxsize)
+ return inversefn(distpix, painter)
class DistancePt(Distance):
"""For a distance in points."""
@@ -832,7 +817,7 @@
typename = 'distance-or-auto'
- distregexp = Distance.distregexp + [(re.compile('^Auto$'), None, None)]
+ distre = re.compile( distre_expr + r'|^Auto$', re.VERBOSE )
def isAuto(self):
return self.val == 'Auto'
@@ -935,7 +920,7 @@
keys = self.val.keys()
keys.sort()
- text = ['%s = %g' % (key, self.val[key]) for key in keys]
+ text = ['%s = %s' % (k, uilocale.toString(self.val[k])) for k in keys]
return '\n'.join(text)
def fromText(self, text):
@@ -954,9 +939,8 @@
if len(p) != 2:
raise InvalidType
- try:
- v = float(p[1])
- except ValueError:
+ v, ok = uilocale.toDouble(p[1])
+ if not ok:
raise InvalidType
out[ p[0].strip() ] = v
@@ -970,8 +954,6 @@
typename = 'float-list'
- list_re = re.compile(r'[\t\n, ]+')
-
def convertTo(self, val):
if type(val) not in (list, tuple):
raise InvalidType
@@ -987,17 +969,28 @@
def toText(self):
"""Make a string a, b, c."""
- return ', '.join( [str(i) for i in self.val] )
+ # can't use the comma for splitting if used as a decimal point
+
+ join = ', '
+ if uilocale.decimalPoint() == qt4.QChar(','):
+ join = '; '
+ return join.join( [unicode(uilocale.toString(x)) for x in self.val] )
def fromText(self, text):
"""Convert from a, b, c or a b c."""
+ # don't use commas if it is the decimal separator
+ splitre = r'[\t\n, ]+'
+ if uilocale.decimalPoint() == qt4.QChar(','):
+ splitre = r'[\t\n; ]+'
+
out = []
- for x in self.list_re.split(text.strip()):
+ for x in re.split(splitre, text.strip()):
if x:
- try:
- out.append(float(x))
- except ValueError:
+ f, ok = uilocale.toDouble(x)
+ if ok:
+ out.append(f)
+ else:
out.append( self.safeEvalHelper(x) )
return out
@@ -1031,7 +1024,7 @@
{'relativetoparent': self.relativetoparent,
'allowedwidgets': self.allowedwidgets})
- def getWidget(self, val = None):
+ def getReferredWidget(self, val = None):
"""Get the widget referred to. We double-check here to make sure
it's the one.
@@ -1194,10 +1187,6 @@
typename = 'dataset-or-floatlist'
- # a list of numbers separated by spaces or tabs
- # (requires number at end of line)
- numbers_re = re.compile(r'^([-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?[ \t,$]+)+$')
-
def convertTo(self, val):
"""Check is a string (dataset name) or a list of floats (numbers).
@@ -1217,20 +1206,31 @@
if isinstance(self.val, basestring):
return self.val
else:
- return ', '.join( [str(x) for x in self.val] )
+ # join based on , or ; depending on decimal point
+ join = ', '
+ if uilocale.decimalPoint() == qt4.QChar(','):
+ join = '; '
+ return join.join( [ unicode(uilocale.toString(x))
+ for x in self.val ] )
def fromText(self, text):
- text = text.strip()
- try:
- out = []
- for x in FloatList.list_re.split(text):
- try:
- out.append(float(x))
- except ValueError:
- out.append( self.safeEvalHelper(x) )
- return out
- except InvalidType:
- return text
+ """Convert from text."""
+
+ # split based on , or ; depending on decimal point
+ splitre = r'[\t\n, ]+'
+ if uilocale.decimalPoint() == qt4.QChar(','):
+ splitre = r'[\t\n; ]+'
+
+ out = []
+ for x in re.split(splitre, text.strip()):
+ if x:
+ f, ok = uilocale.toDouble(x)
+ if ok:
+ out.append(f)
+ else:
+ # fail conversion, so exit with text
+ return text
+ return out
def getFloatArray(self, doc):
"""Get a numpy of values or None."""
@@ -1250,6 +1250,10 @@
return (isinstance(self.val, basestring) and
doc.data.get(self.val))
+ def isEmpty(self):
+ """Is this unset?"""
+ return self.val == []
+
def getData(self, doc):
"""Return veusz dataset"""
if isinstance(self.val, basestring):
@@ -1437,16 +1441,25 @@
"""Allows user to choose an axis or enter a name."""
return controls.Axis(self, self.getDocument(), self.direction, *args)
-class Image(Str):
- """Hold the name of a child image."""
+class WidgetChoice(Str):
+ """Hold the name of a child widget."""
+
+ typename = 'widget-choice'
+
+ def __init__(self, name, val, widgettypes={}, **args):
+ """Choose widgets from (named) type given."""
+ Setting.__init__(self, name, val, **args)
+ self.widgettypes = widgettypes
- typename = 'image-widget'
+ def copy(self):
+ """Make a copy of the setting."""
+ return self._copyHelper((), (),
+ {'widgettypes': self.widgettypes})
- @staticmethod
- def buildImageList(level, widget, outdict):
- """A recursive helper to build up a list of possible image widgets.
+ def buildWidgetList(self, level, widget, outdict):
+ """A recursive helper to build up a list of possible widgets.
- This iterates over widget's children, and adds Image widgets as tuples
+ This iterates over widget's children, and adds widgets as tuples
to outdict using outdict[name] = (widget, level)
Lower level images of the same name outweigh other images further down
@@ -1454,14 +1467,14 @@
"""
for child in widget.children:
- if child.typename == 'image':
+ if child.typename in self.widgettypes:
if (child.name not in outdict) or (outdict[child.name][1]>level):
outdict[child.name] = (child, level)
else:
- Image.buildImageList(level+1, child, outdict)
+ self.buildWidgetList(level+1, child, outdict)
- def getImageList(self):
- """Return a dict of valid image names and the corresponding objects."""
+ def getWidgetList(self):
+ """Return a dict of valid widget names and the corresponding objects."""
# find widget which contains setting
widget = self.parent
@@ -1472,10 +1485,10 @@
if widget is not None:
widget = widget.parent
- # get list of images from recursive find
+ # get list of widgets from recursive find
images = {}
if widget is not None:
- Image.buildImageList(0, widget, images)
+ self.buildWidgetList(0, widget, images)
# turn (object, level) pairs into object
outdict = {}
@@ -1484,15 +1497,15 @@
return outdict
- def findImage(self):
+ def findWidget(self):
"""Find the image corresponding to this setting.
Returns Image object if succeeds or None if fails
"""
- images = self.getImageList()
+ widgets = self.getWidgetList()
try:
- return images[self.get()]
+ return widgets[self.get()]
except KeyError:
return None
@@ -1502,7 +1515,7 @@
def makeControl(self, *args):
"""Allows user to choose an image widget or enter a name."""
- return controls.Image(self, self.getDocument(), *args)
+ return controls.WidgetChoice(self, self.getDocument(), *args)
class Marker(Choice):
"""Choose a marker type from one allowable."""
@@ -1687,6 +1700,7 @@
'boxfill',
'fillvert', 'fillhorz',
'linevert', 'linehorz',
+ 'linevertbar', 'linehorzbar'
)
controls.ErrorStyle._errorstyles = _errorstyles
@@ -1768,3 +1782,70 @@
def copy(self):
return self._copyHelper((), (), {'settingsfalse': self.sfalse,
'settingstrue': self.strue})
+
+class RotateInterval(Choice):
+ '''Rotate a label with intervals given.'''
+
+ def __init__(self, name, val, **args):
+ Choice.__init__(self, name,
+ ('-180', '-135', '-90', '-45',
+ '0', '45', '90', '135', '180'),
+ val, **args)
+
+ def convertTo(self, val):
+ """Store rotate angle."""
+ # backward compatibility with rotate option
+ # False: angle 0
+ # True: angle 90
+ if val == False:
+ val = '0'
+ elif val == True:
+ val = '90'
+ return Choice.convertTo(self, val)
+
+ def copy(self):
+ """Make a copy of the setting."""
+ return self._copyHelper((), (), {})
+
+class Colormap(Str):
+ """A setting to set the color map used in an image.
+ This is based on a Str rather than Choice as the list might
+ change later.
+ """
+
+ def makeControl(self, *args):
+ return controls.Colormap(self, self.getDocument(), *args)
+
+class AxisBound(FloatOrAuto):
+ """Axis bound - either numeric, Auto or date."""
+
+ typename = 'axis-bound'
+
+ def makeControl(self, *args):
+ return controls.AxisBound(self, *args)
+
+ def toText(self):
+ """Convert to text, taking into account mode of Axis.
+ Displays datetimes in date format if used
+ """
+
+ try:
+ mode = self.parent.mode
+ except AttributeError:
+ mode = None
+
+ v = self.val
+ if ( not isinstance(v, basestring) and v is not None and
+ mode == 'datetime' ):
+ return utils.dateFloatToString(v)
+
+ return FloatOrAuto.toText(self)
+
+ def fromText(self, txt):
+ """Convert from text, allowing datetimes."""
+
+ v = utils.dateStringToDate(txt)
+ if N.isfinite(v):
+ return v
+ else:
+ return FloatOrAuto.fromText(self, txt)
diff -Nru veusz-1.10/setting/settings.py veusz-1.14/setting/settings.py
--- veusz-1.10/setting/settings.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/setting/settings.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: settings.py 1325 2010-07-12 13:04:13Z jeremysanders $
-
"""Module for holding collections of settings."""
from reference import Reference
@@ -25,21 +23,36 @@
class Settings(object):
"""A class for holding collections of settings."""
- def __init__(self, name, descr = '', usertext='', pixmap=''):
- """A new Settings with a name."""
+ # differentiate widgets, settings and setting
+ nodetype = 'settings'
+
+ def __init__(self, name, descr = '', usertext='', pixmap='',
+ setnsmode='formatting'):
+ """A new Settings with a name.
+
+ name: name in hierarchy
+ descr: description (for user)
+ usertext: name for user of class
+ pixmap: pixmap to show in tab (if appropriate)
+ setnsmode: type of Settings class, one of
+ ('formatting', 'groupedsetting', 'widgetsettings', 'stylesheet')
+ """
self.__dict__['setdict'] = {}
self.name = name
self.descr = descr
- self.pixmap = pixmap
self.usertext = usertext
+ self.pixmap = pixmap
+ self.setnsmode = setnsmode
self.setnames = [] # a list of names
self.parent = None
def copy(self):
"""Make a copy of the settings and its subsettings."""
- s = Settings(self.name, descr=self.descr, usertext=self.usertext,
- pixmap=self.pixmap)
+
+ s = Settings(
+ self.name, descr=self.descr, usertext=self.usertext,
+ pixmap=self.pixmap, setnsmode=self.setnsmode )
for name in self.setnames:
s.add( self.setdict[name].copy() )
return s
@@ -66,6 +79,20 @@
return [self.setdict[n] for n in self.setnames
if isinstance(self.setdict[n], Settings)]
+ def getNames(self):
+ """Return list of names."""
+ return self.setnames
+
+ def getSettingNames(self):
+ """Get list of setting names."""
+ return [n for n in self.setnames
+ if not isinstance(self.setdict[n], Settings)]
+
+ def getSettingsNames(self):
+ """Get list of settings names."""
+ return [n for n in self.setnames
+ if isinstance(self.setdict[n], Settings)]
+
def isSetting(self, name):
"""Is the name a supported setting?"""
return name in self.setdict
@@ -234,5 +261,3 @@
setn.default = ref
except Reference.ResolveException:
pass
-
-
diff -Nru veusz-1.10/setting/stylesheet.py veusz-1.14/setting/stylesheet.py
--- veusz-1.10/setting/stylesheet.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/setting/stylesheet.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: stylesheet.py 1387 2010-08-29 15:24:57Z jeremysanders $
-
import sys
from settings import Settings
@@ -45,7 +43,7 @@
def __init__(self, **args):
"""Create the default settings."""
- Settings.__init__(self, 'StyleSheet', **args)
+ Settings.__init__(self, 'StyleSheet', setnsmode='stylesheet', **args)
self.pixmap = 'settings_stylesheet'
for subset in self.registeredsettings:
diff -Nru veusz-1.10/setup.py veusz-1.14/setup.py
--- veusz-1.10/setup.py 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/setup.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: setup.py 1285 2010-06-15 20:50:10Z jeremysanders $
-
"""
Veusz distutils setup script
see the file INSTALL for details on how to install Veusz
@@ -92,6 +90,14 @@
'scripts': ['scripts/veusz', 'scripts/veusz_listen']
}
+def findData(dirname, extns):
+ """Return tuple for directory name and list of file extensions for data."""
+ files = []
+ for extn in extns:
+ files += glob.glob(os.path.join(dirname, '*.'+extn))
+ files.sort()
+ return ( os.path.join('veusz', dirname), files )
+
setup(name = 'veusz',
version = version,
description = 'A scientific plotting package',
@@ -111,27 +117,29 @@
'veusz.dialogs': 'dialogs',
'veusz.document': 'document',
'veusz.helpers': 'helpers',
+ 'veusz.plugins': 'plugins',
+ 'veusz.qtwidgets': 'qtwidgets',
'veusz.setting': 'setting',
+ 'veusz.tests': 'tests',
'veusz.utils': 'utils',
'veusz.widgets': 'widgets',
'veusz.windows': 'windows',
- 'veusz.plugins': 'plugins',
- 'veusz.tests': 'tests' },
+ },
data_files = [ ('veusz', ['VERSION']),
- ('veusz/dialogs', glob.glob('dialogs/*.ui')),
- ('veusz/widgets/data', glob.glob('widgets/data/*.dat')),
- ('veusz/windows/icons',
- glob.glob('windows/icons/*.png')+
- glob.glob('windows/icons/*.svg')) ],
+ findData('dialogs', ('ui',)),
+ findData('windows/icons', ('png', 'svg')),
+ findData('examples', ('vsz', 'py', 'csv', 'dat')),
+ ],
packages = [ 'veusz',
'veusz.dialogs',
'veusz.document',
+ 'veusz.helpers',
+ 'veusz.plugins',
+ 'veusz.qtwidgets',
'veusz.setting',
'veusz.utils',
'veusz.widgets',
- 'veusz.helpers',
'veusz.windows',
- 'veusz.plugins',
],
ext_modules = [
@@ -148,6 +156,8 @@
'helpers/src/polylineclip.cpp',
'helpers/src/beziers.cpp',
'helpers/src/beziers_qtwrap.cpp',
+ 'helpers/src/recordpaintdevice.cpp',
+ 'helpers/src/recordpaintengine.cpp',
'helpers/src/qtloops.sip'],
language="c++",
include_dirs=['/helpers/src',
diff -Nru veusz-1.10/tests/check_all.sh veusz-1.14/tests/check_all.sh
--- veusz-1.10/tests/check_all.sh 2010-12-12 12:41:10.000000000 +0000
+++ veusz-1.14/tests/check_all.sh 1970-01-01 00:00:00.000000000 +0000
@@ -1,26 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2009 Jeremy S. Sanders
-# Email: Jeremy Sanders
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-##############################################################################
-
-# $Id: check_all.sh 964 2009-05-10 11:26:12Z jeremysanders $
-
-# run pychecker on all script files
-
-for f in `find . -name "*.py"`; do
- pychecker $f >> pychecker-out.txt
-done
diff -Nru veusz-1.10/tests/comparison/1dto2d.vsz.selftest veusz-1.14/tests/comparison/1dto2d.vsz.selftest
--- veusz-1.10/tests/comparison/1dto2d.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/1dto2d.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,92 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/autodetect.vsz.selftest veusz-1.14/tests/comparison/autodetect.vsz.selftest
--- veusz-1.10/tests/comparison/autodetect.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/autodetect.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,129 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/bar_labels.vsz.selftest veusz-1.14/tests/comparison/bar_labels.vsz.selftest
--- veusz-1.10/tests/comparison/bar_labels.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/bar_labels.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,165 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/barplots.vsz.selftest veusz-1.14/tests/comparison/barplots.vsz.selftest
--- veusz-1.10/tests/comparison/barplots.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/barplots.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,355 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/blockeddata.vsz.selftest veusz-1.14/tests/comparison/blockeddata.vsz.selftest
--- veusz-1.10/tests/comparison/blockeddata.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/blockeddata.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,79 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/boxplot.vsz.selftest veusz-1.14/tests/comparison/boxplot.vsz.selftest
--- veusz-1.10/tests/comparison/boxplot.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/boxplot.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,133 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/coloredpoints.vsz.selftest veusz-1.14/tests/comparison/coloredpoints.vsz.selftest
--- veusz-1.10/tests/comparison/coloredpoints.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/coloredpoints.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,590 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/contour.vsz.selftest veusz-1.14/tests/comparison/contour.vsz.selftest
--- veusz-1.10/tests/comparison/contour.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/contour.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,108 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/csv1.vsz.selftest veusz-1.14/tests/comparison/csv1.vsz.selftest
--- veusz-1.10/tests/comparison/csv1.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/csv1.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,83 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/csv_locale.vsz.selftest veusz-1.14/tests/comparison/csv_locale.vsz.selftest
--- veusz-1.10/tests/comparison/csv_locale.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/csv_locale.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,98 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/csv_missing.vsz.selftest veusz-1.14/tests/comparison/csv_missing.vsz.selftest
--- veusz-1.10/tests/comparison/csv_missing.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/csv_missing.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,106 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/custom.vsz.selftest veusz-1.14/tests/comparison/custom.vsz.selftest
--- veusz-1.10/tests/comparison/custom.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/custom.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,66 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/dataset_operations.vsz.selftest veusz-1.14/tests/comparison/dataset_operations.vsz.selftest
--- veusz-1.10/tests/comparison/dataset_operations.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/dataset_operations.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,437 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/datebar.vsz.selftest veusz-1.14/tests/comparison/datebar.vsz.selftest
--- veusz-1.10/tests/comparison/datebar.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/datebar.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,120 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/example_csv.vsz.selftest veusz-1.14/tests/comparison/example_csv.vsz.selftest
--- veusz-1.10/tests/comparison/example_csv.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/example_csv.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,150 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/example_import.vsz.selftest veusz-1.14/tests/comparison/example_import.vsz.selftest
--- veusz-1.10/tests/comparison/example_import.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/example_import.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,400 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/fit.vsz.selftest veusz-1.14/tests/comparison/fit.vsz.selftest
--- veusz-1.10/tests/comparison/fit.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/fit.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,123 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/functions.vsz.selftest veusz-1.14/tests/comparison/functions.vsz.selftest
--- veusz-1.10/tests/comparison/functions.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/functions.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,107 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/histo.vsz.selftest veusz-1.14/tests/comparison/histo.vsz.selftest
--- veusz-1.10/tests/comparison/histo.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/histo.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,105 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/inside.vsz.selftest veusz-1.14/tests/comparison/inside.vsz.selftest
--- veusz-1.10/tests/comparison/inside.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/inside.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,437 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/isolatedaxes.vsz.selftest veusz-1.14/tests/comparison/isolatedaxes.vsz.selftest
--- veusz-1.10/tests/comparison/isolatedaxes.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/isolatedaxes.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,61 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/labels.vsz.selftest veusz-1.14/tests/comparison/labels.vsz.selftest
--- veusz-1.10/tests/comparison/labels.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/labels.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,98 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/linked_datasets.vsz.selftest veusz-1.14/tests/comparison/linked_datasets.vsz.selftest
--- veusz-1.10/tests/comparison/linked_datasets.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/linked_datasets.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,500 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/mandelbrot.vsz.selftest veusz-1.14/tests/comparison/mandelbrot.vsz.selftest
--- veusz-1.10/tests/comparison/mandelbrot.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/mandelbrot.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,81 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/markerspolygon.vsz.selftest veusz-1.14/tests/comparison/markerspolygon.vsz.selftest
--- veusz-1.10/tests/comparison/markerspolygon.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/markerspolygon.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,866 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/multiaxes.vsz.selftest veusz-1.14/tests/comparison/multiaxes.vsz.selftest
--- veusz-1.10/tests/comparison/multiaxes.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/multiaxes.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,242 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/multixy.vsz.selftest veusz-1.14/tests/comparison/multixy.vsz.selftest
--- veusz-1.10/tests/comparison/multixy.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/multixy.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,230 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/noheader.vsz.selftest veusz-1.14/tests/comparison/noheader.vsz.selftest
--- veusz-1.10/tests/comparison/noheader.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/noheader.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,84 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/polar.vsz.selftest veusz-1.14/tests/comparison/polar.vsz.selftest
--- veusz-1.10/tests/comparison/polar.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/polar.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,163 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/profile.vsz.selftest veusz-1.14/tests/comparison/profile.vsz.selftest
--- veusz-1.10/tests/comparison/profile.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/profile.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,457 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/rangeds.vsz.selftest veusz-1.14/tests/comparison/rangeds.vsz.selftest
--- veusz-1.10/tests/comparison/rangeds.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/rangeds.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,126 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/shapes.vsz.selftest veusz-1.14/tests/comparison/shapes.vsz.selftest
--- veusz-1.10/tests/comparison/shapes.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/shapes.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,130 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/sin_byhand.vsz.selftest veusz-1.14/tests/comparison/sin_byhand.vsz.selftest
--- veusz-1.10/tests/comparison/sin_byhand.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/sin_byhand.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,93 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/sin.vsz.selftest veusz-1.14/tests/comparison/sin.vsz.selftest
--- veusz-1.10/tests/comparison/sin.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/sin.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,93 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/sizetest.vsz.selftest veusz-1.14/tests/comparison/sizetest.vsz.selftest
--- veusz-1.10/tests/comparison/sizetest.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/sizetest.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,143 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/spectrum.vsz.selftest veusz-1.14/tests/comparison/spectrum.vsz.selftest
--- veusz-1.10/tests/comparison/spectrum.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/spectrum.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,356 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/stackedxy.vsz.selftest veusz-1.14/tests/comparison/stackedxy.vsz.selftest
--- veusz-1.10/tests/comparison/stackedxy.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/stackedxy.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,217 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/starchart.vsz.selftest veusz-1.14/tests/comparison/starchart.vsz.selftest
--- veusz-1.10/tests/comparison/starchart.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/starchart.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,1264 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/ternary.vsz.selftest veusz-1.14/tests/comparison/ternary.vsz.selftest
--- veusz-1.10/tests/comparison/ternary.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/ternary.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,363 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/testcontour.vsz.selftest veusz-1.14/tests/comparison/testcontour.vsz.selftest
--- veusz-1.10/tests/comparison/testcontour.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/testcontour.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,70 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/testcsverr.vsz.selftest veusz-1.14/tests/comparison/testcsverr.vsz.selftest
--- veusz-1.10/tests/comparison/testcsverr.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/testcsverr.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,145 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/test_npy_npz.vsz.selftest veusz-1.14/tests/comparison/test_npy_npz.vsz.selftest
--- veusz-1.10/tests/comparison/test_npy_npz.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/test_npy_npz.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,86 @@
+
+
+
diff -Nru veusz-1.10/tests/comparison/vectorfield.vsz.selftest veusz-1.14/tests/comparison/vectorfield.vsz.selftest
--- veusz-1.10/tests/comparison/vectorfield.vsz.selftest 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/comparison/vectorfield.vsz.selftest 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,4472 @@
+
+
+
diff -Nru veusz-1.10/tests/pychecker_all.sh veusz-1.14/tests/pychecker_all.sh
--- veusz-1.10/tests/pychecker_all.sh 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/pychecker_all.sh 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,24 @@
+#!/bin/bash
+# Copyright (C) 2009 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+# run pychecker on all script files
+
+for f in `find . -name "*.py"`; do
+ pychecker $f >> pychecker-out.txt
+done
diff -Nru veusz-1.10/tests/runselftest.py veusz-1.14/tests/runselftest.py
--- veusz-1.10/tests/runselftest.py 2010-12-12 12:41:10.000000000 +0000
+++ veusz-1.14/tests/runselftest.py 2011-11-22 20:23:31.000000000 +0000
@@ -1,3 +1,42 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+"""A program to self test the Veusz installation.
+
+This code compares the output of example + self test input files with
+expected output. It returns 0 if the tests succeeded, otherwise the
+number of tests failed. If you use an argument "regenerate" to the
+program, the comparison files will be recreated.
+
+This program requires the veusz module to be on the PYTHONPATH.
+
+On Unix/Linux, Qt requires the DISPLAY environment to be set to an X11
+server for the self test to run. In a non graphical environment Xvfb
+can be used to create a hidden X11 server.
+
+The comparison files are close to being SVG files, but use XPM for any
+images and use a fixed (hacked) font metric to give the same results
+on each platform. In addition Unicode characters are expanded to their
+Unicode code to work around different font handling on platforms.
+"""
+
import glob
import os.path
import sys
@@ -8,11 +47,18 @@
import veusz.setting as setting
import veusz.windows.mainwindow
+# these tests fail for some reason which haven't been debugged
+# it appears the failures aren't important however
excluded_tests = set([
- # the 2pi x in the axis gives different positions depending on font
- 'inside.vsz',
- # for some reason more points in polyline: clipping issue?
- 'histo.vsz',
+
+ # fails on Windows
+ 'histo.vsz', # duplicate in long list of values
+ 'spectrum.vsz', # angstrom is split into two on linux
+
+ # fails on Mac OS X
+ 'histo.vsz', # somewhere in long list of values
+ 'spectrum.vsz', # symbol issue
+ 'labels.vsz' # symbol issue
])
class StupidFontMetrics(object):
@@ -40,6 +86,13 @@
def boundingRect(self, c):
return qt4.QRectF(0, 0, self.height()*0.5, self.height())
+_pt = veusz.utils.textrender.PartText
+class PartTextAscii(_pt):
+ """Text renderer which converts text to ascii."""
+ def __init__(self, text):
+ text = unicode(text).encode('ascii', 'xmlcharrefreplace')
+ _pt.__init__(self, text)
+
def renderTest(invsz, outfile):
"""Render vsz document to create outfile."""
@@ -58,18 +111,31 @@
exec open(invsz) in cmds
ifc.Export(outfile)
+
+class Dirs(object):
+ """Directories and files object."""
+ def __init__(self):
+ self.thisdir = os.path.dirname(__file__)
+ self.exampledir = os.path.join(self.thisdir, '..', 'examples')
+ self.testdir = os.path.join(self.thisdir, 'selftests')
+ self.comparisondir = os.path.join(self.thisdir, 'comparison')
+
+ files = ( glob.glob( os.path.join(self.exampledir, '*.vsz') ) +
+ glob.glob( os.path.join(self.testdir, '*.vsz') ) )
+
+ self.invszfiles = [ f for f in files if
+ os.path.basename(f) not in excluded_tests ]
+
def renderAllTests():
+ """Check documents produce same output as in comparison directory."""
+
print "Regenerating all test output"
- thisdir = os.path.dirname(__file__)
- exampledir = os.path.join(thisdir, '..', 'examples' )
- for vsz in glob.glob( os.path.join(exampledir, '*.vsz') ):
+ d = Dirs()
+ for vsz in d.invszfiles:
base = os.path.basename(vsz)
- if base in excluded_tests:
- continue
print base
-
- outfile = os.path.join(thisdir, 'comparison', base + '.selftest')
+ outfile = os.path.join(d.comparisondir, base + '.selftest')
renderTest(vsz, outfile)
def runTests():
@@ -78,18 +144,15 @@
fails = 0
passes = 0
- thisdir = os.path.dirname(__file__)
- exampledir = os.path.join(thisdir, '..', 'examples' )
- for vsz in glob.glob( os.path.join(exampledir, '*.vsz') ):
+ d = Dirs()
+ for vsz in sorted(d.invszfiles):
base = os.path.basename(vsz)
- if base in excluded_tests:
- continue
print base
- outfile = os.path.join(thisdir, base + '.temp.selftest')
+ outfile = os.path.join(d.thisdir, base + '.temp.selftest')
renderTest(vsz, outfile)
- comparfile = os.path.join(thisdir, 'comparison', base + '.selftest')
+ comparfile = os.path.join(d.thisdir, 'comparison', base + '.selftest')
t1 = open(outfile, 'rU').read()
t2 = open(comparfile, 'rU').read()
@@ -101,12 +164,13 @@
passes += 1
os.unlink(outfile)
+ print
if fails == 0:
print "All tests %i/%i PASSED" % (passes, passes)
sys.exit(0)
else:
print "%i/%i tests FAILED" % (fails, passes+fails)
- sys.exit(1)
+ sys.exit(fails)
if __name__ == '__main__':
app = qt4.QApplication([])
@@ -114,8 +178,11 @@
veusz.setting.transient_settings['unsafe_mode'] = True
# hack metrics object to always return same metrics
+ # and replace text renderer with one that encodes unicode symbols
veusz.utils.textrender.FontMetrics = StupidFontMetrics
veusz.utils.FontMetrics = StupidFontMetrics
+ #veusz.utils.Renderer = AsciiRenderer
+ veusz.utils.textrender.PartText = PartTextAscii
# nasty hack to remove underlining
del veusz.utils.textrender.part_commands[r'\underline']
diff -Nru veusz-1.10/tests/selftests/1dto2d.vsz veusz-1.14/tests/selftests/1dto2d.vsz
--- veusz-1.10/tests/selftests/1dto2d.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/1dto2d.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,40 @@
+# Veusz saved document (version 1.12.99)
+# Saved at 2011-08-16T14:20:29.340311
+
+AddImportPath(u'/data/jss/veusz/code/veusz/tests/selftests')
+
+xv = []
+yv = []
+zv = []
+for x in xrange(10):
+ for y in xrange(10):
+ z = sqrt((x-5.)**2 + (y-5.)**2)
+ xv.append(x)
+ yv.append(y)
+ zv.append(z)
+SetData("x", xv)
+SetData("y", yv)
+SetData("z", zv)
+
+SetData2DExpressionXYZ(u'data2d', u'x', u'y', u'z', linked=True)
+
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+To('x')
+Set('autoExtend', False)
+To('..')
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('autoExtend', False)
+Set('direction', 'vertical')
+To('..')
+Add('contour', name='contour1', autoadd=False)
+To('contour1')
+Set('data', u'data2d')
+Set('SubLines/hide', False)
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/autodetect.csv veusz-1.14/tests/selftests/autodetect.csv
--- veusz-1.10/tests/selftests/autodetect.csv 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/autodetect.csv 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,6 @@
+a,b,c,,d,e (text)
+1,01/01/10,hello,,1.00E+001,01/01/10
+2,01/02/10,foo,,1.00E+002,02/01/11
+3,20/02/10,bar,,1.00E+003,03/02/12
+4,15/03/10,xxx,,1.00E+004,01/11/11
+5,10/04/10,aaa,,1.00E+003,11/11/11
diff -Nru veusz-1.10/tests/selftests/autodetect.vsz veusz-1.14/tests/selftests/autodetect.vsz
--- veusz-1.10/tests/selftests/autodetect.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/autodetect.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,43 @@
+# Veusz saved document (version 1.13)
+# Saved at 2011-10-29T16:58:13.552813
+
+ImportFileCSV(u'autodetect.csv', linked=True, blanksaredata=True, dateformat=u'DD/MM/YY| |hh:mm:ss', headermode='1st', numericlocale='en_GB')
+ImportFileCSV(u'autodetect.csv', linked=True, blanksaredata=True, dateformat=u'DD/MM/YY| |hh:mm:ss', dsprefix=u'p_', headerignore=1, headermode='1st', numericlocale='en_GB', rowsignore=2)
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('grid', name='grid1', autoadd=False)
+To('grid1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('mode', u'datetime')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('xData', u'a')
+Set('yData', u'b')
+Set('labels', u'c')
+To('..')
+To('..')
+Add('graph', name='graph2', autoadd=False)
+To('graph2')
+Add('axis', name='x', autoadd=False)
+To('x')
+Set('log', True)
+To('..')
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy2', autoadd=False)
+To('xy2')
+Set('xData', u'p_1.00E+002')
+Set('yData', u'p_2')
+Set('labels', u'p_foo')
+To('..')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/blockeddata.dat veusz-1.14/tests/selftests/blockeddata.dat
--- veusz-1.10/tests/selftests/blockeddata.dat 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/blockeddata.dat 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,8 @@
+1 2
+4 5
+6 7
+8 9
+
+10 2
+1 3
+3 6
diff -Nru veusz-1.10/tests/selftests/blockeddata.vsz veusz-1.14/tests/selftests/blockeddata.vsz
--- veusz-1.10/tests/selftests/blockeddata.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/blockeddata.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,25 @@
+# Veusz saved document (version 1.13)
+# Saved at 2011-11-06T14:18:14.450714
+
+ImportFile(u'blockeddata.dat', u'x y', linked=True, ignoretext=True, useblocks=True)
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('xData', u'x_1')
+Set('yData', u'y_1')
+To('..')
+Add('xy', name='xy2', autoadd=False)
+To('xy2')
+Set('xData', u'x_2')
+Set('yData', u'y_2')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/csv1.csv veusz-1.14/tests/selftests/csv1.csv
--- veusz-1.10/tests/selftests/csv1.csv 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/csv1.csv 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,7 @@
+"test","foo","+-","aerg"
+1,7,0.1,"fdfd"
+2,4,0.1,"dsfh"
+3,3,0.1,"bgtr"
+4,2,0.1,"RR"
+5,3,0.2,"ZZ"
+6,4,0.2,"AA"
diff -Nru veusz-1.10/tests/selftests/csv1.vsz veusz-1.14/tests/selftests/csv1.vsz
--- veusz-1.10/tests/selftests/csv1.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/csv1.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,22 @@
+# Veusz saved document (version 1.12)
+# User: jss
+# Date: Sat, 06 Aug 2011 10:31:14 +0000
+
+ImportFileCSV(u'csv1.csv', linked=True)
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('xData', u'foo')
+Set('yData', u'test')
+Set('labels', u'aerg')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/csv_locale.csv veusz-1.14/tests/selftests/csv_locale.csv
--- veusz-1.10/tests/selftests/csv_locale.csv 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/csv_locale.csv 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,5 @@
+a b c d
+1,23 3.21 01/01/10 2012-12-06
+100,3 200.1 02/02/11 2011-03-01
+1.001,2 1,500.30 23/03/09 2011-04-05
+10 66 05/05/10 2012-01-01
diff -Nru veusz-1.10/tests/selftests/csv_locale.vsz veusz-1.14/tests/selftests/csv_locale.vsz
--- veusz-1.10/tests/selftests/csv_locale.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/csv_locale.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,29 @@
+# Veusz saved document (version 1.13)
+# Saved at 2011-10-29T17:11:42.391345
+
+ImportFileCSV(u'csv_locale.csv', linked=True, delimiter='\t', dsprefix=u'a_')
+ImportFileCSV(u'csv_locale.csv', linked=True, dateformat=u'DD/MM/YY| |hh:mm:ss', delimiter='\t', dsprefix=u'b_', numericlocale='de_DE')
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('mode', u'datetime')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('xData', u'a_b')
+Set('yData', u'a_d')
+Set('labels', u'a_a')
+To('..')
+Add('xy', name='xy2', autoadd=False)
+To('xy2')
+Set('xData', u'b_a')
+Set('yData', u'b_c')
+Set('labels', u'b_d')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/csv_missing.csv veusz-1.14/tests/selftests/csv_missing.csv
--- veusz-1.10/tests/selftests/csv_missing.csv 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/csv_missing.csv 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,5 @@
+a,b,c,d,e (numeric)
+,,,6,
+2,,00:02:33.40,,nan
+3,hello,01:32:44,4,3
+4,foo,01:01:01,8,invalid
diff -Nru veusz-1.10/tests/selftests/csv_missing.vsz veusz-1.14/tests/selftests/csv_missing.vsz
--- veusz-1.10/tests/selftests/csv_missing.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/csv_missing.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,41 @@
+# Veusz saved document (version 1.13)
+# Saved at 2011-10-29T18:48:52.635479
+
+ImportFileCSV(u'csv_missing.csv', linked=True, blanksaredata=True, dateformat=u'DD/MM/YY| |hh:mm:ss', headermode='1st', numericlocale='en_GB')
+SetDataExpression(u'cdiv', u'c/1000.', linked=True)
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('xData', u'a')
+Set('yData', [])
+Set('labels', u'b')
+To('..')
+Add('xy', name='xy2', autoadd=False)
+To('xy2')
+Set('xData', u'd')
+Set('yData', [])
+Set('marker', u'diamond')
+To('..')
+Add('xy', name='xy3', autoadd=False)
+To('xy3')
+Set('xData', u'e')
+Set('yData', [])
+Set('markerSize', u'30pt')
+Set('MarkerFill/hide', True)
+To('..')
+Add('xy', name='xy4', autoadd=False)
+To('xy4')
+Set('xData', [])
+Set('yData', u'cdiv')
+Set('marker', u'cross')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/custom.vsz veusz-1.14/tests/selftests/custom.vsz
--- veusz-1.10/tests/selftests/custom.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/custom.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,32 @@
+# Veusz saved document (version 1.13)
+# Saved at 2011-09-06T18:47:34.442485
+
+AddImportPath(u'/home/jss/code/veusz/veusz/tests/selftests')
+AddCustom('constant', u'myconst', u'10')
+
+AddCustom('constant', 'myconst', '41', mode='replace')
+AddCustom('constant', 'myconst', '42', mode='append')
+
+AddCustom('function', 'myfunc(x)', 'myconst*x**2')
+AddCustom(u'import', u'numpy.linalg', u'inv')
+
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('function', name='function1', autoadd=False)
+To('function1')
+Set('function', u'inv([[1,0],[0,1]])[0,0] * x')
+To('..')
+Add('function', name='function2', autoadd=False)
+To('function2')
+Set('function', u'myfunc(x)')
+Set('Line/color', u'red')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/noheader.csv veusz-1.14/tests/selftests/noheader.csv
--- veusz-1.10/tests/selftests/noheader.csv 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/noheader.csv 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,5 @@
+1,hello,10:20:30
+2,5,20:30:10
+3,2.2,
+4,2,04:20:10.1010
+5,foo,10:10:10
diff -Nru veusz-1.10/tests/selftests/noheader.vsz veusz-1.14/tests/selftests/noheader.vsz
--- veusz-1.10/tests/selftests/noheader.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/noheader.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,24 @@
+# Veusz saved document (version 1.13)
+# Saved at 2011-10-29T19:22:19.911232
+
+ImportFileCSV(u'noheader.csv', linked=True, blanksaredata=True, headermode='none', numericlocale='en_GB')
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+To('x')
+Set('mode', u'datetime')
+To('..')
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('xData', u'col3')
+Set('yData', u'col1')
+Set('labels', u'col2')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/rangeds.vsz veusz-1.14/tests/selftests/rangeds.vsz
--- veusz-1.10/tests/selftests/rangeds.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/rangeds.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,34 @@
+# Veusz saved document (version 1.11)
+# User: jss
+# Date: Fri, 10 Jun 2011 19:21:55 +0000
+
+AddImportPath(u'/home/jss/code/veusz/veusz/tests/selftests')
+SetDataExpression(u'para', u't**2', parametric=(0.0, 1.0, 10), linked=True)
+SetDataRange(u'x', 10, (0.0, 10.0), linked=True)
+SetDataRange(u'x2', 10, (2.0, 5.0), poserr=(0.1, 0.1), negerr=(-0.1, -0.1), linked=True)
+SetDataExpression(u'y', u'(para**2 + x**3)/100', symerr=u'2', linked=True)
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('yData', u'para')
+To('..')
+Add('xy', name='xy2', autoadd=False)
+To('xy2')
+Set('marker', u'plus')
+Set('errorStyle', u'linevertbar')
+To('..')
+Add('xy', name='xy3', autoadd=False)
+To('xy3')
+Set('xData', u'x2')
+Set('errorStyle', u'fillvert')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/sizetest.vsz veusz-1.14/tests/selftests/sizetest.vsz
--- veusz-1.10/tests/selftests/sizetest.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/sizetest.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,63 @@
+# Veusz saved document (version 1.12.999)
+# Saved at 2011-08-17T21:59:29.985354
+
+AddImportPath(u'/home/jss/code/veusz-git/veusz/tests/selftests')
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name=u'pt', autoadd=False)
+To(u'pt')
+Set('xData', [1.0, 2.1, 2.5])
+Set('yData', [3.0, 2.0, 1.0])
+Set('markerSize', u'5pt')
+Set('MarkerFill/color', u'blue')
+To('..')
+Add('xy', name=u'perc', autoadd=False)
+To(u'perc')
+Set('xData', [1.0, 2.0, 3.0])
+Set('yData', [1.0, 3.0, 2.0])
+Set('markerSize', u'10%')
+Set('MarkerFill/color', u'magenta')
+To('..')
+Add('xy', name=u'ratio', autoadd=False)
+To(u'ratio')
+Set('xData', [0.5, 1.5, 2.5])
+Set('yData', [2.5, 0.8, 4.0])
+Set('markerSize', u'1/20')
+To('..')
+Add('xy', name=u'frac', autoadd=False)
+To(u'frac')
+Set('xData', [1.4, 1.7, 2.5])
+Set('yData', [3.2, 1.5, 3.0])
+Set('markerSize', u'0.04')
+Set('MarkerFill/color', u'cyan')
+To('..')
+Add('xy', name=u'cm', autoadd=False)
+To(u'cm')
+Set('xData', [0.4, 1.5, 3.0])
+Set('yData', [3.0, 2.5, 1.0])
+Set('markerSize', u'0.5cm')
+Set('MarkerFill/color', u'yellow')
+To('..')
+Add('xy', name=u'mm', autoadd=False)
+To(u'mm')
+Set('xData', [1.9, 1.5, 1.7])
+Set('yData', [1.3, 1.7, 1.2])
+Set('markerSize', u'3mm')
+Set('MarkerFill/color', u'red')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('xData', [1.9, 1.5, 1.7])
+Set('yData', [1.5, 2.7, 2.2])
+Set('markerSize', u'0.2in')
+Set('MarkerFill/color', u'#aaaaff')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/testcontour.vsz veusz-1.14/tests/selftests/testcontour.vsz
--- veusz-1.10/tests/selftests/testcontour.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/testcontour.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,43 @@
+# Veusz saved document (version 0.99.0)
+# User: jss
+# Date: Fri, 21 Sep 2007 19:00:30 +0000
+
+# A test to make sure 2d arrays are working with different dimensions
+# in x and y
+
+ImportString2D(u'test', '''
+xrange 0.000000e+00 1.000000e+01
+yrange 0.000000e+00 1.200000e+01
+0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
+0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00
+0.000000e+00 2.000000e+00 4.000000e+00 6.000000e+00 8.000000e+00 1.000000e+01 1.200000e+01 1.400000e+01 1.600000e+01 1.800000e+01
+0.000000e+00 3.000000e+00 6.000000e+00 9.000000e+00 1.200000e+01 1.500000e+01 1.800000e+01 2.100000e+01 2.400000e+01 2.700000e+01
+0.000000e+00 4.000000e+00 8.000000e+00 1.200000e+01 1.600000e+01 2.000000e+01 2.400000e+01 2.800000e+01 3.200000e+01 3.600000e+01
+0.000000e+00 5.000000e+00 1.000000e+01 1.500000e+01 2.000000e+01 2.500000e+01 3.000000e+01 3.500000e+01 4.000000e+01 4.500000e+01
+0.000000e+00 6.000000e+00 1.200000e+01 1.800000e+01 2.400000e+01 3.000000e+01 3.600000e+01 4.200000e+01 4.800000e+01 5.400000e+01
+0.000000e+00 7.000000e+00 1.400000e+01 2.100000e+01 2.800000e+01 3.500000e+01 4.200000e+01 4.900000e+01 5.600000e+01 6.300000e+01
+0.000000e+00 8.000000e+00 1.600000e+01 2.400000e+01 3.200000e+01 4.000000e+01 4.800000e+01 5.600000e+01 6.400000e+01 7.200000e+01
+0.000000e+00 9.000000e+00 1.800000e+01 2.700000e+01 3.600000e+01 4.500000e+01 5.400000e+01 6.300000e+01 7.200000e+01 8.100000e+01
+0.000000e+00 1.000000e+01 2.000000e+01 3.000000e+01 4.000000e+01 5.000000e+01 6.000000e+01 7.000000e+01 8.000000e+01 9.000000e+01
+0.000000e+00 1.100000e+01 2.200000e+01 3.300000e+01 4.400000e+01 5.500000e+01 6.600000e+01 7.700000e+01 8.800000e+01 9.900000e+01
+''')
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('contour', name='contour1', autoadd=False)
+To('contour1')
+Set('data', u'test')
+To('..')
+Add('image', name='image1', autoadd=False)
+To('image1')
+Set('data', u'test')
+Set('colorMap', u'spectrum2')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/selftests/testcsverr.csv veusz-1.14/tests/selftests/testcsverr.csv
--- veusz-1.10/tests/selftests/testcsverr.csv 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/testcsverr.csv 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,10 @@
+"test","test+","test-","foo","+-","foo2ððð","+","-"
+1,0.1,2,3,0.3,6,0.6,-0.1
+2,0.2,3,3,0.2,7,0.8,-0.3
+3,0.3,4,4,0.3,4,0.2,-0.4
+4,0.2,5,5,0.2,2,0.1,-0.1
+,,,"a","b","+",,"c"
+,,,5,4,0.1,,9
+,,,6,4,0.3,,8
+,,,6,3,0.2,,4
+,,,5,3,0.1,,1
diff -Nru veusz-1.10/tests/selftests/testcsverr.vsz veusz-1.14/tests/selftests/testcsverr.vsz
--- veusz-1.10/tests/selftests/testcsverr.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/testcsverr.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,58 @@
+# Veusz saved document (version 1.12.99)
+# Saved at 2011-08-14T14:43:33.721297
+
+AddImportPath(u'/home/jss/code/veusz-git/veusz')
+ImportFileCSV(u'testcsverr.csv', linked=True, blanksaredata=True)
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name=u'footest', autoadd=False)
+To(u'footest')
+Set('xData', u'foo')
+Set('yData', u'test')
+To('..')
+Add('xy', name=u'foo2foo', autoadd=False)
+To(u'foo2foo')
+Set('xData', u'foo2\xf0\xf0\xf0')
+Set('yData', u'foo')
+Set('marker', u'diamond')
+Set('MarkerFill/color', u'red')
+To('..')
+Add('xy', name=u'testplusminus', autoadd=False)
+To(u'testplusminus')
+Set('xData', u'test+')
+Set('yData', u'test-')
+Set('marker', u'lineplus')
+Set('MarkerFill/color', u'blue')
+To('..')
+Add('xy', name=u'testplustest', autoadd=False)
+To(u'testplustest')
+Set('xData', u'test+')
+Set('yData', u'test')
+Set('marker', u'barvert')
+Set('MarkerFill/color', u'cyan')
+To('..')
+Add('xy', name=u'ab', autoadd=False)
+To(u'ab')
+Set('xData', u'a')
+Set('yData', u'b')
+Set('marker', u'pentagon')
+Set('errorStyle', u'barends')
+Set('MarkerFill/color', u'darkred')
+To('..')
+Add('xy', name=u'ac', autoadd=False)
+To(u'ac')
+Set('xData', u'c')
+Set('yData', u'a')
+Set('PlotLine/steps', u'centre')
+Set('PlotLine/color', u'red')
+Set('PlotLine/width', u'1pt')
+To('..')
+To('..')
+To('..')
Binary files /tmp/PNa_ZWTTVg/veusz-1.10/tests/selftests/testdat.npy and /tmp/3oWkwrHSii/veusz-1.14/tests/selftests/testdat.npy differ
Binary files /tmp/PNa_ZWTTVg/veusz-1.10/tests/selftests/testdat.npz and /tmp/3oWkwrHSii/veusz-1.14/tests/selftests/testdat.npz differ
diff -Nru veusz-1.10/tests/selftests/test_npy_npz.vsz veusz-1.14/tests/selftests/test_npy_npz.vsz
--- veusz-1.10/tests/selftests/test_npy_npz.vsz 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/tests/selftests/test_npy_npz.vsz 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,40 @@
+# Veusz saved document (version 1.13)
+# Saved at 2011-09-09T18:43:18.107056
+
+ImportFilePlugin(u'Numpy NPZ import', u'testdat.npz', linked=True, errorsin2d=True)
+ImportFilePlugin(u'Numpy NPY import', u'testdat.npy', linked=True, errorsin2d=True, name=u'c')
+Set('StyleSheet/xy/marker', u'none')
+Add('page', name='page1', autoadd=False)
+To('page1')
+Add('graph', name='graph1', autoadd=False)
+To('graph1')
+Add('axis', name='x', autoadd=False)
+Add('axis', name='y', autoadd=False)
+To('y')
+Set('direction', 'vertical')
+To('..')
+Add('xy', name='xy1', autoadd=False)
+To('xy1')
+Set('xData', u'a')
+Set('yData', u'b')
+To('..')
+Add('xy', name='xy2', autoadd=False)
+To('xy2')
+Set('xData', u'a')
+Set('yData', u'c')
+Set('PlotLine/color', u'green')
+To('..')
+Add('xy', name='xy3', autoadd=False)
+To('xy3')
+Set('xData', u'a')
+Set('yData', u'd')
+Set('PlotLine/color', u'blue')
+To('..')
+Add('xy', name='xy4', autoadd=False)
+To('xy4')
+Set('xData', u'a')
+Set('yData', u'e')
+Set('PlotLine/color', u'cyan')
+To('..')
+To('..')
+To('..')
diff -Nru veusz-1.10/tests/testcontour.vsz veusz-1.14/tests/testcontour.vsz
--- veusz-1.10/tests/testcontour.vsz 2010-12-12 12:41:10.000000000 +0000
+++ veusz-1.14/tests/testcontour.vsz 1970-01-01 00:00:00.000000000 +0000
@@ -1,43 +0,0 @@
-# Veusz saved document (version 0.99.0)
-# User: jss
-# Date: Fri, 21 Sep 2007 19:00:30 +0000
-
-# A test to make sure 2d arrays are working with different dimensions
-# in x and y
-
-ImportString2D(u'test', '''
-xrange 0.000000e+00 1.000000e+01
-yrange 0.000000e+00 1.200000e+01
-0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
-0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00
-0.000000e+00 2.000000e+00 4.000000e+00 6.000000e+00 8.000000e+00 1.000000e+01 1.200000e+01 1.400000e+01 1.600000e+01 1.800000e+01
-0.000000e+00 3.000000e+00 6.000000e+00 9.000000e+00 1.200000e+01 1.500000e+01 1.800000e+01 2.100000e+01 2.400000e+01 2.700000e+01
-0.000000e+00 4.000000e+00 8.000000e+00 1.200000e+01 1.600000e+01 2.000000e+01 2.400000e+01 2.800000e+01 3.200000e+01 3.600000e+01
-0.000000e+00 5.000000e+00 1.000000e+01 1.500000e+01 2.000000e+01 2.500000e+01 3.000000e+01 3.500000e+01 4.000000e+01 4.500000e+01
-0.000000e+00 6.000000e+00 1.200000e+01 1.800000e+01 2.400000e+01 3.000000e+01 3.600000e+01 4.200000e+01 4.800000e+01 5.400000e+01
-0.000000e+00 7.000000e+00 1.400000e+01 2.100000e+01 2.800000e+01 3.500000e+01 4.200000e+01 4.900000e+01 5.600000e+01 6.300000e+01
-0.000000e+00 8.000000e+00 1.600000e+01 2.400000e+01 3.200000e+01 4.000000e+01 4.800000e+01 5.600000e+01 6.400000e+01 7.200000e+01
-0.000000e+00 9.000000e+00 1.800000e+01 2.700000e+01 3.600000e+01 4.500000e+01 5.400000e+01 6.300000e+01 7.200000e+01 8.100000e+01
-0.000000e+00 1.000000e+01 2.000000e+01 3.000000e+01 4.000000e+01 5.000000e+01 6.000000e+01 7.000000e+01 8.000000e+01 9.000000e+01
-0.000000e+00 1.100000e+01 2.200000e+01 3.300000e+01 4.400000e+01 5.500000e+01 6.600000e+01 7.700000e+01 8.800000e+01 9.900000e+01
-''')
-Add('page', name='page1', autoadd=False)
-To('page1')
-Add('graph', name='graph1', autoadd=False)
-To('graph1')
-Add('axis', name='x', autoadd=False)
-Add('axis', name='y', autoadd=False)
-To('y')
-Set('direction', 'vertical')
-To('..')
-Add('contour', name='contour1', autoadd=False)
-To('contour1')
-Set('data', u'test')
-To('..')
-Add('image', name='image1', autoadd=False)
-To('image1')
-Set('data', u'test')
-Set('colorMap', u'spectrum2')
-To('..')
-To('..')
-To('..')
diff -Nru veusz-1.10/utils/action.py veusz-1.14/utils/action.py
--- veusz-1.10/utils/action.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/action.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: action.py 1036 2009-07-28 19:41:03Z jeremysanders $
-
import veusz.qtall as qt4
import utilfuncs
import os.path
diff -Nru veusz-1.10/utils/colormap.py veusz-1.14/utils/colormap.py
--- veusz-1.10/utils/colormap.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/utils/colormap.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,228 @@
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+###############################################################################
+
+import numpy as N
+import veusz.qtall as qt4
+
+# use fast or slow helpers
+slowfuncs = False
+try:
+ from veusz.helpers.qtloops import numpyToQImage, applyImageTransparancy
+except ImportError:
+ slowfuncs = True
+ from slowfuncs import slowNumpyToQImage
+
+# Default colormaps used by widgets.
+# Each item in this dict is a colormap entry, with the key the name.
+# The values in the dict are tuples of (B, G, R, alpha).
+# B, G, R and alpha go from 0 to 255
+# Colors are linearly interpolated in this space.
+
+defaultcolormaps = {
+ 'blank': (
+ (0, 0, 0, 0),
+ (0, 0, 0, 0),
+ ),
+ 'heat': (
+ (0, 0, 0, 255),
+ (0, 0, 186, 255),
+ (50, 139, 255, 255),
+ (19, 239, 248, 255),
+ (255, 255, 255, 255),
+ ),
+ 'spectrum2': (
+ (0, 0, 255, 255),
+ (0, 255, 255, 255),
+ (0, 255, 0, 255),
+ (255, 255, 0, 255),
+ (255, 0, 0, 255),
+ ),
+ 'spectrum': (
+ (0, 0, 0, 255),
+ (0, 0, 255, 255),
+ (0, 255, 255, 255),
+ (0, 255, 0, 255),
+ (255, 255, 0, 255),
+ (255, 0, 0, 255),
+ (255, 255, 255, 255),
+ ),
+ 'grey': (
+ (0, 0, 0, 255),
+ (255, 255, 255, 255),
+ ),
+ 'blue': (
+ (0, 0, 0, 255),
+ (255, 0, 0, 255),
+ (255, 255, 255, 255),
+ ),
+ 'red': (
+ (0, 0, 0, 255),
+ (0, 0, 255, 255),
+ (255, 255, 255, 255),
+ ),
+ 'green': (
+ (0, 0, 0, 255),
+ (0, 255, 0, 255),
+ (255, 255, 255, 255),
+ ),
+ 'bluegreen': (
+ (0, 0, 0, 255),
+ (255, 123, 0, 255),
+ (255, 226, 72, 255),
+ (161, 255, 0, 255),
+ (255, 255, 255, 255),
+ ),
+ 'transblack': (
+ (0, 0, 0, 255),
+ (0, 0, 0, 0),
+ ),
+ 'royal': (
+ (0, 0, 0, 255),
+ (128, 0, 0, 255),
+ (255, 0, 128, 255),
+ (0, 255, 255, 255),
+ (255, 255, 255, 255),
+ ),
+ 'complement': (
+ (0, 0, 0, 255),
+ (0, 255, 0, 255),
+ (255, 0, 255, 255),
+ (0, 0, 255, 255),
+ (0, 255, 255, 255),
+ (255, 255, 255, 255),
+ ),
+ }
+
+def applyScaling(data, mode, minval, maxval):
+ """Apply a scaling transformation on the data.
+ data is a numpy array
+ mode is one of 'linear', 'sqrt', 'log', or 'squared'
+ minval is the minimum value of the scale
+ maxval is the maximum value of the scale
+
+ returns transformed data, valid between 0 and 1
+ """
+
+ # catch naughty people by hardcoding a range
+ if minval == maxval:
+ minval, maxval = 0., 1.
+
+ if mode == 'linear':
+ # linear scaling
+ data = (data - minval) / (maxval - minval)
+
+ elif mode == 'sqrt':
+ # sqrt scaling
+ # translate into fractions of range
+ data = (data - minval) / (maxval - minval)
+ # clip off any bad sqrts
+ data[data < 0.] = 0.
+ # actually do the sqrt transform
+ data = N.sqrt(data)
+
+ elif mode == 'log':
+ # log scaling of image
+ # clip any values less than lowermin
+ lowermin = data < minval
+ data = N.log(data - (minval - 1)) / N.log(maxval - (minval - 1))
+ data[lowermin] = 0.
+
+ elif mode == 'squared':
+ # squared scaling
+ # clip any negative values
+ lowermin = data < minval
+ data = (data-minval)**2 / (maxval-minval)**2
+ data[lowermin] = 0.
+
+ else:
+ raise RuntimeError, 'Invalid scaling mode "%s"' % mode
+
+ return data
+
+def applyColorMap(cmap, scaling, datain, minval, maxval,
+ trans, transimg=None):
+ """Apply a colour map to the 2d data given.
+
+ cmap is the color map (numpy of BGRalpha quads)
+ scaling is scaling mode => 'linear', 'sqrt', 'log' or 'squared'
+ data are the imaging data
+ minval and maxval are the extremes of the data for the colormap
+ trans is a number from 0 to 100
+ transimg is an optional image to apply transparency from
+ Returns a QImage
+ """
+
+ cmap = N.array(cmap, dtype=N.intc)
+
+ # invert colour map if min and max are swapped
+ if minval > maxval:
+ minval, maxval = maxval, minval
+ cmap = cmap[::-1]
+
+ # apply transparency
+ if trans != 0:
+ cmap = cmap.copy()
+ cmap[:,3] = (cmap[:,3].astype(N.float32) * (100-trans) /
+ 100.).astype(N.intc)
+
+ # apply scaling of data
+ fracs = applyScaling(datain, scaling, minval, maxval)
+
+ if not slowfuncs:
+ img = numpyToQImage(fracs, cmap, transimg is not None)
+ if transimg is not None:
+ applyImageTransparancy(img, transimg)
+ else:
+ img = slowNumpyToQImage(fracs, cmap, transimg)
+ return img
+
+def makeColorbarImage(minval, maxval, scaling, cmap, transparency,
+ direction='horz'):
+ """Make a colorbar for the scaling given."""
+
+ barsize = 128
+
+ if scaling in ('linear', 'sqrt', 'squared'):
+ # do a linear color scaling
+ vals = N.arange(barsize)/(barsize-1.0)*(maxval-minval) + minval
+ colorscaling = scaling
+ coloraxisscale = 'linear'
+ else:
+ assert scaling == 'log'
+
+ # a logarithmic color scaling
+ # we cheat here by actually plotting a linear colorbar
+ # and telling veusz to put a log axis along it
+ # (as we only care about the endpoints)
+ # maybe should do this better...
+
+ vals = N.arange(barsize)/(barsize-1.0)*(maxval-minval) + minval
+ colorscaling = 'linear'
+ coloraxisscale = 'log'
+
+ # convert 1d array to 2d image
+ if direction == 'horizontal':
+ vals = vals.reshape(1, barsize)
+ else:
+ assert direction == 'vertical'
+ vals = vals.reshape(barsize, 1)
+
+ img = applyColorMap(cmap, colorscaling, vals,
+ minval, maxval, transparency)
+
+ return (minval, maxval, coloraxisscale, img)
diff -Nru veusz-1.10/utils/dates.py veusz-1.14/utils/dates.py
--- veusz-1.10/utils/dates.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/dates.py 2011-11-22 20:23:31.000000000 +0000
@@ -86,6 +86,13 @@
frac, sec = math.modf(f - days*24*60*60)
return datetime.timedelta(days, sec, frac*1e6) + offsetdate
+def dateFloatToString(f):
+ """Convert date float to string."""
+ if N.isfinite(f):
+ return floatToDateTime(f).isoformat()
+ else:
+ return unicode(f)
+
def datetimeToTuple(dt):
"""Return tuple (year,month,day,hour,minute,second,microsecond) from
datetime object."""
@@ -163,5 +170,101 @@
#print "rounded", timein
return tuple(timein)
-
-
+
+def dateStrToRegularExpression(instr):
+ """Convert date-time string to regular expression.
+
+ Converts format yyyy-mm-dd|T|hh:mm:ss to re for date
+ """
+
+ # first rename each special string to a unique string (this is a
+ # unicode character which is in the private use area) then rename
+ # back again to the regular expression. This avoids the regular
+ # expression being remapped.
+ maps = (
+ ('YYYY', u'\ue001', r'(?P[0-9]{4})'),
+ ('YY', u'\ue002', r'(?P[0-9]{2})'),
+ ('MM', u'\ue003', r'(?P[0-9]{2})'),
+ ('M', u'\ue004', r'(?P[0-9]{1,2})'),
+ ('DD', u'\ue005', r'(?P
[0-9]{2})'),
+ ('D', u'\ue006', r'(?P
[0-9]{1,2})'),
+ ('hh', u'\ue007', r'(?P[0-9]{2})'),
+ ('h', u'\ue008', r'(?P[0-9]{1,2})'),
+ ('mm', u'\ue009', r'(?P[0-9]{2})'),
+ ('m', u'\ue00a', r'(?P[0-9]{1,2})'),
+ ('ss', u'\ue00b', r'(?P[0-9]{2}(\.[0-9]*)?)'),
+ ('s', u'\ue00c', r'(?P[0-9]{1,2}(\.[0-9]*)?)'),
+ )
+
+ out = []
+ for p in instr.split('|'):
+ # escape special characters (non alpha-num)
+ p = re.escape(p)
+
+ # replace strings with characters
+ for search, char, repl in maps:
+ p = p.replace(search, char)
+ # replace characters with re strings
+ for search, char, repl in maps:
+ p = p.replace(char, repl)
+
+ # save as an optional group
+ out.append( '(?:%s)?' % p )
+
+ # return final expression
+ return '^\s*%s\s*$' % (''.join(out))
+
+def dateREMatchToDate(match):
+ """Take match object for above regular expression,
+ and convert to float date value."""
+
+ if match is None:
+ raise ValueError, "match object is None"
+
+ # remove None matches
+ grps = {}
+ for k, v in match.groupdict().iteritems():
+ if v is not None:
+ grps[k] = v
+
+ # bomb out if nothing matches
+ if len(grps) == 0:
+ raise ValueError, "no groups matched"
+
+ # get values of offset
+ oyear = offsetdate.year
+ omon = offsetdate.month
+ oday = offsetdate.day
+ ohour = offsetdate.hour
+ omin = offsetdate.minute
+ osec = offsetdate.second
+ omicrosec = offsetdate.microsecond
+
+ # now convert each element from the re
+ if 'YYYY' in grps:
+ oyear = int(grps['YYYY'])
+ if 'YY' in grps:
+ y = int(grps['YY'])
+ if y >= 70:
+ oyear = int('19' + grps['YY'])
+ else:
+ oyear = int('20' + grps['YY'])
+ if 'MM' in grps:
+ omon = int(grps['MM'])
+ if 'DD' in grps:
+ oday = int(grps['DD'])
+ if 'hh' in grps:
+ ohour = int(grps['hh'])
+ if 'mm' in grps:
+ omin = int(grps['mm'])
+ if 'ss' in grps:
+ s = float(grps['ss'])
+ osec = int(s)
+ omicrosec = int(1e6*(s-osec))
+
+ # convert to python datetime object
+ d = datetime.datetime(
+ oyear, omon, oday, ohour, omin, osec, omicrosec)
+
+ # return to veusz float time
+ return datetimeToFloat(d)
diff -Nru veusz-1.10/utils/fitlm.py veusz-1.14/utils/fitlm.py
--- veusz-1.10/utils/fitlm.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/fitlm.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: fitlm.py 1302 2010-06-24 15:36:54Z jeremysanders $
-
"""
Numerical fitting of functions to data.
"""
diff -Nru veusz-1.10/utils/formatting.py veusz-1.14/utils/formatting.py
--- veusz-1.10/utils/formatting.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/utils/formatting.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,224 @@
+# Copyright (C) 2010 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+###############################################################################
+
+import re
+import math
+
+import dates
+import veusz.qtall as qt4
+
+_formaterror = 'FormatError'
+
+# a format statement in a string
+_format_re = re.compile(r'%([-#0-9 +.hlL]*?)([diouxXeEfFgGcrs%])')
+
+def localeFormat(totfmt, args, locale=None):
+ """Format using fmt statement fmt, qt QLocale object locale and
+ arguments to formatting args.
+
+ * arguments are not supported in this formatting, nor is using
+ a dict to supply values for statement
+ """
+
+ # substitute all format statements with string format statements
+ newfmt = _format_re.sub("%s", totfmt)
+
+ # do formatting separately for all statements
+ strings = []
+ i = 0
+ for f in _format_re.finditer(totfmt):
+ code = f.group(2)
+ if code == '%':
+ s = '%'
+ else:
+ try:
+ s = f.group() % args[i]
+ i += 1
+ except IndexError:
+ raise TypeError, "Not enough arguments for format string"
+ if locale is not None and code in 'eEfFgG':
+ s = s.replace('.', qt4.QString(locale.decimalPoint()))
+
+ strings.append(s)
+
+ if i != len(args):
+ raise TypeError, "Not all arguments converted during string formatting"
+
+ return newfmt % tuple(strings)
+
+def formatSciNotation(num, formatargs, locale=None):
+ """Format number into form X \times 10^{Y}.
+ This function trims trailing zeros and decimal point unless a formatting
+ argument is supplied
+
+ This is similar to the %e format string
+ formatargs is the standard argument in a format string to control the
+ number of decimal places, etc.
+
+ locale is a QLocale object
+ """
+
+ # create an initial formatting string
+ if formatargs:
+ format = '%' + formatargs + 'e'
+ else:
+ format = '%.10e'
+
+ # try to format the number
+ # this may be user-supplied data, so don't crash hard by returning
+ # useless output
+ try:
+ text = format % num
+ except:
+ return _formaterror
+
+ # split around the exponent
+ leader, exponent = text.split('e')
+
+ # strip off trailing decimal point and zeros if no format args
+ if not formatargs:
+ leader = '%.10g' % float(leader)
+
+ # trim off leading 1
+ if leader == '1' and not formatargs:
+ leader = ''
+ else:
+ # the unicode string is a small space, multiply and small space
+ leader += u'\u00d7'
+
+ # do substitution of decimals
+ if locale is not None:
+ leader = leader.replace('.', qt4.QString(locale.decimalPoint()))
+
+ return '%s10^{%i}' % (leader, int(exponent))
+
+def formatGeneral(num, fmtarg, locale=None):
+ """General formatting which switches from normal to scientic
+ notation."""
+
+ a = abs(num)
+ # manually choose when to switch from normal to scientific
+ # as the default isn't very good
+ if a >= 1e4 or (a < 1e-2 and a > 1e-110):
+ retn = formatSciNotation(num, fmtarg, locale=locale)
+ else:
+ if fmtarg:
+ f = '%' + fmtarg + 'g'
+ else:
+ f = '%.10g'
+
+ try:
+ retn = f % num
+ except TypeError:
+ retn = _formaterror
+
+ if locale is not None:
+ retn = retn.replace('.', qt4.QString(locale.decimalPoint()))
+ return retn
+
+engsuffixes = ( 'y', 'z', 'a', 'f', 'p', 'n',
+ u'\u03bc', 'm', '', 'k', 'M', 'G',
+ 'T', 'P', 'E', 'Z', 'Y' )
+
+def formatEngineering(num, fmtarg, locale=None):
+ """Engineering suffix format notation using SI suffixes."""
+
+ if num != 0.:
+ logindex = math.log10( abs(num) ) / 3.
+
+ # for numbers < 1 round down suffix
+ if logindex < 0. and (int(logindex)-logindex) > 1e-6:
+ logindex -= 1
+
+ # make sure we don't go out of bounds
+ logindex = min( max(logindex, -8),
+ len(engsuffixes) - 9 )
+
+ suffix = engsuffixes[ int(logindex) + 8 ]
+ val = num / 10**( int(logindex) *3)
+ else:
+ suffix = ''
+ val = num
+
+ text = ('%' + fmtarg + 'g%s') % (val, suffix)
+ if locale is not None:
+ text = text.replace('.', qt4.QString(locale.decimalPoint()))
+ return text
+
+# catch general veusz formatting expression
+_formatRE = re.compile(r'%([^A-Za-z]*)(VDVS|VD.|V.|[A-Za-z])')
+
+def formatNumber(num, format, locale=None):
+ """ Format a number in different ways.
+
+ format is a standard C format string, with some additions:
+ %Ve scientific notation X \times 10^{Y}
+ %Vg switches from normal notation to scientific outside 10^-2 to 10^4
+ %VE engineering suffix option
+
+ %VDx date formatting, where x is one of the arguments in
+ http://docs.python.org/lib/module-time.html in the function
+ strftime
+ """
+
+ while True:
+ # repeatedly try to do string format
+ m = _formatRE.search(format)
+ if not m:
+ break
+
+ # argument and type of formatting
+ farg, ftype = m.groups()
+
+ # special veusz formatting
+ if ftype[:1] == 'V':
+ # special veusz formatting
+ if ftype == 'Ve':
+ out = formatSciNotation(num, farg, locale=locale)
+ elif ftype == 'Vg':
+ out = formatGeneral(num, farg, locale=locale)
+ elif ftype == 'VE':
+ out = formatEngineering(num, farg, locale=locale)
+ elif ftype[:2] == 'VD':
+ d = dates.floatToDateTime(num)
+ # date formatting (seconds since start of epoch)
+ if ftype[:4] == 'VDVS':
+ # special seconds operator
+ out = ('%'+ftype[4:]+'g') % (d.second+d.microsecond*1e-6)
+ else:
+ # use date formatting
+ try:
+ out = d.strftime(str('%'+ftype[2:]))
+ except ValueError:
+ out = _formaterror
+ else:
+ out = _formaterror
+
+ # replace hyphen with true - and small space
+ out = out.replace('-', u'\u2212')
+
+ else:
+ # standard C formatting
+ try:
+ out = localeFormat('%' + farg + ftype, (num,), locale=locale)
+ except:
+ out = _formaterror
+
+ format = format[:m.start()] + out + format[m.end():]
+
+ return format
diff -Nru veusz-1.10/utils/__init__.py veusz-1.14/utils/__init__.py
--- veusz-1.10/utils/__init__.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/__init__.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: __init__.py 1401 2010-09-11 09:28:00Z jeremysanders $
-
from version import version
from textrender import Renderer, FontMetrics
from safe_eval import checkCode
@@ -30,6 +28,8 @@
from action import *
from pdf import *
from dates import *
+from formatting import *
+from colormap import *
try:
from veusz.helpers.qtloops import addNumpyToPolygonF, plotPathsToPainter, \
diff -Nru veusz-1.10/utils/pdf.py veusz-1.14/utils/pdf.py
--- veusz-1.10/utils/pdf.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/pdf.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: pdf.py 1056 2009-09-05 16:51:59Z jeremysanders $
-
import re
def scalePDFMediaBox(text, pagewidth,
diff -Nru veusz-1.10/utils/points.py veusz-1.14/utils/points.py
--- veusz-1.10/utils/points.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/points.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: points.py 1280 2010-06-13 14:53:17Z jeremysanders $
-
from itertools import izip
import veusz.qtall as qt4
@@ -30,6 +28,8 @@
except ImportError:
from slowfuncs import plotPathsToPainter
+import colormap
+
"""This is the symbol plotting part of Veusz
There are actually several different ways symbols are plotted.
@@ -383,7 +383,7 @@
)
def plotMarkers(painter, xpos, ypos, markername, markersize, scaling=None,
- clip=None):
+ clip=None, cmap=None, colorvals=None):
"""Funtion to plot an array of markers on a painter.
painter: QPainter
@@ -392,6 +392,8 @@
markersize: size of marker to plot
scaling: scale size of markers by array, or don't in None
clip: rectangle if clipping wanted
+ cmap: colormap to use if colorvals is set
+ colorvals: color values 0-1 of each point if used
"""
# minor optimization
@@ -411,21 +413,18 @@
# turn off brush
painter.setBrush( qt4.QBrush() )
- # split up into two loops as this is a critical path
- if scaling is None:
- plotPathsToPainter(painter, path, xpos, ypos, clip)
- else:
- # plot markers, scaling each one
- s = painter.scale
- t = painter.translate
- d = painter.drawPath
- r = painter.resetTransform
- for x, y, sc in izip(xpos, ypos, scaling):
- t(x, y)
- s(sc, sc)
- d(path)
- r()
-
+ # if using colored points
+ colorimg = None
+ if colorvals is not None:
+ # convert colors to rgb values via a 2D image and pass to function
+ trans = (1-painter.brush().color().alphaF())*100
+ color2d = colorvals.reshape( 1, len(colorvals) )
+ colorimg = colormap.applyColorMap(
+ cmap, 'linear', color2d, 0., 1., trans)
+
+ # this is the fast (C++) or slow (python) helper
+ plotPathsToPainter(painter, path, xpos, ypos, scaling, clip, colorimg)
+
painter.restore()
def plotMarker(painter, xpos, ypos, markername, markersize):
diff -Nru veusz-1.10/utils/safe_eval.py veusz-1.14/utils/safe_eval.py
--- veusz-1.10/utils/safe_eval.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/safe_eval.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: safe_eval.py 1089 2009-10-25 17:59:57Z jeremysanders $
-
"""
'Safe' python code evaluation
@@ -329,7 +327,9 @@
if securityonly is set, then don't return errors from Python
exceptions.
"""
-
+
+ # compiler can't parse strings with unicode
+ code = code.encode('utf8')
try:
ast = compiler.parse(code)
except SyntaxError, e:
diff -Nru veusz-1.10/utils/slowfuncs.py veusz-1.14/utils/slowfuncs.py
--- veusz-1.10/utils/slowfuncs.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/slowfuncs.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: slowfuncs.py 1286 2010-06-16 09:44:53Z jeremysanders $
-
"""
These are slow versions of routines also implemented in C++
"""
@@ -53,7 +51,8 @@
for p in points:
pappend( qpointf(*p) )
-def plotPathsToPainter(painter, path, x, y, clip=None):
+def plotPathsToPainter(painter, path, x, y, scaling=None,
+ clip=None, colorimg=None):
"""Plot array of x, y points."""
if clip is None:
@@ -67,12 +66,27 @@
pathbox.bottom(), pathbox.right())
# draw the paths
- for xp, yp in izip(x, y):
- pt = qt4.QPointF(xp, yp)
+ numpts = min(len(x), len(y))
+ if scaling is not None:
+ numpts = min(numpts, len(scaling))
+ if colorimg is not None:
+ numpts = min(numpts, colorimg.width())
+
+ origtrans = painter.worldTransform()
+ for i in xrange(numpts):
+ pt = qt4.QPointF(x[i], y[i])
if clip.contains(pt):
painter.translate(pt)
+ # scale if wanted
+ if scaling is not None:
+ painter.scale(scaling[i], scaling[i])
+ # set color if given
+ if colorimg is not None:
+ b = qt4.QBrush( qt4.QColor.fromRgba(colorimg.pixel(i, 0)) )
+ painter.setBrush(b)
+
painter.drawPath(path)
- painter.translate(-pt)
+ painter.setWorldTransform(origtrans)
def plotLinesToPainter(painter, x1, y1, x2, y2, clip=None, autoexpand=True):
"""Plot lines given in numpy arrays to painter."""
@@ -136,3 +150,59 @@
# paint it
if rects:
painter.drawRects(rects)
+
+def slowNumpyToQImage(img, cmap, transparencyimg):
+ """Slow version of routine to convert numpy array to QImage
+ This is hard work in Python, but it was like this originally.
+
+ img: numpy array to convert to QImage
+ cmap: 2D array of colors (BGRA rows)
+ forcetrans: force image to have alpha component."""
+
+ if struct.pack("h", 1) == "\000\001":
+ # have to swap colors for big endian architectures
+ cmap2 = cmap.copy()
+ cmap2[:,0] = cmap[:,3]
+ cmap2[:,1] = cmap[:,2]
+ cmap2[:,2] = cmap[:,1]
+ cmap2[:,3] = cmap[:,0]
+ cmap = cmap2
+
+ fracs = N.clip(N.ravel(img), 0., 1.)
+
+ # Work out which is the minimum colour map. Assumes we have <255 bands.
+ numbands = cmap.shape[0]-1
+ bands = (fracs*numbands).astype(N.uint8)
+ bands = N.clip(bands, 0, numbands-1)
+
+ # work out fractional difference of data from band to next band
+ deltafracs = (fracs - bands * (1./numbands)) * numbands
+
+ # need to make a 2-dimensional array to multiply against triplets
+ deltafracs.shape = (deltafracs.shape[0], 1)
+
+ # calculate BGRalpha quadruplets
+ # this is a linear interpolation between the band and the next band
+ quads = (deltafracs*cmap[bands+1] +
+ (1.-deltafracs)*cmap[bands]).astype(N.uint8)
+
+ # apply transparency if a transparency image is set
+ if transparencyimg is not None and transparencyimg.shape == img.shape:
+ quads[:,3] = ( N.clip(N.ravel(transparencyimg), 0., 1.) *
+ quads[:,3] ).astype(N.uint8)
+
+ # convert 32bit quads to a Qt QImage
+ s = quads.tostring()
+
+ fmt = qt4.QImage.Format_RGB32
+ if N.any(cmap[:,3] != 255) or transparencyimg is not None:
+ # any transparency
+ fmt = qt4.QImage.Format_ARGB32
+
+ img = qt4.QImage(s, img.shape[1], img.shape[0], fmt)
+ img = img.mirrored()
+
+ # hack to ensure string isn't freed before QImage
+ img.veusz_string = s
+ return img
+
diff -Nru veusz-1.10/utils/textrender.py veusz-1.14/utils/textrender.py
--- veusz-1.10/utils/textrender.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/textrender.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,8 +19,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: textrender.py 1403 2010-09-12 13:44:35Z jeremysanders $
-
import math
import re
@@ -40,11 +38,13 @@
r'\}': '}',
r'\[': '[',
r'\]': ']',
+ r'\backslash' : u'\u005c',
# operators
r'\pm': u'\u00b1',
r'\mp': u'\u2213',
r'\times': u'\u00d7',
+ r'\cdot': u'\u22c5',
r'\ast': u'\u2217',
r'\star': u'\u22c6',
r'\deg': u'\u00b0',
@@ -56,6 +56,7 @@
r'\uplus': u'\u228e',
r'\vee': u'\u22c1',
r'\wedge': u'\u22c0',
+ r'\nabla': u'\u2207',
r'\lhd': u'\u22b2',
r'\rhd': u'\u22b3',
r'\unlhd': u'\u22b4',
@@ -75,9 +76,14 @@
r'\infty': u'\u221e',
r'\int': u'\u222b',
r'\leftarrow': u'\u2190',
+ r'\Leftarrow': u'\u21d0',
r'\uparrow': u'\u2191',
r'\rightarrow': u'\u2192',
+ r'\to': u'\u2192',
+ r'\Rightarrow': u'\u21d2',
r'\downarrow': u'\u2193',
+ r'\leftrightarrow': u'\u2194',
+ r'\Leftrightarrow': u'\u21d4',
r'\circ': u'\u0e50',
# relations
@@ -190,7 +196,7 @@
painter = self.painter
pixperpt = painter.device().logicalDpiY() / 72.
try:
- pixperpt *= painter.veusz_scaling
+ pixperpt *= painter.scaling
except AttributeError:
pass
return pixperpt
@@ -209,6 +215,9 @@
def __init__(self, text):
self.text = text
+ def addText(self, text):
+ self.text += text
+
def render(self, state):
"""Render some text."""
@@ -387,6 +396,21 @@
font.setPointSizeF(size)
state.painter.setFont(font)
+class PartMultiScript(Part):
+ """Represents multiple parts with the same starting x, e.g. a combination of
+ super- and subscript parts."""
+ def render(self, state):
+ oldx = state.x
+ newx = oldx
+ for p in self.children:
+ state.x = oldx
+ p.render(state)
+ newx = max([state.x, newx])
+ state.x = newx
+
+ def append(p):
+ self.children.append(p)
+
class PartItalic(Part):
"""Represents italic part."""
def render(self, state):
@@ -548,6 +572,7 @@
r'\size': (PartSize, 2),
r'\frac': (PartFrac, 2),
r'\bar': (PartBar, 1),
+ r'\overline': (PartBar, 1),
r'\dot': (PartDot, 1),
}
@@ -555,8 +580,7 @@
splitter_re = re.compile(r'''
(
\\[A-Za-z]+[ ]* | # normal latex command
-\\\{ | \\\} | # escaped {} brackets
-\\\[ | \\\] | # escaped [] brackets
+\\[\[\]{}_^] | # escaped special characters
\\\\ | # line end
\{ | # begin block
\} | # end block
@@ -580,14 +604,12 @@
# we may need to drop excess spaces after \foo commands
ps = p.rstrip()
if ps in symbols:
- # convert to symbol if possible
- text = symbols[ps]
+ # it will become a symbol, so preserve whitespace
+ doAdd(ps)
if ps != p:
- # add back spacing
- text += p[len(ps)-len(p):]
- doAdd(text)
+ doAdd(p[len(ps)-len(p):])
else:
- # add as possible command
+ # add as possible command, so drop excess whitespace
doAdd(ps)
elif p == '{':
# add a new level
@@ -606,6 +628,14 @@
lines = []
itemlist = []
length = len(partlist)
+
+ def addText(text):
+ """Try to merge consecutive text items for better rendering."""
+ if itemlist and isinstance(itemlist[-1], PartText):
+ itemlist[-1].addText(text)
+ else:
+ itemlist.append( PartText(text) )
+
i = 0
while i < length:
p = partlist[i]
@@ -613,13 +643,38 @@
lines.append( Part(itemlist) )
itemlist = []
elif isinstance(p, basestring):
- if p in part_commands:
+ if p in symbols:
+ addText(symbols[p])
+ elif p in part_commands:
klass, numargs = part_commands[p]
- partargs = [makePartTree(k) for k in partlist[i+1:i+numargs+1]]
- itemlist.append( klass(partargs) )
+ if numargs == 1 and len(partlist) > i+1 and isinstance(partlist[i+1], basestring):
+ # coerce a single argument to a partlist so that things
+ # like "A^\dagger" render correctly without needing
+ # curly brackets
+ partargs = [makePartTree([partlist[i+1]])]
+ else:
+ partargs = [makePartTree(k) for k in partlist[i+1:i+numargs+1]]
+
+ if (p == '^' or p == '_'):
+ if len(itemlist) > 0 and (
+ isinstance(itemlist[-1], PartSubScript) or
+ isinstance(itemlist[-1], PartSuperScript) or
+ isinstance(itemlist[-1], PartMultiScript)):
+ # combine sequences of multiple sub-/superscript parts into
+ # a MultiScript item so that a single text item can have
+ # both super and subscript indicies
+ # e.g. X^{(q)}_{i}
+ if isinstance(itemlist[-1], PartMultiScript):
+ itemlist.append( klass(partargs) )
+ else:
+ itemlist[-1] = PartMultiScript([itemlist[-1], klass(partargs)])
+ else:
+ itemlist.append( klass(partargs) )
+ else:
+ itemlist.append( klass(partargs) )
i += numargs
else:
- itemlist.append( PartText(p) )
+ addText(p)
else:
itemlist.append( makePartTree(p) )
i += 1
diff -Nru veusz-1.10/utils/treemodel.py veusz-1.14/utils/treemodel.py
--- veusz-1.10/utils/treemodel.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/utils/treemodel.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,265 @@
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+###############################################################################
+
+"""A Qt data model show a tree of Python nodes."""
+
+import bisect
+import veusz.qtall as qt4
+
+class TMNode(object):
+ """Object to represent nodes in TreeModel.
+
+ Each node has a tuple of data items, a parent node and a list of
+ child nodes.
+ """
+
+ def __init__(self, data, parent):
+ self.data = data
+ self.parent = parent
+ self.childnodes = []
+
+ # model uses these to map objects to qmodelindexes
+ # self._idx = None
+
+ def toolTip(self, column):
+ """Return tooltip for column, if any."""
+ return qt4.QVariant()
+
+ def doPrint(self, indent=0):
+ """Print out tree for debugging."""
+ print " "*indent, self.data, self
+ for c in self.childnodes:
+ c.doPrint(indent=indent+1)
+
+ def deleteFromParent(self):
+ """Delete this node from its parent."""
+ del self.parent.childnodes[self.parent.childnodes.index(self)]
+
+ def nodeData(self, idx):
+ """Get data with index given."""
+ try:
+ return qt4.QVariant(self.data[idx])
+ except:
+ return qt4.QVariant()
+
+ def childWithData1(self, d):
+ """Get child node with 1st column data d."""
+ for c in self.childnodes:
+ if c.data[0] == d:
+ return c
+ return None
+
+ def insertChildSorted(self, newchild):
+ """Insert child alphabetically using data d."""
+ cdata = [c.data for c in self.childnodes]
+ idx = bisect.bisect_left(cdata, newchild.data)
+ newchild.parent = self
+ self.childnodes.insert(idx, newchild)
+
+ def cloneTo(self, newroot):
+ """Make a clone of self at the root given."""
+ return self.__class__(self.data, newroot)
+
+class TreeModel(qt4.QAbstractItemModel):
+ """A Qt model for storing Python nodes in a tree.
+
+ The nodes are TMNode objects above."""
+
+ def __init__(self, rootdata, *args):
+ """Construct the model.
+ rootdata is a tuple of data for the root node - it should have
+ the same number of columns as other datasets."""
+
+ qt4.QAbstractItemModel.__init__(self, *args)
+ self.root = TMNode(rootdata, None)
+
+ # the nodes are stored here when in the tree,
+ # to be looked up with node._idx
+ self.nodes = {}
+ # next index to assign in self.nodes
+ self.nodeindex = 0
+
+ def columnCount(self, parent):
+ """Use root data to get column count."""
+ return len(self.root.data)
+
+ def data(self, index, role):
+ """Get text or tooltip."""
+ if index.isValid():
+ item = self.objFromIndex(index)
+ if role == qt4.Qt.DisplayRole:
+ return item.nodeData(index.column())
+ elif role == qt4.Qt.ToolTipRole:
+ return item.toolTip(index.column())
+
+ return qt4.QVariant()
+
+ def flags(self, index):
+ """Return whether node is editable."""
+ if not index.isValid():
+ return qt4.Qt.NoItemFlags
+ return qt4.Qt.ItemIsEnabled | qt4.Qt.ItemIsSelectable
+
+ def headerData(self, section, orientation, role):
+ """Use root node to get headers."""
+ if orientation == qt4.Qt.Horizontal and role == qt4.Qt.DisplayRole:
+ return self.root.nodeData(section)
+ return qt4.QVariant()
+
+ def objFromIndex(self, idx):
+ """Given an index, return the node."""
+ if idx.isValid():
+ try:
+ return self.nodes[idx.internalId()]
+ except KeyError:
+ pass
+ return None
+
+ def index(self, row, column, parent):
+ """Return index of node."""
+ if not self.hasIndex(row, column, parent):
+ return qt4.QModelIndex()
+
+ parentitem = self.objFromIndex(parent)
+ if parentitem is None:
+ parentitem = self.root
+
+ childitem = parentitem.childnodes[row]
+ if childitem:
+ return self.createIndex(row, column, childitem._idx)
+ return qt4.QModelIndex()
+
+ def parent(self, index):
+ """Get parent index of index."""
+ if not index.isValid():
+ return qt4.QModelIndex()
+
+ childitem = self.objFromIndex(index)
+ parentitem = childitem.parent
+
+ if parentitem is self.root:
+ return qt4.QModelIndex()
+
+ parentrow = parentitem.parent.childnodes.index(parentitem)
+ return self.createIndex(parentrow, 0, parentitem._idx)
+
+ def rowCount(self, parent):
+ """Compute row count of node."""
+ if parent.column() > 0:
+ return 0
+
+ if not parent.isValid():
+ parentitem = self.root
+ else:
+ parentitem = self.objFromIndex(parent)
+
+ return len(parentitem.childnodes)
+
+ @staticmethod
+ def _getdata(theroot):
+ """Get a set of child node data and a mapping of data to node."""
+ lookup = {}
+ data = []
+ for c in theroot.childnodes:
+ d = c.data[0]
+ lookup[d] = c
+ data.append(d)
+ return lookup, set(data)
+
+ def _syncbranch(self, parentidx, root, rootnew):
+ """For synchronising branches in node tree."""
+
+ # FIXME: this doesn't work if there are duplicates
+ # use LCS - longest common sequence instead
+ clookup, cdata = self._getdata(root)
+ nlookup, ndata = self._getdata(rootnew)
+ if not cdata and not ndata:
+ return
+
+ common = cdata & ndata
+
+ # items to remove (no longer in new data)
+ todelete = cdata - common
+
+ # sorted list to add (added to new data)
+ toadd = list(ndata - common)
+ toadd.sort()
+
+ # iterate over entries, adding and deleting as necessary
+ i = 0
+ c = root.childnodes
+
+ while i < len(rootnew.childnodes) or i < len(c):
+ if i < len(c):
+ k = c[i].data[0]
+ else:
+ k = None
+
+ # one to be deleted
+ if k in todelete:
+ todelete.remove(k)
+ self.beginRemoveRows(parentidx, i, i)
+ del self.nodes[c[i]._idx]
+ del c[i]
+ self.endRemoveRows()
+ continue
+
+ # one to insert
+ if toadd and (k > toadd[0] or k is None):
+ self.beginInsertRows(parentidx, i, i)
+ a = nlookup[toadd[0]].cloneTo(root)
+ a._idx = self.nodeindex
+ self.nodeindex += 1
+ self.nodes[a._idx] = a
+ c.insert(i, a)
+ self.endInsertRows()
+ del toadd[0]
+ else:
+ # neither delete or add
+ if clookup[k].data != nlookup[k].data:
+ # the name is the same but data are not
+ # swap node entry to point to new cloned node
+ clone = nlookup[k].cloneTo(root)
+ idx = clookup[k]._idx
+ clone._idx = idx
+ self.nodes[idx] = clone
+
+ self.emit(qt4.SIGNAL('dataChanged(const QModelIndex &, '
+ 'const QModelIndex &)'),
+ self.index(i, 0, parentidx),
+ self.index(i, len(c[i].data)-1,
+ parentidx))
+
+ # now recurse to update any subnodes
+ newindex = self.index(i, 0, parentidx)
+ self._syncbranch(newindex, c[i], rootnew.childnodes[i])
+
+ i += 1
+
+ def syncTree(self, newroot):
+ """Syncronise the displayed tree with the given tree new."""
+
+ toreset = self.root.data != newroot.data
+ if toreset:
+ # header changed, so do reset
+ self.beginResetModel()
+
+ self._syncbranch( qt4.QModelIndex(), self.root, newroot )
+ if toreset:
+ self.root.data = newroot.data
+ self.endResetModel()
diff -Nru veusz-1.10/utils/utilfuncs.py veusz-1.14/utils/utilfuncs.py
--- veusz-1.10/utils/utilfuncs.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/utilfuncs.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,18 +19,14 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: utilfuncs.py 1447 2010-11-11 20:35:25Z jeremysanders $
-
import sys
import string
-import weakref
import re
import os.path
import threading
-import dates
import codecs
import csv
-import math
+import StringIO
import veusz.qtall as qt4
import numpy as N
@@ -42,13 +38,20 @@
"""Get installed directory to find files relative to this one."""
if hasattr(sys, 'frozen'):
- # for py2exe compatability
- return os.path.dirname(os.path.abspath(sys.executable))
+ # for pyinstaller/py2app compatability
+ dirname = os.path.dirname(os.path.abspath(sys.executable))
+ if sys.platform == 'darwin':
+ # py2app
+ return os.path.join(dirname, '..', 'Resources', 'veusz')
+ else:
+ # pyinstaller
+ return dirname
else:
# standard installation
return os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
veuszDirectory = _getVeuszDirectory()
+exampleDirectory = os.path.join(veuszDirectory, 'examples')
id_re = re.compile('^[A-Za-z_][A-Za-z0-9_]*$')
def validPythonIdentifier(name):
@@ -59,7 +62,7 @@
"""Validate dataset name is okay.
Dataset names can contain anything except back ticks!
"""
- return len(name) > 0 and name.find('`') == -1
+ return len(name.strip()) > 0 and name.find('`') == -1
def validateWidgetName(name):
"""Validate widget name is okay.
@@ -117,25 +120,14 @@
return '#%02x%02x%02x%02x' % (col.red(), col.green(), col.blue(),
col.alpha())
-class WeakBoundMethod:
- """A weak reference to a bound method.
-
- Based on code by Frederic Jolliton
- See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81253
- """
-
- def __init__(self, f):
- self.f = f.im_func
- self.c = weakref.ref(f.im_self)
-
- def isEqual(self, f):
- """Is the bound method pointed to the same as this one?"""
- return f.im_func == self.f and f.im_self == self.c
-
- def __call__(self , *arg):
- if self.c() is None:
- raise ValueError, 'Method called on dead object'
- self.f(self.c(), *arg)
+def pixmapAsHtml(pix):
+ """Get QPixmap as html image text."""
+ ba = qt4.QByteArray()
+ buf = qt4.QBuffer(ba)
+ buf.open(qt4.QIODevice.WriteOnly)
+ pix.toImage().save(buf, "PNG")
+ b64 = str(buf.data().toBase64())
+ return '' % b64
def BoundCaller(function, *params):
"""Wrap a function with its initial arguments."""
@@ -203,156 +195,6 @@
return out
-_formaterror = 'FormatError'
-
-def formatSciNotation(num, formatargs=''):
- """Format number into form X \times 10^{Y}.
- This function trims trailing zeros and decimal point unless a formatting
- argument is supplied
-
- This is similar to the %e format string
- formatargs is the standard argument in a format string to control the
- number of decimal places, etc."""
-
- # create an initial formatting string
- if formatargs:
- format = '%' + formatargs + 'e'
- else:
- format = '%.10e'
-
- # try to format the number
- # this may be user-supplied data, so don't crash hard by returning
- # useless output
- try:
- text = format % num
- except:
- return _formaterror
-
- # split around the exponent
- leader, exponent = text.split('e')
-
- # strip off trailing decimal point and zeros if no format args
- if not formatargs:
- leader = '%.10g' % float(leader)
-
- # trim off leading 1
- if leader == '1' and not formatargs:
- leader = ''
- else:
- # the unicode string is a small space, multiply and small space
- leader += u'\u00d7'
-
- return '%s10^{%i}' % (leader, int(exponent))
-
-def formatGeneral(num, fmtarg):
- """General formatting which switches from normal to scientic
- notation."""
-
- a = abs(num)
- # manually choose when to switch from normal to scientific
- # as the default isn't very good
- if a >= 1e4 or (a < 1e-2 and a > 1e-110):
- return formatSciNotation(num, fmtarg)
- else:
- if fmtarg:
- f = '%' + fmtarg + 'g'
- else:
- f = '%.10g'
-
- try:
- return f % num
- except:
- return _formaterror
-
-engsuffixes = ( 'y', 'z', 'a', 'f', 'p', 'n',
- u'\u03bc', 'm', '', 'k', 'M', 'G',
- 'T', 'P', 'E', 'Z', 'Y' )
-
-def formatEngineering(num, fmtarg):
- """Engineering suffix format notation using SI suffixes."""
-
- if num != 0.:
- logindex = math.log10( abs(num) ) / 3.
-
- # for numbers < 1 round down suffix
- if logindex < 0. and (int(logindex)-logindex) > 1e-6:
- logindex -= 1
-
- # make sure we don't go out of bounds
- logindex = min( max(logindex, -8),
- len(engsuffixes) - 9 )
-
- suffix = engsuffixes[ int(logindex) + 8 ]
- val = num / 10**( int(logindex) *3)
- else:
- suffix = ''
- val = num
-
- text = ('%' + fmtarg + 'g%s') % (val, suffix)
- return text
-
-_formatRE = re.compile(r'%([^A-Za-z]*)(VDVS|VD.|V.|[A-Za-z])')
-
-def formatNumber(num, format):
- """ Format a number in different ways.
-
- format is a standard C format string, with some additions:
- %Ve scientific notation X \times 10^{Y}
- %Vg switches from normal notation to scientific outside 10^-2 to 10^4
- %VE engineering suffix option
-
- %VDx date formatting, where x is one of the arguments in
- http://docs.python.org/lib/module-time.html in the function
- strftime
- """
-
- while True:
- # repeatedly try to do string format
- m = _formatRE.search(format)
- if not m:
- break
-
- # argument and type of formatting
- farg, ftype = m.groups()
-
- # special veusz formatting
- if ftype[:1] == 'V':
- # special veusz formatting
- if ftype == 'Ve':
- out = formatSciNotation(num, farg)
- elif ftype == 'Vg':
- out = formatGeneral(num, farg)
- elif ftype == 'VE':
- out = formatEngineering(num, farg)
- elif ftype[:2] == 'VD':
- d = dates.floatToDateTime(num)
- # date formatting (seconds since start of epoch)
- if ftype[:4] == 'VDVS':
- # special seconds operator
- out = ('%'+ftype[4:]+'g') % (d.second+d.microsecond*1e-6)
- else:
- # use date formatting
- try:
- out = d.strftime(str('%'+ftype[2:]))
- except ValueError:
- out = _formaterror
- else:
- out = _formaterror
-
- # replace hyphen with true - and small space
- out = out.replace('-', u'\u2212')
-
- else:
- # standard C formatting
- try:
- out = ('%' + farg + ftype) % num
- except:
- out = _formaterror
-
- format = format[:m.start()] + out + format[m.end():]
-
- return format
-
def validLinePoints(x, y):
"""Take x and y points and split into sets of points which
don't have invalid points.
@@ -370,93 +212,6 @@
if last < x.shape[0]-1:
yield x[last:], y[last:]
-# This is Tim Peter's topological sort
-# see http://www.python.org/tim_one/000332.html
-# adapted to use later python features
-
-def topsort(pairlist):
- """Given a list of pairs, perform a topological sort.
- That means, each item has something which needs to be done first.
-
- topsort( [(1,2), (3,4), (5,6), (1,3), (1,5), (1,6), (2,5)] )
- returns [1, 2, 3, 5, 4, 6]
- """
-
- numpreds = {} # elt -> # of predecessors
- successors = {} # elt -> list of successors
- for first, second in pairlist:
- # make sure every elt is a key in numpreds
- if not numpreds.has_key( first ):
- numpreds[first] = 0
-
- if not numpreds.has_key( second ):
- numpreds[second] = 0
-
- # since first < second, second gains a pred ...
- numpreds[second] += 1
-
- # ... and first gains a succ
- if successors.has_key( first ):
- successors[first].append( second )
- else:
- successors[first] = [second]
-
- # suck up everything without a predecessor
- answer = [key for key, item in numpreds.iteritems()
- if item == 0]
-
- # for everything in answer, knock down the pred count on
- # its successors; note that answer grows *in* the loop
-
- for x in answer:
- del numpreds[x]
- if successors.has_key( x ):
- for y in successors[x]:
- numpreds[y] -= 1
- if numpreds[y] == 0:
- answer.append( y )
- # following del; isn't needed; just makes
- # CycleError details easier to grasp
- # del successors[x]
-
- # assert catches cycle errors
- assert not numpreds
-
- return answer
-
-class _NoneSoFar:
- pass
-_NoneSoFar = _NoneSoFar()
-
-def lazy(func, resultclass):
- """A decorator to allow lazy evaluation of functions.
- The products of this function is a lazy version of the function
- given.
-
- func is the function to evaluate
- resultclass is the class this function returns."""
-
- class __proxy__:
- def __init__(self, args, kw):
- self.__func = func
- self.__args = args
- self.__kw = kw
- self.__result = _NoneSoFar
- for (k, v) in resultclass.__dict__.items():
- setattr(self, k, self.__promise__(v))
-
- def __promise__(self, func):
- def __wrapper__(*args, **kw):
- if self.__result is _NoneSoFar:
- self.__result = self.__func(*self.__args, **self.__kw)
- return func(self.__result, *args, **kw)
- return __wrapper__
-
- def __wrapper__(*args, **kw):
- return __proxy__(args, kw)
-
- return __wrapper__
-
class NonBlockingReaderThread(threading.Thread):
"""A class to read blocking file objects and return the result.
@@ -544,9 +299,16 @@
]
def openEncoding(filename, encoding, mode='r'):
- """Convenience function for opening file with encoding given."""
- return codecs.open(filename, mode, encoding, 'ignore')
+ """Convenience function for opening file with encoding given.
+ If filename == '{clipboard}', then load the data from the clipboard
+ instead.
+ """
+ if filename == '{clipboard}':
+ text = unicode(qt4.QApplication.clipboard().text())
+ return StringIO.StringIO(text)
+ else:
+ return codecs.open(filename, mode, encoding, 'ignore')
# The following two classes are adapted from the Python documentation
# they are modified to turn off encoding errors
@@ -571,8 +333,18 @@
which is encoded in the given encoding.
"""
- def __init__(self, f, dialect=csv.excel, encoding='utf-8', **kwds):
- f = UTF8Recoder(f, encoding)
+ def __init__(self, filename, dialect=csv.excel, encoding='utf-8', **kwds):
+
+ if filename != '{clipboard}':
+ # recode the opened file as utf-8
+ f = UTF8Recoder(open(filename), encoding)
+ else:
+ # take the unicode clipboard and just put into utf-8 format
+ s = unicode(qt4.QApplication.clipboard().text())
+ s = s.encode('utf-8')
+ f = StringIO.StringIO(s)
+
+ # the actual csv reader based on the file above
self.reader = csv.reader(f, dialect=dialect, **kwds)
def next(self):
@@ -613,3 +385,31 @@
# get index for current value
index = combo.findText(currenttext)
combo.setCurrentIndex(index)
+
+def positionFloatingPopup(popup, widget):
+ """Position a popped up window (popup) to side and below widget given."""
+ pos = widget.parentWidget().mapToGlobal( widget.pos() )
+ desktop = qt4.QApplication.desktop()
+
+ # recalculates out position so that size is correct below
+ popup.adjustSize()
+
+ # is there room to put this widget besides the widget?
+ if pos.y() + popup.height() + 1 < desktop.height():
+ # put below
+ y = pos.y() + 1
+ else:
+ # put above
+ y = pos.y() - popup.height() - 1
+
+ # is there room to the left for us?
+ if ( (pos.x() + widget.width() + popup.width() < desktop.width()) or
+ (pos.x() + widget.width() < desktop.width()/2) ):
+ # put left justified with widget
+ x = pos.x() + widget.width()
+ else:
+ # put extending to left
+ x = pos.x() - popup.width() - 1
+
+ popup.move(x, y)
+ popup.setFocus()
diff -Nru veusz-1.10/utils/version.py veusz-1.14/utils/version.py
--- veusz-1.10/utils/version.py 2010-12-12 12:41:09.000000000 +0000
+++ veusz-1.14/utils/version.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,8 +19,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: version.py 872 2008-12-29 12:51:59Z jeremysanders $
-
"""
Return Veusz' version number
"""
diff -Nru veusz-1.10/VERSION veusz-1.14/VERSION
--- veusz-1.10/VERSION 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/VERSION 2011-11-22 20:25:39.000000000 +0000
@@ -1 +1 @@
-1.10
+1.14
diff -Nru veusz-1.10/veusz_listen.py veusz-1.14/veusz_listen.py
--- veusz-1.10/veusz_listen.py 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/veusz_listen.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: veusz_listen.py 1463 2010-12-01 17:06:14Z jeremysanders $
-
"""
Veusz interface which listens to stdin, and receives commands.
Results are written to stdout
diff -Nru veusz-1.10/veusz_main.py veusz-1.14/veusz_main.py
--- veusz-1.10/veusz_main.py 2010-12-12 12:41:12.000000000 +0000
+++ veusz-1.14/veusz_main.py 2011-11-22 20:23:31.000000000 +0000
@@ -1,8 +1,5 @@
#!/usr/bin/env python
-# veusz.py
-# Main veusz program file
-
# Copyright (C) 2004 Jeremy S. Sanders
# Email: Jeremy Sanders
#
@@ -21,7 +18,8 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: veusz_main.py 1460 2010-11-29 21:32:08Z jeremysanders $
+"""Main Veusz executable.
+"""
import sys
import os.path
@@ -45,12 +43,12 @@
copyr='''Veusz %s
-Copyright (C) Jeremy Sanders 2003-2010
+Copyright (C) Jeremy Sanders 2003-2011 and contributors
Licenced under the GNU General Public Licence (version 2 or greater)
'''
splashcopyr='''Veusz %s
-Copyright (C) Jeremy Sanders 2003-2010
+Copyright (C) Jeremy Sanders 2003-2011 and contributors
Licenced under the GPL (version 2 or greater)
'''
@@ -92,11 +90,6 @@
d = ExceptionDialog((excepttype, exceptvalue, tracebackobj), None)
d.exec_()
-def embedremote():
- '''For running with --remote-embed option.'''
- from veusz.embed_remote import remote
- remote.main()
-
def listen(args, quiet):
'''For running with --listen option.'''
from veusz.veusz_listen import openWindow
@@ -122,9 +115,31 @@
# create blank window
MainWindow.CreateWindow()
+def convertArgsUnicode(args):
+ '''Convert set of arguments to unicode.
+ Arguments in argv use current file system encoding
+ '''
+ enc = sys.getfilesystemencoding()
+ # bail out if not supported
+ if enc is None:
+ return args
+ out = []
+ for a in args:
+ if isinstance(a, str):
+ out.append( a.decode(enc) )
+ else:
+ out.append(a)
+ return out
+
def run():
'''Run the main application.'''
+ # jump to the embedding client entry point if required
+ if len(sys.argv) == 2 and sys.argv[1] == '--embed-remote':
+ from veusz.embed_remote import runremote
+ runremote()
+ return
+
# this function is spaghetti-like and has nasty code paths.
# the idea is to postpone the imports until the splash screen
# is shown
@@ -155,12 +170,17 @@
' output image file, exiting when finished')
parser.add_option('--embed-remote', action='store_true',
help=optparse.SUPPRESS_HELP)
-
+ parser.add_option('--plugin', action='append', metavar='FILE',
+ help='load the plugin from the file given for '
+ 'the session')
options, args = parser.parse_args( app.argv() )
- # show splash in normal mode
+ # convert args to unicode from filesystem strings
+ args = convertArgsUnicode(args)
+
splash = None
- if not options.embed_remote and not options.listen and not options.export:
+ if not (options.listen or options.export):
+ # show the splash screen on normal start
splash = qt4.QSplashScreen(makeSplashLogo())
splash.show()
app.processEvents()
@@ -174,18 +194,24 @@
veusz.setting.transient_settings['unsafe_mode'] = bool(
options.unsafe_mode)
- # these are the different modes
- if options.embed_remote:
- embedremote()
- elif options.listen:
+ # load any requested plugins
+ if options.plugin:
+ import veusz.document
+ veusz.document.Document.loadPlugins(pluginlist=options.plugin)
+
+ # different modes
+ if options.listen:
+ # listen to incoming commands
listen(args, quiet=options.quiet)
elif options.export:
+ # export files to make images
if len(options.export) != len(args)-1:
parser.error(
'export option needs same number of documents and output files')
export(options.export, args)
return
else:
+ # standard start main window
mainwindow(args)
# clear splash when startup done
diff -Nru veusz-1.10/widgets/axis.py veusz-1.14/widgets/axis.py
--- veusz-1.10/widgets/axis.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/axis.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: axis.py 1412 2010-09-25 08:42:01Z jeremysanders $
-
"""Widget to plot axes, and to handle conversion of coordinates to plot
positions."""
@@ -109,9 +107,10 @@
descr = 'Place axis label close to edge'
' of graph',
usertext='At edge') )
- self.add( setting.Bool( 'rotate', False,
- descr = 'Rotate the label by 90 degrees',
- usertext='Rotate') )
+ self.add( setting.RotateInterval(
+ 'rotate', '0',
+ descr = 'Angle by which to rotate label by',
+ usertext='Rotate') )
self.add( setting.DistancePt( 'offset',
'0pt',
descr = 'Additional offset of axis label'
@@ -133,9 +132,10 @@
def __init__(self, name, **args):
setting.Text.__init__(self, name, **args)
- self.add( setting.Bool( 'rotate', False,
- descr = 'Rotate the label by 90 degrees',
- usertext='Rotate') )
+ self.add( setting.RotateInterval(
+ 'rotate', '0',
+ descr = 'Angle by which to rotate label by',
+ usertext='Rotate') )
self.add( setting.ChoiceOrMore( 'format',
TickLabel.formatchoices,
'Auto',
@@ -195,12 +195,12 @@
s.add( setting.Str('label', '',
descr='Axis label text',
usertext='Label') )
- s.add( setting.FloatOrAuto('min', 'Auto',
- descr='Minimum value of axis',
- usertext='Min') )
- s.add( setting.FloatOrAuto('max', 'Auto',
- descr='Maximum value of axis',
- usertext='Max') )
+ s.add( setting.AxisBound('min', 'Auto',
+ descr='Minimum value of axis',
+ usertext='Min') )
+ s.add( setting.AxisBound('max', 'Auto',
+ descr='Maximum value of axis',
+ usertext='Max') )
s.add( setting.Bool('log', False,
descr = 'Whether axis is logarithmic',
usertext='Log') )
@@ -324,7 +324,7 @@
# locate widget we're matching
# this is ensured to be an Axis
try:
- widget = s.get('match').getWidget()
+ widget = s.get('match').getReferredWidget()
except setting.InvalidType:
widget = None
@@ -359,9 +359,9 @@
# make sure log axes don't blow up
if s.log:
- if self.plottedrange[0] <= 0.:
+ if self.plottedrange[0] < 1e-99:
self.plottedrange[0] = 1e-99
- if self.plottedrange[1] <= 0.:
+ if self.plottedrange[1] < 1e-99:
self.plottedrange[1] = 1e-99
if self.plottedrange[0] == self.plottedrange[1]:
self.plottedrange[1] = self.plottedrange[0]*2
@@ -378,9 +378,12 @@
extendzero = s.autoExtendZero,
logaxis = s.log )
- (self.plottedrange[0],self.plottedrange[1],
- self.majortickscalc, self.minortickscalc,
- self.autoformat) = axs.getTicks()
+ axs.getTicks()
+ self.plottedrange[0] = axs.minval
+ self.plottedrange[1] = axs.maxval
+ self.majortickscalc = axs.tickvals
+ self.minortickscalc = axs.minorticks
+ self.autoformat = axs.autoformat
# override values if requested
if len(s.MajorTicks.manualTicks) > 0:
@@ -426,8 +429,8 @@
# other axis coordinates
self.coordPerp = y2 - dy*otherposition
- self.coordPerp1 = y2 - dy*p1
- self.coordPerp2 = y2 - dy*p2
+ self.coordPerp1 = y1
+ self.coordPerp2 = y2
else: # vertical
self.coordParr1 = y2 - dy*p1
@@ -435,8 +438,8 @@
# other axis coordinates
self.coordPerp = x1 + dx*otherposition
- self.coordPerp1 = x1 + dx*p1
- self.coordPerp2 = x1 + dx*p2
+ self.coordPerp1 = x1
+ self.coordPerp2 = x2
# is this axis reflected
if otherposition > 0.5:
@@ -575,9 +578,21 @@
a = (b1, a1, b2, a2)
utils.plotLinesToPainter(painter, a[0], a[1], a[2], a[3])
- def _drawGridLines(self, subset, painter, coordticks):
+ def _drawGridLines(self, subset, painter, coordticks, parentposn):
"""Draw grid lines on the plot."""
painter.setPen( self.settings.get(subset).makeQPen(painter) )
+
+ # drop points which overlap with graph box (if used)
+ if self.parent.typename == 'graph':
+ if not self.parent.settings.Border.hide:
+ if self.settings.direction == 'horizontal':
+ ok = ( (N.abs(coordticks-parentposn[0]) > 1e-3) &
+ (N.abs(coordticks-parentposn[2]) > 1e-3) )
+ else:
+ ok = ( (N.abs(coordticks-parentposn[1]) > 1e-3) &
+ (N.abs(coordticks-parentposn[3]) > 1e-3) )
+ coordticks = coordticks[ok]
+
self.swaplines(painter,
coordticks, coordticks*0.+self.coordPerp1,
coordticks, coordticks*0.+self.coordPerp2)
@@ -610,9 +625,9 @@
delta *= -1
y = coordminorticks*0.+self.coordPerp
- self.swaplines(painter,
- coordminorticks, y,
- coordminorticks, y-delta)
+ self.swaplines( painter,
+ coordminorticks, y,
+ coordminorticks, y-delta )
def _drawMajorTicks(self, painter, tickcoords):
"""Draw major ticks on the plot."""
@@ -633,19 +648,19 @@
delta *= -1
y = tickcoords*0.+self.coordPerp
- self.swaplines(painter,
- tickcoords, y,
- tickcoords, y-delta)
+ self.swaplines( painter,
+ tickcoords, y,
+ tickcoords, y-delta )
# account for ticks if they are in the direction of the label
if s.outerticks and not self.coordReflected:
self._delta_axis += abs(delta)
- def generateLabelLabels(self, painter):
+ def generateLabelLabels(self, phelper):
"""Generate list of positions and labels from widgets using this
axis."""
try:
- plotters = painter.veusz_axis_plotter_map[self]
+ plotters = phelper.axisplottermap[self]
except (AttributeError, KeyError):
return
@@ -663,7 +678,7 @@
if N.isfinite(coord) and (minval <= coord <= maxval):
yield pcoord, lab
- def _drawTickLabels(self, painter, coordticks, sign, outerbounds,
+ def _drawTickLabels(self, phelper, painter, coordticks, sign, outerbounds,
texttorender):
"""Draw tick labels on the plot.
@@ -679,13 +694,9 @@
tl_spacing = fm.leading() + fm.descent()
# work out font alignment
- if s.TickLabels.rotate:
- if self.coordReflected:
- angle = 90
- else:
- angle = 270
- else:
- angle = 0
+ angle = int(s.TickLabels.rotate)
+ if not self.coordReflected and angle != 0:
+ angle = 360-angle
if vertical:
# limit tick labels to be directly below/besides axis
@@ -717,7 +728,8 @@
# generate positions and labels
for posn, tickval in izip(coordticks, self.majortickscalc):
- text = utils.formatNumber(tickval*scale, format)
+ text = utils.formatNumber(tickval*scale, format,
+ locale=self.document.locale)
yield posn, text
# position of label perpendicular to axis
@@ -725,7 +737,7 @@
# use generator function to get labels and positions
if s.mode == 'labels':
- ticklabels = self.generateLabelLabels(painter)
+ ticklabels = self.generateLabelLabels(phelper)
else:
ticklabels = generateTickLabels()
@@ -795,15 +807,16 @@
if reflected:
ax, ay = -ax, -ay
- # angle of text
- if ( (horz and not sl.rotate) or
- (not horz and sl.rotate) ):
- angle = 0
+ # angle of text (logic is slightly complex)
+ angle = int(sl.rotate)
+ if horz:
+ if not reflected:
+ angle = 360-angle
else:
+ angle = angle+270
if reflected:
- angle = 90
- else:
- angle = 270
+ angle = 360-angle
+ angle = angle % 360
x = 0.5*(self.coordParr1 + self.coordParr2)
y = self.coordPerp + sign*(self._delta_axis+al_spacing)
@@ -912,10 +925,13 @@
return True
return False
- def draw(self, parentposn, painter, outerbounds=None):
+ def draw(self, parentposn, phelper, outerbounds=None,
+ useexistingpainter=None):
"""Plot the axis on the painter.
- if suppresstext is True, then we don't number or label the axis
+ useexistingpainter is a hack so that a colorbar can reuse the
+ drawing code here. If set to a painter, it will use this rather
+ than opening a new one.
"""
s = self.settings
@@ -924,17 +940,19 @@
if self.docchangeset != self.document.changeset:
self._computePlottedRange()
- posn = widget.Widget.draw(self, parentposn, painter, outerbounds)
+ posn = widget.Widget.draw(self, parentposn, phelper, outerbounds)
self._updatePlotRange(posn)
+ # get ready to draw
+ if useexistingpainter is not None:
+ painter = useexistingpainter
+ else:
+ painter = phelper.painter(self, posn)
+
# make control item for axis
- self.controlgraphitems = [
- controlgraph.ControlAxisLine(self, s.direction,
- self.coordParr1,
- self.coordParr2,
- self.coordPerp,
- posn)
- ]
+ phelper.setControlGraph(self, [ controlgraph.ControlAxisLine(
+ self, s.direction, self.coordParr1,
+ self.coordParr2, self.coordPerp, posn) ])
# get tick vals
coordticks = self._graphToPlotter(self.majortickscalc)
@@ -944,10 +962,6 @@
if s.hide:
return
- # save the state of the painter for later
- painter.beginPaintingWidget(self, posn)
- painter.save()
-
texttorender = []
# multiplication factor if reflection on the axis is requested
@@ -959,9 +973,11 @@
# plot gridlines
if not s.MinorGridLines.hide:
- self._drawGridLines('MinorGridLines', painter, coordminorticks)
+ self._drawGridLines('MinorGridLines', painter, coordminorticks,
+ parentposn)
if not s.GridLines.hide:
- self._drawGridLines('GridLines', painter, coordticks)
+ self._drawGridLines('GridLines', painter, coordticks,
+ parentposn)
# plot the line along the axis
if not s.Line.hide:
@@ -978,19 +994,11 @@
if not s.MajorTicks.hide:
self._drawMajorTicks(painter, coordticks)
- # debugging
- #painter.save()
- #painter.setPen(qt4.QPen(qt4.Qt.blue))
- #painter.drawRect(
- # qt4.QRectF(qt4.QPointF(outerbounds[0], outerbounds[1]),
- # qt4.QPointF(outerbounds[2], outerbounds[3])) )
- #painter.restore()
-
# plot tick labels
suppresstext = self._suppressText(painter, parentposn, outerbounds)
if not s.TickLabels.hide and not suppresstext:
- self._drawTickLabels(painter, coordticks, sign, outerbounds,
- texttorender)
+ self._drawTickLabels(phelper, painter, coordticks, sign,
+ outerbounds, texttorender)
# draw an axis label
if not s.Label.hide and not suppresstext:
@@ -1012,36 +1020,48 @@
painter.setPen(pen)
box = r.render()
drawntext.addRect(rect)
-
- # restore the state of the painter
- painter.restore()
-
- painter.endPaintingWidget()
-
+
def updateControlItem(self, cgi):
"""Update axis position from control item."""
s = self.settings
p = cgi.maxposn
- if s.direction == 'horizontal':
- minfrac = abs((cgi.minpos - p[0]) / (p[2] - p[0]))
- maxfrac = abs((cgi.maxpos - p[0]) / (p[2] - p[0]))
- axisfrac = abs((cgi.axispos - p[3]) / (p[1] - p[3]))
- else:
- minfrac = abs((cgi.minpos - p[3]) / (p[1] - p[3]))
- maxfrac = abs((cgi.maxpos - p[3]) / (p[1] - p[3]))
- axisfrac = abs((cgi.axispos - p[0]) / (p[2] - p[0]))
-
- if minfrac > maxfrac:
- minfrac, maxfrac = maxfrac, minfrac
-
- operations = (
- document.OperationSettingSet(s.get('lowerPosition'), minfrac),
- document.OperationSettingSet(s.get('upperPosition'), maxfrac),
- document.OperationSettingSet(s.get('otherPosition'), axisfrac),
- )
- self.document.applyOperation(
- document.OperationMultiple(operations, descr='adjust axis'))
+
+ if cgi.zoomed():
+ # zoom axis scale
+ c1, c2 = self.plotterToGraphCoords(
+ cgi.maxposn, N.array([cgi.minzoom, cgi.maxzoom]))
+ if c1 > c2:
+ c1, c2 = c2, c1
+ operations = (
+ document.OperationSettingSet(s.get('min'), float(c1)),
+ document.OperationSettingSet(s.get('max'), float(c2)),
+ document.OperationSettingSet(s.get('autoExtend'), False),
+ document.OperationSettingSet(s.get('autoExtendZero'), False),
+ )
+ self.document.applyOperation(
+ document.OperationMultiple(operations, descr='zoom axis'))
+ elif cgi.moved():
+ # move axis
+ # convert positions to fractions
+ pt1, pt2, ppt1, ppt2 = ( (3, 1, 0, 2), (0, 2, 3, 1)
+ ) [s.direction == 'horizontal']
+ minfrac = abs((cgi.minpos - p[pt1]) / (p[pt2] - p[pt1]))
+ maxfrac = abs((cgi.maxpos - p[pt1]) / (p[pt2] - p[pt1]))
+ axisfrac = abs((cgi.axispos - p[ppt1]) / (p[ppt2] - p[ppt1]))
+
+ # swap if wrong way around
+ if minfrac > maxfrac:
+ minfrac, maxfrac = maxfrac, minfrac
+
+ # update doc
+ operations = (
+ document.OperationSettingSet(s.get('lowerPosition'), minfrac),
+ document.OperationSettingSet(s.get('upperPosition'), maxfrac),
+ document.OperationSettingSet(s.get('otherPosition'), axisfrac),
+ )
+ self.document.applyOperation(
+ document.OperationMultiple(operations, descr='adjust axis'))
# allow the factory to instantiate an axis
document.thefactory.register( Axis )
diff -Nru veusz-1.10/widgets/axisticks.py veusz-1.14/widgets/axisticks.py
--- veusz-1.10/widgets/axisticks.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/axisticks.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,8 +19,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: axisticks.py 1120 2010-01-16 22:34:31Z jeremysanders $
-
import math
import numpy as N
@@ -41,7 +39,8 @@
def __init__( self, minval, maxval, numticks, numminorticks,
logaxis = False, prefermore = True,
- extendbounds = True, extendzero = True ):
+ extendbounds = True, extendzero = True,
+ forceinterval = None ):
"""Initialise the class.
minval and maxval are the range of the data to be plotted
@@ -49,7 +48,10 @@
logaxis: axis logarithmic?
prefermore: prefer more ticks rather than fewer
extendbounds: extend minval and maxval to nearest tick if okay
- extendzero: extend one end to zero if it is okay"""
+ extendzero: extend one end to zero if it is okay
+ forceinterval: force interval to one given (if allowed). interval
+ is tuple as returned in self.interval after calling getTicks()
+ """
self.minval = minval
self.maxval = maxval
@@ -59,11 +61,14 @@
self.prefermore = prefermore
self.extendbounds = extendbounds
self.extendzero = extendzero
+ self.forceinterval = forceinterval
def getTicks( self ):
"""Calculate and return the position of the major ticks.
- Returns a tuple (minval, maxval, majorticks, minorticks)"""
+ Results are returned as attributes of this object in
+ interval, minval, maxval, tickvals, minorticks, autoformat
+ """
class AxisTicks(AxisTicksBase):
"""Class to work out at what values axis major ticks should appear."""
@@ -94,7 +99,7 @@
startmult = int( math.ceil( minval / delta ) )
stopmult = int( math.floor( maxval / delta ) )
-
+
return N.arange(startmult, stopmult+1) * delta
def _tickNums(self, minval, maxval, delta):
@@ -201,9 +206,40 @@
ticks.append(v)
return N.array( ticks )
-
- def _axisScaler(self, allowed_intervals):
- """With minval and maxval find best tick positions."""
+
+ def _selectBestTickFromSelection(self, selection):
+ """Choose best tick from selection given."""
+ # we now try to find the best matching value
+ minabsdelta = 1e99
+ mindelta = 1e99
+ bestsel = ()
+
+ # find the best set of tick labels
+ for s in selection:
+ # difference between what we want and what we have
+ delta = s[0] - self.numticks
+ absdelta = abs(delta)
+
+ # if it matches better choose this
+ if absdelta < minabsdelta:
+ minabsdelta = absdelta
+ mindelta = delta
+ bestsel = s
+
+ # if we find two closest matching label sets, we
+ # test whether we prefer too few to too many labels
+ if absdelta == minabsdelta:
+ if (self.prefermore and (delta > mindelta)) or \
+ (not self.prefermore and (delta < mindelta)):
+ minabsdelta = absdelta
+ mindelta = delta
+ bestsel = s
+
+ return bestsel
+
+ def _getBestTickSelection(self, allowed_intervals):
+ """Go through allowed tick intervals and find one best matching
+ requested parameters."""
# work out range and log range
therange = self.maxval - self.minval
@@ -217,7 +253,9 @@
# Maybe a better algorithm is required
selection = []
+ # keep track of largest number of ticks calculated
largestno = 0
+
while True:
for interval in allowed_intervals:
no, minval, maxval = self._calcNoTicks( interval, logstep )
@@ -235,48 +273,32 @@
if logstep < 0 and self.logaxis:
break
- # we now try to find the best matching value
- minabsdelta = 1e99
- mindelta = 1e99
- bestsel = ()
+ return selection
- # find the best set of tick labels
- for s in selection:
- # difference between what we want and what we have
- delta = s[0] - self.numticks
- absdelta = abs(delta)
-
- # if it matches better choose this
- if absdelta < minabsdelta:
- minabsdelta = absdelta
- mindelta = delta
- bestsel = s
-
- # if we find two closest matching label sets, we
- # test whether we prefer too few to too many labels
- if absdelta == minabsdelta:
- if (self.prefermore and (delta > mindelta)) or \
- (not self.prefermore and (delta < mindelta)):
- minabsdelta = absdelta
- mindelta = delta
- bestsel = s
+ def _tickSelector(self, allowed_intervals):
+ """With minval and maxval find best tick positions."""
- # now we have the best, we work out the ticks and return
- interval = bestsel[1]
- loginterval = bestsel[2]
+ if self.forceinterval is None:
+ # get selection of closely matching ticks
+ selection = self._getBestTickSelection(allowed_intervals)
+
+ # now we have the best, we work out the ticks and return
+ bestsel = self._selectBestTickFromSelection(selection)
+ dummy, interval, loginterval, minval, maxval = bestsel
+ else:
+ # forced specific interval requested
+ interval, loginterval = self.forceinterval
+ no, minval, maxval = self._calcNoTicks(interval, loginterval)
+ # calculate the positions of the ticks from parameters
tickdelta = interval * 10.**loginterval
- minval = bestsel[3]
- maxval = bestsel[4]
-
- # calculate the positions of the ticks
ticks = self._calcTickValues( minval, maxval, tickdelta )
+
return (minval, maxval, ticks, interval, loginterval)
- def getTicks( self ):
+ def getTicks(self):
"""Calculate and return the position of the major ticks.
-
- Returns a tuple (minval, maxval, majorticks, minorticks)"""
+ """
if self.logaxis:
# which intervals we'll accept for major ticks
@@ -285,30 +307,31 @@
# transform range into log space
self.minval = N.log10( self.minval )
self.maxval = N.log10( self.maxval )
-
else:
# which linear intervals we'll allow
intervals = AxisTicks.allowed_intervals_linear
-
- minval, maxval, tickvals, interval, loginterval = self._axisScaler( intervals )
+
+ minval, maxval, tickvals, interval, loginterval = self._tickSelector(
+ intervals )
# work out the most appropriate minor tick intervals
if not self.logaxis:
# just plain minor ticks
# try to achieve no of minors close to value requested
-
+
minorticks = self._calcLinearMinorTickValues(
minval, maxval, interval, loginterval,
AxisTicks.allowed_minorintervals_linear[interval]
)
else:
+ # log axis
if interval == 1.:
# calculate minor ticks
# here we use 'conventional' minor log tick spacing
# e.g. 0.9, 1, 2, .., 8, 9, 10, 20, 30 ...
- minorticks = self._calcLogMinorTickValues(10.**minval,
- 10.**maxval)
+ minorticks = self._calcLogMinorTickValues(
+ 10.**minval, 10.**maxval)
# Here we test whether more log major tick values are needed...
# often we might only have one tick value, and so we add 2, then 5
@@ -323,25 +346,30 @@
n = low10 + math.log10(i)
if n >= minval and n <= maxval:
tickvals = N.concatenate( (tickvals, N.array([n]) ))
-
+
else:
# if we increase by more than one power of 10 on the
# axis, we can't do the above, so we do linear ticks
# in log space
# aim is to choose powers of 3 for majors and minors
# to make it easy to read the axis. comments?
-
- minorticks = self._calcLinearMinorTickValues\
- (minval, maxval, interval, loginterval,
- AxisTicks.allowed_minorintervals_log)
+
+ minorticks = self._calcLinearMinorTickValues(
+ minval, maxval, interval, loginterval,
+ AxisTicks.allowed_minorintervals_log)
minorticks = 10.**minorticks
-
+
# transform normal ticks back to real space
minval = 10.**minval
maxval = 10.**maxval
tickvals = 10.**tickvals
-
- return (minval, maxval, tickvals, minorticks, '%Vg')
+
+ self.interval = (interval, loginterval)
+ self.minorticks = minorticks
+ self.minval = minval
+ self.maxval = maxval
+ self.tickvals = tickvals
+ self.autoformat = '%Vg'
class DateTicks(AxisTicksBase):
"""For formatting dates. We want something that chooses appropriate
@@ -349,58 +377,58 @@
So we want to choose most apropriate interval depending on number of
ticks requested
"""
-
+
# possible intervals for a time/date axis
# tuples of ((y, m, d, h, m, s, msec), autoformat)
intervals = (
- ((200, 0, 0, 0, 0, 0, 0), '%VDY'),
- ((100, 0, 0, 0, 0, 0, 0), '%VDY'),
- ((50, 0, 0, 0, 0, 0, 0), '%VDY'),
+ ((200, 0, 0, 0, 0, 0, 0), '%VDY'),
+ ((100, 0, 0, 0, 0, 0, 0), '%VDY'),
+ ((50, 0, 0, 0, 0, 0, 0), '%VDY'),
((20, 0, 0, 0, 0, 0, 0), '%VDY'),
- ((10, 0, 0, 0, 0, 0, 0), '%VDY'),
- ((5, 0, 0, 0, 0, 0, 0), '%VDY'),
- ((2, 0, 0, 0, 0, 0, 0), '%VDY'),
- ((1, 0, 0, 0, 0, 0, 0), '%VDY'),
- ((0, 6, 0, 0, 0, 0, 0), '%VDY-%VDm'),
+ ((10, 0, 0, 0, 0, 0, 0), '%VDY'),
+ ((5, 0, 0, 0, 0, 0, 0), '%VDY'),
+ ((2, 0, 0, 0, 0, 0, 0), '%VDY'),
+ ((1, 0, 0, 0, 0, 0, 0), '%VDY'),
+ ((0, 6, 0, 0, 0, 0, 0), '%VDY-%VDm'),
((0, 4, 0, 0, 0, 0, 0), '%VDY-%VDm'),
- ((0, 3, 0, 0, 0, 0, 0), '%VDY-%VDm'),
+ ((0, 3, 0, 0, 0, 0, 0), '%VDY-%VDm'),
((0, 2, 0, 0, 0, 0, 0), '%VDY-%VDm'),
- ((0, 1, 0, 0, 0, 0, 0), '%VDY-%VDm'),
- ((0, 0, 28, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
- ((0, 0, 14, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
- ((0, 0, 7, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
- ((0, 0, 2, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
- ((0, 0, 1, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
- ((0, 0, 0, 12, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'),
- ((0, 0, 0, 6, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'),
- ((0, 0, 0, 4, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'),
- ((0, 0, 0, 3, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'),
- ((0, 0, 0, 2, 0, 0, 0), '%VDH:%VDM'),
- ((0, 0, 0, 1, 0, 0, 0), '%VDH:%VDM'),
- ((0, 0, 0, 0, 30, 0, 0), '%VDH:%VDM'),
- ((0, 0, 0, 0, 15, 0, 0), '%VDH:%VDM'),
- ((0, 0, 0, 0, 10, 0, 0), '%VDH:%VDM'),
- ((0, 0, 0, 0, 5, 0, 0), '%VDH:%VDM'),
- ((0, 0, 0, 0, 2, 0, 0), '%VDH:%VDM'),
+ ((0, 1, 0, 0, 0, 0, 0), '%VDY-%VDm'),
+ ((0, 0, 28, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
+ ((0, 0, 14, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
+ ((0, 0, 7, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
+ ((0, 0, 2, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
+ ((0, 0, 1, 0, 0, 0, 0), '%VDY-%VDm-%VDd'),
+ ((0, 0, 0, 12, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'),
+ ((0, 0, 0, 6, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'),
+ ((0, 0, 0, 4, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'),
+ ((0, 0, 0, 3, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'),
+ ((0, 0, 0, 2, 0, 0, 0), '%VDH:%VDM'),
+ ((0, 0, 0, 1, 0, 0, 0), '%VDH:%VDM'),
+ ((0, 0, 0, 0, 30, 0, 0), '%VDH:%VDM'),
+ ((0, 0, 0, 0, 15, 0, 0), '%VDH:%VDM'),
+ ((0, 0, 0, 0, 10, 0, 0), '%VDH:%VDM'),
+ ((0, 0, 0, 0, 5, 0, 0), '%VDH:%VDM'),
+ ((0, 0, 0, 0, 2, 0, 0), '%VDH:%VDM'),
((0, 0, 0, 0, 1, 0, 0), '%VDH:%VDM'),
- ((0, 0, 0, 0, 0, 30, 0), '%VDH:%VDM:%VDS'),
- ((0, 0, 0, 0, 0, 15, 0), '%VDH:%VDM:%VDS'),
- ((0, 0, 0, 0, 0, 10, 0), '%VDH:%VDM:%VDS'),
- ((0, 0, 0, 0, 0, 5, 0), '%VDH:%VDM:%VDS'),
- ((0, 0, 0, 0, 0, 2, 0), '%VDH:%VDM:%VDS'),
- ((0, 0, 0, 0, 0, 1, 0), '%VDH:%VDM:%VDS'),
- ((0, 0, 0, 0, 0, 0, 500000), '%VDH:%VDM:%VDVS'),
- ((0, 0, 0, 0, 0, 0, 200000), '%VDVS'),
- ((0, 0, 0, 0, 0, 0, 100000), '%VDVS'),
- ((0, 0, 0, 0, 0, 0, 50000), '%VDVS'),
- ((0, 0, 0, 0, 0, 0, 10000), '%VDVS'),
+ ((0, 0, 0, 0, 0, 30, 0), '%VDH:%VDM:%VDS'),
+ ((0, 0, 0, 0, 0, 15, 0), '%VDH:%VDM:%VDS'),
+ ((0, 0, 0, 0, 0, 10, 0), '%VDH:%VDM:%VDS'),
+ ((0, 0, 0, 0, 0, 5, 0), '%VDH:%VDM:%VDS'),
+ ((0, 0, 0, 0, 0, 2, 0), '%VDH:%VDM:%VDS'),
+ ((0, 0, 0, 0, 0, 1, 0), '%VDH:%VDM:%VDS'),
+ ((0, 0, 0, 0, 0, 0, 500000), '%VDH:%VDM:%VDVS'),
+ ((0, 0, 0, 0, 0, 0, 200000), '%VDVS'),
+ ((0, 0, 0, 0, 0, 0, 100000), '%VDVS'),
+ ((0, 0, 0, 0, 0, 0, 50000), '%VDVS'),
+ ((0, 0, 0, 0, 0, 0, 10000), '%VDVS'),
)
-
+
intervals_sec = N.array([(ms*1e-6+s+mi*60+hr*60*60+dy*24*60*60+
mn*(365/12.)*24*60*60+
yr*365*24*60*60)
for (yr, mn, dy, hr, mi, s, ms), fmt in intervals])
-
+
def bestTickFinder(self, minval, maxval, numticks, extendbounds,
intervals, intervals_sec):
"""Try to find best choice of numticks ticks between minval and maxval
@@ -416,10 +444,10 @@
tick1 = max(estimated.searchsorted(numticks)-1, 0)
tick2 = min(tick1+1, len(estimated)-1)
-
+
del1 = abs(estimated[tick1] - numticks)
del2 = abs(estimated[tick2] - numticks)
-
+
if del1 < del2:
best = tick1
else:
@@ -432,7 +460,7 @@
# round min and max to nearest
minround = utils.tupleToDateTime(utils.roundDownToTimeTuple(mindate, besttt))
maxround = utils.tupleToDateTime(utils.roundDownToTimeTuple(maxdate, besttt))
-
+
if minround == mindate:
mintick = minround
else:
@@ -450,7 +478,7 @@
if extendbounds and (deltamax != 0. and deltamax < delta*0.15):
maxdate = utils.addTimeTupleToDateTime(maxtick, besttt)
maxtick = maxdate
-
+
# make ticks
ticks = []
dt = mintick
@@ -458,11 +486,11 @@
ticks.append( utils.datetimeToFloat(dt))
dt = utils.addTimeTupleToDateTime(dt, besttt)
- return ( utils.datetimeToFloat(mindate),
+ return ( utils.datetimeToFloat(mindate),
utils.datetimeToFloat(maxdate),
- intervals_sec[best],
+ intervals_sec[best],
N.array(ticks), format )
-
+
def filterIntervals(self, estint):
"""Filter intervals and intervals_sec to be
multiples of estint seconds."""
@@ -477,20 +505,23 @@
def getTicks(self):
"""Calculate and return the position of the major ticks.
-
- Returns a tuple (minval, maxval, majorticks, minorticks, format)"""
+ """
# find minor ticks
- mindate, maxdate, est, ticks, format = self.bestTickFinder(
- self.minval, self.maxval, self.numticks, self.extendbounds,
+ mindate, maxdate, est, ticks, format = self.bestTickFinder(
+ self.minval, self.maxval, self.numticks, self.extendbounds,
self.intervals, self.intervals_sec)
# try to make minor ticks divide evenly into major ticks
intervals, intervals_sec = self.filterIntervals(est)
# get minor ticks
ig, ig, ig, minorticks, ig = self.bestTickFinder(
- mindate, maxdate, self.numminorticks, False,
+ mindate, maxdate, self.numminorticks, False,
intervals, intervals_sec)
- return (mindate, maxdate, ticks, minorticks, format)
-
+ self.interval = (intervals, intervals_sec)
+ self.minval = mindate
+ self.maxval = maxdate
+ self.minorticks = minorticks
+ self.tickvals = ticks
+ self.autoformat = format
diff -Nru veusz-1.10/widgets/bar.py veusz-1.14/widgets/bar.py
--- veusz-1.10/widgets/bar.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/bar.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: bar.py 1449 2010-11-22 09:26:58Z jeremysanders $
-
"""For plotting bar graphs."""
from itertools import izip, repeat
@@ -156,11 +154,11 @@
positions = s.get('posn').getData(doc)
if positions is None:
lengths = s.get('lengths').getData(doc)
- if lengths is None:
+ if not lengths:
return (None, None)
p = N.arange( max([len(d.data) for d in lengths]) )+1.
else:
- p = positions
+ p = positions.data
return (labels, p)
@@ -427,10 +425,10 @@
qt4.QPointF(x+width, y+height*0.8)) )
- def draw(self, parentposn, painter, outerbounds=None):
+ def draw(self, parentposn, phelper, outerbounds=None):
"""Plot the data on a plotter."""
- widgetposn = GenericPlotter.draw(self, parentposn, painter,
+ widgetposn = GenericPlotter.draw(self, parentposn, phelper,
outerbounds=outerbounds)
s = self.settings
@@ -477,17 +475,13 @@
dsvals.append(vals)
# clip data within bounds of plotter
- painter.beginPaintingWidget(self, widgetposn)
- painter.save()
- clip = self.clipAxesBounds(painter, axes, widgetposn)
+ clip = self.clipAxesBounds(axes, widgetposn)
+ painter = phelper.painter(self, widgetposn, clip=clip)
# actually do the drawing
fn = {'stacked': self.barDrawStacked,
'grouped': self.barDrawGroup}[s.mode]
fn(painter, barposns, maxwidth, dsvals, axes, widgetposn, clip)
- painter.restore()
- painter.endPaintingWidget()
-
# allow the factory to instantiate a bar plotter
document.thefactory.register( BarPlotter )
diff -Nru veusz-1.10/widgets/boxplot.py veusz-1.14/widgets/boxplot.py
--- veusz-1.10/widgets/boxplot.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/boxplot.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: boxplot.py 1470 2010-12-09 09:39:12Z jeremysanders $
-
"""For making box plots."""
import math
@@ -41,7 +39,8 @@
# interpolate between indices
frac, index = math.modf(index)
index = int(index)
- interpol = (1-frac)*sortedds[index] + frac*sortedds[index+1]
+ indexplus1 = min(index+1, sortedds.shape[0]-1)
+ interpol = (1-frac)*sortedds[index] + frac*sortedds[indexplus1]
return interpol
def swapline(painter, x1, y1, x2, y2, swap):
@@ -374,18 +373,18 @@
markersize, clip=clip )
# draw mean
- meanplt = axes[not horz].dataToPlotterCoords(posn,
- N.array([stats.mean]))[0]
+ meanplt = axes[not horz].dataToPlotterCoords(
+ posn, N.array([stats.mean]))[0]
if horz:
x, y = meanplt, boxposn
else:
x, y = boxposn, meanplt
utils.plotMarker( painter, x, y, s.meanmarker, markersize )
- def draw(self, parentposn, painter, outerbounds=None):
+ def draw(self, parentposn, phelper, outerbounds=None):
"""Plot the data on a plotter."""
- widgetposn = GenericPlotter.draw(self, parentposn, painter,
+ widgetposn = GenericPlotter.draw(self, parentposn, phelper,
outerbounds=outerbounds)
s = self.settings
@@ -418,10 +417,8 @@
axes[1].settings.direction != 'vertical' ):
return
- # clip data within bounds of plotter
- painter.beginPaintingWidget(self, widgetposn)
- painter.save()
- clip = self.clipAxesBounds(painter, axes, widgetposn)
+ clip = self.clipAxesBounds(axes, widgetposn)
+ painter = phelper.painter(self, widgetposn, clip=clip)
# get boxes visible along direction of boxes to work out width
horz = (s.direction == 'horizontal')
@@ -468,8 +465,5 @@
self.plotBox(painter, axes, vals[6][i], widgetposn,
width, clip, stats)
- painter.restore()
- painter.endPaintingWidget()
-
# allow the factory to instantiate a boxplot
document.thefactory.register( BoxPlot )
diff -Nru veusz-1.10/widgets/colorbar.py veusz-1.14/widgets/colorbar.py
--- veusz-1.10/widgets/colorbar.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/colorbar.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: colorbar.py 1401 2010-09-11 09:28:00Z jeremysanders $
-
"""A colorbar widget for the image widget. Should show the scale of
the image."""
@@ -56,9 +54,10 @@
"""Construct list of settings."""
axis.Axis.addSettings(s)
- s.add( setting.Image('image', '',
- descr = 'Corresponding image',
- usertext = 'Image'), 0 )
+ s.add( setting.WidgetChoice('widgetName', '',
+ descr='Corresponding widget',
+ widgettypes=('image', 'xy'),
+ usertext = 'Widget'), 0 )
s.get('log').readonly = True
s.get('datascale').readonly = True
@@ -97,13 +96,15 @@
usertext='Border'),
pixmap='settings_border')
+ s.add( setting.SettingBackwardCompat('image', 'widgetName', None) )
+
def chooseName(self):
"""Get name of widget."""
# override axis naming of x and y
return widget.Widget.chooseName(self)
- def draw(self, parentposn, painter, outerbounds = None):
+ def draw(self, parentposn, phelper, outerbounds = None):
'''Update the margins before drawing.'''
s = self.settings
@@ -113,11 +114,13 @@
return
# get height of label font
- font = s.get('Label').makeQFont(painter)
+ bounds = self.computeBounds(parentposn, phelper)
+ painter = phelper.painter(self, parentposn)
+
+ font = s.get('Label').makeQFont(phelper)
painter.setFont(font)
fontheight = utils.FontMetrics(font, painter.device()).height()
- bounds = self.computeBounds(parentposn, painter)
horz = s.direction == 'horizontal'
# use above to estimate width and height if necessary
@@ -171,30 +174,41 @@
bounds[1] += (bounds[3]-bounds[1])*s.vertManual
bounds[3] = bounds[1] + totalheight
+ # this is ugly - update bounds in helper state
+ phelper.states[self].bounds = bounds
+
# do no painting if hidden or no image
- imgwidget = s.get('image').findImage()
- if s.hide or not imgwidget:
+ imgwidget = s.get('widgetName').findWidget()
+ if s.hide:
return bounds
# update image if necessary with new settings
- (minval, maxval,
- axisscale, img) = imgwidget.makeColorbarImage(s.direction)
+ if imgwidget is not None:
+ # could find widget
+ (minval, maxval,
+ axisscale, img) = imgwidget.makeColorbarImage(s.direction)
+ else:
+ # couldn't find widget
+ minval, maxval, axisscale = 0., 1., 'linear'
+ img = None
+
self.setAutoRange([minval, maxval])
s.get('log').setSilent(axisscale == 'log')
- painter.beginPaintingWidget(self, bounds)
-
# now draw image on axis...
- minpix, maxpix = self.graphToPlotterCoords( bounds,
- N.array([minval, maxval]) )
+ minpix, maxpix = self.graphToPlotterCoords(
+ bounds, N.array([minval, maxval]) )
if s.direction == 'horizontal':
c = [ minpix, bounds[1], maxpix, bounds[3] ]
else:
c = [ bounds[0], maxpix, bounds[2], minpix ]
r = qt4.QRectF(c[0], c[1], c[2]-c[0], c[3]-c[1])
- painter.drawImage(r, img)
+
+ # really draw the img
+ if img is not None:
+ painter.drawImage(r, img)
# if there's a border
if not s.Border.hide:
@@ -209,10 +223,10 @@
# will mess up range if called twice
savedposition = self.position
self.position = (0., 0., 1., 1.)
- axis.Axis.draw(self, bounds, painter, outerbounds=outerbounds)
- self.position = savedposition
- painter.endPaintingWidget()
-
+ axis.Axis.draw(self, bounds, phelper, outerbounds=outerbounds,
+ useexistingpainter=painter)
+ self.position = savedposition
+
# allow the factory to instantiate a colorbar
document.thefactory.register( ColorBar )
diff -Nru veusz-1.10/widgets/contour.py veusz-1.14/widgets/contour.py
--- veusz-1.10/widgets/contour.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/contour.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: contour.py 1401 2010-09-11 09:28:00Z jeremysanders $
-
"""Contour plotting from 2d datasets.
Contour plotting requires that the veusz_helpers package is installed,
@@ -238,15 +236,15 @@
s = self.settings
d = self.document
- if s.data not in d.data:
- # this dataset doesn't exist
- minval = 0.
- maxval = 1.
- else:
+ minval, maxval = 0., 1.
+ if s.data in d.data:
# scan data
- data = d.data[s.data]
- minval = data.data.min()
- maxval = data.data.max()
+ data = d.data[s.data].data
+ minval, maxval = N.nanmin(data), N.nanmax(data)
+ if not N.isfinite(minval):
+ minval = 0.
+ if not N.isfinite(maxval):
+ maxval = 1.
# override if not auto
if s.min != 'Auto':
@@ -293,7 +291,7 @@
"""Calculate sublevels between contours."""
s = self.settings
num = s.SubLines.numLevels
- if s.SubLines.hide or len(s.SubLines.lines) == 0:
+ if s.SubLines.hide or len(s.SubLines.lines) == 0 or len(levels) <= 1:
return N.array([])
# indices where contour levels should be placed
@@ -364,7 +362,8 @@
if s.keyLevels:
cl = s.get('ContourLabels')
return utils.formatNumber( s.levelsOut[number] * cl.scale,
- cl.format )
+ cl.format,
+ locale=self.document.locale )
else:
return ''
@@ -384,7 +383,7 @@
# return if no data or if the dataset isn't two dimensional
data = d.data.get(s.data, None)
- if data is None or data.dimensions != 2:
+ if data is None or data.dimensions != 2 or data.data.size == 0:
self.contsettings = self.lastdataset = None
s.levelsOut = []
return False
@@ -402,10 +401,10 @@
return True
- def draw(self, parentposn, painter, outerbounds = None):
+ def draw(self, parentposn, phelper, outerbounds = None):
"""Draw the contours."""
- posn = plotters.GenericPlotter.draw(self, parentposn, painter,
+ posn = plotters.GenericPlotter.draw(self, parentposn, phelper,
outerbounds = outerbounds)
s = self.settings
@@ -427,17 +426,13 @@
return
# plot the precalculated contours
- painter.beginPaintingWidget(self, posn)
- painter.save()
- clip = self.clipAxesBounds(painter, axes, posn)
+ clip = self.clipAxesBounds(axes, posn)
+ painter = phelper.painter(self, posn, clip=clip)
self.plotContourFills(painter, posn, axes, clip)
self.plotContours(painter, posn, axes, clip)
self.plotSubContours(painter, posn, axes, clip)
- painter.restore()
- painter.endPaintingWidget()
-
def updateContours(self):
"""Update calculated contours."""
@@ -452,6 +447,9 @@
rangex, rangey = data.getDataRanges()
yw, xw = data.data.shape
+ if xw == 0 or yw == 0:
+ return
+
# arrays containing coordinates of pixels in x and y
xpts = N.fromfunction(lambda y,x:
(x+0.5)*((rangex[1]-rangex[0])/xw) + rangex[0],
@@ -460,13 +458,16 @@
(y+0.5)*((rangey[1]-rangey[0])/yw) + rangey[0],
(yw, xw))
+ # only keep finite data points
+ mask = N.logical_not(N.isfinite(data.data))
+
# iterate over the levels and trace the contours
self._cachedcontours = None
self._cachedpolygons = None
self._cachedsubcontours = None
if self.Cntr is not None:
- c = self.Cntr(xpts, ypts, data.data)
+ c = self.Cntr(xpts, ypts, data.data, mask)
# trace the contour levels
if len(s.Lines.lines) != 0:
@@ -490,13 +491,17 @@
self._cachedsubcontours.append( finitePoly(linelist) )
def plotContourLabel(self, painter, number, xplt, yplt, showline):
+ """Draw a label on a contour.
+ This clips when drawing the line, plotting the label on top.
+ """
s = self.settings
cl = s.get('ContourLabels')
painter.save()
# get text and font
- text = utils.formatNumber(number * cl.scale, cl.format)
+ text = utils.formatNumber(number * cl.scale, cl.format,
+ locale=self.document.locale)
font = cl.makeQFont(painter)
descent = utils.FontMetrics(font, painter.device()).descent()
@@ -535,8 +540,8 @@
painter.restore()
- def _plotContours(self, painter, posn, axes, linestyles, contours,
- showlabels, hidelines, clip):
+ def _plotContours(self, painter, posn, axes, linestyles,
+ contours, showlabels, hidelines, clip):
"""Plot a set of contours.
"""
@@ -562,8 +567,8 @@
utils.addNumpyToPolygonF(pts, xplt, yplt)
if showlabels:
- self.plotContourLabel(painter, s.levelsOut[num], xplt, yplt,
- not hidelines)
+ self.plotContourLabel(painter, s.levelsOut[num],
+ xplt, yplt, not hidelines)
else:
# actually draw the curve to the plotter
if not hidelines:
diff -Nru veusz-1.10/widgets/controlgraph.py veusz-1.14/widgets/controlgraph.py
--- veusz-1.10/widgets/controlgraph.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/controlgraph.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: controlgraph.py 1411 2010-09-25 08:38:27Z jeremysanders $
-
"""
Classes for moving widgets around
@@ -87,14 +85,14 @@
##############################################################################
class ControlMarginBox(object):
- def __init__(self, widget, posn, maxposn, painter,
+ def __init__(self, widget, posn, maxposn, painthelper,
ismovable = True, isresizable = True):
"""Create control box item.
widget: widget this is controllng
posn: coordinates of box [x1, y1, x2, y2]
maxposn: coordinates of biggest possibe box
- painter: painter to get scaling from
+ painthelper: painterhelper to get scaling from
ismovable: box can be moved
isresizable: box can be resized
"""
@@ -107,9 +105,9 @@
self.isresizable = isresizable
# we need these later to convert back to original units
- self.page_size = painter.veusz_page_size
- self.scaling = painter.veusz_scaling
- self.pixperpt = painter.veusz_pixperpt
+ self.pagesize = painthelper.pagesize
+ self.scaling = painthelper.scaling
+ self.dpi = painthelper.dpi
def createGraphicsItem(self):
return _GraphMarginBox(self)
@@ -118,8 +116,7 @@
"""A helpful routine for setting widget margins after
moving or resizing.
- This is called by the widget after receiving
- updateControlItem
+ This is called by the widget after receiving updateControlItem
"""
s = self.widget.settings
@@ -129,17 +126,15 @@
top = self.posn[1] - self.maxposn[1]
bottom = self.maxposn[3] - self.posn[3]
- # set up fake painter containing veusz scalings
- fakepainter = qt4.QPainter()
- fakepainter.veusz_page_size = self.page_size
- fakepainter.veusz_scaling = self.scaling
- fakepainter.veusz_pixperpt = self.pixperpt
+ # set up fake painthelper containing veusz scalings
+ helper = document.PaintHelper(self.pagesize, scaling=self.scaling,
+ dpi=self.dpi)
# convert to physical units
- left = s.get('leftMargin').convertInverse(left, fakepainter)
- right = s.get('rightMargin').convertInverse(right, fakepainter)
- top = s.get('topMargin').convertInverse(top, fakepainter)
- bottom = s.get('bottomMargin').convertInverse(bottom, fakepainter)
+ left = s.get('leftMargin').convertInverse(left, helper)
+ right = s.get('rightMargin').convertInverse(right, helper)
+ top = s.get('topMargin').convertInverse(top, helper)
+ bottom = s.get('bottomMargin').convertInverse(bottom, helper)
# modify widget margins
operations = (
@@ -151,6 +146,33 @@
self.widget.document.applyOperation(
document.OperationMultiple(operations, descr='resize margins'))
+ def setPageSize(self):
+ """Helper for setting document/page widget size.
+
+ This is called by the widget after receiving updateControlItem
+ """
+ s = self.widget.settings
+
+ # get margins in pixels
+ width = self.posn[2] - self.posn[0]
+ height = self.posn[3] - self.posn[1]
+
+ # set up fake painter containing veusz scalings
+ helper = document.PaintHelper(self.pagesize, scaling=self.scaling,
+ dpi=self.dpi)
+
+ # convert to physical units
+ width = s.get('width').convertInverse(width, helper)
+ height = s.get('height').convertInverse(height, helper)
+
+ # modify widget margins
+ operations = (
+ document.OperationSettingSet(s.get('width'), width),
+ document.OperationSettingSet(s.get('height'), height),
+ )
+ self.widget.document.applyOperation(
+ document.OperationMultiple(operations, descr='change page size'))
+
class _GraphMarginBox(qt4.QGraphicsItem):
"""A box which can be moved or resized.
@@ -410,10 +432,10 @@
the real position of the widget is
"""
- def __init__(self, widget, posn, painter, crosspos=None):
+ def __init__(self, widget, posn, painthelper, crosspos=None):
ControlMarginBox.__init__(self, widget, posn,
[-10000, -10000, 10000, 10000],
- painter, isresizable=False)
+ painthelper, isresizable=False)
self.deltacrosspos = (crosspos[0] - self.posn[0],
crosspos[1] - self.posn[1])
@@ -528,11 +550,22 @@
maxposn):
self.widget = widget
self.direction = direction
- self.minpos = minpos
- self.maxpos = maxpos
- self.axispos = axispos
+ if minpos > maxpos:
+ minpos, maxpos = maxpos, minpos
+ self.minpos = self.minzoom = self.minorig = minpos
+ self.maxpos = self.maxzoom = self.maxorig = maxpos
+ self.axisorigpos = self.axispos = axispos
self.maxposn = maxposn
+ def zoomed(self):
+ """Is this a zoom?"""
+ return self.minzoom != self.minorig or self.maxzoom != self.maxorig
+
+ def moved(self):
+ """Has axis moved?"""
+ return ( self.minpos != self.minorig or self.maxpos != self.maxorig or
+ self.axisorigpos != self.axispos )
+
def createGraphicsItem(self):
return _GraphAxisLine(self)
@@ -540,21 +573,27 @@
curs = {True: qt4.Qt.SizeVerCursor,
False: qt4.Qt.SizeHorCursor}
+ curs_zoom = {True: qt4.Qt.SplitVCursor,
+ False: qt4.Qt.SplitHCursor}
def __init__(self, params):
"""Line is about to be shown."""
qt4.QGraphicsItem.__init__(self)
self.params = params
- self.pts = [ _ShapeCorner(self),
- _ShapeCorner(self) ]
+ self.pts = [ _ShapeCorner(self), _ShapeCorner(self),
+ _ShapeCorner(self), _ShapeCorner(self) ]
self.line = _AxisGraphicsLineItem(self)
- # set correct coordinates
+ # set cursors and tooltips for items
self.horz = (params.direction == 'horizontal')
- endcurs = self.curs[not self.horz]
- self.pts[0].setCursor(endcurs)
- self.pts[1].setCursor(endcurs)
+ for p in self.pts[0:2]:
+ p.setCursor(self.curs[not self.horz])
+ p.setToolTip("Move axis ends")
+ for p in self.pts[2:]:
+ p.setCursor(self.curs_zoom[not self.horz])
+ p.setToolTip("Change axis scale")
self.line.setCursor( self.curs[self.horz] )
+ self.line.setToolTip("Move axis position")
self.setZValue(2.)
self.updatePos()
@@ -563,47 +602,58 @@
"""Set ends of line and line positions from stored values."""
par = self.params
mxp = par.maxposn
+
+ def _clip(*args):
+ """Clip positions to bounds of box given coords."""
+ par.minpos = max(par.minpos, mxp[args[0]])
+ par.maxpos = min(par.maxpos, mxp[args[1]])
+ par.axispos = max(par.axispos, mxp[args[2]])
+ par.axispos = min(par.axispos, mxp[args[3]])
+
if self.horz:
- # clip to box bounds
- par.minpos = max(par.minpos, mxp[0])
- par.maxpos = min(par.maxpos, mxp[2])
- par.axispos = max(par.axispos, mxp[1])
- par.axispos = min(par.axispos, mxp[3])
+ _clip(0, 2, 1, 3)
# set positions
- self.line.setPos(par.minpos, par.axispos)
- self.line.setLine(0, 0, par.maxpos-par.minpos, 0)
+ if par.zoomed():
+ self.line.setPos(par.minzoom, par.axispos)
+ self.line.setLine(0, 0, par.maxzoom-par.minzoom, 0)
+ else:
+ self.line.setPos(par.minpos, par.axispos)
+ self.line.setLine(0, 0, par.maxpos-par.minpos, 0)
self.pts[0].setPos(par.minpos, par.axispos)
self.pts[1].setPos(par.maxpos, par.axispos)
+ self.pts[2].setPos(par.minzoom, par.axispos-15)
+ self.pts[3].setPos(par.maxzoom, par.axispos-15)
else:
- # clip to box bounds
- par.minpos = max(par.minpos, mxp[1])
- par.maxpos = min(par.maxpos, mxp[3])
- par.axispos = max(par.axispos, mxp[0])
- par.axispos = min(par.axispos, mxp[2])
+ _clip(1, 3, 0, 2)
# set positions
- self.line.setPos(par.axispos, par.minpos)
- self.line.setLine(0, 0, 0, par.maxpos-par.minpos)
+ if par.zoomed():
+ self.line.setPos(par.axispos, par.minzoom)
+ self.line.setLine(0, 0, 0, par.maxzoom-par.minzoom)
+ else:
+ self.line.setPos(par.axispos, par.minpos)
+ self.line.setLine(0, 0, 0, par.maxpos-par.minpos)
self.pts[0].setPos(par.axispos, par.minpos)
self.pts[1].setPos(par.axispos, par.maxpos)
+ self.pts[2].setPos(par.axispos+15, par.minzoom)
+ self.pts[3].setPos(par.axispos+15, par.maxzoom)
def updateFromCorner(self, corner, event):
"""Ends of axis have moved, so update values."""
par = self.params
+ pt = (corner.y(), corner.x())[self.horz]
# which end has moved?
if corner is self.pts[0]:
# horizonal or vertical axis?
- if self.horz:
- par.minpos = corner.x()
- else:
- par.minpos = corner.y()
- else:
- if self.horz:
- par.maxpos = corner.x()
- else:
- par.maxpos = corner.y()
+ par.minpos = pt
+ elif corner is self.pts[1]:
+ par.maxpos = pt
+ elif corner is self.pts[2]:
+ par.minzoom = pt
+ elif corner is self.pts[3]:
+ par.maxzoom = pt
# swap round end points if min > max
if par.minpos > par.maxpos:
diff -Nru veusz-1.10/widgets/data/colormaps.dat veusz-1.14/widgets/data/colormaps.dat
--- veusz-1.10/widgets/data/colormaps.dat 2010-12-12 12:41:10.000000000 +0000
+++ veusz-1.14/widgets/data/colormaps.dat 1970-01-01 00:00:00.000000000 +0000
@@ -1,62 +0,0 @@
-# Veusz colour maps
-# Format is following:
-# name
-# B G R alpha
-# ...
-# name: name of colourmap
-# B G R alpha are quads for each point on map
-
-# $Id: colormaps.dat 598 2007-05-10 19:00:13Z jeremysanders $
-
-heat
-0 0 0 255
-0 0 186 255
-50 139 255 255
-19 239 248 255
-255 255 255 255
-
-spectrum2
-0 0 255 255
-0 255 255 255
-0 255 0 255
-255 255 0 255
-255 0 0 255
-
-spectrum
-0 0 0 255
-0 0 255 255
-0 255 255 255
-0 255 0 255
-255 255 0 255
-255 0 0 255
-255 255 255 255
-
-grey
-0 0 0 255
-255 255 255 255
-
-blue
-0 0 0 255
-255 0 0 255
-255 255 255 255
-
-red
-0 0 0 255
-0 0 255 255
-255 255 255 255
-
-green
-0 0 0 255
-0 255 0 255
-255 255 255 255
-
-bluegreen
-0 0 0 255
-255 123 0 255
-255 226 72 255
-161 255 0 255
-255 255 255 255
-
-transblack
-0 0 0 255
-0 0 0 0
diff -Nru veusz-1.10/widgets/fit.py veusz-1.14/widgets/fit.py
--- veusz-1.10/widgets/fit.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/fit.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,8 +19,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: fit.py 1450 2010-11-24 15:56:05Z jeremysanders $
-
import re
import sys
@@ -33,6 +31,76 @@
from function import FunctionPlotter
import widget
+try:
+ import minuit
+except ImportError:
+ minuit = None
+
+def minuitFit(evalfunc, params, names, values, xvals, yvals, yserr):
+ """Do fitting with minuit (if installed)."""
+
+ def chi2(params):
+ """generate a lambda function to impedance-match between PyMinuit's
+ use of multiple parameters versus our use of a single numpy vector."""
+ c = ((evalfunc(params, xvals) - yvals)**2 / yserr**2).sum()
+ if chi2.runningFit:
+ chi2.iters += 1
+ p = [chi2.iters, c] + params.tolist()
+ str = ("%5i " + "%8g " * (len(params)+1)) % tuple(p)
+ print str
+
+ return c
+
+ namestr = ', '.join(names)
+ fnstr = 'lambda %s: chi2(N.array([%s]))' % (namestr, namestr)
+
+ # this is safe because the only user-controlled variable is len(names)
+ fn = eval(fnstr, {'chi2' : chi2, 'N' : N})
+
+ print 'Fitting via Minuit:'
+ m = minuit.Minuit(fn, fix_x=True, **values)
+
+ # run the fit
+ chi2.runningFit = True
+ chi2.iters = 0
+ m.migrad()
+
+ # do some error analysis
+ have_symerr, have_err = False, False
+ try:
+ chi2.runningFit = False
+ m.hesse()
+ have_symerr = True
+ m.minos()
+ have_err = True
+ except minuit.MinuitError, e:
+ print e
+ if str(e).startswith('Discovered a new minimum'):
+ # the initial fit really failed
+ raise
+
+ # print the results
+ retchi2 = m.fval
+ dof = len(yvals) - len(params)
+ redchi2 = retchi2 / dof
+
+ if have_err:
+ print 'Fit results:\n', "\n".join([
+ u" %s = %g \u00b1 %g (+%g / %g)"
+ % (n, m.values[n], m.errors[n], m.merrors[(n, 1.0)], m.merrors[(n, -1.0)]) for n in names])
+ elif have_symerr:
+ print 'Fit results:\n', "\n".join([
+ u" %s = %g \u00b1 %g" % (n, m.values[n], m.errors[n]) for n in names])
+ print 'MINOS error estimate not available.'
+ else:
+ print 'Fit results:\n', "\n".join([' %s = %g' % (n, m.values[n]) for n in names])
+ print 'No error analysis available: fit quality uncertain'
+
+ print "chi^2 = %g, dof = %i, reduced-chi^2 = %g" % (retchi2, dof, redchi2)
+
+ vals = m.values
+ return vals, retchi2, dof
+
class Fit(FunctionPlotter):
"""A plotter to fit a function to data."""
@@ -71,22 +139,29 @@
'the function variable',
usertext='Fit only range'),
4 )
+ s.add( setting.WidgetChoice(
+ 'outLabel', '',
+ descr='Write best fit parameters to this text label '
+ 'after fitting',
+ widgettypes=('label',),
+ usertext='Output label'),
+ 5 )
s.add( setting.Str('outExpr', '',
descr = 'Output best fitting expression',
usertext='Output expression'),
- 5, readonly=True )
+ 6, readonly=True )
s.add( setting.Float('chi2', -1,
descr = 'Output chi^2 from fitting',
usertext='Fit χ2'),
- 6, readonly=True )
+ 7, readonly=True )
s.add( setting.Int('dof', -1,
descr = 'Output degrees of freedom from fitting',
usertext='Fit d.o.f.'),
- 7, readonly=True )
+ 8, readonly=True )
s.add( setting.Float('redchi2', -1,
descr = 'Output reduced-chi-squared from fitting',
usertext='Fit reduced χ2'),
- 8, readonly=True )
+ 9, readonly=True )
f = s.get('function')
f.newDefault('a + b*x')
@@ -117,6 +192,29 @@
env.update( self.settings.values )
return env
+ def updateOutputLabel(self, ops, vals, chi2, dof):
+ """Use best fit parameters to update text label."""
+ s = self.settings
+ labelwidget = s.get('outLabel').findWidget()
+
+ if labelwidget is not None:
+ # build up a set of X=Y values
+ loc = self.document.locale
+ txt = []
+ for l, v in sorted(vals.iteritems()):
+ val = utils.formatNumber(v, '%.4Vg', locale=loc)
+ txt.append( '%s = %s' % (l, val) )
+ # add chi2 output
+ txt.append( r'\chi^{2}_{\nu} = %s/%i = %s' % (
+ utils.formatNumber(chi2, '%.4Vg', locale=loc),
+ dof,
+ utils.formatNumber(chi2/dof, '%.4Vg', locale=loc) ))
+
+ # update label with text
+ text = r'\\'.join(txt)
+ ops.append( document.OperationSettingSet(
+ labelwidget.settings.get('label') , text ) )
+
def actionFit(self):
"""Fit the data."""
@@ -203,18 +301,25 @@
sys.stderr.write('No degrees of freedom for fit. Not fitting\n')
return
- # actually do the fit
- retn, chi2, dof = utils.fitLM(self.evalfunc, params,
- xvals,
- yvals, yserr)
+ # actually do the fit, either via Minuit or our own LM fitter
+ chi2 = 1
+ dof = 1
+
+ if minuit is not None:
+ vals, chi2, dof = minuitFit(self.evalfunc, params, names, s.values, xvals, yvals, yserr)
+ else:
+ print 'Minuit not available, falling back to simple L-M fitting:'
+ retn, chi2, dof = utils.fitLM(self.evalfunc, params,
+ xvals,
+ yvals, yserr)
+ vals = {}
+ for i, v in zip(names, retn):
+ vals[i] = float(v)
# list of operations do we can undo the changes
operations = []
# populate the return parameters
- vals = {}
- for i, v in zip(names, retn):
- vals[i] = float(v)
operations.append( document.OperationSettingSet(s.get('values'), vals) )
# populate the read-only fit quality params
@@ -231,8 +336,11 @@
expr = self.generateOutputExpr(vals)
operations.append( document.OperationSettingSet(s.get('outExpr'), expr) )
+ self.updateOutputLabel(operations, vals, chi2, dof)
+
# actually change all the settings
- d.applyOperation( document.OperationMultiple(operations, descr='fit') )
+ d.applyOperation(
+ document.OperationMultiple(operations, descr='fit') )
def evalfunc(self, params, xvals):
diff -Nru veusz-1.10/widgets/function.py veusz-1.14/widgets/function.py
--- veusz-1.10/widgets/function.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/function.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: function.py 1449 2010-11-22 09:26:58Z jeremysanders $
-
"""For plotting numerical functions."""
import veusz.qtall as qt4
@@ -28,6 +26,7 @@
import veusz.setting as setting
import veusz.utils as utils
+import pickable
from plotters import GenericPlotter
class FunctionChecker(object):
@@ -178,15 +177,19 @@
varaxrange[1] = min(s.max, varaxrange[1])
# work out function in steps
- if axis.settings.log:
- # log spaced steps
- l1, l2 = N.log(varaxrange[1]), N.log(varaxrange[0])
- delta = (l2-l1)/20.
- points = N.exp(N.arange(l1, l2+delta, delta))
- else:
- # linear spaced steps
- delta = (varaxrange[1] - varaxrange[0])/20.
- points = N.arange(varaxrange[0], varaxrange[1]+delta, delta)
+ try:
+ if axis.settings.log:
+ # log spaced steps
+ l1, l2 = N.log(varaxrange[1]), N.log(varaxrange[0])
+ delta = (l2-l1)/20.
+ points = N.exp(N.arange(l1, l2+delta, delta))
+ else:
+ # linear spaced steps
+ delta = (varaxrange[1] - varaxrange[0])/20.
+ points = N.arange(varaxrange[0], varaxrange[1]+delta, delta)
+ except ZeroDivisionError:
+ # delta is zero
+ return
env = self.initEnviron()
env[s.variable] = points
@@ -289,16 +292,15 @@
def initEnviron(self):
"""Set up function environment."""
return self.document.eval_context.copy()
-
- def calcFunctionPoints(self, axes, posn):
- """Calculate the pixels to plot for the function
- returns (pxpts, pypts)."""
+
+ def getIndependentPoints(self, axes, posn):
+ """Calculate the real and screen points to plot for the independent axis"""
s = self.settings
- try:
- self.checker.check(s.function, s.variable)
- except RuntimeError, e:
- self.logEvalError(e)
+
+ if ( None in axes or
+ axes[0].settings.direction != 'horizontal' or
+ axes[1].settings.direction != 'vertical' ):
return None, None
# get axes function is plotted along and on and
@@ -323,27 +325,77 @@
axispts = axispts[ axispts <= s.max ]
plotpts = axis1.dataToPlotterCoords(posn, axispts)
+ return axispts, plotpts
+
+ def calcDependentPoints(self, axispts, axes, posn):
+ """Calculate the real and screen points to plot for the dependent axis"""
+
+ s = self.settings
+
+ if ( None in axes or
+ axes[0].settings.direction != 'horizontal' or
+ axes[1].settings.direction != 'vertical' ):
+ return None, None
+
+ if axispts is None:
+ return None, None
+
+ try:
+ self.checker.check(s.function, s.variable)
+ except RuntimeError, e:
+ self.logEvalError(e)
+ return None, None
+
+ axis2 = axes[1] if s.variable == 'x' else axes[0]
+
# evaluate function
env = self.initEnviron()
env[s.variable] = axispts
try:
- results = eval(self.checker.compiled, env)
- resultpts = axis2.dataToPlotterCoords(
- posn, results + N.zeros(axispts.shape))
+ results = eval(self.checker.compiled, env) + N.zeros(axispts.shape)
+ resultpts = axis2.dataToPlotterCoords(posn, results)
except Exception, e:
self.logEvalError(e)
+ results = None
resultpts = None
- # return x and y coordinates for plot
+ return results, resultpts
+
+ def calcFunctionPoints(self, axes, posn):
+ ipts, pipts = self.getIndependentPoints(axes, posn)
+ dpts, pdpts = self.calcDependentPoints(ipts, axes, posn)
+
+ if self.settings.variable == 'x':
+ return (ipts, dpts), (pipts, pdpts)
+ else:
+ return (dpts, ipts), (pdpts, pipts)
+
+ def _pickable(self, posn):
+ s = self.settings
+
+ axisnames = [s.xAxis, s.yAxis]
+ axes = self.parent.getAxes(axisnames)
+
if s.variable == 'x':
- return plotpts, resultpts
+ axisnames[1] = axisnames[1] + '(' + axisnames[0] + ')'
else:
- return resultpts, plotpts
+ axisnames[0] = axisnames[0] + '(' + axisnames[1] + ')'
- def draw(self, parentposn, painter, outerbounds = None):
+ (xpts, ypts), (pxpts, pypts) = self.calcFunctionPoints(axes, posn)
+
+ return pickable.GenericPickable(
+ self, axisnames, (xpts, ypts), (pxpts, pypts) )
+
+ def pickPoint(self, x0, y0, bounds, distance='radial'):
+ return self._pickable(bounds).pickPoint(x0, y0, bounds, distance)
+
+ def pickIndex(self, oldindex, direction, bounds):
+ return self._pickable(bounds).pickIndex(oldindex, direction, bounds)
+
+ def draw(self, parentposn, painthelper, outerbounds = None):
"""Draw the function."""
- posn = GenericPlotter.draw(self, parentposn, painter,
+ posn = GenericPlotter.draw(self, parentposn, painthelper,
outerbounds = outerbounds)
x1, y1, x2, y2 = posn
s = self.settings
@@ -362,12 +414,11 @@
return
# clip data within bounds of plotter
- painter.beginPaintingWidget(self, posn)
- painter.save()
- cliprect = self.clipAxesBounds(painter, axes, posn)
+ cliprect = self.clipAxesBounds(axes, posn)
+ painter = painthelper.painter(self, posn, clip=cliprect)
# get the points to plot by evaluating the function
- pxpts, pypts = self.calcFunctionPoints(axes, posn)
+ (xpts, ypts), (pxpts, pypts) = self.calcFunctionPoints(axes, posn)
# draw the function line
if pxpts is None or pypts is None:
@@ -395,9 +446,6 @@
painter.setPen( s.Line.makeQPen(painter) )
self._plotLine(painter, pxpts, pypts, posn, cliprect)
- painter.restore()
- painter.endPaintingWidget()
-
# allow the factory to instantiate an function plotter
document.thefactory.register( FunctionPlotter )
diff -Nru veusz-1.10/widgets/graph.py veusz-1.14/widgets/graph.py
--- veusz-1.10/widgets/graph.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/graph.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,10 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: graph.py 1416 2010-09-25 09:14:16Z jeremysanders $
-
-from itertools import izip
-
import veusz.qtall as qt4
import veusz.setting as setting
import veusz.utils as utils
@@ -113,29 +109,29 @@
# return list of found widgets
return [widgets[n] for n in axesnames]
- def draw(self, parentposn, painter, outerbounds = None):
+ def draw(self, parentposn, painthelper, outerbounds = None):
'''Update the margins before drawing.'''
s = self.settings
- margins = ( s.get('leftMargin').convert(painter),
- s.get('topMargin').convert(painter),
- s.get('rightMargin').convert(painter),
- s.get('bottomMargin').convert(painter) )
- bounds = self.computeBounds(parentposn, painter, margins=margins)
- maxbounds = self.computeBounds(parentposn, painter)
+ margins = ( s.get('leftMargin').convert(painthelper),
+ s.get('topMargin').convert(painthelper),
+ s.get('rightMargin').convert(painthelper),
+ s.get('bottomMargin').convert(painthelper) )
+
+ bounds = self.computeBounds(parentposn, painthelper, margins=margins)
+ maxbounds = self.computeBounds(parentposn, painthelper)
# controls for adjusting graph margins
- self.controlgraphitems = [
- controlgraph.ControlMarginBox(self, bounds, maxbounds, painter)
- ]
+ painter = painthelper.painter(self, bounds)
+ painthelper.setControlGraph(self, [
+ controlgraph.ControlMarginBox(self, bounds, maxbounds,
+ painthelper) ])
# do no painting if hidden
if s.hide:
return bounds
- painter.beginPaintingWidget(self, bounds)
-
# set graph rectangle attributes
painter.setBrush( s.get('Background').makeQBrushWHide() )
painter.setPen( s.get('Border').makeQPenWHide(painter) )
@@ -144,17 +140,10 @@
painter.drawRect( qt4.QRectF(qt4.QPointF(bounds[0], bounds[1]),
qt4.QPointF(bounds[2], bounds[3])) )
- painter.endPaintingWidget()
-
- # set default pen/brush
- # this is probably sticking plaster
- painter.setPen( qt4.QPen() )
- painter.setBrush( qt4.QBrush() )
-
# do normal drawing of children
# iterate over children in reverse order
for c in reversed(self.children):
- c.draw(bounds, painter, outerbounds=outerbounds)
+ c.draw(bounds, painthelper, outerbounds=outerbounds)
# now need to find axes which aren't children, and draw those again
axestodraw = set()
@@ -173,7 +162,7 @@
axeswidgets = self.getAxes(axestodraw)
for w in axeswidgets:
if w is not None:
- w.draw(bounds, painter, outerbounds=outerbounds)
+ w.draw(bounds, painthelper, outerbounds=outerbounds)
return bounds
diff -Nru veusz-1.10/widgets/grid.py veusz-1.14/widgets/grid.py
--- veusz-1.10/widgets/grid.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/grid.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: grid.py 1413 2010-09-25 08:48:02Z jeremysanders $
-
"""The grid class allows graphs to be arranged in a regular grid.
The graphs may share axes if they are stored in the grid widget.
"""
@@ -298,7 +296,7 @@
self.document.applyOperation(
document.OperationMultiple(operations, descr='zero margins') )
- def _drawChild(self, painter, child, bounds, parentposn):
+ def _drawChild(self, phelper, child, bounds, parentposn):
"""Draw child at correct position, with correct bounds."""
# save old position, then update with calculated
@@ -326,7 +324,7 @@
coutbound[3] = parentposn[3]
# draw widget
- child.draw(bounds, painter, outerbounds=coutbound)
+ child.draw(bounds, phelper, outerbounds=coutbound)
# debugging
#painter.setPen(qt4.QPen(qt4.Qt.red))
@@ -336,7 +334,7 @@
# restore position
child.position = oldposn
- def draw(self, parentposn, painter, outerbounds=None):
+ def draw(self, parentposn, phelper, outerbounds=None):
"""Draws the widget's children."""
s = self.settings
@@ -353,27 +351,25 @@
self.lastdimensions = dimensions
self.lastscalings = scalings
- margins = ( s.get('leftMargin').convert(painter),
- s.get('topMargin').convert(painter),
- s.get('rightMargin').convert(painter),
- s.get('bottomMargin').convert(painter) )
+ margins = ( s.get('leftMargin').convert(phelper),
+ s.get('topMargin').convert(phelper),
+ s.get('rightMargin').convert(phelper),
+ s.get('bottomMargin').convert(phelper) )
- bounds = self.computeBounds(parentposn, painter, margins=margins)
- maxbounds = self.computeBounds(parentposn, painter)
+ bounds = self.computeBounds(parentposn, phelper, margins=margins)
+ maxbounds = self.computeBounds(parentposn, phelper)
- painter.beginPaintingWidget(self, bounds)
- painter.endPaintingWidget()
+ painter = phelper.painter(self, bounds)
# controls for adjusting grid margins
- self.controlgraphitems = [
- controlgraph.ControlMarginBox(self, bounds, maxbounds, painter)
- ]
+ phelper.setControlGraph(self,[
+ controlgraph.ControlMarginBox(self, bounds, maxbounds, phelper)])
for child in self.children:
if child.typename != 'axis':
- self._drawChild(painter, child, bounds, parentposn)
+ self._drawChild(phelper, child, bounds, parentposn)
- # do not call widget.Widget.draw
+ # do not call widget.Widget.draw, do not collect 200 pounds
pass
def updateControlItem(self, cgi):
diff -Nru veusz-1.10/widgets/image.py veusz-1.14/widgets/image.py
--- veusz-1.10/widgets/image.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/image.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,14 +16,8 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: image.py 1368 2010-08-21 11:12:13Z jeremysanders $
-
"""Image plotting from 2d datasets."""
-import string
-import os.path
-import struct
-
import veusz.qtall as qt4
import numpy as N
@@ -33,120 +27,10 @@
import plotters
-slowfuncs = False
-try:
- from veusz.helpers.qtloops import numpyToQImage, applyImageTransparancy
-except ImportError:
- slowfuncs = True
-
-def applyScaling(data, mode, minval, maxval):
- """Apply a scaling transformation on the data.
-
- mode is one of 'linear', 'sqrt', 'log', or 'squared'
- minval is the minimum value of the scale
- maxval is the maximum value of the scale
-
- returns transformed data, valid between 0 and 1
- """
-
- # catch naughty people by hardcoding a range
- if minval == maxval:
- minval, maxval = 0., 1.
-
- if mode == 'linear':
- # linear scaling
- data = (data - minval) / (maxval - minval)
-
- elif mode == 'sqrt':
- # sqrt scaling
- # translate into fractions of range
- data = (data - minval) / (maxval - minval)
- # clip off any bad sqrts
- data[data < 0.] = 0.
- # actually do the sqrt transform
- data = N.sqrt(data)
-
- elif mode == 'log':
- # log scaling of image
- # clip any values less than lowermin
- lowermin = data < minval
- data = N.log(data - (minval - 1)) / N.log(maxval - (minval - 1))
- data[lowermin] = 0.
-
- elif mode == 'squared':
- # squared scaling
- # clip any negative values
- lowermin = data < minval
- data = (data-minval)**2 / (maxval-minval)**2
- data[lowermin] = 0.
-
- else:
- raise RuntimeError, 'Invalid scaling mode "%s"' % mode
-
- return data
-
-def slowNumpyToQImage(img, cmap, transparencyimg):
- """Slow version of routine to convert numpy array to QImage
- This is hard work in Python, but it was like this originally.
-
- img: numpy array to convert to QImage
- cmap: 2D array of colors (BGRA rows)
- forcetrans: force image to have alpha component."""
-
- if struct.pack("h", 1) == "\000\001":
- # have to swap colors for big endian architectures
- cmap2 = cmap.copy()
- cmap2[:,0] = cmap[:,3]
- cmap2[:,1] = cmap[:,2]
- cmap2[:,2] = cmap[:,1]
- cmap2[:,3] = cmap[:,0]
- cmap = cmap2
-
- fracs = N.clip(N.ravel(img), 0., 1.)
-
- # Work out which is the minimum colour map. Assumes we have <255 bands.
- numbands = cmap.shape[0]-1
- bands = (fracs*numbands).astype(N.uint8)
- bands = N.clip(bands, 0, numbands-1)
-
- # work out fractional difference of data from band to next band
- deltafracs = (fracs - bands * (1./numbands)) * numbands
-
- # need to make a 2-dimensional array to multiply against triplets
- deltafracs.shape = (deltafracs.shape[0], 1)
-
- # calculate BGRalpha quadruplets
- # this is a linear interpolation between the band and the next band
- quads = (deltafracs*cmap[bands+1] +
- (1.-deltafracs)*cmap[bands]).astype(N.uint8)
-
- # apply transparency if a transparency image is set
- if transparencyimg is not None and transparencyimg.shape == img.shape:
- quads[:,3] = ( N.clip(N.ravel(transparencyimg), 0., 1.) *
- quads[:,3] ).astype(N.uint8)
-
- # convert 32bit quads to a Qt QImage
- s = quads.tostring()
-
- fmt = qt4.QImage.Format_RGB32
- if N.any(cmap[:,3] != 255) or transparencyimg is not None:
- # any transparency
- fmt = qt4.QImage.Format_ARGB32
-
- img = qt4.QImage(s, img.shape[1], img.shape[0], fmt)
- img = img.mirrored()
-
- # hack to ensure string isn't freed before QImage
- img.veusz_string = s
- return img
-
class Image(plotters.GenericPlotter):
"""A class which plots an image on a graph with a specified
coordinate system."""
- # a dict of colormaps loaded in from external file
- colormaps = None
-
typename='image'
allowusercreation=True
description='Plot a 2d dataset as an image'
@@ -172,10 +56,6 @@
"""Construct list of settings."""
plotters.GenericPlotter.addSettings(s)
- # lazy read of colormap file (Let's help startup times)
- if klass.colormaps is None:
- klass.readColorMaps()
-
s.add( setting.Dataset('data', '',
dimensions = 2,
descr = 'Dataset to plot',
@@ -203,11 +83,11 @@
usertext='Transparent data'),
4 )
- s.add( ColormapSetting('colorMap',
- 'grey',
- descr = 'Set of colors to plot data with',
- usertext='Colormap',
- formatting=True),
+ s.add( setting.Colormap('colorMap',
+ 'grey',
+ descr = 'Set of colors to plot data with',
+ usertext='Colormap',
+ formatting=True),
5 )
s.add( setting.Bool('colorInvert', False,
descr = 'Invert color map',
@@ -237,90 +117,6 @@
return ', '.join(out)
userdescription = property(_getUserDescription)
- def readColorMaps(cls):
- """Read color maps data file (a class method)
-
- File is made up of:
- comments (prefaced by # on separate line)
- colormapname
- list of colors with B G R alpha order from 0->255 on separate lines
- [colormapname ...]
- """
-
- name = ''
- vals = []
- cls.colormaps = {}
-
- # locate file holding colormap data
- filename = os.path.join(utils.veuszDirectory, 'widgets', 'data',
- 'colormaps.dat')
-
- # iterate over file
- for l in open(filename):
- p = l.split()
- if len(p) == 0 or p[0][0] == '#':
- # blank or commented line
- pass
- elif p[0][0] not in string.digits:
- # new colormap follows
- if name != '':
- cls.colormaps[name] = N.array(vals).astype(N.intc)
- name = p[0]
- vals = []
- else:
- # add value to current colormap
- assert name != ''
- assert len(p) == 4
- vals.append( [int(i) for i in p] )
-
- # add on final colormap
- if name != '':
- cls.colormaps[name] = N.array(vals).astype(N.intc)
-
- # collect names and sort alphabetically
- names = cls.colormaps.keys()
- names.sort()
- cls.colormapnames = names
-
- readColorMaps = classmethod(readColorMaps)
-
- def applyColorMap(self, cmap, scaling, datain, minval, maxval,
- trans, transimg=None):
- """Apply a colour map to the 2d data given.
-
- cmap is the color map (numpy of BGRalpha quads)
- scaling is scaling mode => 'linear', 'sqrt', 'log' or 'squared'
- data are the imaging data
- minval and maxval are the extremes of the data for the colormap
- trans is a number from 0 to 100
- transimg is an optional image to apply transparency from
- Returns a QImage
- """
-
- # invert colour map if min and max are swapped
- if minval > maxval:
- minval, maxval = maxval, minval
- cmap = cmap[::-1]
-
- # apply transparency
- if trans != 0:
- cmap = cmap.copy()
- cmap[:,3] = (cmap[:,3].astype(N.float32) * (100-trans) /
- 100.).astype(N.intc)
-
- # apply scaling of data
- fracs = applyScaling(datain, scaling, minval, maxval)
-
- if not slowfuncs:
- img = numpyToQImage(fracs, cmap, transimg is not None)
- if transimg is not None:
- applyImageTransparancy(img, transimg)
- else:
- img = slowNumpyToQImage(fracs, cmap, transimg)
- return img
-
- applyColorMap = classmethod(applyColorMap)
-
def updateImage(self):
"""Update the image with new contents."""
@@ -342,14 +138,12 @@
# this is used currently by colorbar objects
self.cacheddatarange = (minval, maxval)
- cmap = self.colormaps[s.colorMap]
- if s.colorInvert:
- cmap = cmap[::-1]
-
- self.image = self.applyColorMap(cmap, s.colorScaling,
- data.data,
- minval, maxval, s.transparency,
- transimg=transimg)
+ # get color map
+ cmap = self.document.getColormap(s.colorMap, s.colorInvert)
+
+ self.image = utils.applyColorMap(
+ cmap, s.colorScaling, data.data, minval, maxval,
+ s.transparency, transimg=transimg)
def providesAxesDependency(self):
"""Range information provided by widget."""
@@ -443,44 +237,15 @@
"""
self.recomputeInternals()
-
- barsize = 128
- s = self.settings
minval, maxval = self.cacheddatarange
+ s = self.settings
- if s.colorScaling in ('linear', 'sqrt', 'squared'):
- # do a linear color scaling
- vals = N.arange(barsize)/(barsize-1.0)*(maxval-minval) + minval
- colorscaling = s.colorScaling
- coloraxisscale = 'linear'
- else:
- assert s.colorScaling == 'log'
-
- # a logarithmic color scaling
- # we cheat here by actually plotting a linear colorbar
- # and telling veusz to put a log axis along it
- # (as we only care about the endpoints)
- # maybe should do this better...
-
- vals = N.arange(barsize)/(barsize-1.0)*(maxval-minval) + minval
- colorscaling = 'linear'
- coloraxisscale = 'log'
-
- # convert 1d array to 2d image
- if direction == 'horizontal':
- vals = vals.reshape(1, barsize)
- else:
- assert direction == 'vertical'
- vals = vals.reshape(barsize, 1)
-
- cmap = self.colormaps[s.colorMap]
- if s.colorInvert:
- cmap = cmap[::-1]
-
- img = self.applyColorMap(cmap, colorscaling, vals,
- minval, maxval, s.transparency)
+ # get colormap
+ cmap = self.document.getColormap(s.colorMap, s.colorInvert)
- return (minval, maxval, coloraxisscale, img)
+ return utils.makeColorbarImage(
+ minval, maxval, s.colorScaling, cmap, s.transparency,
+ direction=direction)
def recomputeInternals(self):
"""Recompute the internals if required.
@@ -507,10 +272,10 @@
else:
return None
- def draw(self, parentposn, painter, outerbounds = None):
+ def draw(self, parentposn, phelper, outerbounds = None):
"""Draw the image."""
- posn = plotters.GenericPlotter.draw(self, parentposn, painter,
+ posn = plotters.GenericPlotter.draw(self, parentposn, phelper,
outerbounds = outerbounds)
x1, y1, x2, y2 = posn
s = self.settings
@@ -548,9 +313,8 @@
image = self.image
# clip data within bounds of plotter
- painter.beginPaintingWidget(self, posn)
- painter.save()
- self.clipAxesBounds(painter, axes, posn)
+ clip = self.clipAxesBounds(axes, posn)
+ painter = phelper.painter(self, posn, clip=clip)
# optionally smooth images before displaying
if s.smooth:
@@ -558,61 +322,30 @@
qt4.Qt.IgnoreAspectRatio,
qt4.Qt.SmoothTransformation )
- # now draw pixmap
- painter.drawImage( qt4.QRectF(coordsx[0], coordsy[1],
- coordsx[1]-coordsx[0],
- coordsy[0]-coordsy[1]),
- image )
- painter.restore()
- painter.endPaintingWidget()
+ # get position and size of output image
+ xp, yp = coordsx[0], coordsy[1]
+ xw = coordsx[1]-coordsx[0]
+ yw = coordsy[0]-coordsy[1]
+
+ # invert output drawing if axes go from positive->negative
+ # we only translate the coordinate system if this is the case
+ xscale = yscale = 1
+ if xw < 0:
+ xscale = -1
+ if yw < 0:
+ yscale = -1
+ if xscale != 1 or yscale != 1:
+ painter.save()
+ painter.translate(xp, yp)
+ xp = yp = 0
+ painter.scale(xscale, yscale)
+
+ # draw image
+ painter.drawImage(qt4.QRectF(xp, yp, abs(xw), abs(yw)), image)
+
+ # restore painter if image was inverted
+ if xscale != 1 or yscale != 1:
+ painter.restore()
# allow the factory to instantiate an image
document.thefactory.register( Image )
-
-class ColormapSetting(setting.Choice):
- """A setting to set the colour map used in an image."""
-
- def __init__(self, name, value, **args):
- setting.Choice.__init__(self, name, Image.colormapnames, value, **args)
-
- def copy(self):
- """Make a copy of the setting."""
- return self._copyHelper((), (), {})
-
- def makeControl(self, *args):
- return ColormapControl(self, *args)
-
-class ColormapControl(setting.controls.Choice):
- """Give the user a preview of colourmaps."""
-
- _icons = []
-
- size = (32, 12)
-
- def __init__(self, setn, parent):
- if not self._icons:
- self._generateIcons()
-
- setting.controls.Choice.__init__(self, setn, False,
- Image.colormapnames, parent,
- icons=self._icons)
- self.setIconSize( qt4.QSize(*self.size) )
-
- def _generateIcons(cls):
- """Generate a list of icons for drop down menu."""
- size = cls.size
-
- # create a fake dataset smoothly varying from 0 to size[0]-1
- fakedataset = N.fromfunction(lambda x, y: y,
- (size[1], size[0]))
-
- # iterate over colour maps
- for cmap in Image.colormapnames:
- image = Image.applyColorMap(Image.colormaps[cmap], 'linear',
- fakedataset,
- 0., size[0]-1., 0)
- pixmap = qt4.QPixmap.fromImage(image)
- cls._icons.append( qt4.QIcon(pixmap) )
-
- _generateIcons = classmethod(_generateIcons)
-
diff -Nru veusz-1.10/widgets/__init__.py veusz-1.14/widgets/__init__.py
--- veusz-1.10/widgets/__init__.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/__init__.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: __init__.py 1447 2010-11-11 20:35:25Z jeremysanders $
-
"""Widgets are defined in this module."""
from widget import Widget, Action
@@ -25,6 +23,7 @@
from graph import Graph
from grid import Grid
from plotters import GenericPlotter, FreePlotter
+from pickable import PickInfo
from point import PointPlotter
from function import FunctionPlotter
from textlabel import TextLabel
@@ -42,5 +41,6 @@
from vectorfield import VectorField
from boxplot import BoxPlot
from polar import Polar
+from ternary import Ternary
from nonorthpoint import NonOrthPoint
from nonorthfunction import NonOrthFunction
diff -Nru veusz-1.10/widgets/key.py veusz-1.14/widgets/key.py
--- veusz-1.10/widgets/key.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/key.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: key.py 1451 2010-11-25 21:25:41Z jeremysanders $
-
import veusz.qtall as qt4
import veusz.document as document
import veusz.setting as setting
@@ -29,6 +27,8 @@
import graph
import controlgraph
+import math
+
#############################################################################
# classes for controlling key position interactively
@@ -197,6 +197,12 @@
descr = 'Length of line to show in sample',
usertext='Key length',
formatting=True) )
+
+ s.add( setting.AlignVert( 'keyAlign',
+ 'top',
+ descr = 'Alignment of key symbols relative to text',
+ usertext = 'Key alignment',
+ formatting = True) )
s.add( setting.Float( 'horzManual',
0.,
@@ -223,16 +229,102 @@
minval = 1,
maxval = 100,
formatting = True) )
-
- def draw(self, parentposn, painter, outerbounds = None):
+
+ @staticmethod
+ def _layoutChunk(entries, start, dims):
+ """Layout the entries into the given box, starting at start"""
+ row, col = start
+ numrows, numcols = dims
+ colstats = [0] * numcols
+ layout = []
+ for (plotter, num, lines) in entries:
+ if row+lines > numrows:
+ # this item doesn't fit in this column, so move to the next
+ col += 1
+ row = 0
+ if col >= numcols:
+ # this layout failed, suggest expanding the box by 1 row
+ return ([], [], numrows+1)
+ if lines > numrows:
+ # this layout failed, suggest expanding the box to |lines|
+ return ([], [], lines)
+
+ # col -> yp, row -> xp
+ layout.append( (plotter, num, col, row, lines) )
+ row += lines
+ colstats[col] += 1
+
+ return (layout, colstats, numrows)
+
+ def _layout(self, entries, totallines):
+ """Layout the items, trying to keep the box as small as possible
+ while still filling the columns"""
+
+ maxcols = self.settings.columns
+ numcols = min(maxcols, max(len(entries), 1))
+
+ if not entries:
+ return (list(), (0, 0))
+
+ # start with evenly-sized rows and expand to fit
+ numrows = totallines / numcols
+ layout = []
+
+ while not layout:
+ # try to do a first cut of the layout, and expand the box until
+ # everything fits
+ (layout, colstats, newrows) = self._layoutChunk(entries, (0, 0), (numrows, numcols))
+ if not layout:
+ numrows = newrows
+
+ # ok, we've got a layout where everything fits, now pull items right
+ # to fill the remaining columns, if need be
+ while colstats[-1] == 0:
+ # shift 1 item to the right, up to the first column that has
+ # excess items
+ meanoccupation = max(1, sum(colstats)/float(numcols))
+
+ # loop until we find a victim item which can be safely moved
+ victimcol = numcols
+ while True:
+ # find the right-most column with excess occupation number
+ for i in reversed(xrange(victimcol)):
+ if colstats[i] > meanoccupation:
+ victimcol = i
+ break
+
+ # find the last item in the victim column
+ victim = 0
+ for i in reversed(xrange(len(layout))):
+ if layout[i][2] == victimcol:
+ victim = i
+ break
+
+ # try to relayout with the victim item shoved to the next column
+ (newlayout, newcolstats, newrows) = self._layoutChunk(entries[victim:],
+ (0, victimcol+1), (numrows, numcols))
+ if newlayout:
+ # the relayout worked, so accept it
+ layout = layout[0:victim] + newlayout
+ colstats[victimcol] -= 1
+ del colstats[victimcol+1:]
+ colstats += newcolstats[victimcol+1:]
+ break
+
+ # if we've run out of potential victims, just return what we have
+ if victimcol == 0:
+ return (layout, (numrows, numcols))
+
+ return (layout, (numrows, numcols))
+
+ def draw(self, parentposn, phelper, outerbounds = None):
"""Plot the key on a plotter."""
s = self.settings
if s.hide:
return
- painter.beginPaintingWidget(self, parentposn)
- painter.save()
+ painter = phelper.painter(self, parentposn)
font = s.get('Text').makeQFont(painter)
painter.setFont(font)
@@ -240,10 +332,10 @@
showtext = not s.Text.hide
- # keep track of widgets to place
- keywidgets = []
# maximum width of text required
maxwidth = 1
+ # total number of layout lines required
+ totallines = 0
entries = []
# iterate over children and find widgets which are suitable
@@ -255,19 +347,18 @@
if not c.settings.hide:
# add an entry for each key entry for each widget
for i in xrange(num):
- entries.append( (c, i) )
-
+ lines = 1
if showtext:
w, h = utils.Renderer(painter, font, 0, 0,
c.getKeyText(i)).getDimensions()
maxwidth = max(maxwidth, w)
+ lines = max(1, math.ceil(float(h)/float(height)))
+
+ totallines += lines
+ entries.append( (c, i, lines) )
- # get number of columns
- count = len(entries)
- numcols = min(s.columns, max(count, 1))
- numrows = count / numcols
- if count % numcols != 0:
- numrows += 1
+ # layout the box
+ layout, (numrows, numcols) = self._layout(entries, totallines)
# total size of box
symbolwidth = s.get('keyLength').convert(painter)
@@ -321,14 +412,19 @@
textpen = s.get('Text').makeQPen()
# plot dataset entries
- for index, (plotter, num) in enumerate(entries):
- xp, yp = index / numrows, index % numrows
+ for (plotter, num, xp, yp, lines) in layout:
xpos = x + xp*(maxwidth+2*height+symbolwidth)
ypos = y + yp*height
# plot key symbol
painter.save()
- plotter.drawKeySymbol(num, painter, xpos, ypos,
+ keyoffset = 0
+ if s.keyAlign == 'centre':
+ keyoffset = (lines-1)*height/2.0
+ elif s.keyAlign == 'bottom':
+ keyoffset = (lines-1)*height
+
+ plotter.drawKeySymbol(num, painter, xpos, ypos+keyoffset,
symbolwidth, height)
painter.restore()
@@ -340,11 +436,7 @@
plotter.getKeyText(num),
-1, 1).render()
- self.controlgraphitems = [
- ControlKey(self, parentposn, boxposn, boxdims, height)
- ]
-
- painter.restore()
- painter.endPaintingWidget()
+ phelper.setControlGraph(
+ self, [ControlKey(self, parentposn, boxposn, boxdims, height)] )
document.thefactory.register( Key )
diff -Nru veusz-1.10/widgets/line.py veusz-1.14/widgets/line.py
--- veusz-1.10/widgets/line.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/line.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: line.py 1387 2010-08-29 15:24:57Z jeremysanders $
-
"""Plotting a line with arrowheads or labels."""
import itertools
@@ -80,7 +78,7 @@
usertext='Arrow left', formatting=True), 0)
- def draw(self, posn, painter, outerbounds = None):
+ def draw(self, posn, phelper, outerbounds = None):
"""Plot the key on a plotter."""
s = self.settings
@@ -105,12 +103,13 @@
not s.get('yPos').isDataset(d) and
not s.get('length').isDataset(d) and
not s.get('angle').isDataset(d) )
- self.controlgraphitems = []
- arrowsize = s.get('arrowSize').convert(painter)
+ # now do the drawing
+ painter = phelper.painter(self, posn)
- painter.beginPaintingWidget(self, posn)
- painter.save()
+ # adjustable positions for the lines
+ controlgraphitems = []
+ arrowsize = s.get('arrowSize').convert(painter)
# drawing settings for line
if not s.Line.hide:
@@ -145,10 +144,9 @@
cgi.index = index
cgi.widgetposn = posn
index += 1
- self.controlgraphitems.append(cgi)
+ controlgraphitems.append(cgi)
- painter.restore()
- painter.endPaintingWidget()
+ phelper.setControlGraph(self, controlgraphitems)
def updateControlItem(self, cgi, pt1, pt2):
"""If control items are moved, update line."""
diff -Nru veusz-1.10/widgets/nonorthfunction.py veusz-1.14/widgets/nonorthfunction.py
--- veusz-1.10/widgets/nonorthfunction.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/nonorthfunction.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: nonorthfunction.py 1447 2010-11-11 20:35:25Z jeremysanders $
-
'''Non orthogonal function plotting.'''
import numpy as N
@@ -27,6 +25,7 @@
import veusz.setting as setting
import veusz.utils as utils
+import pickable
from nonorthgraph import NonOrthGraph, FillBrush
from widget import Widget
from function import FunctionChecker
@@ -130,13 +129,28 @@
def updateDataRanges(self, inrange):
'''Update ranges of data given function.'''
- def draw(self, parentposn, painter, outerbounds=None):
+ def _pickable(self):
+ apts, bpts = self.getFunctionPoints()
+ px, py = self.parent.graphToPlotCoords(apts, bpts)
+
+ if self.settings.variable == 'a':
+ labels = ('a', 'b(a)')
+ else:
+ labels = ('a(b)', 'b')
+
+ return pickable.GenericPickable( self, labels, (apts, bpts), (px, py) )
+
+ def pickPoint(self, x0, y0, bounds, distance='radial'):
+ return self._pickable().pickPoint(x0, y0, bounds, distance)
+
+ def pickIndex(self, oldindex, direction, bounds):
+ return self._pickable().pickIndex(oldindex, direction, bounds)
+
+ def draw(self, parentposn, phelper, outerbounds=None):
'''Plot the function on a plotter.'''
- posn = Widget.draw(self, parentposn, painter,
+ posn = Widget.draw(self, parentposn, phelper,
outerbounds=outerbounds)
- x1, y1, x2, y2 = posn
- cliprect = qt4.QRectF( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) )
s = self.settings
d = self.document
@@ -152,8 +166,10 @@
self.logEvalError(e)
return
- painter.beginPaintingWidget(self, posn)
- painter.save()
+ x1, y1, x2, y2 = posn
+ cliprect = qt4.QRectF( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) )
+ painter = phelper.painter(self, posn)
+ self.parent.setClip(painter, posn)
apts, bpts = self.getFunctionPoints()
px, py = self.parent.graphToPlotCoords(apts, bpts)
@@ -179,7 +195,4 @@
painter.setPen( s.PlotLine.makeQPen(painter) )
utils.plotClippedPolyline(painter, cliprect, p)
- painter.restore()
- painter.endPaintingWidget()
-
document.thefactory.register( NonOrthFunction )
diff -Nru veusz-1.10/widgets/nonorthgraph.py veusz-1.14/widgets/nonorthgraph.py
--- veusz-1.10/widgets/nonorthgraph.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/nonorthgraph.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: nonorthgraph.py 1447 2010-11-11 20:35:25Z jeremysanders $
-
"""Non orthogonal graph root."""
import controlgraph
@@ -27,7 +25,9 @@
import veusz.setting as setting
-filloptions = ('center', 'outside', 'top', 'bottom', 'left', 'right')
+filloptions = ('center', 'outside', 'top', 'bottom', 'left', 'right',
+ 'polygon')
+
class FillBrush(setting.Brush):
'''Brush for filling point region.'''
def __init__(self, *args, **argsv):
@@ -107,40 +107,36 @@
c.updateDataRanges(drange)
return drange
- def draw(self, parentposn, painter, outerbounds=None):
+ def draw(self, parentposn, phelper, outerbounds=None):
'''Update the margins before drawing.'''
s = self.settings
- margins = ( s.get('leftMargin').convert(painter),
- s.get('topMargin').convert(painter),
- s.get('rightMargin').convert(painter),
- s.get('bottomMargin').convert(painter) )
- bounds = self.computeBounds(parentposn, painter, margins=margins)
- maxbounds = self.computeBounds(parentposn, painter)
-
- # controls for adjusting graph margins
- self.controlgraphitems = [
- controlgraph.ControlMarginBox(self, bounds, maxbounds, painter)
- ]
+ margins = ( s.get('leftMargin').convert(phelper),
+ s.get('topMargin').convert(phelper),
+ s.get('rightMargin').convert(phelper),
+ s.get('bottomMargin').convert(phelper) )
+ bounds = self.computeBounds(parentposn, phelper, margins=margins)
+ maxbounds = self.computeBounds(parentposn, phelper)
+
+ painter = phelper.painter(self, bounds)
+
+ # controls for adjusting margins
+ phelper.setControlGraph(self, [
+ controlgraph.ControlMarginBox(self, bounds, maxbounds, phelper)])
# do no painting if hidden
if s.hide:
return bounds
# plot graph
- painter.beginPaintingWidget(self, bounds)
datarange = self.getDataRange()
self.drawGraph(painter, bounds, datarange, outerbounds=outerbounds)
self.drawAxes(painter, bounds, datarange, outerbounds=outerbounds)
- painter.endPaintingWidget()
# paint children
- painter.save()
- self.setClip(painter, bounds)
for c in reversed(self.children):
- c.draw(bounds, painter, outerbounds=outerbounds)
- painter.restore()
+ c.draw(bounds, phelper, outerbounds=outerbounds)
return bounds
diff -Nru veusz-1.10/widgets/nonorthpoint.py veusz-1.14/widgets/nonorthpoint.py
--- veusz-1.10/widgets/nonorthpoint.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/nonorthpoint.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,10 +16,9 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: nonorthpoint.py 1447 2010-11-11 20:35:25Z jeremysanders $
-
"""Non orthogonal point plotting."""
+import itertools
import numpy as N
import veusz.qtall as qt4
@@ -27,6 +26,8 @@
import veusz.setting as setting
import veusz.utils as utils
+import pickable
+
from nonorthgraph import NonOrthGraph, FillBrush
from widget import Widget
from point import MarkerFillBrush
@@ -63,6 +64,9 @@
'scalePoints', '',
descr = 'Scale size of plotted markers by this dataset or'
' list of values', usertext='Scale markers') )
+ s.add( setting.DatasetOrStr('labels', '',
+ descr='Dataset or string to label points',
+ usertext='Labels', datatype='text') )
s.add( setting.DistancePt('markerSize',
'3pt',
@@ -92,6 +96,10 @@
descr = 'Fill settings (2)',
usertext = 'Area fill 2'),
pixmap = 'settings_plotfillbelow' )
+ s.add( setting.PointLabel('Label',
+ descr = 'Label settings',
+ usertext='Label'),
+ pixmap = 'settings_axislabel' )
def updateDataRanges(self, inrange):
'''Extend inrange to range of data.'''
@@ -105,24 +113,62 @@
inrange[2] = min( N.nanmin(d2.data), inrange[2] )
inrange[3] = max( N.nanmax(d2.data), inrange[3] )
- def plotMarkers(self, painter, plta, pltb, scaling, clip):
+ def pickPoint(self, x0, y0, bounds, distance = 'radial'):
+ p = pickable.DiscretePickable(self, 'data1', 'data2',
+ lambda v1, v2: self.parent.graphToPlotCoords(v1, v2))
+ return p.pickPoint(x0, y0, bounds, distance)
+
+ def pickIndex(self, oldindex, direction, bounds):
+ p = pickable.DiscretePickable(self, 'data1', 'data2',
+ lambda v1, v2: self.parent.graphToPlotCoords(v1, v2))
+ return p.pickIndex(oldindex, direction, bounds)
+
+ def plotMarkers(self, painter, plta, pltb, scaling, markersize, clip):
'''Draw markers in widget.'''
s = self.settings
if not s.MarkerLine.hide or not s.MarkerFill.hide:
painter.setBrush( s.MarkerFill.makeQBrushWHide() )
painter.setPen( s.MarkerLine.makeQPenWHide(painter) )
-
- size = s.get('markerSize').convert(painter)
- utils.plotMarkers(painter, plta, pltb, s.marker, size,
+
+ utils.plotMarkers(painter, plta, pltb, s.marker, markersize,
scaling=scaling, clip=clip)
- def draw(self, parentposn, painter, outerbounds=None):
+ def drawLabels(self, painter, xplotter, yplotter,
+ textvals, markersize):
+ """Draw labels for the points.
+
+ This is copied from the xy (point) widget class, so it
+ probably should be somehow be shared.
+
+ FIXME: sane automatic placement of labels
+ """
+
+ s = self.settings
+ lab = s.get('Label')
+
+ # work out offset an alignment
+ deltax = markersize*1.5*{'left':-1, 'centre':0, 'right':1}[lab.posnHorz]
+ deltay = markersize*1.5*{'top':-1, 'centre':0, 'bottom':1}[lab.posnVert]
+ alignhorz = {'left':1, 'centre':0, 'right':-1}[lab.posnHorz]
+ alignvert = {'top':-1, 'centre':0, 'bottom':1}[lab.posnVert]
+
+ # make font and len
+ textpen = lab.makeQPen()
+ painter.setPen(textpen)
+ font = lab.makeQFont(painter)
+ angle = lab.angle
+
+ # iterate over each point and plot each label
+ for x, y, t in itertools.izip(xplotter+deltax, yplotter+deltay,
+ textvals):
+ utils.Renderer( painter, font, x, y, t,
+ alignhorz, alignvert, angle ).render()
+
+ def draw(self, parentposn, phelper, outerbounds=None):
'''Plot the data on a plotter.'''
- posn = Widget.draw(self, parentposn, painter,
+ posn = Widget.draw(self, parentposn, phelper,
outerbounds=outerbounds)
- x1, y1, x2, y2 = posn
- cliprect = qt4.QRectF( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) )
s = self.settings
d = self.document
@@ -134,14 +180,18 @@
d1 = s.get('data1').getData(d)
d2 = s.get('data2').getData(d)
dscale = s.get('scalePoints').getData(d)
+ text = s.get('labels').getData(d, checknull=True)
if not d1 or not d2:
return
- painter.beginPaintingWidget(self, posn)
- painter.save()
+ x1, y1, x2, y2 = posn
+ cliprect = qt4.QRectF( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) )
+ painter = phelper.painter(self, posn)
+ self.parent.setClip(painter, posn)
# split parts separated by NaNs
- for v1, v2, vs in document.generateValidDatasetParts(d1, d2, dscale):
+ for v1, v2, scalings, textitems in document.generateValidDatasetParts(
+ d1, d2, dscale, text):
# convert data (chopping down length)
v1d, v2d = v1.data, v2.data
minlen = min(v1d.shape[0], v2d.shape[0])
@@ -170,13 +220,15 @@
utils.plotClippedPolyline(painter, cliprect, pts)
# markers
+ markersize = s.get('markerSize').convert(painter)
pscale = None
- if vs:
- pscale = vs.data
- self.plotMarkers(painter, px, py, pscale, cliprect)
-
- painter.restore()
- painter.endPaintingWidget()
+ if scalings:
+ pscale = scalings.data
+ self.plotMarkers(painter, px, py, pscale, markersize, cliprect)
+
+ # finally plot any labels
+ if textitems and not s.Label.hide:
+ self.drawLabels(painter, px, py, textitems, markersize)
# allow the factory to instantiate plotter
document.thefactory.register( NonOrthPoint )
diff -Nru veusz-1.10/widgets/page.py veusz-1.14/widgets/page.py
--- veusz-1.10/widgets/page.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/page.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,15 +16,15 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: page.py 1023 2009-07-11 17:52:27Z jeremysanders $
-
"""Widget that represents a page in the document."""
import veusz.qtall as qt4
import veusz.document as document
+import veusz.setting as setting
import widget
import root
+import controlgraph
# x, y, fplot, xyplot
# x-> xyplot(x)
@@ -121,8 +121,11 @@
dwidget, dwidget_dep = dep
if hasattr(dwidget, 'isplotter'):
# update range of axis with (dwidget, dwidget_dep)
- dwidget.updateAxisRange(widget, dwidget_dep,
- self.ranges[widget])
+ # do not do this if the widget is hidden
+ if ( not dwidget.settings.isSetting('hide') or
+ not dwidget.settings.hide ):
+ dwidget.updateAxisRange(widget, dwidget_dep,
+ self.ranges[widget])
elif hasattr(dwidget, 'isaxis'):
# set actual range on axis, as axis no longer has a
# dependency
@@ -185,14 +188,25 @@
if type(self) == Page:
self.readDefaults()
- def draw(self, parentposn, painter, outerbounds=None):
+ @classmethod
+ def addSettings(klass, s):
+ widget.Widget.addSettings(s)
+
+ # page sizes are initially linked to the document page size
+ s.add( setting.Distance('width',
+ setting.Reference('/width'),
+ descr='Width of page',
+ usertext='Page width',
+ formatting=True) )
+ s.add( setting.Distance('height',
+ setting.Reference('/height'),
+ descr='Height of page',
+ usertext='Page height',
+ formatting=True) )
+
+ def draw(self, parentposn, painthelper, outerbounds=None):
"""Draw the plotter. Clip graph inside bounds."""
- # special scaling properties are stored in painter
- if not hasattr(painter, 'veusz_scaling'):
- painter.veusz_scaling = 1.
- painter.veusz_pixperpt = painter.device().logicalDpiY() / 72.
-
# document should pass us the page bounds
x1, y1, x2, y2 = parentposn
@@ -202,27 +216,37 @@
axisdependhelper.findAxisRanges()
# store axis->plotter mappings in painter too (is this nasty?)
- painter.veusz_axis_plotter_map = axisdependhelper.axis_plotter_map
-
- # page size is stored in painter
- painter.veusz_page_size = (x2-x1, y2-y1)
+ painthelper.axisplottermap.update(axisdependhelper.axis_plotter_map)
if self.settings.hide:
- bounds = self.computeBounds(parentposn, painter)
+ bounds = self.computeBounds(parentposn, painthelper)
return bounds
- painter.beginPaintingWidget(self, parentposn)
- painter.save()
+ clip = qt4.QRectF( qt4.QPointF(parentposn[0], parentposn[1]),
+ qt4.QPointF(parentposn[2], parentposn[3]) )
+ painter = painthelper.painter(self, parentposn, clip=clip)
# clip to page
- painter.setClipRect( qt4.QRectF(x1, y1, x2-x1, y2-y1) )
- bounds = widget.Widget.draw(self, parentposn, painter,
+ bounds = widget.Widget.draw(self, parentposn, painthelper,
parentposn)
- painter.restore()
- painter.endPaintingWidget()
+
+ # w and h are non integer
+ w = self.settings.get('width').convert(painter)
+ h = self.settings.get('height').convert(painter)
+ painthelper.setControlGraph(self, [
+ controlgraph.ControlMarginBox(self, [0, 0, w, h],
+ [-10000, -10000,
+ 10000, 10000],
+ painthelper,
+ ismovable = False)
+ ] )
return bounds
+ def updateControlItem(self, cgi):
+ """Call helper to set page size."""
+ cgi.setPageSize()
+
# allow the factory to instantiate this
document.thefactory.register( Page )
diff -Nru veusz-1.10/widgets/pickable.py veusz-1.14/widgets/pickable.py
--- veusz-1.10/widgets/pickable.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/widgets/pickable.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,253 @@
+# pickable.py
+# stuff related to the Picker (aka Read Data) tool
+
+# Copyright (C) 2011 Benjamin K. Stuhl
+# Email: Benjamin K. Stuhl
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+###############################################################################
+
+import numpy as N
+
+import veusz.document as document
+
+class PickInfo:
+ """Encapsulates the results of a Pick operation. screenpos and coords are
+ numeric (x,y) tuples, labels are the textual labels for the x and y
+ datasets, and index is some object that the picker can use to figure out
+ what the 'next' and 'previous' points are. index must implement __str__();
+ return '' if it has no user-visible meaning."""
+ def __init__(self, widget=None, screenpos=None, labels=None, coords=None, index=None):
+ self.widget = widget
+ self.screenpos = screenpos
+ self.labels = labels
+ self.coords = coords
+ self.index = index
+ self.distance = float('inf')
+ self.displaytype = ('numeric', 'numeric')
+
+ def __nonzero__(self):
+ if self.widget and self.screenpos and self.labels and self.coords:
+ return True
+ return False
+
+class Index:
+ """A class containing all the state a GenericPickable needs to find the
+ next or previous point"""
+ def __init__(self, ivar, index, sign):
+ self.ivar = ivar
+ self.index = index
+ self.sign = sign
+
+ # default to not trusting the actual index to be meaningful
+ self.useindex = False
+
+ def __str__(self):
+ if not self.useindex:
+ return ''
+ else:
+ return str(self.index)
+
+def _chooseOrderingSign(m, c, p):
+ """Figures out whether p or m is visually right of c"""
+ assert c is not None
+
+ if p is not None and m is not None:
+ if p[0] > m[0] or (p[0] == m[0] and p[1] < m[1]):
+ # p is visually to the right of or above m
+ return 1
+ else:
+ return -1
+ elif p is not None:
+ if p[0] > c[0]:
+ # p is visually right of c
+ return 1
+ else:
+ return -1
+ elif m is not None:
+ if m[0] < c[0]:
+ # m is visually left of c
+ return 1
+ else:
+ return -1
+ else:
+ assert m is not None or p is not None
+
+class GenericPickable:
+ """Utility class which abstracts the math of picking the closest point out
+ of a list of points"""
+
+ def __init__(self, widget, labels, vals, screenvals):
+ self.widget = widget
+ self.labels = labels
+ self.xvals, self.yvals = vals
+ self.xscreen, self.yscreen = screenvals
+
+ def _pickSign(self, i):
+ if len(self.xscreen) <= 1:
+ # we only have one element, so it doesn't matter anyways
+ return 1
+
+ if i == 0:
+ m = None
+ else:
+ m = self.xscreen[i-1], self.yscreen[i-1]
+
+ c = self.xscreen[i], self.yscreen[i]
+
+ if i+1 == len(self.xscreen):
+ p = None
+ else:
+ p = self.xscreen[i+1], self.yscreen[i+1]
+
+ return _chooseOrderingSign(m, c, p)
+
+ def pickPoint(self, x0, y0, bounds, distance_direction):
+ info = PickInfo(self.widget, labels=self.labels)
+
+ if self.widget.settings.hide:
+ return info
+
+ if None in (self.xvals, self.yvals):
+ return info
+
+ # calculate distances
+ if distance_direction == 'vertical':
+ # measure distance along y
+ dist = N.abs(self.yscreen - y0)
+ elif distance_direction == 'horizontal':
+ # measure distance along x
+ dist = N.abs(self.xscreen - x0)
+ elif distance_direction == 'radial':
+ # measure radial distance
+ dist = N.sqrt((self.xscreen - x0)**2 + (self.yscreen - y0)**2)
+ else:
+ # programming error
+ assert (distance_direction == 'radial' or
+ distance_direction == 'vertical' or
+ distance_direction == 'horizontal')
+
+ # ignore points which are offscreen
+ outofbounds = ( (self.xscreen < bounds[0]) | (self.xscreen > bounds[2]) |
+ (self.yscreen < bounds[1]) | (self.yscreen > bounds[3]) )
+ dist[outofbounds] = float('inf')
+
+ m = N.min(dist)
+ # if there are multiple equidistant points, arbitrarily take
+ # the first one
+ i = N.nonzero(dist == m)[0][0]
+
+ info.screenpos = self.xscreen[i], self.yscreen[i]
+ info.coords = self.xvals[i], self.yvals[i]
+ info.distance = m
+ info.index = Index(self.xvals[i], i, self._pickSign(i))
+
+ return info
+
+ def pickIndex(self, oldindex, direction, bounds):
+ info = PickInfo(self.widget, labels=self.labels)
+
+ if self.widget.settings.hide:
+ return info
+
+ if None in (self.xvals, self.yvals):
+ return info
+
+ if oldindex.index is None:
+ # no explicit index, so find the closest location to the previous
+ # independent variable value
+ i = N.logical_not( N.logical_or(
+ self.xvals < oldindex.ivar, self.xvals > oldindex.ivar) )
+
+ # and pick the next
+ if oldindex.sign == 1:
+ i = max(N.nonzero(i)[0])
+ else:
+ i = min(N.nonzero(i)[0])
+ else:
+ i = oldindex.index
+
+ if direction == 'right':
+ incr = oldindex.sign
+ elif direction == 'left':
+ incr = -oldindex.sign
+ else:
+ assert direction == 'right' or direction == 'left'
+
+ i += incr
+
+ # skip points that are outside of the bounds
+ while ( i >= 0 and i < len(self.xscreen) and
+ (self.xscreen[i] < bounds[0] or self.xscreen[i] > bounds[2] or
+ self.yscreen[i] < bounds[1] or self.yscreen[i] > bounds[3]) ):
+ i += incr
+
+ if i < 0 or i >= len(self.xscreen):
+ return info
+
+ info.screenpos = self.xscreen[i], self.yscreen[i]
+ info.coords = self.xvals[i], self.yvals[i]
+ info.index = Index(self.xvals[i], i, oldindex.sign)
+
+ return info
+
+class DiscretePickable(GenericPickable):
+ """A specialization of GenericPickable that knows how to deal with widgets
+ with axes and data sets"""
+ def __init__(self, widget, xdata_propname, ydata_propname, mapdata_fn):
+ s = widget.settings
+ doc = widget.document
+ self.xdata = xdata = s.get(xdata_propname).getData(doc)
+ self.ydata = ydata = s.get(ydata_propname).getData(doc)
+ labels = s.__getattr__(xdata_propname), s.__getattr__(ydata_propname)
+
+ if not xdata or not ydata or not mapdata_fn:
+ GenericPickable.__init__( self, widget, labels, (None, None), (None, None) )
+ return
+
+ # map all the valid data
+ x, y = N.array([]), N.array([])
+ xs, ys = N.array([]), N.array([])
+ for xvals, yvals in document.generateValidDatasetParts(xdata, ydata):
+ chunklen = min(len(xvals.data), len(yvals.data))
+
+ x = N.append(x, xvals.data[:chunklen])
+ y = N.append(y, yvals.data[:chunklen])
+
+ xs, ys = mapdata_fn(x, y)
+
+ # and set us up with the mapped data
+ GenericPickable.__init__( self, widget, labels, (x, y), (xs, ys) )
+
+ def pickPoint(self, x0, y0, bounds, distance_direction):
+ info = GenericPickable.pickPoint(self, x0, y0, bounds, distance_direction)
+ info.displaytype = (self.xdata.displaytype, self.ydata.displaytype)
+
+ if not info:
+ return info
+
+ # indicies are persistent
+ info.index.useindex = True
+ return info
+
+ def pickIndex(self, oldindex, direction, bounds):
+ info = GenericPickable.pickIndex(self, oldindex, direction, bounds)
+
+ if not info:
+ return info
+
+ # indicies are persistent
+ info.index.useindex = True
+ return info
diff -Nru veusz-1.10/widgets/plotters.py veusz-1.14/widgets/plotters.py
--- veusz-1.10/widgets/plotters.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/plotters.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,8 +19,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: plotters.py 1280 2010-06-13 14:53:17Z jeremysanders $
-
"""A generic plotter widget which is inherited by function and point."""
import veusz.qtall as qt4
@@ -109,10 +107,8 @@
"""
pass
- def clipAxesBounds(self, painter, axes, bounds):
- """Clip painter to start and stop values of axis.
- Returns clipping rectangle
- """
+ def clipAxesBounds(self, axes, bounds):
+ """Returns clipping rectange for start and stop values of axis."""
# update cached coordinates of axes
axes[0].plotterToDataCoords(bounds, N.array([]))
@@ -128,7 +124,6 @@
# actually clip the data
cliprect = qt4.QRectF(qt4.QPointF(x1, y1), qt4.QPointF(x2, y2))
- painter.setClipRect(cliprect)
return cliprect
def getAxisLabels(self, direction):
diff -Nru veusz-1.10/widgets/point.py veusz-1.14/widgets/point.py
--- veusz-1.10/widgets/point.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/point.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: point.py 1449 2010-11-22 09:26:58Z jeremysanders $
-
"""For plotting xy points."""
import veusz.qtall as qt4
@@ -28,6 +26,7 @@
import veusz.setting as setting
import veusz.utils as utils
+import pickable
from plotters import GenericPlotter
try:
@@ -143,7 +142,7 @@
hidevert = True # keep track of what's shown
hidehorz = True
- if ( (style == 'fillvert' or style == 'linevert') and
+ if ( 'vert' in style and
(ymin is not None and ymax is not None) and
not s.ErrorBarLine.hideVert ):
hidevert = False
@@ -151,7 +150,7 @@
utils.addNumpyToPolygonF(ptsbelow, xplotter, ymin)
utils.addNumpyToPolygonF(ptsabove, xplotter, ymax)
- elif ( (style == 'fillhorz' or style == 'linehorz') and
+ elif ( 'horz' in style and
(xmin is not None and xmax is not None) and
not s.ErrorBarLine.hideHorz ):
hidehorz = False
@@ -160,8 +159,7 @@
utils.addNumpyToPolygonF(ptsabove, xmax, yplotter)
# draw filled regions above/left and below/right
- if ( (style == 'fillvert' or style == 'fillhorz') and
- not (hidehorz and hidevert) ):
+ if 'fill' in style and not (hidehorz and hidevert):
# construct points for error bar regions
retnpts = qt4.QPolygonF()
utils.addNumpyToPolygonF(retnpts, xplotter[::-1], yplotter[::-1])
@@ -198,16 +196,53 @@
'fillvert': (_errorBarsFilled,),
'linehorz': (_errorBarsFilled,),
'linevert': (_errorBarsFilled,),
+ 'linehorzbar': (_errorBarsBar, _errorBarsFilled),
+ 'linevertbar': (_errorBarsBar, _errorBarsFilled),
}
-
class MarkerFillBrush(setting.Brush):
def __init__(self, name, **args):
setting.Brush.__init__(self, name, **args)
self.get('color').newDefault( setting.Reference(
'../PlotLine/color') )
-
+
+ self.add( setting.Colormap(
+ 'colorMap', 'grey',
+ descr = 'If color markers dataset is given, use this colormap '
+ 'instead of the fill color',
+ usertext='Color map',
+ formatting=True) )
+ self.add( setting.Bool(
+ 'colorMapInvert', False,
+ descr = 'Invert color map',
+ usertext = 'Invert map',
+ formatting=True) )
+
+class ColorSettings(setting.Settings):
+ """Settings for a coloring points using data values."""
+
+ def __init__(self, name):
+ setting.Settings.__init__(self, name, setnsmode='groupedsetting')
+ self.add( setting.DatasetOrFloatList(
+ 'points', '',
+ descr = 'Use color value (0-1) in dataset to paint points',
+ usertext='Color markers'), 7 )
+ self.add( setting.Float(
+ 'min', 0.,
+ descr = 'Minimum value of color dataset',
+ usertext = 'Min val' ))
+ self.add( setting.Float(
+ 'max', 1.,
+ descr = 'Maximum value of color dataset',
+ usertext = 'Max val' ))
+ self.add( setting.Choice(
+ 'scaling',
+ ['linear', 'sqrt', 'log', 'squared'],
+ 'linear',
+ descr = 'Scaling to transform numbers to color',
+ usertext='Scaling'))
+
class PointPlotter(GenericPlotter):
"""A class for plotting points and their errors."""
@@ -251,6 +286,8 @@
descr = 'Scale size of plotted markers by this dataset or'
' list of values', usertext='Scale markers'), 6 )
+ s.add( ColorSettings('Color') )
+
s.add( setting.DatasetOrFloatList(
'yData', 'y',
descr='Dataset containing y data or list of values',
@@ -349,13 +386,26 @@
return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') )
def updateAxisRange(self, axis, depname, axrange):
+ """Compute the effect of data on the axis range."""
dataname = {'sx': 'xData', 'sy': 'yData'}[depname]
- data = self.settings.get(dataname).getData(self.document)
+ dsetn = self.settings.get(dataname)
+ data = dsetn.getData(self.document)
+
if data:
drange = data.getRange()
if drange:
axrange[0] = min(axrange[0], drange[0])
axrange[1] = max(axrange[1], drange[1])
+ elif dsetn.isEmpty():
+ # no valid dataset.
+ # check if there a valid dataset for the other axis.
+ # if there is, treat this as a row number
+ dataname = {'sy': 'xData', 'sx': 'yData'}[depname]
+ data = self.settings.get(dataname).getData(self.document)
+ if data:
+ length = data.data.shape[0]
+ axrange[0] = min(axrange[0], 1)
+ axrange[1] = max(axrange[1], length)
def _getLinePoints( self, xvals, yvals, posn, xdata, ydata ):
"""Get the points corresponding to the line connecting the points."""
@@ -436,7 +486,8 @@
return path
painter.strokePath(p, painter.pen())
- def _drawBezierLine( self, painter, xvals, yvals, posn, xdata, ydata):
+ def _drawBezierLine( self, painter, xvals, yvals, posn,
+ xdata, ydata):
"""Handle bezier lines and fills."""
pts = self._getLinePoints(xvals, yvals, posn, xdata, ydata)
@@ -533,8 +584,8 @@
# plot error bar
painter.setPen( s.ErrorBarLine.makeQPenWHide(painter) )
for function in _errorBarFunctionMap[style]:
- function(style, xneg, xpos, yneg, ypos, xpts, ypts, s, painter,
- cliprect)
+ function(style, xneg, xpos, yneg, ypos, xpts, ypts, s,
+ painter, cliprect)
# draw line
if not s.PlotLine.hide:
@@ -555,7 +606,8 @@
painter.restore()
- def drawLabels(self, painter, xplotter, yplotter, textvals, markersize):
+ def drawLabels(self, painter, xplotter, yplotter,
+ textvals, markersize):
"""Draw labels for the points."""
s = self.settings
@@ -594,10 +646,53 @@
else:
return (text, yv.data)
- def draw(self, parentposn, painter, outerbounds=None):
+ def _fetchAxes(self):
+ """Returns the axes for this widget"""
+
+ s = self.settings
+ axes = self.parent.getAxes( (s.xAxis, s.yAxis) )
+
+ # fail if we don't have good axes
+ if ( None in axes or
+ axes[0].settings.direction != 'horizontal' or
+ axes[1].settings.direction != 'vertical' ):
+ return None
+
+ return axes
+
+ def _pickable(self, bounds):
+ axes = self._fetchAxes()
+
+ if axes is None:
+ map_fn = None
+ else:
+ map_fn = lambda x, y: ( axes[0].dataToPlotterCoords(bounds, x),
+ axes[1].dataToPlotterCoords(bounds, y) )
+
+ return pickable.DiscretePickable(self, 'xData', 'yData', map_fn)
+
+ def pickPoint(self, x0, y0, bounds, distance = 'radial'):
+ return self._pickable(bounds).pickPoint(x0, y0, bounds, distance)
+
+ def pickIndex(self, oldindex, direction, bounds):
+ return self._pickable(bounds).pickIndex(oldindex, direction, bounds)
+
+ def makeColorbarImage(self, direction='horz'):
+ """Make a QImage colorbar for the current plot."""
+
+ s = self.settings
+ c = s.Color
+ cmap = self.document.getColormap(
+ s.MarkerFill.colorMap, s.MarkerFill.colorMapInvert)
+
+ return utils.makeColorbarImage(
+ c.min, c.max, c.scaling, cmap, 0,
+ direction=direction)
+
+ def draw(self, parentposn, phelper, outerbounds=None):
"""Plot the data on a plotter."""
- posn = GenericPlotter.draw(self, parentposn, painter,
+ posn = GenericPlotter.draw(self, parentposn, phelper,
outerbounds=outerbounds)
x1, y1, x2, y2 = posn
@@ -613,8 +708,20 @@
yv = s.get('yData').getData(doc)
text = s.get('labels').getData(doc, checknull=True)
scalepoints = s.get('scalePoints').getData(doc)
+ colorpoints = s.Color.get('points').getData(doc)
+ # if a missing dataset, make a fake dataset for the second one
+ # based on a row number
+ if xv and not yv and s.get('yData').isEmpty():
+ # use index for y data
+ length = xv.data.shape[0]
+ yv = document.DatasetRange(length, (1,length))
+ elif yv and not xv and s.get('xData').isEmpty():
+ # use index for x data
+ length = yv.data.shape[0]
+ xv = document.DatasetRange(length, (1,length))
if not xv or not yv:
+ # no valid dataset, so exit
return
# if text entered, then multiply up to get same number of values
@@ -624,22 +731,19 @@
text = text*(length / len(text)) + text[:length % len(text)]
# get axes widgets
- axes = self.parent.getAxes( (s.xAxis, s.yAxis) )
-
- # return if there's no proper axes
- if ( None in axes or
- axes[0].settings.direction != 'horizontal' or
- axes[1].settings.direction != 'vertical' ):
+ axes = self._fetchAxes()
+ if not axes:
+ # no valid axes, so exit
return
# clip data within bounds of plotter
- painter.beginPaintingWidget(self, posn)
- painter.save()
- cliprect = self.clipAxesBounds(painter, axes, posn)
+ cliprect = self.clipAxesBounds(axes, posn)
+ painter = phelper.painter(self, posn, clip=cliprect)
# loop over chopped up values
- for xvals, yvals, tvals, ptvals in document.generateValidDatasetParts(
- xv, yv, text, scalepoints):
+ for xvals, yvals, tvals, ptvals, cvals in (
+ document.generateValidDatasetParts(
+ xv, yv, text, scalepoints, colorpoints)):
#print "Calculating coordinates"
# calc plotter coords of x and y points
@@ -689,20 +793,26 @@
yplotter[::s.thinfactor])
# whether to scale markers
- scaling = None
+ scaling = colorvals = cmap = None
if ptvals:
scaling = ptvals.data
+ # color point individually
+ if cvals:
+ colorvals = utils.applyScaling(
+ cvals.data, s.Color.scaling,
+ s.Color.min, s.Color.max)
+ cmap = self.document.getColormap(
+ s.MarkerFill.colorMap, s.MarkerFill.colorMapInvert)
# actually plot datapoints
utils.plotMarkers(painter, xplt, yplt, s.marker, markersize,
- scaling=scaling, clip=cliprect)
+ scaling=scaling, clip=cliprect,
+ cmap=cmap, colorvals=colorvals)
# finally plot any labels
if tvals and not s.Label.hide:
- self.drawLabels(painter, xplotter, yplotter, tvals, markersize)
-
- painter.restore()
- painter.endPaintingWidget()
+ self.drawLabels(painter, xplotter, yplotter,
+ tvals, markersize)
# allow the factory to instantiate an x,y plotter
document.thefactory.register( PointPlotter )
diff -Nru veusz-1.10/widgets/polar.py veusz-1.14/widgets/polar.py
--- veusz-1.10/widgets/polar.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/polar.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: polar.py 1474 2010-12-12 12:40:43Z jeremysanders $
-
"""Polar plot widget."""
import numpy as N
@@ -168,6 +166,8 @@
pp.arcTo(cliprect, 0, 360)
pp.addPolygon(pts)
painter.fillPath(pp, painter.brush())
+ elif filltype == 'polygon':
+ utils.plotClippedPolygon(painter, cliprect, pts)
def drawGraph(self, painter, bounds, datarange, outerbounds=None):
'''Plot graph area and axes.'''
@@ -204,10 +204,10 @@
if self._maxradius <= 0.:
self._maxradius = 1.
- atick = AxisTicks(0, self._maxradius, t.number,
- t.number*4,
+ atick = AxisTicks(0, self._maxradius, t.number, t.number*4,
extendbounds=False, extendzero=False)
- minval, maxval, majtick, mintick, tickformat = atick.getTicks()
+ atick.getTicks()
+ majtick = atick.tickvals
# draw ticks as circles
if not t.hideannuli:
@@ -226,14 +226,15 @@
tl = s.TickLabels
scale, format = tl.scale, tl.format
if format == 'Auto':
- format = tickformat
+ format = atick.autoformat
painter.setPen( tl.makeQPen() )
font = tl.makeQFont(painter)
# draw radial axis
if not s.TickLabels.hideradial:
for tick in majtick[1:]:
- num = utils.formatNumber(tick*scale, format)
+ num = utils.formatNumber(tick*scale, format,
+ locale=self.document.locale)
x = tick / self._maxradius * self._xscale + self._xc
r = utils.Renderer(painter, font, x, self._yc, num,
alignhorz=-1,
diff -Nru veusz-1.10/widgets/polygon.py veusz-1.14/widgets/polygon.py
--- veusz-1.10/widgets/polygon.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/polygon.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: polygon.py 1087 2009-10-06 18:44:36Z jeremysanders $
-
from itertools import izip
import veusz.document as document
@@ -51,7 +49,7 @@
usertext = 'Fill'),
pixmap = 'settings_plotfillbelow' )
- def draw(self, posn, painter, outerbounds=None):
+ def draw(self, posn, phelper, outerbounds=None):
"""Plot the data on a plotter."""
s = self.settings
@@ -67,11 +65,10 @@
# we can't calculate coordinates
return
- painter.beginPaintingWidget(self, posn)
- painter.save()
- painter.setClipRect( qt4.QRectF(posn[0], posn[1],
- posn[2]-posn[0],
- posn[3]-posn[1]) )
+ x1, y1, x2, y2 = posn
+ cliprect = qt4.QRectF( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) )
+ painter = phelper.painter(self, posn, clip=cliprect)
+
painter.setPen( s.Line.makeQPenWHide(painter) )
painter.setBrush( s.Fill.makeQBrushWHide() )
@@ -87,8 +84,5 @@
# draw it
painter.drawPolygon(poly)
- painter.restore()
- painter.endPaintingWidget()
-
# allow the factory to instantiate this
document.thefactory.register( Polygon )
diff -Nru veusz-1.10/widgets/root.py veusz-1.14/widgets/root.py
--- veusz-1.10/widgets/root.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/root.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,8 +19,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: root.py 1023 2009-07-11 17:52:27Z jeremysanders $
-
import veusz.qtall as qt4
import veusz.document as document
@@ -52,6 +50,8 @@
if type(self) == Root:
self.readDefaults()
+ s.get('englishlocale').setOnModified(self.changeLocale)
+
@classmethod
def addSettings(klass, s):
widget.Widget.addSettings(s)
@@ -67,57 +67,48 @@
descr='Height of the pages',
usertext='Page height',
formatting=True) )
+ s.add( setting.Bool('englishlocale', False,
+ descr='Use US/English number formatting for '
+ 'document',
+ usertext='English locale',
+ formatting=True) )
- def getSize(self, painter):
- """Get dimensions of widget in painter coordinates."""
- return ( self.settings.get('width').convert(painter),
- self.settings.get('height').convert(painter) )
-
- def draw(self, painter, pagenum):
+ def changeLocale(self):
+ """Update locale of document if changed by user."""
+
+ if self.settings.englishlocale:
+ self.document.locale = qt4.QLocale.c()
+ else:
+ self.document.locale = qt4.QLocale()
+ self.document.locale.setNumberOptions(qt4.QLocale.OmitGroupSeparator)
+
+ def getPage(self, pagenum):
+ """Get page widget."""
+ return self.children[pagenum]
+
+ def draw(self, painthelper, pagenum):
"""Draw the page requested on the painter."""
- xw, yw = self.getSize(painter)
+ xw, yw = painthelper.pagesize
posn = [0, 0, xw, yw]
- painter.beginPaintingWidget(self, posn)
+ painter = painthelper.painter(self, posn)
page = self.children[pagenum]
- page.draw( posn, painter )
+ page.draw( posn, painthelper )
- self.controlgraphitems = [
- controlgraph.ControlMarginBox(self, posn,
- [-10000, -10000,
- 10000, 10000],
- painter,
- ismovable = False)
- ]
-
- painter.endPaintingWidget()
+ # w and h are non integer
+ w = self.settings.get('width').convert(painter)
+ h = self.settings.get('height').convert(painter)
+ painthelper.setControlGraph(self, [
+ controlgraph.ControlMarginBox(self, [0, 0, w, h],
+ [-10000, -10000,
+ 10000, 10000],
+ painthelper,
+ ismovable = False)
+ ] )
def updateControlItem(self, cgi):
- """Graph resized or moved - call helper routine to move self."""
-
- s = self.settings
-
- # get margins in pixels
- width = cgi.posn[2] - cgi.posn[0]
- height = cgi.posn[3] - cgi.posn[1]
-
- # set up fake painter containing veusz scalings
- fakepainter = qt4.QPainter()
- fakepainter.veusz_page_size = cgi.page_size
- fakepainter.veusz_scaling = cgi.scaling
- fakepainter.veusz_pixperpt = cgi.pixperpt
-
- # convert to physical units
- width = s.get('width').convertInverse(width, fakepainter)
- height = s.get('height').convertInverse(height, fakepainter)
-
- # modify widget margins
- operations = (
- document.OperationSettingSet(s.get('width'), width),
- document.OperationSettingSet(s.get('height'), height),
- )
- self.document.applyOperation(
- document.OperationMultiple(operations, descr='change page size'))
+ """Call helper to set page size."""
+ cgi.setPageSize()
def fillStylesheet(self, stylesheet):
"""Register widgets with stylesheet."""
diff -Nru veusz-1.10/widgets/shape.py veusz-1.14/widgets/shape.py
--- veusz-1.10/widgets/shape.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/shape.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: shape.py 1322 2010-07-10 20:32:38Z jeremysanders $
-
"""For plotting shapes."""
import itertools
@@ -27,7 +25,7 @@
import veusz.setting as setting
import veusz.document as document
import veusz.utils as utils
-
+import widget
import controlgraph
import plotters
@@ -82,7 +80,7 @@
def drawShape(self, painter, rect):
pass
- def draw(self, posn, painter, outerbounds = None):
+ def draw(self, posn, phelper, outerbounds = None):
"""Plot the key on a plotter."""
s = self.settings
@@ -104,15 +102,14 @@
return
# if a dataset is used, we can't use control items
- isnotdataset = ( not s.get('xPos').isDataset(d) and
+ isnotdataset = ( not s.get('xPos').isDataset(d) and
not s.get('yPos').isDataset(d) and
not s.get('width').isDataset(d) and
not s.get('height').isDataset(d) and
not s.get('rotate').isDataset(d) )
- self.controlgraphitems = []
+ controlgraphitems = []
- painter.beginPaintingWidget(self, posn)
- painter.save()
+ painter = phelper.painter(self, posn)
# drawing settings for shape
if not s.Border.hide:
@@ -145,10 +142,9 @@
cgi.index = index
cgi.widgetposn = posn
index += 1
- self.controlgraphitems.append(cgi)
+ controlgraphitems.append(cgi)
- painter.restore()
- painter.endPaintingWidget()
+ phelper.setControlGraph(self, controlgraphitems)
def updateControlItem(self, cgi):
"""If control item is moved or resized, this is called."""
@@ -238,9 +234,15 @@
if type(self) == ImageFile:
self.readDefaults()
- self.cachepixmap = None
+ self.cacheimage = None
self.cachefilename = None
self.cachestat = None
+ self.cacheembeddata = None
+
+ self.addAction( widget.Action('embed', self.actionEmbed,
+ descr = 'Embed image in Veusz document '
+ 'to remove dependency on external file',
+ usertext = 'Embed image') )
@classmethod
def addSettings(klass, s):
@@ -252,6 +254,13 @@
usertext='Filename',
formatting=False),
posn=0 )
+
+ s.add( setting.Str('embeddedImageData', '',
+ descr='Embedded base 64-encoded image data, '
+ 'used if filename set to {embedded}',
+ usertext='Embedded data',
+ hidden=True) )
+
s.add( setting.Bool('aspect', True,
descr='Preserve aspect ratio',
usertext='Preserve aspect',
@@ -259,56 +268,108 @@
posn=0 )
s.Border.get('hide').newDefault(True)
- def updateCachedPixmap(self):
+ def actionEmbed(self):
+ """Embed external image into veusz document."""
+
+ s = self.settings
+
+ if s.filename == '{embedded}':
+ print "Data already embedded"
+ return
+
+ # get data from external file
+ try:
+ f = open(s.filename, 'rb')
+ data = f.read()
+ f.close()
+ except IOError:
+ print "Could not find file. Not embedding."
+ return
+
+ # convert to base 64 to make it nicer in the saved file
+ encoded = str(qt4.QByteArray(data).toBase64())
+
+ # now put embedded data in hidden setting
+ ops = [
+ document.OperationSettingSet(s.get('filename'), '{embedded}'),
+ document.OperationSettingSet(s.get('embeddedImageData'),
+ encoded)
+ ]
+ self.document.applyOperation(
+ document.OperationMultiple(ops, descr='embed image') )
+
+ def updateCachedImage(self):
"""Update cache."""
s = self.settings
self.cachestat = os.stat(s.filename)
- self.cachepixmap = qt4.QPixmap(s.filename)
+ self.cacheimage = qt4.QImage(s.filename)
self.cachefilename = s.filename
- return self.cachepixmap
+
+ def updateCachedEmbedded(self):
+ """Update cached image from embedded data."""
+ s = self.settings
+ self.cacheimage = qt4.QImage()
+
+ # convert the embedded data from base64 and load into the image
+ decoded = qt4.QByteArray.fromBase64(s.embeddedImageData)
+ self.cacheimage.loadFromData(decoded)
+
+ # we cache the data we have decoded
+ self.cacheembeddata = s.embeddedImageData
def drawShape(self, painter, rect):
- """Draw pixmap."""
+ """Draw image."""
s = self.settings
# draw border and fill
painter.drawRect(rect)
- # cache pixmap
- pixmap = None
+ # check to see whether image needs reloading
+ image = None
if s.filename != '' and os.path.isfile(s.filename):
- if (self.cachefilename != s.filename or
+ if (self.cachefilename != s.filename or
os.stat(s.filename) != self.cachestat):
- self.updateCachedPixmap()
- pixmap = self.cachepixmap
+ # update the image cache
+ self.updateCachedImage()
+ # clear any embedded image data
+ self.settings.get('embeddedImageData').set('')
+ image = self.cacheimage
+
+ # or needs recreating from embedded data
+ if s.filename == '{embedded}':
+ if s.embeddedImageData is not self.cacheembeddata:
+ self.updateCachedEmbedded()
+ image = self.cacheimage
+
+ # if no image, then use default image
+ if ( not image or image.isNull() or
+ image.width() == 0 or image.height() == 0 ):
+ # load replacement image
+ fname = os.path.join(utils.imagedir, 'button_imagefile.svg')
+ r = qt4.QSvgRenderer(fname)
+ r.render(painter, rect)
+
+ else:
+ # image rectangle
+ irect = qt4.QRectF(image.rect())
- # if no pixmap, then use default image
- if not pixmap or pixmap.width() == 0 or pixmap.height() == 0:
- pixmap = utils.getIcon('button_imagefile').pixmap(64, 64)
-
- # pixmap rectangle
- prect = qt4.QRectF(pixmap.rect())
-
- # preserve aspect ratio
- if s.aspect:
- xr = rect.width() / prect.width()
- yr = rect.height() / prect.height()
-
- if xr > yr:
- rect = qt4.QRectF(rect.left()+(rect.width()-
- prect.width()*yr)*0.5,
- rect.top(),
- prect.width()*yr,
- rect.height())
- else:
- rect = qt4.QRectF(rect.left(),
- rect.top()+(rect.height()-
- prect.height()*xr)*0.5,
- rect.width(),
- prect.height()*xr)
+ # preserve aspect ratio
+ if s.aspect:
+ xr = rect.width() / irect.width()
+ yr = rect.height() / irect.height()
+
+ if xr > yr:
+ rect = qt4.QRectF(
+ rect.left()+(rect.width()-irect.width()*yr)*0.5,
+ rect.top(), irect.width()*yr, rect.height())
+ else:
+ rect = qt4.QRectF(
+ rect.left(),
+ rect.top()+(rect.height()-irect.height()*xr)*0.5,
+ rect.width(), irect.height()*xr)
- # finally draw pixmap
- painter.drawPixmap(rect, pixmap, prect)
+ # finally draw image
+ painter.drawImage(rect, image, irect)
document.thefactory.register( Ellipse )
document.thefactory.register( Rectangle )
diff -Nru veusz-1.10/widgets/ternary.py veusz-1.14/widgets/ternary.py
--- veusz-1.10/widgets/ternary.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/widgets/ternary.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,492 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+"""Ternary plot widget."""
+
+import numpy as N
+import math
+from itertools import izip
+
+from nonorthgraph import NonOrthGraph
+from axisticks import AxisTicks
+from axis import MajorTick, MinorTick, GridLine, MinorGridLine, AxisLabel, \
+ TickLabel
+
+import veusz.qtall as qt4
+import veusz.document as document
+import veusz.setting as setting
+import veusz.utils as utils
+
+def rotatePts(x, y, theta):
+ '''Rotate points by theta degrees.'''
+ s = math.sin(theta*math.pi/180.)
+ c = math.cos(theta*math.pi/180.)
+ return x*c-y*s, x*s+y*c
+
+# translate coordinates a,b,c from user to plot
+# user can select different coordinate systems
+coord_lookup = {
+ 'bottom-left': (0, 1, 2),
+ 'bottom-right': (0, 2, 1),
+ 'left-bottom': (1, 0, 2),
+ 'left-right': (2, 0, 1),
+ 'right-bottom': (1, 2, 0),
+ 'right-left': (2, 1, 0)
+}
+
+# useful trigonometric identities
+sin30 = 0.5
+sin60 = cos30 = 0.86602540378
+tan30 = 0.5773502691
+
+class Ternary(NonOrthGraph):
+ '''Ternary plotter.'''
+
+ typename='ternary'
+ allowusercreation = True
+ description = 'Ternary graph'
+
+ def __init__(self, parent, name=None):
+ '''Initialise ternary plot.'''
+ NonOrthGraph.__init__(self, parent, name=name)
+ if type(self) == NonOrthGraph:
+ self.readDefaults()
+
+ @classmethod
+ def addSettings(klass, s):
+ '''Construct list of settings.'''
+ NonOrthGraph.addSettings(s)
+
+ s.add( setting.Choice('mode',
+ ('percentage', 'fraction'),
+ 'percentage',
+ descr='Show percentages or fractions',
+ usertext='Mode') )
+
+ s.add( setting.Choice('coords',
+ ('bottom-left', 'bottom-right',
+ 'left-bottom', 'left-right',
+ 'right-left', 'right-bottom'),
+ 'bottom-left',
+ descr='Axes to use for plotting coordinates',
+ usertext='Coord system') )
+
+ s.add( setting.Str('labelbottom', '',
+ descr='Bottom axis label text',
+ usertext='Label bottom') )
+ s.add( setting.Str('labelleft', '',
+ descr='Left axis label text',
+ usertext='Label left') )
+ s.add( setting.Str('labelright', '',
+ descr='Right axis label text',
+ usertext='Label right') )
+
+ s.add( setting.Float('originleft', 0.,
+ descr='Fractional origin of left axis at its top',
+ usertext='Left origin') )
+ s.add( setting.Float('originbottom', 0.,
+ descr='Fractional origin of bottom axis at its '
+ 'left', usertext='Bottom origin') )
+ s.add( setting.Float('fracsize', 1.,
+ descr='Fractional size of plot',
+ usertext='Size') )
+
+ s.add( AxisLabel('Label',
+ descr = 'Axis label settings',
+ usertext = 'Axis label'),
+ pixmap='settings_axislabel' )
+ s.add( TickLabel('TickLabels',
+ descr = 'Tick label settings',
+ usertext = 'Tick labels'),
+ pixmap='settings_axisticklabels' )
+ s.add( MajorTick('MajorTicks',
+ descr = 'Major tick line settings',
+ usertext = 'Major ticks'),
+ pixmap='settings_axismajorticks' )
+ s.add( MinorTick('MinorTicks',
+ descr = 'Minor tick line settings',
+ usertext = 'Minor ticks'),
+ pixmap='settings_axisminorticks' )
+ s.add( GridLine('GridLines',
+ descr = 'Grid line settings',
+ usertext = 'Grid lines'),
+ pixmap='settings_axisgridlines' )
+ s.add( MinorGridLine('MinorGridLines',
+ descr = 'Minor grid line settings',
+ usertext = 'Grid lines for minor ticks'),
+ pixmap='settings_axisminorgridlines' )
+
+ s.get('leftMargin').newDefault('1cm')
+ s.get('rightMargin').newDefault('1cm')
+ s.get('topMargin').newDefault('1cm')
+ s.get('bottomMargin').newDefault('1cm')
+
+ s.MajorTicks.get('number').newDefault(10)
+ s.MinorTicks.get('number').newDefault(50)
+ s.GridLines.get('hide').newDefault(False)
+ s.TickLabels.remove('rotate')
+
+ def _maxVal(self):
+ '''Get maximum value on axis.'''
+ if self.settings.mode == 'percentage':
+ return 100.
+ else:
+ return 1.
+
+ def coordRanges(self):
+ '''Get ranges of coordinates.'''
+
+ mv = self._maxVal()
+
+ # ranges for each coordinate
+ ra = [self._orgbot*mv, (self._orgbot+self._size)*mv]
+ rb = [self._orgleft*mv, (self._orgleft+self._size)*mv]
+ rc = [self._orgright*mv, (self._orgright+self._size)*mv]
+ ranges = [ra, rb, rc]
+
+ lookup = coord_lookup[self.settings.coords]
+ return ranges[lookup.index(0)], ranges[lookup.index(1)]
+
+ def graphToPlotCoords(self, coorda, coordb):
+ '''Convert coordinates in r, theta to x, y.'''
+
+ s = self.settings
+
+ # normalize coordinates
+ maxval = self._maxVal()
+ coordan = coorda / maxval
+ coordbn = coordb / maxval
+
+ # the three coordinates on the plot
+ clist = (coordan, coordbn, 1.-coordan-coordbn)
+
+ # select the right coordinates for a, b and c given the system
+ # requested by the user
+ # normalise by origins and plot size
+ lookup = coord_lookup[s.coords]
+ cbot = ( clist[ lookup[0] ] - self._orgbot ) / self._size
+ cleft = ( clist[ lookup[1] ] - self._orgleft ) / self._size
+ cright = ( clist[ lookup[2] ] - self._orgright ) / self._size
+
+ # from Ingram, 1984, Area, 16, 175
+ # remember that y goes in the opposite direction here
+ x = (0.5*cright + cbot)*self._width + self._box[0]
+ y = self._box[3] - cright * sin60 * self._width
+
+ return x, y
+
+ def drawFillPts(self, painter, cliprect, ptsx, ptsy, filltype):
+ '''Draw points for plotting a fill.'''
+ pts = qt4.QPolygonF()
+ utils.addNumpyToPolygonF(pts, ptsx, ptsy)
+
+ # this is broken: FIXME
+ if filltype == 'left':
+ dyend = ptsy[-1]-self._box[1]
+ pts.append( qt4.QPointF(ptsx[-1]-dyend*tan30, self._box[1]) )
+ dystart = ptsy[0]-self._box[1]
+ pts.append( qt4.QPointF(ptsx[0]-dystart*tan30, self._box[1]) )
+ elif filltype == 'right':
+ pts.append( qt4.QPointF(self._box[2], ptsy[-1]) )
+ pts.append( qt4.QPointF(self._box[2], ptsy[0]) )
+ elif filltype == 'bottom':
+ dyend = self._box[3]-ptsy[-1]
+ pts.append( qt4.QPointF(ptsx[-1]-dyend*tan30, self._box[3]) )
+ dystart = self._box[3]-ptsy[0]
+ pts.append( qt4.QPointF(ptsx[0]-dystart*tan30, self._box[3]) )
+ elif filltype == 'polygon':
+ pass
+ else:
+ pts = None
+
+ if pts is not None:
+ utils.plotClippedPolygon(painter, cliprect, pts)
+
+ def drawGraph(self, painter, bounds, datarange, outerbounds=None):
+ '''Plot graph area and axes.'''
+
+ s = self.settings
+
+ xw, yw = bounds[2]-bounds[0], bounds[3]-bounds[1]
+
+ d60 = 60./180.*math.pi
+ ang = math.atan2(yw, xw/2.)
+ if ang > d60:
+ # taller than wider
+ widthh = xw/2
+ height = math.tan(d60) * widthh
+ else:
+ # wider than taller
+ height = yw
+ widthh = height / math.tan(d60)
+
+ # box for equilateral triangle
+ self._box = ( (bounds[2]+bounds[0])/2 - widthh,
+ (bounds[1]+bounds[3])/2 - height/2,
+ (bounds[2]+bounds[0])/2 + widthh,
+ (bounds[1]+bounds[3])/2 + height/2 )
+ self._width = widthh*2
+ self._height = height
+
+ # triangle shaped polygon for graph
+ self._tripoly = p = qt4.QPolygonF()
+ p.append( qt4.QPointF(self._box[0], self._box[3]) )
+ p.append( qt4.QPointF(self._box[0]+widthh, self._box[1]) )
+ p.append( qt4.QPointF(self._box[2], self._box[3]) )
+
+ painter.setPen( s.Border.makeQPenWHide(painter) )
+ painter.setBrush( s.Background.makeQBrushWHide() )
+ painter.drawPolygon(p)
+
+ # work out origins and size
+ self._size = max(min(s.fracsize, 1.), 0.)
+
+ # make sure we don't go past the ends of the allowed range
+ # value of origin of left axis at top
+ self._orgleft = min(s.originleft, 1.-self._size)
+ # value of origin of bottom axis at left
+ self._orgbot = min(s.originbottom, 1.-self._size)
+ # origin of right axis at bottom
+ self._orgright = 1. - self._orgleft - (self._orgbot + self._size)
+
+ def _computeTickVals(self):
+ """Compute tick values."""
+
+ s = self.settings
+
+ # this is a hack as we lose ends off the axis otherwise
+ d = 1e-6
+
+ # get ticks along left axis
+ atickleft = AxisTicks(self._orgleft-d, self._orgleft+self._size+d,
+ s.MajorTicks.number, s.MinorTicks.number,
+ extendbounds=False, extendzero=False)
+ atickleft.getTicks()
+ # use the interval from above to calculate ticks for right
+ atickright = AxisTicks(self._orgright-d, self._orgright+self._size+d,
+ s.MajorTicks.number, s.MinorTicks.number,
+ extendbounds=False, extendzero=False,
+ forceinterval = atickleft.interval)
+ atickright.getTicks()
+ # then calculate for bottom
+ atickbot = AxisTicks(self._orgbot-d, self._orgbot+self._size+d,
+ s.MajorTicks.number, s.MinorTicks.number,
+ extendbounds=False, extendzero=False,
+ forceinterval = atickleft.interval)
+ atickbot.getTicks()
+
+ return atickbot, atickleft, atickright
+
+ def setClip(self, painter, bounds):
+ '''Set clipping for graph.'''
+ p = qt4.QPainterPath()
+ p.addPolygon( self._tripoly )
+ painter.setClipPath(p)
+
+ def _getLabels(self, ticks, autoformat):
+ """Return tick labels."""
+ labels = []
+ tl = self.settings.TickLabels
+ format = tl.format
+ scale = tl.scale
+ if format.lower() == 'auto':
+ format = autoformat
+ for v in ticks:
+ l = utils.formatNumber(v*scale, format, locale=self.document.locale)
+ labels.append(l)
+ return labels
+
+ def _drawTickSet(self, painter, tickSetn, gridSetn,
+ tickbot, tickleft, tickright,
+ tickLabelSetn=None, labelSetn=None):
+ '''Draw a set of ticks (major or minor).
+
+ tickSetn: tick setting to get line details
+ gridSetn: setting for grid line (if any)
+ tickXXX: tick arrays for each axis
+ tickLabelSetn: setting used to label ticks, or None if minor ticks
+ labelSetn: setting for labels, if any
+ '''
+
+ # this is mostly a lot of annoying trigonometry
+ # compute line ends for ticks and grid lines
+
+ tl = tickSetn.get('length').convert(painter)
+ mv = self._maxVal()
+
+ # bottom ticks
+ x1 = (tickbot - self._orgbot)/self._size*self._width + self._box[0]
+ x2 = x1 - tl * sin30
+ y1 = self._box[3] + N.zeros(x1.shape)
+ y2 = y1 + tl * cos30
+ tickbotline = (x1, y1, x2, y2)
+
+ # bottom grid (removing lines at edge of plot)
+ scaletick = 1 - (tickbot-self._orgbot)/self._size
+ gx = x1 + scaletick*self._width*sin30
+ gy = y1 - scaletick*self._width*cos30
+ ne = (scaletick > 1e-6) & (scaletick < (1-1e-6))
+ gridbotline = (x1[ne], y1[ne], gx[ne], gy[ne])
+
+ # left ticks
+ x1 = -(tickleft - self._orgleft)/self._size*self._width*sin30 + (
+ self._box[0] + self._box[2])*0.5
+ x2 = x1 - tl * sin30
+ y1 = (tickleft - self._orgleft)/self._size*self._width*cos30 + self._box[1]
+ y2 = y1 - tl * cos30
+ tickleftline = (x1, y1, x2, y2)
+
+ # left grid
+ scaletick = 1 - (tickleft-self._orgleft)/self._size
+ gx = x1 + scaletick*self._width*sin30
+ gy = self._box[3] + N.zeros(y1.shape)
+ ne = (scaletick > 1e-6) & (scaletick < (1-1e-6))
+ gridleftline = (x1[ne], y1[ne], gx[ne], gy[ne])
+
+ # right ticks
+ x1 = -(tickright - self._orgright)/self._size*self._width*sin30+self._box[2]
+ x2 = x1 + tl
+ y1 = -(tickright - self._orgright)/self._size*self._width*cos30+self._box[3]
+ y2 = y1
+ tickrightline = (x1, y1, x2, y2)
+
+ # right grid
+ scaletick = 1 - (tickright-self._orgright)/self._size
+ gx = x1 - scaletick*self._width
+ gy = y1
+ gridrightline = (x1[ne], y1[ne], gx[ne], gy[ne])
+
+ if not gridSetn.hide:
+ # draw the grid
+ pen = gridSetn.makeQPen(painter)
+ painter.setPen(pen)
+ utils.plotLinesToPainter(painter, *gridbotline)
+ utils.plotLinesToPainter(painter, *gridleftline)
+ utils.plotLinesToPainter(painter, *gridrightline)
+
+ # calculate deltas for ticks
+ bdelta = ldelta = rdelta = 0
+
+ if not tickSetn.hide:
+ # draw ticks themselves
+ pen = tickSetn.makeQPen(painter)
+ pen.setCapStyle(qt4.Qt.FlatCap)
+ painter.setPen(pen)
+ utils.plotLinesToPainter(painter, *tickbotline)
+ utils.plotLinesToPainter(painter, *tickleftline)
+ utils.plotLinesToPainter(painter, *tickrightline)
+
+ ldelta += tl*sin30
+ bdelta += tl*cos30
+ rdelta += tl
+
+ if tickLabelSetn is not None and not tickLabelSetn.hide:
+ # compute the labels for the ticks
+ tleftlabels = self._getLabels(tickleft*mv, '%Vg')
+ trightlabels = self._getLabels(tickright*mv, '%Vg')
+ tbotlabels = self._getLabels(tickbot*mv, '%Vg')
+
+ painter.setPen( tickLabelSetn.makeQPen() )
+ font = tickLabelSetn.makeQFont(painter)
+ painter.setFont(font)
+
+ fm = utils.FontMetrics(font, painter.device())
+ sp = fm.leading() + fm.descent()
+ off = tickLabelSetn.get('offset').convert(painter)
+
+ # draw tick labels in each direction
+ hlabbot = wlableft = wlabright = 0
+ for l, x, y in izip(tbotlabels, tickbotline[2], tickbotline[3]+off):
+ r = utils.Renderer(painter, font, x, y, l, 0, 1, 0)
+ bounds = r.render()
+ hlabbot = max(hlabbot, bounds[3]-bounds[1])
+ for l, x, y in izip(tleftlabels, tickleftline[2]-off-sp, tickleftline[3]):
+ r = utils.Renderer(painter, font, x, y, l, 1, 0, 0)
+ bounds = r.render()
+ wlableft = max(wlableft, bounds[2]-bounds[0])
+ for l, x, y in izip(trightlabels,tickrightline[2]+off+sp, tickrightline[3]):
+ r = utils.Renderer(painter, font, x, y, l, -1, 0, 0)
+ bounds = r.render()
+ wlabright = max(wlabright, bounds[2]-bounds[0])
+
+ bdelta += hlabbot+off+sp
+ ldelta += wlableft+off+sp
+ rdelta += wlabright+off+sp
+
+ if labelSetn is not None and not labelSetn.hide:
+ # draw label on edges (if requested)
+ painter.setPen( labelSetn.makeQPen() )
+ font = labelSetn.makeQFont(painter)
+ painter.setFont(font)
+
+ fm = utils.FontMetrics(font, painter.device())
+ sp = fm.leading() + fm.descent()
+ off = labelSetn.get('offset').convert(painter)
+
+ # bottom label
+ r = utils.Renderer(painter, font,
+ self._box[0]+self._width/2,
+ self._box[3] + bdelta + off,
+ self.settings.labelbottom,
+ 0, 1)
+ r.render()
+
+ # left label - rotate frame before drawing so we can get
+ # the bounds correct
+ r = utils.Renderer(painter, font, 0, -sp,
+ self.settings.labelleft,
+ 0, -1)
+ painter.save()
+ painter.translate(self._box[0]+self._width*0.25 - ldelta - off,
+ 0.5*(self._box[1]+self._box[3]))
+ painter.rotate(-60)
+ r.render()
+ painter.restore()
+
+ # right label
+ r = utils.Renderer(painter, font, 0, -sp,
+ self.settings.labelright,
+ 0, -1)
+ painter.save()
+ painter.translate(self._box[0]+self._width*0.75 + ldelta + off,
+ 0.5*(self._box[1]+self._box[3]))
+ painter.rotate(60)
+ r.render()
+ painter.restore()
+
+ def drawAxes(self, painter, bounds, datarange, outerbounds=None):
+ '''Draw plot axes.'''
+
+ s = self.settings
+
+ # compute tick values for later when plotting axes
+ tbot, tleft, tright = self._computeTickVals()
+
+ # draw the major ticks
+ self._drawTickSet(painter, s.MajorTicks, s.GridLines,
+ tbot.tickvals, tleft.tickvals, tright.tickvals,
+ tickLabelSetn=s.TickLabels,
+ labelSetn=s.Label)
+
+ # now draw the minor ones
+ self._drawTickSet(painter, s.MinorTicks, s.MinorGridLines,
+ tbot.minorticks, tleft.minorticks, tright.minorticks)
+
+document.thefactory.register(Ternary)
diff -Nru veusz-1.10/widgets/textlabel.py veusz-1.14/widgets/textlabel.py
--- veusz-1.10/widgets/textlabel.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/textlabel.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: textlabel.py 1064 2009-09-19 19:05:01Z jeremysanders $
-
"""For plotting one or more text labels on a graph."""
import itertools
@@ -77,7 +75,7 @@
cnvtalignhorz = { 'left': -1, 'centre': 0, 'right': 1 }
cnvtalignvert = { 'top': 1, 'centre': 0, 'bottom': -1 }
- def draw(self, posn, painter, outerbounds = None):
+ def draw(self, posn, phelper, outerbounds = None):
"""Draw the text label."""
s = self.settings
@@ -94,17 +92,16 @@
# we can't calculate coordinates
return
- painter.beginPaintingWidget(self, posn)
- painter.save()
+ painter = phelper.painter(self, posn)
textpen = s.get('Text').makeQPen()
painter.setPen(textpen)
font = s.get('Text').makeQFont(painter)
# we should only be able to move non-dataset labels
- self.controlgraphitems = []
isnotdataset = ( not s.get('xPos').isDataset(d) and
not s.get('yPos').isDataset(d) )
+ controlgraphitems = []
for index, (x, y, t) in enumerate(itertools.izip(
xp, yp, itertools.cycle(text))):
# render the text
@@ -115,16 +112,14 @@
# add cgi for adjustable positions
if isnotdataset:
- cgi = controlgraph.ControlMovableBox(self, tbounds,
- painter,
+ cgi = controlgraph.ControlMovableBox(self, tbounds, phelper,
crosspos = (x, y))
cgi.labelpt = (x, y)
cgi.widgetposn = posn
cgi.index = index
- self.controlgraphitems.append(cgi)
+ controlgraphitems.append(cgi)
- painter.restore()
- painter.endPaintingWidget()
+ phelper.setControlGraph(self, controlgraphitems)
def updateControlItem(self, cgi):
"""Update position of point given new name and vals."""
diff -Nru veusz-1.10/widgets/vectorfield.py veusz-1.14/widgets/vectorfield.py
--- veusz-1.10/widgets/vectorfield.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/vectorfield.py 2011-11-22 20:23:31.000000000 +0000
@@ -18,8 +18,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: vectorfield.py 1387 2010-08-29 15:24:57Z jeremysanders $
-
import itertools
import numpy as N
@@ -126,10 +124,24 @@
axrange[0] = min( axrange[0], dyrange[0] )
axrange[1] = max( axrange[1], dyrange[1] )
- def draw(self, parentposn, painter, outerbounds = None):
+ def drawKeySymbol(self, number, painter, x, y, width, height):
+ """Draw the plot symbol and/or line."""
+ painter.save()
+
+ s = self.settings
+ painter.setPen( s.Line.makeQPenWHide(painter) )
+ painter.setBrush( s.get('Fill').makeQBrushWHide() )
+ utils.plotLineArrow(painter, x+width, y+height*0.5,
+ width, 180, height*0.25,
+ arrowleft=s.arrowfront,
+ arrowright=s.arrowback)
+
+ painter.restore()
+
+ def draw(self, parentposn, phelper, outerbounds = None):
"""Draw the widget."""
- posn = plotters.GenericPlotter.draw(self, parentposn, painter,
+ posn = plotters.GenericPlotter.draw(self, parentposn, phelper,
outerbounds = outerbounds)
x1, y1, x2, y2 = posn
s = self.settings
@@ -160,9 +172,8 @@
return
# clip data within bounds of plotter
- painter.beginPaintingWidget(self, posn)
- painter.save()
- cliprect = self.clipAxesBounds(painter, axes, posn)
+ cliprect = self.clipAxesBounds(axes, posn)
+ painter = phelper.painter(self, posn, clip=cliprect)
baselength = s.get('baselength').convert(painter)
@@ -218,10 +229,7 @@
utils.plotLineArrow(painter, x, y, l, a, asize,
arrowleft=s.arrowfront,
arrowright=s.arrowback)
-
- painter.restore()
- painter.endPaintingWidget()
-
+
# allow the factory to instantiate a vector field
document.thefactory.register( VectorField )
diff -Nru veusz-1.10/widgets/widget.py veusz-1.14/widgets/widget.py
--- veusz-1.10/widgets/widget.py 2010-12-12 12:41:11.000000000 +0000
+++ veusz-1.14/widgets/widget.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,8 +19,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: widget.py 1325 2010-07-12 13:04:13Z jeremysanders $
-
import itertools
import veusz.document as document
@@ -53,6 +51,9 @@
class Widget(object):
""" Fundamental plotting widget interface."""
+ # differentiate widgets, settings and setting
+ nodetype = 'widget'
+
typename = 'generic'
allowusercreation = False
@@ -85,7 +86,8 @@
self.position = (0., 0., 1., 1.)
# settings for widget
- self.settings = setting.Settings( 'Widget_' + self.typename )
+ self.settings = setting.Settings( 'Widget_' + self.typename,
+ setnsmode='widgetsettings' )
self.settings.parent = self
self.addSettings(self.settings)
@@ -93,9 +95,6 @@
# actions for widget
self.actions = []
- # pts user can move around
- self.controlgraphitems = []
-
@classmethod
def addSettings(klass, s):
"""Add items to settings s."""
@@ -129,7 +128,6 @@
raise ValueError, 'New name "%s" already exists' % name
self.name = name
- self.document.setModified()
def addDefaultSubWidgets(self):
'''Add default sub widgets to widget, if any'''
@@ -257,6 +255,13 @@
raise ValueError, \
"Cannot remove graph '%s' - does not exist" % name
+ def widgetSiblingIndex(self):
+ """Get index of widget in its siblings."""
+ if self.parent is None:
+ return 0
+ else:
+ return self.parent.children.index(self)
+
def _getPath(self):
"""Returns a path for the object, e.g. /plot1/x."""
@@ -272,7 +277,8 @@
return build
path = property(_getPath)
- def computeBounds(self, parentposn, painter, margins = (0., 0., 0., 0.)):
+ def computeBounds(self, parentposn, painthelper,
+ margins = (0., 0., 0., 0.)):
"""Compute a bounds array, giving the bounding box for the widget."""
# get parent's position
@@ -285,20 +291,20 @@
dx1, dy1, dx2, dy2 = margins
return [ x1+dx1, y1+dy1, x2-dx2, y2-dy2 ]
- def draw(self, parentposn, painter, outerbounds = None):
+ def draw(self, parentposn, painthelper, outerbounds = None):
"""Draw the widget and its children in posn (a tuple with x1,y1,x2,y2).
painter is the widget.Painter to draw on
outerbounds contains "ultimate" bounds we don't go outside
"""
- bounds = self.computeBounds(parentposn, painter)
+ bounds = self.computeBounds(parentposn, painthelper)
if not self.settings.hide:
# iterate over children in reverse order
for c in reversed(self.children):
- c.draw(bounds, painter, outerbounds=outerbounds)
+ c.draw(bounds, painthelper, outerbounds=outerbounds)
# return our final bounds
return bounds
@@ -401,7 +407,6 @@
if existingname:
w.name = w.chooseName()
- self.document.setModified(True)
return True
def updateControlItem(self, controlitem, pos):
diff -Nru veusz-1.10/windows/consolewindow.py veusz-1.14/windows/consolewindow.py
--- veusz-1.10/windows/consolewindow.py 2010-12-12 12:41:08.000000000 +0000
+++ veusz-1.14/windows/consolewindow.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,8 +19,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: consolewindow.py 1180 2010-02-27 18:03:13Z jeremysanders $
-
import codeop
import traceback
import sys
@@ -60,6 +58,7 @@
qt4.QLineEdit.__init__(self, *args)
self.history = []
self.history_posn = 0
+ self.entered_text = ''
qt4.QObject.connect( self, qt4.SIGNAL("returnPressed()"),
self.slotReturnPressed )
@@ -73,9 +72,10 @@
command = unicode( self.text() )
self.setText("")
- # keep the command for history (and move back to top)
+ # keep the command for history
self.history.append( command )
- self.history_posn = 0
+ self.history_posn = len(self.history)
+ self.entered_text = ''
# tell the console we have a command
self.emit( qt4.SIGNAL("sigEnter"), command)
@@ -91,29 +91,50 @@
# check whether one of the "history keys" has been pressed
if code in _CommandEdit.historykeys:
- # move up or down in the history list
+ # look for the next or previous history item which our current text
+ # is a prefix of
+ if self.isModified():
+ text = unicode(self.text())
+ self.history_posn = len(self.history)
+ else:
+ text = self.entered_text
+
if code == qt4.Qt.Key_Up:
- self.history_posn += 1
+ step = -1
elif code == qt4.Qt.Key_Down:
- self.history_posn -= 1
+ step = 1
+
+ newpos = self.history_posn + step
- # make sure counter is within bounds
- self.history_posn = max(self.history_posn, 0)
- self.history_posn = min(self.history_posn, len(self.history))
+ while True:
+ if newpos >= len(self.history):
+ break
+ if newpos < 0:
+ return
+ if self.history[newpos].startswith(text):
+ break
+
+ newpos += step
+
+ if newpos >= len(self.history):
+ # go back to whatever the user had typed in
+ self.history_posn = len(self.history)
+ self.setText(self.entered_text)
+ return
+
+ # found a relevant history item
+ self.history_posn = newpos
# user has modified text since last set
if self.isModified():
- self.history.append( unicode(self.text()) )
- self.history_posn += 1
+ self.entered_text = text
# replace the text in the control
- text = ''
- if self.history_posn > 0:
- text = self.history[ -self.history_posn ]
+ text = self.history[ self.history_posn ]
self.setText(text)
-introtext=u'''Welcome to Veusz --- a scientific plotting application.
-Veusz version %s, Copyright \u00a9 2003-2010 Jeremy Sanders <jeremy@jeremysanders.net>
+introtext=u'''Welcome to Veusz %s --- a scientific plotting application.
+Copyright \u00a9 2003-2011 Jeremy Sanders <jeremy@jeremysanders.net> and contributors.
Veusz comes with ABSOLUTELY NO WARRANTY. Veusz is Free Software, and you are
welcome to redistribute it under certain conditions. Enter "GPL()" for details.
This window is a Python command line console and acts as a calculator.
@@ -136,6 +157,7 @@
# start an interpreter instance to the document
self.interpreter = document.CommandInterpreter(thedocument)
+ self.document = thedocument
# output from the interpreter goes to self.output_stdxxx
self.con_stdout = _Writer(self.output_stdout)
@@ -179,6 +201,33 @@
self.connect( thedocument, qt4.SIGNAL("sigLog"),
self.slotDocumentLog )
+ def _makeTextFormat(self, cursor, color):
+ fmt = cursor.charFormat()
+
+ if color is not None:
+ brush = qt4.QBrush(color)
+ fmt.setForeground(brush)
+ else:
+ # use the default foreground color
+ fmt.clearForeground()
+
+ return fmt
+
+ def appendOutput(self, text, style):
+ """Add text to the tail of the error log, with a specified style"""
+ if style == 'error':
+ color = setting.settingdb.color('error')
+ elif style == 'command':
+ color = setting.settingdb.color('command')
+ else:
+ color = None
+
+ cursor = self._outputdisplay.textCursor()
+ cursor.movePosition(qt4.QTextCursor.End)
+ cursor.insertText(text, self._makeTextFormat(cursor, color))
+ self._outputdisplay.setTextCursor(cursor)
+ self._outputdisplay.ensureCursorVisible()
+
def runFunction(self, func):
"""Execute the function within the console window, trapping
exceptions."""
@@ -190,14 +239,16 @@
sys.stderr = _Writer(self.output_stderr)
# catch any exceptions, printing problems to stderr
+ self.document.suspendUpdates()
try:
func()
- except Exception, e:
+ except:
# print out the backtrace to stderr
i = sys.exc_info()
backtrace = traceback.format_exception( *i )
for l in backtrace:
sys.stderr.write(l)
+ self.document.enableUpdates()
# return output streams
sys.stdout = temp_stdout
@@ -223,24 +274,16 @@
def output_stdout(self, text):
""" Write text in stdout font to the log."""
self.checkVisible()
- self._outputdisplay.insertPlainText(text)
- self._outputdisplay.ensureCursorVisible()
+ self.appendOutput(text, 'normal')
def output_stderr(self, text):
""" Write text in stderr font to the log."""
self.checkVisible()
-
- # insert text as bright error color
- self._outputdisplay.setTextColor( setting.settingdb.color('error') )
- self._outputdisplay.insertPlainText(text)
- self._outputdisplay.setTextColor(
- qt4.qApp.palette().color(qt4.QPalette.Text) )
- self._outputdisplay.ensureCursorVisible()
+ self.appendOutput(text, 'error')
def insertTextInOutput(self, text):
""" Inserts the text into the log."""
- self._outputdisplay.append( text )
- self._outputdisplay.ensureCursorVisible()
+ self.appendOutput(text, 'normal')
def slotEnter(self, command):
""" Called if the return key is pressed in the edit control."""
@@ -261,12 +304,7 @@
prompt = '...'
# output the command in the log pane
- self._outputdisplay.setTextColor( setting.settingdb.color('command') )
- self._outputdisplay.insertPlainText('%s %s\n' % (prompt, command))
- self._outputdisplay.setTextColor(
- qt4.qApp.palette().color(qt4.QPalette.Text) )
-
- self._outputdisplay.ensureCursorVisible()
+ self.appendOutput('%s %s\n' % (prompt, command), 'command')
# are we ready to run this?
if c is None or (len(command) != 0 and
diff -Nru veusz-1.10/windows/datanavigator.py veusz-1.14/windows/datanavigator.py
--- veusz-1.10/windows/datanavigator.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/windows/datanavigator.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+###############################################################################
+
+import veusz.qtall as qt4
+from veusz.qtwidgets.datasetbrowser import DatasetBrowser
+
+class DataNavigatorWindow(qt4.QDockWidget):
+ """A dock window containing a dataset browsing widget."""
+
+ def __init__(self, thedocument, mainwin, *args):
+ qt4.QDockWidget.__init__(self, *args)
+ self.setWindowTitle("Data - Veusz")
+ self.setObjectName("veuszdatawindow")
+
+ self.nav = DatasetBrowser(thedocument, mainwin, self)
+ self.setWidget(self.nav)
diff -Nru veusz-1.10/windows/icons/button_ternary.svg veusz-1.14/windows/icons/button_ternary.svg
--- veusz-1.10/windows/icons/button_ternary.svg 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/windows/icons/button_ternary.svg 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,88 @@
+
+
+
+
diff -Nru veusz-1.10/windows/icons/error_linehorzbar.svg veusz-1.14/windows/icons/error_linehorzbar.svg
--- veusz-1.10/windows/icons/error_linehorzbar.svg 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/windows/icons/error_linehorzbar.svg 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,99 @@
+
+
+
+
diff -Nru veusz-1.10/windows/icons/error_linevertbar.svg veusz-1.14/windows/icons/error_linevertbar.svg
--- veusz-1.10/windows/icons/error_linevertbar.svg 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/windows/icons/error_linevertbar.svg 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,99 @@
+
+
+
+
diff -Nru veusz-1.10/windows/icons/kde-clipboard.svg veusz-1.14/windows/icons/kde-clipboard.svg
--- veusz-1.10/windows/icons/kde-clipboard.svg 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/windows/icons/kde-clipboard.svg 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,132 @@
+
+
diff -Nru veusz-1.10/windows/icons/veusz-pick-data.svg veusz-1.14/windows/icons/veusz-pick-data.svg
--- veusz-1.10/windows/icons/veusz-pick-data.svg 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/windows/icons/veusz-pick-data.svg 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,73 @@
+
+
diff -Nru veusz-1.10/windows/__init__.py veusz-1.14/windows/__init__.py
--- veusz-1.10/windows/__init__.py 2010-12-12 12:41:08.000000000 +0000
+++ veusz-1.14/windows/__init__.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,6 +16,4 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: __init__.py 872 2008-12-29 12:51:59Z jeremysanders $
-
"""Veusz windows module."""
diff -Nru veusz-1.10/windows/mainwindow.py veusz-1.14/windows/mainwindow.py
--- veusz-1.10/windows/mainwindow.py 2010-12-12 12:41:08.000000000 +0000
+++ veusz-1.14/windows/mainwindow.py 2011-11-22 20:23:31.000000000 +0000
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
# Copyright (C) 2003 Jeremy S. Sanders
# Email: Jeremy Sanders
#
@@ -16,13 +17,12 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: mainwindow.py 1455 2010-11-26 22:35:03Z jeremysanders $
-
"""Implements the main window of the application."""
import os.path
import sys
import traceback
+import glob
import veusz.qtall as qt4
@@ -34,6 +34,7 @@
import consolewindow
import plotwindow
import treeeditwindow
+from datanavigator import DataNavigatorWindow
from veusz.dialogs.aboutdialog import AboutDialog
from veusz.dialogs.reloaddata import ReloadData
@@ -83,6 +84,13 @@
win.treeedit.doInitialWidgetSelect()
cls.windows.append(win)
+
+ # check if tutorial wanted
+ if not setting.settingdb['ask_tutorial']:
+ win.askTutorial()
+ # don't ask again
+ setting.settingdb['ask_tutorial'] = True
+
return win
def __init__(self, *args):
@@ -121,6 +129,8 @@
self.formatdock = treeeditwindow.FormatDock(self.document,
self.treeedit, self)
self.addDockWidget(qt4.Qt.LeftDockWidgetArea, self.formatdock)
+ self.datadock = DataNavigatorWindow(self.document, self, self)
+ self.addDockWidget(qt4.Qt.RightDockWidgetArea, self.datadock)
# make the console window a dock
self.console = consolewindow.ConsoleWindow(self.document,
@@ -129,15 +139,36 @@
self.interpreter = self.console.interpreter
self.addDockWidget(qt4.Qt.BottomDockWidgetArea, self.console)
- # keep page number up to date
+ # assemble the statusbar
statusbar = self.statusbar = qt4.QStatusBar(self)
self.setStatusBar(statusbar)
self.updateStatusbar('Ready')
- self.pagelabel = qt4.QLabel(statusbar)
- statusbar.addWidget(self.pagelabel)
+
+ # a label for the picker readout
+ self.pickerlabel = qt4.QLabel(statusbar)
+ self._setPickerFont(self.pickerlabel)
+ statusbar.addPermanentWidget(self.pickerlabel)
+ self.pickerlabel.hide()
+
+ # plot queue - how many plots are currently being drawn
+ self.plotqueuecount = 0
+ self.connect( self.plot, qt4.SIGNAL("queuechange"),
+ self.plotQueueChanged )
+ self.plotqueuelabel = qt4.QLabel()
+ self.plotqueuelabel.setToolTip("Number of rendering jobs remaining")
+ statusbar.addWidget(self.plotqueuelabel)
+ self.plotqueuelabel.show()
+
+ # a label for the cursor position readout
self.axisvalueslabel = qt4.QLabel(statusbar)
- statusbar.addWidget(self.axisvalueslabel)
+ statusbar.addPermanentWidget(self.axisvalueslabel)
self.axisvalueslabel.show()
+ self.slotUpdateAxisValues(None)
+
+ # a label for the page number readout
+ self.pagelabel = qt4.QLabel(statusbar)
+ statusbar.addPermanentWidget(self.pagelabel)
+ self.pagelabel.show()
# working directory - use previous one
self.dirname = setdb.get('dirname', qt4.QDir.homePath())
@@ -150,7 +181,11 @@
self.slotUpdatePage )
self.connect( self.plot, qt4.SIGNAL("sigAxisValuesFromMouse"),
self.slotUpdateAxisValues )
-
+ self.connect( self.plot, qt4.SIGNAL("sigPickerEnabled"),
+ self.slotPickerEnabled )
+ self.connect( self.plot, qt4.SIGNAL("sigPointPicked"),
+ self.slotUpdatePickerLabel )
+
# disable save if already saved
self.connect( self.document, qt4.SIGNAL("sigModified"),
self.slotModifiedDoc )
@@ -161,8 +196,8 @@
# if a widget in the plot window is clicked by the user
self.connect( self.plot, qt4.SIGNAL("sigWidgetClicked"),
self.treeedit.selectWidget )
- self.connect( self.treeedit, qt4.SIGNAL("widgetSelected"),
- self.plot.selectedWidget )
+ self.connect( self.treeedit, qt4.SIGNAL("widgetsSelected"),
+ self.plot.selectedWidgets )
# enable/disable undo/redo
self.connect(self.menus['edit'], qt4.SIGNAL('aboutToShow()'),
@@ -267,6 +302,7 @@
"""Undo the previous operation"""
if self.document.canUndo():
self.document.undoOperation()
+ self.treeedit.checkWidgetSelected()
def slotEditRedo(self):
"""Redo the previous operation"""
@@ -405,6 +441,10 @@
'view.console':
a(self, 'Show or hide console window', 'Console window',
None, checkable=True),
+ 'view.datanav':
+ a(self, 'Show or hide data navigator window', 'Data navigator window',
+ None, checkable=True),
+
'view.maintool':
a(self, 'Show or hide main toolbar', 'Main toolbar',
None, checkable=True),
@@ -452,6 +492,9 @@
'help.bug':
a(self, 'Report a bug on the internet',
'Suggestions and bugs', self.slotHelpBug),
+ 'help.tutorial':
+ a(self, 'An interactive Veusz tutorial',
+ 'Tutorial', self.slotHelpTutorial),
'help.about':
a(self, 'Displays information about the program', 'About...',
self.slotHelpAbout, icon='veusz')
@@ -491,12 +534,14 @@
editmenu = [
'edit.undo', 'edit.redo',
'',
+ ['edit.select', '&Select', []],
+ '',
'edit.prefs', 'edit.stylesheet', 'edit.custom',
''
]
viewwindowsmenu = [
'view.edit', 'view.props', 'view.format',
- 'view.console',
+ 'view.console', 'view.datanav',
'',
'view.maintool', 'view.viewtool',
'view.addtool', 'view.edittool'
@@ -521,6 +566,10 @@
helpmenu = [
'help.home', 'help.project', 'help.bug',
'',
+ 'help.tutorial',
+ '',
+ ['help.examples', '&Example documents', []],
+ '',
'help.about'
]
@@ -541,6 +590,28 @@
self.menus = {}
utils.constructMenus(self.menuBar(), self.menus, menus, self.vzactions)
+ self.populateExamplesMenu()
+
+ def _setPickerFont(self, label):
+ f = label.font()
+ f.setBold(True)
+ f.setPointSizeF(f.pointSizeF() * 1.2)
+ label.setFont(f)
+
+ def populateExamplesMenu(self):
+ """Add examples to help menu."""
+
+ examples = glob.glob(os.path.join(utils.exampleDirectory, '*.vsz'))
+ menu = self.menus["help.examples"]
+ for ex in sorted(examples):
+ name = os.path.splitext(os.path.basename(ex))[0]
+
+ def _openexample(ex=ex):
+ MainWindow.CreateWindow(ex)
+
+ a = menu.addAction(name, _openexample)
+ a.setStatusTip("Open %s example document" % name)
+
def defineViewWindowMenu(self):
"""Setup View -> Window menu."""
@@ -557,6 +628,7 @@
(self.propdock, 'view.props'),
(self.formatdock, 'view.format'),
(self.console, 'view.console'),
+ (self.datadock, 'view.datanav'),
(self.maintoolbar, 'view.maintool'),
(self.datatoolbar, 'view.datatool'),
(self.treeedit.edittoolbar, 'view.edittool'),
@@ -584,6 +656,7 @@
self.connect(dialog, qt4.SIGNAL('dialogFinished'), self.deleteDialog)
self.dialogs.append(dialog)
dialog.show()
+ self.emit( qt4.SIGNAL('dialogShown'), dialog )
def deleteDialog(self, dialog):
"""Remove dialog from list of dialogs."""
@@ -599,10 +672,15 @@
self.showDialog(dialog)
return dialog
- def slotDataEdit(self):
- """Edit existing datasets."""
+ def slotDataEdit(self, editdataset=None):
+ """Edit existing datasets.
+
+ If editdataset is set to a dataset name, edit this dataset
+ """
dialog = dataeditdialog.DataEditDialog(self, self.document)
self.showDialog(dialog)
+ if editdataset is not None:
+ dialog.selectDataset(editdataset)
return dialog
def slotDataCreate(self):
@@ -648,6 +726,32 @@
qt4.QDesktopServices.openUrl(
qt4.QUrl('https://gna.org/bugs/?group=veusz') )
+ def askTutorial(self):
+ """Ask if tutorial wanted."""
+ retn = qt4.QMessageBox.question(
+ self, "Veusz Tutorial",
+ "Veusz includes a tutorial to help get you started.\n"
+ "Would you like to start the tutorial now?\n"
+ "If not, you can access it later through the Help menu.",
+ qt4.QMessageBox.Yes | qt4.QMessageBox.No
+ )
+
+ if retn == qt4.QMessageBox.Yes:
+ self.slotHelpTutorial()
+
+ def slotHelpTutorial(self):
+ """Show a Veusz tutorial."""
+ if self.document.isBlank():
+ # run the tutorial
+ from veusz.windows.tutorial import TutorialDock
+ tutdock = TutorialDock(self.document, self, self)
+ self.addDockWidget(qt4.Qt.RightDockWidgetArea, tutdock)
+ tutdock.show()
+ else:
+ # open up a blank window for tutorial
+ win = self.CreateWindow()
+ win.slotHelpTutorial()
+
def slotHelpAbout(self):
"""Show about dialog."""
AboutDialog(self).exec_()
@@ -688,8 +792,8 @@
self.slotFileSave()
# store working directory
- setdb['dirname'] = unicode(self.dirname)
- setdb['dirname_export'] = unicode(self.dirname_export)
+ setdb['dirname'] = self.dirname
+ setdb['dirname_export'] = self.dirname_export
# store the current geometry in the settings database
geometry = ( self.x(), self.y(), self.width(), self.height() )
@@ -699,6 +803,9 @@
data = str(self.saveState())
setdb['geometry_mainwindowstate'] = data
+ # save current setting db
+ setdb.writeSettings()
+
event.accept()
def setupWindowGeometry(self):
@@ -756,6 +863,11 @@
self.setWindowTitle( "%s - Veusz" %
os.path.basename(self.filename) )
+ def plotQueueChanged(self, incr):
+ self.plotqueuecount += incr
+ text = u'•' * self.plotqueuecount
+ self.plotqueuelabel.setText(text)
+
def _fileSaveDialog(self, filetype, filedescr, dialogtitle):
"""A generic file save dialog for exporting / saving."""
@@ -768,7 +880,7 @@
# okay was selected (and is okay to overwrite if it exists)
if fd.exec_() == qt4.QDialog.Accepted:
# save directory for next time
- self.dirname = fd.directory()
+ self.dirname = fd.directory().absolutePath()
# update the edit box
filename = unicode( fd.selectedFiles()[0] )
if os.path.splitext(filename)[1] == '':
@@ -789,7 +901,7 @@
# if the user chooses a file
if fd.exec_() == qt4.QDialog.Accepted:
# save directory for next time
- self.dirname = fd.directory()
+ self.dirname = fd.directory().absolutePath()
filename = unicode( fd.selectedFiles()[0] )
try:
@@ -966,6 +1078,8 @@
self.dirname = os.path.dirname( os.path.abspath(filename) )
self.dirname_export = self.dirname
+ # notify cmpts which need notification that doc has finished opening
+ self.emit(qt4.SIGNAL("documentopened"))
qt4.QApplication.restoreOverrideCursor()
def addRecentFile(self, filename):
@@ -1030,41 +1144,37 @@
# File types we can export to in the form ([extensions], Name)
fd = qt4.QFileDialog(self, 'Export page')
fd.setDirectory( self.dirname_export )
-
+
fd.setFileMode( qt4.QFileDialog.AnyFile )
fd.setAcceptMode( qt4.QFileDialog.AcceptSave )
# Create a mapping between a format string and extensions
filtertoext = {}
+ # convert extensions to filter
+ exttofilter = {}
filters = []
# a list of extensions which are allowed
validextns = []
- formats = self.document.getExportFormats()
+ formats = document.Export.formats
for extns, name in formats:
extensions = " ".join(["*." + item for item in extns])
# join eveything together to make a filter string
filterstr = '%s (%s)' % (name, extensions)
filtertoext[filterstr] = extns
+ for e in extns:
+ exttofilter[e] = filterstr
filters.append(filterstr)
validextns += extns
-
- try:
- # Qt >= 4.4 (reqd for Fedora 12 Qt 4.6)
- fd.setNameFilters(filters)
- except AttributeError:
- fd.setFilters(filters)
+ fd.setNameFilters(filters)
# restore last format if possible
try:
filt = setdb['export_lastformat']
- try:
- # Qt >= 4.4 (reqd for Fedora 12 Qt 4.6)
- fd.selectNameFilter(filt)
- except AttributeError:
- fd.selectFilter(filt)
+ fd.selectNameFilter(filt)
extn = formats[filters.index(filt)][0][0]
except (KeyError, IndexError, ValueError):
- extn = 'eps'
+ extn = 'pdf'
+ fd.selectNameFilter( exttofilter[extn] )
if self.filename:
# try to convert current filename to export name
@@ -1074,7 +1184,7 @@
if fd.exec_() == qt4.QDialog.Accepted:
# save directory for next time
- self.dirname_export = unicode(fd.directory().absolutePath())
+ self.dirname_export = fd.directory().absolutePath()
filterused = str(fd.selectedFilter())
setdb['export_lastformat'] = filterused
@@ -1090,19 +1200,23 @@
# this is the extension without the dot
ext = os.path.splitext(filename)[1][1:]
if (ext not in validextns) and (ext not in chosenextns):
- filename = filename + "." + chosenextns[0]
+ filename += "." + chosenextns[0]
+ e = document.Export( self.document,
+ filename,
+ self.plot.getPageNumber(),
+ bitmapdpi=setdb['export_DPI'],
+ pdfdpi=setdb['export_DPI_PDF'],
+ antialias=setdb['export_antialias'],
+ color=setdb['export_color'],
+ quality=setdb['export_quality'],
+ backcolor=setdb['export_background'] )
try:
- self.document.export(filename, self.plot.getPageNumber(),
- dpi=setdb['export_DPI'],
- antialias=setdb['export_antialias'],
- color=setdb['export_color'],
- quality=setdb['export_quality'],
- backcolor=setdb['export_background'])
+ e.export()
except (IOError, RuntimeError), inst:
qt4.QMessageBox.critical(self, "Veusz",
"Error exporting file:\n%s" % inst)
-
+
# restore the cursor
qt4.QApplication.restoreOverrideCursor()
@@ -1182,6 +1296,40 @@
else:
self.axisvalueslabel.setText('No position')
+ def slotPickerEnabled(self, enabled):
+ if enabled:
+ self.pickerlabel.setText('No point selected')
+ self.pickerlabel.show()
+ else:
+ self.pickerlabel.hide()
+
+ def slotUpdatePickerLabel(self, info):
+ """Display the picked point"""
+ xv, yv = info.coords
+ xn, yn = info.labels
+ xt, yt = info.displaytype
+ ix = str(info.index)
+ if ix:
+ ix = '[' + ix + ']'
+
+ # format values for display
+ def fmt(val, dtype):
+ if dtype == 'date':
+ return utils.dateFloatToString(val)
+ elif dtype == 'numeric':
+ return '%0.5g' % val
+ elif dtype == 'text':
+ return val
+ else:
+ raise RuntimeError
+
+ xtext = fmt(xv, xt)
+ ytext = fmt(yv, yt)
+
+ t = '%s: %s%s = %s, %s%s = %s' % (
+ info.widget.name, xn, ix, xtext, yn, ix, ytext)
+ self.pickerlabel.setText(t)
+
def slotAllowedImportsDoc(self, module, names):
"""Are allowed imports?"""
diff -Nru veusz-1.10/windows/plotwindow.py veusz-1.14/windows/plotwindow.py
--- veusz-1.10/windows/plotwindow.py 2010-12-12 12:41:08.000000000 +0000
+++ veusz-1.14/windows/plotwindow.py 2011-11-22 20:23:31.000000000 +0000
@@ -19,10 +19,9 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: plotwindow.py 1274 2010-06-10 19:00:47Z jeremysanders $
-
import sys
from itertools import izip
+import traceback
import veusz.qtall as qt4
import numpy as N
@@ -33,123 +32,176 @@
import veusz.utils as utils
import veusz.widgets as widgets
-class RecordingPainter(document.Painter):
- """A painter to remember where the positions of the
- painted widgets."""
-
- def __init__(self, device):
- """Start painting on device."""
- document.Painter.__init__(self)
- self.widgetpositions = []
- self.widgetpositionslookup = {}
- self.begin(device)
-
- def beginPaintingWidget(self, widget, bounds):
- """Record the widget and position."""
- self.widgetpositions.append( (widget, bounds) )
- self.widgetpositionslookup[widget] = bounds
-
-class PointPainter(document.Painter):
- """A simple painter variant which works out the last widget
- to overlap with the point specified."""
-
- def __init__(self, pixmap, x, y):
- """Watch the point x, y."""
- document.Painter.__init__(self)
- self.x = x
- self.y = y
- self.widget = None
- self.bounds = {}
-
- self.pixmap = pixmap
- self.begin(pixmap)
-
- def beginPaintingWidget(self, widget, bounds):
-
- if (isinstance(widget, widgets.Graph) and
- bounds[0] <= self.x and bounds[1] <= self.y and
- bounds[2] >= self.x and bounds[3] >= self.y):
- self.widget = widget
-
- # record bounds of each widget
- self.bounds[widget] = bounds
-
-class ClickPainter(document.Painter):
- """A variant of a painter which checks to see whether a certain
- sized area is drawn over each time a widget is drawn. This allows
- the program to identify clicks with a widget.
+class PickerCrosshairItem( qt4.QGraphicsPathItem ):
+ """The picker cross widget: it moves from point to point and curve to curve
+ with the arrow keys, and hides itself when it looses focus"""
+ def __init__(self, parent=None):
+ path = qt4.QPainterPath()
+ path.addRect(-4, -4, 8, 8)
+ path.addRect(-5, -5, 10, 10)
+ path.moveTo(-8, 0)
+ path.lineTo(8, 0)
+ path.moveTo(0, -8)
+ path.lineTo(0, 8)
+
+ qt4.QGraphicsPathItem.__init__(self, path, parent)
+ self.setBrush(qt4.QBrush(qt4.Qt.black))
+ self.setFlags(self.flags() | qt4.QGraphicsItem.ItemIsFocusable)
+
+ def paint(self, painter, option, widget):
+ """Override this to enforce the global antialiasing setting"""
+ aa = setting.settingdb['plot_antialias']
+ painter.save()
+ painter.setRenderHint(qt4.QPainter.Antialiasing, aa)
+ qt4.QGraphicsPathItem.paint(self, painter, option, widget)
+ painter.restore()
+
+ def focusOutEvent(self, event):
+ qt4.QGraphicsPathItem.focusOutEvent(self, event)
+ self.hide()
+
+class RenderControl(qt4.QObject):
+ """Object for rendering plots in a separate thread."""
+
+ def __init__(self, plotwindow):
+ """Start up numthreads rendering threads."""
+ qt4.QObject.__init__(self)
+ self.sem = qt4.QSemaphore()
+ self.mutex = qt4.QMutex()
+ self.threads = []
+ self.exit = False
+ self.latestjobs = []
+ self.latestaddedjob = -1
+ self.latestdrawnjob = -1
+ self.plotwindow = plotwindow
+
+ self.updateNumberThreads()
+
+ def updateNumberThreads(self, num=None):
+ """Changes the number of rendering threads."""
+ if num is None:
+ if qt4.QFontDatabase.supportsThreadedFontRendering():
+ # use number of threads in preference
+ num = setting.settingdb['plot_numthreads']
+ else:
+ # disable threads
+ num = 0
- The painter monitors a certain sized region in the output pixmap
- """
+ if self.threads:
+ # delete old ones
+ self.exit = True
+ self.sem.release(len(self.threads))
+ for t in self.threads:
+ t.wait()
+ del self.threads[:]
+ self.exit = False
+
+ # start new ones
+ for i in xrange(num):
+ t = RenderThread(self)
+ t.start()
+ self.threads.append(t)
+
+ def exitThreads(self):
+ """Exit threads started."""
+ self.updateNumberThreads(num=0)
- def __init__(self, pixmap, xmin, ymin, xw, yw):
- """Monitor the region from (xmin, ymin) to (xmin+xw, ymin+yw).
+ def processNextJob(self):
+ """Take a job from the queue and process it.
- pixmap is the region the painter monitors
+ emits renderfinished(jobid, img, painthelper)
+ when done, if job has not been superseded
"""
-
- document.Painter.__init__(self)
- self.pixmap = pixmap
- self.xmin = xmin
- self.ymin = ymin
- self.xw = xw
- self.yw = yw
-
- # a stack keeping track of the widgets being painted currently
- self.widgets = []
- # a stack of starting state pixmaps of the widgets
- self.pixmaps = []
- # a list of widgets which change the region
- self.foundwidgets = []
-
- # we hope this color isn't actually used by the user
- # if a pixel changes from this color, a widget has drawn something
- self.specialcolor = qt4.QColor(254, 255, 254)
- self.pixmap.fill(self.specialcolor)
- self.begin(self.pixmap)
-
- def beginPaintingWidget(self, widget, bounds):
- self.widgets.append(widget)
-
- # make a small pixmap of the starting state of the image
- # we can compare this after the widget is painted
- pixmap = self.pixmap.copy(self.xmin, self.ymin, self.xw, self.yw)
- self.pixmaps.append(pixmap)
-
- def endPaintingWidget(self):
- """When a widget has finished."""
-
- oldpixmap = self.pixmaps.pop()
- widget = self.widgets.pop()
-
- # compare current pixmap for region with initial contents
- # hope this is not needed
- #self.flush()
- newpixmap = self.pixmap.copy(self.xmin, self.ymin, self.xw, self.yw)
-
- if oldpixmap.toImage() != newpixmap.toImage():
- # drawn here, so make a note
- self.foundwidgets.append(widget)
-
- # copy back original
- self.drawPixmap(qt4.QRect(self.xmin, self.ymin, self.xw, self.yw),
- oldpixmap,
- qt4.QRect(0, 0, self.xw, self.yw))
+ self.mutex.lock()
+ jobid, helper = self.latestjobs[-1]
+ del self.latestjobs[-1]
+ lastadded = self.latestaddedjob
+ self.mutex.unlock()
+
+ # don't process jobs which have been superseded
+ if lastadded == jobid:
+ img = qt4.QImage(helper.pagesize[0], helper.pagesize[1],
+ qt4.QImage.Format_ARGB32_Premultiplied)
+ img.fill( setting.settingdb.color('page').rgb() )
+
+ painter = qt4.QPainter(img)
+ aa = self.plotwindow.antialias
+ painter.setRenderHint(qt4.QPainter.Antialiasing, aa)
+ painter.setRenderHint(qt4.QPainter.TextAntialiasing, aa)
+ helper.renderToPainter(painter)
+ painter.end()
+
+ self.mutex.lock()
+ # just throw away result if it older than the latest one
+ if jobid > self.latestdrawnjob:
+ self.emit( qt4.SIGNAL("renderfinished"),
+ jobid, img, helper )
+ self.latestdrawnjob = jobid
+ self.mutex.unlock()
+
+ # tell any listeners that a job has been processed
+ self.plotwindow.emit( qt4.SIGNAL("queuechange"), -1 )
+
+ def addJob(self, helper):
+ """Process drawing job in PaintHelper given."""
+
+ # indicate that there is a new item to be processed to listeners
+ self.plotwindow.emit( qt4.SIGNAL("queuechange"), 1 )
+
+ # add the job to the queue
+ self.mutex.lock()
+ self.latestaddedjob += 1
+ self.latestjobs.append( (self.latestaddedjob, helper) )
+ self.mutex.unlock()
+
+ if self.threads:
+ # tell a thread to process job
+ self.sem.release(1)
+ else:
+ # process job in current thread if multithreading disabled
+ self.processNextJob()
+
+class RenderThread( qt4.QThread ):
+ """A thread for processing rendering jobs.
+ This is controlled by a RenderControl object
+ """
- def getFoundWidget(self):
- """Return the widget lowest in the tree near the click of the mouse.
+ def __init__(self, rendercontrol):
+ qt4.QThread.__init__(self)
+ self.rc = rendercontrol
+
+ def run(self):
+ """Repeat forever until told to exit.
+ If it aquires 1 resource from the semaphore it will process
+ the next job.
"""
-
- if self.foundwidgets:
- return self.foundwidgets[-1]
- else:
- return None
+ while True:
+ # wait until we can aquire the resources
+ self.rc.sem.acquire(1)
+ if self.rc.exit:
+ break
+ try:
+ self.rc.processNextJob()
+ except Exception:
+ sys.stderr.write("Error in rendering thread\n")
+ traceback.print_exc(file=sys.stderr)
class PlotWindow( qt4.QGraphicsView ):
"""Class to show the plot(s) in a scrollable window."""
- intervals = [0, 100, 250, 500, 1000, 2000, 5000, 10000]
+ # how often the document can update
+ updateintervals = (
+ (0, 'Disable'),
+ (-1, 'On document change'),
+ (100, 'Every 0.1s'),
+ (250, 'Every 0.25s'),
+ (500, 'Every 0.5s'),
+ (1000, 'Every 1s'),
+ (2000, 'Every 2s'),
+ (5000, 'Every 5s'),
+ (10000, 'Every 10s'),
+ )
def __init__(self, document, parent, menu=None):
"""Initialise the window.
@@ -163,47 +215,65 @@
self.setScene(self.scene)
# this graphics scene item is the actual graph
- self.pixmapitem = self.scene.addPixmap( qt4.QPixmap(1, 1) )
- self.controlgraphs = []
- self.widgetcontrolgraphs = {}
- self.selwidget = None
+ pixmap = qt4.QPixmap(1, 1)
+ self.dpi = (pixmap.logicalDpiX(), pixmap.logicalDpiY())
+ self.pixmapitem = self.scene.addPixmap(pixmap)
+
+ # set to be parent's actions
self.vzactions = None
+ # for controlling plot elements
+ g = self.controlgraphgroup = qt4.QGraphicsItemGroup()
+ g.setHandlesChildEvents(False)
+ self.scene.addItem(g)
+
# zoom rectangle for zooming into graph (not shown normally)
self.zoomrect = self.scene.addRect( 0, 0, 100, 100,
qt4.QPen(qt4.Qt.DotLine) )
self.zoomrect.setZValue(2.)
self.zoomrect.hide()
+ # picker graphicsitem for marking the picked point
+ self.pickeritem = PickerCrosshairItem()
+ self.scene.addItem(self.pickeritem)
+ self.pickeritem.setZValue(2.)
+ self.pickeritem.hide()
+
+ # all the widgets that picker key-navigation might cycle through
+ self.pickerwidgets = []
+
+ # the picker state
+ self.pickerinfo = widgets.PickInfo()
+
# set up so if document is modified we are notified
self.document = document
self.docchangeset = -100
+ self.oldpagenumber = -1
+ self.connect(self.document, qt4.SIGNAL("sigModified"),
+ self.slotDocModified)
+
+ # state of last plot from painthelper
+ self.painthelper = None
- self.size = (1, 1)
+ self.lastwidgetsselected = []
self.oldzoom = -1.
self.zoomfactor = 1.
self.pagenumber = 0
- self.forceupdate = False
self.ignoreclick = False
- # work out dpi
- self.widgetdpi = self.logicalDpiY()
-
- # convert size to pixels
- self.setOutputSize()
+ # for rendering plots in separate threads
+ self.rendercontrol = RenderControl(self)
+ self.connect(self.rendercontrol, qt4.SIGNAL("renderfinished"),
+ self.slotRenderFinished)
# mode for clicking
self.clickmode = 'select'
self.currentclickmode = None
- # list of widgets and positions last painted
- self.widgetpositions = []
- self.widgetpositionslookup = {}
-
# set up redrawing timer
self.timer = qt4.QTimer(self)
self.connect( self.timer, qt4.SIGNAL('timeout()'),
- self.slotTimeout )
+ self.checkPlotUpdate )
# for drag scrolling
self.grabpos = None
@@ -214,15 +284,19 @@
self.connect( self.scrolltimer, qt4.SIGNAL('timeout()'),
self.slotBecomeScrollClick )
- # get update period from setting database
- self.interval = setting.settingdb['plot_updateinterval']
-
- # load antialias settings
- self.antialias = setting.settingdb['plot_antialias']
+ # get plot view updating policy
+ # -1: update on document changes
+ # 0: never update automatically
+ # >0: check for updates every x ms
+ self.interval = setting.settingdb['plot_updatepolicy']
+ # if using a time-based document update checking, start timer
if self.interval > 0:
self.timer.start(self.interval)
+ # load antialias settings
+ self.antialias = setting.settingdb['plot_antialias']
+
# allow window to get focus, to allow context menu
self.setFocusPolicy(qt4.Qt.StrongFocus)
@@ -235,6 +309,19 @@
# make the context menu object
self._constructContextMenu()
+ def hideEvent(self, event):
+ """Window closing, so exit rendering threads."""
+ self.rendercontrol.exitThreads()
+ qt4.QGraphicsView.hideEvent(self, event)
+
+ def sizeHint(self):
+ """Return size hint for window."""
+ p = self.pixmapitem.pixmap()
+ if p.width() <= 1 and p.height() <= 1:
+ # if the document has been uninitialized, get the doc size
+ return qt4.QSize(*self.document.docSize())
+ return p.size()
+
def showToolbar(self, show=True):
"""Show or hide toolbar"""
self.viewtoolbar.setVisible(show)
@@ -299,6 +386,11 @@
'Select items or scroll',
None,
icon='kde-mouse-pointer'),
+ 'view.pick':
+ a(self, 'Read data points on the graph',
+ 'Read data points',
+ None,
+ icon='veusz-pick-data'),
'view.zoomgraph':
a(self, 'Zoom into graph', 'Zoom graph',
None,
@@ -314,7 +406,7 @@
'view.zoomheight', 'view.zoompage',
'',
'view.prevpage', 'view.nextpage',
- 'view.select', 'view.zoomgraph',
+ 'view.select', 'view.pick', 'view.zoomgraph',
]),
]
utils.constructMenus(menu, {'view': menu}, menuitems,
@@ -339,13 +431,13 @@
# add items to toolbar
utils.addToolbarActions(self.viewtoolbar, actions,
('view.prevpage', 'view.nextpage',
- 'view.select', 'view.zoomgraph',
- 'view.zoommenu'))
+ 'view.select', 'view.pick',
+ 'view.zoomgraph', 'view.zoommenu'))
# define action group for various different selection models
grp = self.selectactiongrp = qt4.QActionGroup(self)
grp.setExclusive(True)
- for a in ('view.select', 'view.zoomgraph'):
+ for a in ('view.select', 'view.pick', 'view.zoomgraph'):
actions[a].setActionGroup(grp)
actions[a].setCheckable(True)
actions['view.select'].setChecked(True)
@@ -396,17 +488,8 @@
return
# try to work out in which widget the first point is in
- bufferpixmap = qt4.QPixmap( *self.size )
- painter = PointPainter(bufferpixmap, pt1.x(), pt1.y())
- pagenumber = min( self.document.getNumberPages() - 1,
- self.pagenumber )
- if pagenumber >= 0:
- self.document.paintTo(painter, self.pagenumber,
- scaling=self.zoomfactor, dpi=self.widgetdpi)
- painter.end()
-
- # get widget
- widget = painter.widget
+ widget = self.painthelper.pointInWidgetBounds(
+ pt1.x(), pt1.y(), widgets.Graph)
if widget is None:
return
@@ -440,7 +523,11 @@
# convert points on plotter to axis coordinates
# FIXME: Need To Trap Conversion Errors!
- r = axis.plotterToGraphCoords(painter.bounds[axis], p)
+ try:
+ r = axis.plotterToGraphCoords(
+ self.painthelper.widgetBounds(axis), p)
+ except KeyError:
+ continue
# invert if min and max are inverted
if r[1] < r[0]:
@@ -457,7 +544,69 @@
# finally change the axes
self.document.applyOperation(
document.OperationMultiple(operations,descr='zoom axes') )
-
+
+ def axesForPoint(self, mousepos):
+ """Find all the axes which contain the given mouse position"""
+
+ if self.painthelper is None:
+ return []
+
+ pos = self.mapToScene(mousepos)
+ px, py = pos.x(), pos.y()
+
+ axes = []
+ for widget, bounds in self.painthelper.widgetBoundsIterator(
+ widgettype=widgets.Axis):
+ # if widget is axis, and point lies within bounds
+ if ( px>=bounds[0] and px<=bounds[2] and
+ py>=bounds[1] and py<=bounds[3] ):
+
+ # convert correct pointer position
+ if widget.settings.direction == 'horizontal':
+ val = px
+ else:
+ val = py
+ coords=widget.plotterToGraphCoords(bounds, N.array([val]))
+ axes.append( (widget, coords[0]) )
+
+ return axes
+
+ def emitPicked(self, pickinfo):
+ """Report that a new point has been picked"""
+
+ self.pickerinfo = pickinfo
+ self.pickeritem.setPos(pickinfo.screenpos[0], pickinfo.screenpos[1])
+ self.emit(qt4.SIGNAL("sigPointPicked"), pickinfo)
+
+ def doPick(self, mousepos):
+ """Find the point on any plot-like widget closest to the cursor"""
+
+ self.pickerwidgets = []
+
+ pickinfo = widgets.PickInfo()
+ pos = self.mapToScene(mousepos)
+
+ for w, bounds in self.painthelper.widgetBoundsIterator():
+ try:
+ # ask the widget for its (visually) closest point to the cursor
+ info = w.pickPoint(pos.x(), pos.y(), bounds)
+
+ # this is a pickable widget, so remember it for future key navigation
+ self.pickerwidgets.append(w)
+
+ if info.distance < pickinfo.distance:
+ # and remember the overall closest
+ pickinfo = info
+ except AttributeError:
+ # ignore widgets that don't support axes or picking
+ continue
+
+ if not pickinfo:
+ self.pickeritem.hide()
+ return
+
+ self.emitPicked(pickinfo)
+
def slotBecomeScrollClick(self):
"""If the click is still down when this timer is reached then
we turn the click into a scrolling click."""
@@ -472,7 +621,12 @@
qt4.QGraphicsView.mousePressEvent(self, event)
# work out whether user is clicking on a control point
- self.ignoreclick = self.itemAt(event.pos()) is not self.pixmapitem
+ # we have to ignore the item group which seems to be above
+ # its constituents
+ items = self.items(event.pos())
+ if len(items) > 0 and isinstance(items[0], qt4.QGraphicsItemGroup):
+ del items[0]
+ self.ignoreclick = len(items)==0 or items[0] is not self.pixmapitem
if event.button() == qt4.Qt.LeftButton and not self.ignoreclick:
@@ -487,6 +641,11 @@
# select widgets!
self.scrolltimer.start(400)
+ elif self.clickmode == 'pick':
+ self.pickeritem.show()
+ self.pickeritem.setFocus(qt4.Qt.MouseFocusReason)
+ self.doPick(event.pos())
+
elif self.clickmode == 'scroll':
qt4.QApplication.setOverrideCursor(
qt4.QCursor(qt4.Qt.SizeAllCursor))
@@ -528,33 +687,23 @@
self.zoomrect.setRect( r.x(), r.y(), pos.x()-r.x(),
pos.y()-r.y() )
- elif self.clickmode == 'select':
+ elif self.clickmode == 'select' or self.clickmode == 'pick':
# find axes which map to this position
- pos = self.mapToScene(event.pos())
- px, py = pos.x(), pos.y()
-
- vals = {}
- for widget, bounds in self.widgetpositions:
- # if widget is axis, and point lies within bounds
- if ( isinstance(widget, widgets.Axis) and
- px>=bounds[0] and px<=bounds[2] and
- py>=bounds[1] and py<=bounds[3] ):
-
- # convert correct pointer position
- if widget.settings.direction == 'horizontal':
- val = px
- else:
- val = py
- coords=widget.plotterToGraphCoords(bounds, N.array([val]))
- vals[widget.name] = coords[0]
+ axes = self.axesForPoint(event.pos())
+ vals = dict([ (a[0].name, a[1]) for a in axes ])
self.emit( qt4.SIGNAL('sigAxisValuesFromMouse'), vals )
+ if self.currentclickmode == 'pick':
+ # drag the picker around
+ self.doPick(event.pos())
+
def mouseReleaseEvent(self, event):
"""If the mouse button is released, check whether the mouse
clicked on a widget, and emit a sigWidgetClicked(widget)."""
qt4.QGraphicsView.mouseReleaseEvent(self, event)
+
if event.button() == qt4.Qt.LeftButton and not self.ignoreclick:
event.accept()
self.scrolltimer.stop()
@@ -573,6 +722,62 @@
self.grabpos = None
elif self.currentclickmode == 'viewgetclick':
self.clickmode = 'select'
+ elif self.currentclickmode == 'pick':
+ self.currentclickmode = None
+
+ def keyPressEvent(self, event):
+ """Keypad motion moves the picker if it has focus"""
+ if self.pickeritem.hasFocus():
+ event.accept()
+ k = event.key()
+ if k == qt4.Qt.Key_Left or k == qt4.Qt.Key_Right:
+ # navigate to the previous or next point on the curve
+ dir = 'right' if k == qt4.Qt.Key_Right else 'left'
+ ix = self.pickerinfo.index
+ pickinfo = self.pickerinfo.widget.pickIndex(
+ ix, dir, self.painthelper.widgetBounds(
+ self.pickerinfo.widget))
+ if not pickinfo:
+ # no more points visible in this direction
+ return
+
+ self.emitPicked(pickinfo)
+ elif k == qt4.Qt.Key_Up or k == qt4.Qt.Key_Down:
+ # navigate to the next plot up or down on the screen
+ p = self.pickeritem.pos()
+
+ oldw = self.pickerinfo.widget
+ pickinfo = widgets.PickInfo()
+
+ dist = float('inf')
+ for w in self.pickerwidgets:
+ if w == oldw:
+ continue
+
+ # ask the widgets to pick their point which is closest horizontally
+ # to the last (screen) x value picked
+ pi = w.pickPoint(self.pickerinfo.screenpos[0], p.y(),
+ self.painthelper.widgetBounds(w),
+ distance='horizontal')
+ if not pi:
+ continue
+
+ dy = p.y() - pi.screenpos[1]
+
+ # take the new point which is closest vertically to the current
+ # one and either above or below it as appropriate
+ if abs(dy) < dist and ( (k == qt4.Qt.Key_Up and dy > 0)
+ or (k == qt4.Qt.Key_Down and dy < 0) ):
+ pickinfo = pi
+ dist = abs(dy)
+
+ if pickinfo:
+ oldx = self.pickerinfo.screenpos[0]
+ self.emitPicked(pickinfo)
+
+ # restore the previous x-position, so that vertical navigation
+ # stays repeatable
+ pickinfo.screenpos = (oldx, pickinfo.screenpos[1])
def locateClickWidget(self, x, y):
"""Work out which widget was clicked, and if necessary send
@@ -581,41 +786,15 @@
if self.document.getNumberPages() == 0:
return
- # now crazily draw the whole thing again
- # see which widgets change the region in the small box given below
- bufferpixmap = qt4.QPixmap( *self.size )
- painter = ClickPainter(bufferpixmap, x-3, y-3, 7, 7)
-
- pagenumber = min( self.document.getNumberPages() - 1,
- self.pagenumber )
- self.document.paintTo(painter, self.pagenumber,
- scaling=self.zoomfactor, dpi=self.widgetdpi)
- painter.end()
-
- widget = painter.getFoundWidget()
- if not widget:
- widget = self.document.getPage(self.pagenumber)
+ widget = self.painthelper.identifyWidgetAtPoint(
+ x, y, antialias=self.antialias)
+ if widget is None:
+ # select page if nothing clicked
+ widget = self.document.basewidget.getPage(self.pagenumber)
# tell connected objects that widget was clicked
- self.emit( qt4.SIGNAL('sigWidgetClicked'), widget )
-
- def setOutputSize(self):
- """Set the ouput display size."""
-
- # convert distances into pixels
- pix = qt4.QPixmap(1, 1)
- painter = document.Painter(pix,
- scaling = self.zoomfactor,
- dpi = self.widgetdpi)
- size = self.document.basewidget.getSize(painter)
- painter.end()
-
- # make new buffer and resize widget
- if size != self.size:
- self.size = size
- self.bufferpixmap = qt4.QPixmap( *self.size )
- self.forceupdate = True
- self.setSceneRect( 0, 0, size[0], size[1] )
+ if widget is not None:
+ self.emit( qt4.SIGNAL('sigWidgetClicked'), widget )
def setPageNumber(self, pageno):
"""Move the the selected page."""
@@ -630,72 +809,79 @@
pageno = max(0, pageno)
self.pagenumber = pageno
- self.forceupdate = True
+ if self.pagenumber != self.oldpagenumber and self.interval != 0:
+ self.checkPlotUpdate()
def getPageNumber(self):
"""Get the the selected page."""
return self.pagenumber
- def slotTimeout(self):
- """Called after timer times out, to check for updates to window."""
+ def slotDocModified(self, ismodified):
+ """Update plot on document being modified."""
+ # only update if doc is modified and the update policy is set
+ # to update on document updates
+ if ismodified and self.interval == -1:
+ self.checkPlotUpdate()
+
+ def checkPlotUpdate(self):
+ """Check whether plot needs updating."""
+ # print >>sys.stderr, "checking update"
# no threads, so can't get interrupted here
# draw data into background pixmap if modified
if ( self.zoomfactor != self.oldzoom or
self.document.changeset != self.docchangeset or
- self.forceupdate ):
+ self.pagenumber != self.oldpagenumber ):
- self.setOutputSize()
+ # print >>sys.stderr, "updating"
+ self.pickeritem.hide()
- # fill pixmap with proper background colour
- self.bufferpixmap.fill( setting.settingdb.color('page') )
-
self.pagenumber = min( self.document.getNumberPages() - 1,
self.pagenumber )
+ self.oldpagenumber = self.pagenumber
+
if self.pagenumber >= 0:
+ size = self.document.pageSize(
+ self.pagenumber, scaling=self.zoomfactor)
+
# draw the data into the buffer
# errors cause an exception window to pop up
try:
- painter = RecordingPainter(self.bufferpixmap)
- painter.setRenderHint(qt4.QPainter.Antialiasing,
- self.antialias)
- painter.setRenderHint(qt4.QPainter.TextAntialiasing,
- self.antialias)
- self.document.paintTo( painter, self.pagenumber,
- scaling = self.zoomfactor,
- dpi = self.widgetdpi )
- painter.end()
- self.widgetpositions = painter.widgetpositions
- self.widgetpositionslookup = painter.widgetpositionslookup
-
- # collect all controlgraphs (in case these change later
- # from e.g. printing)
- self.widgetcontrolgraphs = dict(
- [ (w[0], w[0].controlgraphitems)
- for w in self.widgetpositions ])
-
- # update selected widget items
- self.selectedWidget(self.selwidget)
-
+ phelper = document.PaintHelper(
+ size, scaling=self.zoomfactor, dpi=self.dpi)
+ self.document.paintTo(phelper, self.pagenumber)
+
except Exception:
# stop updates this time round and show exception dialog
d = exceptiondialog.ExceptionDialog(sys.exc_info(), self)
self.oldzoom = self.zoomfactor
- self.forceupdate = False
self.docchangeset = self.document.changeset
d.exec_()
-
+
+ self.painthelper = phelper
+ self.rendercontrol.addJob(phelper)
else:
+ self.painthelper = None
self.pagenumber = 0
+ size = self.document.docSize()
+ pixmap = qt4.QPixmap(*size)
+ pixmap.fill( setting.settingdb.color('page') )
+ self.setSceneRect(0, 0, *size)
+ self.pixmapitem.setPixmap(pixmap)
self.emit( qt4.SIGNAL("sigUpdatePage"), self.pagenumber )
self.updatePageToolbar()
+ self.updateControlGraphs(self.lastwidgetsselected)
self.oldzoom = self.zoomfactor
- self.forceupdate = False
self.docchangeset = self.document.changeset
- self.pixmapitem.setPixmap(self.bufferpixmap)
+ def slotRenderFinished(self, jobid, img, helper):
+ """Update image on display if rendering (usually in other
+ thread) finished."""
+ bufferpixmap = qt4.QPixmap.fromImage(img)
+ self.setSceneRect(0, 0, bufferpixmap.width(), bufferpixmap.height())
+ self.pixmapitem.setPixmap(bufferpixmap)
def _constructContextMenu(self):
"""Construct the context menu."""
@@ -709,26 +895,18 @@
menu.addAction( self.vzactions['view.nextpage'] )
menu.addSeparator()
- # update NOW!
+ # force an update now menu item
menu.addAction('Force update', self.actionForceUpdate)
- # Update submenu
+ # Update policy submenu
submenu = menu.addMenu('Updates')
intgrp = qt4.QActionGroup(self)
- inttext = ['Disable']
- for intv in self.intervals[1:]:
- inttext.append('Every %gs' % (intv * 0.001))
-
- # need to keep copies of bound objects otherwise they are collected
- self._intfuncs = []
-
# bind interval options to actions
- for intv, text in izip(self.intervals, inttext):
+ for intv, text in self.updateintervals:
act = intgrp.addAction(text)
act.setCheckable(True)
fn = utils.BoundCaller(self.actionSetTimeout, intv)
- self._intfuncs.append(fn)
self.connect(act, qt4.SIGNAL('triggered(bool)'), fn)
if intv == self.interval:
act.setChecked(True)
@@ -742,8 +920,9 @@
def updatePlotSettings(self):
"""Update plot window settings from settings."""
- self.setTimeout(setting.settingdb['plot_updateinterval'])
+ self.setTimeout(setting.settingdb['plot_updatepolicy'])
self.antialias = setting.settingdb['plot_antialias']
+ self.rendercontrol.updateNumberThreads()
self.actionForceUpdate()
def contextMenuEvent(self, event):
@@ -753,36 +932,29 @@
def actionForceUpdate(self):
"""Force an update for the graph."""
self.docchangeset = -100
- self.slotTimeout()
+ self.checkPlotUpdate()
def setTimeout(self, interval):
"""Change timer setting without changing save value."""
- if interval == 0:
+ self.interval = interval
+ if interval <= 0:
+ # stop updates
if self.timer.isActive():
self.timer.stop()
else:
+ # change interval to one selected
self.timer.setInterval(interval)
+ # start timer if it was stopped
if not self.timer.isActive():
self.timer.start()
def actionSetTimeout(self, interval, checked):
"""Called by setting the interval."""
- if interval == 0:
- # stop updates
- self.interval = 0
- if self.timer.isActive():
- self.timer.stop()
- else:
- # change interval to one selected
- self.interval = interval
- self.timer.setInterval(interval)
- # start timer if it was stopped
- if not self.timer.isActive():
- self.timer.start()
+ self.setTimeout(interval)
# remember changes for next time
- setting.settingdb['plot_updateinterval'] = self.interval
+ setting.settingdb['plot_updatepolicy'] = self.interval
def actionAntialias(self):
"""Toggle antialias."""
@@ -793,7 +965,7 @@
def setZoomFactor(self, zoomfactor):
"""Set the zoom factor of the window."""
self.zoomfactor = float(zoomfactor)
- self.update()
+ self.checkPlotUpdate()
def slotViewZoomIn(self):
"""Zoom into the plot."""
@@ -809,14 +981,15 @@
# need to take account of scroll bars when deciding size
viewportsize = self.maximumViewportSize()
aspectwin = viewportsize.width()*1./viewportsize.height()
- aspectplot = self.size[0]*1./self.size[1]
+ r = self.pixmapitem.boundingRect()
+ aspectplot = r.width() / r.height()
width = viewportsize.width()
if aspectwin > aspectplot:
# take account of scroll bar
width -= self.verticalScrollBar().width()
- mult = width*1./self.size[0]
+ mult = width / r.width()
self.setZoomFactor(self.zoomfactor * mult)
def slotViewZoomHeight(self):
@@ -825,22 +998,24 @@
# need to take account of scroll bars when deciding size
viewportsize = self.maximumViewportSize()
aspectwin = viewportsize.width()*1./viewportsize.height()
- aspectplot = self.size[0]*1./self.size[1]
+ r = self.pixmapitem.boundingRect()
+ aspectplot = r.width() / r.height()
height = viewportsize.height()
if aspectwin < aspectplot:
# take account of scroll bar
height -= self.horizontalScrollBar().height()
- mult = height*1./self.size[1]
+ mult = height / r.height()
self.setZoomFactor(self.zoomfactor * mult)
def slotViewZoomPage(self):
"""Make the zoom factor correct to show the whole page."""
viewportsize = self.maximumViewportSize()
- multw = viewportsize.width()*1./self.size[0]
- multh = viewportsize.height()*1./self.size[1]
+ r = self.pixmapitem.boundingRect()
+ multw = viewportsize.width()*1./r.width()
+ multh = viewportsize.height()*1./r.height()
self.setZoomFactor(self.zoomfactor * min(multw, multh))
def slotViewZoom11(self):
@@ -868,17 +1043,25 @@
"""Called when the selection mode has changed."""
modecnvt = { self.vzactions['view.select'] : 'select',
+ self.vzactions['view.pick'] : 'pick',
self.vzactions['view.zoomgraph'] : 'graphzoom' }
+ # close the current picker
+ self.pickeritem.hide()
+ self.emit(qt4.SIGNAL('sigPickerEnabled'), False)
+
# convert action into clicking mode
self.clickmode = modecnvt[action]
if self.clickmode == 'select':
- pass
+ self.pixmapitem.unsetCursor()
#self.label.setCursor(qt4.Qt.ArrowCursor)
elif self.clickmode == 'graphzoom':
- pass
+ self.pixmapitem.unsetCursor()
#self.label.setCursor(qt4.Qt.CrossCursor)
+ elif self.clickmode == 'pick':
+ self.pixmapitem.setCursor(qt4.Qt.CrossCursor)
+ self.emit(qt4.SIGNAL('sigPickerEnabled'), True)
def getClick(self):
"""Return a click point from the graph."""
@@ -896,17 +1079,8 @@
pt = self.grabpos
# try to work out in which widget the first point is in
- bufferpixmap = qt4.QPixmap( *self.size )
- painter = PointPainter(bufferpixmap, pt.x(), pt.y())
- pagenumber = min( self.document.getNumberPages() - 1,
- self.pagenumber )
- if pagenumber >= 0:
- self.document.paintTo(painter, self.pagenumber,
- scaling=self.zoomfactor, dpi=self.widgetdpi)
- painter.end()
-
- # get widget
- widget = painter.widget
+ widget = self.painthelper.pointInWidgetBounds(
+ pt.x(), pt.y(), widgets.Graph)
if widget is None:
return []
@@ -933,25 +1107,33 @@
# convert point on plotter to axis coordinate
# FIXME: Need To Trap Conversion Errors!
- r = axis.plotterToGraphCoords(painter.bounds[axis], p)
+ r = axis.plotterToGraphCoords(
+ self.painthelper.widgetBounds(axis), p)
axesretn.append( (axis.path, r[0]) )
return axesretn
- def selectedWidget(self, widget):
- """Update control items on screen associated with widget."""
+ def selectedWidgets(self, widgets):
+ """Update control items on screen associated with widget.
+ Called when widgets have been selected in the tree edit window
+ """
+ self.updateControlGraphs(widgets)
+ self.lastwidgetsselected = widgets
+
+ def updateControlGraphs(self, widgets):
+ """Add control graphs for the widgets given."""
- self.selwidget = widget
+ cgg = self.controlgraphgroup
- # remove old items from scene
- for item in self.controlgraphs:
- self.scene.removeItem(item)
- del self.controlgraphs[:]
-
- # put in new items
- if widget is not None and widget in self.widgetcontrolgraphs:
- for control in self.widgetcontrolgraphs[widget]:
- graphitem = control.createGraphicsItem()
- self.controlgraphs.append(graphitem)
- self.scene.addItem(graphitem)
+ # delete old items
+ for c in cgg.childItems():
+ cgg.removeFromGroup(c)
+ self.scene.removeItem(c)
+
+ # add each item to the group
+ for widget in widgets:
+ if self.painthelper and widget in self.painthelper.states:
+ for control in self.painthelper.states[widget].cgis:
+ graphitem = control.createGraphicsItem()
+ cgg.addToGroup(graphitem)
diff -Nru veusz-1.10/windows/simplewindow.py veusz-1.14/windows/simplewindow.py
--- veusz-1.10/windows/simplewindow.py 2010-12-12 12:41:08.000000000 +0000
+++ veusz-1.14/windows/simplewindow.py 2011-11-22 20:23:31.000000000 +0000
@@ -16,8 +16,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
##############################################################################
-# $Id: simplewindow.py 1222 2010-05-09 21:35:19Z jeremysanders $
-
import veusz.qtall as qt4
import veusz.document as document
diff -Nru veusz-1.10/windows/treeeditwindow.py veusz-1.14/windows/treeeditwindow.py
--- veusz-1.10/windows/treeeditwindow.py 2010-12-12 12:41:08.000000000 +0000
+++ veusz-1.14/windows/treeeditwindow.py 2011-11-22 20:23:31.000000000 +0000
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
# Copyright (C) 2004 Jeremy S. Sanders
# Email: Jeremy Sanders
#
@@ -16,8 +17,6 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
###############################################################################
-# $Id: treeeditwindow.py 1469 2010-12-08 22:15:00Z jeremysanders $
-
"""Window to edit the document using a tree, widget properties
and formatting properties."""
@@ -30,26 +29,396 @@
from widgettree import WidgetTreeModel, WidgetTreeView
+class SettingsProxy(object):
+ """Object to handle communication between widget/settings
+ or sets of widgets/settings."""
+
+ def childProxyList(self):
+ """Return a list settings and setting variables proxified."""
+
+ def settingsProxyList(self):
+ """Return list of SettingsProxy objects for sub Settings."""
+
+ def settingList(self):
+ """Return list of Setting objects."""
+
+ def actionsList(self):
+ """Return list of Action objects."""
+
+ def onSettingChanged(self, control, setting, val):
+ """Called when a setting has been modified."""
+
+ def onAction(self, action, console):
+ """Called if action pressed. Console window is given."""
+
+ def name(self):
+ """Return name of Settings."""
+
+ def pixmap(self):
+ """Return pixmap for Settings."""
+
+ def usertext(self):
+ """Return text for user."""
+
+ def setnsmode(self):
+ """Return setnsmode of Settings."""
+
+ def multivalued(self, name):
+ """Is setting with name multivalued?"""
+ return False
+
+ def resetToDefault(self, name):
+ """Reset setting to default."""
+
+class SettingsProxySingle(SettingsProxy):
+ """A proxy wrapping settings for a single widget."""
+
+ def __init__(self, document, settings, actions=None):
+ """Initialise settings proxy.
+ settings is the widget settings, actions is its actions."""
+ self.document = document
+ self.settings = settings
+ self.actions = actions
+
+ def childProxyList(self):
+ """Return a list settings and setting variables proxified."""
+ retn = []
+ s = self.settings
+ for n in s.getNames():
+ o = s.get(n)
+ if isinstance(o, setting.Settings):
+ retn.append( SettingsProxySingle(self.document, o) )
+ else:
+ retn.append(o)
+ return retn
+
+ def settingsProxyList(self):
+ """Return list of SettingsProxy objects."""
+ return [ SettingsProxySingle(self.document, s)
+ for s in self.settings.getSettingsList() ]
+
+ def settingList(self):
+ """Return list of Setting objects."""
+ return self.settings.getSettingList()
+
+ def actionsList(self):
+ """Return list of actions."""
+ return self.actions
+
+ def onSettingChanged(self, control, setting, val):
+ """Change setting in document."""
+ if setting.val != val:
+ self.document.applyOperation(
+ document.OperationSettingSet(setting, val))
+
+ def onAction(self, action, console):
+ """Run action on console."""
+ console.runFunction(action.function)
+
+ def name(self):
+ """Return name."""
+ return self.settings.name
+
+ def pixmap(self):
+ """Return pixmap."""
+ return self.settings.pixmap
+
+ def usertext(self):
+ """Return text for user."""
+ return self.settings.usertext
+
+ def setnsmode(self):
+ """Return setnsmode of Settings."""
+ return self.settings.setnsmode
+
+ def resetToDefault(self, name):
+ """Reset setting to default."""
+ setn = self.settings.get(name)
+ self.document.applyOperation(
+ document.OperationSettingSet(setn, setn.default))
+
+class SettingsProxyMulti(SettingsProxy):
+ """A proxy wrapping settings for multiple widgets."""
+
+ def __init__(self, document, widgets, _root=''):
+ """Initialise settings proxy.
+ widgets is a list of widgets to proxy for."""
+ self.document = document
+ self.widgets = widgets
+ self._root = _root
+
+ self._settingsatlevel = self._getSettingsAtLevel()
+ self._cachesettings = self._cachesetting = self._cachechild = None
+
+ def _getSettingsAtLevel(self):
+ """Return settings of widgets at level given."""
+ if self._root:
+ levels = self._root.split('/')
+ else:
+ levels = []
+ setns = []
+ for w in self.widgets:
+ s = w.settings
+ for lev in levels:
+ s = s.get(lev)
+ setns.append(s)
+ return setns
+
+ def _objList(self, filterclasses):
+ """Return a list of objects with the type in filterclasses."""
+
+ setns = self._settingsatlevel
+
+ # get list of names with appropriate class
+ names = []
+ for n in setns[0].getNames():
+ o = setns[0].get(n)
+ for c in filterclasses:
+ if isinstance(o, c):
+ names.append(n)
+ break
+
+ sset = set(names)
+ for s in setns[1:]:
+ sset &= set(s.getNames())
+ names = [n for n in names if n in sset]
+
+ proxylist = []
+ for n in names:
+ o = setns[0].get(n)
+ if isinstance(o, setting.Settings):
+ # construct new proxy settings (adding on name of root)
+ newroot = n
+ if self._root:
+ newroot = self._root + '/' + newroot
+ v = SettingsProxyMulti(self.document, self.widgets,
+ _root=newroot)
+ else:
+ # use setting from first settings as template
+ v = o
+
+ proxylist.append(v)
+ return proxylist
+
+ def childProxyList(self):
+ """Make a list of proxy settings."""
+ if self._cachechild is None:
+ self._cachechild = self._objList( (setting.Settings,
+ setting.Setting) )
+ return self._cachechild
+
+ def settingsProxyList(self):
+ """Get list of settings proxy."""
+ if self._cachesettings is None:
+ self._cachesettings = self._objList( (setting.Settings,) )
+ return self._cachesettings
+
+ def settingList(self):
+ """Set list of common Setting objects for each widget."""
+ if self._cachesetting is None:
+ self._cachesetting = self._objList( (setting.Setting,) )
+ return self._cachesetting
+
+ def actionsList(self):
+ """Get list of common actions."""
+ anames = None
+ for widget in self.widgets:
+ a = set([a.name for a in widget.actions])
+ if anames is None:
+ anames = a
+ else:
+ anames &= a
+ actions = [a for a in self.widgets[0].actions if a.name in anames]
+ return actions
+
+ def onSettingChanged(self, control, setting, val):
+ """Change setting in document."""
+ # construct list of operations to change each setting
+ ops = []
+ sname = setting.name
+ if self._root:
+ sname = self._root + '/' + sname
+ for w in self.widgets:
+ s = self.document.resolveFullSettingPath(w.path + '/' + sname)
+ if s.val != val:
+ ops.append(document.OperationSettingSet(s, val))
+ # apply all operations
+ if ops:
+ self.document.applyOperation(
+ document.OperationMultiple(ops, descr='change settings'))
+
+ def onAction(self, action, console):
+ """Run actions with same name."""
+ aname = action.name
+ for w in self.widgets:
+ for a in w.actions:
+ if a.name == aname:
+ console.runFunction(a.function)
+
+ def name(self):
+ return self._settingsatlevel[0].name
+
+ def pixmap(self):
+ """Return pixmap."""
+ return self._settingsatlevel[0].pixmap
+
+ def usertext(self):
+ """Return text for user."""
+ return self._settingsatlevel[0].usertext
+
+ def setnsmode(self):
+ """Return setnsmode."""
+ return self._settingsatlevel[0].setnsmode
+
+ def multivalued(self, name):
+ """Is setting multivalued?"""
+ slist = [s.get(name) for s in self._settingsatlevel]
+ first = slist[0].get()
+ for s in slist[1:]:
+ if s.get() != first:
+ return True
+ return False
+
+ def resetToDefault(self, name):
+ """Reset settings to default."""
+ ops = []
+ for s in self._settingsatlevel:
+ setn = s.get(name)
+ ops.append(document.OperationSettingSet(setn, setn.default))
+ self.document.applyOperation(
+ document.OperationMultiple(ops, descr="reset to default"))
+
class PropertyList(qt4.QWidget):
"""Edit the widget properties using a set of controls."""
- def __init__(self, document, showsubsettings=True,
+ def __init__(self, document, showformatsettings=True,
*args):
qt4.QWidget.__init__(self, *args)
self.document = document
- self.showsubsettings = showsubsettings
+ self.showformatsettings = showformatsettings
self.layout = qt4.QGridLayout(self)
-
self.layout.setSpacing( self.layout.spacing()/2 )
self.layout.setMargin(4)
self.childlist = []
self.setncntrls = {} # map setting name to controls
- def updateProperties(self, settings, title=None, showformatting=True,
+ def getConsole(self):
+ """Find console window. This is horrible: HACK."""
+ win = self.parent()
+ while not hasattr(win, 'console'):
+ win = win.parent()
+ return win.console
+
+ def _addActions(self, setnsproxy, row):
+ """Add a list of actions."""
+ for action in setnsproxy.actionsList():
+ text = action.name
+ if action.usertext:
+ text = action.usertext
+
+ lab = qt4.QLabel(text)
+ self.layout.addWidget(lab, row, 0)
+ self.childlist.append(lab)
+
+ button = qt4.QPushButton(text)
+ button.setToolTip(action.descr)
+ # need to save reference to caller object
+ button.caller = utils.BoundCaller(setnsproxy.onAction, action,
+ self.getConsole())
+ self.connect(button, qt4.SIGNAL('clicked()'), button.caller)
+
+ self.layout.addWidget(button, row, 1)
+ self.childlist.append(button)
+
+ row += 1
+ return row
+
+ def _addControl(self, setnsproxy, setn, row):
+ """Add a control for a setting."""
+ cntrl = setn.makeControl(None)
+ if cntrl:
+ lab = SettingLabel(self.document, setn, setnsproxy)
+ self.layout.addWidget(lab, row, 0)
+ self.childlist.append(lab)
+
+ self.connect(cntrl, qt4.SIGNAL('settingChanged'),
+ setnsproxy.onSettingChanged)
+ self.layout.addWidget(cntrl, row, 1)
+ self.childlist.append(cntrl)
+ self.setncntrls[setn.name] = (lab, cntrl)
+
+ row += 1
+ return row
+
+ def _addGroupedSettingsControl(self, grpdsetting, row):
+ """Add a control for a set of grouped settings."""
+
+ slist = grpdsetting.settingList()
+
+ # make first widget with expandable button
+
+ # this is a label with a + button by this side
+ setnlab = SettingLabel(self.document, slist[0], grpdsetting)
+ expandbutton = qt4.QPushButton("+", checkable=True, flat=True,
+ maximumWidth=16)
+
+ l = qt4.QHBoxLayout(spacing=0)
+ l.setContentsMargins(0,0,0,0)
+ l.addWidget( expandbutton )
+ l.addWidget( setnlab )
+ lw = qt4.QWidget()
+ lw.setLayout(l)
+ self.layout.addWidget(lw, row, 0)
+ self.childlist.append(lw)
+
+ # make main control
+ cntrl = slist[0].makeControl(None)
+ self.connect(cntrl, qt4.SIGNAL('settingChanged'),
+ grpdsetting.onSettingChanged)
+ self.layout.addWidget(cntrl, row, 1)
+ self.childlist.append(cntrl)
+
+ row += 1
+
+ # set of controls for remaining settings
+ l = qt4.QGridLayout()
+ grp_row = 0
+ for setn in slist[1:]:
+ cntrl = setn.makeControl(None)
+ if cntrl:
+ lab = SettingLabel(self.document, setn, grpdsetting)
+ l.addWidget(lab, grp_row, 0)
+ self.connect(cntrl, qt4.SIGNAL('settingChanged'),
+ grpdsetting.onSettingChanged)
+ l.addWidget(cntrl, grp_row, 1)
+ grp_row += 1
+
+ grpwidget = qt4.QFrame( frameShape = qt4.QFrame.Panel,
+ frameShadow = qt4.QFrame.Raised,
+ visible=False )
+ grpwidget.setLayout(l)
+
+ def ontoggle(checked):
+ """Toggle button text and make grp visible/invisible."""
+ expandbutton.setText( ("+","-")[checked] )
+ grpwidget.setVisible( checked )
+
+ self.connect(expandbutton, qt4.SIGNAL("toggled(bool)"), ontoggle)
+
+ # add group to standard layout
+ self.layout.addWidget(grpwidget, row, 0, 1, -1)
+ self.childlist.append(grpwidget)
+ row += 1
+ return row
+
+ def updateProperties(self, setnsproxy, title=None, showformatting=True,
onlyformatting=False):
- """Update the list of controls with new ones for the settings."""
+ """Update the list of controls with new ones for the SettingsProxy."""
+
+ # keep a reference to keep it alive
+ self._setnsproxy = setnsproxy
# delete all child widgets
self.setUpdatesEnabled(False)
@@ -60,7 +429,7 @@
c.deleteLater()
del c
- if settings is None:
+ if setnsproxy is None:
self.setUpdatesEnabled(True)
return
@@ -70,71 +439,41 @@
# add a title if requested
if title is not None:
- lab = qt4.QLabel(title[0])
- lab.setFrameShape(qt4.QFrame.Panel)
- lab.setFrameShadow(qt4.QFrame.Sunken)
- lab.setToolTip(title[1])
+ lab = qt4.QLabel(title[0], frameShape=qt4.QFrame.Panel,
+ frameShadow=qt4.QFrame.Sunken, toolTip=title[1])
self.layout.addWidget(lab, row, 0, 1, -1)
row += 1
# add actions if parent is widget
- if settings.parent.isWidget() and not showformatting:
- widget = settings.parent
- for action in widget.actions:
- text = action.name
- if action.usertext:
- text = action.usertext
-
- lab = qt4.QLabel(text)
- self.layout.addWidget(lab, row, 0)
- self.childlist.append(lab)
-
- button = qt4.QPushButton(text)
- button.setToolTip(action.descr)
- # need to save reference to caller object
- button.caller = utils.BoundCaller(self.slotActionPressed,
- action)
- self.connect(button, qt4.SIGNAL('clicked()'), button.caller)
-
- self.layout.addWidget(button, row, 1)
- self.childlist.append(button)
-
- row += 1
+ if setnsproxy.actionsList() and not showformatting:
+ row = self._addActions(setnsproxy, row)
- if settings.getSettingsList() and self.showsubsettings:
+ if setnsproxy.settingsProxyList() and self.showformatsettings:
# if we have subsettings, use tabs
- tabbed = TabbedFormatting(self.document, settings)
+ tabbed = TabbedFormatting(self.document, setnsproxy)
self.layout.addWidget(tabbed, row, 1, 1, 2)
row += 1
self.childlist.append(tabbed)
else:
# else add settings proper as a list
- for setn in settings.getSettingList():
- # skip if not to show formatting
- if not showformatting and setn.formatting:
- continue
- # skip if only to show formatting and not formatting
- if onlyformatting and not setn.formatting:
- continue
-
- cntrl = setn.makeControl(None)
- if cntrl:
- lab = SettingLabel(self.document, setn, None)
- self.layout.addWidget(lab, row, 0)
- self.childlist.append(lab)
-
- self.connect(cntrl, qt4.SIGNAL('settingChanged'),
- self.slotSettingChanged)
- self.layout.addWidget(cntrl, row, 1)
- self.childlist.append(cntrl)
- self.setncntrls[setn.name] = (lab, cntrl)
-
- row += 1
+ for setn in setnsproxy.childProxyList():
+
+ # add setting
+ # only add if formatting setting and formatting allowed
+ # and not formatting and not formatting not allowed
+ if ( isinstance(setn, setting.Setting) and (
+ (setn.formatting and (showformatting or onlyformatting))
+ or (not setn.formatting and not onlyformatting)) and
+ not setn.hidden ):
+ row = self._addControl(setnsproxy, setn, row)
+ elif ( isinstance(setn, SettingsProxy) and
+ setn.setnsmode() == 'groupedsetting' and
+ not onlyformatting ):
+ row = self._addGroupedSettingsControl(setn, row)
# add empty widget to take rest of space
- w = qt4.QWidget()
- w.setSizePolicy(qt4.QSizePolicy.Maximum,
- qt4.QSizePolicy.MinimumExpanding)
+ w = qt4.QWidget( sizePolicy=qt4.QSizePolicy(
+ qt4.QSizePolicy.Maximum, qt4.QSizePolicy.MinimumExpanding) )
self.layout.addWidget(w, row, 0)
self.childlist.append(w)
@@ -148,65 +487,49 @@
if setn in self.setncntrls:
for cntrl in self.setncntrls[setn]:
cntrl.setVisible(vis)
-
- def slotSettingChanged(self, widget, setting, val):
- """Called when a setting is changed by the user.
-
- This updates the setting to the value using an operation so that
- it can be undone.
- """
-
- self.document.applyOperation(document.OperationSettingSet(setting, val))
-
- def slotActionPressed(self, action):
- """Activate the action."""
-
- # find console window, this is horrible: HACK
- win = self
- while not hasattr(win, 'console'):
- win = win.parent()
- console = win.console
-
- console.runFunction(action.function)
class TabbedFormatting(qt4.QTabWidget):
"""Class to have tabbed set of settings."""
- def __init__(self, document, settings, shownames=False):
+ def __init__(self, document, setnsproxy, shownames=False):
qt4.QTabWidget.__init__(self)
+ self.document = document
- if settings is None:
+ if setnsproxy is None:
return
# get list of settings
- setnslist = settings.getSettingsList()
+ self.setnsproxy = setnsproxy
+ setnslist = setnsproxy.settingsProxyList()
# add formatting settings if necessary
- numformat = len( [setn for setn in settings.getSettingList()
+ numformat = len( [setn for setn in setnsproxy.settingList()
if setn.formatting] )
if numformat > 0:
# add on a formatting tab
- setnslist.insert(0, settings)
+ setnslist.insert(0, setnsproxy)
+
+ self.connect( self, qt4.SIGNAL('currentChanged(int)'),
+ self.slotCurrentChanged )
+
+ # subsettings for tabs
+ self.tabsubsetns = []
+
+ # collected titles and tooltips for tabs
+ self.tabtitles = []
+ self.tabtooltips = []
+
+ # tabs which have been initialized
+ self.tabinit = set()
# add tab for each subsettings
for subset in setnslist:
- if subset.name == 'StyleSheet':
+ if subset.setnsmode() not in ('formatting', 'widgetsettings'):
continue
-
- # create tab
- tab = qt4.QWidget()
- layout = qt4.QVBoxLayout()
- layout.setMargin(2)
- tab.setLayout(layout)
-
- # create scrollable area
- scroll = qt4.QScrollArea(None)
- layout.addWidget(scroll)
- scroll.setWidgetResizable(True)
+ self.tabsubsetns.append(subset)
# details of tab
- mainsettings = (subset == settings)
- if mainsettings:
+ if subset is setnsproxy:
# main tab formatting, so this is special
pixmap = 'settings_main'
tabname = title = 'Main'
@@ -214,26 +537,54 @@
else:
# others
if hasattr(subset, 'pixmap'):
- pixmap = subset.pixmap
+ pixmap = subset.pixmap()
else:
pixmap = None
- tabname = subset.name
- tooltip = title = subset.usertext
+ tabname = subset.name()
+ tooltip = title = subset.usertext()
- # create list of properties
- plist = PropertyList(document, showsubsettings=not mainsettings)
- plist.updateProperties(subset, title=(title, tooltip),
- onlyformatting=mainsettings)
- scroll.setWidget(plist)
- plist.show()
-
# hide name in tab
if not shownames:
tabname = ''
- indx = self.addTab(tab, utils.getIcon(pixmap), tabname)
+ self.tabtitles.append(title)
+ self.tabtooltips.append(tooltip)
+
+ # create tab
+ indx = self.addTab(qt4.QWidget(), utils.getIcon(pixmap), tabname)
self.setTabToolTip(indx, tooltip)
+ def slotCurrentChanged(self, tab):
+ """Lazy loading of tab when displayed."""
+ if tab in self.tabinit:
+ # already initialized
+ return
+ self.tabinit.add(tab)
+
+ # settings to show
+ subsetn = self.tabsubsetns[tab]
+ # whether these are the main settings
+ mainsettings = subsetn is self.setnsproxy
+
+ # add this property list to the scroll widget for tab
+ plist = PropertyList(self.document, showformatsettings=not mainsettings)
+ plist.updateProperties(subsetn, title=(self.tabtitles[tab],
+ self.tabtooltips[tab]),
+ onlyformatting=mainsettings)
+
+ # create scrollable area
+ scroll = qt4.QScrollArea()
+ scroll.setWidgetResizable(True)
+ scroll.setWidget(plist)
+
+ # layout for tab widget
+ layout = qt4.QVBoxLayout()
+ layout.setMargin(2)
+ layout.addWidget(scroll)
+
+ # finally use layout containing items for tab
+ self.widget(tab).setLayout(layout)
+
class FormatDock(qt4.QDockWidget):
"""A window for formatting the current widget.
Provides tabbed formatting properties
@@ -248,14 +599,11 @@
self.tabwidget = None
# update our view when the tree edit window selection changes
- self.connect(treeedit, qt4.SIGNAL('widgetSelected'),
- self.selectedWidget)
+ self.connect(treeedit, qt4.SIGNAL('widgetsSelected'),
+ self.selectedWidgets)
- # do initial selection
- self.selectedWidget(treeedit.selwidget)
-
- def selectedWidget(self, widget):
- """Created tabbed widget for formatting for each subsettings."""
+ def selectedWidgets(self, widgets, setnsproxy):
+ """Created tabbed widgets for formatting for each subsettings."""
# get current tab (so we can set it afterwards)
if self.tabwidget:
@@ -268,12 +616,7 @@
self.tabwidget.deleteLater()
self.tabwidget = None
- # create new tabbed widget showing formatting
- settings = None
- if widget is not None:
- settings = widget.settings
-
- self.tabwidget = TabbedFormatting(self.document, settings)
+ self.tabwidget = TabbedFormatting(self.document, setnsproxy)
self.setWidget(self.tabwidget)
# wrap tab from zero to max number
@@ -291,8 +634,8 @@
self.document = document
# update our view when the tree edit window selection changes
- self.connect(treeedit, qt4.SIGNAL('widgetSelected'),
- self.slotWidgetSelected)
+ self.connect(treeedit, qt4.SIGNAL('widgetsSelected'),
+ self.slotWidgetsSelected)
# construct scrollable area
self.scroll = qt4.QScrollArea()
@@ -300,31 +643,23 @@
self.setWidget(self.scroll)
# construct properties list in scrollable area
- self.proplist = PropertyList(document, showsubsettings=False)
+ self.proplist = PropertyList(document, showformatsettings=False)
self.scroll.setWidget(self.proplist)
- # do initial selection
- self.slotWidgetSelected(treeedit.selwidget)
-
- def slotWidgetSelected(self, widget):
- """Update properties when selected widget changes."""
-
- settings = None
- if widget is not None:
- settings = widget.settings
- self.proplist.updateProperties(settings, showformatting=False)
+ def slotWidgetsSelected(self, widgets, setnsproxy):
+ """Update properties when selected widgets change."""
+ self.proplist.updateProperties(setnsproxy, showformatting=False)
class TreeEditDock(qt4.QDockWidget):
- """A window for editing the document as a tree."""
-
- # mime type when widgets are stored on the clipboard
+ """A dock window presenting widgets as a tree."""
- def __init__(self, document, parent):
- qt4.QDockWidget.__init__(self, parent)
- self.parent = parent
+ def __init__(self, document, parentwin):
+ """Initialise dock given document and parent widget."""
+ qt4.QDockWidget.__init__(self, parentwin)
+ self.parentwin = parentwin
self.setWindowTitle("Editing - Veusz")
self.setObjectName("veuszeditingwindow")
- self.selwidget = None
+ self.selwidgets = []
self.document = document
self.connect( self.document, qt4.SIGNAL("sigWiped"),
@@ -336,27 +671,28 @@
# receive change in selection
self.connect(self.treeview.selectionModel(),
- qt4.SIGNAL('selectionChanged(const QItemSelection &, const QItemSelection &)'),
- self.slotTreeItemSelected)
+ qt4.SIGNAL('selectionChanged(const QItemSelection &,'
+ ' const QItemSelection &)'),
+ self.slotTreeItemsSelected)
# set tree as main widget
self.setWidget(self.treeview)
# toolbar to create widgets
self.addtoolbar = qt4.QToolBar("Insert toolbar - Veusz",
- parent)
+ parentwin)
# note wrong description!: backwards compatibility
self.addtoolbar.setObjectName("veuszeditingtoolbar")
# toolbar for editting widgets
self.edittoolbar = qt4.QToolBar("Edit toolbar - Veusz",
- parent)
+ parentwin)
self.edittoolbar.setObjectName("veuszedittoolbar")
self._constructToolbarMenu()
- parent.addToolBarBreak(qt4.Qt.TopToolBarArea)
- parent.addToolBar(qt4.Qt.TopToolBarArea, self.addtoolbar)
- parent.addToolBar(qt4.Qt.TopToolBarArea, self.edittoolbar)
+ parentwin.addToolBarBreak(qt4.Qt.TopToolBarArea)
+ parentwin.addToolBar(qt4.Qt.TopToolBarArea, self.addtoolbar)
+ parentwin.addToolBar(qt4.Qt.TopToolBarArea, self.edittoolbar)
# this sets various things up
self.selectWidget(document.basewidget)
@@ -371,53 +707,78 @@
"""If the document is wiped, reselect root widget."""
self.selectWidget(self.document.basewidget)
- def slotTreeItemSelected(self, current, previous):
+ def slotTreeItemsSelected(self, current, previous):
"""New item selected in tree.
This updates the list of properties
"""
- indexes = current.indexes()
-
- if len(indexes) > 1:
- index = indexes[0]
- self.selwidget = self.treemodel.getWidget(index)
- settings = self.treemodel.getSettings(index)
+ # get selected widgets
+ self.selwidgets = widgets = [
+ self.treemodel.getWidget(idx)
+ for idx in self.treeview.selectionModel().selectedRows() ]
+
+ if len(widgets) == 0:
+ setnsproxy = None
+ elif len(widgets) == 1:
+ setnsproxy = SettingsProxySingle(self.document, widgets[0].settings,
+ actions=widgets[0].actions)
else:
- self.selwidget = None
- settings = None
+ setnsproxy = SettingsProxyMulti(self.document, widgets)
self._enableCorrectButtons()
self._checkPageChange()
- self.emit( qt4.SIGNAL('widgetSelected'), self.selwidget )
+ self.emit( qt4.SIGNAL('widgetsSelected'), self.selwidgets, setnsproxy )
def contextMenuEvent(self, event):
"""Bring up context menu."""
+ # no widgets selected
+ if not self.selwidgets:
+ return
+
m = qt4.QMenu(self)
+
+ # selection
+ m.addMenu(self.parentwin.menus['edit.select'])
+ m.addSeparator()
+
+ # actions on widget(s)
for act in ('edit.cut', 'edit.copy', 'edit.paste',
'edit.moveup', 'edit.movedown', 'edit.delete',
'edit.rename'):
m.addAction(self.vzactions[act])
# allow show or hides of selected widget
- if self.selwidget and 'hide' in self.selwidget.settings:
- m.addSeparator()
- hide = self.selwidget.settings.hide
- act = qt4.QAction( ('Hide object', 'Show object')[hide], m )
- self.connect(act, qt4.SIGNAL('triggered()'),
- (self.slotWidgetHide, self.slotWidgetShow)[hide])
- m.addAction(act)
+ anyhide = False
+ anyshow = False
+ for w in self.selwidgets:
+ if 'hide' in w.settings:
+ if w.settings.hide:
+ anyshow = True
+ else:
+ anyhide = True
- m.exec_(self.mapToGlobal(event.pos()))
+ for (enabled, menutext, showhide) in (
+ (anyhide, 'Hide', True), (anyshow, 'Show', False) ):
+ if enabled:
+ m.addSeparator()
+ act = qt4.QAction(menutext, self)
+ self.connect(act, qt4.SIGNAL('triggered()'),
+ utils.BoundCaller(self.slotWidgetHideShow,
+ self.selwidgets, showhide))
+ m.addAction(act)
+ m.exec_(self.mapToGlobal(event.pos()))
event.accept()
def _checkPageChange(self):
"""Check to see whether page has changed."""
- w = self.selwidget
+ w = None
+ if self.selwidgets:
+ w = self.selwidgets[0]
while w is not None and not isinstance(w, widgets.Page):
w = w.parent
@@ -432,7 +793,9 @@
def _enableCorrectButtons(self):
"""Make sure the create graph buttons are correctly enabled."""
- selw = self.selwidget
+ selw = None
+ if self.selwidgets:
+ selw = self.selwidgets[0]
# has to be visible if is to be enabled (yuck)
nonorth = self.vzactions['add.nonorthpoint'].setVisible(True)
@@ -453,8 +816,9 @@
self.vzactions['add.nonorthfunc'].setVisible(nonorth)
self.vzactions['add.function'].setVisible(not nonorth)
- # certain actions shouldn't allow root to be deleted
- isnotroot = not isinstance(selw, widgets.Root)
+ # certain actions shouldn't work on root
+ isnotroot = not any([isinstance(w, widgets.Root)
+ for w in self.selwidgets])
for act in ('edit.cut', 'edit.copy', 'edit.delete',
'edit.moveup', 'edit.movedown', 'edit.rename'):
@@ -470,13 +834,13 @@
self.edittoolbar.setIconSize( qt4.QSize(iconsize, iconsize) )
self.addslots = {}
- self.vzactions = actions = self.parent.vzactions
+ self.vzactions = actions = self.parentwin.vzactions
for widgettype in ('page', 'grid', 'graph', 'axis',
'xy', 'bar', 'fit', 'function', 'boxplot',
'image', 'contour', 'vectorfield',
'key', 'label', 'colorbar',
'rect', 'ellipse', 'imagefile',
- 'line', 'polygon', 'polar',
+ 'line', 'polygon', 'polar', 'ternary',
'nonorthpoint', 'nonorthfunc'):
wc = document.thefactory.getWidgetClass(widgettype)
@@ -493,31 +857,31 @@
a = utils.makeAction
actions.update({
'edit.cut':
- a(self, 'Cut the selected item', 'Cu&t',
+ a(self, 'Cut the selected widget', 'Cu&t',
self.slotWidgetCut,
icon='veusz-edit-cut', key='Ctrl+X'),
'edit.copy':
- a(self, 'Copy the selected item', '&Copy',
+ a(self, 'Copy the selected widget', '&Copy',
self.slotWidgetCopy,
icon='kde-edit-copy', key='Ctrl+C'),
'edit.paste':
- a(self, 'Paste item from the clipboard', '&Paste',
+ a(self, 'Paste widget from the clipboard', '&Paste',
self.slotWidgetPaste,
icon='kde-edit-paste', key='Ctrl+V'),
'edit.moveup':
- a(self, 'Move the selected item up', 'Move &up',
+ a(self, 'Move the selected widget up', 'Move &up',
utils.BoundCaller(self.slotWidgetMove, -1),
icon='kde-go-up'),
'edit.movedown':
- a(self, 'Move the selected item down', 'Move d&own',
+ a(self, 'Move the selected widget down', 'Move d&own',
utils.BoundCaller(self.slotWidgetMove, 1),
icon='kde-go-down'),
'edit.delete':
- a(self, 'Remove the selected item', '&Delete',
+ a(self, 'Remove the selected widget', '&Delete',
self.slotWidgetDelete,
icon='kde-edit-delete'),
'edit.rename':
- a(self, 'Renames the selected item', '&Rename',
+ a(self, 'Renames the selected widget', '&Rename',
self.slotWidgetRename,
icon='kde-edit-rename'),
@@ -533,7 +897,7 @@
'xy', 'nonorthpoint', 'bar', 'fit',
'function', 'nonorthfunc', 'boxplot',
'image', 'contour', 'vectorfield',
- 'key', 'label', 'colorbar', 'polar')]
+ 'key', 'label', 'colorbar', 'polar', 'ternary')]
menuitems = [
('insert', '', addact + [
@@ -547,14 +911,14 @@
'edit.delete', 'edit.rename'
]),
]
- utils.constructMenus( self.parent.menuBar(),
- self.parent.menus,
+ utils.constructMenus( self.parentwin.menuBar(),
+ self.parentwin.menus,
menuitems,
actions )
# create shape toolbar button
# attach menu to insert shape button
- actions['add.shapemenu'].setMenu(self.parent.menus['insert.shape'])
+ actions['add.shapemenu'].setMenu(self.parentwin.menus['insert.shape'])
# add actions to toolbar to create widgets
utils.addToolbarActions(self.addtoolbar, actions,
@@ -566,6 +930,9 @@
'edit.moveup', 'edit.movedown',
'edit.delete', 'edit.rename'))
+ self.connect( self.parentwin.menus['edit.select'],
+ qt4.SIGNAL('aboutToShow()'), self.updateSelectMenu )
+
def slotMakeWidgetButton(self, wc):
"""User clicks button to make widget."""
self.makeWidget(wc.typename)
@@ -582,9 +949,9 @@
"""
# if no widget selected, bomb out
- if self.selwidget is None:
+ if not self.selwidgets:
return
- parent = document.getSuitableParent(widgettype, self.selwidget)
+ parent = document.getSuitableParent(widgettype, self.selwidgets[0])
assert parent is not None
@@ -607,8 +974,8 @@
def slotWidgetCopy(self):
"""Copy selected widget to the clipboard."""
- if self.selwidget:
- mimedata = document.generateWidgetsMime([self.selwidget])
+ if self.selwidgets:
+ mimedata = document.generateWidgetsMime(self.selwidgets)
clipboard = qt4.QApplication.clipboard()
clipboard.setMimeData(mimedata)
@@ -617,7 +984,10 @@
selected widget? If so, enable paste button"""
data = document.getClipboardWidgetMime()
- show = document.isMimePastable(self.selwidget, data)
+ if len(self.selwidgets) == 0:
+ show = False
+ else:
+ show = document.isWidgetMimePastable(self.selwidgets[0], data)
self.vzactions['edit.paste'].setEnabled(show)
def doInitialWidgetSelect(self):
@@ -637,7 +1007,7 @@
data = document.getClipboardWidgetMime()
if data:
- op = document.OperationWidgetPaste(self.selwidget, data)
+ op = document.OperationWidgetPaste(self.selwidgets[0], data)
widgets = self.document.applyOperation(op)
if widgets:
self.selectWidget(widgets[0])
@@ -645,28 +1015,32 @@
def slotWidgetDelete(self):
"""Delete the widget selected."""
- # no item selected, so leave
- w = self.selwidget
- if w is None:
+ widgets = self.selwidgets
+ # if no item selected, leave
+ if not widgets:
return
# get list of widgets in order
widgetlist = []
self.document.basewidget.buildFlatWidgetList(widgetlist)
- widgetnum = widgetlist.index(w)
- assert widgetnum >= 0
+ # find indices of widgets to be deleted - find one to select after
+ indexes = [widgetlist.index(w) for w in widgets]
+ if -1 in indexes:
+ raise RuntimeError, "Invalid widget in list of selected widgets"
+ minindex = min(indexes)
# delete selected widget
- self.document.applyOperation( document.OperationWidgetDelete(w) )
+ self.document.applyOperation(
+ document.OperationWidgetsDelete(widgets))
# rebuild list
widgetlist = []
self.document.basewidget.buildFlatWidgetList(widgetlist)
# find next to select
- if widgetnum < len(widgetlist):
- nextwidget = widgetlist[widgetnum]
+ if minindex < len(widgetlist):
+ nextwidget = widgetlist[minindex]
else:
nextwidget = widgetlist[-1]
@@ -686,8 +1060,13 @@
listview."""
index = self.treemodel.getWidgetIndex(widget)
- self.treeview.scrollTo(index)
- self.treeview.setCurrentIndex(index)
+ if index is not None:
+ self.treeview.scrollTo(index)
+ self.treeview.selectionModel().select(
+ index, qt4.QItemSelectionModel.Clear |
+ qt4.QItemSelectionModel.Current |
+ qt4.QItemSelectionModel.Rows |
+ qt4.QItemSelectionModel.Select )
def slotWidgetMove(self, direction):
"""Move the selected widget up/down in the hierarchy.
@@ -696,26 +1075,79 @@
direction is -1 for 'up' and +1 for 'down'
"""
+ if not self.selwidgets:
+ return
# widget to move
- w = self.selwidget
+ w = self.selwidgets[0]
# actually move the widget
self.document.applyOperation(
document.OperationWidgetMoveUpDown(w, direction) )
- # rehilight moved widget
+ # re-highlight moved widget
self.selectWidget(w)
- def slotWidgetHide(self):
- """Hide the selected widget."""
- self.document.applyOperation(
- document.OperationSettingSet(self.selwidget.settings.get('hide'),
- True) )
- def slotWidgetShow(self):
- """Show the selected widget."""
+ def slotWidgetHideShow(self, widgets, hideshow):
+ """Hide or show selected widgets.
+ hideshow is True for hiding, False for showing
+ """
+ ops = [ document.OperationSettingSet(w.settings.get('hide'), hideshow)
+ for w in widgets ]
+ descr = ('show', 'hide')[hideshow]
self.document.applyOperation(
- document.OperationSettingSet(self.selwidget.settings.get('hide'),
- False) )
+ document.OperationMultiple(ops, descr=descr))
+
+ def checkWidgetSelected(self):
+ """Check widget is selected."""
+ if len(self.treeview.selectionModel().selectedRows()) == 0:
+ self.selectWidget(self.document.basewidget)
+
+ def _selectWidgetsTypeAndOrName(self, wtype, wname):
+ """Select widgets with type or name given.
+ Give None if you don't care for either."""
+ def selectwidget(path, w):
+ """Select widget if of type or name given."""
+ if ( (wtype is None or w.typename == wtype) and
+ (wname is None or w.name == wname) ):
+ idx = self.treemodel.getWidgetIndex(w)
+ self.treeview.selectionModel().select(
+ idx, qt4.QItemSelectionModel.Select |
+ qt4.QItemSelectionModel.Rows)
+
+ self.document.walkNodes(selectwidget, nodetypes=('widget',))
+
+ def _selectWidgetSiblings(self, w, wtype):
+ """Select siblings of widget given with type."""
+ for c in w.parent.children:
+ if c is not w and c.typename == wtype:
+ idx = self.treemodel.getWidgetIndex(c)
+ self.treeview.selectionModel().select(
+ idx, qt4.QItemSelectionModel.Select |
+ qt4.QItemSelectionModel.Rows)
+
+ def updateSelectMenu(self):
+ """Update edit.select menu."""
+ menu = self.parentwin.menus['edit.select']
+ menu.clear()
+
+ if len(self.selwidgets) == 0:
+ return
+
+ wtype = self.selwidgets[0].typename
+ name = self.selwidgets[0].name
+
+ menu.addAction(
+ "All '%s' widgets" % wtype,
+ lambda: self._selectWidgetsTypeAndOrName(wtype, None))
+ menu.addAction(
+ "Siblings of '%s' with type '%s'" % (name, wtype),
+ lambda: self._selectWidgetSiblings(self.selwidgets[0], wtype))
+ menu.addAction(
+ "All '%s' widgets called '%s'" % (wtype, name),
+ lambda: self._selectWidgetsTypeAndOrName(wtype, name))
+ menu.addAction(
+ "All widgets called '%s'" % name,
+ lambda: self._selectWidgetsTypeAndOrName(None, name))
class SettingLabel(qt4.QWidget):
"""A label to describe a setting.
@@ -724,16 +1156,17 @@
access to the context menu
"""
- def __init__(self, document, setting, parent):
+ def __init__(self, document, setting, setnsproxy):
"""Initialise button, passing document, setting, and parent widget."""
- qt4.QWidget.__init__(self, parent)
+ qt4.QWidget.__init__(self)
self.setFocusPolicy(qt4.Qt.StrongFocus)
self.document = document
self.connect(document, qt4.SIGNAL('sigModified'), self.slotDocModified)
self.setting = setting
+ self.setnsproxy = setnsproxy
self.layout = qt4.QHBoxLayout(self)
self.layout.setMargin(2)
@@ -786,9 +1219,10 @@
self.setToolTip(tooltip)
# if not default, make label bold
- bold = not self.setting.isDefault()
f = qt4.QFont(self.labelicon.font())
- f.setBold(bold)
+ multivalued = self.setnsproxy.multivalued(self.setting.name)
+ f.setBold( (not self.setting.isDefault()) or multivalued )
+ f.setItalic( multivalued )
self.labelicon.setFont(f)
def updateHighlight(self):
@@ -911,10 +1345,7 @@
def actionResetDefault(self):
"""Reset setting to default."""
- self.document.applyOperation(
- document.OperationSettingSet(
- self.setting,
- self.setting.default))
+ self.setnsproxy.resetToDefault(self.setting.name)
def actionCopyTypedWidgets(self):
"""Copy setting to widgets of same type."""
@@ -935,37 +1366,11 @@
widgetname=
self._clickwidget.name) )
- def actionDefaultTyped(self):
- """Make default for widgets with the same type."""
- self.setting.setAsDefault(False)
-
- def actionDefaultTypedNamed(self):
- """Make default for widgets with the same name and type."""
- self.setting.setAsDefault(True)
-
- def actionDefaultForget(self):
- """Forget any default setting."""
- self.setting.removeDefault()
-
def actionUnlinkSetting(self):
"""Unlink the setting if it is a reference."""
self.document.applyOperation(
document.OperationSettingSet(self.setting, self.setting.get()) )
- def actionEditLinkedSetting(self):
- """Edit the linked setting rather than the setting."""
-
- realsetn = self.setting.getReference().resolve(self.setting)
- widget = realsetn
- while not isinstance(widget, widgets.Widget) and widget is not None:
- widget = widget.parent
-
- # need to select widget, so need to find treeditwindow :-(
- window = self
- while not hasattr(window, 'treeedit'):
- window = window.parent()
- window.treeedit.selectWidget(widget)
-
def actionSetStyleSheet(self):
"""Use the setting as the default in the stylesheet."""
@@ -979,4 +1384,3 @@
self.setting.default) ],
descr="make default style")
)
-
diff -Nru veusz-1.10/windows/tutorial.py veusz-1.14/windows/tutorial.py
--- veusz-1.10/windows/tutorial.py 1970-01-01 00:00:00.000000000 +0000
+++ veusz-1.14/windows/tutorial.py 2011-11-22 20:23:31.000000000 +0000
@@ -0,0 +1,855 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2011 Jeremy S. Sanders
+# Email: Jeremy Sanders
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+###############################################################################
+
+import os.path
+
+import veusz.qtall as qt4
+import veusz.utils as utils
+import veusz.setting as setting
+
+class TutorialStep(qt4.QObject):
+ def __init__(self, text, mainwin,
+ nextstep=None, flash=None,
+ disablenext=False,
+ closestep=False,
+ nextonsetting=None,
+ nextonselected=None):
+
+ """
+ nextstep is class next TutorialStep class to use
+ If flash is set, flash widget
+ disablenext: wait until nextStep is emitted before going to next slide
+ closestep: add a close button
+ nextonsetting: (setnpath, lambda val: ok) -
+ check setting to go to next slide
+ nextonselected: go to next if widget with name is selected
+ """
+
+ qt4.QObject.__init__(self)
+ self.text = text
+ self.nextstep = nextstep
+ self.flash = flash
+ self.disablenext = disablenext
+ self.closestep = closestep
+ self.mainwin = mainwin
+
+ self.nextonsetting = nextonsetting
+ if nextonsetting is not None:
+ self.connect( mainwin.document,
+ qt4.SIGNAL('sigModified'), self.slotNextSetting )
+
+ self.nextonselected = nextonselected
+ if nextonselected is not None:
+ self.connect(mainwin.treeedit, qt4.SIGNAL('widgetsSelected'),
+ self.slotWidgetsSelected)
+
+ def slotNextSetting(self, *args):
+ """Check setting to emit next."""
+ try:
+ setn = self.mainwin.document.basewidget.prefLookup(
+ self.nextonsetting[0]).get()
+ if self.nextonsetting[1](setn):
+ self.emit( qt4.SIGNAL('nextStep') )
+ except ValueError:
+ pass
+
+ def slotWidgetsSelected(self, widgets, *args):
+ """Go to next page if widget selected."""
+ if len(widgets) == 1 and widgets[0].name == self.nextonselected:
+ self.emit( qt4.SIGNAL('nextStep') )
+
+##########################
+## Introduction to widgets
+
+class StepIntro(TutorialStep):
+ def __init__(self, mainwin):
+ TutorialStep.__init__(
+ self, '''
+
Welcome to Veusz!
+
+
This tutorial aims to get you working with Veusz as quickly as
+possible.
+
+
You can close this tutorial at any time using the close button to
+the top-right of this panel. The tutorial can be replayed in the help
+menu.
Plots in Veusz are constructed from widgets. Different
+types of widgets are used to make different parts of a plot. For
+example, there are widgets for axes, for a graph, for plotting data
+and for plotting functions.
+
+
There are also special widgets. The grid widget arranges graphs
+inside it in a grid arrangement.
Widget can often be placed inside each other. For instance, a graph
+widget is placed in a page widget or a grid widget. Plotting widgets
+are placed in graph widget.
+
+
You can have multiple widgets of different types. For example, you
+can have several graphs on the page, optionally arranged in a
+grid. Several plotting widgets and axis widgets can be put in a
+graph.
The flashing window is the Editing window, which shows the widgets
+currently in the plot in a hierarchical tree. Each widget has a name
+(the left column) and a type (the right column).
Notice that the x axis label of your plot has now been updated.
+Veusz supports LaTeX style formatting for labels, so you could include
+superscripts, subscripts and fractions.
+
+
Other important axis properties include the minimum, maximum values
+of the axis and whether the axis is logarithmic.
The flashing Add Widget toolbar and the Insert menu add widgets to
+the document. New widgets are inserted in the currently selected
+widget, if possible, or its parents.
+
+
Hold your mouse pointer over one of the toolbar buttons to
+see a description of a widget type.
Widgets have a number of formatting options. The Formatting window
+(flashing) shows the options for the currently selected widget, here
+the function widget.
Different types of formatting properties are grouped under separate
+tables. The options for drawing the function line are grouped under
+the flashing Line tab (%s).
Many widgets in Veusz plot datasets. Datasets can be imported from
+files, entered manually or created from existing datasets using
+operations or expressions.
+
+
Imported data can be linked to an external file or embedded in the
+document.
Click the flashing Data Import icon, or choose
+"Import..." From the Data menu.
+''', mainwin,
+ flash=mainwin.datatoolbar.widgetForAction(
+ mainwin.vzactions['data.import']),
+ disablenext=True,
+ nextstep=DataImportDialog)
+
+ # make sure we have the default delimiters
+ for k in ( 'importdialog_csvdelimitercombo_HistoryCombo',
+ 'importdialog_csvtextdelimitercombo_HistoryCombo' ):
+ if k in setting.settingdb:
+ del setting.settingdb[k]
+
+ self.connect(mainwin, qt4.SIGNAL('dialogShown'), self.slotDialogShown )
+
+ def slotDialogShown(self, dialog):
+ """Called when a dialog is opened in the main window."""
+ from veusz.dialogs.importdialog import ImportDialog
+ if isinstance(dialog, ImportDialog):
+ # make life easy by sticking in filename
+ dialog.filenameedit.setText(
+ os.path.join(utils.exampleDirectory, 'tutorialdata.csv'))
+ # and choosing tab
+ dialog.guessImportTab()
+ # get rid of existing values
+ self.emit( qt4.SIGNAL('nextStep') )
+
+class DataImportDialog(TutorialStep):
+ def __init__(self, mainwin):
+ TutorialStep.__init__(
+ self, '''
+
This is the data import dialog. In this tutorial, we have selected
+an example CSV (comma separated value) file for you, but you would
+normally browse to find your data file.
+
+
This example file defines three datasets, alpha, beta
+and gamma, entered as columns in the CSV file.
Veusz will try to guess the datatype - numeric, text or date - from
+the data in the file or you can specify it manually.
+
+
Several different data formats are supported in Veusz and plugins
+can be defined to import any data format. The Link option links data
+to the original file.
Notice how Veusz has loaded the three different datasets from the
+file. You could carry on importing new datasets from the Import dialog
+box or reopen it later.
+
+
Close the Import dialog box.
+''', mainwin,
+ disablenext=True,
+ nextstep=DataImportDialog4)
+
+ self.timer = qt4.QTimer()
+ self.connect( self.timer, qt4.SIGNAL('timeout()'),
+ self.slotTimeout )
+ self.timer.start(200)
+
+ def slotTimeout(self):
+ from veusz.dialogs.importdialog import ImportDialog
+ closed = True
+ for dialog in self.mainwin.dialogs:
+ if isinstance(dialog, ImportDialog):
+ closed = False
+ if closed:
+ # move forward if no import dialog open
+ self.emit( qt4.SIGNAL('nextStep') )
+
+class DataImportDialog4(TutorialStep):
+ def __init__(self, mainwin):
+ TutorialStep.__init__(
+ self, '''
+
The Data viewing window (flashing) shows the currently loaded
+datasets in the document.
+
+
Hover your mouse over datasets to get information about them. You
+can see datasets in more detail in the Data Edit dialog box.